diff options
Diffstat (limited to 'lib/plugins/stonith')
58 files changed, 22524 insertions, 0 deletions
diff --git a/lib/plugins/stonith/Makefile.am b/lib/plugins/stonith/Makefile.am new file mode 100644 index 0000000..01f2f4a --- /dev/null +++ b/lib/plugins/stonith/Makefile.am @@ -0,0 +1,216 @@ +# +# stonith: STONITH plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = external + +INCFILES = stonith_expect_helpers.h \ + stonith_plugin_common.h \ + stonith_signal.h \ + stonith_config_xml.h + +idir=$(includedir)/stonith + +i_HEADERS = $(INCFILES) + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @NON_FATAL_CFLAGS@ + +# +# We need "vacmclient_api.h" and -lvacmclient to make the VACM +# plugin work. +# +if USE_VACM +vacm_LIB = vacm.la +else +vacm_LIB = +endif + +# +# We need <ucd-snmp/asn1.h>, <ucd-snmp/snmp_api.h>, <ucd-snmp/snmp.h> +# <ucd-snmp/snmp_client.h>, <ucd-snmp/mib.h>, -lsnmp and -lcrypto +# for the apcmastersnmp plugin to work +# + +if USE_APC_SNMP +apcmastersnmp_LIB = apcmastersnmp.la wti_mpc.la +else +apcmastersnmp_LIB = +endif +if IPMILAN_BUILD +OPENIPMI_LIB = -lOpenIPMI -lOpenIPMIposix -lOpenIPMIutils +libipmilan_LIB = libipmilan.la +ipmilan_LIB = ipmilan.la +ipmilan_TEST = ipmilantest +else +OPENIPMI_LIB = +libipmilan_LIB = +ipmilan_LIB = +endif +# +# We need <curl/curl.h>, <libxml/xmlmemory.h>, +# <libxml/parser.h>, <libxml/xpath.h>, +# -lcurl and -lxml2 for the drac3 plugin to work +# +if USE_DRAC3 +drac3_LIB = drac3.la +else +drac3_LIB = +endif + +# +# We need OpenHPI to make the IBM BladeCenter plugin work. +# +if USE_OPENHPI +bladehpi_LIB = bladehpi.la +else +bladehpi_LIB = +endif + +noinst_PROGRAMS = $(ipmilan_TEST) +ipmilantest_SOURCES = ipmilan_test.c +ipmilantest_LDADD = $(libipmilan_LIB) \ + $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/lib/stonith/libstonith.la \ + $(OPENIPMI_LIB) + +## libraries + +plugindir = $(stonith_plugindir)/stonith2 + +plugin_LTLIBRARIES = apcmaster.la \ + $(apcmastersnmp_LIB) \ + apcsmart.la \ + baytech.la \ + $(bladehpi_LIB) \ + cyclades.la \ + $(drac3_LIB) \ + external.la \ + rhcs.la \ + ibmhmc.la \ + $(ipmilan_LIB) \ + meatware.la \ + null.la \ + nw_rpc100s.la \ + rcd_serial.la \ + rps10.la \ + ssh.la \ + suicide.la \ + $(vacm_LIB) \ + wti_nps.la +noinst_LTLIBRARIES = $(libipmilan_LIB) + +apcmaster_la_SOURCES = apcmaster.c $(INCFILES) +apcmaster_la_LDFLAGS = -export-dynamic -module -avoid-version +apcmaster_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcmastersnmp_la_SOURCES= apcmastersnmp.c $(INCFILES) +apcmastersnmp_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +apcmastersnmp_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcsmart_la_SOURCES = apcsmart.c $(INCFILES) +apcsmart_la_LDFLAGS = -export-dynamic -module -avoid-version +apcsmart_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +baytech_la_SOURCES = baytech.c $(INCFILES) +baytech_la_LDFLAGS = -export-dynamic -module -avoid-version +baytech_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +bladehpi_la_SOURCES = bladehpi.c $(INCFILES) +bladehpi_la_LDFLAGS = -export-dynamic -module -avoid-version +bladehpi_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) -lopenhpi + +cyclades_la_SOURCES = cyclades.c $(INCFILES) +cyclades_la_LDFLAGS = -export-dynamic -module -avoid-version +cyclades_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +drac3_la_SOURCES = drac3.c drac3_command.c drac3_command.h drac3_hash.c drac3_hash.h $(INCFILES) +drac3_la_LDFLAGS = -export-dynamic -module -avoid-version +drac3_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la -lcurl -lxml2 $(GLIBLIB) + +external_la_SOURCES = external.c $(INCFILES) +external_la_LDFLAGS = -export-dynamic -module -avoid-version +external_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +rhcs_la_SOURCES = rhcs.c $(INCFILES) +rhcs_la_LDFLAGS = -export-dynamic -module -avoid-version +rhcs_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +ibmhmc_la_SOURCES = ibmhmc.c $(INCFILES) +ibmhmc_la_LDFLAGS = -export-dynamic -module -avoid-version +ibmhmc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +ipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +ipmilan_la_LDFLAGS = -export-dynamic -module -avoid-version +ipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +libipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +libipmilan_la_LDFLAGS = -version-info 1:0:0 +libipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +meatware_la_SOURCES = meatware.c $(INCFILES) +meatware_la_LDFLAGS = -export-dynamic -module -avoid-version +meatware_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +null_la_SOURCES = null.c $(INCFILES) +null_la_LDFLAGS = -export-dynamic -module -avoid-version +null_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +nw_rpc100s_la_SOURCES = nw_rpc100s.c $(INCFILES) +nw_rpc100s_la_LDFLAGS = -export-dynamic -module -avoid-version +nw_rpc100s_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +rcd_serial_la_SOURCES = rcd_serial.c $(INCFILES) +rcd_serial_la_LDFLAGS = -export-dynamic -module -avoid-version +rcd_serial_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +rps10_la_SOURCES = rps10.c $(INCFILES) +rps10_la_LDFLAGS = -export-dynamic -module -avoid-version +rps10_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +ssh_la_SOURCES = ssh.c $(INCFILES) +ssh_la_LDFLAGS = -export-dynamic -module -avoid-version +ssh_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +vacm_la_SOURCES = vacm.c $(INCFILES) +vacm_la_LDFLAGS = -export-dynamic -module -avoid-version +vacm_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +wti_nps_la_SOURCES = wti_nps.c $(INCFILES) +wti_nps_la_LDFLAGS = -export-dynamic -module -avoid-version +wti_nps_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +wti_mpc_la_SOURCES= wti_mpc.c $(INCFILES) +wti_mpc_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +wti_mpc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +suicide_la_SOURCES = suicide.c $(INCFILES) +suicide_la_LDFLAGS = -export-dynamic -module -avoid-version +suicide_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +stonithscriptdir = $(stonith_plugindir)/stonith2 + +stonithscript_SCRIPTS = ribcl.py diff --git a/lib/plugins/stonith/apcmaster.c b/lib/plugins/stonith/apcmaster.c new file mode 100644 index 0000000..09a56d3 --- /dev/null +++ b/lib/plugins/stonith/apcmaster.c @@ -0,0 +1,822 @@ +/* +* +* 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. + */ + +#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 * const * 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; + } + 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 * const * +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)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.c b/lib/plugins/stonith/apcmastersnmp.c new file mode 100644 index 0000000..a9eeaeb --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.c @@ -0,0 +1,890 @@ +/* + * Stonith module for APC Masterswitch (SNMP) + * Copyright (c) 2001 Andreas Piesk <a.piesk@gmx.net> + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * 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> + +/* device ID */ +#define DEVICE "APC MasterSwitch (SNMP)" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include <net-snmp/net-snmp-config.h> +# include <net-snmp/net-snmp-includes.h> +# include <net-snmp/agent/net-snmp-agent-includes.h> +# define INIT_AGENT() init_master_agent() +#else +# include <ucd-snmp/ucd-snmp-config.h> +# include <ucd-snmp/ucd-snmp-includes.h> +# include <ucd-snmp/ucd-snmp-agent-includes.h> +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN apcmastersnmp +#define PIL_PLUGIN_S "apcmastersnmp" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * apcmastersnmp_new(const char *); +static void apcmastersnmp_destroy(StonithPlugin *); +static const char * const * apcmastersnmp_get_confignames(StonithPlugin *); +static int apcmastersnmp_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmastersnmp_getinfo(StonithPlugin * s, int InfoType); +static int apcmastersnmp_status(StonithPlugin * ); +static int apcmastersnmp_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmastersnmp_hostlist(StonithPlugin *); + +static struct stonith_ops apcmastersnmpOps ={ + apcmastersnmp_new, /* Create new STONITH object */ + apcmastersnmp_destroy, /* Destroy STONITH object */ + apcmastersnmp_getinfo, /* Return STONITH info string */ + apcmastersnmp_get_confignames, /* Get configuration parameters */ + apcmastersnmp_set_config, /* Set configuration */ + apcmastersnmp_status, /* Return STONITH device status */ + apcmastersnmp_reset_req, /* Request a reset */ + apcmastersnmp_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; + +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; + DEBUGCALL; + + 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 + , &apcmastersnmpOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 1 +#define OUTLET_OFF 2 +#define OUTLET_REBOOT 3 +#define OUTLET_NO_CMD_PEND 2 + +/* oids */ +#define OID_IDENT ".1.3.6.1.4.1.318.1.1.12.1.5.0" +#define OID_NUM_OUTLETS ".1.3.6.1.4.1.318.1.1.12.1.8.0" +#define OID_OUTLET_NAMES ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.2.%i" +#define OID_OUTLET_STATE ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%i" +#define OID_OUTLET_COMMAND_PENDING ".1.3.6.1.4.1.318.1.1.12.3.5.1.1.5.%i" +#define OID_OUTLET_REBOOT_DURATION ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.6.%i" + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* for checking hardware (issue a warning if mismatch) */ +static const char* APC_tested_ident[] = {"AP9606","AP7920","AP7921","AP7900","AP_other_well_tested"}; + +/* constant strings */ +static const char *pluginid = "APCMS-SNMP-Stonith"; +static const char *NOTpluginID = "APCMS SNMP device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void APC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *APC_open(char *hostname, int port +, char *community); +static void *APC_read(struct snmp_session *sptr, const char *objname +, int type); +static int APC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +APC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +APC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + APC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +APC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +APC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +apcmastersnmp_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + int i; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = APC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* issue a warning if ident mismatches */ + for(i=DIMOF(APC_tested_ident) -1; i >=0 ; i--) { + if (strcmp(ident, APC_tested_ident[i]) == 0) { + break; + } + } + + if (i<0) { + LOG(PIL_WARN + , "%s: module not tested with this hardware '%s'." + , __FUNCTION__, ident); + } + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcmastersnmp_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, j + 1); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +apcmastersnmp_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int expect_state = OUTLET_ON; + int i, h, num_outlets, outlet, reboot_duration, *state, bad_outlets; + int outlets[8]; /* Assume that one node is connected to a + maximum of 8 outlets */ + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + num_outlets = 0; + reboot_duration = 0; + bad_outlets = 0; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, outlet); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + /* Check that the outlet is not administratively down */ + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + + /* get outlet's state */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + /* prepare oid */ + snprintf(objname, MAX_STRING, OID_OUTLET_REBOOT_DURATION + , outlet); + + /* read reboot duration of the port */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read reboot duration for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (num_outlets == 0) { + /* save the inital value of the first port */ + reboot_duration = *state; + } else if (reboot_duration != *state) { + LOG(PIL_WARN, "%s: outlet %d has a different reboot duration!" + , __FUNCTION__, outlet); + if (reboot_duration < *state) + reboot_duration = *state; + } + + /* Ok, add it to the list of outlets to control */ + outlets[num_outlets]=outlet; + num_outlets++; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (num_outlets < 1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + expect_state = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + expect_state = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + expect_state = OUTLET_ON; + break; + default: break; + } + + /* Turn them all off */ + + for (outlet=outlets[0], i=0 ; i < num_outlets; i++, outlet = outlets[i]) { + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_COMMAND_PENDING, outlet); + + /* are there pending commands ? */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read pending commands for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != OUTLET_NO_CMD_PEND) { + LOG(PIL_CRIT, "%s: command pending.", __FUNCTION__); + return (S_RESETFAIL); + } + + /* prepare objnames */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!APC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + } + + /* wait max. 2*reboot_duration for all outlets to go back on */ + for (i = 0; i < reboot_duration << 1; i++) { + + sleep(1); + + bad_outlets = 0; + for (outlet=outlets[0], h=0 ; h < num_outlets; h++, + outlet = outlets[h]) { + + /* prepare objname of the first outlet */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + /* get outlet's state */ + + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != expect_state) + bad_outlets++; + } + + if (bad_outlets == 0) + return (S_OK); + } + + if (bad_outlets == num_outlets) { + /* reset failed */ + LOG(PIL_CRIT, "%s: stonith operation for '%s' failed." + , __FUNCTION__, host); + return (S_RESETFAIL); + } else { + /* Not all outlets back on, but at least one; implies node was */ + /* rebooted correctly */ + LOG(PIL_WARN,"%s: Not all outlets in the expected state!" + , __FUNCTION__); + return (S_OK); + } +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +apcmastersnmp_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +apcmastersnmp_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + int * i; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("apcmastersnmp"); + + /* now try to get a snmp session */ + if ((sd->sptr = APC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of outlets from the masterswitch */ + if ((i = APC_read(sd->sptr, OID_NUM_OUTLETS, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + /* store the number of outlets */ + sd->num_outlets = *i; + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +apcmastersnmp_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via SNMP)\n" + "The APC MasterSwitch can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +apcmastersnmp_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcmastersnmp_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcmastersnmpOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.cfg.example b/lib/plugins/stonith/apcmastersnmp.cfg.example new file mode 100644 index 0000000..76fea08 --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.cfg.example @@ -0,0 +1,39 @@ +# +# this is an example config for the stonith module apcmastersnmp +# +# 1. what does the fields on the line mean ? +# +# all parameters must be given on a single line. blank lines and lines +# starting with '#' are ignored. only the first not ignored line will +# be processed. all subsequent lines will be ignored. the different +# fields must be seperated by white-spaces (blanks and/or tabs). +# +# the first field is the either the hostname or the ip address. the +# hostname must be resolvable. the second fields specifies the snmp port +# the masterswitch is listening. for snmp the default is 161. the last +# field contains the so called 'community' string. this must be the same +# as the one in the masterswitch configuration. +# +# +# 2. how must the masterswitch be configured ? +# +# as said above, the community string must be set to the same value entered +# in this config. the different outlets must be named after the connected +# hosts. that means, the outlet names must be the same as the node names +# in /etc/ha.d/ha.cf. the reset values should be set to reasonable values. +# +# the module DON'T configure the module in any way! +# +# +# 3. how does the module work ? +# +# in case of a stonith the module receives the nodename of the host, which +# should be reset. the module looks up this nodename in the list of outlet +# names. that's why the names must be identical (see 2.). if it finds the +# name, it'll reset the appropriate outlet using the configured values +# (eg. delay, duration). then the module waits for the outlet to coming +# up. if it comes up, a successful stonith will be reported back. otherwise +# the stonith failed and a failure code will be returned. +# + +192.168.1.110 161 private diff --git a/lib/plugins/stonith/apcsmart.c b/lib/plugins/stonith/apcsmart.c new file mode 100644 index 0000000..18d1612 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.c @@ -0,0 +1,1028 @@ +/* + * Stonith module for APCSmart Stonith device + * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.net> + * 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 + * + * Original version of this UPS code was taken from: + * 'Network UPS Tools' by Russell Kroll <rkroll@exploits.org> + * homepage: http://www.networkupstools.org/ + * + * Significantly mangled by Alan Robertson <alanr@unix.sh> + */ + +#include <lha_internal.h> + +#define DEVICE "APCSmart" + +#include "stonith_plugin_common.h" + +/* + * APCSmart (tested with old 900XLI, APC SmartUPS 700 and SmartUPS-1000) + * + * The reset is a combined reset: "S" and "@000" + * The "S" command tells the ups that if it is on-battery, it should + * remain offline until the power is back. + * If that command is not accepted, the "@000" command will be sent + * to tell the ups to turn off and back on right away. + * In both cases, if the UPS supports a 20 second shutdown grace + * period (such as on the 900XLI), the shutdown will delay that long, + * otherwise the shutdown will happen immediately (the code searches + * for the smallest possible delay). + */ + +#define CFG_FILE "/etc/ha.d/apcsmart.cfg" + +#define MAX_DEVICES 1 + +#define SERIAL_TIMEOUT 3 /* timeout in sec */ +#define SEND_DELAY 50000 /* in microseconds */ +#define ENDCHAR 10 /* use LF */ +#define MAX_STRING 512 +#define MAX_DELAY_STRING 16 +#define SWITCH_TO_NEXT_VAL "-" /* APC cmd for cycling through + * the values + */ + +#define CMD_SMART_MODE "Y" +#define RSP_SMART_MODE "SM" +#define CMD_GET_STATUS "Q" +#define RSP_GET_STATUS NULL +#define CMD_RESET "S" /* turn off & stay off if on battery */ +#define CMD_RESET2 "@000" /* turn off & immediately turn on */ +#define RSP_RESET "*" /* RESET response from older models */ +#define RSP_RESET2 "OK" /* RESET response from newer models */ +#define RSP_NA "NA" +#define CMD_READREG1 "~" +#define CMD_OFF "Z" +#define CMD_ON "\016" /* (control-n) */ +#define CMD_SHUTDOWN_DELAY "p" +#define CMD_WAKEUP_DELAY "r" + +#define CR 13 + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; /* of object */ + const char * idinfo; /* type of device */ + char ** hostlist; /* served by the device (only 1) */ + int hostcount;/* of hosts (1) */ + char * upsdev; /* */ + int upsfd; /* for serial port */ + int retries; + char shutdown_delay[MAX_DELAY_STRING]; + char old_shutdown_delay[MAX_DELAY_STRING]; + char wakeup_delay[MAX_DELAY_STRING]; + char old_wakeup_delay[MAX_DELAY_STRING]; +}; + +/* saving old settings */ +/* FIXME! These should be part of pluginDevice struct above */ +static struct termios old_tio; + +static int f_serialtimeout; /* flag for timeout */ +static const char *pluginid = "APCSmart-Stonith"; +static const char *NOTpluginID = "APCSmart device has been destroyed"; + +/* + * stonith prototypes + */ + +#define PIL_PLUGIN apcsmart +#define PIL_PLUGIN_S "apcsmart" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * apcsmart_new(const char *); +static void apcsmart_destroy(StonithPlugin *); +static const char * const * apcsmart_get_confignames(StonithPlugin*); +static int apcsmart_set_config(StonithPlugin *, StonithNVpair*); +static const char * apcsmart_get_info(StonithPlugin * s, int InfoType); +static int apcsmart_status(StonithPlugin * ); +static int apcsmart_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcsmart_hostlist(StonithPlugin *); + +static struct stonith_ops apcsmartOps ={ + apcsmart_new, /* Create new STONITH object */ + apcsmart_destroy, /* Destroy STONITH object */ + apcsmart_get_info, /* Return STONITH info string */ + apcsmart_get_confignames, /* Return STONITH info string */ + apcsmart_set_config, /* Get configuration from NVpairs */ + apcsmart_status, /* Return STONITH device status */ + apcsmart_reset_req, /* Request a reset */ + apcsmart_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; + +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 + , &apcsmartOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#include "stonith_config_xml.h" + +static const char *apcsmartXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +int APC_open_serialport(const char *port, speed_t speed); +void APC_close_serialport(const char *port, int upsfd); +void APC_sh_serial_timeout(int sig); +int APC_send_cmd(int upsfd, const char *cmd); +int APC_recv_rsp(int upsfd, char *rsp); +int APC_enter_smartmode(int upsfd); +int APC_set_ups_var(int upsfd, const char *cmd, char *newval); +int APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay); +int APC_init( struct pluginDevice *ad ); +void APC_deinit( struct pluginDevice *ad ); + +/* + * Signal handler for serial port timeouts + */ + +void +APC_sh_serial_timeout(int sig) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + STONITH_IGNORE_SIG(SIGALRM); + + if (Debug) { + LOG(PIL_DEBUG, "%s: serial port timed out.", __FUNCTION__); + } + + f_serialtimeout = TRUE; + + return; +} + +/* + * Open serial port and set it to b2400 + */ + +int +APC_open_serialport(const char *port, speed_t speed) +{ + struct termios tio; + int fd; + int rc; + int errno_save; + int fflags; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if ((rc = OurImports->TtyLock(port)) < 0) { + LOG(PIL_CRIT, "%s: Could not lock tty %s [rc=%d]." + , __FUNCTION__, port, rc); + return -1; + } + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + alarm(SERIAL_TIMEOUT); + f_serialtimeout = FALSE; + + fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL); + errno_save = errno; + + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + if (fd < 0) { + LOG(PIL_CRIT, "%s: Open of %s %s [%s].", __FUNCTION__ + , port + , f_serialtimeout ? "timed out" : "failed" + , strerror(errno_save)); + OurImports->TtyUnlock(port); + return -1; + } + + if ((fflags = fcntl(fd, F_GETFL)) < 0 + || fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) { + LOG(PIL_CRIT, "%s: Setting flags on %s failed [%s]." + , __FUNCTION__ + , port + , strerror(errno_save)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + if (tcgetattr(fd, &old_tio) < 0) { + LOG(PIL_CRIT, "%s: tcgetattr of %s failed [%s].", __FUNCTION__ + , port + , strerror(errno)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + memcpy(&tio, &old_tio, sizeof(struct termios)); + tio.c_cflag = CS8 | CLOCAL | CREAD; + tio.c_iflag = IGNPAR; + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + + cfsetispeed(&tio, speed); + cfsetospeed(&tio, speed); + + tcflush(fd, TCIOFLUSH); + tcsetattr(fd, TCSANOW, &tio); + + return (fd); +} + +/* + * Close serial port and restore old settings + */ + +void +APC_close_serialport(const char *port, int upsfd) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (upsfd < 0) { + return; + } + + tcflush(upsfd, TCIFLUSH); + tcsetattr(upsfd, TCSANOW, &old_tio); + close(upsfd); + if (port != NULL) { + OurImports->TtyUnlock(port); + } +} + +/* + * Send a command to the ups + */ + +int +APC_send_cmd(int upsfd, const char *cmd) +{ + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s(\"%s\")", __FUNCTION__, cmd); + } + + tcflush(upsfd, TCIFLUSH); + for (i = strlen(cmd); i > 0; i--) { + if (write(upsfd, cmd++, 1) != 1) { + return (S_ACCESS); + } + + usleep(SEND_DELAY); + } + return (S_OK); +} + +/* + * Get the response from the ups + */ + +int +APC_recv_rsp(int upsfd, char *rsp) +{ + char *p = rsp; + char inp; + int num = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + *p = '\0'; + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + + alarm(SERIAL_TIMEOUT); + + while (num < MAX_STRING) { + + if (read(upsfd, &inp, 1) == 1) { + + /* shutdown sends only a '*' without LF */ + if ((inp == '*') && (num == 0)) { + *p++ = inp; + num++; + inp = ENDCHAR; + } + + if (inp == ENDCHAR) { + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + *p = '\0'; + if (Debug) { + LOG(PIL_DEBUG, "return(\"%s\")/*%s*/;" + , rsp, __FUNCTION__); + } + return (S_OK); + } + + if (inp != CR) { + *p++ = inp; + num++; + } + }else{ + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + *p = '\0'; + LOG(PIL_DEBUG, "%s: %s.", __FUNCTION__, + f_serialtimeout ? "timeout" : + "can't access device" ); + return (f_serialtimeout ? S_TIMEOUT : S_ACCESS); + } + } + return (S_ACCESS); +} + +/* + * Enter smart mode + */ + +int +APC_enter_smartmode(int upsfd) +{ + int rc; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + strcpy(resp, RSP_SMART_MODE); + + if (((rc = APC_send_cmd(upsfd, CMD_SMART_MODE)) == S_OK) + && ((rc = APC_recv_rsp(upsfd, resp)) == S_OK) + && (strcmp(RSP_SMART_MODE, resp) == 0)) { + return (S_OK); + } + + return (S_ACCESS); +} + +/* + * Set a value in the hardware using the <cmdchar> '-' (repeat) approach + */ + +int +APC_set_ups_var(int upsfd, const char *cmd, char *newval) +{ + char resp[MAX_STRING]; + char orig[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' original val %s" + , __FUNCTION__, cmd, orig); + } + + if (strcmp(orig, newval) == 0) { + return (S_OK); /* already set */ + } + + *resp = '\0'; + + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (strcmp(resp, newval) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' set to %s" + , __FUNCTION__, cmd, newval); + } + + strcpy(newval, orig); /* return the old value */ + return (S_OK); /* got it */ + } + } + + LOG(PIL_CRIT, "%s(): Could not set variable '%s' to %s!" + , __FUNCTION__, cmd, newval); + LOG(PIL_CRIT, "%s(): This UPS may not support STONITH :-(" + , __FUNCTION__); + + return (S_OOPS); +} + +/* + * Query the smallest delay supported by the hardware using the + * <cmdchar> '-' (repeat) approach and looping through all possible values, + * saving the smallest + */ + +int +APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay) +{ + char resp[MAX_DELAY_STRING]; + char orig[MAX_DELAY_STRING]; + int delay, smallest; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + smallest = atoi(orig); + strcpy(smdelay, orig); + + *resp = '\0'; + + /* search for smallest delay; need to loop through all possible + * values so that we leave delay the way we found it */ + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if ((delay = atoi(resp)) < smallest) { + smallest = delay; + strcpy(smdelay, resp); + } + } + + return (S_OK); +} + +/* + * Initialize the ups + */ + +int +APC_init(struct pluginDevice *ad) +{ + int upsfd; + char value[MAX_DELAY_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* if ad->upsfd != -1 device has already been configured. */ + /* Just enter smart mode again because otherwise a SmartUPS-1000 */ + /* has been observed to sometimes not respond. */ + if(ad->upsfd >= 0) { + if(APC_enter_smartmode(ad->upsfd) != S_OK) { + return(S_OOPS); + } + return S_OK; + } + + /* open serial port and store the fd in ad->upsfd */ + if ((upsfd = APC_open_serialport(ad->upsdev, B2400)) == -1) { + return S_OOPS; + } + + /* switch into smart mode */ + if (APC_enter_smartmode(upsfd) != S_OK) { + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the smallest possible delays for this particular hardware */ + if (APC_get_smallest_delay(upsfd, CMD_SHUTDOWN_DELAY + , ad->shutdown_delay) != S_OK + || APC_get_smallest_delay(upsfd, CMD_WAKEUP_DELAY + , ad->wakeup_delay) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't retrieve smallest delay from UPS" + , __FUNCTION__); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the old settings and store them */ + strcpy(value, ad->shutdown_delay); + if (APC_set_ups_var(upsfd, CMD_SHUTDOWN_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set shutdown delay to %s" + , __FUNCTION__, ad->shutdown_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_shutdown_delay, value); + strcpy(value, ad->wakeup_delay); + if (APC_set_ups_var(upsfd, CMD_WAKEUP_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set wakeup delay to %s" + , __FUNCTION__, ad->wakeup_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_wakeup_delay, value); + + ad->upsfd = upsfd; + return S_OK; +} + +/* + * Restore original settings and close the port + */ + +void +APC_deinit(struct pluginDevice *ad) +{ + APC_enter_smartmode( ad->upsfd ); + + APC_set_ups_var(ad->upsfd, CMD_SHUTDOWN_DELAY, ad->old_shutdown_delay); + APC_set_ups_var(ad->upsfd, CMD_WAKEUP_DELAY, ad->old_wakeup_delay); + + /* close serial port */ + if (ad->upsfd >= 0) { + APC_close_serialport(ad->upsdev, ad->upsfd); + ad->upsfd = -1; + } +} +static const char * const * +apcsmart_get_confignames(StonithPlugin* sp) +{ + static const char * names[] = {ST_TTYDEV, ST_HOSTLIST, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Stash away the config info we've been given... + */ + +static int +apcsmart_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice * ad = (struct pluginDevice*)s; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ad->upsdev = namestocopy[0].s_value; + ad->hostlist = OurImports->StringToHostList(namestocopy[1].s_value); + FREE(namestocopy[1].s_value); + + if (ad->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (ad->hostcount = 0; ad->hostlist[ad->hostcount] + ; ad->hostcount++) { + strdown(ad->hostlist[ad->hostcount]); + } + if (access(ad->upsdev, R_OK|W_OK|F_OK) < 0) { + LOG(PIL_CRIT,"Cannot access tty [%s]", ad->upsdev); + return S_BADCONFIG; + } + + return ad->hostcount ? S_OK : S_BADCONFIG; +} + +/* + * return the status for this device + */ + +static int +apcsmart_status(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + char resp[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + + /* get status */ + if (((rc = APC_init( ad )) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_GET_STATUS)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)) { + return (S_OK); /* everything ok. */ + } + if (Debug) { + LOG(PIL_DEBUG, "%s: failed, rc=%d.", __FUNCTION__, rc); + } + return (rc); +} + + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcsmart_hostlist(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFNOTCONFIGED(s,NULL); + + return OurImports->CopyHostList((const char **)(void*)ad->hostlist); +} + +static gboolean +apcsmart_RegisterBitsSet(struct pluginDevice * ad, int nreg, unsigned bits +, gboolean* waserr) +{ + const char* reqregs[4] = {"?", "~", "'", "8"}; + unsigned regval; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + + if (APC_enter_smartmode(ad->upsfd) != S_OK + || APC_send_cmd(ad->upsfd, reqregs[nreg]) != S_OK + || APC_recv_rsp(ad->upsfd, resp) != S_OK + || (sscanf(resp, "%02x", ®val) != 1)) { + if (waserr){ + *waserr = TRUE; + } + return FALSE; + } + if (waserr){ + *waserr = FALSE; + } + return ((regval & bits) == bits); +} + +#define apcsmart_IsPoweredOff(ad, err) apcsmart_RegisterBitsSet(ad,1,0x40,err) +#define apcsmart_ResetHappening(ad,err) apcsmart_RegisterBitsSet(ad,3,0x08,err) + + +static int +apcsmart_ReqOnOff(struct pluginDevice * ad, int request) +{ + const char * cmdstr; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + cmdstr = (request == ST_POWEROFF ? CMD_OFF : CMD_ON); + /* enter smartmode, send on/off command */ + if ((rc =APC_enter_smartmode(ad->upsfd)) != S_OK + || (rc = APC_send_cmd(ad->upsfd, cmdstr)) != S_OK) { + return rc; + } + sleep(2); + if ((rc = APC_send_cmd(ad->upsfd, cmdstr)) == S_OK) { + gboolean ison; + gboolean waserr; + sleep(1); + ison = !apcsmart_IsPoweredOff(ad, &waserr); + if (waserr) { + return S_RESETFAIL; + } + if (request == ST_POWEROFF) { + return ison ? S_RESETFAIL : S_OK; + }else{ + return ison ? S_OK : S_RESETFAIL; + } + } + return rc; +} + +/* + * reset the host + */ + +static int +apcsmart_ReqGenericReset(struct pluginDevice *ad) +{ + char resp[MAX_STRING]; + int rc = S_RESETFAIL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* send reset command(s) */ + if (((rc = APC_init(ad)) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_RESET)) == S_OK)) { + if (((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* first kind of reset command was accepted */ + } else if (((rc = APC_send_cmd(ad->upsfd, CMD_RESET2)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* second kind of command was accepted */ + } else { + if (Debug) { + LOG(PIL_DEBUG, "APC: neither reset command " + "was accepted"); + } + rc = S_RESETFAIL; + } + } + if (rc == S_OK) { + /* we wait grace period + up to 10 seconds after shutdown */ + int maxdelay = atoi(ad->shutdown_delay)+10; + int j; + + for (j=0; j < maxdelay; ++j) { + gboolean err; + if (apcsmart_ResetHappening(ad, &err)) { + return err ? S_RESETFAIL : S_OK; + } + sleep(1); + } + LOG(PIL_CRIT, "%s: timed out waiting for reset to end." + , __FUNCTION__); + return S_RESETFAIL; + + }else{ + if (strcmp(resp, RSP_NA) == 0){ + gboolean iserr; + /* This means it's currently powered off */ + /* or busy on a previous command... */ + if (apcsmart_IsPoweredOff(ad, &iserr)) { + if (iserr) { + LOG(PIL_DEBUG, "%s: power off " + "detection failed.", __FUNCTION__); + return S_RESETFAIL; + } + if (Debug) { + LOG(PIL_DEBUG, "APC: was powered off, " + "powering back on."); + } + return apcsmart_ReqOnOff(ad, ST_POWERON); + } + } + } + strcpy(resp, "?"); + + /* reset failed */ + + return S_RESETFAIL; +} + +static int +apcsmart_reset_req(StonithPlugin * s, int request, const char *host) +{ + char ** hl; + int b_found=FALSE; + struct pluginDevice * ad = (struct pluginDevice *) s; + int rc; + + ERRIFNOTCONFIGED(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "%s: invalid hostname argument.", __FUNCTION__); + return (S_INVAL); + } + + /* look through the hostlist */ + hl = ad->hostlist; + + while (*hl && !b_found ) { + if( strcasecmp( *hl, host ) == 0 ) { + b_found = TRUE; + break; + }else{ + ++hl; + } + } + + /* host not found in hostlist */ + if( !b_found ) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist." + , __FUNCTION__, host); + return S_BADHOST; + } + if ((rc = APC_init(ad)) != S_OK) { + return rc; + } + + if (request == ST_POWERON || request == ST_POWEROFF) { + return apcsmart_ReqOnOff(ad, request); + } + return apcsmart_ReqGenericReset(ad); +} + + +/* + * get info about the stonith device + */ + +static const char * +apcsmart_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + const char *ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->upsdev; + break; + + case ST_DEVICEDESCR: + ret = "APC Smart UPS\n" + " (via serial port - NOT USB!). \n" + " Works with higher-end APC UPSes, like\n" + " Back-UPS Pro, Smart-UPS, Matrix-UPS, etc.\n" + " (Smart-UPS may have to be >= Smart-UPS 700?).\n" + " See http://www.networkupstools.org/protocols/apcsmart.html\n" + " for protocol compatibility details."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcsmartXML; + break; + + default: + ret = NULL; + break; + } + return (ret); +} + +/* + * APC Stonith destructor... + */ + +static void +apcsmart_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + VOIDERRIFWRONGDEV(s); + + if (ad->upsfd >= 0 && ad->upsdev) { + APC_deinit( ad ); + } + + ad->pluginid = NOTpluginID; + + if (ad->hostlist) { + stonith_free_hostlist(ad->hostlist); + ad->hostlist = NULL; + } + if (ad->upsdev != NULL) { + FREE(ad->upsdev); + ad->upsdev = NULL; + } + + ad->hostcount = -1; + ad->upsfd = -1; + + FREE(ad); + +} + +/* + * Create a new APC Stonith device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcsmart_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + memset(ad, 0, sizeof(*ad)); + + ad->pluginid = pluginid; + ad->hostlist = NULL; + ad->hostcount = -1; + ad->upsfd = -1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcsmartOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully.", __FUNCTION__); + } + return &(ad->sp); +} diff --git a/lib/plugins/stonith/apcsmart.cfg.example b/lib/plugins/stonith/apcsmart.cfg.example new file mode 100644 index 0000000..278f925 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.cfg.example @@ -0,0 +1 @@ +/dev/ups hostname diff --git a/lib/plugins/stonith/baytech.c b/lib/plugins/stonith/baytech.c new file mode 100644 index 0000000..33093ad --- /dev/null +++ b/lib/plugins/stonith/baytech.c @@ -0,0 +1,924 @@ +/* + * Stonith module for BayTech Remote Power Controllers (RPC-x devices) + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * 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 "BayTech power switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN baytech +#define PIL_PLUGIN_S "baytech" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * baytech_new(const char *); +static void baytech_destroy(StonithPlugin *); +static int baytech_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * baytech_get_confignames(StonithPlugin * s); +static const char * baytech_get_info(StonithPlugin * s, int InfoType); +static int baytech_status(StonithPlugin *); +static int baytech_reset_req(StonithPlugin * s, int request, const char * host); +static char ** baytech_hostlist(StonithPlugin *); + +static struct stonith_ops baytechOps ={ + baytech_new, /* Create new STONITH object */ + baytech_destroy, /* Destroy STONITH object */ + baytech_get_info, /* Return STONITH info string */ + baytech_get_confignames, /* Return STONITH config vars */ + baytech_set_config, /* set configuration from vars */ + baytech_status, /* Return STONITH device status */ + baytech_reset_req, /* Request a reset */ + baytech_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" + +#define MAXOUTLET 32 + +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 + , &baytechOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an RPC-5. This code has been tested with this switch. + * + * The BayTech switches are quite nice, but the dialogues are a bit of a + * pain for mechanical parsing. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * unitid; + const struct BayTechModelInfo* modelinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +struct BayTechModelInfo { + const char * type; /* Baytech model info */ + size_t socklen; /* Length of socket name string */ + struct Etoken * expect; /* Expect string before outlet list */ +}; + +static int parse_socket_line(struct pluginDevice*,const char * +, int *, char *); + +static const char * pluginid = "BayTech-Stonith"; +static const char * NOTpluginID = "BayTech device has been destroyed"; + +/* + * Different expect strings that we get from the Baytech + * Remote Power Controllers... + */ + +#define BAYTECHASSOC "Bay Technical Associates" + +static struct Etoken BayTechAssoc[] = { {BAYTECHASSOC, 0, 0}, {NULL,0,0}}; +static struct Etoken UnitId[] = { {"Unit ID: ", 0, 0}, {NULL,0,0}}; +static struct Etoken login[] = { {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken password[] = { {"password>", 0, 0} + , {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken Selection[] = { {"election>", 0, 0} ,{NULL,0,0}}; +static struct Etoken RPC[] = { {"RPC", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {"RPC", 0, 0}, {"Invalid password", 1, 0} + , {NULL,0,0}}; +static struct Etoken GTSign[] = { {">", 0, 0} ,{NULL,0,0}}; +static struct Etoken Menu[] = { {"Menu:", 0, 0} ,{NULL,0,0}}; +static struct Etoken Temp[] = { {"emperature: ", 0, 0} + , {NULL,0,0}}; +static struct Etoken Break[] = { {"Status", 0, 0} + , {NULL,0,0}}; +static struct Etoken PowerApplied[] = { {"ower applied to outlet", 0, 0} + , {NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Rebooting[] = { {"ebooting selected outlet", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already off.", 2, 0} + , {NULL,0,0}}; + +static struct Etoken TurningOnOff[] = { {"RPC", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already ", 2, 0} + , {NULL,0,0}}; + + +static struct BayTechModelInfo ModelInfo [] = { + {"BayTech RPC-5", 18, Temp},/* This first model will be the default */ + {"BayTech RPC-3", 10, Break}, + {"BayTech RPC-3A", 10, Break}, + {NULL, 0, NULL}, +}; + +#include "stonith_config_xml.h" + +static const char *baytechXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int RPC_connect_device(struct pluginDevice * bt); +static int RPCLogin(struct pluginDevice * bt); +static int RPCRobustLogin(struct pluginDevice * bt); +static int RPCNametoOutletList(struct pluginDevice*, const char * name +, int outletlist[]); +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +static int RPCLogout(struct pluginDevice * bt); + + +static int RPC_onoff(struct pluginDevice*, int unitnum, const char * unitid +, int request); + +/* Login to the Baytech Remote Power Controller (RPC) */ + +static int +RPCLogin(struct pluginDevice * bt) +{ + char IDinfo[128]; + static char IDbuf[128]; + char * idptr = IDinfo; + char * delim; + int j; + + EXPECT(bt->rdfd, RPC, 10); + + /* Look for the unit type info */ + if (EXPECT_TOK(bt->rdfd, BayTechAssoc, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", bt->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * RPC-5 Telnet Host + * Revision F 4.22, (C) 1999 + * Bay Technical Associates + */ + + /* Truncate the result after the RPC-5 part */ + if ((delim = strchr(idptr, ' ')) != NULL) { + *delim = EOS; + } + snprintf(IDbuf, sizeof(IDbuf), "BayTech RPC%s", idptr); + REPLSTR(bt->idinfo, IDbuf); + if (bt->idinfo == NULL) { + return(S_OOPS); + } + + bt->modelinfo = &ModelInfo[0]; + + for (j=0; ModelInfo[j].type != NULL; ++j) { + /* + * TIMXXX - + * Look at device ID as this really describes the model. + */ + if (strcasecmp(ModelInfo[j].type, IDbuf) == 0) { + bt->modelinfo = &ModelInfo[j]; + break; + } + } + + /* Look for the unit id info */ + EXPECT(bt->rdfd, UnitId, 10); + SNARF(bt->rdfd, IDbuf, 2); + delim = IDbuf + strcspn(IDbuf, WHITESPACE); + *delim = EOS; + REPLSTR(bt->unitid, IDbuf); + if (bt->unitid == NULL) { + return(S_OOPS); + } + + /* Expect "username>" */ + EXPECT(bt->rdfd, login, 2); + + SEND(bt->wrfd, bt->user); + SEND(bt->wrfd, "\r"); + + /* Expect "password>" */ + + switch (StonithLookFor(bt->rdfd, password, 5)) { + case 0: /* Good! */ + break; + + case 1: /* OOPS! got another username prompt */ + LOG(PIL_CRIT, "Invalid username for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + SEND(bt->wrfd, bt->passwd); + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + + switch (StonithLookFor(bt->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + EXPECT(bt->rdfd, Menu, 2); + + return(S_OK); +} + +static int +RPCRobustLogin(struct pluginDevice * bt) +{ + int rc=S_OOPS; + int j; + + for (j=0; j < 20 && rc != S_OK; ++j) { + + + if (RPC_connect_device(bt) != S_OK) { + continue; + } + + rc = RPCLogin(bt); + } + return rc; +} + +/* Log out of the Baytech RPC */ + +static int +RPCLogout(struct pluginDevice* bt) +{ + int rc; + + /* Make sure we're in the right menu... */ + SEND(bt->wrfd, "\r"); + + /* Expect "Selection>" */ + rc = StonithLookFor(bt->rdfd, Selection, 5); + + /* Option 6 is Logout */ + SEND(bt->wrfd, "6\r"); + + close(bt->wrfd); + close(bt->rdfd); + bt->wrfd = bt->rdfd = -1; + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlet number */ +static int +RPCReset(struct pluginDevice* bt, int unitnum, const char * rebootid) +{ + char unum[32]; + + + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send REBOOT command for given outlet */ + snprintf(unum, sizeof(unum), "REBOOT %d\r", unitnum); + SEND(bt->wrfd, unum); + + /* Expect "ebooting "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(bt->rdfd, Rebooting, 5)) { + case 0: /* Got "Rebooting" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + goto retry; + + case 2: /* Outlet is turned off */ + LOG(PIL_CRIT, "Host is OFF: %s.", rebootid); + return(S_ISOFF); + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host %s (outlet %d) being rebooted." + , rebootid, unitnum); + + /* Expect "ower applied to outlet" */ + if (StonithLookFor(bt->rdfd, PowerApplied, 30) < 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 (outlet %d)." + , rebootid, unitnum); + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC,5); + EXPECT(bt->rdfd, GTSign, 5); + + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +static int +RPC_onoff(struct pluginDevice* bt, int unitnum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "on" : "off"); + int rc; + + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %d\r" + , onoff, unitnum); + SEND(bt->wrfd, unum); + + /* Expect "RPC->x "... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(bt->rdfd, TurningOnOff, 10) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + EXPECT(bt->rdfd, TurningOnOff, 10); + } + + EXPECT(bt->rdfd, GTSign, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to host %s (outlet %d) turned %s." + , unitid, unitnum, onoff); + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +RPCNametoOutletList(struct pluginDevice* bt, const char * name +, int outletlist[]) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int maxfound = 0; + + + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + EXPECT(bt->rdfd, bt->modelinfo->expect, 5); + EXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + char * last; + NameMapping[0] = EOS; + SNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + outletlist[maxfound] = sockno; + ++maxfound; + } + } while (strlen(NameMapping) > 2 && maxfound < MAXOUTLET); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + return(maxfound); +} + +static int +baytech_status(StonithPlugin *s) +{ + struct pluginDevice* bt; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + return(RPCLogout(bt)); +} +/* + * Return the list of hosts (outlet names) for the devices on this BayTech unit + */ + +static char ** +baytech_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* bt; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + bt = (struct pluginDevice*) s; + + if (RPCRobustLogin(bt) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(NULL); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + NULLEXPECT(bt->rdfd, bt->modelinfo->expect, 5); + NULLEXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + char * last; + char * nm; + + NameMapping[0] = EOS; + + NULLSNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *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; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + 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)RPCLogout(bt); + 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 BayTech device. + * We should add serial support here eventually... + */ +static int +RPC_connect_device(struct pluginDevice * bt) +{ + int fd = OurImports->OpenStreamSocket(bt->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + bt->rdfd = bt->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +baytech_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = 0; + struct pluginDevice* bt; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + }else{ + int noutlets; + int outlets[MAXOUTLET]; + int j; + noutlets = RPCNametoOutletList(bt, host, outlets); + + if (noutlets < 1) { + LOG(PIL_CRIT, "%s %s doesn't control host [%s]" + , bt->idinfo, bt->unitid, host); + return(S_BADHOST); + } + switch(request) { + + case ST_POWERON: + case ST_POWEROFF: + for (j=0; rc == S_OK && j < noutlets;++j) { + rc = RPC_onoff(bt, outlets[j], host, request); + } + break; + case ST_GENERIC_RESET: + /* + * Our strategy here: + * 1. Power off all outlets except the last one + * 2. reset the last outlet + * 3. power the other outlets back on + */ + + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt,outlets[j],host + , ST_POWEROFF); + } + if (rc == S_OK) { + rc = RPCReset(bt, outlets[j], host); + } + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt, outlets[j], host + , ST_POWERON); + } + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = RPCLogout(bt); + + return(rc != S_OK ? rc : lorc); +} + +static const char * const * +baytech_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +baytech_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* bt = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (bt->sp.isconfigured) { + return S_OOPS; + } + + if ((rc =OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + bt->device = namestocopy[0].s_value; + bt->user = namestocopy[1].s_value; + bt->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +baytech_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* bt; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + bt = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = bt->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = bt->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Bay Technical Associates (Baytech) RPC " + "series power switches (via telnet).\n" + "The RPC-5, RPC-3 and RPC-3A switches are well tested" + "."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.baytech.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = baytechXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Baytech Stonith destructor... + */ +static void +baytech_destroy(StonithPlugin *s) +{ + struct pluginDevice* bt; + + VOIDERRIFWRONGDEV(s); + + bt = (struct pluginDevice *)s; + + bt->pluginid = NOTpluginID; + if (bt->rdfd >= 0) { + close(bt->rdfd); + bt->rdfd = -1; + } + if (bt->wrfd >= 0) { + close(bt->wrfd); + bt->wrfd = -1; + } + if (bt->device != NULL) { + FREE(bt->device); + bt->device = NULL; + } + if (bt->user != NULL) { + FREE(bt->user); + bt->user = NULL; + } + if (bt->passwd != NULL) { + FREE(bt->passwd); + bt->passwd = NULL; + } + if (bt->idinfo != NULL) { + FREE(bt->idinfo); + bt->idinfo = NULL; + } + if (bt->unitid != NULL) { + FREE(bt->unitid); + bt->unitid = NULL; + } + FREE(bt); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +baytech_new(const char *subplugin) +{ + struct pluginDevice* bt = ST_MALLOCT(struct pluginDevice); + + if (bt == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(bt, 0, sizeof(*bt)); + bt->pluginid = pluginid; + bt->pid = -1; + bt->rdfd = -1; + bt->wrfd = -1; + REPLSTR(bt->idinfo, DEVICE); + if (bt->idinfo == NULL) { + FREE(bt); + return(NULL); + } + bt->modelinfo = &ModelInfo[0]; + bt->sp.s_ops = &baytechOps; + + return &(bt->sp); /* same as "bt" */ +} + +static int +parse_socket_line(struct pluginDevice * bt, const char *NameMapping +, int *sockno, char *sockname) +{ +#if 0 + char format[64]; + snprintf(format, sizeof(format), "%%7d %%%dc" + , bt->modelinfo->socklen); + /* 7 digits, 7 blanks, then 'socklen' characters */ + /* [0-6]: digits, NameMapping[13] begins the sockname */ + /* NameMapping strlen must be >= socklen + 14 */ + + if (sscanf(NameMapping, format, sockno, sockname) != 2) { + return FALSE; + } +#else +# define OFFSET 14 + + if (sscanf(NameMapping, "%7d", sockno) != 1 + || strlen(NameMapping) < OFFSET+bt->modelinfo->socklen) { + return FALSE; + } + strncpy(sockname, NameMapping+OFFSET, bt->modelinfo->socklen); + sockname[bt->modelinfo->socklen] = EOS; +#endif + return TRUE; +} diff --git a/lib/plugins/stonith/bladehpi.c b/lib/plugins/stonith/bladehpi.c new file mode 100644 index 0000000..ae9a4cf --- /dev/null +++ b/lib/plugins/stonith/bladehpi.c @@ -0,0 +1,1101 @@ +/* + * Stonith module for BladeCenter via OpenHPI, an implementation of Service + * Availability Forum's Hardware Platfrom Interface + * + * Author: Dave Blaschke <debltc@us.ibm.com> + * + * Copyright (c) 2005 International Business Machines + * + * 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 "IBM BladeCenter (OpenHPI)" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN bladehpi +#define PIL_PLUGIN_S "bladehpi" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include <openhpi/SaHpi.h> + +/* Maximum number of seconds to wait for host to power off */ +#define MAX_POWEROFF_WAIT 60 + +/* entity_root, the one required plugin parameter */ +#define ST_ENTITYROOT "entity_root" + +/* String format of entity_root */ +#define SYSTEM_CHASSIS_FMT "{SYSTEM_CHASSIS,%d}" + +/* soft_reset, the one optional plugin parameter */ +#define ST_SOFTRESET "soft_reset" + +#define OPENHPIURL "http://www.openhpi.org/" + +/* OpenHPI resource types of interest to this plugin */ +#define OHRES_NONE 0 +#define OHRES_BLADECENT 1 +#define OHRES_MGMTMOD 2 +#define OHRES_BLADE 3 + +/* IBMBC_WAIT_FOR_OFF - This constant has to do with the problem that + saHpiResourcePowerStateSet can return before the desired state has been + achieved by the blade. In the SAHPI_POWER_OFF case this is not good, + as whoever calls this plugin assumes that the power is actually off + when the plugin returns with a successful return code. Define this + constant to build code that loops in one second intervals after calling + saHpiResourcePowerStateSet(SAHPI_POWER_OFF) to make sure the power is + really off. +#define IBMBC_WAIT_FOR_OFF */ + +static StonithPlugin * bladehpi_new(const char *); +static void bladehpi_destroy(StonithPlugin *); +static const char * bladehpi_getinfo(StonithPlugin *, int); +static const char * const * bladehpi_get_confignames(StonithPlugin *); +static int bladehpi_status(StonithPlugin *); +static int bladehpi_reset_req(StonithPlugin *, int, const char *); +static char ** bladehpi_hostlist(StonithPlugin *); +static int bladehpi_set_config(StonithPlugin *, StonithNVpair *); + +static struct stonith_ops bladehpiOps = { + bladehpi_new, /* Create new STONITH object */ + bladehpi_destroy, /* Destroy STONITH object */ + bladehpi_getinfo, /* Return STONITH info string */ + bladehpi_get_confignames, /* Return configuration parameters */ + bladehpi_set_config, /* Set configuration */ + bladehpi_status, /* Return STONITH device status */ + bladehpi_reset_req, /* Request a reset */ + bladehpi_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; + + +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 + , &bladehpiOps + , NULL /* close */ + , &OurInterface + , (void *)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * device; + int softreset; + GList * hostlist; + SaHpiVersionT ohver; /* OpenHPI interface version */ + SaHpiSessionIdT ohsession; /* session ID */ + SaHpiUint32T ohrptcnt; /* RPT count for hostlist */ + SaHpiResourceIdT ohdevid; /* device resource ID */ + SaHpiResourceIdT ohsensid; /* sensor resource ID */ + SaHpiSensorNumT ohsensnum; /* sensor number */ +}; + +static int open_hpi_session(struct pluginDevice *dev); +static void close_hpi_session(struct pluginDevice *dev); + +static const char *pluginid = "BladeCenterDevice-Stonith"; +static const char *NOTpluginID = "IBM BladeCenter device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_ENTITYROOT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_ENTITYROOT \ + XML_PARM_SHORTDESC_END + +#define XML_ENTITYROOT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The entity_root of the STONITH device from the OpenHPI config file" \ + XML_PARM_LONGDESC_END + +#define XML_ENTITYROOT_PARM \ + XML_PARAMETER_BEGIN(ST_ENTITYROOT, "string", "1", "0") \ + XML_ENTITYROOT_SHORTDESC \ + XML_ENTITYROOT_LONGDESC \ + XML_PARAMETER_END + +#define XML_SOFTRESET_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SOFTRESET \ + XML_PARM_SHORTDESC_END + +#define XML_SOFTRESET_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Soft reset indicator, true|1 if STONITH device should use soft reset (power cycle) to reset nodes, false|0 if device should use hard reset (power off, wait, power on); default is false" \ + XML_PARM_LONGDESC_END + +#define XML_SOFTRESET_PARM \ + XML_PARAMETER_BEGIN(ST_SOFTRESET, "string", "0", "0") \ + XML_SOFTRESET_SHORTDESC \ + XML_SOFTRESET_LONGDESC \ + XML_PARAMETER_END + +static const char *bladehpiXML = + XML_PARAMETERS_BEGIN + XML_ENTITYROOT_PARM + XML_SOFTRESET_PARM + XML_PARAMETERS_END; + +static int get_resource_type(char *, SaHpiRptEntryT *); +static int get_sensor_num(SaHpiSessionIdT, SaHpiResourceIdT); +static int get_bladehpi_hostlist(struct pluginDevice *); +static void free_bladehpi_hostlist(struct pluginDevice *); +static int get_num_tokens(char *str); + +struct blade_info { + char * name; /* blade name */ + SaHpiResourceIdT resourceId; /* blade resource ID */ + SaHpiCapabilitiesT resourceCaps; /* blade capabilities */ +}; + + +static int +bladehpi_status(StonithPlugin *s) +{ + struct pluginDevice * dev; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_BADCONFIG; + goto done; + } + } + + /* At this point, hostlist is up to date */ + if (dev->ohsensid && dev->ohsensnum) { + /* + * For accurate status, need to make a call that goes out to + * BladeCenter MM because the calls made so far by this + * function (and perhaps get_bladehpi_hostlist) only retrieve + * information from memory cached by OpenHPI + */ + ohrc = saHpiSensorReadingGet(dev->ohsession + , dev->ohsensid, dev->ohsensnum, NULL, NULL); + if (ohrc == SA_ERR_HPI_BUSY || ohrc == SA_ERR_HPI_NO_RESPONSE) { + LOG(PIL_CRIT, "Unable to connect to BladeCenter in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + +done: + close_hpi_session(dev); + return (rc == S_OK) ? (dev->ohdevid ? S_OK : S_OOPS) : rc; +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +bladehpi_hostlist(StonithPlugin *s) +{ + struct pluginDevice * dev; + int numnames = 0, j; + char ** ret = NULL; + GList * node = NULL; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return NULL; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + goto done; + } + } + + /* At this point, hostlist is up to date */ + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + goto done; + } + + ret = (char **)MALLOC((numnames+1) * sizeof(char *)); + if (ret == NULL) { + LOG(PIL_CRIT, "Out of memory for malloc in %s", __FUNCTION__); + goto done; + } + + memset(ret, 0, (numnames+1) * sizeof(char *)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + ret[j] = STRDUP(((struct blade_info *)node->data)->name); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + stonith_free_hostlist(ret); + ret = NULL; + goto done; + } + strdown(ret[j]); + } + +done: + close_hpi_session(dev); + return ret; +} + + +static const char * const * +bladehpi_get_confignames(StonithPlugin *s) +{ + static const char * names[] = {ST_ENTITYROOT, NULL}; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + return names; +} + + +/* + * Reset the given host, and obey the request type. + */ + +static int +bladehpi_reset_req(StonithPlugin *s, int request, const char *host) +{ + GList * node = NULL; + struct pluginDevice * dev = NULL; + struct blade_info * bi = NULL; + SaHpiPowerStateT ohcurstate, ohnewstate; + SaHpiDomainInfoT ohdi; + SaErrorT ohrc; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, request=%d, host=%s" + , __FUNCTION__, request, host); + } + + ERRIFWRONGDEV(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "Invalid host argument to %s", __FUNCTION__); + rc = S_OOPS; + goto done; + } + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + + for (node = g_list_first(dev->hostlist) + ; node != NULL + ; node = g_list_next(node)) { + bi = ((struct blade_info *)node->data); + if (Debug) { + LOG(PIL_DEBUG, "Found host %s in hostlist", bi->name); + } + + if (!strcasecmp(bi->name, host)) { + break; + } + } + + if (!node || !bi) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module, " + "please check your configuration information", host); + rc = S_OOPS; + goto done; + } + + /* Make sure host has proper capabilities for get */ + if (!(bi->resourceCaps & SAHPI_CAPABILITY_POWER)) { + LOG(PIL_CRIT + , "Host %s does not have power capability", host); + rc = S_OOPS; + goto done; + } + + ohrc = saHpiResourcePowerStateGet(dev->ohsession, bi->resourceId + , &ohcurstate); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + + switch (request) { + case ST_POWERON: + if (ohcurstate == SAHPI_POWER_ON) { + LOG(PIL_INFO, "Host %s already on", host); + goto done; + } + ohnewstate = SAHPI_POWER_ON; + + break; + + case ST_POWEROFF: + if (ohcurstate == SAHPI_POWER_OFF) { + LOG(PIL_INFO, "Host %s already off", host); + goto done; + } + ohnewstate = SAHPI_POWER_OFF; + + break; + + case ST_GENERIC_RESET: + if (ohcurstate == SAHPI_POWER_OFF) { + ohnewstate = SAHPI_POWER_ON; + } else { + ohnewstate = SAHPI_POWER_CYCLE; + } + + break; + + default: + LOG(PIL_CRIT, "Invalid request argument to %s" + , __FUNCTION__); + rc = S_INVAL; + goto done; + } + + if (!dev->softreset && (ohnewstate == SAHPI_POWER_CYCLE)) { + int maxwait; + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_OFF); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " OFF (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + + /* + * Must wait for power off here or subsequent power on request + * may take place while power is still on and thus ignored + */ + maxwait = MAX_POWEROFF_WAIT; + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_ON); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " ON (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + } else { + /* Make sure host has proper capabilities to reset */ + if ((ohnewstate == SAHPI_POWER_CYCLE) && + (!(bi->resourceCaps & SAHPI_CAPABILITY_RESET))) { + LOG(PIL_CRIT + , "Host %s does not have reset capability" + , host); + rc = S_OOPS; + goto done; + } + + if ((ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, ohnewstate)) != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + } + +#ifdef IBMBC_WAIT_FOR_OFF + if (ohnewstate == SAHPI_POWER_OFF) { + int maxwait = MAX_POWEROFF_WAIT; + + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + } +#endif + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + +done: + close_hpi_session(dev); + return rc; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +bladehpi_set_config(StonithPlugin *s, StonithNVpair *list) +{ + struct pluginDevice * dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_ENTITYROOT, NULL} + , {NULL, NULL} + }; + int rc, i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + + if (Debug) { + LOG(PIL_DEBUG, "%s conditionally compiled with:" +#ifdef IBMBC_WAIT_FOR_OFF + " IBMBC_WAIT_FOR_OFF" +#endif + , dev->pluginid); + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s = %s", ST_ENTITYROOT + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for soft_reset */ + const char *softreset = + OurImports->GetValue(list, ST_SOFTRESET); + if (softreset != NULL) { + if (!strcasecmp(softreset, "true") || + !strcmp(softreset, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(softreset, "false") || + !strcmp(softreset, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, softreset); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + } else { + /* -p or -F option with args "entity_root [soft_reset]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over entity_root and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (!strcasecmp(pch, "true") || !strcmp(pch, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(pch, "false") || !strcmp(pch, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, pch); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + + dev->device = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (dev->device == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s", __FUNCTION__); + return S_OOPS; + } + + if (strcspn(dev->device, WHITESPACE) != strlen(dev->device) || + sscanf(dev->device, SYSTEM_CHASSIS_FMT, &i) != 1 || i < 0) { + LOG(PIL_CRIT, "Invalid %s %s, must be of format %s" + , ST_ENTITYROOT, dev->device, SYSTEM_CHASSIS_FMT); + return S_BADCONFIG; + } + + dev->ohver = saHpiVersionGet(); + if (dev->ohver > SAHPI_INTERFACE_VERSION) { + LOG(PIL_CRIT, "Installed OpenHPI interface (%x) greater than " + "one used by plugin (%x), incompatibilites may exist" + , dev->ohver, SAHPI_INTERFACE_VERSION); + return S_BADCONFIG; + } + return S_OK; +} + +static int +open_hpi_session(struct pluginDevice *dev) +{ + SaErrorT ohrc; + + ohrc = saHpiSessionOpen(SAHPI_UNSPECIFIED_DOMAIN_ID + , &dev->ohsession, NULL); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to open HPI session (%d)", ohrc); + return S_BADCONFIG; + } + + ohrc = saHpiDiscover(dev->ohsession); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to discover resources (%d)", ohrc); + return S_BADCONFIG; + } + + return S_OK; +} +static void +close_hpi_session(struct pluginDevice *dev) +{ + if (dev && dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } +} + +static const char * +bladehpi_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice * dev; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, reqtype=%d" + , __FUNCTION__, reqtype); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->device; + break; + + case ST_DEVICEDESCR: + ret = "IBM BladeCenter via OpenHPI\n" + "Use for IBM xSeries systems managed by BladeCenter\n" + " Required parameter name " ST_ENTITYROOT " is " + "a string (no white-space) of\n" + "the format \""SYSTEM_CHASSIS_FMT"\" " + "which is entity_root of BladeCenter\n" + "from OpenHPI config file, where %d is a positive " + "integer\n" + " Optional parameter name " ST_SOFTRESET " is " + "true|1 if STONITH device should\n" + "use soft reset (power cycle) to reset nodes or " + "false|0 if device should\n" + "use hard reset (power off, wait, power on); " + "default is false"; + break; + + case ST_DEVICEURL: + ret = OPENHPIURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = bladehpiXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +bladehpi_destroy(StonithPlugin *s) +{ + struct pluginDevice * dev; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->device) { + FREE(dev->device); + dev->device = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_bladehpi_hostlist(dev); + + if (dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } + + FREE(dev); +} + + +static StonithPlugin * +bladehpi_new(const char *subplugin) +{ + struct pluginDevice * dev = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "Out of memory in %s", __FUNCTION__); + return NULL; + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->device = NULL; + dev->hostlist = NULL; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return NULL; + } + dev->sp.s_ops = &bladehpiOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully", __FUNCTION__); + } + + return ((void *)dev); +} + + +static int +get_resource_type(char *entityRoot, SaHpiRptEntryT *ohRPT) +{ + int i, rc = OHRES_NONE; + int foundBlade = 0, foundExp = 0, foundMgmt = 0; + int foundRoot = 0, foundOther = 0; + char rootName[64]; + SaHpiEntityPathT * ohep = &ohRPT->ResourceEntity; + + if (ohep == NULL || entityRoot == NULL) { + return 0; + } + + /* First find root of entity path, which is last entity in entry */ + for (i = 0; i < SAHPI_MAX_ENTITY_PATH; i++) { + if (ohep->Entry[i].EntityType == SAHPI_ENT_ROOT) { + break; + } + } + + /* Then back up through entries looking for specific entity */ + for (i--; i >= 0; i--) { + switch (ohep->Entry[i].EntityType) { + case SAHPI_ENT_SBC_BLADE: + foundBlade = 1; + break; + + case SAHPI_ENT_SYS_EXPANSION_BOARD: + foundExp = 1; + break; + + case SAHPI_ENT_SYS_MGMNT_MODULE: + if (ohep->Entry[i].EntityLocation == 0) { + foundMgmt = 1; + } + break; + + case SAHPI_ENT_SYSTEM_CHASSIS: + snprintf(rootName, sizeof(rootName) + , SYSTEM_CHASSIS_FMT + , ohep->Entry[i].EntityLocation); + if (!strcmp(entityRoot, rootName)) { + foundRoot = 1; + } + break; + + default: + foundOther = 1; + break; + } + } + + /* We are only interested in specific entities on specific device */ + if (foundRoot) { + if (foundMgmt && !(foundBlade||foundExp||foundOther)) { + rc = OHRES_MGMTMOD; + } else if (!(foundMgmt||foundBlade||foundExp||foundOther)) { + rc = OHRES_BLADECENT; + } else if (foundBlade && !foundExp) { + rc = OHRES_BLADE; + } + } + + return rc; +} + + +static int +get_sensor_num(SaHpiSessionIdT ohsession, SaHpiResourceIdT ohresid) +{ + SaErrorT ohrc = SA_OK; + SaHpiEntryIdT ohnextid; + SaHpiRdrT ohRDR; + + ohnextid = SAHPI_FIRST_ENTRY; + do { + ohrc = saHpiRdrGet(ohsession, ohresid, ohnextid + , &ohnextid, &ohRDR); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RDR entry in %s (%d)" + , __FUNCTION__, ohrc); + } else if (ohRDR.RdrType == SAHPI_SENSOR_RDR) { + return ohRDR.RdrTypeUnion.SensorRec.Num; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + return 0; +} + + +/* + * Get RPT update count + * Loop through all RPT entries + * If entry is BladeCenter, save resource ID in dev->ohdevid + * If entry is MgmtMod and has sensor, save resource ID in dev->ohsensid + * and sensor number in dev->ohsensnum + * If entry is blade, save blade_info and add to dev->hostlist + * Get RPT update count + * If RPT update count changed since start of loop, repeat loop + * Save RPT update count in dev->ohrptcnt + * + * Note that not only does this function update hostlist, it also + * updates ohrptcnt, ohdevid, ohsensid and ohsensnum. However, with + * this logic it does not need to be called again until the RPT update + * count changes. + */ + +static int +get_bladehpi_hostlist(struct pluginDevice *dev) +{ + struct blade_info * bi; + SaErrorT ohrc; + SaHpiEntryIdT ohnextid; + SaHpiRptEntryT ohRPT; + SaHpiDomainInfoT ohdi; + SaHpiUint32T ohupdate; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, dev->device=%s" + , __FUNCTION__, dev->device); + } + + if (dev->device == NULL || *dev->device == 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + return S_BADCONFIG; + } + +try_again: + ohupdate = ohdi.RptUpdateCount; + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; + ohnextid = SAHPI_FIRST_ENTRY; + do { + char blname[SAHPI_MAX_TEXT_BUFFER_LENGTH]; + int blnum; + + ohrc = saHpiRptEntryGet(dev->ohsession, ohnextid + , &ohnextid, &ohRPT); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RPT entry in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + switch (get_resource_type(dev->device, &ohRPT)) { + case OHRES_BLADECENT: + dev->ohdevid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG, "BladeCenter '%s' has id %d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohdevid); + } + break; + + case OHRES_MGMTMOD: + if (ohRPT.ResourceCapabilities&SAHPI_CAPABILITY_SENSOR){ + dev->ohsensnum = get_sensor_num(dev->ohsession + , ohRPT.ResourceId); + + if (dev->ohsensnum) { + dev->ohsensid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG + , "MgmtModule '%s' has id %d " + "with sensor #%d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohsensid + , dev->ohsensnum); + } + } + } + break; + + case OHRES_BLADE: + if ((bi = (struct blade_info *) + MALLOC(sizeof(struct blade_info))) == NULL) { + LOG(PIL_CRIT, "Out of memory in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + /* + * New format consists of "Blade N - name" while older + * format consists only of "name"; we only need to + * stash name because ResourceID is the important info + */ + if (sscanf((char*)ohRPT.ResourceTag.Data, "Blade %d - %s" + , &blnum, blname) == 2) { + bi->name = STRDUP(blname); + } else { + bi->name = STRDUP((char*)ohRPT.ResourceTag.Data); + } + if (bi->name == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + bi->resourceId = ohRPT.ResourceId; + bi->resourceCaps = ohRPT.ResourceCapabilities; + dev->hostlist = g_list_append(dev->hostlist, bi); + + if (Debug) { + LOG(PIL_DEBUG, "Blade '%s' has id %d, caps %x" + , bi->name, bi->resourceId, bi->resourceCaps); + } + break; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + if (ohupdate != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if(Debug){ + LOG(PIL_DEBUG, "Looping through entries again," + " count changed from %d to %d" + , ohupdate, ohdi.RptUpdateCount); + } + goto try_again; + } + + dev->ohrptcnt = ohupdate; + + return S_OK; +} + + +static void +free_bladehpi_hostlist(struct pluginDevice *dev) +{ + if (dev->hostlist) { + GList *node; + while (NULL != (node = g_list_first(dev->hostlist))) { + dev->hostlist = + g_list_remove_link(dev->hostlist, node); + FREE(((struct blade_info *)node->data)->name); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; +} + + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} 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 */ +} diff --git a/lib/plugins/stonith/drac3.c b/lib/plugins/stonith/drac3.c new file mode 100644 index 0000000..95be775 --- /dev/null +++ b/lib/plugins/stonith/drac3.c @@ -0,0 +1,359 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * Tiny bits Copyright 2005 International Business Machines + * Significantly Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * (Using snippets of other stonith modules code) + * + * 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 + * + */ + +#define DEVICE "Dell DRACIII Card" +#include "stonith_plugin_common.h" + +#include <curl/curl.h> +#include "drac3_command.h" + +#define PIL_PLUGIN drac3 +#define PIL_PLUGIN_S "drac3" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> +#include "stonith_signal.h" + +static StonithPlugin * drac3_new(const char *); +static void drac3_destroy(StonithPlugin *); +static const char * const * drac3_get_confignames(StonithPlugin *); +static int drac3_set_config(StonithPlugin *, StonithNVpair *); +static const char * drac3_getinfo(StonithPlugin * s, int InfoType); +static int drac3_status(StonithPlugin * ); +static int drac3_reset_req(StonithPlugin * s, int request, const char * host); +static char ** drac3_hostlist(StonithPlugin *); + +static struct stonith_ops drac3Ops ={ + drac3_new, /* Create new STONITH object */ + drac3_destroy, /* Destroy STONITH object */ + drac3_getinfo, /* Return STONITH info string */ + drac3_get_confignames, /* Return configuration parameters */ + drac3_set_config, /* Set configuration */ + drac3_status, /* Return STONITH device status */ + drac3_reset_req, /* Request a reset */ + drac3_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; + +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 + , &drac3Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define BUFLEN 1024 +#define ST_HOST "host" + +struct pluginDevice { + StonithPlugin sp; + const char *pluginid; + const char *idinfo; + CURL *curl; + char *host; + char *user; + char *pass; +}; + +static const char *pluginid = "Dell-DRACIII-Stonith"; +static const char *NOTpluginID = "Dell DRACIII device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_HOST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOST \ + XML_PARM_SHORTDESC_END + +#define XML_HOST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOST_PARM \ + XML_PARAMETER_BEGIN(ST_HOST, "string", "1", "1") \ + XML_HOST_SHORTDESC \ + XML_HOST_LONGDESC \ + XML_PARAMETER_END + +static const char *drac3XML = + XML_PARAMETERS_BEGIN + XML_HOST_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* ------------------------------------------------------------------ */ +/* STONITH PLUGIN API */ +/* ------------------------------------------------------------------ */ +static StonithPlugin * +drac3_new(const char *subplugin) +{ + struct pluginDevice *drac3d = ST_MALLOCT(struct pluginDevice); + + if (drac3d == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(drac3d, 0, sizeof(*drac3d)); + drac3d->pluginid = pluginid; + drac3d->curl = curl_easy_init(); + drac3InitCurl(drac3d->curl); + drac3d->host = NULL; + drac3d->user = NULL; + drac3d->pass = NULL; + drac3d->idinfo = DEVICE; + drac3d->sp.s_ops = &drac3Ops; + return (&(drac3d->sp)); +} + +/* ------------------------------------------------------------------ */ +static void +drac3_destroy(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + + VOIDERRIFWRONGDEV(s); + + drac3d = (struct pluginDevice *) s; + + drac3d->pluginid = NOTpluginID; + + /* release curl connection */ + if (drac3d->curl != NULL) { + drac3Logout(drac3d->curl, drac3d->host); + curl_easy_cleanup(drac3d->curl); + drac3d->curl = NULL; + } + + if (drac3d->host != NULL) { + FREE(drac3d->host); + drac3d->host = NULL; + } + if (drac3d->user != NULL) { + FREE(drac3d->user); + drac3d->user = NULL; + } + if (drac3d->pass != NULL) { + FREE(drac3d->pass); + drac3d->pass = NULL; + } + + /* release stonith-object itself */ + FREE(drac3d); +} + +/* ------------------------------------------------------------------ */ +static const char * const * +drac3_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_HOST, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* ------------------------------------------------------------------ */ +static int +drac3_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOST, 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->host = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->pass = namestocopy[2].s_value; + + return(S_OK); +} + +/* ------------------------------------------------------------------ */ +const char * +drac3_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *drac3d; + const char *ret = NULL; + + ERRIFWRONGDEV(s,NULL); + + drac3d = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = drac3d->idinfo; + break; + case ST_DEVICENAME: + ret = drac3d->host; + break; + case ST_DEVICEDESCR: + ret = "Dell DRACIII (via HTTPS)\n" + "The Dell Remote Access Controller accepts XML " + "commands over HTTPS"; + break; + case ST_DEVICEURL: + ret = "http://www.dell.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = drac3XML; + break; + default: + ret = NULL; + break; + } + + return(ret); +} + +/* ------------------------------------------------------------------ */ +int +drac3_status(StonithPlugin *s) +{ + struct pluginDevice *drac3d; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + if (drac3GetSysInfo(drac3d->curl, drac3d->host)) { + return(S_ACCESS); + }else{ + return(S_OK); + } +} + +/* ------------------------------------------------------------------ */ +int +drac3_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *drac3d; + int rc = S_OK; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (strcasecmp(host, drac3d->host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , drac3d->idinfo, host); + return(S_BADHOST); + } + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + switch(request) { +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + /* TODO... */ +#endif + case ST_GENERIC_RESET: + if (drac3PowerCycle(drac3d->curl, drac3d->host)) + rc = S_ACCESS; + break; + default: + rc = S_INVAL; + break; + } + + return(rc); +} + +/* ------------------------------------------------------------------ */ +char ** +drac3_hostlist(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + char **hl; + + ERRIFNOTCONFIGED(s,NULL); + + drac3d = (struct pluginDevice *) s; + + hl = OurImports->StringToHostList(drac3d->host); + if (hl == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(hl[0]); + } + + return(hl); +} diff --git a/lib/plugins/stonith/drac3_command.c b/lib/plugins/stonith/drac3_command.c new file mode 100644 index 0000000..4d9002d --- /dev/null +++ b/lib/plugins/stonith/drac3_command.c @@ -0,0 +1,342 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include <curl/curl.h> + +#include <libxml/xmlmemory.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> + +#include "drac3_command.h" +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +#define DEBUG 0 + +/* Hardcoded XML commands and response codes */ +#define CMD_POWERCYCLE "<?XML version=\"1.0\"?><?RMCXML version=\"1.0\"?><RMCSEQ><REQ CMD=\"serveraction\"><ACT>powercycle</ACT></REQ></RMCSEQ>\n" +#define CMD_GETSYSINFO "<?XML version=\"1.0\"?><?RMCXML version=\"1.0\"?><RMCSEQ><REQ CMD=\"xml2cli\"><CMDINPUT>getsysinfo -A</CMDINPUT></REQ></RMCSEQ>\n" +#define RC_OK "0x0\n" + +struct Chunk { + char *memory; + size_t size; +}; + +/* prototypes */ +int xmlGetXPathString (const char *str, const char * expr, char * rc, const int len); +size_t writeFunction (void *ptr, size_t size, size_t nmemb, void *data); + + +/* ---------------------------------------------------------------------- * + * XML PARSING * + * ---------------------------------------------------------------------- */ + +int +xmlGetXPathString (const char *str, + const char * expr, + char * rc, + const int len) +{ + xmlDocPtr doc; + xmlNodePtr cur; + xmlXPathContextPtr ctx; + xmlXPathObjectPtr path; + xmlChar *xmlRC; + + if (!strchr(str,'<')) { + fprintf(stderr,"%s malformed\n", str); + rc[0] = 0x00; + return(1); + } + + doc = xmlParseMemory(str, strlen(str)); + xmlXPathInit(); + ctx = xmlXPathNewContext(doc); + path = xmlXPathEvalExpression((const xmlChar *)expr, ctx); + cur = path->nodesetval->nodeTab[0]; + + if (cur != NULL) { + xmlRC = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + snprintf(rc, len, "%s\n", xmlRC); + xmlFree(xmlRC); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + return(0); + } else { + fprintf(stderr,"error in obtaining XPath %s\n", expr); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + rc[0] = 0x00; + return(1); + } +} + + +/* ---------------------------------------------------------------------- * + * CURL CALLBACKS * + * ---------------------------------------------------------------------- */ + +size_t +writeFunction (void *ptr, size_t size, size_t nmemb, void *data) +{ + + register int realsize = size * nmemb; + struct Chunk *mem = (struct Chunk *)data; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory) { + memcpy(&(mem->memory[mem->size]), ptr, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + } + return realsize; +} + + +/* ---------------------------------------------------------------------- * + * DRAC3 CURL COMMANDS * + * ---------------------------------------------------------------------- */ + +int +drac3InitCurl (CURL *curl) +{ +#ifdef CURLOPT_NOSIGNAL + if (curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1)) return(1); +#endif + if (curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30)) return(1); + if (curl_easy_setopt(curl, CURLOPT_VERBOSE, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction)) return(1); + if (curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/dev/null")) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0)) return(1); + return(0); +} + +int +drac3Login (CURL *curl, + const char *host, + const char *user, + const char *pass) +{ + char url[BUFLEN]; + char chall[BUFLEN]; + char token[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) + return(1); + + /* ask for challenge */ + snprintf(url, BUFLEN, "https://%s/cgi/challenge", host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + /* extract challenge */ + status = xmlGetXPathString(chunk.memory, "//CHALLENGE", chall, BUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + /* calculate authToken */ + drac3AuthHash(chall, pass, token, BUFLEN); + + if (DEBUG) printf("T: %s\n", token); + + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + if (status) return(1); + chunk.memory = NULL; + chunk.size = 0; + + /* sends authToken */ + snprintf(url, BUFLEN, "https://%s/cgi/login?user=%s&hash=%s", + host, + user, + token); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3PowerCycle (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_POWERCYCLE; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3GetSysInfo (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_GETSYSINFO; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3Logout (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/logout", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3VerifyLogin (CURL *curl, + const char *host) +{ + /*We try to do a GetSysInfo */ + return(drac3GetSysInfo (curl, host)); +} + +/* -------------------------------------------------------------------- */ + diff --git a/lib/plugins/stonith/drac3_command.h b/lib/plugins/stonith/drac3_command.h new file mode 100644 index 0000000..cd03e15 --- /dev/null +++ b/lib/plugins/stonith/drac3_command.h @@ -0,0 +1,29 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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 + * + */ + +int drac3InitCurl (CURL *curl); +int drac3Login (CURL *curl, const char *host, const char *user, const char *pass); +int drac3PowerCycle (CURL *curl, const char *host); +int drac3GetSysInfo (CURL *curl, const char *host); +int drac3Logout (CURL *curl, const char *host); +int drac3VerifyLogin (CURL *curl, const char *host); + diff --git a/lib/plugins/stonith/drac3_hash.c b/lib/plugins/stonith/drac3_hash.c new file mode 100644 index 0000000..605a126 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.c @@ -0,0 +1,106 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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> + +#include <string.h> +#include <stdio.h> +#include <clplumbing/base64.h> +#include <clplumbing/md5.h> +#include <glib.h> + +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +/* Hash functions for DRAC3 authentication */ + +guint16 +drac3Crc16(const char *str, + const int l) { + + int i,j; + guint16 crc = 0; + + for (i=0; i<l; i++) { + crc = crc ^ (str[i] << 8); + for (j=0; j<8; j++) + crc = ( (crc & 0x8000) == 32768 ? (crc<<1) ^ 0x1021 : crc<<1); + } + crc = crc & 0xFFFF; + return crc; +} + +void +drac3AuthHash(const char * chall, + const char * pass, + char * token, + int len) { + + char * chall_dup; + char challBytes[MD5LEN]; + char passMD5[MD5LEN]; + char xorBytes[MD5LEN]; + char xorBytesMD5[MD5LEN]; + guint16 crc; + char response[MD5LEN+2]; + char responseb64[SBUFLEN]; + int i; + + /* decodes chall -> challBytes */ + memset(challBytes, 0, MD5LEN); + chall_dup = g_strdup(chall); + if (chall_dup[strlen(chall_dup) - 1] == '\n' ) { + chall_dup[strlen(chall_dup) - 1] = '\0'; + } + base64_to_binary(chall_dup, strlen(chall_dup), challBytes, MD5LEN); + + /* gets MD5 from pass -> passMD5 */ + MD5((const unsigned char *)pass, strlen(pass), (unsigned char *)passMD5); + + /* calculate challBytes and passMD5 xor -> xorBytes */ + for (i=0; i<MD5LEN; i++) { + xorBytes[i] = challBytes[i] ^ passMD5[i]; + } + + /* calculate xorBytes MD5 -> xorBytesMD5 */ + MD5((unsigned char *)xorBytes, MD5LEN, (unsigned char *)xorBytesMD5); + + /* calculate xorBytesMD5 crc16 */ + crc = drac3Crc16(xorBytesMD5, MD5LEN); + + /* joins xorBytesMD5 and crc16 -> response */ + memcpy(response, xorBytesMD5, MD5LEN); + memcpy(response+MD5LEN, &crc, 2); + + /* calculate response base64 -> responseb64 */ + memset(responseb64, 0, SBUFLEN); + binary_to_base64(response, MD5LEN+2, responseb64, SBUFLEN); + + /* assuring null-termination */ + responseb64[SBUFLEN-1]=0x00; + + snprintf(token, len, "%s", responseb64); + token[len-1]=0x00; +} diff --git a/lib/plugins/stonith/drac3_hash.h b/lib/plugins/stonith/drac3_hash.h new file mode 100644 index 0000000..fab2f58 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.h @@ -0,0 +1,28 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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 <sys/types.h> +#include <glib.h> + +guint16 drac3Crc16(const char *str, const int l); +void drac3AuthHash(const char *chall, const char *pass, char *token, int len); + diff --git a/lib/plugins/stonith/external.c b/lib/plugins/stonith/external.c new file mode 100644 index 0000000..da03665 --- /dev/null +++ b/lib/plugins/stonith/external.c @@ -0,0 +1,868 @@ +/* + * Stonith module for EXTERNAL Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * Portions Copyright (c) 2004, tummy.com, ltd. + * + * Based on ssh.c, Authors: Joachim Gleissner <jg@suse.de>, + * Lars Marowsky-Bree <lmb@suse.de> + * Modified for external.c: Scott Kleihege <scott@tummy.com> + * Reviewed, tested, and config parsing: Sean Reifschneider <jafo@tummy.com> + * And overhauled by Lars Marowsky-Bree <lmb@suse.de>, so the circle + * closes... + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Changed to allow full-featured external plugins by Dave Blaschke + * <debltc@us.ibm.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> + +#include <dirent.h> + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN external +#define PIL_PLUGIN_S "external" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include <pils/plugin.h> + +static StonithPlugin * external_new(const char *); +static void external_destroy(StonithPlugin *); +static int external_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * external_get_confignames(StonithPlugin *); +static const char * external_getinfo(StonithPlugin * s, int InfoType); +static int external_status(StonithPlugin * ); +static int external_reset_req(StonithPlugin * s, int request, const char * host); +static char ** external_hostlist(StonithPlugin *); + +static struct stonith_ops externalOps ={ + external_new, /* Create new STONITH object */ + external_destroy, /* Destroy STONITH object */ + external_getinfo, /* Return STONITH info string */ + external_get_confignames, /* Return STONITH info string */ + external_set_config, /* Get configuration from NVpairs */ + external_status, /* Return STONITH device status */ + external_reset_req, /* Request a reset */ + external_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; + +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 + , &externalOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * EXTERNAL STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * outputbuf; +}; + +static const char * pluginid = "ExternalDevice-Stonith"; +static const char * NOTpluginID = "External device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int external_run_cmd(struct pluginDevice *sd, const char *op, + char **output); +/* Just free up the configuration and the memory, if any */ +static void external_unconfig(struct pluginDevice *sd); + +static int +external_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "status"; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + rc = external_run_cmd(sd, op, NULL); + if (rc != 0) { + LOG(PIL_WARN, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +external_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int rc, i, namecount; + char ** ret; + char * output = NULL; + char * tmp; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + if (!output) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + return NULL; + } + + namecount = get_num_tokens(output); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + FREE(output); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(output, WHITESPACE); + while (tmp != NULL) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s host %s", + __FUNCTION__, sd->subplugin, tmp); + } + ret[i] = STRDUP(tmp); + if (!ret[i]) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + FREE(output); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + FREE(output); + + if (i == 0) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + stonith_free_hostlist(ret); + ret = NULL; + } + + return(ret); +} + +static int +external_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * args1and2; + int argslen; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host external-reset initiating on %s", host); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + switch (request) { + case ST_GENERIC_RESET: + op = "reset"; + break; + + case ST_POWEROFF: + op = "off"; + break; + + case ST_POWERON: + op = "on"; + break; + + default: + LOG(PIL_CRIT, "%s: Unknown stonith request %d", + __FUNCTION__, request); + return S_OOPS; + break; + } + + argslen = strlen(op) + strlen(host) + 2 /* 1 for blank, 1 for EOS */; + args1and2 = (char *)MALLOC(argslen); + if (args1and2 == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + return S_OOPS; + } + rc = snprintf(args1and2, argslen, "%s %s", op, host); + if (rc <= 0 || rc >= argslen) { + FREE(args1and2); + return S_OOPS; + } + + rc = external_run_cmd(sd, args1and2, NULL); + FREE(args1and2); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + return S_OK; + } + +} + +static int +external_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal); + + /* TODO: Maybe treat "" as delimeters too so + * whitespace can be passed to the plugins... */ + for (nv = info; nv->s_name; nv++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + g_hash_table_insert(sd->cmd_opts, key, value); + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + external_unconfig(sd); + + return(S_OOPS); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + if (key) { + FREE(key); + } + if (value) { + FREE(value); + } + return TRUE; +} + +static void +external_unconfig(struct pluginDevice *sd) { + if (sd->cmd_opts) { + g_hash_table_foreach_remove(sd->cmd_opts, + let_remove_eachitem, NULL); + g_hash_table_destroy(sd->cmd_opts); + sd->cmd_opts = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +external_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + /* make sure that command has not already been set */ + if (s->isconfigured) { + return(S_OOPS); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (external_get_confignames(s) == NULL) { + return(S_OOPS); + } + + for (p = sd->confignames; *p; p++) { + if (OurImports->GetValue(list, *p) == NULL) { + LOG(PIL_DEBUG, "Cannot get parameter %s from " + "StonithNVpair", *p); + } + } + } + + return external_parse_config_info(sd, list); +} + + +/* Only interested in regular files that are also executable */ +static int +exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_EXT_PLUGINDIR, dire->d_name); + if (rc <= 0 || rc >= FILENAME_MAX) { + return 0; + } + + if ((stat(filename, &statf) == 0) && + (S_ISREG(statf.st_mode)) && + (statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) { + if (statf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_WARN, "Executable file %s ignored " + "(writable by group/others)", filename); + return 0; + }else{ + return 1; + } + } + + return 0; +} + +/* + * Return STONITH config vars + */ +static const char * const * +external_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + const char * op = "getconfignames"; + int i, rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + /* return list of subplugin's required parameters */ + char *output = NULL, *pch; + int namecount; + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_DEBUG, "plugin output: %s", output); + } + } + + namecount = get_num_tokens(output); + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + if (output) { FREE(output); } + return NULL; + } + + /* now copy over confignames */ + pch = strtok(output, WHITESPACE); + for (i = 0; i < namecount; i++) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, pch); + } + sd->confignames[i] = STRDUP(pch); + pch = strtok(NULL, WHITESPACE); + } + FREE(output); + sd->confignames[namecount] = NULL; + }else{ + /* return list of subplugins in external directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the external plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_EXT_PLUGINDIR, &files, + SCANSEL_CAST exec_select, NULL); + if (dircount < 0) { + return NULL; + } + + sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *)); + if (!sd->confignames) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + + for (i = 0; i < dircount; i++) { + sd->confignames[i] = STRDUP(files[i]->d_name); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +external_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + char * output = NULL; + const char * op; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + sd = (struct pluginDevice *)s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + break; + + case ST_DEVICENAME: + op = "getinfo-devname"; + break; + + case ST_DEVICEDESCR: + op = "getinfo-devdescr"; + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + break; + + case ST_CONF_XML: + op = "getinfo-xml"; + break; + + default: + return NULL; + } + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + } + sd->outputbuf = output; + return(output); + } + return(NULL); +} + +/* + * EXTERNAL Stonith destructor... + */ +static void +external_destroy(StonithPlugin *s) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginID; + external_unconfig(sd); + if (sd->confignames != NULL) { + for (p = sd->confignames; *p; p++) { + FREE(*p); + } + FREE(sd->confignames); + sd->confignames = NULL; + } + if (sd->subplugin != NULL) { + FREE(sd->subplugin); + sd->subplugin = NULL; + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + FREE(sd); +} + +/* Create a new external Stonith device */ +static StonithPlugin * +external_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + if (subplugin != NULL) { + sd->subplugin = STRDUP(subplugin); + if (sd->subplugin == NULL) { + FREE(sd); + return(NULL); + } + } + sd->sp.s_ops = &externalOps; + return &(sd->sp); +} + +static void +ext_add_to_env(gpointer key, gpointer value, gpointer user_data) +{ + if (setenv((char *)key, (char *)value, 1) != 0) { + LOG(PIL_CRIT, "%s: setenv failed.", __FUNCTION__); + } +} + +static void +ext_del_from_env(gpointer key, gpointer value, gpointer user_data) +{ + unsetenv((char *)key); +} + +#define LOGTAG_VAR "HA_LOGTAG" + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ +static int +external_run_cmd(struct pluginDevice *sd, const char *op, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int status, rc; + char * data = NULL; + FILE * file; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + char *path, *new_path, *logtag, *savevar = NULL; + int new_path_len, logtag_len; + gboolean nodata; + + rc = snprintf(cmd, FILENAME_MAX, "%s/%s", + STONITH_EXT_PLUGINDIR, sd->subplugin); + if (rc <= 0 || rc >= FILENAME_MAX) { + LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__); + return -1; + } + + if (stat(cmd, &buf) != 0) { + LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s", + __FUNCTION__, cmd, strerror(errno)); + return -1; + } + + if (!S_ISREG(buf.st_mode) + || (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + LOG(PIL_CRIT, "%s: %s found NOT to be executable.", + __FUNCTION__, cmd); + return -1; + } + + if (buf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_CRIT, "%s: %s found to be writable by group/others, " + "NOT executing for security purposes.", + __FUNCTION__, cmd); + return -1; + } + + strcat(cmd, " "); + strcat(cmd, op); + + /* We only have a global environment to use here. So we add our + * options to it, and then later remove them again. */ + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_add_to_env, NULL); + } + + /* external plugins need path to ha_log.sh */ + path = getenv("PATH"); + if (strncmp(GLUE_SHARED_DIR,path,strlen(GLUE_SHARED_DIR))) { + new_path_len = strlen(path)+strlen(GLUE_SHARED_DIR)+2; + new_path = (char *)g_malloc(new_path_len); + snprintf(new_path, new_path_len, "%s:%s", GLUE_SHARED_DIR, path); + setenv("PATH", new_path, 1); + g_free(new_path); + } + + /* set the logtag appropriately */ + logtag_len = strlen(PIL_PLUGIN_S)+strlen(sd->subplugin)+2; + logtag = (char *)g_malloc(logtag_len); + snprintf(logtag, logtag_len, "%s/%s", PIL_PLUGIN_S, sd->subplugin); + if (getenv(LOGTAG_VAR)) { + savevar = g_strdup(getenv(LOGTAG_VAR)); + } + setenv(LOGTAG_VAR, logtag, 1); + g_free(logtag); + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + file = popen(cmd, "r"); + if (NULL==file) { + LOG(PIL_CRIT, "%s: Calling '%s' failed", + __FUNCTION__, cmd); + rc = -1; + goto out; + } + + if (output) { + slen=0; + data = MALLOC(1); + data[slen] = EOS; + } + while (!feof(file)) { + nodata = TRUE; + if (output) { + read_len = fread(buff, 1, BUFF_LEN, file); + if (read_len > 0) { + data = REALLOC(data, slen+read_len+1); + if (data == NULL) { + break; + } + memcpy(data + slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + nodata = FALSE; + } + } else { + if (fgets(buff, BUFF_LEN, file)) { + LOG(PIL_INFO, "%s: '%s' output: %s", __FUNCTION__, cmd, buff); + nodata = FALSE; + } + } + if (nodata) { + sleep(1); + } + } + if (output && !data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + rc = -1; + goto out; + } + + status = pclose(file); + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + if (rc != 0 && Debug) { + LOG(PIL_DEBUG, + "%s: Calling '%s' returned %d", __FUNCTION__, cmd, rc); + } + } else { + if (WIFSIGNALED(status)) { + LOG(PIL_CRIT, "%s: '%s' got signal %d", + __FUNCTION__, cmd, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + LOG(PIL_INFO, "%s: '%s' stopped with signal %d", + __FUNCTION__, cmd, WSTOPSIG(status)); + } else { + LOG(PIL_CRIT, "%s: '%s' exited abnormally (core dumped?)", + __FUNCTION__, cmd); + } + rc = -1; + } + if (Debug && output && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + +out: + if (savevar) { + setenv(LOGTAG_VAR, savevar, 1); + g_free(savevar); + } else { + unsetenv(LOGTAG_VAR); + } + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_del_from_env, NULL); + } + if (!rc) { + if (output) { + *output = data; + } + } else { + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + } + return rc; +} diff --git a/lib/plugins/stonith/external/Makefile.am b/lib/plugins/stonith/external/Makefile.am new file mode 100644 index 0000000..42e0046 --- /dev/null +++ b/lib/plugins/stonith/external/Makefile.am @@ -0,0 +1,33 @@ +# Makefile.am for OCF RAs +# +# Author: Sun Jing Dong +# Copyright (C) 2004 IBM +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = drac5 dracmc-telnet ibmrsa-telnet ipmi rackpdu vmware vcenter xen0 \ + xen0-ha-dom0-stonith-helper kdumpcheck nut + +extdir = $(stonith_ext_plugindir) + +helperdir = $(stonith_plugindir) + +ext_SCRIPTS = drac5 dracmc-telnet ibmrsa ibmrsa-telnet ipmi riloe ssh vmware vcenter rackpdu xen0 hmchttp \ + xen0-ha kdumpcheck ippower9258 nut libvirt \ + hetzner + +helper_SCRIPTS = xen0-ha-dom0-stonith-helper diff --git a/lib/plugins/stonith/external/drac5.in b/lib/plugins/stonith/external/drac5.in new file mode 100644 index 0000000..218cbd3 --- /dev/null +++ b/lib/plugins/stonith/external/drac5.in @@ -0,0 +1,113 @@ +#!/bin/sh +# +# External STONITH module for DRAC5 adapters. +# +# Author: Jun Wang +# License: GNU General Public License (GPL) +# + +trap 'if [ -n "$outf" ]; then ha_log.sh err "`cat $outf`"; rm -f "$outf"; fi' 0 +outf=`mktemp` || { + ha_log.sh err "mktemp failed" + exit 1 +} + +sshlogin() { + if [ x = "x$ipaddr" -o x = "x$userid" ] + then + ha_log.sh err "ipaddr or userid missing; check configuration" + return 1 + fi + @SSH@ -q -x -n $userid@$ipaddr racadm serveraction "$1" >$outf 2>&1 +} + +drac_reset() { + sshlogin hardreset +} + +drac_on() { + sshlogin poweron +} + +drac_off() { + sshlogin poweroff +} + +drac_status() { + sshlogin powerstatus +} + +case $1 in +gethosts) + echo $hostname + ;; +on) + drac_poweron + ;; +off) + drac_poweroff + ;; +reset) + drac_reset + ;; +status) + drac_status + ;; +getconfignames) + for i in hostname ipaddr userid; do + echo $i + done + ;; +getinfo-devid) + echo "DRAC5 STONITH device" + ;; +getinfo-devname) + echo "DRAC5 STONITH device" + ;; +getinfo-devdescr) + echo "DRAC5 host reset/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.dell.com" + ;; +getinfo-xml) + cat <<EOF +<parameters> + +<parameter name="hostname" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The hostname of the host to be managed by this STONITH device +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device +</longdesc> +</parameter> + +<parameter name="userid" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used for logging in to the STONITH device +</longdesc> +</parameter> + +</parameters> +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/dracmc-telnet b/lib/plugins/stonith/external/dracmc-telnet new file mode 100644 index 0000000..d993961 --- /dev/null +++ b/lib/plugins/stonith/external/dracmc-telnet @@ -0,0 +1,377 @@ +#!/usr/bin/env python +# vim: set filetype=python +####################################################################### +# +# dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to Dell Drac/MC Blade Enclosure via a Cyclades +# terminal server with telnet and switches power of named +# blade servers appropriatelly. +# +# Required parameters: +# nodename: The name of the server you want to touch on your network +# cyclades_ip: The IP address of the cyclades terminal server +# cyclades_port: The port for telnet to access on the cyclades (i.e. 7032) +# servername: The DRAC/MC server name of the blade (i.e. Server-7) +# username: The login user name for the DRAC/MC +# password: The login password for the DRAC/MC +# +# Author: Alex Tsariounov <alext@novell.com> +# +# Based on ibmrsa-telnet external stonith plugin by Andreas Mock +# (andreas.mock@web.de), Copyright by Adreas Mock and released as part +# of HAv2. +# +# History: +# 2009-10-12 First release. +# +# Copyright (c) 2009 Novell, Inc. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import random +import subprocess + +LOGINRETRIES = 10 + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class DracMC(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self) + self._timeout = 4 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + self._server = args[0] + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer): + self._history.append(self._get_timestamp() + ': WRITE: ' + repr(buffer)) + telnetlib.Telnet.write(self, buffer) + + def read_until(self, what, timeout=2): + line = telnetlib.Telnet.read_until(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line.endswith(what): + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(0.3) + try: + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + except: + self.write("\r") + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + try: + line = self.read_until('DRAC/MC:', self._timeout) + except: + self.write("\r") + line = self.read_until('DRAC/MC:', self._timeout) + + def hardreset(self): + self.write('serveraction -s %s hardreset\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def powercycle(self): + self.write('serveraction -s %s powercycle\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def on(self): + self.write('serveraction -s %s powerup\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def off(self): + self.write('serveraction -s %s powerdown\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class DracMCStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'cyclades_ip', 'cyclades_port', + 'servername', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + subprocess.call("ha_log.sh debug '%s'" % ' '.join(args), shell=True) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call("ha_log.sh %s '%s'" % (level,' '.join(args)), shell=True) + + def _get_connection(self): + if not self._connection: + c = DracMC(self._parameters['servername']) + self._echo_debug("Connecting to '%s:%s'" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + tries = 0 + while tries < LOGINRETRIES: + try: + c.open(self._parameters['cyclades_ip'], + self._parameters['cyclades_port']) + c.login(self._parameters['username'], + self._parameters['password']) + except Exception, args: + if "Connection reset by peer" in str(args): + self._echo_debug("Someone is already logged in... retry=%s" % tries) + c.close() + time.sleep(random.uniform(1.0, 5.0)) + else: + raise + else: + break + tries += 1 + + if tries == LOGINRETRIES: + c.close() + raise Exception("Could not log in to %s:%s" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + # self._connection.hardreset() + self._connection.powercycle() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'cyclades_ip', 'cyclades_port', 'servername', + 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for Dell DRAC/MC via Cyclades") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for Dell Drac/MC connecting " + "via Telnet to a Cyclades port") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a Dell DRAC/MC connected via a Cyclades port with telnet. " + "Commands to turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2009 by Novell, Inc. (alext@novell.com)") + return(0) + + def getinfo_devurl(self): + self.echo("http://support.dell.com/support/edocs/software/smdrac3/dracmc/1.3/en/index.htm") + + def getinfo_xml(self): + info = """<parameters> + <parameter name="nodename" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">nodename to shoot</shortdesc> + <longdesc lang="en"> + Name of the node to be stonithed. + </longdesc> + </parameter> + <parameter name="cyclades_ip" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">hostname or ip address of cyclades</shortdesc> + <longdesc lang="en"> + Hostname or IP address of Cyclades connected to DRAC/MC. + </longdesc> + </parameter> + <parameter name="cyclades_port" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">telnet port to use on cyclades</shortdesc> + <longdesc lang="en"> + Port used with the Cyclades telnet interface which is connected to the DRAC/MC. + </longdesc> + </parameter> + <parameter name="servername" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">DRAC/MC name of blade to be stonithed</shortdesc> + <longdesc lang="en"> + Name of server blade to be stonithed on the DRAC/MC (example: Server-7) + </longdesc> + </parameter> + <parameter name="username" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">username to login on the DRAC/MC</shortdesc> + <longdesc lang="en"> + Username to login to the DRAC/MC once connected via the Cyclades port. + </longdesc> + </parameter> + <parameter name="password" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">password to login on the DRAC/MC</shortdesc> + <longdesc lang="en"> + Password to login to the DRAC/MC once connected via the Cyclades port. + </longdesc> + </parameter> + </parameters> + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = DracMCStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/hetzner b/lib/plugins/stonith/external/hetzner new file mode 100755 index 0000000..2b3e675 --- /dev/null +++ b/lib/plugins/stonith/external/hetzner @@ -0,0 +1,139 @@ +#!/bin/sh +# +# External STONITH module for Hetzner. +# +# Copyright (c) 2011 MMUL S.a.S. - Raoul Scarazzini <rasca@mmul.it> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# Read parameters from config file, format is based upon the hetzner OCF resource agent +# developed by Kumina: http://blog.kumina.nl/2011/02/hetzner-failover-ip-ocf-script/ +conf_file="/etc/hetzner.cfg" + +case $1 in + get*) ;; # don't print errors if conf_file not present + *) + user=`sed -n 's/^user.*=\ *//p' $conf_file` + pass=`sed -n 's/^pass.*=\ *//p' $conf_file` + ;; +esac + +hetzner_server="https://robot-ws.your-server.de" + +check_http_response() { + # If the response is 200 then return 0 + if [ $1 = 200 ] + then + return 0 + else + # If the response is not 200 then display a description of the problem and return 1 + case $1 in + 400) ha_log.sh err "INVALID_INPUT - Invalid input parameters" + ;; + 404) ha_log.sh err "SERVER_NOT_FOUND - Server with ip $remote_ip not found" + ;; + 409) ha_log.sh err "RESET_MANUAL_ACTIVE - There is already a running manual reset" + ;; + 500) ha_log.sh err "RESET_FAILED - Resetting failed due to an internal error" + ;; + esac + return 1 + fi +} + +case $1 in +gethosts) + echo $hostname + exit 0 + ;; +on) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power on is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +off) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power off is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +reset) + # Launching the reset action via webservice + check_http_response $(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/reset/$remote_ip -d type=hw) + exit $? + ;; +status) + # Check if we can contact the webservice + check_http_response "$(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/server/$remote_ip)" + exit $? + ;; +getconfignames) + echo "hostname" + echo "remote_ip" + exit 0 + ;; +getinfo-devid) + echo "Hetzner STONITH device" + exit 0 + ;; +getinfo-devname) + echo "Hetzner STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "Hetzner host reset" + echo "Manages the remote webservice for reset a remote server." + exit 0 + ;; +getinfo-devurl) + echo "http://wiki.hetzner.de/index.php/Robot_Webservice_en" + exit 0 + ;; +getinfo-xml) + cat << HETZNERXML +<parameters> +<parameter name="hostname" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +</longdesc> +</parameter> + +<parameter name="remote_ip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Remote IP +</shortdesc> +<longdesc lang="en"> +The address of the remote IP that manages this server. +</longdesc> +</parameter> +</parameters> +HETZNERXML + exit 0 + ;; +*) + ha_log.sh err "Don't know what to do for '$remote_ip'" + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/hmchttp b/lib/plugins/stonith/external/hmchttp new file mode 100644 index 0000000..9d111bc --- /dev/null +++ b/lib/plugins/stonith/external/hmchttp @@ -0,0 +1,218 @@ +#!/bin/sh +# External STONITH module for HMC web console +# +# Copyright (c) 2007 Xinwei Hu <hxinwei@gmail.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +#set -x +hostlist=`echo $hostlist | tr ',' ' '` + +trap '[ ! -e "$COOKIEFILE" ] || rm -f "$COOKIEFILE"' 0 +COOKIEFILE=`mktemp` || exit 1 + +: ${CURLBIN="/usr/bin/curl"} +: ${user=admin} +: ${password=admin} + +check_parameter() { + if [ ! -x $CURLBIN ] + then + ha_log.sh err "Curl can't be found in normal place. Set CURLBIN to override the default value" + exit 1 + fi + + if [ -z $hmc_ipaddr ] + then + ha_log.sh err "The address of HMC web console is not specified" + exit 1 + fi +} + +HMCUSERNAME=$user +HMCPASSWORD=$password + +HMC_LOGIN_COMMAND="$CURLBIN -3 -k -c $COOKIEFILE -d user=$HMCUSERNAME -d password=$HMCPASSWORD -d lang=0 -d submit=Log+in " +HMC_LOGOUT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d submit=Log+out " +HMC_TOC_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=2 " +HMC_POWERON_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 -d sp=255 -d is=0 -d om=4 -d id=1 -d ip=2 -d plt=1 -d pm=0 -d on=Save+settings+and+power+on " +HMC_POWERSTATE_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 " +HMC_POWEROFF_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=30 -d submit=Continue " +HMC_REBOOT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=74 -d submit=Continue " + +hmc_login() { + iamin=0 + while [ $iamin -eq 0 ]; do + $HMC_LOGIN_COMMAND https://$hmc_ipaddr/cgi-bin/cgi >/dev/null 2>&1 + $HMC_TOC_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Too many users" + iamin=$? + sleep 2 + done +} +hmc_logout() { + $HMC_LOGOUT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} + +hmc_reboot() { + check_parameter + $HMC_REBOOT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_poweron() { + r=1 + while [ 0 -ne $r ]; do + $HMC_POWERON_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Operation completed successfully" + r=$? + done +} +hmc_poweroff() { + check_parameter + $HMC_POWEROFF_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_powerstate() { + check_parameter + r=`$HMC_POWERSTATE_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null| grep "Current system power state:" | sed 's/<br>//g' | awk '{print $5}'` + echo $r +} + +hmc_poweroffon() { + check_parameter + hmc_poweroff + while [ 1 ]; do + r=`hmc_powerstate` + ha_log.sh debug "power state: $r" + if [ $r = "Off" ]; then + break + fi + sleep 5 + done + sleep 3 + hmc_poweron +} + +case $1 in +gethosts) + for h in $hostlist; do + echo $h + done + exit 0 + ;; +status) + if + ping -w1 -c1 "$hmc_ipaddr" 2>&1 + then + exit 0 + fi + exit 1 + ;; +getconfignames) + for f in hostlist hmc_ipaddr user password; do + echo $f + done + exit 0 + ;; +getinfo-devid) + echo "HMC web console STONITH device" + exit 0 + ;; +getinfo-devname) + echo "HMC web console STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "HMC web console based host power control" + echo "Use for i5, p5, pSeries and OpenPower systems that are managed via " + echo "web console through a direct connection to system's HMC port." + exit 0 + ;; +getinfo-devurl) + echo "http://www.ibm.com" + exit 0 + ;; +getinfo-xml) + cat << HMCXML +<parameters> + +<parameter name="hostlist" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">Hostlist</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="hmc_ipaddr" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">HMC IPAddr</shortdesc> +<longdesc lang="en"> +The IP address of the HMC web console +</longdesc> +</parameter> + +<parameter name="user" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">User</shortdesc> +<longdesc lang="en"> +User name to log into HMC web console +</longdesc> +</parameter> + +<parameter name="password" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">Password</shortdesc> +<longdesc lang="en"> +The password of user name to log into HMC web console +</longdesc> +</parameter> + +</parameters> +HMCXML + exit 0 + ;; +esac + +case $1 in +on|off|reset|powerstate|poweroffon) + hmc_login + case $1 in + on) + hmc_poweron $hmc_ipaddr + ;; + off) + hmc_poweroff $hmc_ipaddr + ;; + reset) +# hmc_reboot $hmc_ipaddr + hmc_poweroffon $hmc_ipaddr + ;; + powerstate) + hmc_powerstate + ;; + poweroffon) + hmc_poweroffon + ;; + esac + hmc_logout + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa b/lib/plugins/stonith/external/ibmrsa new file mode 100644 index 0000000..7408465 --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2006 Dejan Muhamedagic <dmuhamedagic@at.ibm.com>, IBM Austria +# +# External STONITH module for IBM RSA adapters. +# External STONITH module for IBM BMC. +# This STONITH module depends on IBMmpcli. +# + +trap 'rm -f "$outf"' 0 +outf=`mktemp` || { + ha_log.sh err 'mktemp failed' + exit 1 +} + +chkmpcli() { + test -x /opt/IBMmpcli/bin/MPCLI.sh +} +mpcli() { + chkmpcli || { + ha_log.sh err "IBM mpcli not installed" + return 1 + } + if [ x = "x$ipaddr" -o x = "x$userid" -o x = "x$passwd" ] + then + ha_log.sh err "ipaddr, userid, or passwd missing; check configuration" + return 1 + fi + type=${type:-"ibm"} + + goodstg="SUCCESS" + failstg="FAILURE" + ( + echo "logonip -h $ipaddr -u $userid -p $passwd -t $type" + echo "outputfile $outf" + cat + ) | /opt/IBMmpcli/bin/MPCLI.sh | grep -w $goodstg >/dev/null 2>&1 + rc=$? + grep -w $failstg $outf >/dev/null + if [ $rc -eq 0 -a $? -eq 1 ]; then + return 0 + else + ha_log.sh err "MPCLI.sh failed: `cat $outf`" + return 1 + fi +} +ibmrsa_reboot() { + echo restart -now | mpcli +} +ibmrsa_poweron() { + echo poweron | mpcli +} +ibmrsa_poweroff() { + echo poweroff | mpcli +} +ibmrsa_status() { + echo | mpcli +} + +hostname=`echo ${hostname} | tr ',' ' '` + +case $1 in +gethosts) + echo $hostname + ;; +on) + ibmrsa_poweron + ;; +off) + ibmrsa_poweroff + ;; +reset) + ibmrsa_reboot + ;; +status) + ibmrsa_status + ;; +getconfignames) + for i in hostname ipaddr userid passwd type; do + echo $i + done + ;; +getinfo-devid) + echo "IBM MP STONITH device" + ;; +getinfo-devname) + echo "IBM MP STONITH device" + ;; +getinfo-devdescr) + echo "IBM MP host reboot/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.ibm.com" + ;; +getinfo-xml) + cat <<EOF +<parameters> + +<parameter name="hostname" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The hostname of the host to be managed by this STONITH device +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device +</longdesc> +</parameter> + +<parameter name="userid" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used to login into the STONITH device +</longdesc> +</parameter> + +<parameter name="passwd" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password used to login into the STONITH device +</longdesc> +</parameter> + +<parameter name="type" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Management processor type +</shortdesc> +<longdesc lang="en"> +The type of the management processor. Possible values are +"ibm" (default, typically used for RSA) and "ipmi" +(for IPMI compliant processors such as BMC). +</longdesc> +</parameter> + +</parameters> +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa-telnet b/lib/plugins/stonith/external/ibmrsa-telnet new file mode 100644 index 0000000..4d75d9a --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa-telnet @@ -0,0 +1,320 @@ +#!/usr/bin/python +# vim: set filetype=python +####################################################################### +# +# ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to IBM RSA Board via telnet and switches power +# of server appropriately. +# +# Author: Andreas Mock (andreas.mock@web.de) +# +# History: +# 2007-10-19 Fixed bad commandline handling in case of stonithing +# 2007-10-11 First release. +# +# Comment: Please send bug fixes and enhancements. +# I hope the functionality of communicating via telnet is encapsulated +# enough so that someone can use it for similar purposes. +# +# Description: IBM offers Remote Supervisor Adapters II for several +# servers. These RSA boards can be accessed in different ways. +# One of that is via telnet. Once logged in you can use 'help' to +# show all available commands. With 'power' you can reset, power on and +# off the controlled server. This command is used in combination +# with python's standard library 'telnetlib' to do it automatically. +# +# cib-snippet: Please see README.ibmrsa-telnet for examples. +# +# Copyright (c) 2007 Andreas Mock (andreas.mock@web.de) +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import subprocess + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class RSABoard(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self, *args, **kwargs) + self._timeout = 10 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer, nolog = False): + self._history.append(self._get_timestamp() + ': WRITE: ' + + (nolog and '******' or repr(buffer))) + telnetlib.Telnet.write(self, buffer) + + def expect(self, what, timeout=20): + line = telnetlib.Telnet.expect(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line: + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(1) + line = self.expect(['\nlogin : ', '\nusername: '], self._timeout) + self.write(user) + self.write('\r') + line = self.expect(['\nPassword: ', '\npassword: '], self._timeout) + self.write(passwd, nolog = True) + self.write('\r') + line = self.expect(['\nsystem>', '> '], self._timeout) + + def reset(self): + self.write('power cycle\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def on(self): + self.write('power on\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def off(self): + self.write('power off\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class RSAStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'ip_address', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + self.echo_log('debug', *args) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call(('ha_log.sh', level) + args) + + def _get_connection(self): + if not self._connection: + c = RSABoard() + self._echo_debug("Connect to '%s'" % + (self._parameters['ip_address'],)) + c.open(self._parameters['ip_address']) + self._echo_debug("Connection established") + c.login(self._parameters['username'], + self._parameters['password']) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + self._connection.reset() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'ip_address', 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for IBM RSA Boards") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for IBM RSA Boards connecting " + "via Telnet") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a RSA board on IBM servers via telnet. Commands to " + "turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2007 by Andreas Mock (andreas.mock@web.de)") + return(0) + + def getinfo_devurl(self): + self.echo("http://www.ibm.com/Search/?q=remote+supervisor+adapter") + + def getinfo_xml(self): + info = """<parameters> + <parameter name="nodename" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">nodename to shoot</shortdesc> + <longdesc lang="en"> + Name of the node which has to be stonithed in case. + </longdesc> + </parameter> + <parameter name="ip_address" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">hostname or ip address of RSA</shortdesc> + <longdesc lang="en"> + Hostname or ip address of RSA board used to reset node. + </longdesc> + </parameter> + <parameter name="username" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">username to login on RSA board</shortdesc> + <longdesc lang="en"> + Username to login on RSA board. + </longdesc> + </parameter> + <parameter name="password" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">password to login on RSA board</shortdesc> + <longdesc lang="en"> + Password to login on RSA board. + </longdesc> + </parameter> + </parameters> + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = RSAStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/ipmi b/lib/plugins/stonith/external/ipmi new file mode 100644 index 0000000..abadd5a --- /dev/null +++ b/lib/plugins/stonith/external/ipmi @@ -0,0 +1,276 @@ +#!/bin/sh +# +# External STONITH module using IPMI. +# This modules uses uses the ipmitool program available from +# http://ipmitool.sf.net/ for actual communication with the +# managed device. +# +# Copyright (c) 2007 Martin Bene <martin.bene@icomedias.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# Initialization -- fix locale settings so we can parse output from +# binaries if we need it +LANG=C +LC_ALL=C + +RESET="power reset" +POWEROFF="power off" +POWERON="power on" +STATUS="power status" +IPMITOOL=${ipmitool:-"`which ipmitool 2>/dev/null`"} + +have_ipmi() { + test -x "${IPMITOOL}" +} + +# Wrapper function for ipmitool that sets the correct host IP address, +# username, and password, and invokes ipmitool with any arguments +# passed in +run_ipmitool() { + local ipmitool_opts privlvl="" + have_ipmi || { + ha_log.sh err "ipmitool not installed" + return 1 + } + if [ -z "${ipaddr}" -o -z "${userid}" -o -z "${passwd}" ]; then + ha_log.sh err "ipaddr, userid or passwd missing; check configuration" + return 1 + fi + + if [ -z "${interface}" ]; then + # default to "lan" interface + interface="lan" + fi + if [ -n "${priv}" ]; then + # default to "lan" interface + privlvl="-L $priv" + fi + + ipmitool_opts="-I ${interface} -H ${ipaddr} $privlvl" + + case "${passwd_method}" in + param|'') + passwd_method=param + M="-P" + ;; + env) + M="-E" + ;; + file) + M="-f" + ;; + *) + ha_log.sh err "invalid passwd_method: \"${passwd_method}\"" + return 1 + esac + + action="$*" + + if [ $passwd_method = env ] + then + IPMI_PASSWORD="${passwd}" ${IPMITOOL} $ipmitool_opts -U "${userid}" -E ${action} + else + ${IPMITOOL} $ipmitool_opts -U "${userid}" $M "${passwd}" ${action} + fi 2>&1 +} + +# Yet another convenience wrapper that invokes run_ipmitool, captures +# its output, logs the output, returns either 0 (on success) or 1 (on +# any error) +do_ipmi() { + if outp=`run_ipmitool $*`; then + ha_log.sh debug "ipmitool output: `echo $outp`" + return 0 + else + ha_log.sh err "error executing ipmitool: `echo $outp`" + return 1 + fi +} + +# Check if the managed node is powered on. To do so, issue the "power +# status" command. Should return either "Chassis Power is on" or +# "Chassis Power is off". +ipmi_is_power_on() { + local outp + outp=`run_ipmitool ${STATUS}` + case "${outp}" in + *on) + return 0 + ;; + *off) + return 1 + ;; + esac +} + + +case ${1} in +gethosts) + echo $hostname + exit 0 + ;; +on) + do_ipmi "${POWERON}" + exit + ;; +off) + do_ipmi "${POWEROFF}" + exit + ;; +reset) + if ipmi_is_power_on; then + do_ipmi "${RESET}" + else + do_ipmi "${POWERON}" + fi + exit + ;; +status) + # "status" reflects the status of the stonith _device_, not + # the managed node. Hence, only check if we can contact the + # IPMI device with "power status" command, don't pay attention + # to whether the node is in fact powered on or off. + do_ipmi "${STATUS}" + exit $? + ;; +getconfignames) + for i in hostname ipaddr userid passwd interface; do + echo $i + done + exit 0 + ;; +getinfo-devid) + echo "IPMI STONITH device" + exit 0 + ;; +getinfo-devname) + echo "IPMI STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ipmitool based power management. Apparently, the power off" + echo "method of ipmitool is intercepted by ACPI which then makes" + echo "a regular shutdown. If case of a split brain on a two-node" + echo "it may happen that no node survives. For two-node clusters" + echo "use only the reset method." + exit 0 + ;; +getinfo-devurl) + echo "http://ipmitool.sf.net/" + exit 0 + ;; +getinfo-xml) + cat << IPMIXML +<parameters> +<parameter name="hostname" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device. +</longdesc> +</parameter> + +<parameter name="userid" unique="0"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used for logging in to the STONITH device. +</longdesc> +</parameter> + +<parameter name="passwd" unique="0"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password used for logging in to the STONITH device. +</longdesc> +</parameter> + +<parameter name="passwd_method" unique="0"> +<content type="string" default="param"/> +<shortdesc lang="en"> +Method for passing passwd parameter +</shortdesc> +<longdesc lang="en"> +Method for passing the passwd parameter to ipmitool + param: pass as parameter (-P) + env: pass via environment (-E) + file: value of "passwd" is actually a file name, pass with (-f) +</longdesc> +</parameter> + +<parameter name="interface" unique="0"> +<content type="string" default="lan"/> +<shortdesc lang="en"> +IPMI interface +</shortdesc> +<longdesc lang="en"> +IPMI interface to use, such as "lan" or "lanplus". +</longdesc> +</parameter> + +<parameter name="priv" unique="0"> +<content type="string" default=""/> +<shortdesc lang="en"> +The privilege level of the user. +</shortdesc> +<longdesc lang="en"> +The privilege level of the user, for instance OPERATOR. If +unspecified the privilege level is ADMINISTRATOR. See +ipmitool(1) -L option for more information. +</longdesc> +</parameter> + +<parameter name="ipmitool" unique="0"> +<content type="string" default=""/> +<shortdesc lang="en"> +IPMI command(ipmitool) +</shortdesc> +<longdesc lang="en"> +Specify the full path to IPMI command. +</longdesc> +</parameter> + +</parameters> +IPMIXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ippower9258.in b/lib/plugins/stonith/external/ippower9258.in new file mode 100755 index 0000000..6ae7e02 --- /dev/null +++ b/lib/plugins/stonith/external/ippower9258.in @@ -0,0 +1,316 @@ +#!/bin/sh +# +# External STONITH module using IP Power 9258 or compatible devices. +# +# Copyright (c) 2010 Helmut Weymann (Helmut (at) h-weymann (dot) de) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# +# Basic commands & parameters independent from individual device + +DEVICE="IP Power 9258" +IPPowerOn="1" +IPPowerOff="0" +IPGetPower="Set.cmd?CMD=GetPower" +IPSetPower="Set.cmd?CMD=SetPower" +IPPort_name="P" +IPPort0=60 +HTTP_COMMAND="wget -q -O - --" +LOG_ERROR="ha_log.sh err" +LOG_WARNING="ha_log.sh warn" +LOG_INFO="ha_log.sh info" +LOG_DEBUG="ha_log.sh debug" +MY_COOKIES="cookies.txt" +MY_TEMPFILE="temp.htm" +PORT_STATUS="iocontrol.htm" +UNDEFINED_HOSTNAME="*not-defined*" + +# +# check MY_ROOT_PATH for IP Power 9258 and create it if necessary +MY_ROOT_PATH="@GLUE_STATE_DIR@/heartbeat/rsctmp/ippower9258" + +# +# script functions +# + +get_challenge() { + # + # device sends a challenge for md5 encryption of username, password and challenge + send_web_command - "http://$deviceip/" | grep Challenge | grep input | cut -d '"' -f 6 +} + +get_cookie_from_device(){ + # the form on the login page has these fields: + # Username, Password, Challenge, Response, ScreenWidth + # + challenge=`get_challenge` + response=`echo -n "$username$password$challenge" | md5sum | cut -b -32` + postdata="Username=$username&Password=&Challenge=&Response=$response&ScreenWidth=1024" + send_web_command " $MY_PATH/$MY_TEMPFILE --post-data=$postdata" "http://$deviceip/tgi/login.tgi" + if grep -qs "Invalid User name or Password" $MY_PATH/$MY_TEMPFILE + then + $LOG_ERROR "Login to device $deviceip failed." + $LOG_ERROR "Received Challenge = <<<$challenge>>>." + $LOG_ERROR "Sent postdata = <<<$postdata>>>." + exit 1 + fi +} + +get_data_from_device() { + # If successful all device info is available in MY_PATH + rm -f "$MY_PATH/$PORT_STATUS" + send_web_command "$MY_PATH/$PORT_STATUS" "http://$deviceip/$PORT_STATUS" + if grep -qs "Cookie Time Out" $MY_PATH/$PORT_STATUS + then + $LOG_ERROR "received no port data from $deviceip (Cookie Time Out)" + exit 1 + fi +} + +send_http_request() { + # ececution of http commands supported by the device + $HTTP_COMMAND "http://$username:$password@$deviceip/$1" +} + +send_web_command(){ + # ececution of web commands through the web-interface + WEB_COMMAND="wget -q --keep-session-cookies" + WEB_COMMAND="$WEB_COMMAND --load-cookies $MY_PATH/$MY_COOKIES" + WEB_COMMAND="$WEB_COMMAND --save-cookies $MY_PATH/$MY_COOKIES" + $WEB_COMMAND -O $1 -- $2 +} + +name2port() { + local name=$1 + local i=$IPPort0 + for h in $device_hostlist ; do + if [ $h = $name ]; then + echo $IPPort_name$i + return + fi + i=`expr $i + 1` + done + echo "invalid" +} + +set_port() { + # + # port status is always set. Even if requested status is current status. + # host status is not considered. + local host=$1 + local requested_status=$2 # 0 or 1 + local port=`name2port $host` + if [ "$port" = "invalid" ] + then + $LOG_ERROR "Host $host is not in hostlist ($hostlist) for $deviceip." + exit 1 + fi + ret=`send_http_request "$IPSetPower+$port=$requested_status" | cut -b 11` + if [ "$ret" != "$requested_status" ] + then + $LOG_ERROR "$DEVICE at $deviceip responds with wrong status $ret for host $host at port $port." + exit 1 + fi + return 0 +} + +build_device_hostlist() { + # + # hostnames are available from http://$deviceip/iocontrol.htm" + # check for number of ports + # + device_hostlist=$( + w3m -dump $MY_PATH/$PORT_STATUS | grep 'Power[1-8]' | + sed 's/[^[]*\[//;s/\].*//;s/ *//' | + while read h; do + [ -z "$h" ] && + echo $UNDEFINED_HOSTNAME || + echo $h + done + ) + if [ x = x"$device_hostlist" ]; then + $LOG_ERROR "cannot get hostlist for $deviceip" + exit 1 + fi + $LOG_DEBUG "Got new hostlist ($device_hostlist) from $deviceip" +} + +filter_device_hostlist() { + # check the given hostlist against the device hostlist + local host + for host in $device_hostlist; do + [ "$host" != "$UNDEFINED_HOSTNAME" ] && + echo $host + done +} + +check_hostlist() { + # check the given hostlist against the device hostlist + local cnt=`echo "$hostlist" | wc -w` + local cnt2=0 + local host + for host in $hostlist; do + if [ `name2port $host` != "invalid" ]; then + cnt2=$((cnt2+1)) + else + $LOG_ERROR "host $host not defined at $deviceip" + fi + done + [ $cnt -ne $cnt2 ] && + exit 1 +} + +get_http_status() { + pattern="P60=[01],P61=[01],P62=[01],P63=[01],P64=[01],P65=[01],P66=[01],P67=[01]" + ret=`send_http_request "$IPGetPower" | grep $pattern` + if [ "X$ret" = "X" ] + then + $LOG_ERROR "$DEVICE at $deviceip returns invalid or no string." + exit 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +case $1 in +gethosts|on|off|reset|status) + # need environment from stonithd + # and device information from individual device + # + # default device username is admin + # IP Power 9258 does not allow user management. + # + if [ "X$username" = "X" ] + then + username="admin" + fi + + mkdir -p $MY_ROOT_PATH + tmp_path="$deviceip" # ensure a simple unique pathname + MY_PATH="$MY_ROOT_PATH/$tmp_path" + mkdir -p $MY_PATH + get_cookie_from_device + get_data_from_device + build_device_hostlist + if [ "X$hostlist" = "X" ]; then + hostlist="`filter_device_hostlist`" + else + check_hostlist + fi + ;; +*) + # the client is asking for meta-data + ;; +esac + +target=`echo $2 | sed 's/[.].*//'` +# the necessary actions for stonithd +case $1 in +gethosts) + echo $hostlist + ;; +on) + set_port $target $IPPowerOn + ;; +off) + set_port $target $IPPowerOff + ;; +reset) + set_port $target $IPPowerOff + sleep 5 + set_port $target $IPPowerOn + ;; +status) + # werify http command interface + get_http_status + ;; +getconfignames) + # return all the config names + for ipparam in deviceip username password hostlist + do + echo $ipparam + done + ;; +getinfo-devid) + echo "IP Power 9258" + ;; +getinfo-devname) + echo "IP Power 9258 power switch" + ;; +getinfo-devdescr) + echo "Power switch IP Power 9258 with 4 or 8 power outlets." + echo "WARNING: It is different from IP Power 9258 HP" + ;; +getinfo-devurl) + echo "http://www.aviosys.com/manual.htm" + ;; +getinfo-xml) + cat << IPPOWERXML +<parameters> +<parameter name="deviceip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +IP address or hostname of the device. +</shortdesc> +<longdesc lang="en"> +The IP Address or the hostname of the device. +</longdesc> +</parameter> + +<parameter name="password" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password to log in with. +</longdesc> +</parameter> + +<parameter name="hostlist" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the device controls. +If you leave this list empty, we will retrieve the hostnames from the device. +</longdesc> +</parameter> + +<parameter name="username" unique="0" required="0"> +<content type="string" default="admin"/> +<shortdesc lang="en"> +Account Name +</shortdesc> +<longdesc lang="en"> +The user to log in with. +</longdesc> +</parameter> + +</parameters> +IPPOWERXML + ;; +*) + $LOG_ERROR "Unexpected command $1 for $DEVICE at $deviceip." + exit 1; + ;; +esac diff --git a/lib/plugins/stonith/external/kdumpcheck.in b/lib/plugins/stonith/external/kdumpcheck.in new file mode 100644 index 0000000..7f3f752 --- /dev/null +++ b/lib/plugins/stonith/external/kdumpcheck.in @@ -0,0 +1,274 @@ +#!/bin/sh +# +# External STONITH module to check kdump. +# +# Copyright (c) 2008 NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n" +#Set default user name. +USERNAME="kdumpchecker" +#Initialize identity file-path options for ssh command +IDENTITY_OPTS="" + +#Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo ${hostlist} | tr ',' ' '` + +## +# Check the parameter hostlist is set or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_hostlist() { + if [ -z "${hostlist}" ]; then + ha_log.sh err "hostlist is empty" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Set kdump check user name to USERNAME. +# always return 0. +## +get_username() { + kdump_conf="/etc/kdump.conf" + + if [ ! -f "${kdump_conf}" ]; then + ha_log.sh debug "${kdump_conf} doesn't exist" + return 0 + fi + + tmp="" + while read config_opt config_val; do + if [ "${config_opt}" = "kdump_check_user" ]; then + tmp="${config_val}" + fi + done < "${kdump_conf}" + if [ -n "${tmp}" ]; then + USERNAME="${tmp}" + fi + + ha_log.sh debug "kdump check user name is ${USERNAME}." +} + +## +# Check the specified or default identity file exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_identity_file() { + IDENTITY_OPTS="" + if [ -n "${identity_file}" ]; then + if [ ! -f "${identity_file}" ]; then + ha_log.sh err "${identity_file} doesn't exist." + exit 6 #ERR_CONFIGURED + fi + IDENTITY_OPTS="-i ${identity_file}" + else + flg_file_exists=0 + homedir=`eval echo "~${USERNAME}"` + for filename in "${homedir}/.ssh/id_rsa" \ + "${homedir}/.ssh/id_dsa" \ + "${homedir}/.ssh/identity" + do + if [ -f "${filename}" ]; then + flg_file_exists=1 + IDENTITY_OPTS="${IDENTITY_OPTS} -i ${filename}" + fi + done + if [ ${flg_file_exists} -eq 0 ]; then + ha_log.sh err "${USERNAME}'s identity file for ssh command" \ + " doesn't exist." + exit 6 #ERR_CONFIGURED + fi + fi +} + +## +# Check the user to check doing kdump exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_user_existence() { + + # Get kdump check user name and check whether he exists or not. + grep -q "^${USERNAME}:" /etc/passwd > /dev/null 2>&1 + ret=$? + if [ ${ret} != 0 ]; then + ha_log.sh err "user ${USERNAME} doesn't exist." \ + "please confirm \"kdump_check_user\" setting in /etc/kdump.conf." \ + "(default user name is \"kdumpchecker\")" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Check the target node is kdumping or not. +# arg1 : target node name. +# ret : 0 -> the target is kdumping. +# : 1 -> the target is _not_ kdumping. +# : else -> failed to check. +## +check_kdump() { + target_node="$1" + + # Get kdump check user name. + get_username + check_user_existence + exec_cmd="${SSH_COMMAND} -l ${USERNAME}" + + # Specify kdump check user's identity file for ssh command. + check_identity_file + exec_cmd="${exec_cmd} ${IDENTITY_OPTS}" + + # Now, check the target! + # In advance, Write the following setting at the head of + # kdump_check_user's public key in authorized_keys file on target node. + # command="test -s /proc/vmcore", \ + # no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty + ha_log.sh debug "execute the command [${exec_cmd} ${target_node}]." + ${exec_cmd} ${target_node} > /dev/null 2>&1 + ret=$? + ha_log.sh debug "the command's result is ${ret}." + + #ret -> 0 : vmcore file's size is not zero. the node is kdumping. + #ret -> 1 : the node is _not_ kdumping (vmcore didn't exist or + # its size is zero). It still needs to be STONITH'ed. + #ret -> 255 : ssh command is failed. + # else : Maybe command strings in authorized_keys is wrong... + return ${ret} +} + +### +# +# Main function. +# +### +case $1 in +gethosts) + check_hostlist + for hostname in ${hostlist} ; do + echo "${hostname}" + done + exit 0 + ;; +on) + # This plugin does only check whether a target node is kdumping or not. + exit 1 + ;; +reset|off) + check_hostlist + ret=1 + h_target=`echo $2 | tr A-Z a-z` + for hostname in ${hostlist} + do + hostname=`echo $hostname | tr A-Z a-z` + if [ "${hostname}" != "$h_target" ]; then + continue + fi + while [ 1 ] + do + check_kdump "$2" + ret=$? + if [ ${ret} -ne 255 ]; then + exit ${ret} + fi + #255 means ssh command itself is failed. + #For example, connection failure as if network doesn't start yet + #in 2nd kernel on the target node. + #So, retry to check after a little while. + sleep 1 + done + done + exit ${ret} + ;; +status) + check_hostlist + for hostname in ${hostlist} + do + if ping -w1 -c1 "${hostname}" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + get_username + check_user_existence + check_identity_file + exit 0 + ;; +getconfignames) + echo "hostlist identity_file" + exit 0 + ;; +getinfo-devid) + echo "kdump check STONITH device" + exit 0 + ;; +getinfo-devname) + echo "kdump check STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based kdump checker" + echo "To check whether a target node is dumping or not." + exit 0 + ;; +getinfo-devurl) + echo "kdump -> http://lse.sourceforge.net/kdump/" + echo "ssh -> http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="identity_file" unique="1" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Identity file's full path for kdump check user +</shortdesc> +<longdesc lang="en"> +The full path of kdump check user's identity file for ssh command. +The identity in the specified file have to be restricted to execute +only the following command. +"test -s /proc/vmcore" +Default: kdump check user's default identity file path. +NOTE: You can specify kdump check user name in /etc/kdump.conf. + The parameter name is "kdump_check_user". + Default user is "kdumpchecker". +</longdesc> +</parameter> + +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/libvirt b/lib/plugins/stonith/external/libvirt new file mode 100644 index 0000000..494b048 --- /dev/null +++ b/lib/plugins/stonith/external/libvirt @@ -0,0 +1,298 @@ +#!/bin/sh +# +# External STONITH module for a libvirt managed hypervisor (kvm/Xen). +# Uses libvirt as a STONITH device to control guest. +# +# Copyright (c) 2010 Holger Teutsch <holger.teutsch@web.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# start a domain +libvirt_start() { + out=$($VIRSH -c $hypervisor_uri start $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was started" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*(running|idle)|already active' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already active" + return 0 + fi + + ha_log.sh err "Failed to start domain $domain_id" + ha_log.sh err "$out" + return 1 +} +# reboot a domain +# return +# 0: success +# 1: error +libvirt_reboot() { + local rc out + out=$($VIRSH -c $hypervisor_uri reboot $domain_id 2>&1) + rc=$? + if [ $rc -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was rebooted" + return 0 + fi + ha_log.sh err "Failed to reboot domain $domain_id (exit code: $rc)" + ha_log.sh err "$out" + return 1 +} + +# stop a domain +# return +# 0: success +# 1: error +# 2: was already stopped +libvirt_stop() { + out=$($VIRSH -c $hypervisor_uri destroy $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was stopped" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*shut off|not found|not running' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already stopped" + return 2 + fi + + ha_log.sh err "Failed to stop domain $domain_id" + ha_log.sh err "$out" + return 1 +} + +# get status of stonith device (*NOT* of the domain). +# If we can retrieve some info from the hypervisor +# the stonith device is OK. +libvirt_status() { + out=$($VIRSH -c $hypervisor_uri version 2>&1) + if [ $? -eq 0 ] + then + return 0 + fi + + ha_log.sh err "Failed to get status for $hypervisor_uri" + ha_log.sh err "$out" + return 1 +} + +# check config and set variables +# does not return on error +libvirt_check_config() { + VIRSH=`which virsh 2>/dev/null` + + if [ ! -x "$VIRSH" ] + then + ha_log.sh err "virsh not installed" + exit 1 + fi + + if [ -z "$hostlist" -o -z "$hypervisor_uri" ] + then + ha_log.sh err "hostlist or hypervisor_uri missing; check configuration" + exit 1 + fi + + case "$reset_method" in + power_cycle|reboot) : ;; + *) + ha_log.sh err "unrecognized reset_method: $reset_method" + exit 1 + ;; + esac +} + +# set variable domain_id for the host specified as arg +libvirt_set_domain_id () +{ + for h in $hostlist + do + case $h in + $1:*) + domain_id=`expr $h : '.*:\(.*\)'` + return + ;; + + $1) + domain_id=$1 + return + esac + done + + ha_log.sh err "Should never happen: Called for host $1 but $1 is not in $hostlist." + exit 1 +} + +libvirt_info() { +cat << LVIRTXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +List of hostname[:domain_id].. +</shortdesc> +<longdesc lang="en"> +List of controlled hosts: hostname[:domain_id].. +The optional domain_id defaults to the hostname. +</longdesc> +</parameter> + +<parameter name="hypervisor_uri" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hypervisor URI +</shortdesc> +<longdesc lang="en"> +URI for connection to the hypervisor. +driver[+transport]://[username@][hostlist][:port]/[path][?extraparameters] +e.g. +qemu+ssh://my_kvm_server.mydomain.my/system (uses ssh for root) +xen://my_kvm_server.mydomain.my/ (uses TLS for client) + +virsh must be installed (e.g. libvir-client package) and access control must +be configured for your selected URI. +</longdesc> +</parameter> + +<parameter name="reset_method" required="0"> +<content type="string" default="power_cycle"/> +<shortdesc lang="en"> +How to reset a guest. +</shortdesc> +<longdesc lang="en"> +A guest reset may be done by a sequence of off and on commands +(power_cycle) or by the reboot command. Which method works +depend on the hypervisor and guest configuration management. +</longdesc> +</parameter> +</parameters> +LVIRTXML +exit 0 +} + +############# +# Main code # +############# + +# don't fool yourself when testing with stonith(8) +# and transport ssh +unset SSH_AUTH_SOCK + +# support , as a separator as well +hostlist=`echo $hostlist| sed -e 's/,/ /g'` + +reset_method=${reset_method:-"power_cycle"} + +case $1 in + gethosts) + hostnames=`echo $hostlist|sed -e 's/:[^ ]*//g'` + for h in $hostnames + do + echo $h + done + exit 0 + ;; + + on) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_start + exit $? + ;; + + off) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_stop + [ $? = 1 ] && exit 1 + exit 0 + ;; + + reset) + libvirt_check_config + libvirt_set_domain_id $2 + + if [ "$reset_method" = "power_cycle" ]; then + libvirt_stop + [ $? = 1 ] && exit 1 + sleep 2 + libvirt_start + else + libvirt_reboot + fi + exit $? + ;; + + status) + libvirt_check_config + libvirt_status + exit $? + ;; + + getconfignames) + echo "hostlist hypervisor_uri reboot_method" + exit 0 + ;; + + getinfo-devid) + echo "libvirt STONITH device" + exit 0 + ;; + + getinfo-devname) + echo "libvirt STONITH external device" + exit 0 + ;; + + getinfo-devdescr) + echo "libvirt-based host reset for Xen/KVM guest domain through hypervisor" + exit 0 + ;; + + getinfo-devurl) + echo "http://libvirt.org/uri.html http://linux-ha.org/wiki" + exit 0 + ;; + + getinfo-xml) + libvirt_info + echo 0; + ;; + + *) + exit 1 + ;; +esac + +# vi:et:ts=4:sw=4 diff --git a/lib/plugins/stonith/external/nut b/lib/plugins/stonith/external/nut new file mode 100644 index 0000000..9e51bb8 --- /dev/null +++ b/lib/plugins/stonith/external/nut @@ -0,0 +1,302 @@ +#!/bin/sh + +# External STONITH module that uses the NUT daemon to control an external UPS. +# See the comments below, and the various NUT man pages, for how this +# script works. It should work unchanged with most modern "smart" APC UPSes in +# a Redhat/Fedora/RHEL-style distribution with the nut package installed. + +# Author: William Seligman <seligman@nevis.columbia.edu> +# License: GPLv2 + +# As you're designing your UPS and STONITH set-up, it may help to consider that +# there can be potentially three computers involved: +# 1) the machine running this STONITH module; +# 2) the machine being controlled by this STONITH module ($hostname); +# 3) the machine that can send commands to the UPS. + +# On my cluster, all the UPSes have SNMP smartcards, so every host can communicate +# with every UPS; in other words, machines (1) and (3) are the same. If your UPSes +# are controlled via serial or USB connections, then you might have a +# situation in which $hostname is plugged into a UPS, which has a serial connection +# to some master "power-control" computer, and can potentially be STONITHed +# by any other machine in your cluster. + +# In general, you'll probably need the nut daemon running on both the hosts (1) and +# (3) in the above list. The NUT daemon will also have to run on (2) if you want the +# reset command to gracefully reboot $hostname. + +# The NUT command default locations. In the RHEL-type nut packages, these binaries +# are in /usr/bin. +RHELUPSCMD="/usr/bin/upscmd" +RHELUPSC="/usr/bin/upsc" + +# Defaults for APC smart UPSes: + +# Reset = reboot $hostname; this will be a graceful reboot if the host +# is running NUT and monitoring $ups. +APCRESET="shutdown.return" + +# Poweroff = turn off $hostname immediately by cutting the power on $ups. +# For a graceful shutdown, use shutdown.stayoff instead of load.off, +# but it might take a few minutes to shutdown in this way. +APCPOWEROFF="load.off" + +# Poweron = turn on the power to $ups, which will presumably turn on $hostname. +# (Did you set $hostname's BIOS to boot up on AC power restore, as opposed to +# "last state"?) +APCPOWERON="load.on" + +# Status = returns a short string with the $ups status; OL = on-line, OFF = off-line, etc. +APCSTATUSVAR="ups.status" + + +# Stick in the defaults, if needed. +if [ -z "${poweron}" ]; then + poweron=${APCPOWERON} +fi +if [ -z "${poweroff}" ]; then + poweroff=${APCPOWEROFF} +fi +if [ -z "${reset}" ]; then + reset=${APCRESET} +fi +if [ -z "${statusvar}" ]; then + statusvar=${APCSTATUSVAR} +fi +if [ -z "${upscmd}" ]; then + upscmd=${RHELUPSCMD} +fi +if [ -z "${upsc}" ]; then + upsc=${RHELUPSC} +fi + + +# Define the command to fetch the UPS status. +STATUSCMD="${upsc} ${ups} ${statusvar}" + +usage() { + echo "Usage: $0 {on|off|reset|status|gethosts|getconfignames|getinfo-devid|getinfo-devname|getinfo-devdescr|getinfo-devurl|getinfo-xml}" +} + +# Can we find the NUT binary? +have_nut() { + test -x "${upscmd}" +} +have_upsc() { + test -x "${upsc}" +} + +do_nut() { + have_nut || { + echo "Can't find NUT upscmd command" + return 1 + } + if [ -z "${username}" -o -z "${password}" -o -z "${ups}" ]; then + echo "username, password or ups name missing; check configuration" + return 1 + fi + # Execute the command given in argument 1. + ${upscmd} -u ${username} -p ${password} ${ups} ${1} || { + echo "error executing nut command" + return 1 + } +} + +case ${1} in +gethosts) + echo ${hostname} + exit 0 + ;; +on) + result=1 + do_nut "${poweron}" + result=$? + exit ${result} + ;; +off) + result=1 + do_nut "${poweroff}" + result=$? + exit ${result} + ;; +reset) + result=1 + do_nut "${reset}" + result=$? + exit $result + ;; +status) + have_upsc || { + echo "Can't find NUT upsc command" + exit 1 + } + ${STATUSCMD} + exit $? + ;; +getconfignames) + echo "hostname ups username password poweron poweroff reset statusvar upscmd upsc" + exit 0 + ;; +getinfo-devid) + echo "NUT STONITH device" + exit 0 + ;; +getinfo-devname) + echo "NUT STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "A STONITH device based on NUT (Network UPS Tools)." + echo " " + echo "For this STONITH script to work, the following conditions have" + echo "to be met:" + echo " " + echo "- NUT has to be installed on both the host running this script" + echo " and the host that controls the UPS (on RHEL systems, NUT is" + echo " in packages nut and nut-client) and the nut daemon services" + echo " (normally called the ups or upsd service) must be running" + echo " on both systems." + echo " " + echo "- The UPS name has to be defined in ups.conf on the host" + echo " that controls the UPS." + echo " " + echo "- The username/password to access the UPS must be defined in" + echo " upsd.users on the host that controls the UPS, with the instcmds" + echo " for poweron, poweroff, and reset allowed." + echo " " + echo "- The host that is running this script must be allowed access" + echo " via upsd.conf and upsd.users on the host the controls the UPS." + echo " " + echo "On RHEL systems, the files listed above are in /etc/ups." + echo " " + echo "The defaults will probably work with APC UPS devices. It might" + echo "work on others; 'upscmd -l (ups)' and 'upsc (ups)' will list" + echo "the commands and variables, and you can change the values" + echo "for poweron, poweroff, reset, and statusvar to suit your UPS." + echo "Change upscmd and upsc if your NUT binaries are not in /usr/bin." + exit 0 + ;; +getinfo-devurl) + echo "http://www.networkupstools.org/" + exit 0 + ;; +getinfo-xml) +cat << nutXML +<parameters> + +<parameter name="hostname" unique="1" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Hostname</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +The nut daemon must be running on the host controllng the +UPS _and_ on the host running this script; this script does +not start/stop the daemons for you. +</longdesc> +</parameter> + +<parameter name="ups" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">UPS name</shortdesc> +<longdesc lang="en"> +The name of the UPS as defined in ups.conf on the host +controlling the UPS. The format for this option is +upsname[@controlhost[:port]]. The default controlhost is +"localhost". +</longdesc> +</parameter> + +<parameter name="username" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Username</shortdesc> +<longdesc lang="en"> +The username used for accessing the UPS. This is defined in +upsd.conf on the host controlling the UPS. +</longdesc> +</parameter> + +<parameter name="password" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Password</shortdesc> +<longdesc lang="en"> +The password used for logging in to the UPS for the host +controlling the UPS, as defined in upsd.conf on that host. +</longdesc> +</parameter> + +<parameter name="poweron"> +<content type="string" default="$APCPOWERON" /> +<shortdesc lang="en">UPS Power On command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to turn on the UPS. The default +should work for most "smart" APC UPSes. For a list of +commands that your UPS can support, type 'upscmd -l (ups)' +on the command line.</longdesc> +</parameter> + +<parameter name="poweroff"> +<content type="string" default="$APCPOWEROFF" /> +<shortdesc lang="en">UPS Power Off command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to turn off the UPS. On most APC +"smart" UPSes, the command shutdown.stayoff will result +in a graceful shutdown, provided the host is running the +nut daemon, but this might take a few minutes; load.off +will cut the power immediately. For a list of commands that +your UPS can support, type 'upscmd -l (ups)' on the command +line. +</longdesc> +</parameter> + +<parameter name="reset"> +<content type="string" default="$APCRESET" /> +<shortdesc lang="en">UPS Reset command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to reset the host. On most APC +"smart" UPSes, the command shutdown.return will result +in a graceful shutdown, with power restored after perhaps +a short interval. For a list of commands that your UPS can + support, type 'upscmd -l (ups)' on the command line. +</longdesc> +</parameter> + +<parameter name="statusvar"> +<content type="string" default="$APCSTATUSVAR" /> +<shortdesc lang="en">UPS Status variable</shortdesc> +<longdesc lang="en"> +The NUT variable that returns the status of the UPS. On APC +UPSes, the value of ups.status will be "OL" if the UPS is +"on-line." For a list of variables that your UPS supports, +type 'upsc (ups)' on the command line. +</longdesc> +</parameter> + +<parameter name="upscmd"> +<content type="string" default="$RHELUPSCMD" /> +<shortdesc lang="en">upscmd binary location</shortdesc> +<longdesc lang="en"> +The full path to the NUT binary command 'upscmd'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upscmd. +</longdesc> +</parameter> + +<parameter name="upsc"> +<content type="string" default="$RHELUPSC" /> +<shortdesc lang="en">upsc binary location</shortdesc> +<longdesc lang="en"> +The full path to the NUT binary command 'upsc'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upsc. +</longdesc> +</parameter> + +</parameters> +nutXML +exit 0 +;; +*) + usage + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/rackpdu b/lib/plugins/stonith/external/rackpdu new file mode 100644 index 0000000..7d0e20b --- /dev/null +++ b/lib/plugins/stonith/external/rackpdu @@ -0,0 +1,280 @@ +#!/bin/sh +# +# External STONITH module for APC Switched Rack PDU +# +# Copyright (c) 2008 Sergey Maznichenko <msergeyb@gmail.com> <inbox@it-consultant.su> +# Version 1.2 +# +# See http://www.it-consultant.su/rackpdu +# for additional information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SWITCH_ON="1" +SWITCH_OFF="2" +SWITCH_RESET="3" + +DEFAULT_NAMES_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" +DEFAULT_COMMAND_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4" + +if [ -z "$oid" ]; then + oid=$DEFAULT_COMMAND_OID +fi + +if [ -z "$names_oid" ]; then + names_oid=$DEFAULT_NAMES_OID +fi + +if [ -z "$outlet_config" ]; then + outlet_config="none" +fi + +GetOutletNumber() { + local nodename=$1 + + if [ "$outlet_config" != "none" ]; then + # Get outlet number from file + + if [ -f "$outlet_config" ]; then + local outlet_num=`grep $nodename $outlet_config | tr -d ' ' | cut -f2 -d'='` + if [ -z "$outlet_num" ]; then + ha_log.sh err "Outlet number not found for node $nodename. Check configuration file $outlet_config" + return 0 + fi + return $outlet_num + else + ha_log.sh err "File $outlet_config not found." + return 0 + fi + else + # Get outlet number from device + + local outlet_num=1 + local snmp_result + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + return 0 + fi + + local names + names=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + for name in $names; do + if [ "$name" != "$nodename" ]; then + local outlet_num=`expr $outlet_num + 1` + continue + fi + + return $outlet_num + done + + ha_log.sh err "Outlet number not found for node $nodename. Result: $snmp_result" + return 0 + fi +} + +SendCommand() { + + local host=$1 + local command=$2 + + GetOutletNumber $host + local outlet=$? + + if [ $outlet -gt 0 ]; then + local set_result + set_result=`snmpset -v1 -c $community $pduip $oid.$outlet i $command 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command failed. Result: $set_result" + return 1 + fi + if echo "$set_result" | grep -qs "Timeout"; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command timed out. Result: $set_result" + return 1 + fi + return 0 + else + return 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +incommand=$1 +innode=$2 + +case $incommand in +gethosts) + if [ "$hostlist" = "AUTO" ]; then + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + exit 1 + fi + if echo "$snmp_result" | grep -qs "Timeout"; then + ha_log.sh err "snmpwalk $community $pduip $names_oid timed out. Result: $snmp_result" + exit 1 + else + hostlist=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + fi + fi + + for h in $hostlist ; do + echo $h + done + + exit 0 + ;; +on) + if + SendCommand $innode $SWITCH_ON + then + exit 0 + else + exit 1 + fi + ;; +off) + if + SendCommand $innode $SWITCH_OFF + then + exit 0 + else + exit 1 + fi + ;; +reset) + if + SendCommand $innode $SWITCH_RESET + then + exit 0 + else + exit 1 + fi + ;; +status) + if [ -z "$pduip" ]; then + exit 1 + fi + + if ping -w1 -c1 $pduip >/dev/null 2>&1; then + exit 0 + else + exit 1 + fi + ;; +getconfignames) + echo "hostlist pduip community" + exit 0 + ;; +getinfo-devid) + echo "rackpdu STONITH device" + exit 0 + ;; +getinfo-devname) + echo "rackpdu STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "APC Switched Rack PDU" + exit 0 + ;; +getinfo-devurl) + echo "http://www.apcc.com/products/family/index.cfm?id=30" + exit 0 + ;; +getinfo-xml) + cat << PDUXML +<parameters> + <parameter name="hostlist" unique="1" required="1"> + <content type="string" default="AUTO" /> + <shortdesc lang="en">Hostlist</shortdesc> + <longdesc lang="en"> +The list of hosts that the STONITH device controls (comma or space separated). +If you set value of this parameter to AUTO, list of hosts will be get from Rack PDU device. + </longdesc> + </parameter> + + <parameter name="pduip" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">Name or IP address of Rack PDU device.</shortdesc> + <longdesc lang="en">Name or IP address of Rack PDU device.</longdesc> + </parameter> + + <parameter name="community" unique="1" required="1"> + <content type="string" default="private" /> + <shortdesc lang="en">Name of write community.</shortdesc> + <longdesc lang="en">Name of write community.</longdesc> + </parameter> + + <parameter name="oid" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en"> + The OID without the outlet number. + </shortdesc> + <longdesc lang="en"> +The SNMP OID for the PDU. minus the outlet number. +Try .1.3.6.1.4.1.318.1.1.12.3.3.1.1.4 (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + </longdesc> + </parameter> + + <parameter name="names_oid" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en">The OID for getting names of outlets.</shortdesc> + <longdesc lang="en"> +The SNMP OID for getting names of outlets. +It is required to recognize outlet number by nodename. +Try ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Names of nodes must be equal names of outlets, in other way use outlet_config parameter. +If you set 'names_oid' parameter then parameter outlet_config must not be use. +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + </longdesc> + </parameter> + + <parameter name="outlet_config" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en">Configuration file. Other way to recognize outlet number by nodename.</shortdesc> + <longdesc lang="en"> +Configuration file. Other way to recognize outlet number by nodename. +Configuration file which contains +node_name=outlet_number +strings. + +Example: +server1=1 +server2=2 + +If you use outlet_config parameter then names_oid parameter can have any value and it is not uses. + </longdesc> + </parameter> + +</parameters> +PDUXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/riloe b/lib/plugins/stonith/external/riloe new file mode 100644 index 0000000..ce98847 --- /dev/null +++ b/lib/plugins/stonith/external/riloe @@ -0,0 +1,530 @@ +#!/usr/bin/env python +# +# Stonith module for RILOE Stonith device +# +# Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca> +# +# Modified by Alan Robertson <alanr@unix.sh> for STONITH external compatibility. +# +# Extended and merged by Tijl Van den broeck <subspawn@gmail.com> +# with ilo-v2 script from Guy Coates +# +# Cleanup by Andrew Beekhof <abeekhof@suse.de> +# +# Rewritten by Dejan Muhamedagic <dejan@suse.de> +# Now, the plugin actually reads replies from iLO. +# +# Extended by Jochen Roeder <jochen.roeder@novell.com> +# to enable access via proxies +# +# 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 +# +import sys +import os +import socket +import subprocess +import xml.dom.minidom +import httplib +import time +import re + +def log_msg(level,msg): + subprocess.call("ha_log.sh %s '%s'" % (level,msg), shell=True) +def my_err(msg): + log_msg("err", msg) +def my_warn(msg): + log_msg("warn", msg) +def my_debug(msg): + log_msg("debug", msg) +def fatal(msg): + my_err(msg) + sys.exit(1) + +argv = sys.argv + +try: + cmd = argv[1] +except IndexError: + my_err("Not enough arguments") + sys.exit(1) + +legacy_RI_HOST = os.environ.get('RI_HOST', '') +legacy_RI_HOSTRI = os.environ.get('RI_HOSTRI', '') +legacy_RI_LOGIN = os.environ.get('RI_LOGIN', 'Administrator') +legacy_RI_PASSWORD = os.environ.get('RI_PASSWORD', '') + +reset_ok = os.environ.get('ilo_can_reset', '0') +ilo_protocol = os.environ.get('ilo_protocol', '1.2') +power_method = os.environ.get('ilo_powerdown_method', 'power') + +realhost = os.environ.get('hostlist', legacy_RI_HOST) +rihost = os.environ.get('ilo_hostname', legacy_RI_HOSTRI) +ilouser = os.environ.get('ilo_user', legacy_RI_LOGIN) +ilopass = os.environ.get('ilo_password', legacy_RI_PASSWORD) +iloproxyhost = os.environ.get('ilo_proxyhost', '') +try: + iloproxyport = int(os.environ.get('ilo_proxyport', 3128)) +except ValueError: + my_err("ilo_proxyport is not a number") + sys.exit(1) + +xmlinfo = '''<parameters> + <parameter name="hostlist" unique="1" required="1"> + <content type="string"/> + <shortdesc lang="en">ilo target hostname</shortdesc> + <longdesc lang="en"> + Contains the hostname that the ilo controls + </longdesc> + </parameter> +<parameter name="ilo_hostname" unique="1" required="1"> + <content type="string"/> + <shortdesc lang="en">ilo device hostname</shortdesc> + <longdesc lang="en"> + The hostname of the ilo device + </longdesc> + </parameter> +<parameter name="ilo_user" unique="0" required="1"> + <content type="string" default="Administrator"/> + <shortdesc lang="en">ilo user</shortdesc> + <longdesc lang="en"> + The user for connecting to the ilo device + </longdesc> + </parameter> +<parameter name="ilo_password" unique="0" required="1"> + <content type="string" default=""/> + <shortdesc lang="en">password</shortdesc> + <longdesc lang="en"> + The password for the ilo device user + </longdesc> + </parameter> +<parameter name="ilo_can_reset" unique="0" required="0"> + <content type="string" default="0"/> + <shortdesc lang="en">Device can reset</shortdesc> + <longdesc lang="en"> + Does the ILO device support RESET commands (hint: older ones cannot) + </longdesc> + </parameter> +<parameter name="ilo_protocol" unique="0" required="0"> + <content type="string" default="1.2"/> + <shortdesc lang="en">ILO Protocol</shortdesc> + <longdesc lang="en"> + Protocol version supported by the ILO device. + Known supported versions: 1.2, 2.0 + </longdesc> + </parameter> +<parameter name="ilo_powerdown_method" unique="0" required="0"> + <content type="string" default="power"/> + <shortdesc lang="en">Power down method</shortdesc> + <longdesc lang="en"> + The method to powerdown the host in question. + * button - Emulate holding down the power button + * power - Emulate turning off the machines power + + NB: A button request takes around 20 seconds. The power method + about half a minute. + </longdesc> + </parameter> +<parameter name="ilo_proxyhost" unique="0" required="0"> + <content type="string" default=""/> + <shortdesc lang="en">Proxy hostname</shortdesc> + <longdesc lang="en"> + proxy hostname if required to access ILO board + </longdesc> + </parameter> +<parameter name="ilo_proxyport" unique="0" required="0"> + <content type="string" default="3128"/> + <shortdesc lang="en">Proxy port</shortdesc> + <longdesc lang="en"> + proxy port if required to access ILO board + parameter will be ignored if proxy hostname is not set + </longdesc> + </parameter> + +</parameters>''' + +info = { + 'getinfo-devid': 'iLO2', + 'getinfo-devname': 'ilo2 ' + rihost, + 'getinfo-devdescr': 'HP/COMPAQ iLO2 STONITH device', + 'getinfo-devurl': 'http://www.hp.com/', + 'gethosts': realhost, + 'getinfo-xml': xmlinfo +} + +if cmd in info: + print info[cmd] + sys.exit(0) + +if cmd == 'getconfignames': + for arg in [ "hostlist", "ilo_hostname", "ilo_user", "ilo_password", "ilo_can_reset", "ilo_protocol", "ilo_powerdown_method", "ilo_proxyhost", "ilo_proxyport"]: + print arg + sys.exit(0) + +if not rihost: + fatal("ILO device hostname not specified") + +if not realhost: + fatal("Host controlled by this ILO device not specified") + +if not power_method in ("power","button"): + my_err('unknown power method %s, setting to "power"') + power_method = "power" + +# XML elements +E_RIBCL = "RIBCL" +E_LOGIN = "LOGIN" +E_SERVER_INFO = "SERVER_INFO" + +# power mgmt methods +E_RESET = "RESET_SERVER" # error if powered off +E_COLD_BOOT = "COLD_BOOT_SERVER" # error if powered off +E_WARM_BOOT = "WARM_BOOT_SERVER" # error if powered off +E_PRESS_BUTTON = "PRESS_PWR_BTN" +E_HOLD_BUTTON = "HOLD_PWR_BTN" + +# get/set status elements +E_SET_POWER = "SET_HOST_POWER" +E_GET_PSTATUS = "GET_HOST_POWER_STATUS" + +# whatever this means, but we have to use it to get good XML +E_LOCFG = "LOCFG" +LOCFG_VER = '2.21' + +# attributes +A_VERSION = "VERSION" # ilo_protocol +A_USER = "USER_LOGIN" +A_PWD = "PASSWORD" +A_MODE = "MODE" # info mode (read or write) +A_POWER_SW = "HOST_POWER" # "Y" or "N" +A_POWER_STATE = "HOST_POWER" # "ON" or "OFF" + +def new_power_req(tag, name = None, value = None): + ''' + Create a new RIBCL request (as XML). + ''' + my_debug("creating power request: %s,%s,%s"%(tag,name,value)) + doc = xml.dom.minidom.Document() + locfg = doc.createElement(E_LOCFG) + locfg.setAttribute(A_VERSION,LOCFG_VER) + ribcl = doc.createElement(E_RIBCL) + ribcl.setAttribute(A_VERSION,ilo_protocol) + login = doc.createElement(E_LOGIN) + login.setAttribute(A_USER,ilouser) + login.setAttribute(A_PWD,ilopass) + serv_info = doc.createElement(E_SERVER_INFO) + # read or write, it doesn't really matter, i.e. even if we + # say "write" that doesn't mean we can't read + serv_info.setAttribute(A_MODE,"write") + doc.appendChild(locfg) + locfg.appendChild(ribcl) + ribcl.appendChild(login) + login.appendChild(serv_info) + el_node = doc.createElement(tag) + if name: + el_node.setAttribute(name,value) + serv_info.appendChild(el_node) + s = doc.toprettyxml() + doc.unlink() + # work around an iLO bug: last line containing "</LOCFG>" + # produces a syntax error + lines = s.split('\n') + return '\n'.join(lines[:-2]) + +E_RESPONSE = "RESPONSE" +E_HOST_POWER = "GET_HOST_POWER" +A_STATUS = "STATUS" +# documentation mentions both; better safe than sorry +A_MSG = "MSG" +A_MSG2 = "MESSAGE" + +def is_element(xmlnode): + return xmlnode.nodeType == xmlnode.ELEMENT_NODE + +def read_resp(node): + ''' + Check if the RESPONSE XML is OK. + ''' + msg = "" + str_status = "" + for attr in node.attributes.keys(): + if attr == A_STATUS: + str_status = node.getAttribute(attr) + elif attr == A_MSG: + msg = node.getAttribute(attr) + elif attr == A_MSG2: + msg = node.getAttribute(attr) + else: + my_warn("unexpected attribute %s in %s" % (attr,E_RESPONSE)) + if not str_status: + my_err("no status in response") + return -1 + try: + status = int(str_status,16) + except ValueError: + my_err("unexpected status %s in response" % str_status) + return -1 + if status != 0: + my_err("%s (rc: %s)"%(msg,str_status)) + return -1 + return 0 + +def read_power(node): + ''' + Read the power from the XML node. Set the global power + variable correspondingly. + ''' + global power + for attr in node.attributes.keys(): + if attr == A_POWER_STATE: + power_state = node.getAttribute(attr).upper() + else: + my_warn("unexpected attribute %s in %s" % (attr,node.tagName)) + if not power_state: + my_err("no %s attribute in %s" % (A_POWER_STATE,node.tagName)) + return -1 + if power_state not in ("ON","OFF"): + my_err("unexpected value for %s: %s" % (A_POWER_STATE,power_state)) + return -1 + power = (power_state == "ON") + my_debug("Host has power: %s"%power) + return 0 + +el_parsers = { + E_RESPONSE:read_resp, + E_HOST_POWER:read_power +} +def proc_resp(doc): + ''' + Process one iLO reply. Real work is done in el_parsers. + ''' + ribcl = doc.childNodes[0] + if not is_element(ribcl) or ribcl.tagName != E_RIBCL: + my_err("unexpected top element in response") + return -1 + for child in ribcl.childNodes: + if not is_element(child): + continue + if child.tagName in el_parsers: + rc = el_parsers[child.tagName](child) + if rc != 0: + return -1 + else: + my_warn("unexpected element in response: %s" % child.toxml()) + return 0 + +def open_ilo(host): + # open https connection + try: + if iloproxyhost != "" and iloproxyport != 0: + proxy=socket.socket(socket.AF_INET,socket.SOCK_STREAM) + proxy.connect((iloproxyhost, iloproxyport)) + proxy_connect='CONNECT %s:%s HTTP/1.1\r\n'%(host,443) + user_agent='User-Agent: python\r\n' + proxy_pieces=proxy_connect+user_agent+'\r\n' + proxy.sendall(proxy_pieces) + response=proxy.recv(8192) + status=response.split()[1] + if status!=str(200): + fatal("Error status=: %s" %(response)) + import ssl + sock = ssl.wrap_socket(proxy) + h=httplib.HTTPConnection('localhost') + h.sock=sock + return h + else: + return httplib.HTTPSConnection(host) + except socket.gaierror, msg: + fatal("%s: %s" %(msg,host)) + except socket.sslerror, msg: + fatal("%s for %s" %(msg,host)) + except socket.error, msg: + fatal("%s while talking to %s" %(msg,host)) + except ImportError, msg: + fatal("ssl support missing (%s)" %msg) + +def send_request(req,proc_f): + ''' + 1. After every request, the iLO closes the connection. + 2. For every request, there are multiple replies. Each reply + is an XML document. Most of replies are just a kind of + (verbose) XML "OK". + ''' + t_begin = time.time() + c = open_ilo(rihost) + try: + c.send(req+'\r\n') + except socket.error, msg: + fatal("%s, while talking to %s" %(msg,rihost)) + t_end = time.time() + my_debug("request sent in %0.2f s" % ((t_end-t_begin))) + + t_begin = time.time() + result = [] + while True: + try: + reply = c.sock.recv(1024) + if not reply: + break + result.append(reply) + except socket.error, msg: + if msg[0] == 6: # connection closed + break + my_err("%s, while talking to %s" %(msg,rihost)) + return -1 + c.close() + t_end = time.time() + + if not result: + fatal("no response from %s within %0.2f s"%(rihost,(t_end-t_begin))) + for reply in result: + # work around the iLO bug, i.e. element RIBCL closed twice + if re.search("</RIBCL", reply) and re.search("<RIBCL.*/>", reply): + reply = re.sub("<(RIBCL.*)/>", r"<\1>", reply) + try: + doc = xml.dom.minidom.parseString(reply) + except xml.parsers.expat.ExpatError,msg: + fatal("malformed response: %s\n%s"%(msg,reply)) + rc = proc_f(doc) + doc.unlink() + if rc != 0: + break + my_debug("iLO processed request (rc=%d) in %0.2f s" % (rc,(t_end-t_begin))) + return rc + +def manage_power(cmd): + ''' + Before trying to send a request we have to check the power + state. + ''' + rc = 0 + req = '' + # it won't do to turn it on if it's already on! + if cmd == "on" and not power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + # also to turn it off if it's already off + elif cmd == "off" and power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"N") + elif cmd == "cold_boot" and power: + req = new_power_req(E_COLD_BOOT) + elif cmd == "warm_boot" and power: + req = new_power_req(E_WARM_BOOT) + elif cmd == "reset": + if power: + req = new_power_req(E_RESET) + # reset doesn't work if the host's off + else: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + if req: + rc = send_request(req,proc_resp) + return rc +def power_on(): + ''' + Update the power variable without checking the power state. + The iLO is slow at times to report the actual power state, so + we assume that it changed if the request succeeded. + ''' + rc = manage_power("on") + if rc == 0: + global power + power = True + return rc +def power_off(): + rc = manage_power("off") + if rc == 0: + global power + power = False + return rc +def cold_boot(): + rc = manage_power("cold_boot") + return rc +def warm_boot(): + rc = manage_power("warm_boot") + return rc +def reset(): + rc = manage_power("reset") + if rc == 0: + global power + power = True + return rc +def hold_button(): + ''' + Hold the power button. Got this error message when tried + without the TOGGLE attribute: + Command without TOGGLE="Yes" attribute is ignored + when host power is off. (rc: 0x0054) + Didn't find any documentation about TOGGLE. + ''' + if power: + req = new_power_req(E_HOLD_BUTTON) + else: + req = new_power_req(E_HOLD_BUTTON,"TOGGLE","Yes") + rc = send_request(req,proc_resp) + return rc +def read_power_state(): + req = new_power_req(E_GET_PSTATUS) + rc = send_request(req,proc_resp) + return rc + +def reset_power(): + ''' + Three methods to reset: + - hold power button + - reset (only if host has power and user said that reset is ok) + - power off/on + ''' + do_power_on = False + if power_method == 'button': + rc = hold_button() + elif reset_ok != '0': + if power: + return reset() + else: + return power_on() + else: + do_power_on = True + rc = power_off() + if rc == 0: + rc = read_power_state() + if do_power_on: + while rc == 0 and power: # wait for the power state to go off + time.sleep(5) + rc = read_power_state() + if rc == 0 and do_power_on and not power: + rc = power_on() + return rc + +# track state of host power +power = -1 + +todo = { +'reset':reset_power, +'on':power_on, +'off':power_off, +'cold':cold_boot, +'warm':warm_boot, +'status':lambda: 0 # just return 0, we already read the state +} + +rc = read_power_state() +if rc == 0: + if cmd in todo: + rc = todo[cmd]() + else: + fatal('Invalid command: %s' % cmd) +if rc != 0: + fatal("request failed") +sys.exit(rc) + +# vi:ts=4:sw=4:et: diff --git a/lib/plugins/stonith/external/ssh.in b/lib/plugins/stonith/external/ssh.in new file mode 100644 index 0000000..2a8eb73 --- /dev/null +++ b/lib/plugins/stonith/external/ssh.in @@ -0,0 +1,176 @@ +#!/bin/sh +# +# External STONITH module for ssh. +# +# Copyright (c) 2004 SUSE LINUX AG - Lars Marowsky-Bree <lmb@suse.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n -l root" +#SSH_COMMAND="@SSH@ -q -x -n -l root" + +REBOOT_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Warning: If you select this poweroff command, it'll physically +# power-off the machine, and quite a number of systems won't be remotely +# revivable. +# TODO: Probably should touch a file on the server instead to just +# prevent heartbeat et al from being started after the reboot. +# POWEROFF_COMMAND="echo 'sleep 2; /sbin/poweroff -nf' | SHELL=/bin/sh at now >/dev/null 2>&1" +POWEROFF_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +is_host_up() { + for j in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + do + if + ping -w1 -c1 "$1" >/dev/null 2>&1 + then + sleep 1 + else + return 1 + fi + done + return 0 +} + + +case $1 in +gethosts) + for h in $hostlist ; do + echo $h + done + exit 0 + ;; +on) + # Can't really be implemented because ssh cannot power on a system + # when it is powered off. + exit 1 + ;; +off) + # Shouldn't really be implemented because if ssh cannot power on a + # system, it shouldn't be allowed to power it off. + exit 1 + ;; +reset) + h_target=`echo $2 | tr A-Z a-z` + for h in $hostlist + do + h=`echo $h | tr A-Z a-z` + [ "$h" != "$h_target" ] && + continue + if + case ${livedangerously} in + [Yy]*) is_host_up $h;; + *) true;; + esac + then + $SSH_COMMAND "$2" "$REBOOT_COMMAND" + # Good thing this is only for testing... + if + is_host_up $h + then + exit 1 + else + exit 0 + fi + else + # well... Let's call it successful, after all this is only for testing... + exit 0 + fi + done + exit 1 + ;; +status) + if + [ -z "$hostlist" ] + then + exit 1 + fi + for h in $hostlist + do + if + ping -w1 -c1 "$h" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist" + exit 0 + ;; +getinfo-devid) + echo "ssh STONITH device" + exit 0 + ;; +getinfo-devname) + echo "ssh STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset" + echo "Fine for testing, but not suitable for production!" + echo "Only reboot action supported, no poweroff, and, surprisingly enough, no poweron." + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="livedangerously" unique="0" required="0"> +<content type="enum" /> +<shortdesc lang="en"> +Live Dangerously!! +</shortdesc> +<longdesc lang="en"> +Set to "yes" if you want to risk your system's integrity. +Of course, since this plugin isn't for production, using it +in production at all is a bad idea. On the other hand, +setting this parameter to yes makes it an even worse idea. +Viva la Vida Loca! +</longdesc> +</parameter> + +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/vcenter b/lib/plugins/stonith/external/vcenter new file mode 100755 index 0000000..71a6302 --- /dev/null +++ b/lib/plugins/stonith/external/vcenter @@ -0,0 +1,280 @@ +#!/usr/bin/env perl +# +# External STONITH module for VMWare vCenter/ESX +# +# Author: Nhan Ngo Dinh +# License: GNU General Public License (GPL) +# + +require 5.010; + +use strict; +use warnings; + +sub dielog { + my $msg = "["; + $msg .= "$ARGV[0]" if defined($ARGV[0]); + $msg .= " $ARGV[1]" if defined($ARGV[1]); + $msg .= "]"; + ( $_ ) = @_; + $msg .= " $_"; + system("ha_log.sh", "err", "$msg"); + die(); +} + +# Define command groups +my @configCommands = qw{getconfignames getinfo-devid getinfo-devname getinfo-devdescr getinfo-devurl getinfo-xml}; +my @actionCommands = qw{reset on off}; +my @netCommands = (@actionCommands, qw{status gethosts listvms}); + +# Process command line arguments +my $command = $ARGV[0] || dielog("No command specified\n"); + +# Command belongs to the group of commands that do not require any connection to VMware vCenter +if ($command ~~ @configCommands) { + if ($command eq "getconfignames") { + print "VI_SERVER\nVI_PORTNUMBER\nVI_PROTOCOL\nVI_SERVICEPATH\nVI_CREDSTORE\nHOSTLIST\nRESETPOWERON\n"; + } + elsif ($command eq "getinfo-devid") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devname") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devdescr") { + print "VMWare vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devurl") { + print "http://www.vmware.com/\n"; + } + elsif ($command eq "getinfo-xml") { + print q{<parameters> +<parameter name="HOSTLIST" required="1"> +<content type="string"/> +<shortdesc lang="en">List of hosts and virtual machines (required)</shortdesc> +<longdesc lang="en"> +The list of hosts that the VMware vCenter STONITH device controls. +Syntax is: + hostname1[=VirtualMachineName1];hostname2[=VirtualMachineName2] + +NOTE: omit =VirtualMachineName if hostname and virtual machine names are identical + +Example: + cluster1=VMCL1;cluster2=VMCL2 +</longdesc> +</parameter> +<parameter name="VI_SERVER"> +<content type="string" default="localhost"/> +<shortdesc lang="en">VMware vCenter address</shortdesc> +<longdesc lang="en"> +The VMware vCenter address +</longdesc> +</parameter> +<parameter name="VI_PROTOCOL"> +<content type="string" default="https"/> +<shortdesc lang="en">VMware vCenter protocol</shortdesc> +<longdesc lang="en"> +The VMware vCenter protocol +</longdesc> +</parameter> +<parameter name="VI_PORTNUMBER"> +<content type="string" default="443"/> +<shortdesc lang="en">VMware vCenter port number</shortdesc> +<longdesc lang="en"> +The VMware vCenter port number +</longdesc> +</parameter> +<parameter name="VI_SERVICEPATH"> +<content type="string" default="/sdk"/> +<shortdesc lang="en">VMware vCenter service path</shortdesc> +<longdesc lang="en"> +The VMware vCenter services path +</longdesc> +</parameter> +<parameter name="VI_CREDSTORE" required="1"> +<content type="string"/> +<shortdesc lang="en">VMware vCenter credentials store file</shortdesc> +<longdesc lang="en"> +VMware vCenter credentials store file +</longdesc> +</parameter> +<parameter name="RESETPOWERON"> +<content type="string" default="1"/> +<shortdesc lang="en">PowerOnVM on reset</shortdesc> +<longdesc lang="en"> +Enable/disable a PowerOnVM on reset when the target virtual machine is off +Allowed values: 0, 1 +</longdesc> +</parameter> +<parameter name="PERL_LWP_SSL_VERIFY_HOSTNAME"> +<content type="string"/> +<shortdesc lang="en">Enable or disable SSL hostname verification</shortdesc> +<longdesc lang="en"> +To disable SSL hostname verification set this option to 0. +To enable hostname verification, set this option to 1. +This option is actually part of the LWP Perl library. +See LWP(3pm) for more information. +</longdesc> +</parameter> +</parameters>} . "\n"; + } + else { dielog("Invalid command specified: $command\n"); } +} + +# Command belongs to the group of commands that require connecting to VMware vCenter +elsif ($command ~~ @netCommands) { + + eval { require VMware::VIRuntime; } + or dielog("Missing perl module VMware::VIRuntime. Download and install 'VMware Infrastructure (VI) Perl Toolkit', available at http://www.vmware.com/support/developer/viperltoolkit/ \n"); + + # A valid VI_CREDSTORE is required to avoid interactive prompt + ( exists $ENV{'VI_CREDSTORE'} ) || dielog("VI_CREDSTORE not specified\n"); + + # HOSTLIST is mandatory + exists $ENV{'HOSTLIST'} || dielog("HOSTLIST not specified\n"); + + # Parse HOSTLIST to %host_to_vm and %vm_to_host + my @hostlist = split(';', $ENV{'HOSTLIST'}); + my %host_to_vm = (); + my %vm_to_host = (); + foreach my $host (@hostlist) { + my @config = split(/=/, $host); + my $key = $config[0]; my $value = $config[1]; + if (!defined($value)) { $value = $config[0]; } + $host_to_vm{$key} = $value; + $vm_to_host{(lc $value)} = $key; + } + + eval { + # VI API: reads options from the environment variables into appropriate data structures for validation. + Opts::parse(); + # VI API: ensures that input values from environment variable are complete, consistent and valid. + Opts::validate(); + # VI API: establishes a session with the VirtualCenter Management Server or ESX Server Web service + Util::connect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } + + # Command belongs to the group of commands that performs actions on Virtual Machines + if ($command ~~ @actionCommands) { + + my $targetHost = $ARGV[1] || dielog("No target specified\n"); + + # Require that specified target host exists in the specified HOSTLIST + if (exists $host_to_vm{$targetHost}) { + + my $vm; + my $esx; + eval { + # VI API: searches the inventory tree for a VirtualMachine managed entity whose name matches + # the name of the virtual machine assigned to the target host in HOSTLIST + $vm = Vim::find_entity_view(view_type => "VirtualMachine", filter => { name => qr/^\Q$host_to_vm{$targetHost}\E/i }); + if (!defined $vm) { + dielog("Machine $targetHost was not found"); + } + + # VI API: retrieves the properties of the managed object reference runtime.host of the VirtualMachine + # managed entity obtained by the previous command + # NOTE: This is essentially a workaround to vSphere Perl SDK + # to allow pointing to the right HostSystem. This is probably + # done by changing the current HostSystem in the Web Service + # session context. WARNING: Do not use the same session for any + # other concurrent operation. + $esx = Vim::get_view(mo_ref => $vm->{"runtime"}{"host"})->name; + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + my $powerState = $vm->get_property('runtime.powerState')->val; + if ($powerState eq "suspended") { + # This implementation assumes that suspending a cluster node can cause + # severe failures on shared resources, thus any failover operation should + # be blocked. + dielog("Machine $esx:$vm->{'name'} is in a suspended state\n"); + } + + eval { + if ($command eq "reset") { + if ($powerState eq "poweredOn") { + $vm->ResetVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been reset"); + } else { + system("ha_log.sh", "warn", "Tried to ResetVM $esx:$vm->{'name'} that was $powerState"); + # Start a virtual machine on reset only if explicitly allowed by RESETPOWERON + if ($powerState eq "poweredOff" && (! exists $ENV{'RESETPOWERON'} || $ENV{'RESETPOWERON'} ne 0)) { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + dielog("Could not complete $esx:$vm->{'name'} power cycle"); + } + } + } + elsif ($command eq "off") { + if ($powerState eq "poweredOn") { + $vm->PowerOffVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered off"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOffVM $esx:$vm->{'name'} that was $powerState"); + + } + } + elsif ($command eq "on") { + if ($powerState eq "poweredOff") { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOnVM $esx:$vm->{'name'} that was $powerState"); + } + } + else { dielog("Invalid command specified: $command\n"); } + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + } else { dielog("Invalid target specified\n"); } + } else { + # Command belongs to the group of commands that lookup the status of VMware vCenter and/or virtual machines + if ($command eq "status") { + # we already connect to the vcenter, no need to do + # anything else in status + ; + } + elsif ($command eq "gethosts") { + foreach my $key (keys(%host_to_vm)) { + print "$key \n"; + } + } + elsif ($command eq "listvms") { + eval { + # VI API: Searches the inventory tree for all VirtualMachine managed objects + my $vms = Vim::find_entity_views(view_type => "VirtualMachine"); + if (defined $vms) { + printf(STDERR "%-50s %-20s\n", "VM Name", "Power state"); + print STDERR "-" x 70 . "\n"; + foreach my $vm (@$vms) { + my $powerState = $vm->get_property('runtime.powerState')->val; + printf("%-50s %-20s\n", $vm->{name}, $powerState); + } + } + }; + } + else { dielog("Invalid command specified: $command\n"); } + } + eval { + Util::disconnect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } +} +else { dielog("Invalid command specified: $command\n"); } + +exit(0); diff --git a/lib/plugins/stonith/external/vmware b/lib/plugins/stonith/external/vmware new file mode 100644 index 0000000..55966ba --- /dev/null +++ b/lib/plugins/stonith/external/vmware @@ -0,0 +1,216 @@ +#!/usr/bin/perl +# External STONITH module for VMWare Server Guests +# +# Copyright (c) 2004 SUSE LINUX AG - Andrew Beekhof <abeekhof@suse.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +sub supply_default +{ + my $name = $_[0]; + my $value = $_[1]; + + if ( defined $ENV{$name} ) { + #print "Set: $name=$ENV{$name}\n"; + } else { + $ENV{$name} = $value; + #print "Default: $name=$ENV{$name}\n"; + } +} + +sub vmware_command +{ + my $config = $_[0]; + my $action = $_[1]; + my @lines; + + my $device = $ENV{'device_host'}; + + if ( $device =~ /localhost/ ) { + @lines = readpipe "vmware-cmd $config $action"; + + } else { + @lines = readpipe "ssh $device \"vmware-cmd \\\"$config\\\" $action\""; + } + + #print @lines; + return @lines; +} + +sub is_host_active +{ + my $config = config_for_host($_[0]); + my @lines = vmware_command($config, "getstate"); + foreach $line (@lines) { + if ( $line =~ /getstate.* = on/ ) { + return 1; + } + } + return 0; +} + +sub supported_hosts +{ + my $line; + my @lines; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + @config = split(/=/, $line); + $host = $config[0]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + + } else { + @lines = vmware_command("-l"); + foreach $line (@lines){ + my @elements = split(/\//, $line); + $host = $elements[$#elements-1]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + } +} + +sub config_for_host +{ + my $line; + my @lines; + my $target = $_[0]; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + if ( $line =~ /^$target=/ ) { + @config = split(/=/, $line); + return $config[1]; + } + } + + } else { + @lines = vmware_command("-l"); + + foreach $line (@lines){ + if ( $line =~ /\/$target\// ) { + chop($line); + return $line; + } + } + } +} + +$command = $ARGV[0]; +if ( defined $ARGV[1] ) { + $targetHost = $ARGV[1]; +} + +supply_default("device_host", "localhost"); + +if ( $command =~ /^gethosts$/ ) { + supported_hosts; + +} elsif ( $command =~ /^getconfignames$/ ) { + print "device_host\n"; + +} elsif ( $command =~ /^getinfo-devid$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devname$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devdescr$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devurl$/ ) { + print "http://www.vmware.com/"; + +} elsif ( $command =~ /^on$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "start hard"); + print @lines; + +} elsif ( $command =~ /^off$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "stop hard"); + print @lines; + +} elsif ( $command =~ /^reset$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "reset hard"); + print @lines; + +} elsif ( $command =~ /^status$/ ) { + my $rc = 7; + my $device = $ENV{'device_host'}; + if ( $device =~ /localhost/ ) { + $rc = 0; + # TODO: Check for the vmware process + print "Local version: always running\n"; + + } else { + print "Remote version: running ping\n"; + @lines = readpipe "ping -c1 $device"; + print @lines; + + foreach $line ( @lines ) { + if ( $line =~ /0% packet loss/ ) { + $rc = 0; + last; + } + } + } + exit($rc); + +} elsif ( $command =~ /^getinfo-xml$/ ) { + $metadata = <<END; +<parameters> +<parameter name="host_map" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Host Map +</shortdesc> +<longdesc lang="en"> +A mapping of hostnames to config paths supported by this device. +Eg. host1=/config/path/1;host2=/config/path/2;host3=/config/path/3; +</longdesc> +</parameter> +<parameter name="device_host" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Device Host +</shortdesc> +<longdesc lang="en"> +The machine _hosting_ the virtual machines +</longdesc> +</parameter> +</parameters> +END + +print $metadata; + +} else { + print "Command $command: not supported\n"; + exit(3); # Not implemented +} + + +exit(0); diff --git a/lib/plugins/stonith/external/xen0 b/lib/plugins/stonith/external/xen0 new file mode 100644 index 0000000..ef1ee40 --- /dev/null +++ b/lib/plugins/stonith/external/xen0 @@ -0,0 +1,253 @@ +#!/bin/sh +# +# External STONITH module for Xen Dom0 through ssh. +# +# Description: Uses Xen Dom0 Domain as a STONITH device +# to control DomUs. +# +# +# Author: Serge Dubrouski (sergeyfd@gmail.com) +# Inspired by Lars Marowsky-Bree's external/ssh agent. +# +# Copyright 2007 Serge Dubrouski <sergeyfd@gmail.com> +# License: GNU General Public License (GPL) +# + +STOP_COMMAND="xm destroy" +START_COMMAND="xm create" +DUMP_COMMAND="xm dump-core" +DEFAULT_XEN_DIR="/etc/xen" +SSH_COMMAND="/usr/bin/ssh -q -x -n" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +CheckIfDead() { + for j in 1 2 3 4 5 + do + if ! ping -w1 -c1 "$1" >/dev/null 2>&1 + then + return 0 + fi + sleep 1 + done + + return 1 +} + +CheckHostList() { + if [ "x" = "x$hostlist" ] + then + ha_log.sh err "hostlist isn't set" + exit 1 + fi +} + +CheckDom0() { + if [ "x" = "x$dom0" ] + then + ha_log.sh err "dom0 isn't set" + exit 1 + fi +} + +RunCommand() { + CheckHostList + CheckDom0 + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + if [ "x" = "x$node" ] + then + ha_log.sh err "Syntax error in host list" + exit 1 + fi + + if [ "x" = "x$cfg" ] + then + cfg="${DEFAULT_XEN_DIR}/${node}.cfg" + fi + + if [ "$node" != "$1" ] + then + continue + fi + + case $2 in + stop) + kill_node=`$SSH_COMMAND $dom0 "grep ^[[:space:]]*name $cfg" | cut -f 2 -d '=' | sed -e 's,",,g'` + if [ "x" = "x$kill_node" ] + then + ha_log.sh err "Couldn't find a node name to stop" + exit 1 + fi + + if [ "x$run_dump" != "x" ] + then + #Need to run core dump + if [ "x$dump_dir" != "x" ] + then + #Dump with the specified core file + TIMESTAMP=`date +%Y-%m%d-%H%M.%S` + DOMAINNAME=`printf "%s" $kill_node` + COREFILE=$dump_dir/$TIMESTAMP-$DOMAINNAME.core + $SSH_COMMAND $dom0 "(mkdir -p $dump_dir; $DUMP_COMMAND $kill_node $COREFILE) >/dev/null 2>&1" + else + $SSH_COMMAND $dom0 "$DUMP_COMMAND $kill_node >/dev/null 2>&1" + fi + fi + $SSH_COMMAND $dom0 "(sleep 2; $STOP_COMMAND $kill_node) >/dev/null 2>&1 &" + break;; + start) + $SSH_COMMAND $dom0 "(sleep 2; $START_COMMAND $cfg) >/dev/null 2>&1 &" + break;; + esac + exit 0 + done +} + + +# Main code + +case $1 in +gethosts) + CheckHostList + + for h in $hostlist ; do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + done + exit 0 + ;; +on) + RunCommand $2 start + exit $? + ;; +off) + if RunCommand $2 stop + then + if CheckIfDead $2 + then + exit 0 + fi + fi + + exit 1 + ;; +reset) + RunCommand $2 stop + + if CheckIfDead $2 + then + RunCommand $2 start + exit 0 + fi + + exit 1 + ;; +status) + CheckHostList + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + if ping -w1 -c1 "$node" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist dom0" + exit 0 + ;; +getinfo-devid) + echo "xen0 STONITH device" + exit 0 + ;; +getinfo-devname) + echo "xen0 STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset for Xen DomU trough Dom0" + echo "Fine for testing, but not really suitable for production!" + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org http://www.xensource.com/ http://linux-ha.org/wiki" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of controlled nodes in a format node[:config_file]. +For example: "node1:/opt/xen/node1.cfg node2" +If config file isn't set it defaults to /etc/xen/{node_name}.cfg +</longdesc> +</parameter> +<parameter name="dom0" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Dom0 +</shortdesc> +<longdesc lang="en"> +Name of the Dom0 Xen node. Root user shall be able to ssh to that node. +</longdesc> +</parameter> +<parameter name="run_dump" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Run dump-core +</shortdesc> +<longdesc lang="en"> +If set plugin will call "xm dump-core" before killing DomU +</longdesc> +</parameter> +<parameter name="dump_dir" unique="1" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Run dump-core with the specified directory +</shortdesc> +<longdesc lang="en"> +This parameter can indicate the dump destination. +Should be set as a full path format, ex.) "/var/log/dump" +The above example would dump the core, like; +/var/log/dump/2009-0316-1403.37-domU.core +</longdesc> +</parameter> +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper new file mode 100755 index 0000000..b313f8b --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper @@ -0,0 +1,72 @@ +#!/bin/bash +# Author: Lars Marowsky-Bree +# +# Copyright 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) + +# This is not an external/stonith plugin by itself, but instead a helper +# script which gets installed in Dom0. + +# TODO: +# - Error handling +# - How to handle if the DomU resource doesn't exist? +# - Does this truly work with split-brain? +# - Is the handling of non-existent resources adequate? +# ... +# Basically: more testing. This is proof-of-concept and works, but deserves +# validation. + +CMD="$1" +DOMU="$2" +TIMEOUT="$3" + +# Make sure the timeout is an integer: +if [ "0$TIMEOUT" -eq 0 ]; then + TIMEOUT=300 +fi + +SetTargetRole() { + local new_role="$1" + crm_resource -r $DOMU --meta -p target_role -v $new_role + + local timeout="$TIMEOUT" + + # We only need to wait for "stopped". + if [ "$new_role" != "stopped" ]; then + return 0 + fi + + while [ $timeout -gt 0 ]; do + local rc + crm_resource -W -r $DOMU 2>&1 | grep -q "is NOT running" + rc=$? + if [ $rc -eq 0 ]; then + return 0 + fi + timeout=$[timeout-1]; + sleep 1 + done + return 1 +} + + +case $CMD in +on) SetTargetRole started + exit $? + ;; +off) SetTargetRole stopped + exit $? + ;; +reset) SetTargetRole stopped || exit 1 + SetTargetRole started + exit $? + ;; +status) exit 0 + ;; +*) ha_log.sh err "Called with unknown command: $CMD" + exit 1 + ;; +esac + +exit 1 + diff --git a/lib/plugins/stonith/external/xen0-ha.in b/lib/plugins/stonith/external/xen0-ha.in new file mode 100755 index 0000000..cb42cbc --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha.in @@ -0,0 +1,96 @@ +#!/bin/bash +# +# This STONITH script integrates a cluster running within DomUs +# with the CRM/Pacemaker cluster running in Dom0. +# +# Author: Lars Marowsky-Bree +# Copyright: 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) +# + +SSH_COMMAND="@SSH@ -q -x -n" +HVM_HELPER="@stonith_plugindir@/xen0-ha-dom0-stonith-helper" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +# Runs a command on the host, waiting for it to return +RunHVMCommand() { + $SSH_COMMAND $dom0_cluster_ip "$HVM_HELPER $1 $2 $stop_timeout" +} + +# Main code +case $1 in +gethosts) + echo $hostlist + exit 0 + ;; +on|off|reset|status) + RunHVMCommand $1 $2 + exit $? + ;; +getconfignames) + echo "hostlist dom0_cluster_ip timeout" + exit 0 + ;; +getinfo-devid) + echo "xen0-ha DomU/Dom0 device" + exit 0 + ;; +getinfo-devname) + echo "xen0-ha DomU/Dom0 external device" + exit 0 + ;; +getinfo-devdescr) + echo "Allows STONITH to control DomUs managed by a CRM/Pacemaker Dom0." + echo "Requires Xen + CRM/Pacemaker at both layers." + echo "Proof-of-concept code!" + exit 0 + ;; +getinfo-devurl) + echo "http://linux-ha.org/wiki/DomUClusters" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of controlled DomUs, separated by whitespace. +These must be configured as Xen RA resources with a name with a matching +id. +For example: "xen-1 xen-2 xen-3" +</longdesc> +</parameter> +<parameter name="dom0_cluster_ip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Dom0 cluster ip +</shortdesc> +<longdesc lang="en"> +The cluster IP address associated with Dom0. +Root user must be able to ssh to that node. +</longdesc> +</parameter> +<parameter name="stop_timeout"> +<content type="integer" /> +<shortdesc lang="en"> +Stop timeout +</shortdesc> +<longdesc lang="en"> +The timeout, in seconds, for which to wait for Dom0 to report that the +DomU has been stopped, before aborting with a failure. +</longdesc> +</parameter> +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/ibmhmc.c b/lib/plugins/stonith/ibmhmc.c new file mode 100644 index 0000000..d33fea9 --- /dev/null +++ b/lib/plugins/stonith/ibmhmc.c @@ -0,0 +1,1261 @@ +/* + * Stonith module for IBM Hardware Management Console (HMC) + * + * Author: Huang Zhen <zhenh@cn.ibm.com> + * Support for HMC V4+ added by Dave Blaschke <debltc@us.ibm.com> + * + * Copyright (c) 2004 International Business Machines + * + * 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 + * + */ + +/* + * + * This code has been tested in following environment: + * + * Hardware Management Console (HMC): Release 3, Version 2.4 + * - Both FullSystemPartition and LPAR Partition: + * - p630 7028-6C4 two LPAR partitions + * - p650 7038-6M2 one LPAR partition and FullSystemPartition + * + * Hardware Management Console (HMC): Version 4, Release 2.1 + * - OP720 1000-6CA three LPAR partitions + * + * Note: Only SSH access to the HMC devices are supported. + * + * This command would make a nice status command: + * + * lshmc -r -F ssh + * + * The following V3 command will get the list of systems we control and their + * mode: + * + * lssyscfg -r sys -F name:mode --all + * + * 0 indicates full system partition + * 255 indicates the system is partitioned + * + * The following V4 command will get the list of systems we control: + * + * lssyscfg -r sys -F name + * + * The following V3 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name --all + * + * Note that we should probably only consider partitions whose boot mode + * is normal (1). (that's my guess, anyway...) + * + * The following V4 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name + * + * The following V3 commands provide the reset/on/off actions: + * + * FULL SYSTEM: + * on: chsysstate -m %1 -r sys -o on -n %1 -c full + * off: chsysstate -m %1 -r sys -o off -n %1 -c full -b norm + * reset:chsysstate -m %1 -r sys -o reset -n %1 -c full -b norm + * + * Partitioned SYSTEM: + * on: chsysstate -m %1 -r lpar -o on -n %2 + * off: reset_partition -m %1 -p %2 -t hard + * reset:do off action above, followed by on action... + * + * where %1 is managed-system, %2 is-lpar name + * + * The following V4 commands provide the reset/on/off actions: + * + * on: chsysstate -m %1 -r lpar -o on -n %2 -f %3 + * off: chsysstate -m %1 -r lpar -o shutdown -n %2 --immed + * reset:chsysstate -m %1 -r lpar -o shutdown -n %2 --immed --restart + * + * where %1 is managed-system, %2 is lpar-name, %3 is profile-name + * + * Of course, to do all this, we need to track which partition name goes with + * which managed system's name, and which systems on the HMC are partitioned + * and which ones aren't... + */ + +#include <lha_internal.h> + +#define DEVICE "IBM HMC" + +#include "stonith_plugin_common.h" + +#ifndef SSH_CMD +# define SSH_CMD "ssh" +#endif +#ifndef HMCROOT +# define HMCROOT "hscroot" +#endif + +#define PIL_PLUGIN ibmhmc +#define PIL_PLUGIN_S "ibmhmc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define MAX_HOST_NAME_LEN (256*4) +#define MAX_CMD_LEN 2048 +#define FULLSYSTEMPARTITION "FullSystemPartition" +#define MAX_POWERON_RETRY 10 +#define MAX_HMC_NAME_LEN 256 + +#define ST_MANSYSPAT "managedsyspat" +#define NOPASS "nopass" + +#define STATE_UNKNOWN -1 +#define STATE_OFF 0 +#define STATE_ON 1 +#define STATE_INVALID 2 + +#define HMCURL "http://publib-b.boulder.ibm.com/redbooks.nsf/RedbookAbstracts"\ + "/SG247038.html" + +static StonithPlugin * ibmhmc_new(const char *); +static void ibmhmc_destroy(StonithPlugin *); +static const char * ibmhmc_getinfo(StonithPlugin * s, int InfoType); +static const char * const * ibmhmc_get_confignames(StonithPlugin* p); +static int ibmhmc_status(StonithPlugin * ); +static int ibmhmc_reset_req(StonithPlugin * s,int request,const char* host); +static char ** ibmhmc_hostlist(StonithPlugin *); +static int ibmhmc_set_config(StonithPlugin *, StonithNVpair*); + +static struct stonith_ops ibmhmcOps = { + ibmhmc_new, /* Create new STONITH object */ + ibmhmc_destroy, /* Destroy STONITH object */ + ibmhmc_getinfo, /* Return STONITH info string */ + ibmhmc_get_confignames, /* Return configuration parameters */ + ibmhmc_set_config, /* Set configuration */ + ibmhmc_status, /* Return STONITH device status */ + ibmhmc_reset_req, /* Request a reset */ + ibmhmc_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; + + +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 + , &ibmhmcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * hmc; + GList* hostlist; + int hmcver; + char * password; + char ** mansyspats; +}; + +static const char * pluginid = "HMCDevice-Stonith"; +static const char * NOTpluginID = "IBM HMC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_MANSYSPAT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MANSYSPAT \ + XML_PARM_SHORTDESC_END + +#define XML_MANSYSPAT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "White-space delimited list of patterns used to match managed system names; if last character is '*', all names that begin with the pattern are matched" \ + XML_PARM_LONGDESC_END + +#define XML_MANSYSPAT_PARM \ + XML_PARAMETER_BEGIN(ST_MANSYSPAT, "string", "0", "0") \ + XML_MANSYSPAT_SHORTDESC \ + XML_MANSYSPAT_LONGDESC \ + XML_PARAMETER_END + +#define XML_OPTPASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Password for " HMCROOT " if passwordless ssh access to HMC has NOT been setup (to do so, it is necessary to create a public/private key pair with empty passphrase - see \"Configure the OpenSSH Client\" in the redbook at " HMCURL " for more details)" \ + XML_PARM_LONGDESC_END + +#define XML_OPTPASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "0", "0") \ + XML_PASSWD_SHORTDESC \ + XML_OPTPASSWD_LONGDESC \ + XML_PARAMETER_END + +static const char *ibmhmcXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_MANSYSPAT_PARM + XML_OPTPASSWD_PARM + XML_PARAMETERS_END; + +static int get_hmc_hostlist(struct pluginDevice* dev); +static void free_hmc_hostlist(struct pluginDevice* dev); +static int get_hmc_mansyspats(struct pluginDevice* dev, const char* mansyspats); +static void free_hmc_mansyspats(struct pluginDevice* dev); +static char* do_shell_cmd(const char* cmd, int* status, const char* password); +static int check_hmc_status(struct pluginDevice* dev); +static int get_num_tokens(char *str); +static gboolean pattern_match(char **patterns, char *string); +/* static char* do_shell_cmd_fake(const char* cmd, int* status); */ + +static int +ibmhmc_status(StonithPlugin *s) +{ + struct pluginDevice* dev = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + dev = (struct pluginDevice*) s; + + return check_hmc_status(dev); +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +ibmhmc_hostlist(StonithPlugin *s) +{ + int j; + struct pluginDevice* dev; + int numnames = 0; + char** ret = NULL; + GList* node = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice*) s; + + /* refresh the hostlist */ + free_hmc_hostlist(dev); + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return NULL; + } + + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "unconfigured stonith object in %s" + , __FUNCTION__); + return(NULL); + } + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + + memset(ret, 0, (numnames+1)*sizeof(char*)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + char* host = strchr((char*)node->data, '/'); + ret[j] = STRDUP(++host); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "out of memory"); + stonith_free_hostlist(ret); + return NULL; + } + strdown(ret[j]); + } + return ret; +} + + +static const char * const * +ibmhmc_get_confignames(StonithPlugin* p) +{ + static const char * names[] = {ST_IPADDR, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + + +/* + * Reset the given host, and obey the request type. + * We should reset without power cycle for the non-partitioned case + */ + +static int +ibmhmc_reset_req(StonithPlugin * s, int request, const char * host) +{ + GList* node = NULL; + struct pluginDevice* dev = NULL; + char off_cmd[MAX_CMD_LEN]; + char on_cmd[MAX_CMD_LEN]; + char reset_cmd[MAX_CMD_LEN]; + gchar** names = NULL; + int i; + int is_lpar = FALSE; + int status; + char* pch; + char* output = NULL; + char state_cmd[MAX_CMD_LEN]; + int state = STATE_UNKNOWN; + + status = 0; + if(Debug){ + LOG(PIL_DEBUG, "%s: called, host=%s\n", __FUNCTION__, host); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (NULL == host) { + LOG(PIL_CRIT, "invalid argument to %s", __FUNCTION__); + return(S_OOPS); + } + + dev = (struct pluginDevice*) s; + + for (node = g_list_first(dev->hostlist) + ; NULL != node + ; node = g_list_next(node)) { + if(Debug){ + LOG(PIL_DEBUG, "%s: node->data=%s\n" + , __FUNCTION__, (char*)node->data); + } + + if ((pch = strchr((char*)node->data, '/')) != NULL + && 0 == strcasecmp(++pch, host)) { + break; + } + } + + if (!node) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module. " + "Please check your configuration information.", host); + return (S_OOPS); + } + + names = g_strsplit((char*)node->data, "/", 2); + /* names[0] will be the name of managed system */ + /* names[1] will be the name of the lpar partition */ + if(Debug){ + LOG(PIL_DEBUG, "%s: names[0]=%s, names[1]=%s\n" + , __FUNCTION__, names[0], names[1]); + } + + if (dev->hmcver < 4) { + if (0 == strcasecmp(names[1], FULLSYSTEMPARTITION)) { + is_lpar = FALSE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o off -n %s -c full" + , dev->hmc, dev->hmc, names[0]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o on -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o reset -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + *state_cmd = 0; + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s reset_partition" + " -m %s -p %s -t hard" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r lpar -m %s -o on -n %s" + , dev->hmc, names[0], names[1]); + + *reset_cmd = 0; + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -r lpar -m %s -F state -n %s" + , dev->hmc, names[0], names[1]); + } + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n \"%s\" --immed" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F \"default_profile\"" + " --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return (S_OOPS); + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o on -n %s -f %s" + , dev->hmc, names[0], names[1], output); + FREE(output); + output = NULL; + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n %s --immed --restart" + , dev->hmc, names[0], names[1]); + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F state --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + } + g_strfreev(names); + + if(Debug){ + LOG(PIL_DEBUG, "%s: off_cmd=%s, on_cmd=%s," + "reset_cmd=%s, state_cmd=%s\n" + , __FUNCTION__, off_cmd, on_cmd, reset_cmd, state_cmd); + } + + output = do_shell_cmd(state_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (strcmp(output, "Running") == 0 + || strcmp(output, "Starting") == 0 + || strcmp(output, "Open Firmware") == 0) { + state = STATE_ON; + }else if (strcmp(output, "Shutting Down") == 0 + || strcmp(output, "Not Activated") == 0 + || strcmp(output, "Ready") == 0) { + state = STATE_OFF; + }else if (strcmp(output, "Not Available") == 0 + || strcmp(output, "Error") == 0) { + state = STATE_INVALID; + } + FREE(output); + output = NULL; + + if (state == STATE_INVALID) { + LOG(PIL_CRIT, "host %s in invalid state", host); + return S_OOPS; + } + + switch (request) { + case ST_POWERON: + if (state == STATE_ON) { + LOG(PIL_INFO, "host %s already on", host); + return S_OK; + } + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + break; + case ST_POWEROFF: + if (state == STATE_OFF) { + LOG(PIL_INFO, "host %s already off", host); + return S_OK; + } + + output = do_shell_cmd(off_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", off_cmd); + return S_OOPS; + } + break; + case ST_GENERIC_RESET: + if (dev->hmcver < 4) { + if (is_lpar) { + if (state == STATE_ON) { + output = do_shell_cmd(off_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s " + "failed", off_cmd); + return S_OOPS; + } + } + for (i = 0; i < MAX_POWERON_RETRY; i++) { + char *output2; + output2 = do_shell_cmd(on_cmd + , &status, dev->password); + if (output2 != NULL) { + FREE(output2); + } + if (0 != status) { + sleep(1); + }else{ + break; + } + } + if (MAX_POWERON_RETRY == i) { + LOG(PIL_CRIT, "command %s failed" + , on_cmd); + return S_OOPS; + } + }else{ + output = do_shell_cmd(reset_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed" , reset_cmd); + return S_OOPS; + } + break; + } + }else{ + if (state == STATE_ON) { + output = do_shell_cmd(reset_cmd + , &status, dev->password); + }else{ + output = do_shell_cmd(on_cmd + , &status, dev->password); + } + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", reset_cmd); + return S_OOPS; + } + } + break; + default: + return S_INVAL; + } + + if (output != NULL) { + FREE(output); + } + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + + return S_OK; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +ibmhmc_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice* dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {NULL, NULL} + }; + int rc; + char get_hmcver[MAX_CMD_LEN]; + char firstchar; + int firstnum; + char* output = NULL; + int status; + const char *mansyspats; + int len; + + ERRIFWRONGDEV(s,S_OOPS); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + dev = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + if(Debug){ + LOG(PIL_DEBUG, "%s: ipaddr=%s\n", __FUNCTION__ + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for managedsyspat */ + mansyspats = OurImports->GetValue(list, ST_MANSYSPAT); + if (mansyspats != NULL) { + if (get_hmc_mansyspats(dev, mansyspats) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + /* look for password */ + dev->password = STRDUP(OurImports->GetValue(list, ST_PASSWD)); + dev->hmc = namestocopy[0].s_value; + }else{ + /* -p or -F option with args "ipaddr [managedsyspat]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over ipaddr and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (get_hmc_mansyspats(dev, pch) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + + dev->hmc = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + } + + /* check whether the HMC has ssh command enabled */ + if (check_hmc_status(dev) != S_OK) { + LOG(PIL_CRIT, "HMC %s does not have remote " + "command execution using the ssh facility enabled", dev->hmc); + return S_BADCONFIG; + } + + /* get the HMC's version info */ + snprintf(get_hmcver, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -v | grep RM", dev->hmc); + if (Debug) { + LOG(PIL_DEBUG, "%s: get_hmcver=%s", __FUNCTION__, get_hmcver); + } + + output = do_shell_cmd(get_hmcver, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: output=%s\n", __FUNCTION__ + , output ? output : "(nil)"); + } + if (output == NULL) { + return S_BADCONFIG; + } + + /* parse the HMC's version info (i.e. "*RM V4R2.1" or "*RM R3V2.6") */ + if ((sscanf(output, "*RM %c%1d", &firstchar, &firstnum) == 2) + && ((firstchar == 'V') || (firstchar == 'R'))) { + dev->hmcver = firstnum; + if(Debug){ + LOG(PIL_DEBUG, "%s: HMC %s version is %d" + , __FUNCTION__, dev->hmc, dev->hmcver); + } + }else{ + LOG(PIL_CRIT, "%s: unable to determine HMC %s version" + , __FUNCTION__, dev->hmc); + FREE(output); + return S_BADCONFIG; + } + + len = strlen(output+4) + sizeof(DEVICE) + 1; + if (dev->idinfo != NULL) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + dev->idinfo = MALLOC(len * sizeof(char)); + if (dev->idinfo == NULL) { + LOG(PIL_CRIT, "out of memory"); + FREE(output); + return S_OOPS; + } + snprintf(dev->idinfo, len, "%s %s", DEVICE, output+4); + FREE(output); + + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + return S_OK; +} + + +static const char* +ibmhmc_getinfo(StonithPlugin* s, int reqtype) +{ + struct pluginDevice* dev; + const char* ret; + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->hmc; + break; + + case ST_DEVICEDESCR: + ret = "IBM Hardware Management Console (HMC)\n" + "Use for IBM i5, p5, pSeries and OpenPower systems " + "managed by HMC\n" + " Optional parameter name " ST_MANSYSPAT " is " + "white-space delimited list of\n" + "patterns used to match managed system names; if last " + "character is '*',\n" + "all names that begin with the pattern are matched\n" + " Optional parameter name " ST_PASSWD " is password " + "for " HMCROOT " if passwordless\n" + "ssh access to HMC has NOT been setup (to do so, it " + "is necessary to create\n" + "a public/private key pair with empty passphrase - " + "see \"Configure the\n" + "OpenSSH client\" in the redbook for more details)"; + break; + + case ST_DEVICEURL: + ret = HMCURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ibmhmcXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +ibmhmc_destroy(StonithPlugin *s) +{ + struct pluginDevice* dev; + + if(Debug){ + LOG(PIL_DEBUG, "%s : called\n", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->hmc) { + FREE(dev->hmc); + dev->hmc = NULL; + } + if (dev->password) { + FREE(dev->password); + dev->password = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_hmc_hostlist(dev); + free_hmc_mansyspats(dev); + + FREE(dev); +} + + +static StonithPlugin * +ibmhmc_new(const char *subplugin) +{ + struct pluginDevice* dev = ST_MALLOCT(struct pluginDevice); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return(NULL); + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->hmc = NULL; + dev->password = NULL; + dev->hostlist = NULL; + dev->mansyspats = NULL; + dev->hmcver = -1; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return(NULL); + } + dev->sp.s_ops = &ibmhmcOps; + + if(Debug){ + LOG(PIL_DEBUG, "%s: returning successfully\n", __FUNCTION__); + } + + return((void *)dev); +} + +static int +get_hmc_hostlist(struct pluginDevice* dev) +{ + int i, j, status; + char* output = NULL; + char get_syslist[MAX_CMD_LEN]; + char host[MAX_HOST_NAME_LEN]; + gchar** syslist = NULL; + gchar** name_mode = NULL; + char get_lpar[MAX_CMD_LEN]; + gchar** lparlist = NULL; + char* pch; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, dev->hmc=%s\n", __FUNCTION__ + , dev->hmc); + } + + if (dev->hmc == NULL || *dev->hmc == 0){ + return S_BADCONFIG; + } + + /* get the managed system's names of the hmc */ + if (dev->hmcver < 4) { + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD " -l " HMCROOT + " %s lssyscfg -r sys -F name:mode --all", dev->hmc); + }else{ + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD + " -l " HMCROOT " %s lssyscfg -r sys -F name", dev->hmc); + } + if(Debug){ + LOG(PIL_DEBUG, "%s: get_syslist=%s", __FUNCTION__, get_syslist); + } + + output = do_shell_cmd(get_syslist, &status, dev->password); + if (output == NULL) { + return S_BADCONFIG; + } + syslist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each managed system */ + for (i = 0; syslist[i] != NULL && syslist[i][0] != 0; i++) { + if (dev->hmcver < 4) { + name_mode = g_strsplit(syslist[i], ":", 2); + if(Debug){ + LOG(PIL_DEBUG, "%s: name_mode0=%s, name_mode1=%s\n" + , __FUNCTION__, name_mode[0], name_mode[1]); + } + + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, name_mode[0])) { + continue; + } + + /* if it is in fullsystempartition */ + if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "0", 1)) { + /* add the FullSystemPartition */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/FullSystemPartition", name_mode[0]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + }else if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "255", 3)){ + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name --all" + , dev->hmc, name_mode[0]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar + , &status, dev->password); + if (output == NULL) { + g_strfreev(name_mode); + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* skip the full system partition */ + if (0 == strncmp(lparlist[j] + , FULLSYSTEMPARTITION + , strlen(FULLSYSTEMPARTITION))) { + continue; + } + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", name_mode[0] + , lparlist[j]); + dev->hostlist = + g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + g_strfreev(name_mode); + }else{ + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, syslist[i])) { + continue; + } + + /* get its state */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r sys -F state" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (!strcmp(output, "No Connection")){ + FREE(output); + continue; + } + FREE(output); + + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", syslist[i],lparlist[j]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + } + g_strfreev(syslist); + + return S_OK; +} + +static void +free_hmc_hostlist(struct pluginDevice* dev) +{ + if (dev->hostlist) { + GList* node; + while (NULL != (node=g_list_first(dev->hostlist))) { + dev->hostlist = g_list_remove_link(dev->hostlist, node); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } +} + +static int +get_hmc_mansyspats(struct pluginDevice * dev, const char *mansyspats) +{ + char *patscopy; + int numpats; + int i; + char *tmp; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, mansyspats=%s\n" + , __FUNCTION__, mansyspats); + } + + patscopy = STRDUP(mansyspats); + if (patscopy == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return S_OOPS; + } + + numpats = get_num_tokens(patscopy); + if (numpats > 0) { + dev->mansyspats = MALLOC((numpats+1)*sizeof(char *)); + if (dev->mansyspats == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + FREE(patscopy); + return S_OOPS; + } + + memset(dev->mansyspats, 0, (numpats+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(patscopy, WHITESPACE); + while (tmp != NULL) { + dev->mansyspats[i] = STRDUP(tmp); + if (dev->mansyspats[i] == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + free_hmc_mansyspats(dev); + dev->mansyspats = NULL; + FREE(patscopy); + return S_OOPS; + } + + if(Debug){ + LOG(PIL_DEBUG, "%s: adding pattern %s\n" + , __FUNCTION__, dev->mansyspats[i]); + } + + /* no patterns necessary if all specified */ + if (strcmp(dev->mansyspats[i], "*") == 0) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + break; + } + + i++; + tmp = strtok(NULL, WHITESPACE); + } + } + FREE(patscopy); + return S_OK; +} + +static void +free_hmc_mansyspats(struct pluginDevice* dev) +{ + if (dev->mansyspats) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + } +} + +static char* +do_shell_cmd(const char* cmd, int* status, const char* password) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char cmd_password[MAX_CMD_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + + FILE* file; + if (NULL == password) { + file = popen(cmd, "r"); + } else { + snprintf(cmd_password, MAX_CMD_LEN + ,"umask 077;" + "if [ ! -d " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc ];" + "then mkdir " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc 2>/dev/null;" + "fi;" + "export ibmhmc_tmp=`mktemp -p " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc/`;" + "echo \"echo '%s'\">$ibmhmc_tmp;" + "chmod +x $ibmhmc_tmp;" + "unset SSH_AGENT_SOCK SSH_AGENT_PID;" + "SSH_ASKPASS=$ibmhmc_tmp DISPLAY=ibmhmc_foo setsid %s;" + "rm $ibmhmc_tmp -f;" + "unset ibmhmc_tmp" + ,password, cmd); + file = popen(cmd_password, "r"); + } + if (NULL == file) { + return NULL; + } + + g_str_tmp = g_string_new(""); + while(!feof(file)) { + memset(buff, 0, BUFF_LEN); + read_len = fread(buff, 1, BUFF_LEN, file); + if (0 < read_len) { + g_string_append(g_str_tmp, buff); + }else{ + sleep(1); + } + } + data = (char*)MALLOC(g_str_tmp->len+1); + if (data != NULL) { + data[0] = data[g_str_tmp->len] = 0; + strncpy(data, g_str_tmp->str, g_str_tmp->len); + } + g_string_free(g_str_tmp, TRUE); + *status = pclose(file); + return data; +} + +static int +check_hmc_status(struct pluginDevice* dev) +{ + int status; + char check_status[MAX_CMD_LEN]; + char* output = NULL; + int rc = S_OK; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, hmc=%s\n", __FUNCTION__, dev->hmc); + } + + snprintf(check_status, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -r -F ssh", dev->hmc); + if(Debug){ + LOG(PIL_DEBUG, "%s: check_status %s\n", __FUNCTION__ + , check_status); + } + + output = do_shell_cmd(check_status, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: status=%d, output=%s\n", __FUNCTION__ + , status, output ? output : "(nil)"); + } + + if (NULL == output || strncmp(output, "enable", 6) != 0) { + rc = S_BADCONFIG; + } + if (NULL != output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static gboolean +pattern_match(char **patterns, char *string) +{ + char **pattern; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, string=%s\n", __FUNCTION__, string); + } + + for (pattern = patterns; *pattern; pattern++) { + int patlen = strlen(*pattern); + + if (pattern[0][patlen-1] == '*') { + /* prefix match */ + if (strncmp(string, *pattern, patlen-1) == 0) { + return TRUE; + } + }else{ + /* exact match */ + if (strcmp(string, *pattern) == 0) { + return TRUE; + } + } + } + + return FALSE; +} + +/* +static char* +do_shell_cmd_fake(const char* cmd, int* status) +{ + printf("%s()\n", __FUNCTION__); + printf("cmd:%s\n", cmd); + *status=0; + return NULL; +} +*/ diff --git a/lib/plugins/stonith/ipmi_os_handler.c b/lib/plugins/stonith/ipmi_os_handler.c new file mode 100644 index 0000000..bdb6d6e --- /dev/null +++ b/lib/plugins/stonith/ipmi_os_handler.c @@ -0,0 +1,257 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.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 <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <OpenIPMI/os_handler.h> +#include <OpenIPMI/selector.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + + +#include <OpenIPMI/ipmi_int.h> + +#include <time.h> + +extern selector_t *os_sel; + +#if 0 +static void check_no_locks(os_handler_t *handler); +#define CHECK_NO_LOCKS(handler) check_no_locks(handler) +#else +#define CHECK_NO_LOCKS(handler) do {} while(0) +#endif + +struct os_hnd_fd_id_s +{ + int fd; + void *cb_data; + os_data_ready_t data_ready; + os_handler_t *handler; +}; + +static void +fd_handler(int fd, void *data) +{ + + os_hnd_fd_id_t *fd_data = (os_hnd_fd_id_t *) data; + + CHECK_NO_LOCKS(fd_data->handler); + fd_data->data_ready(fd, fd_data->cb_data, fd_data); + CHECK_NO_LOCKS(fd_data->handler); +} + +static int +add_fd(os_handler_t *handler, + int fd, + os_data_ready_t data_ready, + void *cb_data, + os_hnd_fd_id_t **id) +{ + os_hnd_fd_id_t *fd_data; + + fd_data = ipmi_mem_alloc(sizeof(*fd_data)); + if (!fd_data) + return ENOMEM; + + fd_data->fd = fd; + fd_data->cb_data = cb_data; + fd_data->data_ready = data_ready; + fd_data->handler = handler; + sel_set_fd_handlers(os_sel, fd, fd_data, fd_handler, NULL, NULL, NULL); + sel_set_fd_read_handler(os_sel, fd, SEL_FD_HANDLER_ENABLED); + sel_set_fd_write_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + sel_set_fd_except_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + + *id = fd_data; + return 0; +} + +static int +remove_fd(os_handler_t *handler, os_hnd_fd_id_t *fd_data) +{ + sel_clear_fd_handlers(os_sel, fd_data->fd); + sel_set_fd_read_handler(os_sel, fd_data->fd, SEL_FD_HANDLER_DISABLED); + ipmi_mem_free(fd_data); + return 0; +} + +struct os_hnd_timer_id_s +{ + void *cb_data; + os_timed_out_t timed_out; + sel_timer_t *timer; + int running; + os_handler_t *handler; +}; + +static void +timer_handler(selector_t *sel, + sel_timer_t *timer, + void *data) +{ + os_hnd_timer_id_t *timer_data = (os_hnd_timer_id_t *) data; + void *cb_data; + os_timed_out_t timed_out; + + CHECK_NO_LOCKS(timer_data->handler); + timed_out = timer_data->timed_out; + cb_data = timer_data->cb_data; + timer_data->running = 0; + timed_out(cb_data, timer_data); + CHECK_NO_LOCKS(timer_data->handler); +} + +static int +start_timer(os_handler_t *handler, + os_hnd_timer_id_t *id, + struct timeval *timeout, + os_timed_out_t timed_out, + void *cb_data) +{ + struct timeval now; + + if (id->running) + return EBUSY; + + id->running = 1; + id->cb_data = cb_data; + id->timed_out = timed_out; + + gettimeofday(&now, NULL); + now.tv_sec += timeout->tv_sec; + now.tv_usec += timeout->tv_usec; + while (now.tv_usec >= 1000000) { + now.tv_usec -= 1000000; + now.tv_sec += 1; + } + + return sel_start_timer(id->timer, &now); +} + +static int +stop_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + return sel_stop_timer(timer_data->timer); +} + +static int +alloc_timer(os_handler_t *handler, + os_hnd_timer_id_t **id) +{ + os_hnd_timer_id_t *timer_data; + int rv; + + timer_data = ipmi_mem_alloc(sizeof(*timer_data)); + if (!timer_data) + return ENOMEM; + + timer_data->running = 0; + timer_data->timed_out = NULL; + timer_data->handler = handler; + + rv = sel_alloc_timer(os_sel, timer_handler, timer_data, + &(timer_data->timer)); + if (rv) { + ipmi_mem_free(timer_data); + return rv; + } + + *id = timer_data; + return 0; +} + +static int +free_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + sel_free_timer(timer_data->timer); + ipmi_mem_free(timer_data); + return 0; +} + +static int +get_random(os_handler_t *handler, void *data, unsigned int len) +{ + int fd = open("/dev/urandom", O_RDONLY); + int rv; + + if (fd == -1) + return errno; + + rv = read(fd, data, len); + + close(fd); + return rv; +} + +static void +sui_log(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + ...) +{ + return; +} + +static void +sui_vlog(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + va_list ap) +{ + return; +} + + +os_handler_t ipmi_os_cb_handlers = +{ + .add_fd_to_wait_for = add_fd, + .remove_fd_to_wait_for = remove_fd, + + .start_timer = start_timer, + .stop_timer = stop_timer, + .alloc_timer = alloc_timer, + .free_timer = free_timer, + + .create_lock = NULL, + .destroy_lock = NULL, + .is_locked = NULL, + .lock = NULL, + .unlock = NULL, + .create_rwlock = NULL, + .destroy_rwlock = NULL, + .read_lock = NULL, + .write_lock = NULL, + .read_unlock = NULL, + .write_unlock = NULL, + .is_readlocked = NULL, + .is_writelocked = NULL, + + .get_random = get_random, + + .log = sui_log, + .vlog = sui_vlog +}; + + diff --git a/lib/plugins/stonith/ipmilan.c b/lib/plugins/stonith/ipmilan.c new file mode 100644 index 0000000..1efdfee --- /dev/null +++ b/lib/plugins/stonith/ipmilan.c @@ -0,0 +1,587 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.com> + * + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005. + * And passed the compiling with OpenIPMI-1.4.8. + * + * 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 + * + */ + + +/* + * See README.ipmi for information regarding this plugin. + * + */ + +#define DEVICE "IPMI Over LAN" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ipmilan +#define PIL_PLUGIN_S "ipmilan" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include <OpenIPMI/ipmi_types.h> +#include <OpenIPMI/ipmi_auth.h> + +#include "ipmilan.h" + +static StonithPlugin * ipmilan_new(const char *); +static void ipmilan_destroy(StonithPlugin *); +static const char * const * ipmilan_get_confignames(StonithPlugin *); +static int ipmilan_set_config(StonithPlugin *, StonithNVpair *); +static const char * ipmilan_getinfo(StonithPlugin * s, int InfoType); +static int ipmilan_status(StonithPlugin * ); +static int ipmilan_reset_req(StonithPlugin * s, int request, const char * host); +static char ** ipmilan_hostlist(StonithPlugin *); + +static struct stonith_ops ipmilanOps ={ + ipmilan_new, /* Create new STONITH object */ + ipmilan_destroy, /* Destroy STONITH object */ + ipmilan_getinfo, /* Return STONITH info string */ + ipmilan_get_confignames,/* Get configuration parameter names */ + ipmilan_set_config, /* Set configuration */ + ipmilan_status, /* Return STONITH device status */ + ipmilan_reset_req, /* Request a reset */ + ipmilan_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug); +const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +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 + , &ipmilanOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * ipmilan STONITH device. + * + * ipmilanHostInfo is a double linked list. Where the prev of the head always + * points to the tail. This is a little wierd. But it saves me from looping + * around to find the tail when destroying the list. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + int hostcount; + struct ipmilanHostInfo * hostlist; +}; + +static const char * pluginid = "IPMI-LANDevice-Stonith"; +static const char * NOTpluginid = "IPMI-LAN device has been destroyed"; + +#define ST_HOSTNAME "hostname" +#define ST_PORT "port" +#define ST_AUTH "auth" +#define ST_PRIV "priv" +#define ST_RESET_METHOD "reset_method" + +#include "stonith_config_xml.h" + +#define XML_HOSTNAME_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOSTNAME \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTNAME_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOSTNAME_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTNAME, "string", "1", "1") \ + XML_HOSTNAME_SHORTDESC \ + XML_HOSTNAME_LONGDESC \ + XML_PARAMETER_END + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number to where the IPMI message is sent" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_AUTH_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_AUTH \ + XML_PARM_SHORTDESC_END + +#define XML_AUTH_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The authorization type of the IPMI session (\"none\", \"straight\", \"md2\", or \"md5\")" \ + XML_PARM_LONGDESC_END + +#define XML_AUTH_PARM \ + XML_PARAMETER_BEGIN(ST_AUTH, "string", "1", "0") \ + XML_AUTH_SHORTDESC \ + XML_AUTH_LONGDESC \ + XML_PARAMETER_END + +#define XML_PRIV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PRIV \ + XML_PARM_SHORTDESC_END + +#define XML_PRIV_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The privilege level of the user (\"operator\" or \"admin\")" \ + XML_PARM_LONGDESC_END + +#define XML_PRIV_PARM \ + XML_PARAMETER_BEGIN(ST_PRIV, "string", "1", "0") \ + XML_PRIV_SHORTDESC \ + XML_PRIV_LONGDESC \ + XML_PARAMETER_END + +#define XML_RESET_METHOD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_RESET_METHOD \ + XML_PARM_SHORTDESC_END + +#define XML_RESET_METHOD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "How to reset the host (\"power_cycle\" or \"hard_reset\")" \ + XML_PARM_LONGDESC_END + +#define XML_RESET_METHOD_PARM \ + XML_PARAMETER_BEGIN(ST_RESET_METHOD, "string", "0", "0") \ + XML_RESET_METHOD_SHORTDESC \ + XML_RESET_METHOD_LONGDESC \ + XML_PARAMETER_END + +static const char *ipmilanXML = + XML_PARAMETERS_BEGIN + XML_HOSTNAME_PARM + XML_IPADDR_PARM + XML_PORT_PARM + XML_AUTH_PARM + XML_PRIV_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* + * Check the status of the IPMI Lan STONITH device. + * + * NOTE: not sure what we should do here since each host is configured + * seperately. + * + * Two options: + * 1) always return S_OK. + * 2) using IPMI ping to confirm the status for every host that's + * configured. + * + * For now I choose the option 1 hoping that I can get by. Maybe we should + * change it to option 2 later. + */ + +static int +ipmilan_status(StonithPlugin *s) +{ + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int ret, rv; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + ret = S_OK; + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + rv = do_ipmi_cmd(node, ST_IPMI_STATUS); + if (rv) { + LOG(PIL_INFO, "Host %s ipmilan status failure." + , node->hostname); + ret = S_ACCESS; + } else { + LOG(PIL_INFO, "Host %s ipmilan status OK." + , node->hostname); + } + + } + + return ret; +} + +/* + * This function returns the list of hosts that's configured. + * + * The detailed configuration is disabled because the STONITH command can be + * run by anyone so there is a security risk if that to be exposed. + */ + +static char * +get_config_string(struct pluginDevice * nd, int index) +{ + struct ipmilanHostInfo * host; + int i; + + if (index >= nd->hostcount || index < 0) { + return (NULL); + } + + host = nd->hostlist; + for (i = 0; i < index; i++) { + host = host->next; + } + + return STRDUP(host->hostname); +} + + +/* + * Return the list of hosts configured for this ipmilan device + * + */ + +static char ** +ipmilan_hostlist(StonithPlugin *s) +{ + int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nd; + int j; + + ERRIFWRONGDEV(s,NULL); + + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in ipmi_hostlist"); + return(NULL); + } + numnames = nd->hostcount; + + ret = (char **)MALLOC((numnames + 1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (ret); + } + + memset(ret, 0, (numnames + 1)*sizeof(char*)); + + for (j = 0; j < numnames; ++j) { + ret[j] = get_config_string(nd, j); + if (!ret[j]) { + stonith_free_hostlist(ret); + ret = NULL; + break; + } + strdown(ret[j]); + } + + return(ret); +} + +/* + * Parse the config information, and stash it away... + * + * The buffer for each string is MAX_IPMI_STRING_LEN bytes long. + * Right now it is set to 64. Hope this is enough. + * + */ + +#define MAX_IPMI_STRING_LEN 64 + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +ipmilan_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + if (strcasecmp(node->hostname, host) == 0) { + break; + } + } + + if (i >= nd->hostcount) { + LOG(PIL_CRIT, "Host %s is not configured in this STONITH " + " module. Please check your configuration file.", host); + return (S_OOPS); + } + + rc = do_ipmi_cmd(node, request); + if (!rc) { + LOG(PIL_INFO, "Host %s ipmilan-reset.", host); + } else { + LOG(PIL_INFO, "Host %s ipmilan-reset error. Error = %d." + , host, rc); + } + return rc; +} + +/* + * Get configuration parameter names + */ +static const char * const * +ipmilan_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = + { ST_HOSTNAME, ST_IPADDR, ST_PORT, ST_AUTH, + ST_PRIV, ST_LOGIN, ST_PASSWD, ST_RESET_METHOD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +ipmilan_set_config(StonithPlugin* s, StonithNVpair * list) +{ + struct pluginDevice* nd; + int rc; + struct ipmilanHostInfo * tmp; + const char *reset_opt; + + StonithNamesToGet namestocopy [] = + { {ST_HOSTNAME, NULL} + , {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_AUTH, NULL} + , {ST_PRIV, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice *)s; + + ERRIFWRONGDEV(s, S_OOPS); + if (nd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + tmp = ST_MALLOCT(struct ipmilanHostInfo); + tmp->hostname = namestocopy[0].s_value; + tmp->ipaddr = namestocopy[1].s_value; + tmp->portnumber = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + if (namestocopy[3].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan auth type is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[3].s_value, "none") == 0) { + tmp->authtype = 0; + } else if (strcmp(namestocopy[3].s_value, "md2") == 0) { + tmp->authtype = 1; + } else if (strcmp(namestocopy[3].s_value, "md5") == 0) { + tmp->authtype = 2; + } else if (strcmp(namestocopy[3].s_value, "key") == 0 || + strcmp(namestocopy[3].s_value, "password") == 0 || + strcmp(namestocopy[3].s_value, "straight") == 0) { + tmp->authtype = 4; + } else { + LOG(PIL_CRIT, "ipmilan auth type '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[3].s_value); + return S_OOPS; + } + FREE(namestocopy[3].s_value); + if (namestocopy[4].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan priv value is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[4].s_value, "operator") == 0) { + tmp->privilege = 3; + } else if (strcmp(namestocopy[4].s_value, "admin") == 0) { + tmp->privilege = 4; + } else { + LOG(PIL_CRIT, "ipmilan priv value '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[4].s_value); + return(S_OOPS); + } + FREE(namestocopy[4].s_value); + tmp->username = namestocopy[5].s_value; + tmp->password = namestocopy[6].s_value; + reset_opt = OurImports->GetValue(list, ST_RESET_METHOD); + if (!reset_opt || !strcmp(reset_opt, "power_cycle")) { + tmp->reset_method = 0; + } else if (!strcmp(reset_opt, "hard_reset")) { + tmp->reset_method = 1; + } else { + LOG(PIL_CRIT, "ipmilan reset_method '%s' invalid", reset_opt); + return S_OOPS; + } + + if (nd->hostlist == NULL ) { + nd->hostlist = tmp; + nd->hostlist->prev = tmp; + nd->hostlist->next = tmp; + } else { + tmp->prev = nd->hostlist->prev; + tmp->next = nd->hostlist; + nd->hostlist->prev->next = tmp; + nd->hostlist->prev = tmp; + } + nd->hostcount++; + + return(S_OK); +} + +static const char * +ipmilan_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = nd->hostlist ? nd->hostlist->hostname : NULL; + break; + + case ST_DEVICEDESCR: + ret = "IPMI LAN STONITH device\n"; + break; + + case ST_DEVICEURL: + ret = "http://www.intel.com/design/servers/ipmi/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ipmilanXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * ipmilan StonithPlugin destructor... + * + * The hostlist is a link list. So have to iterate through. + */ +static void +ipmilan_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + struct ipmilanHostInfo * host; + int i; + + VOIDERRIFWRONGDEV(s); + + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginid; + + if (nd->hostlist) { + host = nd->hostlist->prev; + for (i = 0; i < nd->hostcount; i++) { + struct ipmilanHostInfo * host_prev = host->prev; + + FREE(host->hostname); + FREE(host->ipaddr); + FREE(host->username); + FREE(host->password); + + FREE(host); + host = host_prev; + } + } + + nd->hostcount = -1; + FREE(nd); + ipmi_leave(); +} + +/* Create a new ipmilan StonithPlugin device. Too bad this function can't be static */ +static StonithPlugin * +ipmilan_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + LOG(PIL_WARN, "The ipmilan stonith plugin is deprecated! Please use external/ipmi."); + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = 0; + nd->idinfo = DEVICE; + nd->sp.s_ops = &ipmilanOps; + return(&(nd->sp)); +} diff --git a/lib/plugins/stonith/ipmilan.h b/lib/plugins/stonith/ipmilan.h new file mode 100644 index 0000000..fb548f0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan.h @@ -0,0 +1,41 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.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 + * + */ + +#define ST_IPMI_STATUS 4 +#include <time.h> + +struct ipmilanHostInfo { + char * hostname; + char * ipaddr; + int portnumber; + int authtype; + int privilege; + char * username; + char * password; + int reset_method; + + struct ipmilanHostInfo * prev; + struct ipmilanHostInfo * next; +}; + +int do_ipmi_cmd(struct ipmilanHostInfo * host, int request); +void ipmi_leave(void); diff --git a/lib/plugins/stonith/ipmilan_command.c b/lib/plugins/stonith/ipmilan_command.c new file mode 100644 index 0000000..a3de493 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_command.c @@ -0,0 +1,399 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.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 <stdio.h> + +#include <stdlib.h> /* malloc() */ +#include <unistd.h> /* getopt() */ +#include <string.h> /* strerror() */ +#include <netdb.h> /* gethostbyname() */ +#include <sys/types.h> +#include <sys/socket.h> + +#include <OpenIPMI/ipmiif.h> +#include <OpenIPMI/selector.h> +#include <OpenIPMI/ipmi_conn.h> +#include <OpenIPMI/ipmi_lan.h> +#include <OpenIPMI/ipmi_smi.h> +#include <OpenIPMI/ipmi_auth.h> +#include <OpenIPMI/ipmi_msgbits.h> +#include <OpenIPMI/ipmi_posix.h> +#include <OpenIPMI/ipmi_debug.h> + +#include "ipmilan.h" +#include <stonith/stonith.h> +#include <clplumbing/cl_log.h> + +#include <pils/plugin.h> +extern const PILPluginImports* PluginImports; + +/* #define DUMP_MSG 0 */ +#define OPERATION_TIME_OUT 10 + +os_handler_t *os_hnd=NULL; +selector_t *os_sel; +static ipmi_con_t *con; +extern os_handler_t ipmi_os_cb_handlers; +static int reset_method; + +static int request_done = 0; +static int op_done = 0; + +typedef enum ipmi_status { + /* + IPMI_CONNECTION_FAILURE, + IPMI_SEND_FAILURE, + IPMI_BAD_REQUEST, + IPMI_REQUEST_FAILED, + IPMI_TIME_OUT, + */ + IPMI_RUNNING = 99, +} ipmi_status_t; + +static ipmi_status_t gstatus; + +typedef enum chassis_control_request { + POWER_DOWN = 0X00, + POWER_UP = 0X01, + POWER_CYCLE = 0X02, + HARD_RESET = 0X03, + PULSE_DIAGNOSTIC_INTERRUPT = 0X04, + SOFT_SHUTDOWN = 0X05 +} chassis_control_request_t; + +void dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type); +int rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi); + +void send_ipmi_cmd(ipmi_con_t *con, int request); + +void timed_out(selector_t *sel, sel_timer_t *timer, void *data); + +void +timed_out(selector_t *sel, sel_timer_t *timer, void *data) +{ + PILCallLog(PluginImports->log,PIL_CRIT, "IPMI operation timed out... :(\n"); + gstatus = S_TIMEOUT; +} + +void +dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type) +{ + ipmi_system_interface_addr_t *smi_addr = NULL; + int i; + ipmi_ipmb_addr_t *ipmb_addr = NULL; + + if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) { + smi_addr = (struct ipmi_system_interface_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + smi_addr->lun, + msg->cmd); + } else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE) + || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { + ipmb_addr = (struct ipmi_ipmb_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + ipmb_addr->lun, + msg->cmd); + } + + for (i = 0; i < msg->data_len; i++) { + if (((i%16) == 0) && (i != 0)) { + printf("\n "); + } + fprintf(stderr, "%2.2x ", msg->data[i]); + } + fprintf(stderr, "\n"); +} + +/* + * This function gets called after the response comes back + * from the IPMI device. + * + * Some IPMI device does not return success, 0x00, to the + * remote node when the power-reset was issued. + * + * The host who sent the ipmi cmd might get a 0xc3, + * a timeout instead. This creates problems for + * STONITH operation, where status is critical. :( + * + * Right now I am only checking 0xc3 as the return. + * If your IPMI device returns some wired code after + * reset, you might want to add it in this code block. + * + */ + +int +rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi) +{ + int rv; + long request; + + /*dump_msg_data(&rspi->msg, &rspi->addr, "response");*/ + request = (long) rspi->data1; + + op_done = 1; + if( !rspi || !(rspi->msg.data) ) { + PILCallLog(PluginImports->log,PIL_CRIT, "No data received\n"); + gstatus = S_RESETFAIL; + return IPMI_MSG_ITEM_NOT_USED; + } + rv = rspi->msg.data[0]; + /* some IPMI device might not issue 0x00, success, for reset command. + instead, a 0xc3, timeout, is returned. */ + if (rv == 0x00) { + gstatus = S_OK; + } else if((rv == 0xc3 || rv == 0xff) && request == ST_GENERIC_RESET) { + PILCallLog(PluginImports->log,PIL_WARN , + "IPMI reset request failed: %x, but we assume that it succeeded\n", rv); + gstatus = S_OK; + } else { + PILCallLog(PluginImports->log,PIL_INFO + , "IPMI request %ld failed: %x\n", request, rv); + gstatus = S_RESETFAIL; + } + return IPMI_MSG_ITEM_NOT_USED; +} + +void +send_ipmi_cmd(ipmi_con_t *con, int request) +{ + ipmi_addr_t addr; + unsigned int addr_len; + ipmi_msg_t msg; + struct ipmi_system_interface_addr *si; + int rv; + ipmi_msgi_t *rspi; + /* chassis control command request is only 1 byte long */ + unsigned char cc_data = POWER_CYCLE; + + si = (void *) &addr; + si->lun = 0x00; + si->channel = IPMI_BMC_CHANNEL; + si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + addr_len = sizeof(*si); + + msg.netfn = IPMI_CHASSIS_NETFN; + msg.cmd = IPMI_CHASSIS_CONTROL_CMD; + msg.data = &cc_data; + msg.data_len = 1; + + switch (request) { + case ST_POWERON: + cc_data = POWER_UP; + break; + + case ST_POWEROFF: + cc_data = POWER_DOWN; + break; + + case ST_GENERIC_RESET: + cc_data = (reset_method ? POWER_CYCLE : HARD_RESET); + break; + + case ST_IPMI_STATUS: + msg.netfn = IPMI_APP_NETFN; + msg.cmd = IPMI_GET_DEVICE_ID_CMD; + msg.data_len = 0; + break; + + default: + gstatus = S_INVAL; + return; + } + + gstatus = S_ACCESS; + rspi = calloc(1, sizeof(ipmi_msgi_t)); + if (NULL == rspi) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: Out of memory\n"); + } else { + rspi->data1 = (void *) (long) request; + rv = con->send_command(con, &addr, addr_len, &msg, rsp_handler, rspi); + if (rv == -1) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: %x\n", rv); + } else { + request_done = 1; + } + } + + return; +} + +static void +con_changed_handler(ipmi_con_t *ipmi, int err, unsigned int port_num, + int still_connected, void *cb_data) +{ + int * request; + + if (err) { + PILCallLog(PluginImports->log,PIL_CRIT, "Unable to setup connection: %x\n", err); + return; + } + + if( !request_done ) { + request = (int *) cb_data; + send_ipmi_cmd(ipmi, *request); + } +} + +static int +setup_ipmi_conn(struct ipmilanHostInfo * host, int *request) +{ + int rv; + + struct hostent *ent; + struct in_addr lan_addr[2]; + int lan_port[2]; + int num_addr = 1; + int authtype = 0; + int privilege = 0; + char username[17]; + char password[17]; + + /*DEBUG_MSG_ENABLE();*/ + + os_hnd = ipmi_posix_get_os_handler(); + if (!os_hnd) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_smi_setup_con: Unable to allocate os handler"); + return 1; + } + + rv = sel_alloc_selector(os_hnd, &os_sel); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not allocate selector\n"); + return rv; + } + + ipmi_posix_os_handler_set_sel(os_hnd, os_sel); + + rv = ipmi_init(os_hnd); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_init erro: %d ", rv); + return rv; + } + + ent = gethostbyname(host->ipaddr); + if (!ent) { + PILCallLog(PluginImports->log,PIL_CRIT, "gethostbyname failed: %s\n", strerror(h_errno)); + return 1; + } + + memcpy(&lan_addr[0], ent->h_addr_list[0], ent->h_length); + lan_port[0] = host->portnumber; + lan_port[1] = 0; + + authtype = host->authtype; + privilege = host->privilege; + + memcpy(username, host->username, sizeof(username)); + memcpy(password, host->password, sizeof(password)); + + reset_method = host->reset_method; + + rv = ipmi_lan_setup_con(lan_addr, lan_port, num_addr, + authtype, privilege, + username, strlen(username), + password, strlen(password), + os_hnd, os_sel, + &con); + + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_lan_setup_con: %s\n", strerror(rv)); + return S_ACCESS; + } + +#if OPENIPMI_VERSION_MAJOR < 2 + con->set_con_change_handler(con, con_changed_handler, request); +#else + con->add_con_change_handler(con, con_changed_handler, request); +#endif + + gstatus = IPMI_RUNNING; + + rv = con->start_con(con); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not start IPMI connection: %x\n", rv); + gstatus = S_BADCONFIG; + return rv; + } + return S_OK; +} + +void +ipmi_leave() +{ + if( con && con->close_connection ) { + con->close_connection(con); + con = NULL; + } + if( os_sel ) { + sel_free_selector(os_sel); + os_sel = NULL; + } +} + +int +do_ipmi_cmd(struct ipmilanHostInfo * host, int request) +{ + int rv; + sel_timer_t * timer; + struct timeval timeout; + + request_done = 0; + op_done = 0; + + if( !os_hnd ) { + rv = setup_ipmi_conn(host, &request); + if( rv ) { + return rv; + } + } else { + send_ipmi_cmd(con, request); + } + + gettimeofday(&timeout, NULL); + timeout.tv_sec += OPERATION_TIME_OUT; + timeout.tv_usec += 0; + + sel_alloc_timer(os_sel, timed_out, NULL, &timer); + sel_start_timer(timer, &timeout); + + while (!op_done) { + rv = sel_select(os_sel, NULL, 0, NULL, NULL); + if (rv == -1) { + break; + } + } + + sel_free_timer(timer); + return gstatus; +} + +#if OPENIPMI_VERSION_MAJOR < 2 +void +posix_vlog(char *format, enum ipmi_log_type_e log_type, va_list ap) +{ +} +#endif diff --git a/lib/plugins/stonith/ipmilan_test.c b/lib/plugins/stonith/ipmilan_test.c new file mode 100644 index 0000000..47859a0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_test.c @@ -0,0 +1,63 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.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 + * + */ + +/* + * A quick test program to verify that IPMI host is setup correctly. + * + * You will need to modify the values in user, pass, ip, and port. + */ + +#include <stdio.h> +#include <string.h> +#include "ipmilan.h" +#include <OpenIPMI/ipmi_auth.h> + +int main(int argc, char * argv[]) +{ + struct ipmilanHostInfo host; + int request = 2; + int rv; + + char user[] = "joe"; + char pass[] = "blow"; + char ip[] = "192.168.1.7"; + + host.hostname = NULL; + host.portnumber = 999; + host.authtype = IPMI_AUTHTYPE_NONE; + host.privilege = IPMI_PRIVILEGE_ADMIN; + + host.ipaddr = ip; + memcpy(host.username, user, sizeof(user)); + memcpy(host.password, pass, sizeof(pass)); + /* + memset(host.username, 0, sizeof(host.username)); + memset(host.password, 0, sizeof(host.password)); + */ + + rv = do_ipmi_cmd(&host, request); + if (rv) + printf("rv = %d, operation failed. \n", rv); + else + printf("operation succeeded. \n"); + return rv; +} diff --git a/lib/plugins/stonith/meatware.c b/lib/plugins/stonith/meatware.c new file mode 100644 index 0000000..029ba35 --- /dev/null +++ b/lib/plugins/stonith/meatware.c @@ -0,0 +1,351 @@ +/* + * Stonith module for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder <gbinder@sysfive.com> + * + * This module is largely based on the "NULL Stonith device", written + * by Alan Robertson <alanr@unix.sh>, using code by David C. Teigland + * <teigland@sistina.com> originally appeared in the GFS stomith + * meatware agent. + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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 "Meatware STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN meatware +#define PIL_PLUGIN_S "meatware" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * meatware_new(const char *); +static void meatware_destroy(StonithPlugin *); +static int meatware_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * meatware_get_confignames(StonithPlugin *); +static const char * meatware_getinfo(StonithPlugin * s, int InfoType); +static int meatware_status(StonithPlugin * ); +static int meatware_reset_req(StonithPlugin * s, int request, const char * host); +static char ** meatware_hostlist(StonithPlugin *); + +static struct stonith_ops meatwareOps ={ + meatware_new, /* Create new STONITH object */ + meatware_destroy, /* Destroy STONITH object */ + meatware_getinfo, /* Return STONITH info string */ + meatware_get_confignames,/* Return STONITH info string */ + meatware_set_config, /* Get configuration from NVpairs */ + meatware_status, /* Return STONITH device status */ + meatware_reset_req, /* Request a reset */ + meatware_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; + +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 + , &meatwareOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Meatware STONITH device. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "MeatwareDevice-Stonith"; +static const char * NOTpluginID = "Meatware device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *meatwareXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +meatware_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this Meat device + */ + +static char ** +meatware_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in Meatware_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +Meat_parse_config_info(struct pluginDevice* nd, const char * info) +{ + LOG(PIL_INFO , "parse config info info=%s",info); + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Indicate that host must be power cycled manually. + */ +static int +meatware_reset_req(StonithPlugin * s, int request, const char * host) +{ + int fd, rc; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatclient.c as well */ + + char line[256], meatpipe[256]; + char resp_addr[50], resp_mw[50], resp_result[50]; + + + ERRIFWRONGDEV(s,S_OOPS); + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, host); + umask(0); + unlink(meatpipe); + + rc = mkfifo(meatpipe, (S_IRUSR | S_IWUSR)); + + if (rc < 0) { + LOG(PIL_CRIT, "cannot create FIFO for Meatware_reset_host"); + return S_OOPS; + } + + LOG(PIL_CRIT, "OPERATOR INTERVENTION REQUIRED to reset %s.", host); + LOG(PIL_CRIT, "Run \"meatclient -c %s\" AFTER power-cycling the " + "machine.", host); + + fd = open(meatpipe, O_RDONLY); + + if (fd < 0) { + LOG(PIL_CRIT, "cannot open FIFO for Meatware_reset_host"); + return S_OOPS; + } + + alarm(600); + memset(line, 0, 256); + rc = read(fd, line, 256); + alarm(0); + + if (rc < 0) { + LOG(PIL_CRIT, "read error on FIFO for Meatware_reset_host"); + return S_OOPS; + } + + memset(resp_mw, 0, 50); + memset(resp_result, 0, 50); + memset(resp_addr, 0, 50); + + if (sscanf(line, "%s %s %s", resp_mw, resp_result, resp_addr) < 3) { + LOG(PIL_CRIT, "Format error - failed to Meatware-reset node %s", + host); + return S_RESETFAIL; + } + + strdown(resp_addr); + + if (strncmp(resp_mw, "meatware", 8) || + strncmp(resp_result, "reply", 5) || + strncasecmp(resp_addr, host, strlen(resp_addr))) { + LOG(PIL_CRIT, "failed to Meatware-reset node %s", host); + return S_RESETFAIL; + }else{ + LOG(PIL_INFO, "node Meatware-reset: %s", host); + unlink(meatpipe); + return S_OK; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +meatware_set_config(StonithPlugin* s, StonithNVpair *list) +{ + + struct pluginDevice* nd; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + rc = Meat_parse_config_info(nd, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +meatware_get_confignames(StonithPlugin* p) +{ + static const char * MeatwareParams[] = {ST_HOSTLIST, NULL }; + return MeatwareParams; +} + +/* + * Return STONITH info string + */ +static const char * +meatware_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICENAME: + ret = "Your Name Here"; + break; + case ST_DEVICEDESCR: + ret = "Human (meatware) intervention STONITH device.\n" + "This STONITH agent prompts a human to reset a machine.\n" + "The human tells it when the reset was completed."; + break; + case ST_CONF_XML: /* XML metadata */ + ret = meatwareXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Meat Stonith destructor... + */ +static void +meatware_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Meatware Stonith device. */ + +static StonithPlugin * +meatware_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &meatwareOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/null.c b/lib/plugins/stonith/null.c new file mode 100644 index 0000000..0d0cf04 --- /dev/null +++ b/lib/plugins/stonith/null.c @@ -0,0 +1,260 @@ +/* + * Stonith module for NULL Stonith device + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * 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 "NULL STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN null +#define PIL_PLUGIN_S "null" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static StonithPlugin* null_new(const char *); +static void null_destroy(StonithPlugin *); +static int null_set_config(StonithPlugin* +, StonithNVpair*); +static const char * const * null_get_confignames(StonithPlugin*); +static const char * null_getinfo(StonithPlugin * s, int InfoType); +static int null_status(StonithPlugin * ); +static int null_reset_req(StonithPlugin * s +, int request, const char * host); +static char ** null_hostlist(StonithPlugin *); + +static struct stonith_ops nullOps ={ + null_new, /* Create new STONITH object */ + null_destroy, /* Destroy STONITH object */ + null_getinfo, /* Return STONITH info string */ + null_get_confignames, /* Return list of config params */ + null_set_config, /* configure fron NV pairs */ + null_status, /* Return STONITH device status */ + null_reset_req, /* Request a reset */ + null_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; + +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 + , &nullOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Null STONITH device. We are very agreeable, but don't do much :-) + */ + + +static const char * pluginid = "nullDevice-Stonith"; +static const char * NOTpluginID = "Null device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nullXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +null_status(StonithPlugin *s) +{ + + ERRIFWRONGDEV(s, S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this NULL device + */ + +static char ** +null_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +null_reset_req(StonithPlugin * s, int request, const char * host) +{ + + ERRIFWRONGDEV(s,S_OOPS); + + /* Real devices need to pay attention to the "request" */ + /* (but we don't care ;-)) */ + + LOG(PIL_INFO, "Host null-reset: %s", host); + return S_OK; +} + + +static const char * const * +null_get_confignames(StonithPlugin* p) +{ + static const char * NullParams[] = {ST_HOSTLIST, NULL }; + return NullParams; +} + +/* + * Parse the config information in the given string, + * and stash it away... + */ +static int +null_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nd->hostlist = OurImports->StringToHostList(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount] + ; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return nd->hostcount ? S_OK : S_BADCONFIG; +} + +static const char * +null_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = "(nil)"; + break; + + case ST_DEVICEDESCR: + ret = "Dummy (do-nothing) STONITH device\n" + "FOR TESTING ONLY!"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = nullXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * NULL Stonith destructor... + */ +static void +null_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(s); +} + +/* Create a new Null Stonith device. + * Too bad this function can't be static + */ +static StonithPlugin * +null_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->idinfo = DEVICE; + nd->sp.s_ops = &nullOps; + return (StonithPlugin *)nd; +} diff --git a/lib/plugins/stonith/nw_rpc100s.c b/lib/plugins/stonith/nw_rpc100s.c new file mode 100644 index 0000000..5ba0827 --- /dev/null +++ b/lib/plugins/stonith/nw_rpc100s.c @@ -0,0 +1,779 @@ +/* + * Stonith module for Night/Ware RPC100S + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Modifications for NW RPC100S + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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 "NW RPC100S Power Switch" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN nw_rpc100s +#define PIL_PLUGIN_S "nw_rpc100s" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_CFGLINE 256 +#include <pils/plugin.h> + +static StonithPlugin * nw_rpc100s_new(const char *); +static void nw_rpc100s_destroy(StonithPlugin *); +static int nw_rpc100s_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * nw_rpc100s_get_confignames(StonithPlugin *); +static const char * nw_rpc100s_getinfo(StonithPlugin * s, int InfoType); +static int nw_rpc100s_status(StonithPlugin * ); +static int nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host); +static char ** nw_rpc100s_hostlist(StonithPlugin *); + +static struct stonith_ops nw_rpc100sOps ={ + nw_rpc100s_new, /* Create new STONITH object */ + nw_rpc100s_destroy, /* Destroy STONITH object */ + nw_rpc100s_getinfo, /* Return STONITH info string */ + nw_rpc100s_get_confignames,/* Return STONITH info string */ + nw_rpc100s_set_config, /* Get configuration from NVpairs */ + nw_rpc100s_status, /* Return STONITH device status */ + nw_rpc100s_reset_req, /* Request a reset */ + nw_rpc100s_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_signal.h" + +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#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 + , &nw_rpc100sOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + The Nightware RPS-100S is manufactured by: + + Micro Energetics Corp + +1 703 250-3000 + http://www.nightware.com/ + + Thank you to David Hicks of Micro Energetics Corp. for providing + a demo unit to write this software. + + This switch has a very simple protocol, + You issue a command and it gives a response. + Sample commands are conveniently documented on a sticker on the + bottom of the device. + + The switch accepts a single command of the form + + //0,yyy,zzz[/m][/h]<CR> + + Where yyy is the wait time before activiting the relay. + zzz is the relay time. + + The default is that the relay is in a default state of ON, which + means that usually yyy is the number of seconds to wait + before shutting off the power and zzz is the number of seconds the + power remains off. There is a dip switch to change the default + state to 'OFF'. Don't set this switch. It will screw up this code. + + An asterisk can be used for zzz to specify an infinite switch time. + The /m /and /h command options will convert the specified wait and + switch times to either minutewes or hours. + + A response is either + <cr><lf>OK<cr><lf> + or + <cr><lf>Invalid Entry<cr><lf> + + + As far as THIS software is concerned, we have to implement 4 commands: + + status --> //0,0,BOGUS; # Not a real command, this is just a + # probe to see if switch is alive + open(on) --> //0,0,0; # turn power to default state (on) + close(off) --> //0,0,*; # leave power off indefinitely + reboot --> //0,0,10; # immediately turn power off for 10 seconds. + + and expect the response 'OK' to confirm that the unit is operational. +*/ + + + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + + char * node; /* Name of the node that this is controlling */ + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "NW_RPC100S"; +static const char * NOTrpcid = "NW RPC100S device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nw_rpc100sXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * Different expect strings that we get from the NW_RPC100S + * Remote Power Controllers... + */ + +static struct Etoken NWtokOK[] = { {"OK", 0, 0}, {NULL,0,0}}; +static struct Etoken NWtokInvalidEntry[] = { {"Invalid Entry", 0, 0}, {NULL,0,0}}; +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken NWtokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPCConnect(struct pluginDevice * ctx); +static int RPCDisconnect(struct pluginDevice * ctx); + +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +#if defined(ST_POWERON) +static int RPCOn(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPCOff(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +static int RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +/*static int RPC_parse_config_info(struct pluginDevice* ctx, const char * info);*/ + + +#define SENDCMD(cmd, timeout) { \ + int return_val = RPCSendCommand(ctx, cmd, timeout); \ + if (return_val != S_OK) { \ + return return_val; \ + } \ + } + +/* + * RPCSendCommand - send a command to the specified outlet + */ +static int +RPCSendCommand (struct pluginDevice *ctx, const char *command, int timeout) +{ + char writebuf[64]; /* All commands are short. + They should be WAY LESS + than 64 chars long! + */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + /* list of FDs for select() */ + struct timeval tv; /* */ + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s\r", command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s" + , pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPCSendCommand() */ + +/* + * RPCReset - Reset (power-cycle) the given outlet number + * + * This device can only control one power outlet - unitnum is ignored. + * + */ +static int +RPCReset(struct pluginDevice* ctx, int unitnum, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "Calling RPCReset (%s)", pluginid); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD("//0,0,10;\r\n", 12); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got OK"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); + +} /* end RPCReset() */ + + +#if defined(ST_POWERON) +/* + * RPCOn - Turn OFF the given outlet number + */ +static int +RPCOn(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD("//0,0,0;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPCOff - Turn Off the given outlet number + */ +static int +RPCOff(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD("//0,0,*;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOff() */ +#endif + + +/* + * nw_rpc100s_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +nw_rpc100s_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_status (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPCConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPCDisconnect(ctx)); +} + +/* + * nw_rpc100s_hostlist - API entry point to return the list of hosts + * for the devices on this NW_RPC100S unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +nw_rpc100s_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_hostlist (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + ret = OurImports->StringToHostList(ctx->node); + if (ret == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(ret[0]); + } + + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash it away... + * + * <info> contains the parameters specific to this type of object + * + * The format of <parameters> for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * stonith nodea NW_RPC100S /dev/ttyS0 nodeb 0 + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices accessible + * through 2 different serial ports on nodeb: + * + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 + * stonith nodeb NW_RPC100S /dev/ttyS1 nodec 0 + */ + +/*static int +RPC_parse_config_info(struct pluginDevice* ctx, const char * info) +{ +}*/ + + +/* + * RPCConnect - + * + * Connect to the given NW_RPC100S device. + * Side Effects + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPCConnect(struct pluginDevice * ctx) +{ + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + + /* Send a BOGUS string */ + SENDCMD("//0,0,BOGUS;\r\n", 10); + + /* Should reply with "Invalid Command" */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for \"Invalid Entry\""); + } + EXPECT(ctx->fd, NWtokInvalidEntry, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got Invalid Entry"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); +} + +static int +RPCDisconnect(struct pluginDevice * ctx) +{ + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the characters + and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPCNametoOutlet - Map a hostname to an outlet number on this stonith device. + * + * Returns: + * 0 on success ( the outlet number on the RPS10 - there is only one ) + * -1 on failure (host not found in the config file) + * + */ +static int +RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int rc = -1; + + if (!strcasecmp(ctx->node, host)) { + rc = 0; + } + + return rc; +} + + +/* + * nw_rpc100s_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + int outletnum = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_reset (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPCConnect(ctx)) != S_OK) { + return(rc); + } + + outletnum = RPCNametoOutlet(ctx, host); + LOG(PIL_DEBUG, "zk:outletname=%d", outletnum); + + if (outletnum < 0) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ctx->device, host); + RPCDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPCOn(ctx, outletnum, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPCOff(ctx, outletnum, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPCReset(ctx, outletnum, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPCDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +nw_rpc100s_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + ctx = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ctx->device = namestocopy[0].s_value; + ctx->node = namestocopy[1].s_value; + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +nw_rpc100s_get_confignames(StonithPlugin* p) +{ + static const char * RpcParams[] = {ST_TTYDEV , ST_HOSTLIST, NULL }; + return RpcParams; +} + + + +/* + * nw_rpc100s_getinfo - API entry point to retrieve something from the handle + */ +static const char * +nw_rpc100s_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Micro Energetics Night/Ware RPC100S"; + break; + case ST_DEVICEURL: + ret = "http://www.microenergeticscorp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = nw_rpc100sXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * nw_rpc100s_destroy - API entry point to destroy a NW_RPC100S Stonith object. + */ +static void +nw_rpc100s_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTrpcid; + + /* close the fd if open and set ctx->fd to invalid */ + RPCDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->node != NULL) { + FREE(ctx->node); + ctx->node = NULL; + } + FREE(ctx); +} + +/* + * nw_rpc100s_new - API entry point called to create a new NW_RPC100S Stonith + * device object. + */ +static StonithPlugin * +nw_rpc100s_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->device = NULL; + ctx->node = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &nw_rpc100sOps; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/rcd_serial.c b/lib/plugins/stonith/rcd_serial.c new file mode 100644 index 0000000..f1396a7 --- /dev/null +++ b/lib/plugins/stonith/rcd_serial.c @@ -0,0 +1,602 @@ +/* + * Stonith module for RCD_SERIAL Stonith device + * + * Original code from null.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Copious borrowings from nw_rpc100s.c by + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * and from apcsmart.c by + * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.net> + * + * Modifications for RC Delayed Serial Ciruit by + * Copyright (c) 2002 John Sutton <john@scl.co.uk> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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 "RC Delayed Serial" +#include "stonith_plugin_common.h" +#include "stonith_signal.h" + +#define PIL_PLUGIN rcd_serial +#define PIL_PLUGIN_S "rcd_serial" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#define ST_DTRRTS "dtr_rts" +#define ST_MSDURATION "msduration" +#define MAX_RCD_SERIALLINE 512 + +#include <pils/plugin.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +static StonithPlugin* rcd_serial_new(const char *); +static void rcd_serial_destroy(StonithPlugin *); +static int rcd_serial_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rcd_serial_get_confignames(StonithPlugin *); +static const char * rcd_serial_getinfo(StonithPlugin * s, int InfoType); +static int rcd_serial_status(StonithPlugin * ); +static int rcd_serial_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rcd_serial_hostlist(StonithPlugin *); + +static struct stonith_ops rcd_serialOps ={ + rcd_serial_new, /* Create new STONITH object */ + rcd_serial_destroy, /* Destroy STONITH object */ + rcd_serial_getinfo, /* Return STONITH info string */ + rcd_serial_get_confignames,/* Return STONITH info string */ + rcd_serial_set_config, /* Get configuration from NVpairs */ + rcd_serial_status, /* Return STONITH device status */ + rcd_serial_reset_req, /* Request a reset */ + rcd_serial_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; + +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 + , &rcd_serialOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* ------------------- RCD specific stuff -------------- */ + +/* + A diagram of a circuit suitable for use with this plugin is in + README.rcd_serial which should be somewhere in the distribution (if Alan + includes it ;-) and/or at http://www.scl.co.uk/rcd_serial/ (if I remember + to put it there ;-). + + Once you've got this built, you can test things using the stonith command + as follows: + + stonith -L + will show a list of plugin types, including rcd_serial + + stonith -t rcd_serial testhost + will show required parameters + + In these 3 you can either pass the params after the -p option or you can + put them in a config file and use -F configname instead of -p "param ...". + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -S + will show the status of the device + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -l + will list the single host testhost + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" testhost + will reset testhost (provided testhost has its reset pins + suitably wired to the RTS signal coming out of port /dev/ttyS0 + and that 1.5s is enough time to cause a reset ;-) +*/ + +/* + Define RCD_NOPAUSE if you are using the serial port for some purpose + _in_addition_ to using it as a stonith device. For example, I use one + of the input pins on the same serial port for monitoring the state of a + power supply. Periodically, a cron job has to open the port to read the + state of this input and thus has to clear down the output pins DTR and RTS + in order to avoid causing a spurious stonith reset. Now, if it should + happen that just at the same time as we are _really_ trying to do a stonith + reset, this cron job starts up, then the stonith reset won't occur ;-(. + To avoid this (albeit unlikely) outcome, you should #define RCD_NOPAUSE. + The effect of this is that instead of setting the line high just once and + then falling into a pause until an alarm goes off, rather, the program falls + into a loop which is continuously setting the line high. That costs us a bit + of CPU as compared with sitting in a pause, but hey, how often is this code + going to get exercised! Never, we hope... +*/ +#undef RCD_NOPAUSE + +#ifdef RCD_NOPAUSE +static int RCD_alarmcaught; +#endif + +/* + * own prototypes + */ + +static void RCD_alarm_handler(int sig); +static int RCD_open_serial_port(char *device); +static int RCD_close_serial_port(char *device, int fd); + +static void +RCD_alarm_handler(int sig) { +#if !defined(HAVE_POSIX_SIGNALS) + if (sig) { + signal(sig, SIG_DFL); + }else{ + signal(sig, RCD_alarm_handler); + } +#else + struct sigaction sa; + sigset_t sigmask; + + /* Maybe a bit naughty but it works and it saves duplicating all */ + /* this setup code - if handler called with 0 for sig, we install */ + /* ourself as handler. */ + if (sig) { + sa.sa_handler = (void (*)(int))SIG_DFL; + }else{ + sa.sa_handler = RCD_alarm_handler; + } + + sigemptyset(&sigmask); + sa.sa_mask = sigmask; + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); +#endif + +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 1; +#endif + return; +} + +static int +RCD_open_serial_port(char *device) { + int fd; + int status; + int bothbits; + + if (OurImports->TtyLock(device) < 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: ttylock failed.", __FUNCTION__); + } + return -1; + } + + bothbits = TIOCM_RTS | TIOCM_DTR; + + if ((fd = open(device, O_RDONLY | O_NDELAY)) != -1) { + /* + Opening the device always sets DTR & CTS high. + Clear them down immediately. + */ + status = ioctl(fd, TIOCMBIC, &bothbits); + /* If there was an error clearing bits, set the fd to -1 + * ( indicates error ) */ + if (status != 0 ) { + fd = -1; + } + } + + return fd; +} + +static int +RCD_close_serial_port(char *device, int fd) { + int rc = close(fd); + if (device != NULL) { + OurImports->TtyUnlock(device); + } + return rc; +} + +/* + * RCD_Serial STONITH device. + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; /* name of single host we can reset */ + int hostcount; /* i.e. 1 after initialisation */ + char * device; /* serial device name */ + char * signal; /* either rts or dtr */ + long msduration; /* how long (ms) to assert the signal */ +}; + +static const char * pluginid = "RCD_SerialDevice-Stonith"; +static const char * NOTrcd_serialID = "RCD_Serial device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_DTRRTS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_DTRRTS \ + XML_PARM_SHORTDESC_END + +#define XML_DTRRTS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hardware handshaking technique to use with " ST_TTYDEV "(\"dtr\" or \"rts\")" \ + XML_PARM_LONGDESC_END + +#define XML_DTRRTS_PARM \ + XML_PARAMETER_BEGIN(ST_DTRRTS, "string", "1", "0") \ + XML_DTRRTS_SHORTDESC \ + XML_DTRRTS_LONGDESC \ + XML_PARAMETER_END + +#define XML_MSDURATION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MSDURATION \ + XML_PARM_SHORTDESC_END + +#define XML_MSDURATION_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The delay duration (in milliseconds) between the assertion of the control signal on " ST_TTYDEV " and the closing of the reset switch" \ + XML_PARM_LONGDESC_END + +#define XML_MSDURATION_PARM \ + XML_PARAMETER_BEGIN(ST_MSDURATION, "string", "1", "0") \ + XML_MSDURATION_SHORTDESC \ + XML_MSDURATION_LONGDESC \ + XML_PARAMETER_END + +static const char *rcd_serialXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_TTYDEV_PARM + XML_DTRRTS_PARM + XML_MSDURATION_PARM + XML_PARAMETERS_END; + +static int +rcd_serial_status(StonithPlugin *s) +{ + struct pluginDevice* rcd; + int fd; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice*) s; + + /* + All we can do is make sure the serial device exists and + can be opened and closed without error. + */ + + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + return S_OK; +} + + +/* + * Return the list of hosts configured for this RCD_SERIAL device + */ +static char ** +rcd_serial_hostlist(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + ERRIFWRONGDEV(s,NULL); + rcd = (struct pluginDevice*) s; + if (rcd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in RCD_SERIAL_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)rcd->hostlist); +} + +/* + * At last, we really do it! I don't know what the request argument + * is so am just ignoring it... + */ +static int +rcd_serial_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* rcd; + int fd; + int sigbit; + struct itimerval timer; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice *) s; + + /* check that host matches */ + if (strcasecmp(host, rcd->hostlist[0])) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist.", + __FUNCTION__, host); + return(S_BADHOST); + } + + /* Set the appropriate bit for the signal */ + sigbit = *(rcd->signal)=='r' ? TIOCM_RTS : TIOCM_DTR; + + /* Set up the timer */ + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = 0; + timer.it_value.tv_sec = rcd->msduration / 1000; + timer.it_value.tv_usec = (rcd->msduration % 1000) * 1000; + + /* Open the device */ + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { +#ifdef HAVE_STRERROR + err = strerror(errno); +#else + err = sys_errlist[errno]; +#endif + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + /* Start the timer */ + RCD_alarm_handler(0); +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 0; +#endif + setitimer(ITIMER_REAL, &timer, 0); + + /* Set the line high */ + ioctl(fd, TIOCMBIS, &sigbit); + + /* Wait for the alarm signal */ +#ifdef RCD_NOPAUSE + while(!RCD_alarmcaught) ioctl(fd, TIOCMBIS, &sigbit); +#else + pause(); +#endif + + /* Clear the line low */ + ioctl(fd, TIOCMBIC, &sigbit); + + /* Close the port */ + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + LOG(PIL_INFO,"Host rcd_serial-reset: %s", host); + return S_OK; +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rcd_serial_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* rcd; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {ST_TTYDEV, NULL} + , {ST_DTRRTS, NULL} + , {ST_MSDURATION, NULL} + , {NULL, NULL} + }; + char *endptr; + int rc = 0; + + LOG(PIL_DEBUG, "%s:called", __FUNCTION__); + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + rcd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if ((rcd->hostlist = (char **)MALLOC(2*sizeof(char*))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + FREE(namestocopy[0].s_value); + FREE(namestocopy[1].s_value); + FREE(namestocopy[2].s_value); + FREE(namestocopy[3].s_value); + return S_OOPS; + } + rcd->hostlist[0] = namestocopy[0].s_value; + strdown(rcd->hostlist[0]); + rcd->hostlist[1] = NULL; + rcd->hostcount = 1; + rcd->device = namestocopy[1].s_value; + rcd->signal = namestocopy[2].s_value; + if (strcmp(rcd->signal, "rts") && strcmp(rcd->signal, "dtr")) { + LOG(PIL_CRIT, "%s: Invalid signal name '%s'", + pluginid, rcd->signal); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + + errno = 0; + rcd->msduration = strtol(namestocopy[3].s_value, &endptr, 0); + if (((errno == ERANGE) + && (rcd->msduration == LONG_MIN || rcd->msduration == LONG_MAX)) + || *endptr != 0 || rcd->msduration < 1) { + LOG(PIL_CRIT, "%s: Invalid msduration '%s'", + pluginid, namestocopy[3].s_value); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + FREE(namestocopy[3].s_value); + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rcd_serial_get_confignames(StonithPlugin* p) +{ + static const char * RcdParams[] = {ST_HOSTLIST, ST_TTYDEV + , ST_DTRRTS, ST_MSDURATION, NULL }; + return RcdParams; +} + +/* + * Return STONITH info string + */ +static const char * +rcd_serial_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* rcd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + rcd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = rcd->idinfo; + break; + case ST_DEVICENAME: + ret = rcd->device; + break; + case ST_DEVICEDESCR: + ret = "RC Delayed Serial STONITH Device\n" + "This device can be constructed cheaply from" + " readily available components,\n" + "with sufficient expertise and testing.\n" + "See README.rcd_serial for circuit diagram.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.scl.co.uk/rcd_serial/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rcd_serialXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RCD_SERIAL Stonith destructor... + */ +static void +rcd_serial_destroy(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + VOIDERRIFWRONGDEV(s); + + rcd = (struct pluginDevice *)s; + + rcd->pluginid = NOTrcd_serialID; + if (rcd->hostlist) { + stonith_free_hostlist(rcd->hostlist); + rcd->hostlist = NULL; + } + rcd->hostcount = -1; + if (rcd->device) { + FREE(rcd->device); + } + if (rcd->signal) { + FREE(rcd->signal); + } + FREE(rcd); +} + +/* + * Create a new RCD_Serial Stonith device. + * Too bad this function can't be static. (Hmm, weird, it _is_ static?) + */ +static StonithPlugin * +rcd_serial_new(const char *subplugin) +{ + struct pluginDevice* rcd = ST_MALLOCT(struct pluginDevice); + + if (rcd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(rcd, 0, sizeof(*rcd)); + + rcd->pluginid = pluginid; + rcd->hostlist = NULL; + rcd->hostcount = -1; + rcd->device = NULL; + rcd->signal = NULL; + rcd->msduration = 0; + rcd->idinfo = DEVICE; + rcd->sp.s_ops = &rcd_serialOps; + + return &(rcd->sp); +} diff --git a/lib/plugins/stonith/rhcs.c b/lib/plugins/stonith/rhcs.c new file mode 100644 index 0000000..293a081 --- /dev/null +++ b/lib/plugins/stonith/rhcs.c @@ -0,0 +1,1035 @@ +/* + * Stonith module for RedHat Cluster Suite fencing plugins + * + * Copyright (c) 2001 SuSE Linux AG + * Portions Copyright (c) 2004, tummy.com, ltd. + * + * Based on ssh.c, Authors: Joachim Gleissner <jg@suse.de>, + * Lars Marowsky-Bree <lmb@suse.de> + * Modified for external.c: Scott Kleihege <scott@tummy.com> + * Reviewed, tested, and config parsing: Sean Reifschneider <jafo@tummy.com> + * And overhauled by Lars Marowsky-Bree <lmb@suse.de>, so the circle + * closes... + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Changed to allow full-featured external plugins by Dave Blaschke + * <debltc@us.ibm.com> + * Modified for rhcs.c: Dejan Muhamedagic <dejan@suse.de> + * + * 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> + +#include <dirent.h> +#include <libxml/xmlmemory.h> +#include <libxml/xmlreader.h> +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN rhcs +#define PIL_PLUGIN_S "rhcs" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include <pils/plugin.h> + +static StonithPlugin * rhcs_new(const char *); +static void rhcs_destroy(StonithPlugin *); +static int rhcs_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rhcs_get_confignames(StonithPlugin *); +static const char * rhcs_getinfo(StonithPlugin * s, int InfoType); +static int rhcs_status(StonithPlugin * ); +static int rhcs_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rhcs_hostlist(StonithPlugin *); + +static struct stonith_ops rhcsOps ={ + rhcs_new, /* Create new STONITH object */ + rhcs_destroy, /* Destroy STONITH object */ + rhcs_getinfo, /* Return STONITH info string */ + rhcs_get_confignames, /* Return STONITH info string */ + rhcs_set_config, /* Get configuration from NVpairs */ + rhcs_status, /* Return STONITH device status */ + rhcs_reset_req, /* Request a reset */ + rhcs_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; + +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 + , &rhcsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * RHCS STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * hostlist; + char * outputbuf; + xmlDoc * metadata; +}; + +static const char * pluginid = "RHCSDevice-Stonith"; +static const char * NOTpluginID = "RHCS device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int rhcs_run_cmd(struct pluginDevice *sd, const char *op, + const char *host, char **output); +/* Just free up the configuration and the memory, if any */ +static void rhcs_unconfig(struct pluginDevice *sd); + +static int +rhcs_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "monitor"; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + rc = rhcs_run_cmd(sd, op, NULL, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + if (output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + if (!str) + return namecount; + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +rhcs_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int i, namecount; + char ** ret; + char * tmp; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + namecount = get_num_tokens(sd->hostlist); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the sd->hostlist here */ + i = 0; + tmp = strtok(sd->hostlist, WHITESPACE); + while (tmp != NULL) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s host %s", + __FUNCTION__, sd->subplugin, tmp); + } + ret[i] = STRDUP(tmp); + if (!ret[i]) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + if (i == 0) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + stonith_free_hostlist(ret); + ret = NULL; + } + + return(ret); +} + +static int +rhcs_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host rhcs-reset initiating on %s", host); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + switch (request) { + case ST_GENERIC_RESET: + op = "reboot"; + break; + + case ST_POWEROFF: + op = "off"; + break; + + case ST_POWERON: + op = "on"; + break; + + default: + LOG(PIL_CRIT, "%s: Unknown stonith request %d", + __FUNCTION__, request); + return S_OOPS; + break; + } + + rc = rhcs_run_cmd(sd, op, host, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (output) { + LOG(PIL_INFO, "plugin output: %s", output); + FREE(output); + } + return S_OK; + } + +} + +static int +rhcs_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->hostlist = NULL; + sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal); + + /* TODO: Maybe treat "" as delimeters too so + * whitespace can be passed to the plugins... */ + for (nv = info; nv->s_name; nv++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + if (!strcmp(key,"hostlist")) { + sd->hostlist = value; + FREE(key); + } else { + g_hash_table_insert(sd->cmd_opts, key, value); + } + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + rhcs_unconfig(sd); + + return(S_OOPS); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + if (key) { + FREE(key); + } + if (value) { + FREE(value); + } + return TRUE; +} + +static void +rhcs_unconfig(struct pluginDevice *sd) { + if (sd->cmd_opts) { + g_hash_table_foreach_remove(sd->cmd_opts, + let_remove_eachitem, NULL); + g_hash_table_destroy(sd->cmd_opts); + sd->cmd_opts = NULL; + } + if (sd->hostlist) { + FREE(sd->hostlist); + sd->hostlist = NULL; + } + if (sd->metadata) { + xmlFreeDoc(sd->metadata); + xmlCleanupParser(); + sd->metadata = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rhcs_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + /* make sure that command has not already been set */ + if (s->isconfigured) { + return(S_OOPS); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + +#if 0 + /* the required parameters may be acquired from the metadata + * */ + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (rhcs_get_confignames(s) == NULL) { + return(S_OOPS); + } + + for (p = sd->confignames; *p; p++) { + if (OurImports->GetValue(list, *p) == NULL) { + LOG(PIL_INFO, "Cannot get parameter %s from " + "StonithNVpair", *p); + } + } + } +#endif + + return rhcs_parse_config_info(sd, list); +} + + +/* Only interested in regular files starting with fence_ that are also executable */ +static int +rhcs_exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_RHCS_PLUGINDIR, dire->d_name); + if (rc <= 0 || rc >= FILENAME_MAX) { + return 0; + } + + if ((stat(filename, &statf) == 0) && + (S_ISREG(statf.st_mode)) && + (statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) { + if (statf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_WARN, "Executable file %s ignored " + "(writable by group/others)", filename); + return 0; + }else{ + return 1; + } + } + + return 0; +} + +static xmlDoc * +load_metadata(struct pluginDevice * sd) +{ + xmlDoc *doc = NULL; + const char *op = "metadata"; + int rc; + char *ret = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + rc = rhcs_run_cmd(sd, op, NULL, &ret); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (ret) { + LOG(PIL_CRIT, "plugin output: %s", ret); + FREE(ret); + } + goto err; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + doc = xmlParseMemory(ret, strlen(ret)); + if (!doc) { + LOG(PIL_CRIT, "%s: could not parse metadata", + __FUNCTION__); + goto err; + } + sd->metadata = doc; + +err: + if (ret) { + FREE(ret); + } + return doc; +} + +static const char *skip_attrs[] = { + "action", "verbose", "debug", "version", "help", "separator", + NULL +}; +/* XML stuff */ +typedef int (*node_proc) + (xmlNodeSet *nodes, struct pluginDevice *sd); + +static int +proc_xpath(const char *xpathexp, struct pluginDevice *sd, node_proc fun) +{ + xmlXPathObject *xpathObj = NULL; + xmlXPathContext *xpathCtx = NULL; + int rc = 1; + + if (!sd->metadata && !load_metadata(sd)) { + LOG(PIL_INFO, "%s: no metadata", __FUNCTION__); + return 1; + } + + /* Create xpath evaluation context */ + xpathCtx = xmlXPathNewContext(sd->metadata); + if(xpathCtx == NULL) { + LOG(PIL_CRIT, "%s: unable to create new XPath context", __FUNCTION__); + return 1; + } + /* Evaluate xpath expression */ + xpathObj = xmlXPathEvalExpression((const xmlChar*)xpathexp, xpathCtx); + if(xpathObj == NULL) { + LOG(PIL_CRIT, "%s: unable to evaluate expression %s", + __FUNCTION__, xpathexp); + goto err; + } + + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + rc = fun(xpathObj->nodesetval, sd); +err: + if (xpathObj) + xmlXPathFreeObject(xpathObj); + if (xpathCtx) + xmlXPathFreeContext(xpathCtx); + return rc; +} + +static int +load_confignames(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *attr; + const char * const*skip; + xmlNode *cur; + int i, j, namecount; + + namecount = nodes->nodeNr; + if (!namecount) { + LOG(PIL_INFO, "%s: no configuration parameters", __FUNCTION__); + return 1; + } + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return 1; + } + + /* now copy over confignames */ + j = 0; + for (i = 0; i < nodes->nodeNr; i++) { + cur = nodes->nodeTab[i]; + attr = xmlGetProp(cur, (const xmlChar*)"name"); + for (skip = skip_attrs; *skip; skip++) { + if (!strcmp(*skip,(char *)attr)) + goto skip; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, (char *)attr); + } + sd->confignames[j++] = strdup((char *)attr); + xmlFree(attr); + skip: + continue; + } + sd->confignames[j] = NULL; + + return 0; +} + +static int +dump_content(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *content = NULL; + xmlNode *cur; + int rc = 1; + + if (!nodes || !nodes->nodeTab || !nodes->nodeTab[0]) { + LOG(PIL_WARN, "%s: %s no nodes", + __FUNCTION__, sd->subplugin); + return 1; + } + cur = nodes->nodeTab[0]; + content = xmlNodeGetContent(cur); + if (content && strlen((char *)content) > 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s found content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + sd->outputbuf = STRDUP((char *)content); + rc = !(*sd->outputbuf); + } else { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s no content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + rc = 1; + } + + if (content) + xmlFree(content); + return rc; +} + +static int +dump_params_xml(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + int len = 0; + xmlNode *cur; + xmlBuffer *xml_buffer = NULL; + int rc = 0; + + xml_buffer = xmlBufferCreate(); + if (!xml_buffer) { + LOG(PIL_CRIT, "%s: failed to create xml buffer", __FUNCTION__); + return 1; + } + cur = nodes->nodeTab[0]; + len = xmlNodeDump(xml_buffer, sd->metadata, cur, 0, TRUE); + if (len <= 0) { + LOG(PIL_CRIT, "%s: could not dump xml for %s", + __FUNCTION__, (char *)xmlGetProp(cur, (const xmlChar*)"name")); + rc = 1; + goto err; + } + sd->outputbuf = STRDUP((char *)xml_buffer->content); +err: + xmlBufferFree(xml_buffer); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rhcs_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + proc_xpath("/resource-agent/parameters/parameter", sd, load_confignames); + } else { + /* return list of subplugins in rhcs directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the rhcs plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_RHCS_PLUGINDIR, &files, + SCANSEL_CAST rhcs_exec_select, NULL); + if (dircount < 0) { + return NULL; + } + + sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *)); + if (!sd->confignames) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + + for (i = 0; i < dircount; i++) { + sd->confignames[i] = STRDUP(files[i]->d_name+strlen("fence_")); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +fake_op(struct pluginDevice * sd, const char *op) +{ + const char *pfx = "RHCS plugin "; + char *ret = NULL; + + LOG(PIL_INFO, "rhcs plugins don't really support %s", op); + ret = MALLOC(strlen(pfx) + strlen(op) + 1); + strcpy(ret, pfx); + strcat(ret, op); + sd->outputbuf = ret; + return(ret); +} + +static const char * +rhcs_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + const char * op; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + sd = (struct pluginDevice *)s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + return fake_op(sd, op); + break; + + case ST_DEVICENAME: + if (!proc_xpath("/resource-agent/shortdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devname"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEDESCR: + if (!proc_xpath("/resource-agent/longdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devdescr"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + return fake_op(sd, op); + break; + + case ST_CONF_XML: + if (!proc_xpath("/resource-agent/parameters", sd, dump_params_xml)) { + return sd->outputbuf; + } + break; + + default: + return NULL; + } + return NULL; +} + +/* + * RHCS Stonith destructor... + */ +static void +rhcs_destroy(StonithPlugin *s) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginID; + rhcs_unconfig(sd); + if (sd->confignames != NULL) { + for (p = sd->confignames; *p; p++) { + FREE(*p); + } + FREE(sd->confignames); + sd->confignames = NULL; + } + if (sd->subplugin != NULL) { + FREE(sd->subplugin); + sd->subplugin = NULL; + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + FREE(sd); +} + +/* Create a new rhcs Stonith device */ +static StonithPlugin * +rhcs_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + if (subplugin != NULL) { + sd->subplugin = STRDUP(subplugin); + if (sd->subplugin == NULL) { + FREE(sd); + return(NULL); + } + } + sd->sp.s_ops = &rhcsOps; + return &(sd->sp); +} + +#define MAXLINE 512 + +static void +printparam_to_fd(int fd, const char *key, const char *value) +{ + char arg[MAXLINE]; + int cnt; + + cnt = snprintf(arg, MAXLINE, "%s=%s\n", key, value); + if (cnt <= 0 || cnt >= MAXLINE) { + LOG(PIL_CRIT, "%s: param/value pair too large", __FUNCTION__); + return; + } + if (Debug) { + LOG(PIL_DEBUG, "set rhcs plugin param '%s=%s'", key, value); + } + if (write(fd, arg, cnt) < 0) { + LOG(PIL_CRIT, "%s: write: %m", __FUNCTION__); + } +} + +static void +rhcs_print_var(gpointer key, gpointer value, gpointer user_data) +{ + printparam_to_fd(GPOINTER_TO_UINT(user_data), (char *)key, (char *)value); +} + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ + +static int +rhcs_run_cmd(struct pluginDevice *sd, const char *op, const char *host, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int rc; + char * data = NULL; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + int pid, status; + int fd1[2]; /* our stdout/their stdin */ + int fd2[2]; /* our stdin/their stdout and stderr */ + + rc = snprintf(cmd, FILENAME_MAX, "%s/fence_%s", + STONITH_RHCS_PLUGINDIR, sd->subplugin); + if (rc <= 0 || rc >= FILENAME_MAX) { + LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__); + return -1; + } + + if (stat(cmd, &buf) != 0) { + LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s", + __FUNCTION__, cmd, strerror(errno)); + return -1; + } + + if (!S_ISREG(buf.st_mode) + || (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + LOG(PIL_CRIT, "%s: %s found NOT to be executable.", + __FUNCTION__, cmd); + return -1; + } + + if (buf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_CRIT, "%s: %s found to be writable by group/others, " + "NOT executing for security purposes.", + __FUNCTION__, cmd); + return -1; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + + if (pipe(fd1) || pipe(fd2)) + goto err; + + pid = fork(); + if (pid < 0) { + LOG(PIL_CRIT, "%s: fork: %m", __FUNCTION__); + goto err; + } + if (pid) { /* parent */ + close(fd1[0]); + close(fd2[1]); + + if (sd->cmd_opts) { + printparam_to_fd(fd1[1], "agent", sd->subplugin); + printparam_to_fd(fd1[1], "action", op); + if( host ) + printparam_to_fd(fd1[1], "nodename", host); + g_hash_table_foreach(sd->cmd_opts, rhcs_print_var, + GUINT_TO_POINTER(fd1[1])); + } + close(fd1[1]); /* we have nothing more to say */ + + fcntl(fd2[0], F_SETFL, fcntl(fd2[0], F_GETFL, 0) | O_NONBLOCK); + data = NULL; + slen=0; + data = MALLOC(1); + /* read stdout/stderr from the fence agent */ + do { + data[slen]=EOS; + read_len = read(fd2[0], buff, BUFF_LEN); + if (read_len > 0) { + data=REALLOC(data, slen+read_len+1); + if (data == NULL) { + goto err; + } + memcpy(data+slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + } else if (read_len < 0) { + if (errno == EAGAIN) + continue; + LOG(PIL_CRIT, "%s: read from pipe: %m", __FUNCTION__); + goto err; + } + } while (read_len); + + if (!data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + goto err; + } + close(fd2[0]); + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) { + LOG(PIL_CRIT, "%s: fence agent failed: %m", __FUNCTION__); + goto err; + } else { + rc = WEXITSTATUS(status); + if (rc) { + LOG(PIL_CRIT, "%s: fence agent exit code: %d", + __FUNCTION__, rc); + goto err; + } + } + } else { /* child */ + close(fd1[1]); + close(fd2[0]); + close(STDIN_FILENO); + if (dup(fd1[0]) < 0) + goto err; + close(fd1[0]); + close(STDOUT_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(STDERR_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(fd2[1]); + rc = sd->cmd_opts ? + execlp(cmd, cmd, NULL) : execlp(cmd, cmd, "-o", op, NULL); + if (rc < 0) { + LOG(PIL_CRIT, "%s: Calling '%s' failed: %m", + __FUNCTION__, cmd); + } + goto err; + } + + if (Debug && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + + if (output) { + *output = data; + } else { + FREE(data); + } + + return 0; + +err: + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + + return(-1); + +} diff --git a/lib/plugins/stonith/ribcl.py.in b/lib/plugins/stonith/ribcl.py.in new file mode 100644 index 0000000..14e070c --- /dev/null +++ b/lib/plugins/stonith/ribcl.py.in @@ -0,0 +1,101 @@ +#!@PYTHON@ + + +# +# 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 +# + +import sys +import socket +from httplib import * +from time import sleep + + +argv = sys.argv + + +try: + host = argv[1].split('.')[0]+'-rm' + cmd = argv[2] +except IndexError: + print "Not enough arguments" + sys.exit(1) + + +login = [ '<RIBCL VERSION="1.2">', + '<LOGIN USER_LOGIN="Administrator" PASSWORD="********">' ] + + +logout = [ '</LOGIN>', '</RIBCL>' ] + + +status = [ '<SERVER_INFO MODE="read">', '<GET_HOST_POWER_STATUS/>', + '</SERVER_INFO>' ] + + +reset = [ '<SERVER_INFO MODE="write">', '<RESET_SERVER/>', '</SERVER_INFO>' ] + + +off = [ '<SERVER_INFO MODE = "write">', '<SET_HOST_POWER HOST_POWER = "N"/>', + '</SERVER_INFO>' ] + + +on = [ '<SERVER_INFO MODE = "write">', '<SET_HOST_POWER HOST_POWER = "Y"/>', + '</SERVER_INFO>' ] + + +todo = { 'reset':reset, 'on':on, 'off':off, 'status':status } + + +acmds=[] +try: + if cmd == 'reset' and host.startswith('gfxcl'): + acmds.append(login + todo['off'] + logout) + acmds.append(login + todo['on'] + logout) + else: + acmds.append(login + todo[cmd] + logout) +except KeyError: + print "Invalid command: "+ cmd + sys.exit(1) + + +try: + for cmds in acmds: + + + c=HTTPSConnection(host) + c.send('<?xml version="1.0"?>\r\n') + c.sock.recv(1024) + + + for line in cmds: + c.send(line+'\r\n') + c.sock.recv(1024) + + + c.close() + sleep(1) + + +except socket.gaierror, msg: + print msg + sys.exit(1) +except socket.sslerror, msg: + print msg + sys.exit(1) +except socket.error, msg: + print msg + sys.exit(1) + diff --git a/lib/plugins/stonith/riloe.c b/lib/plugins/stonith/riloe.c new file mode 100644 index 0000000..a4a8312 --- /dev/null +++ b/lib/plugins/stonith/riloe.c @@ -0,0 +1,338 @@ +/* + * Stonith module for RILOE Stonith device + * + * Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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 + * + */ + +#define DEVICE "Compaq RILOE" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN riloe +#define PIL_PLUGIN_S "riloe" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * riloe_new(const char *); +static void riloe_destroy(StonithPlugin *); +static int riloe_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * riloe_get_confignames(StonithPlugin * ); +static const char * riloe_getinfo(StonithPlugin * s, int InfoType); +static int riloe_status(StonithPlugin * ); +static int riloe_reset_req(StonithPlugin * s, int request, const char * host); +static char ** riloe_hostlist(StonithPlugin *); + +static struct stonith_ops riloeOps ={ + riloe_new, /* Create new STONITH object */ + riloe_destroy, /* Destroy STONITH object */ + riloe_getinfo, /* Return STONITH info string */ + riloe_get_confignames, /* Return STONITH info string */ + riloe_set_config, /* Get configuration from NVpairs */ + riloe_status, /* Return STONITH device status */ + riloe_reset_req, /* Request a reset */ + riloe_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; + +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 + , &riloeOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define RILOE_COMMAND STONITH_MODULES "/ribcl.py" + +/* + * Riloe STONITH device. We are very agreeable, but don't do much :-) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "RiloeDevice-Stonith"; +static const char * NOTriloeID = "Riloe device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *riloeXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +riloe_status(StonithPlugin *s) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this RILOE device + */ + +static char ** +riloe_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const*)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +RILOE_parse_config_info(struct pluginDevice* nd, const char * info) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +riloe_reset_req(StonithPlugin * s, int request, const char * host) +{ + char cmd[4096]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + snprintf(cmd, sizeof(cmd), "%s %s reset", RILOE_COMMAND, host); + + if (Debug) { + LOG(PIL_DEBUG, "command %s will be executed", cmd); + } + + if (system(cmd) == 0) { + return S_OK; + } else { + LOG(PIL_CRIT, "command %s failed", cmd); + return(S_RESETFAIL); + } +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +riloe_set_config(StonithPlugin* s, StonithNVpair *list) +{ + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + struct pluginDevice* nd; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + rc = RILOE_parse_config_info(nd , namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + */ +static const char* const * +riloe_get_confignames(StonithPlugin* p) +{ + static const char * RiloeParams[] = {ST_HOSTLIST, NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return RiloeParams; +} + +/* + * Return STONITH info string + */ + +static const char * +riloe_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICEDESCR: + ret = "Compaq RILOE STONITH device\n" + "Very early version!"; + break; + case ST_DEVICEURL: + ret = "http://www.hp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = riloeXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RILOE Stonith destructor... + */ +static void +riloe_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTriloeID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Riloe Stonith device. Too bad this function can't be static */ +static StonithPlugin * +riloe_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &riloeOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/rps10.c b/lib/plugins/stonith/rps10.c new file mode 100644 index 0000000..08d9873 --- /dev/null +++ b/lib/plugins/stonith/rps10.c @@ -0,0 +1,1070 @@ +/* + * Stonith module for WTI Remote Power Controllers (RPS-10M device) + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Modifications for WTI RPS10 + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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 "WTI RPS10 Power Switch" +#include "stonith_plugin_common.h" + +#include <termios.h> +#define PIL_PLUGIN rps10 +#define PIL_PLUGIN_S "rps10" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define ST_RPS10 "serial_to_targets" +#define MAX_PRSID 256 +#include <pils/plugin.h> + +static StonithPlugin * rps10_new(const char *); +static void rps10_destroy(StonithPlugin *); +static int rps10_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rps10_get_confignames(StonithPlugin *); +static const char * rps10_getinfo(StonithPlugin * s, int InfoType); +static int rps10_status(StonithPlugin * ); +static int rps10_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rps10_hostlist(StonithPlugin *); + +static struct stonith_ops rps10Ops ={ + rps10_new, /* Create new STONITH object */ + rps10_destroy, /* Destroy STONITH object */ + rps10_getinfo, /* Return STONITH info string */ + rps10_get_confignames, /* Return STONITH info string */ + rps10_set_config, /* Get configuration from NVpairs */ + rps10_status, /* Return STONITH device status */ + rps10_reset_req, /* Request a reset */ + rps10_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_signal.h" +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#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 + , &rps10Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * This was written for a Western Telematic Inc. (WTI) + * Remote Power Switch - RPS-10M. + * + * It has a DB9 serial port, a Rotary Address Switch, + * and a pair of RJ-11 jacks for linking multiple switches + * together. The 'M' unit is a master unit which can control + * up to 9 additional slave units. (the master unit also has an + * A/C outlet, so you can control up to 10 devices) + * + * There are a set of dip switches. The default shipping configuration + * is with all dip switches down. I highly recommend that you flip + * switch #3 up, so that when the device is plugged in, the power + * to the unit comes on. + * + * The serial interface is fixed at 9600 BPS (well, you *CAN* + * select 2400 BPS with a dip switch, but why?) 8-N-1 + * + * The ASCII command string is: + * + * ^B^X^X^B^X^Xac^M + * + * ^B^X^X^B^X^X "fixed password" prefix (CTRL-B CTRL-X ... ) + * ^M the carriage return character + * + * a = 0-9 Indicates the address of the module to receive the command + * a = * Sends the command to all modules + * + * c = 0 Switch the AC outlet OFF + * Returns: + * Plug 0 Off + * Complete + * + * c = 1 Switch the AC outlet ON + * Returns: + * Plug 0 On + * Complete + * + * c = T Toggle AC OFF (delay) then back ON + * Returns: + * Plug 0 Off + * Plug 0 On + * Complete + * + * c = ? Read and display status of the selected module + * Returns: + * Plug 0 On # or Plug 0 Off + * Complete + * + * e.g. ^B^X^X^B^X^X0T^M toggles the power on plug 0 OFF and then ON + * + * 21 September 2000 + * Eric Z. Ayers + * Computer Generation, Inc. + */ + +struct cntrlr_str { + char outlet_id; /* value 0-9, '*' */ + char * node; /* name of the node attached to this outlet */ +}; + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + +#define WTI_NUM_CONTROLLERS 10 + struct cntrlr_str + controllers[WTI_NUM_CONTROLLERS]; + /* one master switch can address 10 controllers */ + + /* Number of actually configured units */ + int unit_count; + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "WTI_RPS10"; +static const char * NOTwtiid = "OBJECT DESTROYED: (WTI RPS-10)"; + +#include "stonith_config_xml.h" + +#define XML_RPS10_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Value in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_SHORTDESC_END + +#define XML_RPS10_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The RPS-10 STONITH device configuration information in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_LONGDESC_END + +#define XML_RPS10_PARM \ + XML_PARAMETER_BEGIN(ST_RPS10, "string", "1", "1") \ + XML_RPS10_SHORTDESC \ + XML_RPS10_LONGDESC \ + XML_PARAMETER_END + +static const char *rps10XML = + XML_PARAMETERS_BEGIN + XML_RPS10_PARM + XML_PARAMETERS_END; + +/* WTIpassword - The fixed string ^B^X^X^B^X^X */ +static const char WTIpassword[7] = {2,24,24,2,24,24,0}; + +/* + * Different expect strings that we get from the WTI_RPS10 + * Remote Power Controllers... + */ + +static struct Etoken WTItokReady[] = { {"RPS-10 Ready", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokComplete[] = { {"Complete", 0, 0} ,{NULL,0,0}}; +static struct Etoken WTItokPlug[] = { {"Plug", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokOutlet[] = { {"0", 0, 0}, + {"1", 0, 0}, + {"2", 0, 0}, + {"3", 0, 0}, + {"4", 0, 0}, + {"5", 0, 0}, + {"6", 0, 0}, + {"7", 0, 0}, + {"8", 0, 0}, + {"9", 0, 0}, + {NULL,0,0}}; + +static struct Etoken WTItokOff[] = { {"Off", 0, 0}, {NULL,0,0}}; + +/* + * Tokens currently not used because they don't show up on all RPS10 units: + * + */ +static struct Etoken WTItokOn[] = { {"On", 0, 0}, {NULL,0,0}}; + +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken WTItokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPSConnect(struct pluginDevice * ctx); +static int RPSDisconnect(struct pluginDevice * ctx); + +static int RPSReset(struct pluginDevice*, char unit_id, const char * rebootid); +#if defined(ST_POWERON) +static int RPSOn(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPSOff(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +static signed char RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +static int RPS_parse_config_info(struct pluginDevice* ctx, const char * info); + +#define SENDCMD(outlet, cmd, timeout) { \ + int ret_val = RPSSendCommand(ctx, outlet, cmd, timeout);\ + if (ret_val != S_OK) { \ + return ret_val; \ + } \ + } + +/* + * RPSSendCommand - send a command to the specified outlet + */ +static int +RPSSendCommand (struct pluginDevice *ctx, char outlet, char command, int timeout) +{ + char writebuf[10]; /* all commands are 9 chars long! */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + struct timeval tv; /* */ + + /* list of FDs for select() */ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s%c%c\r", + WTIpassword, outlet, command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s\n", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s", + pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPSSendCommand() */ + +/* + * RPSReset - Reset (power-cycle) the given outlet id + */ +static int +RPSReset(struct pluginDevice* ctx, char unit_id, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD(unit_id, 'T', 10); + + /* Expect "Plug 0 Off" */ + /* Note: If asked to control "*", the RPS10 will report all units it + * separately; however, we don't know how many, so we can only wait + * for the first unit to report something and then wait until the + * "Complete" */ + EXPECT(ctx->fd, WTItokPlug, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got Plug\n"); + } + EXPECT(ctx->fd, WTItokOutlet, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Outlet #\n"); + } + EXPECT(ctx->fd, WTItokOff, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Off\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 14); + if (Debug) { + LOG(PIL_DEBUG, "Got Complete\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); + +} /* end RPSReset() */ + + +#if defined(ST_POWERON) +/* + * RPSOn - Turn OFF the given outlet id + */ +static int +RPSOn(struct pluginDevice* ctx, char unit_id, const char * host) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD(unit_id, '1', 10); + + /* Expect "Plug 0 On" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOn, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPSOff - Turn Off the given outlet id + */ +static int +RPSOff(struct pluginDevice* ctx, char unit_id, const char * host) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD(unit_id, '0', 10); + + /* Expect "Plug 0 Off" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOff, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOff() */ +#endif + + +/* + * rps10_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +rps10_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPSConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPSDisconnect(ctx)); +} + +/* + * rps10_hostlist - API entry point to return the list of hosts + * for the devices on this WTI_RPS10 unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +rps10_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + int i; + int j; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + if (ctx->unit_count >= 1) { + ret = (char **)MALLOC((ctx->unit_count+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + ret[ctx->unit_count]=NULL; /* null terminate the array */ + for (i=0; i < ctx->unit_count; i++) { + ret[i] = STRDUP(ctx->controllers[i].node); + if (ret[i] == NULL) { + for(j=0; j<i; j++) { + FREE(ret[j]); + } + FREE(ret); ret = NULL; + break; + } + } /* end for each possible outlet */ + } /* end if any outlets are configured */ + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash + * it away... + * + * The format of <info> for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * <assuming this is the heartbeat configuration syntax:> + * + * stonith nodea rps10 /dev/ttyS0 nodeb 0 + * stonith nodeb rps10 /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices + * accessible through 2 different serial ports on nodeb: + * + * stonith nodeb rps10 /dev/ttyS0 nodea 0 + * stonith nodeb rps10 /dev/ttyS1 nodec 0 + */ + +/* + * OOPS! + * + * Most of the large block of comments above is incorrect as far as this + * module is concerned. It is somewhat applicable to the heartbeat code, + * but not to this Stonith module. + * + * The format of parameter string for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + */ + +static int +RPS_parse_config_info(struct pluginDevice* ctx, const char * info) +{ + char *copy; + char *token; + char *outlet, *node; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* strtok() is nice to use to parse a string with + (other than it isn't threadsafe), but it is destructive, so + we're going to alloc our own private little copy for the + duration of this function. + */ + + copy = STRDUP(info); + if (!copy) { + LOG(PIL_CRIT, "out of memory"); + return S_OOPS; + } + + /* Grab the serial device */ + token = strtok (copy, " \t"); + + if (!token) { + LOG(PIL_CRIT, "%s: Can't find serial device on config line '%s'", + pluginid, info); + goto token_error; + } + + ctx->device = STRDUP(token); + if (!ctx->device) { + LOG(PIL_CRIT, "out of memory"); + goto token_error; + } + + /* Loop through the rest of the command line which should consist of */ + /* <nodename> <outlet> pairs */ + while ((node = strtok (NULL, " \t")) + && (outlet = strtok (NULL, " \t\n"))) { + char outlet_id; + + /* validate the outlet token */ + if ((sscanf (outlet, "%c", &outlet_id) != 1) + || !( ((outlet_id >= '0') && (outlet_id <= '9')) + || (outlet_id == '*') || (outlet_id == 'A') ) + ) { + LOG(PIL_CRIT + , "%s: the outlet_id %s must be between" + " 0 and 9 or '*' / 'A'", + pluginid, outlet); + goto token_error; + } + + if (outlet_id == 'A') { + /* Remap 'A' to '*'; in some configurations, + * a '*' can't be configured because it breaks + * scripts -- lmb */ + outlet_id = '*'; + } + + if (ctx->unit_count >= WTI_NUM_CONTROLLERS) { + LOG(PIL_CRIT, + "%s: Tried to configure too many controllers", + pluginid); + goto token_error; + } + + ctx->controllers[ctx->unit_count].node = STRDUP(node); + strdown(ctx->controllers[ctx->unit_count].node); + ctx->controllers[ctx->unit_count].outlet_id = outlet_id; + ctx->unit_count++; + + } + + /* free our private copy of the string we've been destructively + * parsing with strtok() + */ + FREE(copy); + return ((ctx->unit_count > 0) ? S_OK : S_BADCONFIG); + +token_error: + FREE(copy); + if (ctx->device) { + FREE(ctx->device); + ctx->device = NULL; + } + return(S_BADCONFIG); +} + + +/* + * dtrtoggle - toggle DTR on the serial port + * + * snarfed from minicom, sysdep1.c, a well known POSIX trick. + * + */ +static void dtrtoggle(int fd) { + struct termios tty, old; + int sec = 2; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + tcgetattr(fd, &tty); + tcgetattr(fd, &old); + cfsetospeed(&tty, B0); + cfsetispeed(&tty, B0); + tcsetattr(fd, TCSANOW, &tty); + if (sec>0) { + sleep(sec); + tcsetattr(fd, TCSANOW, &old); + } + + if (Debug) { + LOG(PIL_DEBUG, "dtrtoggle Complete (%s)\n", pluginid); + } +} + +/* + * RPSConnect - + * + * Connect to the given WTI_RPS10 device. + * Side Effects + * DTR on the serial port is toggled + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPSConnect(struct pluginDevice * ctx) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + /* Toggle DTR - this 'resets' the controller serial port interface + In minicom, try CTRL-A H to hangup and you can see this behavior. + */ + dtrtoggle(ctx->fd); + + /* Wait for the switch to respond with "RPS-10 Ready". + Emperically, this usually takes 5-10 seconds... + ... If this fails, this may be a hint that you got + a broken serial cable, which doesn't connect hardware + flow control. + */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for READY\n"); + } + EXPECT(ctx->fd, WTItokReady, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got READY\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); +} + +static int +RPSDisconnect(struct pluginDevice * ctx) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the + * characters and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPSNametoOutlet - Map a hostname to an outlet on this stonith device. + * + * Returns: + * 0-9, * on success ( the outlet id on the RPS10 ) + * -1 on failure (host not found in the config file) + * + */ +static signed char +RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int i=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* scan the controllers[] array to see if this host is there */ + for (i=0;i<ctx->unit_count;i++) { + /* return the outlet id */ + if ( ctx->controllers[i].node + && !strcasecmp(host, ctx->controllers[i].node)) { + /* found it! */ + break; + } + } + + if (i == ctx->unit_count) { + return -1; + } else { + return ctx->controllers[i].outlet_id; + } +} + + +/* + * rps10_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +rps10_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + signed char outlet_id = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPSConnect(ctx)) != S_OK) { + return(rc); + } + + outlet_id = RPSNametoOutlet(ctx, host); + + if (outlet_id < 0) { + LOG(PIL_WARN, "%s: %s doesn't control host [%s]" + , pluginid, ctx->device, host ); + RPSDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPSOn(ctx, outlet_id, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPSOff(ctx, outlet_id, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPSReset(ctx, outlet_id, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPSDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +rps10_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_RPS10, NULL} + , {NULL, NULL} + }; + int rc=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (s->isconfigured) { + /* The module is already configured. */ + return(S_OOPS); + } + + ctx = (struct pluginDevice*) s; + + if((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK){ + LOG(PIL_DEBUG , "get all calues failed"); + return rc; + } + + rc = RPS_parse_config_info(ctx, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +rps10_get_confignames(StonithPlugin* p) +{ + static const char * Rps10Params[] = {ST_RPS10 ,NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return Rps10Params; +} + +/* + * rps10_getinfo - API entry point to retrieve something from the handle + */ +static const char * +rps10_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic Inc. (WTI) " + "Remote Power Switch - RPS-10M.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rps10XML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * rps10_destroy - API entry point to destroy a WTI_RPS10 Stonith object. + */ +static void +rps10_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTwtiid; + + /* close the fd if open and set ctx->fd to invalid */ + RPSDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->unit_count > 0) { + for (i = 0; i < ctx->unit_count; i++) { + if (ctx->controllers[i].node != NULL) { + FREE(ctx->controllers[i].node); + ctx->controllers[i].node = NULL; + } + } + } + FREE(ctx); +} + +/* + * rps10_new - API entry point called to create a new WTI_RPS10 Stonith device + * object. + */ +static StonithPlugin * +rps10_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->unit_count = 0; + ctx->device = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &rps10Ops; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/ssh.c b/lib/plugins/stonith/ssh.c new file mode 100644 index 0000000..e90c199 --- /dev/null +++ b/lib/plugins/stonith/ssh.c @@ -0,0 +1,351 @@ +/* + * Stonith module for SSH Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * + * Authors: Joachim Gleissner <jg@suse.de>, Lars Marowsky-Brée <lmb@suse.de> + * + * 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> + +#include <config.h> + +#define DEVICE "SSH STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ssh +#define PIL_PLUGIN_S "ssh" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * ssh_new(const char *); +static void ssh_destroy(StonithPlugin *); +static const char * const * ssh_get_confignames(StonithPlugin *); +static int ssh_set_config(StonithPlugin *, StonithNVpair*); +static const char * ssh_get_info(StonithPlugin * s, int InfoType); +static int ssh_status(StonithPlugin * ); +static int ssh_reset_req(StonithPlugin * s, int request +, const char * host); +static char ** ssh_hostlist(StonithPlugin *); + +static struct stonith_ops sshOps ={ + ssh_new, /* Create new STONITH object */ + ssh_destroy, /* Destroy STONITH object */ + ssh_get_info, /* Return STONITH info string */ + ssh_get_confignames, /* Return configuration parameters */ + ssh_set_config, /* set configuration */ + ssh_status, /* Return STONITH device status */ + ssh_reset_req, /* Request a reset */ + ssh_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; + +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 + , &sshOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* uncomment this if you have an ssh that can do what it claims +#define SSH_COMMAND "ssh -q -x -o PasswordAuthentication=no StrictHostKeyChecking=no" +*/ +/* use this if you have the (broken) OpenSSH 2.1.1 */ +/* sunjd@cn.ibm.com added the option -f to temporily work around the block issue + * in which the child process always stay in 'system' call. Please FIX this. + * Additonally, this issue seems related to both of 2.6 kernel and stonithd. + */ +#define SSH_COMMAND "ssh -q -x -n -l root" + +/* We need to do a real hard reboot without syncing anything to simulate a + * power cut. + * We have to do it in the background, otherwise this command will not + * return. + */ +#define REBOOT_COMMAND "nohup sh -c '(sleep 2; nohup " REBOOT " " REBOOT_OPTIONS ") </dev/null >/dev/null 2>&1' &" +#undef REBOOT_COMMAND +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" + +#define MAX_PING_ATTEMPTS 15 + +/* + * SSH STONITH device + * + * I used the null device as template, so I guess there is missing + * some functionality. + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "SSHDevice-Stonith"; +static const char * NOTpluginid = "SSH device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *sshXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +ssh_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return system(NULL) ? S_OK : S_OOPS; +} + + +/* + * Return the list of hosts configured for this SSH device + */ + +static char ** +ssh_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + + if (sd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)sd->hostlist); +} + + +/* + * Reset the given host on this Stonith device. + */ +static int +ssh_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + char cmd[4096]; + int i, status = -1; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + return S_INVAL; + } + + for (i = 0; i < sd->hostcount; i++) { + if (strcasecmp(host, sd->hostlist[i]) == 0) { + break; + } + } + + if (i >= sd->hostcount) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , sd->idinfo, host); + return(S_BADHOST); + } + + LOG(PIL_INFO, "Initiating ssh-%s on host: %s" + , request == ST_POWEROFF ? "poweroff" : "reset", host); + + snprintf(cmd, sizeof(cmd)-1, "%s \"%s\" \"%s\"", SSH_COMMAND + , host + , request == ST_POWEROFF ? POWEROFF_COMMAND : REBOOT_COMMAND); + + status = system(cmd); + if (WIFEXITED(status) && 0 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "checking whether %s stonith'd", host); + } + + snprintf(cmd, sizeof(cmd)-1 + , "ping -w1 -c1 %s >/dev/null 2>&1", host); + + for (i = 0; i < MAX_PING_ATTEMPTS; i++) { + status = system(cmd); + if (WIFEXITED(status) && 1 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "unable to ping %s" + " after %d tries, stonith did work" + , host, i); + } + return S_OK; + } + sleep(1); + } + + LOG(PIL_CRIT, "still able to ping %s after %d tries, stonith" + " did not work", host, MAX_PING_ATTEMPTS); + return S_RESETFAIL; + }else{ + LOG(PIL_CRIT, "command %s failed", cmd); + return S_RESETFAIL; + } +} + +static const char * const * +ssh_get_confignames(StonithPlugin* p) +{ + static const char * SshParams[] = {ST_HOSTLIST, NULL }; + return SshParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +ssh_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * hlist; + + ERRIFWRONGDEV(s,S_OOPS); + + if ((hlist = OurImports->GetValue(list, ST_HOSTLIST)) == NULL) { + return S_OOPS; + } + sd->hostlist = OurImports->StringToHostList(hlist); + if (sd->hostlist == NULL) { + LOG(PIL_CRIT, "out of memory"); + sd->hostcount = 0; + }else{ + for (sd->hostcount = 0; sd->hostlist[sd->hostcount] + ; sd->hostcount++) { + strdown(sd->hostlist[sd->hostcount]); + } + } + + return sd->hostcount ? S_OK : S_OOPS; +} + + +static const char * +ssh_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + + case ST_DEVICENAME: + ret = "ssh STONITH device"; + break; + + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "SSH-based host reset\n" + "Fine for testing, but not suitable for production!"; + break; + + + case ST_DEVICEURL: + ret = "http://openssh.org"; + break; + + + case ST_CONF_XML: /* XML metadata */ + ret = sshXML; + break; + + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * SSH Stonith destructor... + */ +static void +ssh_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + + VOIDERRIFWRONGDEV(s); + + sd->pluginid = NOTpluginid; + if (sd->hostlist) { + stonith_free_hostlist(sd->hostlist); + sd->hostlist = NULL; + } + sd->hostcount = -1; + FREE(sd); +} + +/* Create a new ssh Stonith device */ +static StonithPlugin* +ssh_new(const char *subplugin) +{ + 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->hostlist = NULL; + sd->hostcount = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &sshOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/stonith_config_xml.h b/lib/plugins/stonith/stonith_config_xml.h new file mode 100644 index 0000000..ff04ae9 --- /dev/null +++ b/lib/plugins/stonith/stonith_config_xml.h @@ -0,0 +1,157 @@ +/* + * stonith_config_xml.h: common macros easing the writing of config + * XML for STONITH plugins. Only a STONITH + * plugin should include this header! + * + * Copyright (C) International Business Machines Corp., 2005 + * Author: Dave Blaschke <debltc@us.ibm.com> + * Support: linux-ha@lists.linux-ha.org + * + * 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 + * + */ +#ifndef _STONITH_CONFIG_XML_H +#define _STONITH_CONFIG_XML_H + +/* + * The generic constants for XML + */ + +/* <parameters>?</parameters> */ +#define XML_PARAMETERS_BEGIN "<parameters>" +#define XML_PARAMETERS_END "</parameters>" + +/* <parameter name="ipaddr" unique="?">?<content type="string" /></parameter> */ +#define XML_PARAMETER_BEGIN(name,type,req,uniq) \ + "<parameter name=\"" name "\" unique=\"" uniq "\" required=\"" req "\">" \ + "<content type=\"" type "\" />\n" +#define XML_PARAMETER_END "</parameter>\n" + +/* <shortdesc lang="en">?</shortdesc> */ +#define XML_PARM_SHORTDESC_BEGIN(lang) \ + "<shortdesc lang=\"" lang "\">\n" +#define XML_PARM_SHORTDESC_END "</shortdesc>\n" + +/* <longdesc lang="en">?</longdesc> */ +#define XML_PARM_LONGDESC_BEGIN(lang) \ + "<longdesc lang=\"" lang "\">\n" +#define XML_PARM_LONGDESC_END "</longdesc>\n" + +/* + * The short and long descriptions for the few standardized parameter names; + * these can be translated by appending different languages to these constants + * (must include XML_PARM_****DESC_BEGIN(), the translated description, and + * XML_PARM_****DESC_END for each language) + */ +#define XML_HOSTLIST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Hostlist" \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTLIST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The list of hosts that the STONITH device controls" \ + XML_PARM_LONGDESC_END + +#define XML_IPADDR_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "IP Address" \ + XML_PARM_SHORTDESC_END + +#define XML_IPADDR_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The IP address of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_LOGIN_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Login" \ + XML_PARM_SHORTDESC_END + +#define XML_LOGIN_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The username used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PASSWD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Password" \ + XML_PARM_SHORTDESC_END + +#define XML_PASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The password used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_COMMUNITY_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "SNMP Community" \ + XML_PARM_SHORTDESC_END + +#define XML_COMMUNITY_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The SNMP community string associated with the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_TTYDEV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "TTY Device" \ + XML_PARM_SHORTDESC_END + +#define XML_TTYDEV_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The TTY device used for connecting to the STONITH device" \ + XML_PARM_LONGDESC_END + +/* + * Complete parameter descriptions for the few standardized parameter names + */ +#define XML_HOSTLIST_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTLIST, "string", "1", "0") \ + XML_HOSTLIST_SHORTDESC \ + XML_HOSTLIST_LONGDESC \ + XML_PARAMETER_END + +#define XML_IPADDR_PARM \ + XML_PARAMETER_BEGIN(ST_IPADDR, "string", "1", "0") \ + XML_IPADDR_SHORTDESC \ + XML_IPADDR_LONGDESC \ + XML_PARAMETER_END + +#define XML_LOGIN_PARM \ + XML_PARAMETER_BEGIN(ST_LOGIN, "string", "1", "0") \ + XML_LOGIN_SHORTDESC \ + XML_LOGIN_LONGDESC \ + XML_PARAMETER_END + +#define XML_PASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "1", "0") \ + XML_PASSWD_SHORTDESC \ + XML_PASSWD_LONGDESC \ + XML_PARAMETER_END + +#define XML_COMMUNITY_PARM \ + XML_PARAMETER_BEGIN(ST_COMMUNITY, "string", "1", "0") \ + XML_COMMUNITY_SHORTDESC \ + XML_COMMUNITY_LONGDESC \ + XML_PARAMETER_END + +#define XML_TTYDEV_PARM \ + XML_PARAMETER_BEGIN(ST_TTYDEV, "string", "1", "0") \ + XML_TTYDEV_SHORTDESC \ + XML_TTYDEV_LONGDESC \ + XML_PARAMETER_END + +#endif diff --git a/lib/plugins/stonith/stonith_expect_helpers.h b/lib/plugins/stonith/stonith_expect_helpers.h new file mode 100644 index 0000000..f9eaa19 --- /dev/null +++ b/lib/plugins/stonith/stonith_expect_helpers.h @@ -0,0 +1,120 @@ +/* + * stonith_expect_helpers.h: Some common expect defines. + * + * Copyright (C) 2004 Lars Marowsky-Bree <lmb@suse.de> + * + * 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 + * + */ + +/* This is still somewhat ugly. It needs to be included after the PILS + * definitions so that it can access them, but the code reduction seemed + * to justify this. Hopefully it can be made somewhat more elegant + * eventually. */ + +/* + * Many expect/telnet plugins use these defines and functions. + */ + +#define SEND(fd,s) { \ + size_t slen = strlen(s); \ + if (Debug) { \ + LOG(PIL_DEBUG \ + , "Sending [%s] (len %d)" \ + , (s) \ + , (int)slen); \ + } \ + if (write((fd), (s), slen) != slen) { \ + LOG(PIL_CRIT \ + , "%s: write failed" \ + , __FUNCTION__); \ + } \ + } + +#define EXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(errno == ETIMEDOUT \ + ? S_TIMEOUT : S_OOPS); \ + } + +#define NULLEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(NULL); \ + } + +#define SNARF(fd,s, to) { \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK){ \ + return(S_OOPS); \ + } \ + } + +#define NULLSNARF(fd,s, to){ \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK) { \ + return(NULL); \ + } \ + } + +/* Look for any of the given patterns. We don't care which */ +static int +StonithLookFor(int fd, struct Etoken * tlist, int timeout) +{ + int rc; + char savebuf[512]; + + if ((rc = EXPECT_TOK(fd, tlist, timeout, savebuf, sizeof(savebuf) + , Debug)) < 0) { + LOG(PIL_CRIT, "Did not find string %s from " DEVICE "." + , tlist[0].string); + LOG(PIL_CRIT, "Received [%s]", savebuf); + } + return(rc); +} + +#ifndef DOESNT_USE_STONITHSCANLINE +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int +StonithScanLine(int fd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(fd, CRNL, timeout, buf, max, Debug) < 0) { + LOG(PIL_CRIT, "Could not read line from" DEVICE "."); + return(S_OOPS); + } + return(S_OK); +} +#endif + +#ifndef DOESNT_USE_STONITHKILLCOMM +static void +Stonithkillcomm(int *rdfd, int *wrfd, int *pid) +{ + if ((rdfd != NULL) && (*rdfd >= 0)) { + close(*rdfd); + *rdfd = -1; + } + if ((wrfd != NULL) && (*wrfd >= 0)) { + close(*wrfd); + *wrfd = -1; + } + if ((pid != NULL) && (*pid > 0)) { + STONITH_KILL(*pid, SIGKILL); + (void)waitpid(*pid, NULL, 0); + *pid = -1; + } +} +#endif diff --git a/lib/plugins/stonith/stonith_plugin_common.h b/lib/plugins/stonith/stonith_plugin_common.h new file mode 100644 index 0000000..dcdd7c8 --- /dev/null +++ b/lib/plugins/stonith/stonith_plugin_common.h @@ -0,0 +1,127 @@ +/* + * stonith_plugin_common.h: common macros easing the writing of STONITH + * plugins. Only a STONITH plugin should + * include this header! + * + * Copyright (C) 2004 Lars Marowsky-Bree <lmb@suse.de> + * + * 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 + * + */ +#ifndef _STONITH_PLUGIN_COMMON_H +#define _STONITH_PLUGIN_COMMON_H + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <libintl.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <ctype.h> +#include <string.h> +#include <fcntl.h> +#include <netdb.h> +#ifdef HAVE_TERMIO_H +# include <termio.h> +#endif +#ifdef HAVE_SYS_TERMIOS_H +#include <sys/termios.h> +#else +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#endif +#include <glib.h> + + +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + +#define LOG(w...) PILCallLog(PluginImports->log, w) + +#define MALLOC PluginImports->alloc +#define REALLOC PluginImports->mrealloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree +#define EXPECT_TOK OurImports->ExpectToken +#define STARTPROC OurImports->StartProcess + +#ifdef MALLOCT +# undef MALLOCT +#endif +#define ST_MALLOCT(t) ((t *)(MALLOC(sizeof(t)))) + +#define N_(text) (text) +#define _(text) dgettext(ST_TEXTDOMAIN, text) + +#define WHITESPACE " \t\n\r\f" + +#ifndef MIN +/* some macros */ +# define MIN( i, j ) ( i > j ? j : i ) +#endif + +#define REPLSTR(s,v) { \ + if ((s) != NULL) { \ + FREE(s); \ + (s)=NULL; \ + } \ + (s) = STRDUP(v); \ + if ((s) == NULL) { \ + PILCallLog(PluginImports->log, \ + PIL_CRIT, "out of memory"); \ + } \ + } + +#ifndef DEVICE +#define DEVICE "Dummy" +#endif + +#define PIL_PLUGINTYPE STONITH_TYPE +#define PIL_PLUGINTYPE_S STONITH_TYPE_S + +#define ISCORRECTDEV(i) ((i)!= NULL \ + && ((struct pluginDevice *)(i))->pluginid == pluginid) + +#define ERRIFWRONGDEV(s, retval) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFWRONGDEV(s) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return; \ + } + +#define ISCONFIGED(i) (i->isconfigured) + +#define ERRIFNOTCONFIGED(s,retval) ERRIFWRONGDEV(s,retval); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFNOTCONFIGED(s) VOIDERRIFWRONGDEV(s); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return; \ + } + +#endif + diff --git a/lib/plugins/stonith/stonith_signal.h b/lib/plugins/stonith/stonith_signal.h new file mode 100644 index 0000000..99513f5 --- /dev/null +++ b/lib/plugins/stonith/stonith_signal.h @@ -0,0 +1,68 @@ +/* + * stonith_signal.h: signal handling routines to be used by stonith + * plugin libraries + * + * Copyright (C) 2002 Horms <horms@verge.net.au> + * + * 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 + * + */ +#ifndef _STONITH_SIGNAL_H +#define _STONITH_SIGNAL_H + +#include <signal.h> +#include <sys/signal.h> + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact); + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + (void)stonith_signal_set_simple_handler; + if(sigemptyset(&mask) < 0) { + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + return(-1); + } + + return(0); +} + +#define STONITH_SIGNAL(_sig, _handler) \ + stonith_signal_set_simple_handler((_sig), (_handler), NULL) +#ifdef HAVE_SIGIGNORE +#define STONITH_IGNORE_SIG(_sig) \ + sigignore((_sig)) +#else +#define STONITH_IGNORE_SIG(_sig) \ + STONITH_SIGNAL((_sig), SIG_IGN) +#endif +#define STONITH_DEFAULT_SIG(_sig) STONITH_SIGNAL((_sig), SIG_DFL) + +#define STONITH_KILL(_pid, _sig) kill((_pid), (_sig)) + +#endif /* _STONITH_SIGNAL_H */ diff --git a/lib/plugins/stonith/suicide.c b/lib/plugins/stonith/suicide.c new file mode 100644 index 0000000..b9d1db4 --- /dev/null +++ b/lib/plugins/stonith/suicide.c @@ -0,0 +1,274 @@ +/* File: suicide.c + * Description: Stonith module for suicide + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * 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> +#include <config.h> +#include <sys/utsname.h> + +#define DEVICE "Suicide STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN suicide +#define PIL_PLUGIN_S "suicide" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * suicide_new(const char *); +static void suicide_destroy(StonithPlugin *); +static const char * const * suicide_get_confignames(StonithPlugin *); +static int suicide_set_config(StonithPlugin *, StonithNVpair*); +static const char * suicide_get_info(StonithPlugin * s, int InfoType); +static int suicide_status(StonithPlugin * ); +static int suicide_reset_req(StonithPlugin * s, int request + , const char * host); +static char ** suicide_hostlist(StonithPlugin *); + +static struct stonith_ops suicideOps ={ + suicide_new, /* Create new STONITH object */ + suicide_destroy, /* Destroy STONITH object */ + suicide_get_info, /* Return STONITH info string */ + suicide_get_confignames, /* Return configuration parameters */ + suicide_set_config, /* Set configuration */ + suicide_status, /* Return STONITH device status */ + suicide_reset_req, /* Request a reset */ + suicide_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; + +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 + , &suicideOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define REBOOT_COMMAND "nohup sh -c 'sleep 2; " REBOOT " " REBOOT_OPTIONS " </dev/null >/dev/null 2>&1' &" +#define POWEROFF_COMMAND "nohup sh -c 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS " </dev/null >/dev/null 2>&1' &" +/* +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +*/ + +/* + * Suicide STONITH device + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; +}; + +static const char * pluginid = "SuicideDevice-Stonith"; +static const char * NOTpluginid = "Suicide device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *suicideXML = + XML_PARAMETERS_BEGIN + XML_PARAMETERS_END; + +static int +suicide_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return S_OK; +} + +/* + * Return the list of hosts configured for this Suicide device + */ +static char ** +suicide_hostlist(StonithPlugin *s) +{ + char** ret = NULL; + struct utsname name; + + ERRIFWRONGDEV(s, NULL); + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return ret; + } + + ret = OurImports->StringToHostList(name.nodename); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + strdown(ret[0]); + + return ret; +} + +/* + * Suicide - reset or poweroff itself. + */ +static int +suicide_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = -1; + struct utsname name; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + LOG(PIL_CRIT, "As for suicide virtual stonith device, " + "reset request=%d is not supported", request); + return S_INVAL; + } + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return S_RESETFAIL ; + } + + if (strcmp(name.nodename, host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , name.nodename, host); + return S_RESETFAIL ; + } + + LOG(PIL_INFO, "Initiating suicide on host %s", host); + + rc = system( + request == ST_GENERIC_RESET ? REBOOT_COMMAND : POWEROFF_COMMAND); + + if (rc == 0) { + LOG(PIL_INFO, "Suicide stonith succeeded."); + return S_OK; + } else { + LOG(PIL_CRIT, "Suicide stonith failed."); + return S_RESETFAIL ; + } +} + +static const char * const * +suicide_get_confignames(StonithPlugin* p) +{ + /* Donnot need to initialize from external. */ + static const char * SuicideParams[] = { NULL }; + return SuicideParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +suicide_set_config(StonithPlugin* s, StonithNVpair* list) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + +static const char * +suicide_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + case ST_DEVICENAME: + ret = "suicide STONITH device"; + break; + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "Virtual device to reboot/powerdown itself.\n"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = suicideXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Suicide Stonith destructor... + */ +static void +suicide_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginid; + FREE(sd); +} + +/* Create a new suicide Stonith device */ +static StonithPlugin* +suicide_new(const char * subplugin) +{ + 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->idinfo = DEVICE; + sd->sp.s_ops = &suicideOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/vacm.c b/lib/plugins/stonith/vacm.c new file mode 100644 index 0000000..ce6d041 --- /dev/null +++ b/lib/plugins/stonith/vacm.c @@ -0,0 +1,485 @@ + +/****************************************************************************** +* +* Copyright 2000 Sistina Software, Inc. +* Tiny bits Copyright 2000 Alan Robertson <alanr@unix.sh> +* Tiny bits Copyright 2000 Zac Sprackett, VA Linux Systems +* Tiny bits Copyright 2005 International Business Machines +* Significantly Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 +* +* This is free software released under the GNU General Public License. +* There is no warranty for this software. See the file COPYING for +* details. +* +* See the file CONTRIBUTORS for a list of contributors. +* +* This file is maintained by: +* Michael C Tilstra <conrad@sistina.com> +* +* Becasue I have no device to test, now I just make it pass the compiling +* with vacm-2.0.5a. Please review before using. +* Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 +* +* This module provides a driver for the VA Linux Cluster Manager. +* For more information on VACM, see http://vacm.sourceforge.net/ +* +* This module is rather poorly commented. But if you've read the +* VACM Manual, and looked at the code example they have, this +* should make pretty clean sense. (You obiviously should have +* looked at the other stonith source too) +* +*/ + +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define DEVICE "VA Linux Cluster Manager" + +#include "stonith_plugin_common.h" +#include "vacmclient_api.h" + +#define PIL_PLUGIN vacm +#define PIL_PLUGIN_S "vacm" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * vacm_new(const char *); +static void vacm_destroy(StonithPlugin *); +static const char * const * vacm_get_confignames(StonithPlugin *); +static int vacm_set_config(StonithPlugin *, StonithNVpair *); +static const char * vacm_getinfo(StonithPlugin * s, int InfoType); +static int vacm_status(StonithPlugin * ); +static int vacm_reset_req(StonithPlugin * s, int request, const char * host); +static char ** vacm_hostlist(StonithPlugin *); + +static struct stonith_ops vacmOps ={ + vacm_new, /* Create new STONITH object */ + vacm_destroy, /* Destroy STONITH object */ + vacm_getinfo, /* Return STONITH info string */ + vacm_get_confignames, /* Return configuration parameters */ + vacm_set_config, /* Set configuration */ + vacm_status, /* Return STONITH device status */ + vacm_reset_req, /* Request a reset */ + vacm_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; + +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 + , &vacmOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/*structs*/ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + void *h; /* a handle to the nexxus. */ + char * nexxus; + char * user; + char * passwd; +}; + +#define ST_NEXXUS "nexxus" + +static const char * pluginid = "VACMDevice-Stonith"; +static const char * NOTpluginid = "VACM device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_NEXXUS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_NEXXUS \ + XML_PARM_SHORTDESC_END + +#define XML_NEXXUS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The Nexxus component of the VA Cluster Manager" \ + XML_PARM_LONGDESC_END + +#define XML_NEXXUS_PARM \ + XML_PARAMETER_BEGIN(ST_NEXXUS, "string", "1", "1") \ + XML_NEXXUS_SHORTDESC \ + XML_NEXXUS_LONGDESC \ + XML_PARAMETER_END + +static const char *vacmXML = + XML_PARAMETERS_BEGIN + XML_NEXXUS_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/*funcs*/ +int +vacm_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:VERSION"; + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + /* If grabbing the nexxus version works, then the status must be ok. + * right? + */ + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + break; + } + if (!(tk = strtok(rcv,":"))) { /*NEXXUS*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of the below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; /* YEAH!! */ + }else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if(!strcmp(tk, "VERSION")) { + free(rcv); + continue; + } else { + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_OOPS; +} + +/* Better make sure the current group is correct. + * Can't think of a good way to do this. + */ +char ** +vacm_hostlist(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:NODE_LIST"; + char *rcv,*tk; + int rcvlen; + char ** hlst=NULL; + int hacnt=0, hrcnt=0; +#define MSTEP 20 + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice*)s; + + hlst = (char **)MALLOC(MSTEP * sizeof(char*)); + if (hlst == NULL) { + LOG(PIL_CRIT, "out of memory"); + return NULL; + } + hacnt=MSTEP; + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if(api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + goto HL_cleanup; + } + if(!(tk=strtok(rcv, ":"))) { /* NEXXUS */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* Job ID */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* JOB_* or NODELIST */ + goto HL_cleanup; + }else if( !strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return hlst; + }else if( !strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if( !strcmp(tk, "NODELIST")) { + if(!(tk = strtok(NULL,":"))) { /* group */ + goto HL_cleanup; + }else if((tk = strtok(NULL," \t\n\r"))) { /*Finally, a machine name.*/ + if( hrcnt >= (hacnt-1)) { /* grow array. */ + char **oldhlst = hlst; + hlst = (char **)REALLOC(hlst, (hacnt +MSTEP)*sizeof(char*)); + if( !hlst ) { + stonith_free_hostlist(oldhlst); + return NULL; + } + hacnt += MSTEP; + } + hlst[hrcnt] = STRDUP(tk); /* stuff the name. */ + hlst[hrcnt+1] = NULL; /* set next to NULL for looping */ + if (hlst[hrcnt] == NULL) { + stonith_free_hostlist(hlst); + return NULL; + } + strdown(hlst[hrcnt]); + hrcnt++; + } + }else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n",tk,rcv); + break; + } + } + +HL_cleanup: + stonith_free_hostlist(hlst); /* give the mem back */ + return NULL; +} + +#define SND_SIZE 256 +int +vacm_reset_req(StonithPlugin *s, int request, const char *host) +{ + struct pluginDevice *sd; + char snd[SND_SIZE]; /* god forbid its bigger than this */ + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + switch(request) { +#ifdef ST_POWERON + case ST_POWERON: + snprintf(snd, SND_SIZE, "EMP:POWER_ON:%s", host); + break; +#endif /*ST_POWERON*/ +#ifdef ST_POWEROFF + case ST_POWEROFF: + snprintf(snd, SND_SIZE, "EMP:POWER_OFF:%s", host); + break; +#endif /*ST_POWEROFF*/ + case ST_GENERIC_RESET: + snprintf(snd, SND_SIZE, "EMP:POWER_CYCLE:%s", host); + break; + default: + return S_INVAL; + } + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_RESETFAIL; + } + if (!(tk = strtok(rcv,":"))) { /*EMP*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of teh below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; + } else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + } else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + return S_RESETFAIL; + } else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_RESETFAIL; +} + +/* list => "nexxus:username:password" */ +static const char * const * +vacm_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_NEXXUS, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +static int +vacm_set_config(StonithPlugin *s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_NEXXUS, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + char *rcv; + int rcvlen; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->nexxus = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + /* When to initialize the sd->h */ + + if (api_nexxus_connect(sd->nexxus, sd->user, sd->passwd, &sd->h)<0){ + return S_OOPS; + } + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_OOPS; + } + if (strcmp(rcv, "NEXXUS_READY")) { + rc = S_BADCONFIG; + }else{ + rc = S_OK; + } + free(rcv); + + return(rc); +} + +/* + * The "vacmconf:" is in the conffile so that one file could be used for + * multiple device configs. This module will only look at the first line + * that starts with this token. All other line are ignored. (and thus + * could contain configs for other modules.) + * + * I don't think any other stonith modules do this currently. + */ +const char * +vacm_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = dgettext(ST_TEXTDOMAIN, "VACM"); + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "A driver for the VA Linux Cluster Manager."; + break; + + case ST_DEVICEURL: /* VACM's web site */ + ret = "http://vacm.sourceforge.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = vacmXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + +void +vacm_destroy(StonithPlugin *s) +{ + struct pluginDevice *sd; + + VOIDERRIFWRONGDEV(s); + sd = (struct pluginDevice*)s; + + if( sd->h ) { + api_nexxus_disconnect(sd->h); + } + + sd->pluginid = NOTpluginid; + if (sd->nexxus != NULL) { + FREE(sd->nexxus); + sd->nexxus = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + if (sd->passwd != NULL) { + FREE(sd->passwd); + sd->passwd = NULL; + } + + FREE(sd); +} + +static StonithPlugin * +vacm_new(const char *subplugin) +{ + struct pluginDevice *sd; + + sd = MALLOC(sizeof(struct pluginDevice)); + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->h = NULL; + sd->pluginid = pluginid; + sd->nexxus = NULL; + sd->user = NULL; + sd->passwd = NULL; + sd->idinfo = DEVICE; + sd->sp.s_ops = &vacmOps; + return &(sd->sp); /* same as "sd" */ +} diff --git a/lib/plugins/stonith/wti_mpc.c b/lib/plugins/stonith/wti_mpc.c new file mode 100644 index 0000000..548f91c --- /dev/null +++ b/lib/plugins/stonith/wti_mpc.c @@ -0,0 +1,856 @@ +/* + * Stonith module for WTI MPC (SNMP) + * Copyright (c) 2001 Andreas Piesk <a.piesk@gmx.net> + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * Modified for WTI MPC by Denis Chapligin <chollya@satgate.net>, SatGate, 2009 + * + * 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> + +/* device ID */ +#define DEVICE "WTI MPC" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include <net-snmp/net-snmp-config.h> +# include <net-snmp/net-snmp-includes.h> +# include <net-snmp/agent/net-snmp-agent-includes.h> +# define INIT_AGENT() init_master_agent() +#else +# include <ucd-snmp/ucd-snmp-config.h> +# include <ucd-snmp/ucd-snmp-includes.h> +# include <ucd-snmp/ucd-snmp-agent-includes.h> +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN wti_mpc +#define PIL_PLUGIN_S "wti_mpc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * wti_mpc_new(const char *); +static void wti_mpc_destroy(StonithPlugin *); +static const char * const * wti_mpc_get_confignames(StonithPlugin *); +static int wti_mpc_set_config(StonithPlugin *, StonithNVpair *); +static const char * wti_mpc_getinfo(StonithPlugin * s, int InfoType); +static int wti_mpc_status(StonithPlugin * ); +static int wti_mpc_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_mpc_hostlist(StonithPlugin *); + +static struct stonith_ops wti_mpcOps ={ + wti_mpc_new, /* Create new STONITH object */ + wti_mpc_destroy, /* Destroy STONITH object */ + wti_mpc_getinfo, /* Return STONITH info string */ + wti_mpc_get_confignames, /* Get configuration parameters */ + wti_mpc_set_config, /* Set configuration */ + wti_mpc_status, /* Return STONITH device status */ + wti_mpc_reset_req, /* Request a reset */ + wti_mpc_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; + +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; + DEBUGCALL; + + 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_mpcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 5 +#define OUTLET_OFF 6 +#define OUTLET_REBOOT 7 + +/* oids */ +#define OID_IDENT ".1.3.6.1.2.1.1.5.0" + +#define OID_GROUP_NAMES_V1 ".1.3.6.1.4.1.2634.3.1.3.1.2.%u" +#define OID_GROUP_STATE_V1 ".1.3.6.1.4.1.2634.3.1.3.1.3.%i" + +#define OID_GROUP_NAMES_V3 ".1.3.6.1.4.1.2634.3.100.300.1.2.%u" +#define OID_GROUP_STATE_V3 ".1.3.6.1.4.1.2634.3.100.300.1.3.%i" + +#define MAX_OUTLETS 128 + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" +#define ST_MIBVERSION "mib-version" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + int mib_version; /* mib version to use */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* constant strings */ +static const char *pluginid = "WTI-MPC-Stonith"; +static const char *NOTpluginID = "WTI MPC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_MIBVERSION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MIBVERSION \ + XML_PARM_SHORTDESC_END + +#define XML_MIBVERSION_LONGDESC \ + XML_MIBVERSION_LONGDESC_BEGIN("en") \ + "Version number of MPC MIB that we should use. Valid values are 1 (for 1.44 firmware) and 3 (for 1.62 firmware and later)" \ + XML_PARM_LONGDESC_END + +#define XML_MIBVERSION_PARM \ + XML_PARAMETER_BEGIN(ST_MIBVERSION, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_MIBVERSION_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void MPC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *MPC_open(char *hostname, int port +, char *community); +static void *MPC_read(struct snmp_session *sptr, const char *objname +, int type); +static int MPC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +MPC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +MPC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + MPC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +MPC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +MPC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +wti_mpc_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = MPC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +wti_mpc_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,j+1); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,j+1); + break; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: using %s as group names oid", __FUNCTION__, objname); + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +wti_mpc_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int outlet; + int found_outlet=-1; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,outlet); + break; + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + + /* Ok, stop iterating over host list */ + found_outlet=outlet; + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (found_outlet == -1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + break; + default: break; + } + + /* Turn them all off */ + + /* prepare objnames */ + + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V3,found_outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V1,found_outlet); + break; + } + + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!MPC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, found_outlet); + return (S_RESETFAIL); + } + + return (S_OK); +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +wti_mpc_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, ST_MIBVERSION, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +wti_mpc_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + char * i; + int mo; + char objname[MAX_STRING]; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {ST_MIBVERSION, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + sd->mib_version = atoi(namestocopy[3].s_value); + PluginImports->mfree(namestocopy[3].s_value); + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("wti_mpc"); + + /* now try to get a snmp session */ + if ((sd->sptr = MPC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of groups from the mpc */ + sd->num_outlets=0; + /* We scan goup names table starting from 1 to MAX_OUTLETS */ + /* and increase num_outlet counter on every group entry with name */ + /* first entry without name is the mark of the end of the group table */ + for (mo=1;mo<MAX_OUTLETS;mo++) { + switch (sd->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,mo); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,mo); + break; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: used for groupTable retrieval: %s." + , __FUNCTION__, objname); + } + + if ((i = MPC_read(sd->sptr, objname, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + if (strlen(i)) { + /* store the number of outlets */ + sd->num_outlets++; + } else { + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +wti_mpc_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "WTI MPC (via SNMP)\n" + "The WTI MPC can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +wti_mpc_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +wti_mpc_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->mib_version=1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &wti_mpcOps; + + /* return the object */ + return (&(ad->sp)); +} 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); +} + |