diff --git a/.gitignore b/.gitignore index 205f4379..70cfca50 100644 --- a/.gitignore +++ b/.gitignore @@ -1,97 +1,56 @@ +*.orig +*.rej *.swp Makefile.in aclocal.m4 autoconf autoheader autom4te.cache automake autoscan.log compile configure configure.scan config.guess config.log config.sub config.status Makefile depcomp install-sh libtoolize ltmain.sh libtool make/stamp-h1 # ignore "libtoolized" m4 files, but keep our (enumerated) ones /m4/* !/m4/ac_python_module.m4 # make/release.mk related litter /.tarball-version /tag-* make/clusterautoconfig.h* missing *.pc .deps .libs *.o *.la *.lo -fence/agents/alom/fence_alom -fence/agents/apc/fence_apc -fence/agents/apc_snmp/fence_apc_snmp -fence/agents/baytech/fence_baytech -fence/agents/bladecenter/fence_bladecenter -fence/agents/brocade/fence_brocade -fence/agents/bullpap/fence_bullpap -fence/agents/cisco_mds/fence_cisco_mds -fence/agents/cisco_ucs/fence_cisco_ucs -fence/agents/cpint/fence_cpint -fence/agents/docker/fence_docker -fence/agents/drac/fence_drac -fence/agents/drac5/fence_drac5 -fence/agents/eaton_snmp/fence_eaton_snmp -fence/agents/egenera/fence_egenera -fence/agents/eps/fence_eps -fence/agents/ibmblade/fence_ibmblade -fence/agents/ifmib/fence_ifmib -fence/agents/ilo/fence_ilo -fence/agents/ilo_mp/fence_ilo_mp -fence/agents/intelmodular/fence_intelmodular -fence/agents/ipdu/fence_ipdu -fence/agents/ipmilan/fence_ipmilan -fence/agents/ldom/fence_ldom -fence/agents/lib/fencing.py -fence/agents/lib/fencing_snmp.py -fence/agents/lib/fencing.pyc -fence/agents/lib/fencing_snmp.pyc -fence/agents/lpar/fence_lpar -fence/agents/manual/fence_ack_manual -fence/agents/mcdata/fence_mcdata -fence/agents/netio/fence_netio -fence/agents/node_assassin/fence_na -fence/agents/node_assassin/fence_na.conf -fence/agents/node_assassin/fence_na.lib -fence/agents/node_assassin/fence_na.pod -fence/agents/nss_wrapper/fence_nss_wrapper -fence/agents/pve/fence_pve -fence/agents/rackswitch/fence_rackswitch -fence/agents/rhevm/fence_rhevm -fence/agents/raritan/fence_raritan -fence/agents/rsa/fence_rsa -fence/agents/rsb/fence_rsb -fence/agents/sanbox2/fence_sanbox2 -fence/agents/scsi/fence_scsi -fence/agents/scsi/fence_scsi_test -fence/agents/virsh/fence_virsh -fence/agents/vixel/fence_vixel -fence/agents/vmware/fence_vmware -fence/agents/vmware/fence_vmware_helper -fence/agents/wti/fence_wti -fence/agents/xcat/fence_xcat -fence/agents/zvm/fence_zvm -fence/agents/zvm/fence_zvmip +fence/agents/lib/*.py* +!fence/agents/lib/*.py.py +!fence/agents/lib/check_used_options.py +fence/agents/*/fence_* +fence/agents/*/.dirstamp +!fence/agents/*/fence_*.py +!fence/agents/*/fence_*.c +!fence/agents/*/fence_*.h +!fence/agents/kdump/fence_kdump_send.8 +!fence/agents/manual/fence_ack_manual.8 +!fence/agents/zvm/fence_zvm_man_page .fence*.tmp fence-agents* .version tests/devices.d/* diff --git a/.travis.yml b/.travis.yml index 36f3ed0f..df9e5995 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,41 +1,44 @@ language: python sudo: required dist: trusty python: - "2.7" addons: apt: packages: - python-pexpect - xsltproc - time - libpam0g-dev - libxml2-utils - libcimcclient0-dev - swig + - iputils-ping before_install: - pip install suds - pip install pycurl - pip install requests - pip install pexpect + - pip install boto3 + - pip install google-api-python-client before_script: - wget https://github.com/Openwsman/openwsman/archive/v2.6.3.tar.gz - tar zxvf v2.6.3.tar.gz - cd openwsman-2.6.3 - perl -p -i -e "s/(\\$\{CURL_LIBRARIES\})/\1 ssl crypto/g" src/lib/CMakeLists.txt - mkdir build && cd build - cmake .. -DPYTHON_EXECUTABLE:FILEPATH=~/virtualenv/python2.7/bin/python -DLIB=/lib/x86_64-linux-gnu -DCMAKE_LIBRARY_ARCHITECTURE=x86_64-linux-gnu -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_VERBOSE_MAKEFILE=TRUE - make - sudo make install - cd ../.. script: - ./autogen.sh - ./configure - make -j4 - make -j4 check - PYTHONPATH=fence/agents/lib python fence/agents/lib/tests/test_fencing.py diff --git a/Makefile.am b/Makefile.am index e5ee3203..4560002d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,49 +1,47 @@ -EXTRA_DIST = autogen.sh make/fencebuild.mk scripts/fenceparse \ +EXTRA_DIST = autogen.sh make/fencebuild.mk \ .version make/release.mk \ make/git-version-gen make/gitlog-to-changelog tests AUTOMAKE_OPTIONS = foreign MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure depcomp \ config.guess config.sub missing install-sh \ autoheader automake autoconf libtool libtoolize \ ltmain.sh compile make/clusterautoconfig.h.in \ make/clusterautoconfig.h.in~ autoscan.log \ configure.scan -noinst_HEADERS = make/copyright.cf - ACLOCAL_AMFLAGS = -I m4 SUBDIRS = fence/agents/lib fence doc install-exec-local: $(INSTALL) -d $(DESTDIR)/$(LOGDIR) $(INSTALL) -d $(DESTDIR)/$(CLUSTERVARRUN) uninstall-local: rmdir $(DESTDIR)/$(LOGDIR) || :; rmdir $(DESTDIR)/$(CLUSTERVARRUN) || :; BUILT_SOURCES = .version .version: echo $(VERSION) > $@-t && mv $@-t $@ dist-hook: gen-ChangeLog echo $(VERSION) > $(distdir)/.tarball-version gen_start_date = 2000-01-01 .PHONY: gen-ChangeLog gen-ChangeLog: if test -d .git; then \ $(top_srcdir)/make/gitlog-to-changelog \ --since=$(gen_start_date) > $(distdir)/cl-t; \ rm -f $(distdir)/ChangeLog; \ mv $(distdir)/cl-t $(distdir)/ChangeLog; \ fi # this will get rid of "libtoolized" m4 files maintainer-clean-local: rm -rf $(filter-out \ $(top_srcdir)/m4/ac_python_module.m4,$(wildcard \ $(top_srcdir)/m4/*.m4)) diff --git a/configure.ac b/configure.ac index 790341f6..4b9e742d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,313 +1,346 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.63]) AC_INIT([fence-agents], m4_esyscmd([make/git-version-gen .tarball-version]), [linux-cluster@redhat.com]) AM_INIT_AUTOMAKE([-Wno-portability dist-bzip2 dist-xz subdir-objects]) LT_PREREQ([2.2.6]) LT_INIT AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([fence/agents/lib/fencing.py.py]) AC_CONFIG_HEADERS([make/clusterautoconfig.h]) AC_CANONICAL_HOST AC_PROG_LIBTOOL AC_LANG([C]) # Sanitize path if test "$prefix" = "NONE"; then prefix="/usr" if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi if test "$sysconfdir" = "\${prefix}/etc"; then sysconfdir="/etc" fi if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi case $exec_prefix in NONE) exec_prefix=$prefix;; prefix) exec_prefix=$prefix;; esac # Checks for programs. # check stolen from gnulib/m4/gnu-make.m4 if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then AC_MSG_ERROR([you don't seem to have GNU make; it is required]) fi AC_PROG_CC AM_PROG_CC_C_O AC_PROG_LN_S AC_PROG_INSTALL AC_PROG_MAKE_SET AC_PROG_AWK AC_PROG_CXX AC_PROG_RANLIB ## local helper functions # this function checks if CC support options passed as # args. Global CFLAGS are ignored during this test. cc_supports_flag() { local CFLAGS="$@" AC_MSG_CHECKING([whether $CC supports "$@"]) AC_COMPILE_IFELSE([int main(){return 0;}] , [RC=0; AC_MSG_RESULT([yes])], [RC=1; AC_MSG_RESULT([no])]) return $RC } # this function tests if a library has a certain function # by using AC_CHECK_LIB but restores the original LIBS global # envvar. This is required to avoid libtool to link everything # with everything. check_lib_no_libs() { AC_CHECK_LIB([$1], [$2],, [AC_MSG_ERROR([Unable to find $1 library])]) LIBS=$ac_check_lib_save_LIBS } # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h libintl.h limits.h netdb.h stddef.h sys/socket.h sys/time.h syslog.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT32_T # Checks for library functions. AC_FUNC_FORK AC_FUNC_MALLOC AC_CHECK_FUNCS([alarm atexit bzero dup2 memmove memset select socket strcasecmp strchr strdup strerror strtol]) # local options AC_ARG_ENABLE([debug], [ --enable-debug enable debug build. ], [ default="no" ]) AC_ARG_WITH([fenceagentslibdir], [ --with-fenceagentslibdir=PATH installation path for fence library. ], [ FENCEAGENTSLIBDIR="$withval" ], [ FENCEAGENTSLIBDIR="${datadir}/fence" ]) AC_ARG_WITH([default-config-dir], [ --with-default-config-dir=DIR cluster config directory. ], [ DEFAULT_CONFIG_DIR="$withval" ], [ DEFAULT_CONFIG_DIR="$sysconfdir/cluster" ]) AC_ARG_WITH([default-config-file], [ --with-default-config-file=FILE cluster config file. ], [ DEFAULT_CONFIG_FILE="$withval" ], [ DEFAULT_CONFIG_FILE="cluster.conf" ]) AC_ARG_WITH([agents], [ --with-agents=LIST list of agents to build/ship (default: all). ], [ AGENTS_LIST="$withval" ], [ AGENTS_LIST="all" ]) if test "x$AGENTS_LIST" = x; then AC_ERROR([No agents selected]) fi FENCE_KDUMP=0 if echo "$AGENTS_LIST" | grep -q -E "all|kdump"; then FENCE_KDUMP=1 AGENTS_LIST=$(echo "$AGENTS_LIST" | sed -E "s/kdump( |$)//") fi FENCE_MANUAL=0 if echo "$AGENTS_LIST" | grep -q -E "all|manual"; then FENCE_MANUAL=1 AGENTS_LIST=$(echo "$AGENTS_LIST" | sed -E "s/manual( |$)//") fi FENCE_SCSI=0 if echo "$AGENTS_LIST" | grep -q -E "all|scsi"; then FENCE_SCSI=1 fi FENCE_ZVM=0 if echo "$AGENTS_LIST" | grep -q -E "all|zvm( |$)"; then FENCE_ZVM=1 fi if test "x$AGENTS_LIST" != xall; then for j in $AGENTS_LIST; do if ! test -f fence/agents/$j/fence_$j*.py; then AC_ERROR([Agent $j does not exists]) fi AGENTS_LIST=`echo "$AGENTS_LIST" | sed -E -e "s#$j([^_/]|$)#$j/fence_$j\1#g" -e "s#zvm/fence_zvm( |$)#zvm/fence_zvmip\1#g"` done fi if test "x$AGENTS_LIST" = xall; then AGENTS_LIST=`find $srcdir/fence/agents -mindepth 2 -maxdepth 2 -name '*.py' -printf '%P ' | sed -e 's#lib/[A-Za-z_.]* ##g' -e 's#nss_wrapper/[A-Za-z_.]* ##g' -e 's#autodetect/[A-Za-z_.]* ##g'` fi XENAPILIB=0 if echo "$AGENTS_LIST" | grep -q xenapi; then XENAPILIB=1 fi ## random vars LOGDIR=${localstatedir}/log/cluster CLUSTERVARRUN=${localstatedir}/run/cluster CLUSTERDATA=${datadir}/cluster AM_PATH_PYTHON if test -z "$PYTHON"; then echo "*** Essential program python not found" 1>&2 exit 1 fi -AC_PYTHON_MODULE(suds, 1) AC_PYTHON_MODULE(pexpect, 1) AC_PYTHON_MODULE(pycurl, 1) AC_PYTHON_MODULE(requests, 1) -AC_PYTHON_MODULE(pywsman, 1) + +if echo "$AGENTS_LIST" | grep -q amt_ws; then + AC_PYTHON_MODULE(pywsman, 1) +fi +if echo "$AGENTS_LIST" | grep -q aws; then + AC_PYTHON_MODULE(boto3, 1) +fi +if echo "$AGENTS_LIST" | grep -q -E "ovh|vmware_soap"; then + AC_PYTHON_MODULE(suds, 1) +fi +if echo "$AGENTS_LIST" | grep -q gce; then + AC_PYTHON_MODULE(googleapiclient, 1) +fi ## path to 3rd-party binaries AC_PATH_PROG([IPMITOOL_PATH], [ipmitool], [/usr/bin/ipmitool]) AC_PATH_PROG([OPENSTACK_PATH], [openstack], [/usr/bin/openstack]) AC_PATH_PROG([AMTTOOL_PATH], [amttool], [/usr/bin/amttool]) AC_PATH_PROG([GNUTLSCLI_PATH], [gnutlscli], [/usr/bin/gnutls-cli]) AC_PATH_PROG([COROSYNC_CMAPCTL_PATH], [corosync-cmapctl], [/usr/sbin/corosync-cmapctl]) AC_PATH_PROG([SG_PERSIST_PATH], [sg_persist], [/usr/bin/sg_persist]) AC_PATH_PROG([SG_TURS_PATH], [sg_turs], [/usr/bin/sg_turs]) AC_PATH_PROG([VGS_PATH], [vgs], [/usr/sbin/vgs]) AC_PATH_PROG([SUDO_PATH], [sudo], [/usr/bin/sudo]) AC_PATH_PROG([SSH_PATH], [ssh], [/usr/bin/ssh]) AC_PATH_PROG([TELNET_PATH], [telnet], [/usr/bin/telnet]) AC_PATH_PROG([MPATH_PATH], [mpathpersist], [/usr/sbin/mpathpersist]) AC_PATH_PROG([SBD_PATH], [sbd], [/sbin/sbd]) AC_PATH_PROG([SUDO_PATH], [sudo], [/usr/bin/sudo]) AC_PATH_PROG([SNMPWALK_PATH], [snmpwalk], [/usr/bin/snmpwalk]) AC_PATH_PROG([SNMPSET_PATH], [snmpset], [/usr/bin/snmpset]) AC_PATH_PROG([SNMPGET_PATH], [snmpget], [/usr/bin/snmpget]) AC_PATH_PROG([NOVA_PATH], [nova], [/usr/bin/nova]) AC_PATH_PROG([POWERMAN_PATH], [powerman], [/usr/bin/powerman]) +AC_PATH_PROG([PING_CMD], [ping]) +AC_PATH_PROG([PING6_CMD], [ping6]) +AC_PATH_PROG([PING4_CMD], [ping4]) + +if test "x${ac_cv_path_PING_CMD}" = x; then + # assume multicall-ping just not available in build-environment + PING_CMD="/bin/ping" + PING4_CMD="/bin/ping -4" + PING6_CMD="/bin/ping -6" +elif test "x${ac_cv_path_PING6_CMD}" = x; then + # just IPv4 + PING4_CMD="${ac_cv_path_PING_CMD}" +elif test -L ${ac_cv_path_PING6_CMD}; then + # assume multicall-ping + PING4_CMD="${ac_cv_path_PING_CMD} -4" +else + # ping is just IPv4 + PING4_CMD="${ac_cv_path_PING_CMD}" +fi + + ## do subst AC_SUBST([DEFAULT_CONFIG_DIR]) AC_DEFINE_UNQUOTED([DEFAULT_CONFIG_DIR], "$(eval echo ${DEFAULT_CONFIG_DIR})", [Default config directory]) AC_SUBST([DEFAULT_CONFIG_FILE]) AC_DEFINE_UNQUOTED([DEFAULT_CONFIG_FILE], "$(eval echo ${DEFAULT_CONFIG_FILE})", [Default config file]) AC_SUBST([LOGDIR]) AC_DEFINE_UNQUOTED([LOGDIR], "$(eval echo ${LOGDIR})", [Default logging directory]) AC_SUBST([CLUSTERVARRUN]) AC_DEFINE_UNQUOTED([CLUSTERVARRUN], "$(eval echo ${CLUSTERVARRUN})", [Default cluster var/run directory]) AC_SUBST([CLUSTERDATA]) AC_SUBST([FENCEAGENTSLIBDIR]) AC_SUBST([SNMPBIN]) AC_SUBST([AGENTS_LIST]) AM_CONDITIONAL(BUILD_FENCE_KDUMP, test $FENCE_KDUMP -eq 1) AM_CONDITIONAL(BUILD_FENCE_MANUAL, test $FENCE_MANUAL -eq 1) AM_CONDITIONAL(BUILD_FENCE_SCSI, test $FENCE_SCSI -eq 1) AM_CONDITIONAL(BUILD_FENCE_ZVM, test $FENCE_ZVM -eq 1) AM_CONDITIONAL(BUILD_XENAPILIB, test $XENAPILIB -eq 1) AC_SUBST([IPMITOOL_PATH]) AC_SUBST([OPENSTACK_PATH]) AC_SUBST([AMTTOOL_PATH]) AC_SUBST([COROSYNC_CMAPCTL_PATH]) AC_SUBST([SG_PERSIST_PATH]) AC_SUBST([SG_TURS_PATH]) AC_SUBST([VGS_PATH]) AC_SUBST([POWERMAN_PATH]) + ## *FLAGS handling ENV_CFLAGS="$CFLAGS" ENV_CPPFLAGS="$CPPFLAGS" ENV_LDFLAGS="$LDFLAGS" # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0" else OPT_CFLAGS="-O2" fi # gdb flags if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi # extra warnings EXTRA_WARNINGS="" WARNLIST=" all shadow missing-prototypes missing-declarations strict-prototypes declaration-after-statement pointer-arith write-strings cast-align bad-function-cast missing-format-attribute format=2 format-security format-nonliteral no-long-long unsigned-char gnu89-inline no-strict-aliasing " for j in $WARNLIST; do if cc_supports_flag -W$j; then EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j"; fi done CFLAGS="$ENV_CFLAGS $OPT_CFLAGS $GDB_FLAGS \ $EXTRA_WARNINGS $WERROR_CFLAGS" CPPFLAGS="-I\$(top_builddir)/make -I\$(top_srcdir)/make -I. $ENV_CPPFLAGS" LDFLAGS="$ENV_LDFLAGS" AM_EXTRA_RECURSIVE_TARGETS([xml-upload]) AC_CONFIG_FILES([Makefile fence/Makefile fence/agents/Makefile fence/agents/lib/Makefile doc/Makefile]) AC_OUTPUT diff --git a/doc/COPYRIGHT b/doc/COPYRIGHT index 8124c533..49f88c61 100644 --- a/doc/COPYRIGHT +++ b/doc/COPYRIGHT @@ -1,73 +1,74 @@ Unless specified otherwise in the "exceptions section" below: Copyright (C) 1997-2003 Sistina Software, Inc. All rights reserved. Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. Exceptions: fence/agents/hds_cb/*: Copyright (C) 2012 Matthew Clark. Author: Matthew Clark fence/agents/xenapi/*: Copyright (C) 2011 Matthew Clark. Author: Matthew Clark fence/agents/apc_snmp/powernet369.mib: Copyright (c) 2005 American Power Conversion, Inc. PowerNet is a Trademark of American Power Conversion Corp. fence/agents/eaton_snmp/fence_eaton_snmp.py: Copyright (c) 2011 eaton.com Author: Arnaud Quette fence/agents/ifmib/fence_ifmib.py: Copyright (C) 2008-2011 Ross Vandegrift. Written by Ross Vandegrift fence/agents/intelmodular/fence_intelmodular.pl: Contributed by Matthew Kent fence/agents/ipmilan/expect.{c,h}: Copyright (C) 2000 Alan Robertson fence/agents/node_assassin/* Copyright (C) 2009-2011 Madison Kelly/Alteeve's Niche! Author: Digimer fence/man/fence_ifmib.8: Copyright (C) 2008-2011 Ross Vandegrift. Written by Ross Vandegrift Authors as known by current RCS as of the time of writing: Abhijith Das Adam Manthei A. J. Lewis Alasdair G. Kergon Andrew Price Benjamin Marzinski Bob Peterson Chris Feist Christine Caulfield Daniel Phillips David Teigland Fabio M. Di Nitto James Parsons Joel Becker Jonathan Brassow jparsons Ken Preslan +Klaus Wenninger Lon Hohberger Marc - A. Dahlhaus Marek 'marx' Grac Mark Hlawatschek Michael Conrad Tadpol Tilstra Patrick Caulfield Robert Peterson Ross Vandegrift Ryan McCabe Ryan O'Hara Stanko Kupcevic Steven Whitehouse Wendy Cheng diff --git a/fence/agents/Makefile.am b/fence/agents/Makefile.am index c5e06bd3..023eea7d 100644 --- a/fence/agents/Makefile.am +++ b/fence/agents/Makefile.am @@ -1,107 +1,108 @@ MAINTAINERCLEANFILES = Makefile.in TARGET = $(AGENTS_LIST:%.py=%) SRC = $(TARGET:=.py) CLEAN_TARGET_ADDITIONAL = kdump/fence_kdump_send manual/fence_ack_manual */*.o EXTRA_DIST = $(SRC) sbin_PROGRAMS = sbin_SCRIPTS = $(TARGET) libexec_PROGRAMS = dist_man_MANS = noinst_HEADERS = kdump/list.h kdump/message.h kdump/options.h kdump/version.h zvm/fence_zvm.h EXTRA_SCRIPTS = man_MANS = $(sbin_SCRIPTS:=.8) if BUILD_FENCE_KDUMP sbin_PROGRAMS += kdump/fence_kdump libexec_PROGRAMS += kdump/fence_kdump_send dist_man_MANS += kdump/fence_kdump.8 kdump/fence_kdump_send.8 kdump_fence_kdump_SOURCES = kdump/fence_kdump.c kdump_fence_kdump_CFLAGS = -D_GNU_SOURCE -Ikdump kdump_fence_kdump_send_SOURCES = kdump/fence_kdump_send.c kdump_fence_kdump_send_CFLAGS = -D_GNU_SOURCE -Ikdump endif if BUILD_FENCE_MANUAL EXTRA_DIST += manual/fence_ack_manual.in manual/fence_ack_manual.8 sbin_SCRIPTS += manual/fence_ack_manual endif if BUILD_FENCE_ZVM EXTRA_DIST += zvm/fence_zvm_man_page sbin_PROGRAMS += zvm/fence_zvm dist_man_MANS += zvm/fence_zvm.8 zvm_fence_zvm_SOURCES = zvm/fence_zvm.c zvm_fence_zvm_CFLAGS = -D_GNU_SOURCE -Izvm endif if BUILD_FENCE_SCSI scsidatadir = $(CLUSTERDATA) scsidata_SCRIPTS = scsi/fence_scsi_check scsi/fence_scsi_check_hardreboot endif FENCE_TEST_ARGS = \ login=test\n\ passwd=test\n\ ipaddr=test\n\ port=1\n\ managed=1\n\ devices=test\n\ session_url=http://test\n\ -email=test@test.te +email=test@test.te\n\ +ping_targets=localhost manual/fence_ack_manual: manual/fence_ack_manual.in mkdir -p $(@D) cat $^ | sed \ -e 's#@clustervarrun@#${CLUSTERVARRUN}#g' \ > $@ scsi/fence_scsi_check: scsi/fence_scsi cp $^ $@ scsi/fence_scsi_check_hardreboot: scsi/fence_scsi cp $^ $@ kdump/fence_kdump.8: kdump/fence_kdump $(top_srcdir)/fence/agents/lib/fence2man.xsl set -e && \ ./$(@:%.8=%) -o metadata > $(@D)/.$(@F).tmp && \ xmllint --noout --relaxng $(top_srcdir)/fence/agents/lib/metadata.rng $(@D)/.$(@F).tmp && \ xsltproc $(top_srcdir)/fence/agents/lib/fence2man.xsl $(@D)/.$(@F).tmp > $@ xsltproc $(top_srcdir)/fence/agents/lib/fence2wiki.xsl $(@D)/.$(@F).tmp | grep -v ' $(@D)/$(@F:%.8=%.wiki) kdump/fence_kdump_send.8: true manual/fence_ack_manual.8: true zvm/fence_zvm.8: zvm/fence_zvm cp $(top_srcdir)/fence/agents/zvm/fence_zvm_man_page $(@D)/fence_zvm.8 cisco_mds/fence_cisco_mds.delay-check: cisco_mds/fence_cisco_mds $(eval INPUT=$(subst .delay-check,,$@)) FENCE_TEST_ARGS_CISCO_MDS=$$(/bin/echo -e '$(FENCE_TEST_ARGS)' | sed 's#port=1#port=fc1/1#'); \ test `PYTHONPATH=$(abs_srcdir)/lib:$(abs_builddir)/lib /usr/bin/time -f "%e" \ sh -c "/bin/echo -e 'delay=10\n $$FENCE_TEST_ARGS_CISCO_MDS' | $(PYTHON) ./$(INPUT)" 2>&1 |\ sed 's/\.//' | tail -n 1` -ge 1000 || ( \ PYTHONPATH=$(abs_srcdir)/lib:$(abs_builddir)/lib /usr/bin/time -f "%e" \ sh -c "/bin/echo -e "delay=0\n $$FENCE_TEST_ARGS_CISCO_MDS" | $(PYTHON) ./$(INPUT)"; false ) include $(top_srcdir)/make/fencebuild.mk include $(top_srcdir)/make/fenceman.mk include $(top_srcdir)/make/agentpycheck.mk diff --git a/fence/agents/alom/fence_alom.py b/fence/agents/alom/fence_alom.py index 62ffd7d0..7b03dc2a 100644 --- a/fence/agents/alom/fence_alom.py +++ b/fence/agents/alom/fence_alom.py @@ -1,59 +1,53 @@ #!@PYTHON@ -tt # The Following Agent Has Been Tested On: # # Sun(tm) Advanced Lights Out Manager CMT v1.6.1 # as found on SUN T2000 Niagara import sys, re, time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Sun Advanced Lights Out Manager (ALOM)" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send_eol("showplatform") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) status = re.search("standby", conn.before.lower()) result = (status != None and "off" or "on") return result def set_power_status(conn, options): cmd_line = (options["--action"] == "on" and "poweron" or "poweroff -f -y") conn.send_eol(cmd_line) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) # Get the machine some time between poweron and poweroff time.sleep(int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure"] atexit.register(atexit_handler) all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = [r"sc\>\ "] options = check_input(device_opt, process_input(device_opt)) options["telnet_over_ssh"] = 1 docs = {} docs["shortdesc"] = "Fence agent for Sun ALOM" docs["longdesc"] = "fence_alom is an I/O Fencing \ agent which can be used with ALOM connected machines." docs["vendorurl"] = "http://www.sun.com" show_docs(options, docs) # Operate the fencing device conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, None) fence_logout(conn, "logout") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/amt/fence_amt.py b/fence/agents/amt/fence_amt.py index 082f5d09..feec6e3e 100644 --- a/fence/agents/amt/fence_amt.py +++ b/fence/agents/amt/fence_amt.py @@ -1,134 +1,128 @@ #!@PYTHON@ -tt import sys, re, os import atexit from pipes import quote sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Fence agent for Intel AMT" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(_, options): output = amt_run_command(options, create_command(options, "status")) match = re.search('Powerstate:[\\s]*(..)', str(output)) status = match.group(1) if match else None if status == None: return "fail" elif status == "S0": # SO = on; S3 = sleep; S5 = off return "on" else: return "off" def set_power_status(_, options): amt_run_command(options, create_command(options, options["--action"])) return def reboot_cycle(_, options): (status, _, _) = run_command(options, create_command(options, "cycle")) return not bool(status) def amt_run_command(options, command, timeout=None): env = os.environ.copy() x = quote(options["--password"]) x = x[:-1] if x.endswith("'") else x x = x[1:] if x.startswith("'") else x env["AMT_PASSWORD"] = x # This is needed because setting the AMT_PASSWORD env # variable only works when no pipe is involved. E.g.: # - Broken: # $ AMT_PASSWORD='foobar' echo 'y' | /usr/bin/amttool nuc2 powerdown # 401 Unauthorized at /usr/bin/amttool line 129. # - Working: # $ AMT_PASSWORD='foobar' sh -c "(echo 'y' | /usr/bin/amttool nuc2 powerdown)" # execute: powerdown # result: pt_status: success newcommand = "sh -c \"(%s)\"" % command return run_command(options, newcommand, timeout, env) def create_command(options, action): cmd = options["--amttool-path"] # --ip / -a cmd += " " + options["--ip"] # --action / -o if action == "status": cmd += " info" elif action == "on": cmd = "echo \"y\"|" + cmd cmd += " powerup" elif action == "off": cmd = "echo \"y\"|" + cmd cmd += " powerdown" elif action == "cycle": cmd = "echo \"y\"|" + cmd cmd += " powercycle" if action in ["on", "off", "cycle"] and "--boot-option" in options: cmd += options["--boot-option"] # --use-sudo / -d if "--use-sudo" in options: cmd = options["--sudo-path"] + " " + cmd return cmd def define_new_opts(): all_opt["boot_option"] = { "getopt" : "b:", "longopt" : "boot-option", "help" : "-b, --boot-option=[option] " "Change the default boot behavior of the machine. (pxe|hd|hdsafe|cd|diag)", "required" : "0", "shortdesc" : "Change the default boot behavior of the machine.", "choices" : ["pxe", "hd", "hdsafe", "cd", "diag"], "order" : 1 } all_opt["amttool_path"] = { "getopt" : ":", "longopt" : "amttool-path", "help" : "--amttool-path=[path] Path to amttool binary", "required" : "0", "shortdesc" : "Path to amttool binary", "default" : "@AMTTOOL_PATH@", "order": 200 } def main(): atexit.register(atexit_handler) device_opt = ["ipaddr", "no_login", "passwd", "boot_option", "no_port", "sudo", "amttool_path", "method"] define_new_opts() all_opt["ipport"]["default"] = "16994" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for AMT" docs["longdesc"] = "fence_amt is an I/O Fencing agent \ which can be used with Intel AMT. This agent calls support software amttool\ (http://www.kraxel.org/cgit/amtterm/)." docs["vendorurl"] = "http://www.intel.com/" show_docs(options, docs) run_delay(options) if not is_executable(options["--amttool-path"]): fail_usage("Amttool not found or not accessible") result = fence_action(None, options, set_power_status, get_power_status, None, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/amt_ws/fence_amt_ws.py b/fence/agents/amt_ws/fence_amt_ws.py index 5284a77a..b99f20ad 100755 --- a/fence/agents/amt_ws/fence_amt_ws.py +++ b/fence/agents/amt_ws/fence_amt_ws.py @@ -1,243 +1,236 @@ #!@PYTHON@ -tt # # Fence agent for Intel AMT (WS) based on code from the openstack/ironic project: # https://github.com/openstack/ironic/blob/master/ironic/drivers/modules/amt/power.py # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import run_delay, fail_usage, fail, EC_STATUS import pywsman from xml.etree import ElementTree - -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Fence agent for Intel AMT (WS)" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - POWER_ON='2' POWER_OFF='8' POWER_CYCLE='10' RET_SUCCESS = '0' CIM_PowerManagementService = ('http://schemas.dmtf.org/wbem/wscim/1/' 'cim-schema/2/CIM_PowerManagementService') CIM_ComputerSystem = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/CIM_ComputerSystem') CIM_AssociatedPowerManagementService = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/' 'CIM_AssociatedPowerManagementService') CIM_BootConfigSetting = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/CIM_BootConfigSetting') CIM_BootSourceSetting = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/CIM_BootSourceSetting') def xml_find(doc, namespace, item): if doc is None: return tree = ElementTree.fromstring(doc.root().string()) query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace, 'item': item}) return tree.find(query) def _generate_power_action_input(action): method_input = "RequestPowerStateChange_INPUT" address = 'http://schemas.xmlsoap.org/ws/2004/08/addressing' anonymous = ('http://schemas.xmlsoap.org/ws/2004/08/addressing/' 'role/anonymous') wsman = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd' namespace = CIM_PowerManagementService doc = pywsman.XmlDoc(method_input) root = doc.root() root.set_ns(namespace) root.add(namespace, 'PowerState', action) child = root.add(namespace, 'ManagedElement', None) child.add(address, 'Address', anonymous) grand_child = child.add(address, 'ReferenceParameters', None) grand_child.add(wsman, 'ResourceURI', CIM_ComputerSystem) g_grand_child = grand_child.add(wsman, 'SelectorSet', None) g_g_grand_child = g_grand_child.add(wsman, 'Selector', 'ManagedSystem') g_g_grand_child.attr_add(wsman, 'Name', 'Name') return doc def get_power_status(_, options): client = pywsman.Client(options["--ip"], int(options["--ipport"]), \ '/wsman', 'http', 'admin', options["--password"]) namespace = CIM_AssociatedPowerManagementService client_options = pywsman.ClientOptions() doc = client.get(client_options, namespace) _SOAP_ENVELOPE = 'http://www.w3.org/2003/05/soap-envelope' item = 'Fault' fault = xml_find(doc, _SOAP_ENVELOPE, item) if fault is not None: logging.error("Failed to get power state for: %s port:%s", \ options["--ip"], options["--ipport"]) fail(EC_STATUS) item = "PowerState" try: power_state = xml_find(doc, namespace, item).text except AttributeError: logging.error("Failed to get power state for: %s port:%s", \ options["--ip"], options["--ipport"]) fail(EC_STATUS) if power_state == POWER_ON: return "on" elif power_state == POWER_OFF: return "off" else: fail(EC_STATUS) def set_power_status(_, options): client = pywsman.Client(options["--ip"], int(options["--ipport"]), \ '/wsman', 'http', 'admin', options["--password"]) method = 'RequestPowerStateChange' client_options = pywsman.ClientOptions() client_options.add_selector('Name', 'Intel(r) AMT Power Management Service') if options["--action"] == "on": target_state = POWER_ON elif options["--action"] == "off": target_state = POWER_OFF elif options["--action"] == "reboot": target_state = POWER_CYCLE if options["--action"] in ["on", "off", "reboot"] \ and "--boot-option" in options: set_boot_order(_, client, options) doc = _generate_power_action_input(target_state) client_doc = client.invoke(client_options, CIM_PowerManagementService, \ method, doc) item = "ReturnValue" return_value = xml_find(client_doc, CIM_PowerManagementService, item).text if return_value != RET_SUCCESS: logging.error("Failed to set power state: %s for: %s", \ options["--action"], options["--ip"]) fail(EC_STATUS) def set_boot_order(_, client, options): method_input = "ChangeBootOrder_INPUT" address = 'http://schemas.xmlsoap.org/ws/2004/08/addressing' anonymous = ('http://schemas.xmlsoap.org/ws/2004/08/addressing/' 'role/anonymous') wsman = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd' namespace = CIM_BootConfigSetting if options["--boot-option"] == "pxe": device = "Intel(r) AMT: Force PXE Boot" elif options["--boot-option"] == "hd" or "hdsafe": device = "Intel(r) AMT: Force Hard-drive Boot" elif options["--boot-option"] == "cd": device = "Intel(r) AMT: Force CD/DVD Boot" elif options["--boot-option"] == "diag": device = "Intel(r) AMT: Force Diagnostic Boot" else: logging.error('Boot device: %s not supported.', \ options["--boot-option"]) return method = 'ChangeBootOrder' client_options = pywsman.ClientOptions() client_options.add_selector('InstanceID', \ 'Intel(r) AMT: Boot Configuration 0') doc = pywsman.XmlDoc(method_input) root = doc.root() root.set_ns(namespace) child = root.add(namespace, 'Source', None) child.add(address, 'Address', anonymous) grand_child = child.add(address, 'ReferenceParameters', None) grand_child.add(wsman, 'ResourceURI', CIM_BootSourceSetting) g_grand_child = grand_child.add(wsman, 'SelectorSet', None) g_g_grand_child = g_grand_child.add(wsman, 'Selector', device) g_g_grand_child.attr_add(wsman, 'Name', 'InstanceID') if options["--boot-option"] == "hdsafe": g_g_grand_child = g_grand_child.add(wsman, 'Selector', 'True') g_g_grand_child.attr_add(wsman, 'Name', 'UseSafeMode') client_doc = client.invoke(client_options, CIM_BootConfigSetting, \ method, doc) item = "ReturnValue" return_value = xml_find(client_doc, CIM_BootConfigSetting, item).text if return_value != RET_SUCCESS: logging.error("Failed to set boot device to: %s for: %s", \ options["--boot-option"], options["--ip"]) fail(EC_STATUS) def reboot_cycle(_, options): status = set_power_status(_, options) return not bool(status) def define_new_opts(): all_opt["boot_option"] = { "getopt" : "b:", "longopt" : "boot-option", "help" : "-b, --boot-option=[option] " "Change the default boot behavior of the\n" " machine." " (pxe|hd|hdsafe|cd|diag)", "required" : "0", "shortdesc" : "Change the default boot behavior of the machine.", "choices" : ["pxe", "hd", "hdsafe", "cd", "diag"], "order" : 1 } def main(): atexit.register(atexit_handler) device_opt = ["ipaddr", "no_login", "passwd", "boot_option", "no_port", "method"] define_new_opts() all_opt["ipport"]["default"] = "16992" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for AMT (WS)" docs["longdesc"] = "fence_amt_ws is an I/O Fencing agent \ which can be used with Intel AMT (WS). This agent requires \ the pywsman Python library which is included in OpenWSMAN. \ (http://openwsman.github.io/)." docs["vendorurl"] = "http://www.intel.com/" show_docs(options, docs) run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, \ None, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/apc/fence_apc.py b/fence/agents/apc/fence_apc.py index 1e9c3761..24a5a423 100644 --- a/fence/agents/apc/fence_apc.py +++ b/fence/agents/apc/fence_apc.py @@ -1,266 +1,260 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Model Firmware ## +---------------------------------------------+ ## AP7951 AOS v2.7.0, PDU APP v2.7.3 ## AP7941 AOS v3.5.7, PDU APP v3.5.6 ## AP9606 AOS v2.5.4, PDU APP v2.7.3 ## ## @note: ssh is very slow on AP79XX devices protocol (1) and ## cipher (des/blowfish) have to be defined ##### import sys, re, time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_STATUS -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New APC Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="March, 2008" -#END_VERSION_GENERATION - # Fix for connection timed out issue in: # https://bugzilla.redhat.com/show_bug.cgi?id=1342584 TIMEDOUT_DELAY = 0.5 def get_power_status(conn, options): exp_result = 0 outlets = {} conn.send_eol("1") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) version = 0 admin = 0 switch = 0 if None != re.compile('.* MasterSwitch plus.*', re.IGNORECASE | re.S).match(conn.before): switch = 1 if None != re.compile('.* MasterSwitch plus 2', re.IGNORECASE | re.S).match(conn.before): if "--switch" not in options: fail_usage("Failed: You have to enter physical switch number") else: if "--switch" not in options: options["--switch"] = "1" if None == re.compile('.*Outlet Management.*', re.IGNORECASE | re.S).match(conn.before): version = 2 else: version = 3 if None == re.compile('.*Outlet Control/Configuration.*', re.IGNORECASE | re.S).match(conn.before): admin = 0 else: admin = 1 if switch == 0: if version == 2: if admin == 0: conn.send_eol("2") else: conn.send_eol("3") else: conn.send_eol("2") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.send_eol("1") else: conn.send_eol(options["--switch"]) while True: exp_result = conn.log_expect( ["Press "] + options["--command-prompt"], int(options["--shell-timeout"])) lines = conn.before.split("\n") show_re = re.compile(r'(^|\x0D)\s*(\d+)- (.*?)\s+(ON|OFF)\s*') for line in lines: res = show_re.search(line) if res != None: outlets[res.group(2)] = (res.group(3), res.group(4)) time.sleep(TIMEDOUT_DELAY) conn.send_eol("") if exp_result != 0: break conn.send(chr(0o3)) conn.log_expect("- Logout", int(options["--shell-timeout"])) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) if ["list", "monitor"].count(options["--action"]) == 1: return outlets else: try: (_, status) = outlets[options["--plug"]] return status.lower().strip() except KeyError: fail(EC_STATUS) def set_power_status(conn, options): action = { 'on' : "1", 'off': "2" }[options["--action"]] conn.send_eol("1") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) version = 0 admin2 = 0 admin3 = 0 switch = 0 if None != re.compile('.* MasterSwitch plus.*', re.IGNORECASE | re.S).match(conn.before): switch = 1 ## MasterSwitch has different schema for on/off actions action = { 'on' : "1", 'off': "3" }[options["--action"]] if None != re.compile('.* MasterSwitch plus 2', re.IGNORECASE | re.S).match(conn.before): if "--switch" not in options: fail_usage("Failed: You have to enter physical switch number") else: if "--switch" not in options: options["--switch"] = 1 if None == re.compile('.*Outlet Management.*', re.IGNORECASE | re.S).match(conn.before): version = 2 else: version = 3 if None == re.compile('.*Outlet Control/Configuration.*', re.IGNORECASE | re.S).match(conn.before): admin2 = 0 else: admin2 = 1 if switch == 0: if version == 2: if admin2 == 0: conn.send_eol("2") else: conn.send_eol("3") else: conn.send_eol("2") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) if None == re.compile('.*2- Outlet Restriction.*', re.IGNORECASE | re.S).match(conn.before): admin3 = 0 else: admin3 = 1 conn.send_eol("1") else: conn.send_eol(options["--switch"]) while 0 == conn.log_expect( ["Press "] + options["--command-prompt"], int(options["--shell-timeout"])): time.sleep(TIMEDOUT_DELAY) conn.send_eol("") conn.send_eol(options["--plug"]+"") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) if switch == 0: if admin2 == 1: conn.send_eol("1") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) if admin3 == 1: conn.send_eol("1") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) else: conn.send_eol("1") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.send_eol(action) conn.log_expect("Enter 'YES' to continue or to cancel :", int(options["--shell-timeout"])) conn.send_eol("YES") conn.log_expect("Press to continue...", int(options["--power-timeout"])) time.sleep(TIMEDOUT_DELAY) conn.send_eol("") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) conn.send(chr(0o3)) conn.log_expect("- Logout", int(options["--shell-timeout"])) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_power_status5(conn, options): outlets = {} conn.send_eol("olStatus all") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) lines = conn.before.split("\n") show_re = re.compile(r'^\s*(\d+): (.*): (On|Off)\s*$', re.IGNORECASE) for line in lines: res = show_re.search(line) if res != None: outlets[res.group(1)] = (res.group(2), res.group(3)) if ["list", "monitor"].count(options["--action"]) == 1: return outlets else: try: (_, status) = outlets[options["--plug"]] return status.lower().strip() except KeyError: fail(EC_STATUS) def set_power_status5(conn, options): action = { 'on' : "olOn", 'off': "olOff" }[options["--action"]] conn.send_eol(action + " " + options["--plug"]) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "switch", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["\n>", "\napc>"] all_opt["ssh_options"]["default"] = "-1 -c blowfish" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for APC over telnet/ssh" docs["longdesc"] = "fence_apc is an I/O Fencing agent \ which can be used with the APC network power switch. It logs into device \ via telnet/ssh and reboots a specified outlet. Lengthy telnet/ssh connections \ should be avoided while a GFS cluster is running because the connection \ will block any necessary fencing actions." docs["vendorurl"] = "http://www.apc.com" show_docs(options, docs) ## Support for --plug [switch]:[plug] notation that was used before if (("--plug" in options) == 1) and (-1 != options["--plug"].find(":")): (switch, plug) = options["--plug"].split(":", 1) options["--switch"] = switch options["--plug"] = plug ## ## Operate the fencing device #### conn = fence_login(options) ## Detect firmware version (ASCII menu vs command-line interface) ## and continue with proper action #### result = -1 firmware_version = re.compile(r'\s*v(\d)*\.').search(conn.before) if (firmware_version != None) and (firmware_version.group(1) in [ "5", "6" ]): result = fence_action(conn, options, set_power_status5, get_power_status5, get_power_status5) else: result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "4") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/apc_snmp/fence_apc_snmp.py b/fence/agents/apc_snmp/fence_apc_snmp.py index f93cfa84..f68c0cb6 100644 --- a/fence/agents/apc_snmp/fence_apc_snmp.py +++ b/fence/agents/apc_snmp/fence_apc_snmp.py @@ -1,224 +1,218 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - APC Switched Rack PDU - SNMP v1 # (MB:v3.7.0 PF:v2.7.0 PN:apc_hw02_aos_270.bin AF1:v2.7.3 # AN1:apc_hw02_aos_270.bin AF1:v2.7.3 AN1:apc_hw02_rpdu_273.bin MN:AP7930 HR:B2) # - APC Web/SNMP Management Card - SNMP v1 and v3 (noAuthNoPrivacy,authNoPrivacy, authPrivacy) # (MB:v3.8.6 PF:v3.5.8 PN:apc_hw02_aos_358.bin AF1:v3.5.7 # AN1:apc_hw02_aos_358.bin AF1:v3.5.7 AN1:apc_hw02_rpdu_357.bin MN:AP7900 HR:B2) # - APC Switched Rack PDU - SNMP v1 # (MB:v3.7.0 PF:v2.7.0 PN:apc_hw02_aos_270.bin AF1:v2.7.3 # AN1:apc_hw02_rpdu_273.bin MN:AP7951 HR:B2) # - Tripplite PDUMH20HVNET 12.04.0055 - SNMP v1, v2c, v3 import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="APC SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see ApcRPDU, ApcMSP, ApcMS, TripplitePDU device = None # Port ID port_id = None # Switch ID switch_id = None # Classes describing Device params class TripplitePDU(object): # Rack PDU status_oid = '.1.3.6.1.4.1.850.10.2.3.5.1.2.1.%d' control_oid = '.1.3.6.1.4.1.850.10.2.3.5.1.4.1.%d' outlet_table_oid = '.1.3.6.1.4.1.850.10.2.3.5.1.5' ident_str = "Tripplite" state_on = 2 state_off = 1 turn_on = 2 turn_off = 1 has_switches = False class ApcRPDU(object): # Rack PDU status_oid = '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.4.%d' control_oid = '.1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%d' outlet_table_oid = '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.2' ident_str = "APC rPDU" state_on = 1 state_off = 2 turn_on = 1 turn_off = 2 has_switches = False class ApcMSP(object): # Master Switch+ status_oid = '.1.3.6.1.4.1.318.1.1.6.7.1.1.5.%d.1.%d' control_oid = '.1.3.6.1.4.1.318.1.1.6.5.1.1.5.%d.1.%d' outlet_table_oid = '.1.3.6.1.4.1.318.1.1.6.7.1.1.4' ident_str = "APC Master Switch+" state_on = 1 state_off = 2 turn_on = 1 turn_off = 3 has_switches = True class ApcMS(object): # Master Switch - seems oldest, but supported on every APC PDU status_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' control_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' outlet_table_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.4' ident_str = "APC Master Switch (fallback)" state_on = 1 state_off = 2 turn_on = 1 turn_off = 2 has_switches = False class ApcMS6(object): # Master Switch with 6.x firmware status_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' control_oid = '.1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%d' outlet_table_oid = '1.3.6.1.4.1.318.1.1.4.4.2.1.4' ident_str = "APC Master Switch with firmware v6.x" state_on = 1 state_off = 2 turn_on = 1 turn_off = 2 has_switches = False ### FUNCTIONS ### def apc_set_device(conn): global device agents_dir = {'.1.3.6.1.4.1.318.1.3.4.5':ApcRPDU, '.1.3.6.1.4.1.318.1.3.4.4':ApcMSP, '.1.3.6.1.4.1.850.1':TripplitePDU, '.1.3.6.1.4.1.318.1.3.4.6':ApcMS6, None:ApcMS} # First resolve type of APC apc_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(apc_type) == 1) and (apc_type[0][1] in agents_dir)): apc_type = [[None, None]] device = agents_dir[apc_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def apc_resolv_port_id(conn, options): global port_id, switch_id if device == None: apc_set_device(conn) # Now we resolv port_id/switch_id if (options["--plug"].isdigit()) and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.outlet_table_oid, 30) for x in table: if x[1].strip('"') == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: fail_usage("Can't find port with name %s!"%(options["--plug"])) def get_power_status(conn, options): if port_id == None: apc_resolv_port_id(conn, options) oid = ((device.has_switches) and device.status_oid%(switch_id, port_id) or device.status_oid%(port_id)) (oid, status) = conn.get(oid) return status == str(device.state_on) and "on" or "off" def set_power_status(conn, options): if port_id == None: apc_resolv_port_id(conn, options) oid = ((device.has_switches) and device.control_oid%(switch_id, port_id) or device.control_oid%(port_id)) conn.set(oid, (options["--action"] == "on" and device.turn_on or device.turn_off)) def get_outlets_status(conn, options): result = {} if device == None: apc_set_device(conn) res_ports = conn.walk(device.outlet_table_oid, 30) for x in res_ports: t = x[0].split('.') port_num = ((device.has_switches) and "%s:%s"%(t[len(t)-3], t[len(t)-1]) or "%s"%(t[len(t)-1])) port_name = x[1].strip('"') port_status = "" result[port_num] = (port_name, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "1" all_opt["community"]["default"] = "private" options = check_input(device_opt, process_input(device_opt)) ## Support for -n [switch]:[plug] notation that was used before if ("--plug" in options) and (-1 != options["--plug"].find(":")): (switch, plug) = options["--plug"].split(":", 1) if switch.isdigit() and plug.isdigit(): options["--switch"] = switch options["--plug"] = plug if "--switch" not in options: options["--switch"] = "1" docs = {} docs["shortdesc"] = "Fence agent for APC, Tripplite PDU over SNMP" docs["longdesc"] = "fence_apc_snmp is an I/O Fencing agent \ which can be used with the APC network power switch or Tripplite PDU devices.\ It logs into a device via SNMP and reboots a specified outlet. It supports \ SNMP v1, v2c, v3 with all combinations of authenticity/privacy settings." docs["vendorurl"] = "http://www.apc.com" docs["symlink"] = [("fence_tripplite_snmp", "Fence agent for Tripplife over SNMP")] show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/autodetect/fencing.py b/fence/agents/autodetect/fencing.py index 3976298c..ea21ace1 100644 --- a/fence/agents/autodetect/fencing.py +++ b/fence/agents/autodetect/fencing.py @@ -1,1393 +1,1393 @@ #!/usr/bin/python -tt import sys, getopt, time, os, uuid, pycurl, stat import pexpect, re, syslog import logging import subprocess import threading import shlex import exceptions import socket import textwrap import __main__ ## do not add code here. #BEGIN_VERSION_GENERATION RELEASE_VERSION="4.0.21.23-eaa13" BUILD_DATE="(built Wed Nov 4 13:28:43 CET 2015)" REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." #END_VERSION_GENERATION __all__ = ['atexit_handler', 'check_input', 'process_input', 'all_opt', 'show_docs', 'fence_login', 'fence_action', 'fence_logout'] EC_OK = 0 EC_GENERIC_ERROR = 1 EC_BAD_ARGS = 2 EC_LOGIN_DENIED = 3 EC_CONNECTION_LOST = 4 EC_TIMED_OUT = 5 EC_WAITING_ON = 6 EC_WAITING_OFF = 7 EC_STATUS = 8 EC_STATUS_HMC = 9 EC_PASSWORD_MISSING = 10 EC_INVALID_PRIVILEGES = 11 all_opt = { "help" : { "getopt" : "h", "longopt" : "help", "help" : "-h, --help Display this help and exit", "required" : "0", "shortdesc" : "Display help and exit", "order" : 54}, "version" : { "getopt" : "V", "longopt" : "version", "help" : "-V, --version Display version information and exit", "required" : "0", "shortdesc" : "Display version information and exit", "order" : 53}, "verbose" : { "getopt" : "v", "longopt" : "verbose", "help" : "-v, --verbose Verbose mode", "required" : "0", "order" : 51}, "debug" : { "getopt" : "D:", "longopt" : "debug-file", "help" : "-D, --debug-file=[debugfile] Debugging to output file", "required" : "0", "shortdesc" : "Write debug information to given file", "order" : 52}, "delay" : { "getopt" : ":", "longopt" : "delay", "help" : "--delay=[seconds] Wait X seconds before fencing is started", "required" : "0", "default" : "0", "order" : 200}, "agent" : { "getopt" : "", "help" : "", "order" : 1}, "web" : { "getopt" : "", "help" : "", "order" : 1}, "force_on" : { "getopt" : "", "help" : "", "order" : 1}, "action" : { "getopt" : "o:", "longopt" : "action", "help" : "-o, --action=[action] Action: status, reboot (default), off or on", "required" : "1", "shortdesc" : "Fencing action", "default" : "reboot", "order" : 1}, "fabric_fencing" : { "getopt" : "", "help" : "", "order" : 1}, "ipaddr" : { "getopt" : "a:", "longopt" : "ip", "help" : "-a, --ip=[ip] IP address or hostname of fencing device", "required" : "1", "order" : 1}, "ipport" : { "getopt" : "u:", "longopt" : "ipport", "help" : "-u, --ipport=[port] TCP/UDP port to use for connection", "required" : "0", "shortdesc" : "TCP/UDP port to use for connection with device", "order" : 1}, "login" : { "getopt" : "l:", "longopt" : "username", "help" : "-l, --username=[name] Login name", "required" : "?", "order" : 1}, "no_login" : { "getopt" : "", "help" : "", "order" : 1}, "no_password" : { "getopt" : "", "help" : "", "order" : 1}, "no_port" : { "getopt" : "", "help" : "", "order" : 1}, "no_status" : { "getopt" : "", "help" : "", "order" : 1}, "no_on" : { "getopt" : "", "help" : "", "order" : 1}, "no_off" : { "getopt" : "", "help" : "", "order" : 1}, "telnet" : { "getopt" : "", "help" : "", "order" : ""}, "passwd" : { "getopt" : "p:", "longopt" : "password", "help" : "-p, --password=[password] Login password or passphrase", "required" : "0", "order" : 1}, "passwd_script" : { "getopt" : "S:", "longopt" : "password-script", "help" : "-S, --password-script=[script] Script to run to retrieve password", "required" : "0", "order" : 1}, "identity_file" : { "getopt" : "k:", "longopt" : "identity-file", "help" : "-k, --identity-file=[filename] Identity file (private key) for SSH", "required" : "0", "order" : 1}, "cmd_prompt" : { "getopt" : "c:", "longopt" : "command-prompt", "help" : "-c, --command-prompt=[prompt] Force Python regex for command prompt", "required" : "0", "order" : 1}, "secure" : { "getopt" : "x", "longopt" : "ssh", "help" : "-x, --ssh Use SSH connection", "required" : "0", "order" : 1}, "ssh_options" : { "getopt" : ":", "longopt" : "ssh-options", "help" : "--ssh-options=[options] SSH options to use", "required" : "0", "order" : 1}, "ssl" : { "getopt" : "z", "longopt" : "ssl", "help" : "-z, --ssl Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "ssl_insecure" : { "getopt" : "", "longopt" : "ssl-insecure", "help" : "--ssl-insecure Use SSL connection without verifying certificate", "required" : "0", "order" : 1}, "ssl_secure" : { "getopt" : "", "longopt" : "ssl-secure", "help" : "--ssl-secure Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "notls" : { "getopt" : "t", "longopt" : "notls", "help" : "-t, --notls " "Disable TLS negotiation and force SSL3.0. " "This should only be used for devices that do not support TLS1.0 and up.", "required" : "0", "order" : 1}, "tls1.0" : { "getopt" : "", "longopt" : "tls1.0", "help" : "--tls1.0 " "Disable TLS negotiation and force TLS1.0. " "This should only be used for devices that do not support TLS1.1 and up.", "required" : "0", "order" : 1}, "port" : { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[id] " "Physical plug number on device, UUID or identification of machine", "required" : "1", "order" : 1}, "switch" : { "getopt" : "s:", "longopt" : "switch", "help" : "-s, --switch=[id] Physical switch number on device", "required" : "0", "order" : 1}, "exec" : { "getopt" : "e:", "longopt" : "exec", "help" : "-e, --exec=[command] Command to execute", "required" : "0", "order" : 1}, "vmware_type" : { "getopt" : "d:", "longopt" : "vmware_type", "help" : "-d, --vmware_type=[type] Type of VMware to connect", "required" : "0", "order" : 1}, "vmware_datacenter" : { "getopt" : "s:", "longopt" : "vmware-datacenter", "help" : "-s, --vmware-datacenter=[dc] VMWare datacenter filter", "required" : "0", "order" : 2}, "snmp_version" : { "getopt" : "d:", "longopt" : "snmp-version", "help" : "-d, --snmp-version=[version] Specifies SNMP version to use (1|2c|3)", "required" : "0", "shortdesc" : "Specifies SNMP version to use", "choices" : ["1", "2c", "3"], "order" : 1}, "community" : { "getopt" : "c:", "longopt" : "community", "help" : "-c, --community=[community] Set the community string", "required" : "0", "order" : 1}, "snmp_auth_prot" : { "getopt" : "b:", "longopt" : "snmp-auth-prot", "help" : "-b, --snmp-auth-prot=[prot] Set authentication protocol (MD5|SHA)", "required" : "0", "shortdesc" : "Set authentication protocol", "choices" : ["MD5", "SHA"], "order" : 1}, "snmp_sec_level" : { "getopt" : "E:", "longopt" : "snmp-sec-level", "help" : "-E, --snmp-sec-level=[level] " "Set security level (noAuthNoPriv|authNoPriv|authPriv)", "required" : "0", "shortdesc" : "Set security level", "choices" : ["noAuthNoPriv", "authNoPriv", "authPriv"], "order" : 1}, "snmp_priv_prot" : { "getopt" : "B:", "longopt" : "snmp-priv-prot", "help" : "-B, --snmp-priv-prot=[prot] Set privacy protocol (DES|AES)", "required" : "0", "shortdesc" : "Set privacy protocol", "choices" : ["DES", "AES"], "order" : 1}, "snmp_priv_passwd" : { "getopt" : "P:", "longopt" : "snmp-priv-passwd", "help" : "-P, --snmp-priv-passwd=[pass] Set privacy protocol password", "required" : "0", "order" : 1}, "snmp_priv_passwd_script" : { "getopt" : "R:", "longopt" : "snmp-priv-passwd-script", "help" : "-R, --snmp-priv-passwd-script Script to run to retrieve privacy password", "required" : "0", "order" : 1}, "inet4_only" : { "getopt" : "4", "longopt" : "inet4-only", "help" : "-4, --inet4-only Forces agent to use IPv4 addresses only", "required" : "0", "order" : 1}, "inet6_only" : { "getopt" : "6", "longopt" : "inet6-only", "help" : "-6, --inet6-only Forces agent to use IPv6 addresses only", "required" : "0", "order" : 1}, "separator" : { "getopt" : "C:", "longopt" : "separator", "help" : "-C, --separator=[char] Separator for CSV created by 'list' operation", "default" : ",", "required" : "0", "order" : 100}, "login_timeout" : { "getopt" : ":", "longopt" : "login-timeout", "help" : "--login-timeout=[seconds] Wait X seconds for cmd prompt after login", "default" : "5", "required" : "0", "order" : 200}, "shell_timeout" : { "getopt" : ":", "longopt" : "shell-timeout", "help" : "--shell-timeout=[seconds] Wait X seconds for cmd prompt after issuing command", "default" : "3", "required" : "0", "order" : 200}, "power_timeout" : { "getopt" : ":", "longopt" : "power-timeout", "help" : "--power-timeout=[seconds] Test X seconds for status change after ON/OFF", "default" : "20", "required" : "0", "order" : 200}, "power_wait" : { "getopt" : ":", "longopt" : "power-wait", "help" : "--power-wait=[seconds] Wait X seconds after issuing ON/OFF", "default" : "0", "required" : "0", "order" : 200}, "missing_as_off" : { "getopt" : "", "longopt" : "missing-as-off", "help" : "--missing-as-off Missing port returns OFF instead of failure", "required" : "0", "order" : 200}, "retry_on" : { "getopt" : ":", "longopt" : "retry-on", "help" : "--retry-on=[attempts] Count of attempts to retry power on", "default" : "1", "required" : "0", "order" : 201}, "session_url" : { "getopt" : "s:", "longopt" : "session-url", "help" : "-s, --session-url URL to connect to XenServer on", "required" : "1", "order" : 1}, "sudo" : { "getopt" : "", "longopt" : "use-sudo", "help" : "--use-sudo Use sudo (without password) when calling 3rd party software", "required" : "0", "order" : 205}, "method" : { "getopt" : "m:", "longopt" : "method", "help" : "-m, --method=[method] Method to fence (onoff|cycle) (Default: onoff)", "required" : "0", "shortdesc" : "Method to fence", "default" : "onoff", "choices" : ["onoff", "cycle"], "order" : 1}, "telnet_path" : { "getopt" : ":", "longopt" : "telnet-path", "help" : "--telnet-path=[path] Path to telnet binary", "required" : "0", "default" : "/usr/bin/telnet", "order": 300}, "ssh_path" : { "getopt" : ":", "longopt" : "ssh-path", "help" : "--ssh-path=[path] Path to ssh binary", "required" : "0", "default" : "/usr/bin/ssh", "order": 300}, "gnutlscli_path" : { "getopt" : ":", "longopt" : "gnutlscli-path", "help" : "--gnutlscli-path=[path] Path to gnutls-cli binary", "required" : "0", "default" : "/usr/bin/gnutls-cli", "order": 300}, "sudo_path" : { "getopt" : ":", "longopt" : "sudo-path", "help" : "--sudo-path=[path] Path to sudo binary", "required" : "0", "default" : "/usr/bin/sudo", "order": 300}, "snmpwalk_path" : { "getopt" : ":", "longopt" : "snmpwalk-path", "help" : "--snmpwalk-path=[path] Path to snmpwalk binary", "required" : "0", "default" : "/usr/bin/snmpwalk", "order" : 300}, "snmpset_path" : { "getopt" : ":", "longopt" : "snmpset-path", "help" : "--snmpset-path=[path] Path to snmpset binary", "required" : "0", "default" : "/usr/bin/snmpset", "order" : 300}, "snmpget_path" : { "getopt" : ":", "longopt" : "snmpget-path", "help" : "--snmpget-path=[path] Path to snmpget binary", "required" : "0", "default" : "/usr/bin/snmpget", "order" : 300}, "snmp": { "getopt" : "", "help" : "", "order" : 1}, "port_as_ip": { "getopt" : "", "longopt" : "port-as-ip", "help" : "--port-as-ip Make \"port/plug\" to be an alias to IP address", "required" : "0", "order" : 200}, "on_target": { "getopt" : "", "help" : "", "order" : 1}, "quiet": { "getopt" : "q", "longopt": "quiet", - "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug logging to syslog.", + "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.", "required" : "0", "order" : 50} } # options which are added automatically if 'key' is encountered ("default" is always added) DEPENDENCY_OPT = { "default" : ["help", "debug", "verbose", "version", "action", "agent", \ "power_timeout", "shell_timeout", "login_timeout", "power_wait", "retry_on", \ "delay", "quiet"], "passwd" : ["passwd_script"], "sudo" : ["sudo_path"], "secure" : ["identity_file", "ssh_options", "ssh_path"], "telnet" : ["telnet_path"], "ipaddr" : ["ipport", "inet4_only", "inet6_only"], "port" : ["separator"], "ssl" : ["ssl_secure", "ssl_insecure", "gnutlscli_path"], "snmp" : ["snmp_auth_prot", "snmp_sec_level", "snmp_priv_prot", \ "snmp_priv_passwd", "snmp_priv_passwd_script", "community", \ "snmpset_path", "snmpget_path", "snmpwalk_path"] } class fspawn(pexpect.spawn): def __init__(self, options, command): logging.info("Running command: %s", command) pexpect.spawn.__init__(self, command) self.opt = options def log_expect(self, pattern, timeout): result = self.expect(pattern, timeout) logging.debug("Received: %s", str(self.before) + str(self.after)) return result def send(self, message): logging.debug("Sent: %s", message) return pexpect.spawn.send(self, message) # send EOL according to what was detected in login process (telnet) def send_eol(self, message): return self.send(message + self.opt["eol"]) def atexit_handler(): try: sys.stdout.close() os.close(1) except IOError: logging.error("%s failed to close standard output\n", sys.argv[0]) sys.exit(EC_GENERIC_ERROR) def _add_dependency_options(options): ## Add also options which are available for every fence agent added_opt = [] for opt in options + ["default"]: if DEPENDENCY_OPT.has_key(opt): added_opt.extend([y for y in DEPENDENCY_OPT[opt] if options.count(y) == 0]) if not "port" in (options + added_opt) and \ not "nodename" in (options + added_opt) and \ "ipaddr" in (options + added_opt): added_opt.append("port_as_ip") all_opt["port"]["help"] = "-n, --plug=[ip] IP address or hostname of fencing device " \ "(together with --port-as-ip)" return added_opt def fail_usage(message="", stop=True): if len(message) > 0: logging.error("%s\n", message) if stop: logging.error("Please use '-h' for usage\n") sys.exit(EC_GENERIC_ERROR) def fail(error_code): message = { EC_LOGIN_DENIED : "Unable to connect/login to fencing device", EC_CONNECTION_LOST : "Connection lost", EC_TIMED_OUT : "Connection timed out", EC_WAITING_ON : "Failed: Timed out waiting to power ON", EC_WAITING_OFF : "Failed: Timed out waiting to power OFF", EC_STATUS : "Failed: Unable to obtain correct plug status or plug is not available", EC_STATUS_HMC : "Failed: Either unable to obtain correct plug status, " "partition is not available or incorrect HMC version used", EC_PASSWORD_MISSING : "Failed: You have to set login password", EC_INVALID_PRIVILEGES : "Failed: The user does not have the correct privileges to do the requested action." }[error_code] + "\n" logging.error("%s\n", message) sys.exit(EC_GENERIC_ERROR) def usage(avail_opt): print "Usage:" print "\t" + os.path.basename(sys.argv[0]) + " [options]" print "Options:" sorted_list = [(key, all_opt[key]) for key in avail_opt] sorted_list.sort(lambda x, y: cmp(x[1]["order"], y[1]["order"])) for key, value in sorted_list: if len(value["help"]) != 0: print " " + _join_wrap([value["help"]], first_indent=3) def metadata(avail_opt, docs): # avail_opt has to be unique, if there are duplicities then they should be removed sorted_list = [(key, all_opt[key]) for key in list(set(avail_opt))] sorted_list.sort(lambda x, y: cmp(x[0], y[0])) sorted_list.sort(lambda x, y: cmp(x[1]["order"], y[1]["order"])) print "" print "" for (symlink, desc) in docs.get("symlink", []): print "" print "" + docs["longdesc"] + "" print "" + docs["vendorurl"] + "" print "" for option, _ in sorted_list: if all_opt[option].has_key("help") and len(all_opt[option]["help"]) > 0: print "\t" default = "" if all_opt[option].has_key("default"): default = "default=\"" + _encode_html_entities(str(all_opt[option]["default"])) + "\" " mixed = all_opt[option]["help"] ## split it between option and help text res = re.compile(r"^(.*?--\S+)\s+", re.IGNORECASE | re.S).search(mixed) if None != res: mixed = res.group(1) mixed = _encode_html_entities(mixed) if not "shortdesc" in all_opt[option]: shortdesc = re.sub("\s\s+", " ", all_opt[option]["help"][31:]) else: shortdesc = all_opt[option]["shortdesc"] print "\t\t" if all_opt[option].has_key("choices"): print "\t\t" for choice in all_opt[option]["choices"]: print "\t\t\t" elif all_opt[option]["getopt"].count(":") > 0: print "\t\t" else: print "\t\t" print "\t\t" + shortdesc + "" print "\t" print "" print "" (available_actions, _) = _get_available_actions(avail_opt) if "on" in available_actions: available_actions.remove("on") on_target = ' on_target="1"' if avail_opt.count("on_target") else '' print "\t" % (on_target, avail_opt.count("fabric_fencing")) for action in available_actions: print "\t" % (action) print "" print "" def process_input(avail_opt): avail_opt.extend(_add_dependency_options(avail_opt)) # @todo: this should be put elsewhere? os.putenv("LANG", "C") os.putenv("LC_ALL", "C") if "port_as_ip" in avail_opt: avail_opt.append("port") if len(sys.argv) > 1: opt = _parse_input_cmdline(avail_opt) else: opt = _parse_input_stdin(avail_opt) if "--port-as-ip" in opt and "--plug" in opt: opt["--ip"] = opt["--plug"] return opt ## ## This function checks input and answers if we want to have same answers ## in each of the fencing agents. It looks for possible errors and run ## password script to set a correct password ###### def check_input(device_opt, opt, other_conditions = False): device_opt.extend(_add_dependency_options(device_opt)) options = dict(opt) options["device_opt"] = device_opt _update_metadata(options) options = _set_default_values(options) options["--action"] = options["--action"].lower() ## In special cases (show help, metadata or version) we don't need to check anything ##### # OCF compatibility if options["--action"] == "meta-data": options["--action"] = "metadata" if options["--action"] == "metadata" or any(options.has_key(k) for k in ("--help", "--version")): return options if options.has_key("--verbose"): logging.getLogger().setLevel(logging.DEBUG) ## add logging to syslog logging.getLogger().addHandler(SyslogLibHandler()) if not options.has_key("--quiet"): ## add logging to stderr logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) (acceptable_actions, _) = _get_available_actions(device_opt) if 1 == device_opt.count("fabric_fencing"): acceptable_actions.extend(["enable", "disable"]) if 0 == acceptable_actions.count(options["--action"]): fail_usage("Failed: Unrecognised action '" + options["--action"] + "'") ## Compatibility layer ##### if options["--action"] == "enable": options["--action"] = "on" if options["--action"] == "disable": options["--action"] = "off" if options["--action"] == "validate-all" and not other_conditions: _validate_input(options, False) sys.exit(EC_OK) else: _validate_input(options, True) if options.has_key("--debug-file"): try: debug_file = logging.FileHandler(options["--debug-file"]) debug_file.setLevel(logging.DEBUG) logging.getLogger().addHandler(debug_file) except IOError: logging.error("Unable to create file %s", options["--debug-file"]) fail_usage("Failed: Unable to create file " + options["--debug-file"]) if options.has_key("--snmp-priv-passwd-script"): options["--snmp-priv-passwd"] = os.popen(options["--snmp-priv-passwd-script"]).read().rstrip() if options.has_key("--password-script"): options["--password"] = os.popen(options["--password-script"]).read().rstrip() return options ## Obtain a power status from possibly more than one plug ## "on" is returned if at least one plug is ON ###### def get_multi_power_fn(connection, options, get_power_fn): status = "off" plugs = options["--plugs"] if options.has_key("--plugs") else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug plug_status = get_power_fn(connection, options) if plug_status != "off": status = plug_status return status def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts=1): plugs = options["--plugs"] if options.has_key("--plugs") else [""] for _ in range(retry_attempts): for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug set_power_fn(connection, options) time.sleep(int(options["--power-wait"])) for _ in xrange(int(options["--power-timeout"])): if get_multi_power_fn(connection, options, get_power_fn) != options["--action"]: time.sleep(1) else: return True return False def show_docs(options, docs=None): device_opt = options["device_opt"] if docs == None: docs = {} docs["shortdesc"] = "Fence agent" docs["longdesc"] = "" if options.has_key("--help"): usage(device_opt) sys.exit(0) if options.get("--action", "") == "metadata": if "port_as_ip" in device_opt: device_opt.remove("separator") metadata(device_opt, docs) sys.exit(0) if options.has_key("--version"): print __main__.RELEASE_VERSION, __main__.BUILD_DATE print __main__.REDHAT_COPYRIGHT sys.exit(0) def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None): result = 0 try: if options.has_key("--plug"): options["--plugs"] = options["--plug"].split(",") ## Process options that manipulate fencing device ##### if (options["--action"] in ["list", "list-status"]) or \ ((options["--action"] == "monitor") and 1 == options["device_opt"].count("port") and \ 0 == options["device_opt"].count("port_as_ip")): if 0 == options["device_opt"].count("port"): print "N/A" elif get_outlet_list == None: ## @todo: exception? ## This is just temporal solution, we will remove default value ## None as soon as all existing agent will support this operation print "NOTICE: List option is not working on this device yet" else: options["--original-action"] = options["--action"] options["--action"] = "list" outlets = get_outlet_list(connection, options) options["--action"] = options["--original-action"] del options["--original-action"] ## keys can be numbers (port numbers) or strings (names of VM, UUID) for outlet_id in outlets.keys(): (alias, status) = outlets[outlet_id] if status is None or (not status.upper() in ["ON", "OFF"]): status = "UNKNOWN" status = status.upper() if options["--action"] == "list": print outlet_id + options["--separator"] + alias elif options["--action"] == "list-status": print outlet_id + options["--separator"] + alias + options["--separator"] + status return if options["--action"] == "monitor" and not "port" in options["device_opt"] and "no_status" in options["device_opt"]: # Unable to do standard monitoring because 'status' action is not available return 0 status = None if not "no_status" in options["device_opt"]: status = get_multi_power_fn(connection, options, get_power_fn) if status != "on" and status != "off": fail(EC_STATUS) if options["--action"] == status: if not (status == "on" and "force_on" in options["device_opt"]): print "Success: Already %s" % (status.upper()) return 0 if options["--action"] == "on": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, 1 + int(options["--retry-on"])): print "Success: Powered ON" else: fail(EC_WAITING_ON) elif options["--action"] == "off": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn): print "Success: Powered OFF" else: fail(EC_WAITING_OFF) elif options["--action"] == "reboot": power_on = False if options.get("--method", "").lower() == "cycle" and reboot_cycle_fn is not None: for _ in range(1, 1 + int(options["--retry-on"])): if reboot_cycle_fn(connection, options): power_on = True break if not power_on: fail(EC_TIMED_OUT) else: if status != "off": options["--action"] = "off" if not set_multi_power_fn(connection, options, set_power_fn, get_power_fn): fail(EC_WAITING_OFF) options["--action"] = "on" try: power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, int(options["--retry-on"])) except Exception, ex: # an error occured during power ON phase in reboot # fence action was completed succesfully even in that case logging.warning("%s", str(ex)) if power_on == False: # this should not fail as node was fenced succesfully logging.error('Timed out waiting to power ON\n') print "Success: Rebooted" elif options["--action"] == "status": print "Status: " + status.upper() if status.upper() == "OFF": result = 2 elif options["--action"] == "monitor": pass except pexpect.EOF: fail(EC_CONNECTION_LOST) except pexpect.TIMEOUT: fail(EC_TIMED_OUT) except pycurl.error, ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) except socket.timeout, ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) return result def fence_login(options, re_login_string=r"(login\s*: )|((?!Last )Login Name: )|(username: )|(User Name :)"): run_delay(options) if not options.has_key("eol"): options["eol"] = "\r\n" if options.has_key("--command-prompt") and type(options["--command-prompt"]) is not list: options["--command-prompt"] = [options["--command-prompt"]] try: if options.has_key("--ssl"): conn = _open_ssl_connection(options) elif options.has_key("--ssh") and not options.has_key("--identity-file"): conn = _login_ssh_with_password(options, re_login_string) elif options.has_key("--ssh") and options.has_key("--identity-file"): conn = _login_ssh_with_identity_file(options) else: conn = _login_telnet(options, re_login_string) except pexpect.EOF, exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT, exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) return conn def is_executable(path): if os.path.exists(path): stats = os.stat(path) if stat.S_ISREG(stats.st_mode) and os.access(path, os.X_OK): return True return False def run_command(options, command, timeout=None, env=None, log_command=None): if timeout is None and "--power-timeout" in options: timeout = options["--power-timeout"] if timeout is not None: timeout = float(timeout) logging.info("Executing: %s\n", log_command or command) try: process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) except OSError: fail_usage("Unable to run %s\n" % command) thread = threading.Thread(target=process.wait) thread.start() thread.join(timeout) if thread.is_alive(): process.kill() fail(EC_TIMED_OUT) status = process.wait() (pipe_stdout, pipe_stderr) = process.communicate() process.stdout.close() process.stderr.close() logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr)) return (status, pipe_stdout, pipe_stderr) def run_delay(options): ## Delay is important for two-node clusters fencing but we do not need to delay 'status' operations if options["--action"] in ["off", "reboot"]: logging.info("Delay %s second(s) before logging in to the fence device", options["--delay"]) time.sleep(int(options["--delay"])) def fence_logout(conn, logout_string, sleep=0): # Logout is not required part of fencing but we should attempt to do it properly # In some cases our 'exit' command is faster and we can not close connection as it # was already closed by fencing device try: conn.send_eol(logout_string) time.sleep(sleep) conn.close() except exceptions.OSError: pass except pexpect.ExceptionPexpect: pass # Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is # in format a.b.c.d...z and returned dict has key only z def array_to_dict(array): return dict([[x[0].split(".")[-1], x[1]] for x in array]) ## Own logger handler that uses old-style syslog handler as otherwise everything is sourced ## from /dev/syslog class SyslogLibHandler(logging.StreamHandler): """ A handler class that correctly push messages into syslog """ def emit(self, record): syslog_level = { logging.CRITICAL:syslog.LOG_CRIT, logging.ERROR:syslog.LOG_ERR, logging.WARNING:syslog.LOG_WARNING, logging.INFO:syslog.LOG_INFO, logging.DEBUG:syslog.LOG_DEBUG, logging.NOTSET:syslog.LOG_DEBUG, }[record.levelno] msg = self.format(record) # syslos.syslog can not have 0x00 character inside or exception is thrown syslog.syslog(syslog_level, msg.replace("\x00", "\n")) return def _open_ssl_connection(options): gnutls_opts = "" ssl_opts = "" if options.has_key("--notls"): gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:-VERS-TLS1.0:+VERS-SSL3.0\"" elif options.has_key("--tls1.0"): gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:+VERS-TLS1.0:%LATEST_RECORD_VERSION\"" # --ssl is same as the --ssl-secure; it means we want to verify certificate in these cases if options.has_key("--ssl-insecure"): ssl_opts = "--insecure" command = '%s %s %s --crlf -p %s %s' % \ (options["--gnutlscli-path"], gnutls_opts, ssl_opts, options["--ipport"], options["--ip"]) try: conn = fspawn(options, command) except pexpect.ExceptionPexpect, ex: logging.error("%s\n", str(ex)) sys.exit(EC_GENERIC_ERROR) return conn def _login_ssh_with_identity_file(options): if options.has_key("--inet6-only"): force_ipvx = "-6 " elif options.has_key("--inet4-only"): force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -i %s -p %s' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], \ options["--identity-file"], options["--ipport"]) if options.has_key("--ssh-options"): command += ' ' + options["--ssh-options"] conn = fspawn(options, command) result = conn.log_expect(["Enter passphrase for key '" + options["--identity-file"] + "':", \ "Are you sure you want to continue connecting (yes/no)?"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") result = conn.log_expect( ["Enter passphrase for key '" + options["--identity-file"]+"':"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 0: if options.has_key("--password"): conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) else: fail_usage("Failed: You have to enter passphrase (-p) for identity file") return conn def _login_telnet(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.log_expect(re_login, int(options["--login-timeout"])) conn.send_eol(options["--username"]) ## automatically change end of line separator screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) if re_login.search(screen) != None: options["eol"] = "\n" conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) elif re_pass.search(screen) == None: conn.log_expect(re_pass, int(options["--shell-timeout"])) try: conn.send_eol(options["--password"]) valid_password = conn.log_expect([re_login] + \ options["--command-prompt"], int(options["--shell-timeout"])) if valid_password == 0: ## password is invalid or we have to change EOL separator options["eol"] = "\r" conn.send_eol("") screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) ## after sending EOL the fence device can either show 'Login' or 'Password' if re_login.search(conn.after + screen) != None: conn.send_eol("") conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) conn.send_eol(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) except KeyError: fail(EC_PASSWORD_MISSING) return conn def _login_ssh_with_password(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) if options.has_key("--inet6-only"): force_ipvx = "-6 " elif options.has_key("--inet4-only"): force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -p %s -o PubkeyAuthentication=no' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], options["--ipport"]) if options.has_key("--ssh-options"): command += ' ' + options["--ssh-options"] conn = fspawn(options, command) if options.has_key("telnet_over_ssh"): # This is for stupid ssh servers (like ALOM) which behave more like telnet # (ignore name and display login prompt) result = conn.log_expect( \ [re_login, "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") # Host identity confirm conn.log_expect(re_login, int(options["--login-timeout"])) conn.sendline(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) else: result = conn.log_expect( \ ["ssword:", "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") conn.log_expect("ssword:", int(options["--login-timeout"])) conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) return conn # # To update metadata, we change values in all_opt def _update_metadata(options): device_opt = options["device_opt"] if device_opt.count("login") and device_opt.count("no_login") == 0: all_opt["login"]["required"] = "1" else: all_opt["login"]["required"] = "0" if device_opt.count("port_as_ip"): all_opt["ipaddr"]["required"] = "0" all_opt["port"]["required"] = "0" (available_actions, default_value) = _get_available_actions(device_opt) all_opt["action"]["default"] = default_value actions_with_default = \ [x if not x == all_opt["action"]["default"] else x + " (default)" for x in available_actions] all_opt["action"]["help"] = \ "-o, --action=[action] Action: %s" % (_join_wrap(actions_with_default, last_separator=" or ")) if device_opt.count("ipport"): default_value = None default_string = None if all_opt["ipport"].has_key("default"): default_value = all_opt["ipport"]["default"] elif device_opt.count("web") and device_opt.count("ssl"): default_value = "80" default_string = "(default 80, 443 if --ssl option is used)" elif device_opt.count("telnet") and device_opt.count("secure"): default_value = "23" default_string = "(default 23, 22 if --ssh option is used)" else: tcp_ports = {"community" : "161", "secure" : "22", "telnet" : "23", "web" : "80", "ssl" : "443"} # all cases where next command returns multiple results are covered by previous blocks protocol = [x for x in ["community", "secure", "ssl", "web", "telnet"] if device_opt.count(x)][0] default_value = tcp_ports[protocol] if default_string is None: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use (default %s)" % \ (default_value) else: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use\n" + " "*40 + default_string def _set_default_values(options): if "ipport" in options["device_opt"]: if not "--ipport" in options: if "default" in all_opt["ipport"]: options["--ipport"] = all_opt["ipport"]["default"] elif "community" in options["device_opt"]: options["--ipport"] = "161" elif "--ssh" in options or all_opt["secure"].get("default", "0") == "1": options["--ipport"] = "22" elif "--ssl" in options or all_opt["ssl"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-secure" in options or all_opt["ssl_secure"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-insecure" in options or all_opt["ssl_insecure"].get("default", "0") == "1": options["--ipport"] = "443" elif "web" in options["device_opt"]: options["--ipport"] = "80" elif "telnet" in options["device_opt"]: options["--ipport"] = "23" if "--ipport" in options: all_opt["ipport"]["default"] = options["--ipport"] for opt in options["device_opt"]: if all_opt[opt].has_key("default") and not opt == "ipport": getopt_long = "--" + all_opt[opt]["longopt"] if not options.has_key(getopt_long): options[getopt_long] = all_opt[opt]["default"] return options # stop = True/False : exit fence agent when problem is encountered def _validate_input(options, stop = True): device_opt = options["device_opt"] valid_input = True if not options.has_key("--username") and \ device_opt.count("login") and (device_opt.count("no_login") == 0): valid_input = False fail_usage("Failed: You have to set login name", stop) if device_opt.count("ipaddr") and not options.has_key("--ip") and not options.has_key("--managed"): valid_input = False fail_usage("Failed: You have to enter fence address", stop) if device_opt.count("no_password") == 0: if 0 == device_opt.count("identity_file"): if not (options.has_key("--password") or options.has_key("--password-script")): valid_input = False fail_usage("Failed: You have to enter password or password script", stop) else: if not (options.has_key("--password") or \ options.has_key("--password-script") or options.has_key("--identity-file")): valid_input = False fail_usage("Failed: You have to enter password, password script or identity file", stop) if not options.has_key("--ssh") and options.has_key("--identity-file"): valid_input = False fail_usage("Failed: You have to use identity file together with ssh connection (-x)", stop) if options.has_key("--identity-file") and not os.path.isfile(options["--identity-file"]): valid_input = False fail_usage("Failed: Identity file " + options["--identity-file"] + " does not exist", stop) if (0 == ["list", "list-status", "monitor"].count(options["--action"])) and \ not options.has_key("--plug") and device_opt.count("port") and \ device_opt.count("no_port") == 0 and not device_opt.count("port_as_ip"): valid_input = False fail_usage("Failed: You have to enter plug number or machine identification", stop) if options.has_key("--plug") and len(options["--plug"].split(",")) > 1 and \ options.has_key("--method") and options["--method"] == "cycle": valid_input = False fail_usage("Failed: Cannot use --method cycle for more than 1 plug", stop) for failed_opt in _get_opts_with_invalid_choices(options): valid_input = False fail_usage("Failed: You have to enter a valid choice for %s from the valid values: %s" % \ ("--" + all_opt[failed_opt]["longopt"], str(all_opt[failed_opt]["choices"])), stop) return valid_input def _encode_html_entities(text): return text.replace("&", "&").replace('"', """).replace('<', "<"). \ replace('>', ">").replace("'", "'") def _prepare_getopt_args(options): getopt_string = "" longopt_list = [] for k in options: if all_opt.has_key(k) and all_opt[k]["getopt"] != ":": # getopt == ":" means that opt is without short getopt, but has value getopt_string += all_opt[k]["getopt"] elif not all_opt.has_key(k): fail_usage("Parse error: unknown option '"+k+"'") if all_opt.has_key(k) and all_opt[k].has_key("longopt"): if all_opt[k]["getopt"].endswith(":"): longopt_list.append(all_opt[k]["longopt"] + "=") else: longopt_list.append(all_opt[k]["longopt"]) return (getopt_string, longopt_list) def _parse_input_stdin(avail_opt): opt = {} name = "" for line in sys.stdin.readlines(): line = line.strip() if (line.startswith("#")) or (len(line) == 0): continue (name, value) = (line + "=").split("=", 1) value = value[:-1] if avail_opt.count(name) == 0 and name in ["nodename"]: continue elif avail_opt.count(name) == 0: logging.warning("Parse error: Ignoring unknown option '%s'\n", line) continue if all_opt[name]["getopt"].endswith(":"): opt["--"+all_opt[name]["longopt"].rstrip(":")] = value elif value.lower() in ["1", "yes", "on", "true"]: opt["--"+all_opt[name]["longopt"]] = "1" else: logging.warning("Parse error: Ignoring option '%s' because it does not have value\n", name) return opt def _parse_input_cmdline(avail_opt): filtered_opts = {} _verify_unique_getopt(avail_opt) (getopt_string, longopt_list) = _prepare_getopt_args(avail_opt) try: (entered_opt, left_arg) = getopt.gnu_getopt(sys.argv[1:], getopt_string, longopt_list) if len(left_arg) > 0: logging.warning("Unused arguments on command line: %s" % (str(left_arg))) except getopt.GetoptError, error: fail_usage("Parse error: " + error.msg) for opt in avail_opt: filtered_opts.update({opt : all_opt[opt]}) # Short and long getopt names are changed to consistent "--" + long name (e.g. --username) long_opts = {} for arg_name in dict(entered_opt).keys(): all_key = [key for (key, value) in filtered_opts.items() \ if "--" + value.get("longopt", "") == arg_name or "-" + value.get("getopt", "").rstrip(":") == arg_name][0] long_opts["--" + filtered_opts[all_key]["longopt"]] = dict(entered_opt)[arg_name] # This test is specific because it does not apply to input on stdin if "port_as_ip" in avail_opt and not "--port-as-ip" in long_opts and "--plug" in long_opts: fail_usage("Parser error: option -n/--plug is not recognized") return long_opts # for ["John", "Mary", "Eli"] returns "John, Mary and Eli" def _join2(words, normal_separator=", ", last_separator=" and "): if len(words) <= 1: return "".join(words) else: return last_separator.join([normal_separator.join(words[:-1]), words[-1]]) def _join_wrap(words, normal_separator=", ", last_separator=" and ", first_indent=42): x = _join2(words, normal_separator, last_separator) wrapper = textwrap.TextWrapper() wrapper.initial_indent = " "*first_indent wrapper.subsequent_indent = " "*40 wrapper.width = 85 wrapper.break_on_hyphens = False wrapper.break_long_words = False wrapped_text = "" for line in wrapper.wrap(x): wrapped_text += line + "\n" return wrapped_text.lstrip().rstrip("\n") def _get_opts_with_invalid_choices(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if all_opt[opt].has_key("choices"): longopt = "--" + all_opt[opt]["longopt"] possible_values_upper = [y.upper() for y in all_opt[opt]["choices"]] if options.has_key(longopt): options[longopt] = options[longopt].upper() if not options["--" + all_opt[opt]["longopt"]] in possible_values_upper: options_failed.append(opt) return options_failed def _verify_unique_getopt(avail_opt): used_getopt = set() for opt in avail_opt: getopt_value = all_opt[opt].get("getopt", "").rstrip(":") if getopt_value and getopt_value in used_getopt: fail_usage("Short getopt for %s (-%s) is not unique" % (opt, getopt_value)) else: used_getopt.add(getopt_value) def _get_available_actions(device_opt): available_actions = ["on", "off", "reboot", "status", "list", "list-status", \ "monitor", "metadata", "validate-all"] default_value = "reboot" if device_opt.count("fabric_fencing"): available_actions.remove("reboot") default_value = "off" if device_opt.count("no_status"): available_actions.remove("status") if device_opt.count("no_on"): available_actions.remove("on") if device_opt.count("no_off"): available_actions.remove("off") if not device_opt.count("separator"): available_actions.remove("list") available_actions.remove("list-status") return (available_actions, default_value) diff --git a/fence/agents/aws/fence_aws.py b/fence/agents/aws/fence_aws.py new file mode 100644 index 00000000..647b66fc --- /dev/null +++ b/fence/agents/aws/fence_aws.py @@ -0,0 +1,126 @@ +#!@PYTHON@ -tt + +import sys, re +import logging +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay + +import boto3 +from botocore.exceptions import ClientError, EndpointConnectionError, NoRegionError + +def get_nodes_list(conn, options): + result = {} + try: + for instance in conn.instances.all(): + result[instance.id] = ("", None) + except ClientError: + fail_usage("Failed: Incorrect Access Key or Secret Key.") + except EndpointConnectionError: + fail_usage("Failed: Incorrect Region.") + + return result + +def get_power_status(conn, options): + try: + instance = conn.instances.filter(Filters=[{"Name": "instance-id", "Values": [options["--plug"]]}]) + state = list(instance)[0].state["Name"] + if state == "running": + return "on" + elif state == "stopped": + return "off" + else: + return "unknown" + + except ClientError: + fail_usage("Failed: Incorrect Access Key or Secret Key.") + except EndpointConnectionError: + fail_usage("Failed: Incorrect Region.") + except IndexError: + return "fail" + +def set_power_status(conn, options): + if (options["--action"]=="off"): + conn.instances.filter(InstanceIds=[options["--plug"]]).stop(Force=True) + elif (options["--action"]=="on"): + conn.instances.filter(InstanceIds=[options["--plug"]]).start() + + +def define_new_opts(): + all_opt["region"] = { + "getopt" : "r:", + "longopt" : "region", + "help" : "-r, --region=[name] Region, e.g. us-east-1", + "shortdesc" : "Region.", + "required" : "0", + "order" : 2 + } + all_opt["access_key"] = { + "getopt" : "a:", + "longopt" : "access-key", + "help" : "-a, --access-key=[name] Access Key", + "shortdesc" : "Access Key.", + "required" : "0", + "order" : 3 + } + all_opt["secret_key"] = { + "getopt" : "s:", + "longopt" : "secret-key", + "help" : "-s, --secret-key=[name] Secret Key", + "shortdesc" : "Secret Key.", + "required" : "0", + "order" : 4 + } + +# Main agent method +def main(): + conn = None + + device_opt = ["port", "no_password", "region", "access_key", "secret_key"] + + atexit.register(atexit_handler) + + define_new_opts() + + all_opt["power_timeout"]["default"] = "60" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for AWS (Amazon Web Services)" + docs["longdesc"] = "fence_aws is an I/O Fencing agent for AWS (Amazon Web\ +Services). It uses the boto3 library to connect to AWS.\ +\n.P\n\ +boto3 can be configured with AWS CLI or by creating ~/.aws/credentials.\n\ +For instructions see: https://boto3.readthedocs.io/en/latest/guide/quickstart.html#configuration" + docs["vendorurl"] = "http://www.amazon.com" + show_docs(options, docs) + + run_delay(options) + + if "--region" in options and "--access-key" in options and "--secret-key" in options: + region = options["--region"] + access_key = options["--access-key"] + secret_key = options["--secret-key"] + try: + conn = boto3.resource('ec2', region_name=region, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key) + except: + fail_usage("Failed: Unable to connect to AWS. Check your configuration.") + else: + # If setup with "aws configure" or manually in + # ~/.aws/credentials + try: + conn = boto3.resource('ec2') + except: + # If any of region/access/secret are missing + fail_usage("Failed: Unable to connect to AWS. Check your configuration.") + + # Operate the fencing device + result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/fence/agents/azure_arm/fence_azure_arm.py b/fence/agents/azure_arm/fence_azure_arm.py index 72caddfc..e4960035 100644 --- a/fence/agents/azure_arm/fence_azure_arm.py +++ b/fence/agents/azure_arm/fence_azure_arm.py @@ -1,131 +1,143 @@ #!@PYTHON@ -tt import sys, re, pexpect import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -BUILD_DATE="" -REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." -#END_VERSION_GENERATION - def get_nodes_list(compute_client, options): result = {} if compute_client: rgName = options["--resourceGroup"] vms = compute_client.virtual_machines.list(rgName) - for vm in vms: - result[vm.name] = ("", None) + try: + for vm in vms: + result[vm.name] = ("", None) + except Exception as e: + fail_usage("Failed: %s" % e) return result def get_power_status(compute_client, options): logging.info("getting power status for VM " + options["--plug"]) if compute_client: rgName = options["--resourceGroup"] vmName = options["--plug"] powerState = "unknown" - vmStatus = compute_client.virtual_machines.get(rgName, vmName, "instanceView") + try: + vmStatus = compute_client.virtual_machines.get(rgName, vmName, "instanceView") + except Exception as e: + fail_usage("Failed: %s" % e) for status in vmStatus.instance_view.statuses: if status.code.startswith("PowerState"): powerState = status.code break logging.info("Found power state of VM: " + powerState) if powerState == "PowerState/running": return "on" return "off" def set_power_status(compute_client, options): logging.info("setting power status for VM " + options["--plug"] + " to " + options["--action"]) if compute_client: rgName = options["--resourceGroup"] vmName = options["--plug"] if (options["--action"]=="off"): logging.info("Deallocating " + vmName + "in resource group " + rgName) compute_client.virtual_machines.deallocate(rgName, vmName) elif (options["--action"]=="on"): logging.info("Starting " + vmName + "in resource group " + rgName) compute_client.virtual_machines.start(rgName, vmName) def define_new_opts(): all_opt["resourceGroup"] = { "getopt" : ":", "longopt" : "resourceGroup", - "help" : "--resourceGroup=[name] Name of the resource group", + "help" : "--resourceGroup=[name] Name of the resource group", "shortdesc" : "Name of resource group.", "required" : "1", "order" : 2 } all_opt["tenantId"] = { "getopt" : ":", "longopt" : "tenantId", - "help" : "--tenantId=[name] Id of the Azure Active Directory tenant", + "help" : "--tenantId=[name] Id of the Azure Active Directory tenant", "shortdesc" : "Id of Azure Active Directory tenant.", "required" : "1", "order" : 3 } all_opt["subscriptionId"] = { "getopt" : ":", "longopt" : "subscriptionId", - "help" : "--subscriptionId=[name] Id of the Azure subscription", + "help" : "--subscriptionId=[name] Id of the Azure subscription", "shortdesc" : "Id of the Azure subscription.", "required" : "1", "order" : 4 } # Main agent method def main(): compute_client = None device_opt = ["resourceGroup", "login", "passwd", "tenantId", "subscriptionId","port"] atexit.register(atexit_handler) define_new_opts() + + all_opt["power_timeout"]["default"] = "150" + + all_opt["login"]["help"] = "-l, --username=[appid] Application ID" + all_opt["passwd"]["help"] = "-p, --password=[authkey] Authentication key" + options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Azure Resource Manager" - docs["longdesc"] = "Used to deallocate virtual machines and to report power state of virtual machines running in Azure" + docs["longdesc"] = "Used to deallocate virtual machines and to report power state of virtual machines running in Azure. It uses Azure SDK for Python to connect to Azure.\ +\n.P\n\ +For instructions to setup credentials see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal\ +\n.P\n\ +Username and password are application ID and authentication key from \"App registrations\"." docs["vendorurl"] = "http://www.microsoft.com" show_docs(options, docs) run_delay(options) try: from azure.common.credentials import ServicePrincipalCredentials from azure.mgmt.compute import ComputeManagementClient tenantid = options["--tenantId"] servicePrincipal = options["--username"] spPassword = options["--password"] subscriptionId = options["--subscriptionId"] credentials = ServicePrincipalCredentials( client_id = servicePrincipal, secret = spPassword, tenant = tenantid ) compute_client = ComputeManagementClient( credentials, subscriptionId ) except ImportError: - fail_usage("Azure Resource Manager Pyhton SDK not found or not accessible") + fail_usage("Azure Resource Manager Python SDK not found or not accessible") + except Exception as e: + fail_usage("Failed: %s" % re.sub("^, ", "", str(e))) # Operate the fencing device result = fence_action(compute_client, options, set_power_status, get_power_status, get_nodes_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/bladecenter/fence_bladecenter.py b/fence/agents/bladecenter/fence_bladecenter.py index b45f3157..d670367f 100644 --- a/fence/agents/bladecenter/fence_bladecenter.py +++ b/fence/agents/bladecenter/fence_bladecenter.py @@ -1,111 +1,105 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Model Firmware ## +--------------------+---------------------------+ ## (1) Main application BRET85K, rev 16 ## Boot ROM BRBR67D, rev 16 ## Remote Control BRRG67D, rev 16 ## ##### import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_GENERIC_ERROR -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New Bladecenter Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="March, 2008" -#END_VERSION_GENERATION - def get_power_status(conn, options): node_cmd = r"system:blade\[" + options["--plug"] + r"\]>" conn.send_eol("env -T system:blade[" + options["--plug"] + "]") i = conn.log_expect([node_cmd, "system>"], int(options["--shell-timeout"])) if i == 1: ## Given blade number does not exist if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) conn.send_eol("power -state") conn.log_expect(node_cmd, int(options["--shell-timeout"])) status = conn.before.splitlines()[-1] conn.send_eol("env -T system") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) return status.lower().strip() def set_power_status(conn, options): node_cmd = r"system:blade\[" + options["--plug"] + r"\]>" conn.send_eol("env -T system:blade[" + options["--plug"] + "]") i = conn.log_expect([node_cmd, "system>"], int(options["--shell-timeout"])) if i == 1: ## Given blade number does not exist if "--missing-as-off" in options: return else: fail(EC_GENERIC_ERROR) conn.send_eol("power -"+options["--action"]) conn.log_expect(node_cmd, int(options["--shell-timeout"])) conn.send_eol("env -T system") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_blades_list(conn, options): outlets = {} node_cmd = "system>" conn.send_eol("env -T system") conn.log_expect(node_cmd, int(options["--shell-timeout"])) conn.send_eol("list -l 2") conn.log_expect(node_cmd, int(options["--shell-timeout"])) lines = conn.before.split("\r\n") filter_re = re.compile(r"^\s*blade\[(\d+)\]\s+(.*?)\s*$") for blade_line in lines: res = filter_re.search(blade_line) if res != None: outlets[res.group(1)] = (res.group(2), "") return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "missing_as_off", "telnet"] atexit.register(atexit_handler) all_opt["power_wait"]["default"] = "10" all_opt["cmd_prompt"]["default"] = ["system>"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM BladeCenter" docs["longdesc"] = "fence_bladecenter is an I/O Fencing agent \ which can be used with IBM Bladecenters with recent enough firmware that \ includes telnet support. It logs into a Brocade chasis via telnet or ssh \ and uses the command line interface to power on and off blades." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options, "(username\s*:\s*)") result = fence_action(conn, options, set_power_status, get_power_status, get_blades_list) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/brocade/fence_brocade.py b/fence/agents/brocade/fence_brocade.py index 4cf039a9..9f413db6 100644 --- a/fence/agents/brocade/fence_brocade.py +++ b/fence/agents/brocade/fence_brocade.py @@ -1,78 +1,72 @@ #!@PYTHON@ -tt import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New Brocade Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="March, 20013" -#END_VERSION_GENERATION - def set_power_status(conn, options): action = { 'on' : "portCfgPersistentEnable", 'off': "portCfgPersistentDisable" }[options["--action"]] conn.send_eol(action + " " + options["--plug"]) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def get_power_status(conn, options): line_re = re.compile(r'=========', re.IGNORECASE) outlets = {} in_index = False conn.send_eol("switchshow") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) for line in str(conn.before).split("\n"): if line_re.search(line): in_index = True elif in_index and line.lstrip()[0].isdigit(): tokens = line.lstrip().split() status = "off" if len(tokens) > 7 and tokens[7] == "Disabled" else "on" outlets[tokens[0]] = ("", status) if ["list", "monitor"].count(options["--action"]) == 0: (_, status) = outlets[options["--plug"]] return status else: return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "fabric_fencing", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["> "] options = check_input(device_opt, process_input(device_opt)) options["eol"] = "\n" docs = {} docs["shortdesc"] = "Fence agent for HP Brocade over telnet/ssh" docs["longdesc"] = "fence_brocade is an I/O Fencing agent which can be used with Brocade FC switches. \ It logs into a Brocade switch via telnet and disables a specified port. Disabling the port which a machine is \ connected to effectively fences that machine. Lengthy telnet connections to the switch should be avoided while \ a GFS cluster is running because the connection will block any necessary fencing actions. \ \ After a fence operation has taken place the fenced machine can no longer connect to the Brocade FC switch. \ When the fenced machine is ready to be brought back into the GFS cluster (after reboot) the port on the Brocade \ FC switch needs to be enabled. This can be done by running fence_brocade and specifying the enable action" docs["vendorurl"] = "http://www.brocade.com" show_docs(options, docs) ## ## Operate the fencing device #### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/cisco_mds/fence_cisco_mds.py b/fence/agents/cisco_mds/fence_cisco_mds.py index cb3c9605..fbb876a9 100644 --- a/fence/agents/cisco_mds/fence_cisco_mds.py +++ b/fence/agents/cisco_mds/fence_cisco_mds.py @@ -1,100 +1,94 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - Cisco MDS UROS 9134 FC (1 Slot) Chassis ("1/2/4 10 Gbps FC/Supervisor-2") Motorola, e500v2 # with BIOS 1.0.16, kickstart 4.1(1c), system 4.1(1c) # - Cisco MDS 9124 (1 Slot) Chassis ("1/2/4 Gbps FC/Supervisor-2") Motorola, e500 # with BIOS 1.0.16, kickstart 4.1(1c), system 4.1(1c) import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, array_to_dict from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Cisco MDS 9xxx SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # Cisco admin status PORT_ADMIN_STATUS_OID = ".1.3.6.1.2.1.75.1.2.2.1.1" # IF-MIB trees for alias, status and port ALIASES_OID = ".1.3.6.1.2.1.31.1.1.1.18" PORTS_OID = ".1.3.6.1.2.1.2.2.1.2" ### GLOBAL VARIABLES ### # OID converted from fc port name (fc(x)/(y)) PORT_OID = "" ### FUNCTIONS ### # Convert cisco port name (fc(x)/(y)) to OID def cisco_port2oid(port): port = port.lower() nums = re.match(r'^fc(\d+)/(\d+)$', port) if nums and len(nums.groups()) == 2: return "%s.%d.%d"% (PORT_ADMIN_STATUS_OID, int(nums.group(1))+21, int(nums.group(2))-1) else: fail_usage("Mangled port number: %s"%(port)) def get_power_status(conn, options): (_, status) = conn.get(PORT_OID) return status == "1" and "on" or "off" def set_power_status(conn, options): conn.set(PORT_OID, (options["--action"] == "on" and 1 or 2)) def get_outlets_status(conn, options): result = {} res_fc = conn.walk(PORTS_OID, 30) res_aliases = array_to_dict(conn.walk(ALIASES_OID, 30)) fc_re = re.compile(r'^"fc\d+/\d+"$') for x in res_fc: if fc_re.match(x[1]): port_num = x[0].split('.')[-1] port_name = x[1].strip('"') port_alias = (port_num in res_aliases and res_aliases[port_num].strip('"') or "") port_status = "" result[port_name] = (port_alias, port_status) return result # Main agent method def main(): global PORT_OID device_opt = ["fabric_fencing", "ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Cisco MDS" docs["longdesc"] = "fence_cisco_mds is an I/O Fencing agent \ which can be used with any Cisco MDS 9000 series with SNMP enabled device." docs["vendorurl"] = "http://www.cisco.com" show_docs(options, docs) if not options["--action"] in ["list", "monitor"]: PORT_OID = cisco_port2oid(options["--plug"]) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/cisco_ucs/fence_cisco_ucs.py b/fence/agents/cisco_ucs/fence_cisco_ucs.py index 7102c445..d509b3e0 100644 --- a/fence/agents/cisco_ucs/fence_cisco_ucs.py +++ b/fence/agents/cisco_ucs/fence_cisco_ucs.py @@ -1,202 +1,196 @@ #!@PYTHON@ -tt import sys, re import pycurl, io import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_LOGIN_DENIED, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New Cisco UCS Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="March, 2008" -#END_VERSION_GENERATION - RE_COOKIE = re.compile("", int(options["--shell-timeout"])) result = RE_GET_PNDN.search(res) if result == None: pndn = "" else: pndn = result.group(1) if pndn.strip() == "": if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) res = send_command(options, "", int(options["--shell-timeout"])) result = RE_GET_PRESENCE.search(res) if result == None: fail(EC_STATUS) else: presence_status = result.group(1) if presence_status in ["missing", "mismatch"]: return "off" else: result = RE_GET_OPERPOWER.search(res) if result == None: fail(EC_STATUS) else: power_status = result.group(1) if power_status == "on": return "on" else: return "off" def set_power_status(conn, options): del conn action = { 'on' : "admin-up", 'off' : "admin-down" }[options["--action"]] send_command(options, "" + "" + "" + "", int(options["--shell-timeout"])) return def get_list(conn, options): del conn outlets = {} try: res = send_command(options, "", int(options["--shell-timeout"])) lines = res.split("", int(options_global["--shell-timeout"])) except Exception: pass def main(): global options_global device_opt = ["ipaddr", "login", "passwd", "ssl", "notls", "port", "web", "suborg", "missing_as_off"] atexit.register(atexit_handler) atexit.register(logout) define_new_opts() options_global = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Cisco UCS" docs["longdesc"] = "fence_cisco_ucs is an I/O Fencing agent which can be \ used with Cisco UCS to fence machines." docs["vendorurl"] = "http://www.cisco.com" show_docs(options_global, docs) run_delay(options_global) ### Login try: res = send_command(options_global, "", int(options_global["--login-timeout"])) result = RE_COOKIE.search(res) if result == None: ## Cookie is absenting in response fail(EC_LOGIN_DENIED) except Exception: fail(EC_LOGIN_DENIED) options_global["cookie"] = result.group(1) ## ## Modify suborg to format /suborg if options_global["--suborg"] != "": options_global["--suborg"] = "/" + options_global["--suborg"].lstrip("/").rstrip("/") ## ## Fence operations #### result = fence_action(None, options_global, set_power_status, get_power_status, get_list) ## Logout is done every time at atexit phase sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/compute/fence_compute.py b/fence/agents/compute/fence_compute.py index e36a9505..a0bf3894 100644 --- a/fence/agents/compute/fence_compute.py +++ b/fence/agents/compute/fence_compute.py @@ -1,505 +1,498 @@ #!@PYTHON@ -tt import sys import time import atexit import logging import inspect import requests.exceptions sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="4.0.11" -BUILD_DATE="(built Wed Nov 12 06:33:38 EST 2014)" -REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." -#END_VERSION_GENERATION - override_status = "" EVACUABLE_TAG = "evacuable" TRUE_TAGS = ['true'] def get_power_status(connection, options): if len(override_status): logging.debug("Pretending we're " + override_status) return override_status status = "unknown" logging.debug("get action: " + options["--action"]) if connection: try: services = connection.services.list(host=options["--plug"], binary="nova-compute") for service in services: logging.debug("Status of %s on %s is %s, %s" % (service.binary, options["--plug"], service.state, service.status)) if service.state == "up" and service.status == "enabled": # Up and operational status = "on" elif service.state == "down" and service.status == "disabled": # Down and fenced status = "off" elif service.state == "down": # Down and requires fencing status = "failed" elif service.state == "up": # Up and requires unfencing status = "running" else: logging.warning("Unknown status detected from nova for %s: %s, %s" % (options["--plug"], service.state, service.status)) status = "%s %s" % (service.state, service.status) break except requests.exception.ConnectionError as err: logging.warning("Nova connection failed: " + str(err)) logging.debug("Final status of %s is %s" % (options["--plug"], status)) return status def get_power_status_simple(connection, options): status = get_power_status(connection, options) if status in [ "off" ]: return status return "on" def set_attrd_status(host, status, options): logging.debug("Setting fencing status for %s to %s" % (host, status)) run_command(options, "attrd_updater -p -n evacuate -Q -N %s -U %s" % (host, status)) def get_attrd_status(host, options): (status, pipe_stdout, pipe_stderr) = run_command(options, "attrd_updater -p -n evacuate -Q -N %s" % (host)) fields = pipe_stdout.split('"') if len(fields) > 6: return fields[5] logging.debug("Got %s: o:%s e:%s n:%d" % (status, pipe_stdout, pipe_stderr, len(fields))) return "" def set_power_status_on(connection, options): # Wait for any evacuations to complete while True: current = get_attrd_status(options["--plug"], options) if current in ["no", ""]: logging.info("Evacuation complete for: %s '%s'" % (options["--plug"], current)) break else: logging.info("Waiting for %s to complete evacuations: %s" % (options["--plug"], current)) time.sleep(2) status = get_power_status(connection, options) # Should we do it for 'failed' too? if status in [ "off", "running", "failed" ]: try: # Forcing the host back up logging.info("Forcing nova-compute back up on "+options["--plug"]) connection.services.force_down(options["--plug"], "nova-compute", force_down=False) logging.info("Forced nova-compute back up on "+options["--plug"]) except Exception as e: # In theory, if force_down=False fails, that's for the exact # same possible reasons that below with force_down=True # eg. either an incompatible version or an old client. # Since it's about forcing back to a default value, there is # no real worries to just consider it's still okay even if the # command failed logging.warn("Exception from attempt to force " "host back up via nova API: " "%s: %s" % (e.__class__.__name__, e)) # Forcing the service back up in case it was disabled logging.info("Enabling nova-compute on "+options["--plug"]) connection.services.enable(options["--plug"], 'nova-compute') # Pretend we're 'on' so that the fencing library doesn't loop forever waiting for the node to boot override_status = "on" elif status not in ["on"]: # Not safe to unfence, don't waste time looping to see if the status changes to "on" options["--power-timeout"] = "0" def set_power_status_off(connection, options): status = get_power_status(connection, options) if status in [ "off" ]: return connection.services.disable(options["--plug"], 'nova-compute') try: # Until 2.53 connection.services.force_down( options["--plug"], "nova-compute", force_down=True) except Exception as e: # Something went wrong when we tried to force the host down. # That could come from either an incompatible API version # eg. UnsupportedVersion or VersionNotFoundForAPIMethod # or because novaclient is old and doesn't include force_down yet # eg. AttributeError # In that case, fallbacking to wait for Nova to catch the right state. logging.error("Exception from attempt to force host down via nova API: " "%s: %s" % (e.__class__.__name__, e)) # need to wait for nova to update its internal status or we # cannot call host-evacuate while get_power_status(connection, options) not in ["off"]: # Loop forever if need be. # # Some callers (such as Pacemaker) will have a timer # running and kill us if necessary logging.debug("Waiting for nova to update its internal state for %s" % options["--plug"]) time.sleep(1) set_attrd_status(options["--plug"], "yes", options) def set_power_status(connection, options): global override_status override_status = "" logging.debug("set action: " + options["--action"]) if not connection: return if options["--action"] in ["off", "reboot"]: set_power_status_off(connection, options) else: set_power_status_on(connection, options) logging.debug("set action passed: " + options["--action"]) sys.exit(0) def fix_domain(connection, options): domains = {} last_domain = None if connection: # Find it in nova services = connection.services.list(binary="nova-compute") for service in services: shorthost = service.host.split('.')[0] if shorthost == service.host: # Nova is not using FQDN calculated = "" else: # Compute nodes are named as FQDN, strip off the hostname calculated = service.host.replace(shorthost+".", "") if calculated == last_domain: # Avoid complaining for each compute node with the same name # One hopes they don't appear interleaved as A.com B.com A.com B.com logging.debug("Calculated the same domain from: %s" % service.host) continue domains[calculated] = service.host last_domain = calculated if "--domain" in options and options["--domain"] != calculated: # Warn in case nova isn't available at some point logging.warning("Supplied domain '%s' does not match the one calculated from: %s" % (options["--domain"], service.host)) if len(domains) == 0 and "--domain" not in options: logging.error("Could not calculate the domain names used by compute nodes in nova") elif len(domains) == 1 and "--domain" not in options: options["--domain"] = last_domain elif len(domains) == 1 and options["--domain"] != last_domain: logging.error("Overriding supplied domain '%s' as it does not match the one calculated from: %s" % (options["--domain"], domains[last_domain])) options["--domain"] = last_domain elif len(domains) > 1: logging.error("The supplied domain '%s' did not match any used inside nova: %s" % (options["--domain"], repr(domains))) sys.exit(1) return last_domain def fix_plug_name(connection, options): if options["--action"] == "list": return if "--plug" not in options: return calculated = fix_domain(connection, options) if calculated is None or "--domain" not in options: # Nothing supplied and nova not available... what to do... nothing return short_plug = options["--plug"].split('.')[0] logging.debug("Checking target '%s' against calculated domain '%s'"% (options["--plug"], calculated)) if options["--domain"] == "": # Ensure any domain is stripped off since nova isn't using FQDN options["--plug"] = short_plug elif options["--plug"].endswith(options["--domain"]): # Plug already uses the domain, don't re-add return else: # Add the domain to the plug options["--plug"] = short_plug + "." + options["--domain"] def get_plugs_list(connection, options): result = {} if connection: services = connection.services.list(binary="nova-compute") for service in services: longhost = service.host shorthost = longhost.split('.')[0] result[longhost] = ("", None) result[shorthost] = ("", None) return result def create_nova_connection(options): nova = None try: from novaclient import client from novaclient.exceptions import NotAcceptable except ImportError: fail_usage("Nova not found or not accessible") from keystoneauth1 import loading from keystoneauth1 import session from keystoneclient import discover # Prefer the oldest and strip the leading 'v' keystone_versions = discover.available_versions(options["--auth-url"]) keystone_version = keystone_versions[0]['id'][1:] kwargs = dict( auth_url=options["--auth-url"], username=options["--username"], password=options["--password"] ) if discover.version_match("2", keystone_version): kwargs["tenant_name"] = options["--tenant-name"] elif discover.version_match("3", keystone_version): kwargs["project_name"] = options["--tenant-name"] kwargs["user_domain_name"] = options["--user-domain"] kwargs["project_domain_name"] = options["--project-domain"] loader = loading.get_plugin_loader('password') keystone_auth = loader.load_from_options(**kwargs) keystone_session = session.Session(auth=keystone_auth, verify=(not options["--insecure"])) nova_versions = [ "2.11", "2" ] for version in nova_versions: clientargs = inspect.getargspec(client.Client).varargs # Some versions of Openstack prior to Ocata only # supported positional arguments for username, # password, and tenant. # # Versions since Ocata only support named arguments. # # So we need to use introspection to figure out how to # create a Nova client. # # Happy days # if clientargs: # OSP < 11 # ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'], # varargs=None, # keywords='kwargs', defaults=(None, None, None, None)) nova = client.Client(version, None, # User None, # Password None, # Tenant None, # Auth URL insecure=options["--insecure"], region_name=options["--region-name"], endpoint_type=options["--endpoint-type"], session=keystone_session, auth=keystone_auth, http_log_debug=options.has_key("--verbose")) else: # OSP >= 11 # ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None) nova = client.Client(version, region_name=options["--region-name"], endpoint_type=options["--endpoint-type"], session=keystone_session, auth=keystone_auth, http_log_debug=options.has_key("--verbose")) try: nova.hypervisors.list() return nova except NotAcceptable as e: logging.warning(e) except Exception as e: logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e)) logging.warning("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(versions)) return None def define_new_opts(): - all_opt["endpoint-type"] = { + all_opt["endpoint_type"] = { "getopt" : "e:", "longopt" : "endpoint-type", "help" : "-e, --endpoint-type=[endpoint] Nova Endpoint type (publicURL, internalURL, adminURL)", "required" : "0", "shortdesc" : "Nova Endpoint type", "default" : "internalURL", "order": 1, } - all_opt["tenant-name"] = { + all_opt["tenant_name"] = { "getopt" : "t:", "longopt" : "tenant-name", "help" : "-t, --tenant-name=[name] Keystone v2 Tenant or v3 Project Name", "required" : "0", "shortdesc" : "Keystone Admin Tenant or v3 Project", "default" : "", "order": 1, } all_opt["user-domain"] = { "getopt" : "u:", "longopt" : "user-domain", "help" : "-u, --user-domain=[name] Keystone v3 User Domain", "required" : "0", "shortdesc" : "Keystone v3 User Domain", "default" : "Default", "order": 2, } all_opt["project-domain"] = { "getopt" : "P:", "longopt" : "project-domain", "help" : "-d, --project-domain=[name] Keystone v3 Project Domain", "required" : "0", "shortdesc" : "Keystone v3 Project Domain", "default" : "Default", "order": 2, } - all_opt["auth-url"] = { + all_opt["auth_url"] = { "getopt" : "k:", "longopt" : "auth-url", "help" : "-k, --auth-url=[url] Keystone Admin Auth URL", "required" : "0", "shortdesc" : "Keystone Admin Auth URL", "default" : "", "order": 1, } - all_opt["region-name"] = { + all_opt["region_name"] = { "getopt" : "", "longopt" : "region-name", "help" : "--region-name=[region] Region Name", "required" : "0", "shortdesc" : "Region Name", "default" : "", "order": 1, } all_opt["insecure"] = { "getopt" : "", "longopt" : "insecure", "help" : "--insecure Explicitly allow agent to perform \"insecure\" TLS (https) requests", "required" : "0", "shortdesc" : "Allow Insecure TLS Requests", "default" : "False", "order": 2, } all_opt["compute-domain"] = { "getopt" : "d:", "longopt" : "domain", "help" : "-d, --domain=[string] DNS domain in which hosts live, useful when the cluster uses short names and nova uses FQDN", "required" : "0", "shortdesc" : "DNS domain in which hosts live", "order": 5, } - all_opt["record-only"] = { + all_opt["record_only"] = { "getopt" : "r:", "longopt" : "record-only", "help" : "--record-only Record the target as needing evacuation but as yet do not intiate it", "required" : "0", "shortdesc" : "Only record the target as needing evacuation", "default" : "False", "order": 5, } - all_opt["instance-filtering"] = { + all_opt["instance_filtering"] = { "getopt" : "", "longopt" : "instance-filtering", "help" : "--instance-filtering Allow instances created from images and flavors with evacuable=true to be evacuated (or all if no images/flavors have been tagged)", "required" : "0", "shortdesc" : "Allow instances to be evacuated", "default" : "True", "order": 5, } - all_opt["no-shared-storage"] = { + all_opt["no_shared_storage"] = { "getopt" : "", "longopt" : "no-shared-storage", "help" : "--no-shared-storage Disable functionality for shared storage", "required" : "0", "shortdesc" : "Disable functionality for dealing with shared storage", "default" : "False", "order": 5, } def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts=1): for _ in range(retry_attempts): set_power_fn(connection, options) time.sleep(int(options["--power-wait"])) for _ in range(int(options["--power-timeout"])): if get_power_fn(connection, options) != options["--action"]: time.sleep(1) else: return True return False def main(): global override_status atexit.register(atexit_handler) - device_opt = ["login", "passwd", "tenant-name", "auth-url", "fabric_fencing", + device_opt = ["login", "passwd", "tenant_name", "auth_url", "fabric_fencing", "no_login", "no_password", "port", "compute-domain", "project-domain", "user-domain", - "no-shared-storage", "endpoint-type", "record-only", "instance-filtering", "insecure", - "region-name"] + "no_shared_storage", "endpoint_type", "record_only", "instance_filtering", "insecure", "region_name"] define_new_opts() all_opt["shell_timeout"]["default"] = "180" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for the automatic resurrection of OpenStack compute instances" docs["longdesc"] = "Used to tell Nova that compute nodes are down and to reschedule flagged instances" docs["vendorurl"] = "" show_docs(options, docs) if options["--record-only"] in [ "2", "Disabled", "disabled" ]: sys.exit(0) run_delay(options) logging.debug("Running "+options["--action"]) connection = create_nova_connection(options) if options["--action"] in ["off", "on", "reboot", "status"]: fix_plug_name(connection, options) if options["--action"] in ["reboot"]: options["--action"]="off" if options["--action"] in ["off", "on"]: # No status first, call our own version result = not set_multi_power_fn(connection, options, set_power_status, get_power_status_simple, 1 + int(options["--retry-on"])) elif options["--action"] in ["monitor"]: result = 0 else: result = fence_action(connection, options, set_power_status, get_power_status_simple, get_plugs_list, None) logging.debug("Result for "+options["--action"]+": "+repr(result)) if result == None: result = 0 sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/compute/fence_evacuate.py b/fence/agents/compute/fence_evacuate.py index a475907d..f39df273 100644 --- a/fence/agents/compute/fence_evacuate.py +++ b/fence/agents/compute/fence_evacuate.py @@ -1,413 +1,407 @@ #!@PYTHON@ -tt import sys import time import atexit import logging import inspect import requests.exceptions sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="4.0.11" -BUILD_DATE="(built Wed Nov 12 06:33:38 EST 2014)" -REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." -#END_VERSION_GENERATION - EVACUABLE_TAG = "evacuable" TRUE_TAGS = ['true'] def get_power_status(connection, options): status = "unknown" logging.debug("get action: " + options["--action"]) if connection: try: services = connection.services.list(host=options["--plug"], binary="nova-compute") for service in services: logging.debug("Status of %s is %s, %s" % (service.binary, service.state, service.status)) if service.state == "up" and service.status == "enabled": # Up and operational status = "on" elif service.state == "down" and service.status == "disabled": # Down and fenced status = "off" elif service.state == "down": # Down and requires fencing status = "failed" elif service.state == "up": # Up and requires unfencing status = "running" else: logging.warning("Unknown status detected from nova for %s: %s, %s" % (options["--plug"], service.state, service.status)) status = "%s %s" % (service.state, service.status) break except requests.exception.ConnectionError as err: logging.warning("Nova connection failed: " + str(err)) return status # NOTE(sbauza); We mimic the host-evacuate module since it's only a contrib # module which is not stable def _server_evacuate(connection, server, on_shared_storage): success = False error_message = "" try: logging.debug("Resurrecting instance: %s" % server) (response, dictionary) = connection.servers.evacuate(server=server, on_shared_storage=on_shared_storage) if response == None: error_message = "No response while evacuating instance" elif response.status_code == 200: success = True error_message = response.reason else: error_message = response.reason except Exception as e: error_message = "Error while evacuating instance: %s" % e return { "uuid": server, "accepted": success, "reason": error_message, } def _is_server_evacuable(server, evac_flavors, evac_images): if server.flavor.get('id') in evac_flavors: return True if hasattr(server.image, 'get'): if server.image.get('id') in evac_images: return True logging.debug("Instance %s is not evacuable" % server.image.get('id')) return False def _get_evacuable_flavors(connection): result = [] flavors = connection.flavors.list() # Since the detailed view for all flavors doesn't provide the extra specs, # we need to call each of the flavor to get them. for flavor in flavors: tag = flavor.get_keys().get(EVACUABLE_TAG) if tag and tag.strip().lower() in TRUE_TAGS: result.append(flavor.id) return result def _get_evacuable_images(connection): result = [] images = [] if hasattr(connection, "images"): images = connection.images.list(detailed=True) elif hasattr(connection, "glance"): # OSP12+ images = connection.glance.list() for image in images: if hasattr(image, 'metadata'): tag = image.metadata.get(EVACUABLE_TAG) if tag and tag.strip().lower() in TRUE_TAGS: result.append(image.id) elif hasattr(image, 'tags'): # OSP12+ if EVACUABLE_TAG in image.tags: result.append(image.id) return result def _host_evacuate(connection, options): result = True images = _get_evacuable_images(connection) flavors = _get_evacuable_flavors(connection) servers = connection.servers.list(search_opts={'host': options["--plug"], 'all_tenants': 1 }) if options["--instance-filtering"] == "False": logging.debug("Not evacuating anything") evacuables = [] elif len(flavors) or len(images): logging.debug("Filtering images and flavors: %s %s" % (repr(flavors), repr(images))) # Identify all evacuable servers logging.debug("Checking %s" % repr(servers)) evacuables = [server for server in servers if _is_server_evacuable(server, flavors, images)] logging.debug("Evacuating %s" % repr(evacuables)) else: logging.debug("Evacuating all images and flavors") evacuables = servers if options["--no-shared-storage"] != "False": on_shared_storage = False else: on_shared_storage = True for server in evacuables: logging.debug("Processing %s" % server) if hasattr(server, 'id'): response = _server_evacuate(connection, server.id, on_shared_storage) if response["accepted"]: logging.debug("Evacuated %s from %s: %s" % (response["uuid"], options["--plug"], response["reason"])) else: logging.error("Evacuation of %s on %s failed: %s" % (response["uuid"], options["--plug"], response["reason"])) result = False else: logging.error("Could not evacuate instance: %s" % server.to_dict()) # Should a malformed instance result in a failed evacuation? # result = False return result def set_attrd_status(host, status, options): logging.debug("Setting fencing status for %s to %s" % (host, status)) run_command(options, "attrd_updater -p -n evacuate -Q -N %s -U %s" % (host, status)) def set_power_status(connection, options): logging.debug("set action: " + options["--action"]) if not connection: return if options["--action"] == "off" and not _host_evacuate(options): sys.exit(1) sys.exit(0) def get_plugs_list(connection, options): result = {} if connection: services = connection.services.list(binary="nova-compute") for service in services: longhost = service.host shorthost = longhost.split('.')[0] result[longhost] = ("", None) result[shorthost] = ("", None) return result def create_nova_connection(options): nova = None try: from novaclient import client from novaclient.exceptions import NotAcceptable except ImportError: fail_usage("Nova not found or not accessible") from keystoneauth1 import loading from keystoneauth1 import session from keystoneclient import discover # Prefer the oldest and strip the leading 'v' keystone_versions = discover.available_versions(options["--auth-url"]) keystone_version = keystone_versions[0]['id'][1:] kwargs = dict( auth_url=options["--auth-url"], username=options["--username"], password=options["--password"] ) if discover.version_match("2", keystone_version): kwargs["tenant_name"] = options["--tenant-name"] elif discover.version_match("3", keystone_version): kwargs["project_name"] = options["--tenant-name"] kwargs["user_domain_name"] = options["--user-domain"] kwargs["project_domain_name"] = options["--project-domain"] loader = loading.get_plugin_loader('password') keystone_auth = loader.load_from_options(**kwargs) keystone_session = session.Session(auth=keystone_auth, verify=(not options["--insecure"])) versions = [ "2.11", "2" ] for version in versions: clientargs = inspect.getargspec(client.Client).varargs # Some versions of Openstack prior to Ocata only # supported positional arguments for username, # password, and tenant. # # Versions since Ocata only support named arguments. # # So we need to use introspection to figure out how to # create a Nova client. # # Happy days # if clientargs: # OSP < 11 # ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'], # varargs=None, # keywords='kwargs', defaults=(None, None, None, None)) nova = client.Client(version, None, # User None, # Password None, # Tenant None, # Auth URL insecure=options["--insecure"], region_name=options["--region-name"], endpoint_type=options["--endpoint-type"], session=keystone_session, auth=keystone_auth, http_log_debug=options.has_key("--verbose")) else: # OSP >= 11 # ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None) nova = client.Client(version, region_name=options["--region-name"], endpoint_type=options["--endpoint-type"], session=keystone_session, auth=keystone_auth, http_log_debug=options.has_key("--verbose")) try: nova.hypervisors.list() return nova except NotAcceptable as e: logging.warning(e) except Exception as e: logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e)) logging.warning("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(versions)) return None def define_new_opts(): - all_opt["endpoint-type"] = { + all_opt["endpoint_type"] = { "getopt" : "e:", "longopt" : "endpoint-type", "help" : "-e, --endpoint-type=[endpoint] Nova Endpoint type (publicURL, internalURL, adminURL)", "required" : "0", "shortdesc" : "Nova Endpoint type", "default" : "internalURL", "order": 1, } - all_opt["tenant-name"] = { + all_opt["tenant_name"] = { "getopt" : "t:", "longopt" : "tenant-name", "help" : "-t, --tenant-name=[name] Keystone v2 Tenant or v3 Project Name", "required" : "0", "shortdesc" : "Keystone Admin Tenant or v3 Project", "default" : "", "order": 1, } all_opt["user-domain"] = { "getopt" : "u:", "longopt" : "user-domain", "help" : "-u, --user-domain=[name] Keystone v3 User Domain", "required" : "0", "shortdesc" : "Keystone v3 User Domain", "default" : "Default", "order": 2, } all_opt["project-domain"] = { "getopt" : "P:", "longopt" : "project-domain", "help" : "-d, --project-domain=[name] Keystone v3 Project Domain", "required" : "0", "shortdesc" : "Keystone v3 Project Domain", "default" : "Default", "order": 2, } - all_opt["auth-url"] = { + all_opt["auth_url"] = { "getopt" : "k:", "longopt" : "auth-url", "help" : "-k, --auth-url=[url] Keystone Admin Auth URL", "required" : "0", "shortdesc" : "Keystone Admin Auth URL", "default" : "", "order": 1, } - all_opt["region-name"] = { + all_opt["region_name"] = { "getopt" : "", "longopt" : "region-name", "help" : "--region-name=[region] Region Name", "required" : "0", "shortdesc" : "Region Name", "default" : "", "order": 1, } all_opt["insecure"] = { "getopt" : "", "longopt" : "insecure", "help" : "--insecure Explicitly allow agent to perform \"insecure\" TLS (https) requests", "required" : "0", "shortdesc" : "Allow Insecure TLS Requests", "default" : "False", "order": 2, } all_opt["compute-domain"] = { "getopt" : "d:", "longopt" : "domain", "help" : "-d, --domain=[string] DNS domain in which hosts live, useful when the cluster uses short names and nova uses FQDN", "required" : "0", "shortdesc" : "DNS domain in which hosts live", "order": 5, } - all_opt["instance-filtering"] = { + all_opt["instance_filtering"] = { "getopt" : "", "longopt" : "instance-filtering", "help" : "--instance-filtering Allow instances created from images and flavors with evacuable=true to be evacuated (or all if no images/flavors have been tagged)", "required" : "0", "shortdesc" : "Allow instances to be evacuated", "default" : "True", "order": 5, } - all_opt["no-shared-storage"] = { + all_opt["no_shared_storage"] = { "getopt" : "", "longopt" : "no-shared-storage", "help" : "--no-shared-storage Disable functionality for shared storage", "required" : "0", "shortdesc" : "Disable functionality for dealing with shared storage", "default" : "False", "order": 5, } def main(): atexit.register(atexit_handler) - device_opt = ["login", "passwd", "tenant-name", "auth-url", - "no_login", "no_password", "port", "compute-domain", "project-domain", "user-domain", - "no-shared-storage", "endpoint-type", - "instance-filtering", "insecure", "region-name"] + device_opt = ["login", "passwd", "tenant_name", "auth_url", + "no_login", "no_password", "port", "compute-domain", "project-domain", + "user-domain", "no_shared_storage", "endpoint_type", + "instance_filtering", "insecure", "region_name"] define_new_opts() all_opt["shell_timeout"]["default"] = "180" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for the automatic resurrection of OpenStack compute instances" docs["longdesc"] = "Used to reschedule flagged instances" docs["vendorurl"] = "" show_docs(options, docs) run_delay(options) connection = create_nova_connection(options) # Un-evacuating a server doesn't make sense if options["--action"] in ["on"]: logging.error("Action %s is not supported by this agent" % (options["--action"])) sys.exit(1) if options["--action"] in ["off", "reboot"]: status = get_power_status(connection, options) if status != "off": logging.error("Cannot resurrect instances from %s in state '%s'" % (options["--plug"], status)) sys.exit(1) elif not _host_evacuate(connection, options): logging.error("Resurrection of instances from %s failed" % (options["--plug"])) sys.exit(1) logging.info("Resurrection of instances from %s complete" % (options["--plug"])) sys.exit(0) result = fence_action(connection, options, set_power_status, get_power_status, get_plugs_list, None) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/docker/fence_docker.py b/fence/agents/docker/fence_docker.py index 2bfa5eae..b2921016 100644 --- a/fence/agents/docker/fence_docker.py +++ b/fence/agents/docker/fence_docker.py @@ -1,164 +1,158 @@ #!@PYTHON@ -tt import atexit import sys import io import logging import pycurl import json sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail_usage, all_opt, fence_action, atexit_handler, check_input, process_input, show_docs, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION = "" -REDHAT_COPYRIGHT = "" -BUILD_DATE = "" -#END_VERSION_GENERATION - def get_power_status(conn, options): del conn status = send_cmd(options, "containers/%s/json" % options["--plug"]) if status is None: return None return "on" if status["State"]["Running"] else "off" def set_power_status(conn, options): del conn if options["--action"] == "on": send_cmd(options, "containers/%s/start" % options["--plug"], True) else: send_cmd(options, "containers/%s/kill" % options["--plug"], True) return def reboot_cycle(conn, options): del conn send_cmd(options, "containers/%s/restart" % options["--plug"], True) return get_power_status(conn, options) def get_list(conn, options): del conn output = send_cmd(options, "containers/json?all=1") containers = {} for container in output: containers[container["Id"]] = (container["Names"][0], {True:"off", False: "on"}[container["Status"][:4].lower() == "exit"]) return containers def send_cmd(options, cmd, post = False): url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd) conn = pycurl.Curl() output_buffer = io.BytesIO() if logging.getLogger().getEffectiveLevel() < logging.WARNING: conn.setopt(pycurl.VERBOSE, True) conn.setopt(pycurl.HTTPGET, 1) conn.setopt(pycurl.URL, url.encode("ascii")) if post: conn.setopt(pycurl.POST, 1) conn.setopt(pycurl.POSTFIELDSIZE, 0) conn.setopt(pycurl.WRITEFUNCTION, output_buffer.write) conn.setopt(pycurl.TIMEOUT, int(options["--shell-timeout"])) if "--ssl" in options: if not (set(("--tlscert", "--tlskey", "--tlscacert")) <= set(options)): fail_usage("Failed. If --ssl option is used, You have to also \ specify: --tlscert, --tlskey and --tlscacert") conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSLCERT, options["--tlscert"]) conn.setopt(pycurl.SSLKEY, options["--tlskey"]) conn.setopt(pycurl.CAINFO, options["--tlscacert"]) else: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) logging.debug("URL: " + url) try: conn.perform() result = output_buffer.getvalue().decode() return_code = conn.getinfo(pycurl.RESPONSE_CODE) logging.debug("RESULT [" + str(return_code) + \ "]: " + result) conn.close() if return_code == 200: return json.loads(result) except pycurl.error: logging.error("Connection failed") except: if result is not None: logging.error(result) logging.error("Cannot parse json") return None def main(): atexit.register(atexit_handler) all_opt["tlscert"] = { "getopt" : ":", "longopt" : "tlscert", "help" : "--tlscert " "Path to client certificate for TLS authentication", "required" : "0", "shortdesc" : "Path to client certificate (PEM format) \ for TLS authentication. Required if --ssl option is used.", "order": 2 } all_opt["tlskey"] = { "getopt" : ":", "longopt" : "tlskey", "help" : "--tlskey " "Path to client key for TLS authentication", "required" : "0", "shortdesc" : "Path to client key (PEM format) for TLS \ authentication. Required if --ssl option is used.", "order": 2 } all_opt["tlscacert"] = { "getopt" : ":", "longopt" : "tlscacert", "help" : "--tlscacert " "Path to CA certificate for TLS authentication", "required" : "0", "shortdesc" : "Path to CA certificate (PEM format) for \ TLS authentication. Required if --ssl option is used.", "order": 2 } all_opt["api_version"] = { "getopt" : ":", "longopt" : "api-version", "help" : "--api-version " "Version of Docker Remote API (default: 1.11)", "required" : "0", "order" : 2, "default" : "1.11", } device_opt = ["ipaddr", "no_password", "no_login", "port", "method", "web", "tlscert", "tlskey", "tlscacert", "ssl", "api_version"] options = check_input(device_opt, process_input(device_opt)) docs = { } docs["shortdesc"] = "Fence agent for Docker" docs["longdesc"] = "fence_docker is I/O fencing agent which \ can be used with the Docker Engine containers. You can use this \ fence-agent without any authentication, or you can use TLS authentication \ (use --ssl option, more info about TLS authentication in docker: \ http://docs.docker.com/examples/https/)." docs["vendorurl"] = "www.docker.io" show_docs(options, docs) run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, get_list, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/drac/fence_drac.py b/fence/agents/drac/fence_drac.py index f698b398..be3e9a58 100644 --- a/fence/agents/drac/fence_drac.py +++ b/fence/agents/drac/fence_drac.py @@ -1,68 +1,62 @@ #!@PYTHON@ -tt import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send_eol("getmodinfo") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) status = re.compile(r"\s+(on|off)\s+", re.IGNORECASE).search(conn.before).group(1) return status.lower().strip() def set_power_status(conn, options): action = { 'on' : "powerup", 'off': "powerdown" }[options["--action"]] conn.send_eol("serveraction -d 0 " + action) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "telnet"] atexit.register(atexit_handler) opt = process_input(device_opt) if "--username" in opt: all_opt["cmd_prompt"]["default"] = ["\\[" + opt["--username"] + "\\]# "] else: all_opt["cmd_prompt"]["default"] = ["\\[" "username" + "\\]# "] options = check_input(device_opt, opt) docs = {} docs["shortdesc"] = "I/O Fencing agent for Dell DRAC IV" docs["longdesc"] = "fence_drac is an I/O Fencing agent which can be used with \ the Dell Remote Access Card (DRAC). This card provides remote access to controlling \ power to a server. It logs into the DRAC through the telnet interface of the card. By \ default, the telnet interface is not enabled. To enable the interface, you will need \ to use the racadm command in the racser-devel rpm available from Dell. \ \ To enable telnet on the DRAC: \ \ [root]# racadm config -g cfgSerial -o cfgSerialTelnetEnable 1 \ \ [root]# racadm racreset \ " docs["vendorurl"] = "http://www.dell.com" show_docs(options, docs) ## ## Operate the fencing device #### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, None) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/drac5/fence_drac5.py b/fence/agents/drac5/fence_drac5.py index df113fea..648ecd91 100644 --- a/fence/agents/drac5/fence_drac5.py +++ b/fence/agents/drac5/fence_drac5.py @@ -1,153 +1,147 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## DRAC Version Firmware ## +-----------------+---------------------------+ ## DRAC 5 1.0 (Build 06.05.12) ## DRAC 5 1.21 (Build 07.05.04) ## ## @note: drac_version was removed ##### import sys, re, time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New Drac5 Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="March, 2008" -#END_VERSION_GENERATION - def get_power_status(conn, options): if options["--drac-version"] == "DRAC MC": (_, status) = get_list_devices(conn, options)[options["--plug"]] else: if options["--drac-version"] == "DRAC CMC": conn.send_eol("racadm serveraction powerstatus -m " + options["--plug"]) elif options["--drac-version"] == "DRAC 5": conn.send_eol("racadm serveraction powerstatus") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) status = re.compile(r"(^|: )(ON|OFF|Powering ON|Powering OFF)\s*$", re.IGNORECASE | re.MULTILINE).search(conn.before).group(2) if status.lower().strip() in ["on", "powering on", "powering off"]: return "on" else: return "off" def set_power_status(conn, options): action = { 'on' : "powerup", 'off': "powerdown" }[options["--action"]] if options["--drac-version"] == "DRAC CMC": conn.send_eol("racadm serveraction " + action + " -m " + options["--plug"]) elif options["--drac-version"] == "DRAC 5": conn.send_eol("racadm serveraction " + action) elif options["--drac-version"] == "DRAC MC": conn.send_eol("racadm serveraction -s " + options["--plug"] + " " + action) ## Fix issue with double-enter [CR/LF] ## We need to read two additional command prompts (one from get + one from set command) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) if len(conn.before.strip()) == 0: options["eol"] = options["eol"][:-1] conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def get_list_devices(conn, options): outlets = {} if options["--drac-version"] == "DRAC CMC": conn.send_eol("getmodinfo") list_re = re.compile(r"^([^\s]*?)\s+Present\s*(ON|OFF)\s*.*$") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) for line in conn.before.splitlines(): if list_re.search(line): outlets[list_re.search(line).group(1)] = ("", list_re.search(line).group(2)) elif options["--drac-version"] == "DRAC MC": conn.send_eol("getmodinfo") list_re = re.compile(r"^\s*([^\s]*)\s*---->\s*(.*?)\s+Present\s*(ON|OFF)\s*.*$") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) for line in conn.before.splitlines(): if list_re.search(line): outlets[list_re.search(line).group(2)] = ("", list_re.search(line).group(3)) elif options["--drac-version"] == "DRAC 5": ## DRAC 5 can be used only for one computer ## standard fence library can't handle correctly situation ## when some fence devices supported by fence agent ## works with 'list' and other should returns 'N/A' print("N/A") return outlets def define_new_opts(): all_opt["drac_version"] = { "getopt" : "d:", "longopt" : "drac-version", "help" : "-d, --drac-version=[version] Force DRAC version to use (DRAC 5|DRAC CMC|DRAC MC)", "required" : "0", "shortdesc" : "Force DRAC version to use", "choices" : ["DRAC CMC", "DRAC MC", "DRAC 5"], "order" : 1} def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "drac_version", "port", "no_port", "telnet"] atexit.register(atexit_handler) define_new_opts() all_opt["cmd_prompt"]["default"] = [r"\$", r"DRAC\/MC:"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Dell DRAC CMC/5" docs["longdesc"] = "fence_drac5 is an I/O Fencing agent \ which can be used with the Dell Remote Access Card v5 or CMC (DRAC). \ This device provides remote access to controlling power to a server. \ It logs into the DRAC through the telnet/ssh interface of the card. \ By default, the telnet interface is not enabled." docs["vendorurl"] = "http://www.dell.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options) if "--drac-version" not in options: ## autodetect from text issued by fence device if conn.before.find("CMC") >= 0: options["--drac-version"] = "DRAC CMC" elif conn.before.find("DRAC 5") >= 0: options["--drac-version"] = "DRAC 5" elif conn.after.find("DRAC/MC") >= 0: options["--drac-version"] = "DRAC MC" else: ## Assume this is DRAC 5 by default as we don't want to break anything options["--drac-version"] = "DRAC 5" if options["--drac-version"] in ["DRAC MC", "DRAC CMC"]: if "--plug" not in options and 0 == ["monitor", "list"].count(options["--action"]): fail_usage("Failed: You have to enter module name (-n)") result = fence_action(conn, options, set_power_status, get_power_status, get_list_devices) fence_logout(conn, "exit", 1) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/dummy/fence_dummy.py b/fence/agents/dummy/fence_dummy.py index e4f1589c..8fa2d9af 100644 --- a/fence/agents/dummy/fence_dummy.py +++ b/fence/agents/dummy/fence_dummy.py @@ -1,139 +1,133 @@ #!@PYTHON@ -tt import sys, random import logging import time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New Dummy Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - plug_status = "on" def get_power_status_file(conn, options): del conn try: status_file = open(options["--status-file"], 'r') except Exception: return "off" status = status_file.read() status_file.close() return status.lower() def set_power_status_file(conn, options): del conn if not (options["--action"] in ["on", "off"]): return status_file = open(options["--status-file"], 'w') status_file.write(options["--action"]) status_file.close() def get_power_status_fail(conn, options): outlets = get_outlets_fail(conn, options) if len(outlets) == 0 or "--plug" not in options: fail_usage("Failed: You have to enter existing machine!") else: return outlets[options["--plug"]][0] def set_power_status_fail(conn, options): global plug_status del conn plug_status = "unknown" if options["--action"] == "on": plug_status = "off" def get_outlets_fail(conn, options): del conn result = {} global plug_status if options["--action"] == "on": plug_status = "off" # This fake agent has no port data to list, so we have to make # something up for the list action. if options.get("--action", None) == "list": result["fake_port_1"] = [plug_status, "fake"] result["fake_port_2"] = [plug_status, "fake"] elif "--plug" not in options: fail_usage("Failed: You have to enter existing machine!") else: port = options["--plug"] result[port] = [plug_status, "fake"] return result def main(): device_opt = ["no_password", "status_file", "random_sleep_range", "type", "no_port"] atexit.register(atexit_handler) all_opt["status_file"] = { "getopt" : ":", "longopt" : "status-file", "help":"--status-file=[file] Name of file that holds current status", "required" : "0", "shortdesc" : "File with status", "default" : "/tmp/fence_dummy.status", "order": 1 } all_opt["random_sleep_range"] = { "getopt" : ":", "longopt" : "random_sleep_range", "help":"--random_sleep_range=[seconds] Issue a sleep between 1 and [seconds]", "required" : "0", "shortdesc" : "Issue a sleep between 1 and X seconds. Used for testing.", "order": 1 } all_opt["type"] = { "getopt" : ":", "longopt" : "type", "help":"--type=[type] Possible types are: file and fail", "required" : "0", "shortdesc" : "Type of the dummy fence agent", "default" : "file", "order": 1 } options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Dummy fence agent" docs["longdesc"] = "fence_dummy" docs["vendorurl"] = "http://www.example.com" show_docs(options, docs) run_delay(options) # random sleep for testing if "--random_sleep_range" in options: val = int(options["--random_sleep_range"]) ran = random.randint(1, val) logging.info("Random sleep for %d seconds\n", ran) time.sleep(ran) if options["--type"] == "fail": result = fence_action(None, options, set_power_status_fail, get_power_status_fail, get_outlets_fail) else: result = fence_action(None, options, set_power_status_file, get_power_status_file, None) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/eaton_snmp/fence_eaton_snmp.py b/fence/agents/eaton_snmp/fence_eaton_snmp.py index dfd540c0..9fbc0563 100644 --- a/fence/agents/eaton_snmp/fence_eaton_snmp.py +++ b/fence/agents/eaton_snmp/fence_eaton_snmp.py @@ -1,235 +1,229 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - Eaton ePDU Managed - SNMP v1 # EATON | Powerware ePDU model: Managed ePDU (PW104MA0UB99), firmware: 01.01.01 # - Eaton ePDU Switched - SNMP v1 # EATON | Powerware ePDU model: Switched ePDU (IPV3600), firmware: 2.0.K import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Eaton SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see EatonManagedePDU, EatonSwitchedePDU device = None # Port ID port_id = None # Switch ID switch_id = None # Did we issue a set before get (to adjust OID with Switched ePDU) after_set = False # Classes describing Device params # Managed ePDU class EatonManagedePDU(object): status_oid = '.1.3.6.1.4.1.534.6.6.6.1.2.2.1.3.%d' control_oid = '.1.3.6.1.4.1.534.6.6.6.1.2.2.1.3.%d' outlet_table_oid = '.1.3.6.1.4.1.534.6.6.6.1.2.2.1.1' ident_str = "Eaton Managed ePDU" state_off = 0 state_on = 1 state_cycling = 2 # FIXME: not usable with fence-agents turn_off = 0 turn_on = 1 turn_cycle = 2 # FIXME: not usable with fence-agents has_switches = False # Switched ePDU (Pulizzi 2) # NOTE: sysOID reports "20677.1", while data are actually at "20677.2" class EatonSwitchedePDU(object): status_oid = '.1.3.6.1.4.1.20677.2.6.3.%d.0' control_oid = '.1.3.6.1.4.1.20677.2.6.2.%d.0' outlet_table_oid = '.1.3.6.1.4.1.20677.2.6.3' ident_str = "Eaton Switched ePDU" state_off = 2 state_on = 1 state_cycling = 0 # Note: this status doesn't exist on this device turn_off = 2 turn_on = 1 turn_cycle = 3 # FIXME: not usable with fence-agents has_switches = False ### FUNCTIONS ### def eaton_set_device(conn): global device agents_dir = {'.1.3.6.1.4.1.534.6.6.6':EatonManagedePDU, '.1.3.6.1.4.1.20677.1':EatonSwitchedePDU, '.1.3.6.1.4.1.20677.2':EatonSwitchedePDU} # First resolve type of Eaton eaton_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(eaton_type) == 1) and (eaton_type[0][1] in agents_dir)): eaton_type = [[None, None]] device = agents_dir[eaton_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def eaton_resolv_port_id(conn, options): global port_id, switch_id if device == None: eaton_set_device(conn) # Restore the increment, that was removed in main for ePDU Managed if device.ident_str == "Eaton Switched ePDU": options["--plug"] = str(int(options["--plug"]) + 1) # Now we resolv port_id/switch_id if options["--plug"].isdigit() and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.outlet_table_oid, 30) for x in table: if x[1].strip('"') == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: if device.ident_str == "Eaton Switched ePDU": port_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: # Restore index offset, to provide a valid error output on Managed ePDU if device.ident_str != "Eaton Switched ePDU": options["--plug"] = str(int(options["--plug"]) + 1) fail_usage("Can't find port with name %s!"%(options["--plug"])) def get_power_status(conn, options): global port_id, after_set if port_id == None: eaton_resolv_port_id(conn, options) # Ajust OID for Switched ePDU when the get is after a set if after_set and device.ident_str == "Eaton Switched ePDU": port_id -= 1 after_set = False oid = ((device.has_switches) and device.status_oid%(switch_id, port_id) or device.status_oid%(port_id)) try: (oid, status) = conn.get(oid) if status == str(device.state_on): return "on" elif status == str(device.state_off): return "off" else: return None except Exception: return None def set_power_status(conn, options): global port_id, after_set after_set = True if port_id == None: eaton_resolv_port_id(conn, options) # Controls start at #2 on Switched ePDU, since #1 is the global command if device.ident_str == "Eaton Switched ePDU": port_id = int(port_id)+1 oid = ((device.has_switches) and device.control_oid%(switch_id, port_id) or device.control_oid%(port_id)) conn.set(oid, (options["--action"] == "on" and device.turn_on or device.turn_off)) def get_outlets_status(conn, options): outletCount = 0 result = {} if device == None: eaton_set_device(conn) res_ports = conn.walk(device.outlet_table_oid, 30) for x in res_ports: outletCount += 1 status = x[1] t = x[0].split('.') # Plug indexing start from zero, so we substract '1' from the # user's given plug number if device.ident_str == "Eaton Managed ePDU": port_num = str(int(((device.has_switches) and "%s:%s"%(t[len(t)-3], t[len(t)-1]) or "%s"%(t[len(t)-1]))) + 1) # Plug indexing start from zero, so we add '1' # for the user's exposed plug number port_name = str(int(x[1].strip('"')) + 1) port_status = "" result[port_num] = (port_name, port_status) else: # Switched ePDU do not propose an outletCount OID! # Invalid status (ie value == '0'), retrieved via the walk, # means the outlet is absent port_num = str(outletCount) port_name = str(outletCount) port_status = "" if status != '0': result[port_num] = (port_name, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["switch"]["default"] = 1 all_opt["power_wait"]["default"] = 2 all_opt["snmp_version"]["default"] = "1" all_opt["community"]["default"] = "private" options = check_input(device_opt, process_input(device_opt)) # Plug indexing start from zero on ePDU Managed, so we substract '1' from # the user's given plug number. # For Switched ePDU, we will add this back again later. if "--plug" in options and options["--plug"].isdigit(): options["--plug"] = str(int(options["--plug"]) - 1) docs = {} docs["shortdesc"] = "Fence agent for Eaton over SNMP" docs["longdesc"] = "fence_eaton_snmp is an I/O Fencing agent \ which can be used with the Eaton network power switch. It logs \ into a device via SNMP and reboots a specified outlet. It supports \ SNMP v1 and v3 with all combinations of authenticity/privacy settings." docs["vendorurl"] = "http://powerquality.eaton.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/emerson/fence_emerson.py b/fence/agents/emerson/fence_emerson.py index ed5b5ae5..2e65cda0 100644 --- a/fence/agents/emerson/fence_emerson.py +++ b/fence/agents/emerson/fence_emerson.py @@ -1,68 +1,62 @@ #!@PYTHON@ -tt import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Emerson SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### STATUSES_OID = ".1.3.6.1.4.1.476.1.42.3.8.50.20.1.95" CONTROL_OID = ".1.3.6.1.4.1.476.1.42.3.8.50.20.1.100" NAMES_OID = ".1.3.6.1.4.1.476.1.42.3.8.50.20.1.10" # Status constants returned as value from SNMP STATUS_DOWN = 1 STATUS_UP = 2 # Status constants to set as value to SNMP STATUS_SET_OFF = 0 STATUS_SET_ON = 1 def get_power_status(conn, options): (_, status) = conn.get("%s.%s"% (STATUSES_OID, options["--plug"])) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): conn.set("%s.%s" % (CONTROL_OID, options["--plug"]), (options["--action"] == "on" and STATUS_SET_ON or STATUS_SET_OFF)) def get_outlets_status(conn, _): result = {} res_outlet = conn.walk(STATUSES_OID, 30) for outlet_info in res_outlet: port_num = ".".join(outlet_info[0].split('.')[-3:]) port_alias = conn.get("%s.%s"% (NAMES_OID, port_num))[1] port_status = (outlet_info[1] == str(STATUS_UP) and "on" or "off") result[port_num] = (port_alias, port_status) return result def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["power_wait"]["default"] = "5" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Emerson over SNMP" docs["longdesc"] = "fence_emerson is an I/O Fencing agent \ which can be used with MPX and MPH2 managed rack PDU." docs["vendorurl"] = "http://www.emersonnetworkpower.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/eps/fence_eps.py b/fence/agents/eps/fence_eps.py index 65de6a0b..74c89b95 100644 --- a/fence/agents/eps/fence_eps.py +++ b/fence/agents/eps/fence_eps.py @@ -1,134 +1,128 @@ #!@PYTHON@ -tt # The Following Agent Has Been Tested On: # ePowerSwitch 8M+ version 1.0.0.4 import sys, re import base64, string, socket import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_LOGIN_DENIED, EC_TIMED_OUT, run_delay if sys.version_info[0] > 2: import http.client as httplib else: import httplib -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="ePowerSwitch 8M+ (eps)" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - # Run command on EPS device. # @param options Device options # @param params HTTP GET parameters (without ?) def eps_run_command(options, params): try: # New http connection conn = httplib.HTTPConnection(options["--ip"]) request_str = "/"+options["--page"] if params != "": request_str += "?"+params logging.debug("GET %s\n", request_str) conn.putrequest('GET', request_str) if "--username" in options: if "--password" not in options: options["--password"] = "" # Default is empty password # String for Authorization header auth_str = 'Basic ' + string.strip(base64.encodestring(options["--username"]+':'+options["--password"])) logging.debug("Authorization: %s\n", auth_str) conn.putheader('Authorization', auth_str) conn.endheaders() response = conn.getresponse() logging.debug("%d %s\n", response.status, response.reason) #Response != OK -> couldn't login if response.status != 200: fail(EC_LOGIN_DENIED) result = response.read() logging.debug("%s \n", result) conn.close() except socket.timeout: fail(EC_TIMED_OUT) except socket.error: fail(EC_LOGIN_DENIED) return result def get_power_status(conn, options): del conn ret_val = eps_run_command(options, "") result = {} status = re.findall(r"p(\d{2})=(0|1)\s*\", ret_val.lower()) for out_num, out_stat in status: result[out_num] = ("", (out_stat == "1" and "on" or "off")) if not options["--action"] in ['monitor', 'list']: if not options["--plug"] in result: fail_usage("Failed: You have to enter existing physical plug!") else: return result[options["--plug"]][1] else: return result def set_power_status(conn, options): del conn eps_run_command(options, "P%s=%s"%(options["--plug"], (options["--action"] == "on" and "1" or "0"))) # Define new option def eps_define_new_opts(): all_opt["hidden_page"] = { "getopt" : "c:", "longopt" : "page", "help":"-c, --page=[page] Name of hidden page (default: hidden.htm)", "required" : "0", "shortdesc" : "Name of hidden page", "default" : "hidden.htm", "order": 1 } # Starting point of fence agent def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "hidden_page", "web"] atexit.register(atexit_handler) eps_define_new_opts() options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for ePowerSwitch" docs["longdesc"] = "fence_eps is an I/O Fencing agent \ which can be used with the ePowerSwitch 8M+ power switch to fence \ connected machines. Fence agent works ONLY on 8M+ device, because \ this is only one, which has support for hidden page feature. \ \n.TP\n\ Agent basically works by connecting to hidden page and pass \ appropriate arguments to GET request. This means, that hidden \ page feature must be enabled and properly configured." docs["vendorurl"] = "http://www.epowerswitch.com" show_docs(options, docs) run_delay(options) #Run fence action. Conn is None, beacause we always need open new http connection result = fence_action(None, options, set_power_status, get_power_status, get_power_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/gce/fence_gce.py b/fence/agents/gce/fence_gce.py new file mode 100644 index 00000000..fcb792d2 --- /dev/null +++ b/fence/agents/gce/fence_gce.py @@ -0,0 +1,110 @@ +#!@PYTHON@ -tt + +import atexit +import sys +sys.path.append("@FENCEAGENTSLIBDIR@") +from googleapiclient import discovery +from oauth2client.client import GoogleCredentials + +from fencing import * +from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay + + +def get_nodes_list(conn, options): + result = {} + try: + instanceList = conn.instances().list(project=options["--project"], zone=options["--zone"]).execute() + for instance in instanceList["items"]: + status = "unknown" + if instance["status"] == "RUNNING": + status = "on" + elif instance["status"] == "TERMINATED": + status = "off" + result[instance["id"]] = (instance["name"], status) + # TODO: check which Exceptions it can throw + except: + fail_usage("Failed: Unable to connect to GCE. Check your configuration.") + + return result + +def get_power_status(conn, options): + try: + instance = conn.instances().get(project=options["--project"], zone=options["--zone"], + instance=options["--plug"]).execute() + if instance["status"] == "RUNNING": + return "on" + elif instance["status"] == "TERMINATED": + return "off" + else: + return "unknown" + # TODO: check which Exceptions it can throw + except: + fail_usage("Failed: Unable to connect to GCE. Check your configuration.") + +def set_power_status(conn, options): + try: + if (options["--action"]=="off"): + conn.instances().stop(project=options["--project"], zone=options["--zone"], + instance=options["--plug"]).execute() + elif (options["--action"]=="on"): + conn.instances().start(project=options["--project"], zone=options["--zone"], + instance=options["--plug"]).execute() + # TODO: check which Exceptions it can throw + except : + fail_usage("Failed: Unable to connect to GCE. Check your configuration.") + +def define_new_opts(): + all_opt["zone"] = { + "getopt" : ":", + "longopt" : "zone", + "help" : "--zone=[name] Zone, e.g. us-central1-b", + "shortdesc" : "Zone.", + "required" : "0", + "order" : 2 + } + all_opt["project"] = { + "getopt" : ":", + "longopt" : "project", + "help" : "--project=[name] Project", + "shortdesc" : "Project.", + "required" : "0", + "order" : 3 + } + +def main(): + conn = None + + device_opt = ["port", "no_password", "zone", "project"] + + atexit.register(atexit_handler) + + define_new_opts() + + all_opt["power_timeout"]["default"] = "60" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for GCE (Google Cloud Engine)" + docs["longdesc"] = "fence_gce is an I/O Fencing agent for GCE (Google Cloud " \ + "Engine). It uses the googleapiclient library to connect to GCE.\n" \ + "googleapiclient can be configured with Google SDK CLI or by " \ + "executing 'gcloud auth application-default login'.\n" \ + "For instructions see: https://cloud.google.com/compute/docs/tutorials/python-guide" + docs["vendorurl"] = "http://cloud.google.com" + show_docs(options, docs) + + run_delay(options) + + try: + credentials = GoogleCredentials.get_application_default() + conn = discovery.build('compute', 'v1', credentials=credentials) + except: + fail_usage("Failed: Unable to connect to GCE. Check your configuration.") + + # Operate the fencing device + result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/fence/agents/hds_cb/fence_hds_cb.py b/fence/agents/hds_cb/fence_hds_cb.py index 51cf7176..375054cf 100755 --- a/fence/agents/hds_cb/fence_hds_cb.py +++ b/fence/agents/hds_cb/fence_hds_cb.py @@ -1,138 +1,132 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Model Modle/Firmware ## +--------------------+---------------------------+ ## (1) Main application CB2000/A0300-E-6617 ## ##### import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New Compute Blade 2000 Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="November, 2012" -#END_VERSION_GENERATION - RE_STATUS_LINE = r"^([0-9]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*$" def get_power_status(conn, options): #### Maybe should put a conn.log_expect here to make sure #### we have properly entered into the main menu conn.sendline("S") # Enter System Command Mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("PC") # Enter partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) result = {} # Status can now be obtained from the output of the PC # command. Line looks like the following: # "P Power Condition LID lamp Mode Auto power on" # "0 On Normal Off Basic Synchronized" # "1 On Normal Off Basic Synchronized" for line in conn.before.splitlines(): # populate the relevant fields based on regex partition = re.search(RE_STATUS_LINE, line) if partition != None: # find the blade number defined in args if partition.group(1) == options["--plug"]: result = partition.group(2).lower() # We must make sure we go back to the main menu as the # status is checked before any fencing operations are # executed. We could in theory save some time by staying in # the partition control, but the logic is a little cleaner # this way. conn.sendline("Q") # Back to system command mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("EX") # Back to system console main menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) return result def set_power_status(conn, options): action = { 'on' : "P", 'off': "F", 'reboot' : "H", }[options["--action"]] conn.sendline("S") # Enter System Command Mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("PC") # Enter partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("P") # Enter power control menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline(action) # Execute action from array above conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline(options["--plug"]) # Select blade number from args conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("Y") # Confirm action conn.log_expect("Hit enter key.", int(options["--shell-timeout"])) conn.sendline("") # Press the any key conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("Q") # Quit back to partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("Q") # Quit back to system command mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("EX") # Quit back to system console menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_blades_list(conn, options): outlets = {} conn.sendline("S") # Enter System Command Mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("PC") # Enter partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) # Status can now be obtained from the output of the PC # command. Line looks like the following: # "P Power Condition LID lamp Mode Auto power on" # "0 On Normal Off Basic Synchronized" # "1 On Normal Off Basic Synchronized" for line in conn.before.splitlines(): partition = re.search(RE_STATUS_LINE, line) if partition != None: outlets[partition.group(1)] = (partition.group(2), "") conn.sendline("Q") # Quit back to system command mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("EX") # Quit back to system console menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "missing_as_off", "telnet"] atexit.register(atexit_handler) all_opt["power_wait"]["default"] = "5" all_opt["cmd_prompt"]["default"] = [r"\) :"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Hitachi Compute Blade systems" docs["longdesc"] = "fence_hds_cb is an I/O Fencing agent \ which can be used with Hitachi Compute Blades with recent enough firmware that \ includes telnet support." docs["vendorurl"] = "http://www.hds.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_blades_list) fence_logout(conn, "X") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/heuristics_ping/fence_heuristics_ping.py b/fence/agents/heuristics_ping/fence_heuristics_ping.py new file mode 100644 index 00000000..e5acc96f --- /dev/null +++ b/fence/agents/heuristics_ping/fence_heuristics_ping.py @@ -0,0 +1,198 @@ +#!@PYTHON@ -tt + +# The Following Agent Has Been Tested On: +# +# RHEL 7.4 +# + +import io +import re +import subprocess +import shlex +import sys, stat +import logging +import os +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import fail_usage, run_command, fence_action, all_opt +from fencing import atexit_handler, check_input, process_input, show_docs +from fencing import run_delay + +def ping_test(con, options): + # Send pings to the targets + + if options["--action"] == "on": + # we want unfencing to always succeed + return True + + if not "--ping-targets" in options or options["--ping-targets"] == "": + # "off" was requested so fake "on" to provoke failure + logging.error("ping target required") + return False + + timeout = int(options["--ping-timeout"]) + count = int(options["--ping-count"]) + interval = int(options["--ping-interval"]) + good_required = int(options["--ping-good-count"]) + maxfail = int(options["--ping-maxfail"]) + targets = options["--ping-targets"].split(",") + exitcode = True + p = {} + failcount = 0 + # search string for parsing the results of the ping-executable + packet_count = re.compile(r".*transmitted, ([0-9]*) received.*") + + # start a ping-process per target + for target in targets: + ping_path = '@PING_CMD@' + target_mangled = target + if target.startswith('inet6:'): + if '@PING6_CMD@' == '': + p[target] = None + continue + ping_path = '@PING6_CMD@' + target_mangled = target.split(':',2)[1] + elif target.startswith('inet:'): + ping_path = '@PING4_CMD@' + target_mangled = target.split(':',2)[1] + + ping_cmd = "%s -n -q -W %d -c %d -i %d %s" % ( + ping_path, timeout, count, interval, target_mangled) + logging.info("Running command: %s", ping_cmd) + try: + p[target] = subprocess.Popen(shlex.split(ping_cmd), + stdout=subprocess.PIPE); + except OSError: + p[target] = None + + # collect the results of the ping-processes + for target in targets: + good = 0 + if p[target] != None: + p[target].wait() + if p[target].returncode == 0: + for line in p[target].stdout: + searchres = packet_count.search(line) + if searchres: + good = int(searchres.group(1)) + break + if good >= good_required: + logging.info("ping target %s received %d of %d" \ + % (target, good, count)) + continue + failcount += 1 + logging.info("ping target %s received %d of %d and thus failed" + % (target, good, count)) + else: + failcount += 1 + logging.error("ping target %s failed on OS level" % target) + + if failcount > maxfail: + exitcode = False + + return exitcode + + +def define_new_opts(): + all_opt["ping_count"] = { + "getopt" : ":", + "longopt" : "ping-count", + "required" : "0", + "help" : "--ping-count=[number] Number of ping-probes to send", + "shortdesc" : "The number of ping-probes that is being sent per target", + "default" : "10", + "order" : 1 + } + + all_opt["ping_good_count"] = { + "getopt" : ":", + "longopt" : "ping-good-count", + "required" : "0", + "help" : "--ping-good-count=[number] Number of positive ping-probes required", + "shortdesc" : "The number of positive ping-probes required to account a target as available", + "default" : "8", + "order" : 1 + } + + all_opt["ping_interval"] = { + "getopt" : ":", + "longopt" : "ping-interval", + "required" : "0", + "help" : "--ping-interval=[seconds] Seconds between ping-probes", + "shortdesc" : "The interval in seconds between ping-probes", + "default" : "1", + "order" : 1 + } + + all_opt["ping_timeout"] = { + "getopt" : ":", + "longopt" : "ping-timeout", + "required" : "0", + "help" : "--ping-timeout=[seconds] Timeout for individual ping-probes", + "shortdesc" : "The timeout in seconds till an individual ping-probe is accounted as lost", + "default" : "2", + "order" : 1 + } + + all_opt["ping_maxfail"] = { + "getopt" : ":", + "longopt" : "ping-maxfail", + "required" : "0", + "help" : "--ping-maxfail=[number] Number of failed ping-targets allowed", + "shortdesc" : "The number of failed ping-targets to still account as overall success", + "default" : "0", + "order" : 1 + } + + all_opt["ping_targets"] = { + "getopt" : ":", + "longopt" : "ping-targets", + "required" : "1", + "help" : "--ping-targets=tgt1,[inet6:]tgt2 Comma separated list of ping-targets", + "shortdesc" : "A comma separated list of ping-targets (optionally prepended by 'inet:' or 'inet6:') to be probed", + "default" : "", + "order" : 1 + } + + +def main(): + device_opt = ["no_status", "no_password", "ping_count", "ping_good_count", + "ping_interval", "ping_timeout", "ping_maxfail", "ping_targets", "method"] + define_new_opts() + atexit.register(atexit_handler) + + all_opt["method"]["default"] = "cycle" + all_opt["method"]["help"] = "-m, --method=[method] Method to fence (cycle|onoff) (Default: cycle)" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for ping-heuristic based fencing" + docs["longdesc"] = "fence_heuristics_ping uses ping-heuristics to control execution of another fence agent on the same fencing level.\ +\n.P\n\ +This is not a fence agent by itself! \ +Its only purpose is to enable/disable another fence agent that lives on the same fencing level but after fence_heuristics_ping." + docs["vendorurl"] = "" + show_docs(options, docs) + + # move ping-test to the end of the time-window set via --delay + # as to give the network time to settle after the incident that has + # caused fencing and have the results as current as possible + max_pingcheck = (int(options["--ping-count"]) - 1) * \ + int(options["--ping-interval"]) + int(options["--ping-timeout"]) + run_delay(options, reserve=max_pingcheck) + + result = fence_action(\ + None, \ + options, \ + None, \ + None, \ + reboot_cycle_fn = ping_test, + sync_set_power_fn = ping_test) + + # execute the remaining delay + run_delay(options, result=result) + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/fence/agents/hpblade/fence_hpblade.py b/fence/agents/hpblade/fence_hpblade.py index a9e712cc..b2cc94a3 100644 --- a/fence/agents/hpblade/fence_hpblade.py +++ b/fence/agents/hpblade/fence_hpblade.py @@ -1,140 +1,134 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## * HP BladeSystem c7000 Enclosure ## * HP Integrity Superdome X (BL920s) ##### import sys, re import pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="4.0.11-HP" -BUILD_DATE="(built Mon Mar 30 08:31:24 EDT 2015)" -REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." -#END_VERSION_GENERATION - def get_enclosure_type(conn, options): conn.send_eol("show enclosure info") conn.log_expect(options, options["--command-prompt"], int(options["--shell-timeout"])) type_re=re.compile(r"^\s*Enclosure Type: (\w+)(.*?)\s*$") enclosure="unknown" for line in conn.before.splitlines(): res = type_re.search(line) if res != None: enclosure=res.group(1) if enclosure == "unknown": fail(EC_GENERIC_ERROR) return enclosure.lower().strip() def get_power_status(conn, options): if options["enc_type"] == "superdome": cmd_send = "parstatus -M -p " + options["--plug"] powrestr = "^partition:\\d\\s+:\\w+\\s+/(\\w+)\\s.*$" else: cmd_send = "show server status " + options["--plug"] powrestr = "^\\s*Power: (.*?)\\s*$" conn.send_eol(cmd_send) conn.log_expect(options, options["--command-prompt"], int(options["--shell-timeout"])) power_re = re.compile(powrestr) status = "unknown" for line in conn.before.splitlines(): res = power_re.search(line) if res != None: if options["enc_type"] == "superdome": if res.group(1) == "DOWN": status = "off" else: status = "on" else: status = res.group(1) if status == "unknown": if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) return status.lower().strip() def set_power_status(conn, options): if options["enc_type"] == "superdome": dev="partition " else: dev="server " if options["--action"] == "on": conn.send_eol("poweron " + dev + options["--plug"]) elif options["--action"] == "off": conn.send_eol("poweroff " + dev + options["--plug"] + " force") conn.log_expect(options, options["--command-prompt"], int(options["--shell-timeout"])) def get_instances_list(conn, options): outlets = {} if options["enc_type"] == "superdome": cmd_send = "parstatus -P -M" listrestr = "^partition:(\\d+)\\s+:\\w+\\s+/(\\w+)\\s+:OK.*?:(\\w+)\\s*$" else: cmd_send = "show server list" listrestr = "^\\s*(\\d+)\\s+(.*?)\\s+(.*?)\\s+OK\\s+(.*?)\\s+(.*?)\\s*$" conn.send_eol(cmd_send) conn.log_expect(options, options["--command-prompt"], int(options["--shell-timeout"])) list_re = re.compile(listrestr) for line in conn.before.splitlines(): res = list_re.search(line) if res != None: if options["enc_type"] == "superdome": outlets[res.group(1)] = (res.group(3), res.group(2).lower()) else: outlets[res.group(1)] = (res.group(2), res.group(4).lower()) return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "missing_as_off", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["c7000oa>"] all_opt["login_timeout"]["default"] = "10" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP BladeSystem" docs["longdesc"] = "fence_hpblade is an I/O Fencing agent \ which can be used with HP BladeSystem and HP Integrity Superdome X. \ It logs into the onboard administrator of an enclosure via telnet or \ ssh and uses the command line interface to power blades or partitions \ on or off." docs["vendorurl"] = "http://www.hp.com" show_docs(options, docs) ## ## Operate the fencing device ###### options["eol"] = "\n" conn = fence_login(options) options["enc_type"] = get_enclosure_type(conn, options) result = fence_action(conn, options, set_power_status, get_power_status, get_instances_list) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ibmblade/fence_ibmblade.py b/fence/agents/ibmblade/fence_ibmblade.py index 11ba1698..d623fff3 100644 --- a/fence/agents/ibmblade/fence_ibmblade.py +++ b/fence/agents/ibmblade/fence_ibmblade.py @@ -1,78 +1,72 @@ #!@PYTHON@ -tt import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="IBM Blade SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # From fence_ibmblade.pl STATUSES_OID = ".1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.4" # remoteControlBladePowerState CONTROL_OID = ".1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7" # powerOnOffBlade # Status constants returned as value from SNMP STATUS_DOWN = 0 STATUS_UP = 1 # Status constants to set as value to SNMP STATUS_SET_OFF = 0 STATUS_SET_ON = 1 ### FUNCTIONS ### def get_power_status(conn, options): (_, status) = conn.get("%s.%s"% (STATUSES_OID, options["--plug"])) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): conn.set("%s.%s" % (CONTROL_OID, options["--plug"]), (options["--action"] == "on" and STATUS_SET_ON or STATUS_SET_OFF)) def get_outlets_status(conn, _): result = {} res_blades = conn.walk(STATUSES_OID, 30) for blade_info in res_blades: port_num = blade_info[0].split('.')[-1] port_alias = "" port_status = (blade_info[1] == str(STATUS_UP) and "on" or "off") result[port_num] = (port_alias, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM BladeCenter over SNMP" docs["longdesc"] = "fence_ibmblade is an I/O Fencing agent \ which can be used with IBM BladeCenter chassis. It issues SNMP Set \ request to BladeCenter chassis, rebooting, powering up or down \ the specified Blade Server." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ifmib/README b/fence/agents/ifmib/README index cd2a3c16..6fe8a2be 100644 --- a/fence/agents/ifmib/README +++ b/fence/agents/ifmib/README @@ -1,45 +1,45 @@ Intro: ------ This is an SNMP-based fencing agent for RHCS. It was designed with the use-case of disabling ethernet ports on an iSCSI SAN, but could be used to disable any port on any SNMP v1/2c/3 device that implementes the IF-MIB. The script requires NetSNMP to be installed and working on all nodes in the cluster. There are no requirements for any MIBs to be setup --- all of the required OIDs are hard-coded into the script. Since the IF-MIB is an IETF standard, these identifiers are very widely supported and will not change. Typical usage: -------------- -To use this agen with the switch used on the iSCSI network, you'll require: +To use this agent with the switch used on the iSCSI network, you'll need: 1) A managed switch running SNMP. 2) An SNMP community with write privileges. 3) Permission to send SNMP through any ACLs or firewalls from the nodes. 4) The ifIndex or ifPort associated with the ports being used by the cluster nodes. Consider a three-node cluster composed of A, B, and C. Each node has two network interfaces - one used for network and cluster communication, the second used for iSCSI traffic. If A needs to be fenced, B and C will run this script to administratively disable the switchport for A's connection to the iSCSI storage. If you are using a single interface for cluster and iSCSI traffic, this will still work, but you will lose network connectivity to the fenced host. cluster.conf: ------------- There is no GUI support for this fence agent at this time. To use it, you will need something like this cluster.conf In a node's fencing methods, you'll include a line like this: This node will be fenced by disabling the port with ifIndex 43 on the host sw1. In SNMP speak, we set IF-MIB::ifAdminStatus.43 = down(2). If you will use port name (like fc1/1), script will try to find ifIndex. diff --git a/fence/agents/ifmib/fence_ifmib.py b/fence/agents/ifmib/fence_ifmib.py index 963d3e93..d1191340 100644 --- a/fence/agents/ifmib/fence_ifmib.py +++ b/fence/agents/ifmib/fence_ifmib.py @@ -1,122 +1,116 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - Cisco MDS UROS 9134 FC (1 Slot) Chassis ("1/2/4 10 Gbps FC/Supervisor-2") Motorola, e500v2 # with BIOS 1.0.16, kickstart 4.1(1c), system 4.1(1c) # - Cisco MDS 9124 (1 Slot) Chassis ("1/2/4 Gbps FC/Supervisor-2") Motorola, e500 # with BIOS 1.0.16, kickstart 4.1(1c), system 4.1(1c) # - Partially with APC PDU (Network Management Card AOS v2.7.0, Rack PDU APP v2.7.3) # Only lance if is visible import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, array_to_dict from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="IF:MIB SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # IF-MIB trees for alias, status and port ALIASES_OID = ".1.3.6.1.2.1.31.1.1.1.18" PORTS_OID = ".1.3.6.1.2.1.2.2.1.2" STATUSES_OID = ".1.3.6.1.2.1.2.2.1.7" # Status constants returned as value from SNMP STATUS_UP = 1 STATUS_DOWN = 2 STATUS_TESTING = 3 ### GLOBAL VARIABLES ### # Port number converted from port name or index port_num = None ### FUNCTIONS ### # Convert port index or name to port index def port2index(conn, port): res = None if port.isdigit(): res = int(port) else: ports = conn.walk(PORTS_OID, 30) for x in ports: if x[1].strip('"') == port: res = int(x[0].split('.')[-1]) break if res == None: fail_usage("Can't find port with name %s!"%(port)) return res def get_power_status(conn, options): global port_num if port_num == None: port_num = port2index(conn, options["--plug"]) (_, status) = conn.get("%s.%d"%(STATUSES_OID, port_num)) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): global port_num if port_num == None: port_num = port2index(conn, options["--plug"]) conn.set("%s.%d" % (STATUSES_OID, port_num), (options["--action"] == "on" and STATUS_UP or STATUS_DOWN)) def get_outlets_status(conn, options): result = {} res_fc = conn.walk(PORTS_OID, 30) res_aliases = array_to_dict(conn.walk(ALIASES_OID, 30)) for x in res_fc: port_number = x[0].split('.')[-1] port_name = x[1].strip('"') port_alias = (port_number in res_aliases and res_aliases[port_number].strip('"') or "") port_status = "" result[port_name] = (port_alias, port_status) return result # Main agent method def main(): device_opt = ["fabric_fencing", "ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "2c" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IF MIB" docs["longdesc"] = "fence_ifmib is an I/O Fencing agent \ which can be used with any SNMP IF-MIB capable device. \ \n.P\n\ It was written with managed ethernet switches in mind, in order to \ fence iSCSI SAN connections. However, there are many devices that \ support the IF-MIB interface. The agent uses IF-MIB::ifAdminStatus \ to control the state of an interface." docs["vendorurl"] = "http://www.ietf.org/wg/concluded/ifmib.html" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ilo/fence_ilo.py b/fence/agents/ilo/fence_ilo.py index 36e2ba14..61245056 100644 --- a/fence/agents/ilo/fence_ilo.py +++ b/fence/agents/ilo/fence_ilo.py @@ -1,149 +1,143 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## iLO Version ## +---------------------------------------------+ ## iLO / firmware 1.91 / RIBCL 2.22 ## iLO2 / firmware 1.22 / RIBCL 2.22 ## iLO2 / firmware 1.50 / RIBCL 2.22 ##### import sys, re, pexpect import atexit from xml.sax.saxutils import quoteattr sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_LOGIN_DENIED -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="New ILO Agent - test release on steroids" -REDHAT_COPYRIGHT="" -BUILD_DATE="March, 2008" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send("\r\n") conn.send("\r\n") conn.send("\r\n") conn.log_expect("HOST_POWER=\"(.*?)\"", int(options["--power-timeout"])) status = conn.match.group(1) return status.lower().strip() def set_power_status(conn, options): conn.send("\r\n") conn.send("") if options.get("fw_processor", None) == "iLO2": if options["fw_version"] > 1.29: conn.send("\r\n") else: conn.send("\r\n") elif options["--ribcl-version"] < 2.21: conn.send("\r\n") else: if options["--action"] == "off": conn.send("\r\n") else: conn.send("\r\n") conn.send("\r\n") return def define_new_opts(): all_opt["ribcl"] = { "getopt" : "r:", "longopt" : "ribcl-version", "help" : "-r, --ribcl-version=[version] Force ribcl version to use", "required" : "0", "shortdesc" : "Force ribcl version to use", "order" : 1} def main(): device_opt = ["ipaddr", "login", "passwd", "ssl", "notls", "tls1.0", "ribcl"] atexit.register(atexit_handler) define_new_opts() all_opt["login_timeout"]["default"] = "10" all_opt["retry_on"]["default"] = "3" all_opt["ssl"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP iLO" docs["longdesc"] = "fence_ilo is an I/O Fencing agent \ used for HP servers with the Integrated Light Out (iLO) PCI card.\ The agent opens an SSL connection to the iLO card. Once the SSL \ connection is established, the agent is able to communicate with \ the iLO card through an XML stream." docs["vendorurl"] = "http://www.hp.com" docs["symlink"] = [("fence_ilo2", "Fence agent for HP iLO2")] show_docs(options, docs) ## ## Login and get version number #### conn = fence_login(options) try: conn.send("\r\n") conn.log_expect(["", ""], int(options["--login-timeout"])) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) except pexpect.EOF: if "--tls1.0" in options: fail(EC_LOGIN_DENIED) options["--tls1.0"] = "1" conn.close() conn = fence_login(options) try: conn.send("\r\n") conn.log_expect(["", ""], int(options["--login-timeout"])) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) except pexpect.EOF: fail(EC_LOGIN_DENIED) try: version = re.compile("= 2: conn.send("\r\n") else: conn.send("\r\n") conn.send("\r\n") if options["--ribcl-version"] >= 2: conn.send("\r\n") conn.send("\r\n") conn.log_expect(r"", int(options["--shell-timeout"])) options["fw_version"] = float(re.compile(r"FIRMWARE_VERSION\s*=\s*\"(.*?)\"", re.IGNORECASE).search(conn.before).group(1)) options["fw_processor"] = re.compile(r"MANAGEMENT_PROCESSOR\s*=\s*\"(.*?)\"", re.IGNORECASE).search(conn.before).group(1) conn.send("\r\n") except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) except pexpect.EOF: fail(EC_LOGIN_DENIED) ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status, None) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ilo_moonshot/fence_ilo_moonshot.py b/fence/agents/ilo_moonshot/fence_ilo_moonshot.py index c534947c..a066a9c9 100644 --- a/fence/agents/ilo_moonshot/fence_ilo_moonshot.py +++ b/fence/agents/ilo_moonshot/fence_ilo_moonshot.py @@ -1,69 +1,63 @@ #!@PYTHON@ -tt import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send_eol("show node list") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) nodes = {} for line in conn.before.splitlines(): if len(line.split()) == 10: nodes[line.split()[1]] = ("", line.split()[8].lower().strip()) if ["list", "monitor"].count(options["--action"]) == 1: return nodes else: try: (_, status) = nodes[options["--plug"]] return status.lower() except KeyError: fail(EC_STATUS) def set_power_status(conn, options): if options["--action"] == "on": conn.send_eol("set node power on %s" % (options["--plug"])) else: conn.send_eol("set node power off force %s" % (options["--plug"])) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "port"] atexit.register(atexit_handler) all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP Moonshot iLO" docs["longdesc"] = "" docs["vendorurl"] = "http://www.hp.com" show_docs(options, docs) conn = fence_login(options) ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ilo_mp/fence_ilo_mp.py b/fence/agents/ilo_mp/fence_ilo_mp.py index f878c5c4..1ae4d3ed 100644 --- a/fence/agents/ilo_mp/fence_ilo_mp.py +++ b/fence/agents/ilo_mp/fence_ilo_mp.py @@ -1,64 +1,58 @@ #!@PYTHON@ -tt import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send_eol("show /system1") re_state = re.compile('EnabledState=(.*)', re.IGNORECASE) conn.log_expect(re_state, int(options["--shell-timeout"])) status = conn.match.group(1).lower() if status.startswith("enabled"): return "on" else: return "off" def set_power_status(conn, options): if options["--action"] == "on": conn.send_eol("start /system1") else: conn.send_eol("stop -f /system1") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] all_opt["power_wait"]["default"] = 5 options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP iLO MP" docs["longdesc"] = "" docs["vendorurl"] = "http://www.hp.com" show_docs(options, docs) conn = fence_login(options) conn.send_eol("SMCLP") ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ilo_ssh/fence_ilo_ssh.py b/fence/agents/ilo_ssh/fence_ilo_ssh.py index fb2d9f3f..b0366157 100644 --- a/fence/agents/ilo_ssh/fence_ilo_ssh.py +++ b/fence/agents/ilo_ssh/fence_ilo_ssh.py @@ -1,74 +1,68 @@ #!@PYTHON@ -tt import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send_eol("show /system1") re_state = re.compile('EnabledState=(.*)', re.IGNORECASE) conn.log_expect(re_state, int(options["--shell-timeout"])) status = conn.match.group(1).lower() if status.startswith("enabled"): return "on" else: return "off" def set_power_status(conn, options): if options["--action"] == "on": conn.send_eol("start /system1") else: conn.send_eol("power off hard") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def reboot_cycle(conn, options): conn.send_eol("reset hard /system1") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "method", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] all_opt["power_wait"]["default"] = 5 options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP iLO over SSH" docs["longdesc"] = "fence_ilo_ssh is a fence agent that connects to iLO device. It logs into \ device via ssh and reboot a specified outlet. " docs["vendorurl"] = "http://www.hp.com" docs["symlink"] = [("fence_ilo3_ssh", "Fence agent for HP iLO3 over SSH"), ("fence_ilo4_ssh", "Fence agent for HP iLO4 over SSH")] show_docs(options, docs) options["eol"] = "\r" conn = fence_login(options) conn.send_eol("SMCLP") ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status, None, reboot_cycle) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/intelmodular/fence_intelmodular.py b/fence/agents/intelmodular/fence_intelmodular.py index 4e1301c9..294ea4dd 100644 --- a/fence/agents/intelmodular/fence_intelmodular.py +++ b/fence/agents/intelmodular/fence_intelmodular.py @@ -1,92 +1,86 @@ #!@PYTHON@ -tt # Tested with an Intel MFSYS25 using firmware package 2.6 Should work with an # MFSYS35 as well. # # Notes: # # The manual and firmware release notes says SNMP is read only. This is not # true, as per the MIBs that ship with the firmware you can write to # the bladePowerLed oid to control the servers. # # Thanks Matthew Kent for original agent and testing. import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Intel Modular SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # From INTELCORPORATION-MULTI-FLEX-SERVER-BLADES-MIB.my that ships with # firmware updates STATUSES_OID = ".1.3.6.1.4.1.343.2.19.1.2.10.202.1.1.6" # Status constants returned as value from SNMP STATUS_UP = 2 STATUS_DOWN = 0 # Status constants to set as value to SNMP STATUS_SET_ON = 2 STATUS_SET_OFF = 3 ### FUNCTIONS ### def get_power_status(conn, options): (_, status) = conn.get("%s.%s"% (STATUSES_OID, options["--plug"])) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): conn.set("%s.%s" % (STATUSES_OID, options["--plug"]), (options["--action"] == "on" and STATUS_SET_ON or STATUS_SET_OFF)) def get_outlets_status(conn, options): result = {} res_blades = conn.walk(STATUSES_OID, 30) for x in res_blades: port_num = x[0].split('.')[-1] port_alias = "" port_status = (x[1] == str(STATUS_UP) and "on" or "off") result[port_num] = (port_alias, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", "port", "snmp_version", "snmp"] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Intel Modular" docs["longdesc"] = "fence_intelmodular is an I/O Fencing agent \ which can be used with Intel Modular device (tested on Intel MFSYS25, should \ work with MFSYS35 as well). \ \n.P\n\ Note: Since firmware update version 2.7, SNMP v2 write support is \ removed, and replaced by SNMP v3 support. So agent now has default \ SNMP version 3. If you are using older firmware, please supply -d \ for command line and snmp_version option for your cluster.conf." docs["vendorurl"] = "http://www.intel.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ipdu/fence_ipdu.py b/fence/agents/ipdu/fence_ipdu.py index 7e648598..da34d2b6 100644 --- a/fence/agents/ipdu/fence_ipdu.py +++ b/fence/agents/ipdu/fence_ipdu.py @@ -1,159 +1,153 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # IBM iPDU model 46M4002 # Firmware release OPDP_sIBM_v01.2_1 # import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="IBM iPDU SNMP fence agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see IBM iPDU device = None # Port ID port_id = None # Switch ID switch_id = None # Classes describing Device params class IBMiPDU(object): # iPDU status_oid = '.1.3.6.1.4.1.2.6.223.8.2.2.1.11.%d' control_oid = '.1.3.6.1.4.1.2.6.223.8.2.2.1.11.%d' outlet_table_oid = '.1.3.6.1.4.1.2.6.223.8.2.2.1.2' ident_str = "IBM iPDU" state_on = 1 state_off = 0 turn_on = 1 turn_off = 0 has_switches = False ### FUNCTIONS ### def ipdu_set_device(conn, options): global device agents_dir = {'.1.3.6.1.4.1.2.6.223':IBMiPDU, None:IBMiPDU} # First resolve type of PDU device pdu_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(pdu_type) == 1) and (pdu_type[0][1] in agents_dir)): pdu_type = [[None, None]] device = agents_dir[pdu_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def ipdu_resolv_port_id(conn, options): global port_id, switch_id if device == None: ipdu_set_device(conn, options) # Now we resolv port_id/switch_id if options["--plug"].isdigit() and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.outlet_table_oid, 30) for x in table: if x[1].strip('"') == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: fail_usage("Can't find port with name %s!"%(options["--plug"])) def get_power_status(conn, options): if port_id == None: ipdu_resolv_port_id(conn, options) oid = ((device.has_switches) and device.status_oid%(switch_id, port_id) or device.status_oid%(port_id)) (oid, status) = conn.get(oid) return status == str(device.state_on) and "on" or "off" def set_power_status(conn, options): if port_id == None: ipdu_resolv_port_id(conn, options) oid = ((device.has_switches) and device.control_oid%(switch_id, port_id) or device.control_oid%(port_id)) conn.set(oid, (options["--action"] == "on" and device.turn_on or device.turn_off)) def get_outlets_status(conn, options): result = {} if device == None: ipdu_set_device(conn, options) res_ports = conn.walk(device.outlet_table_oid, 30) for x in res_ports: t = x[0].split('.') port_num = ((device.has_switches) and "%s:%s"%(t[len(t)-3], t[len(t)-1]) or "%s"%(t[len(t)-1])) port_name = x[1].strip('"') port_status = "" result[port_num] = (port_name, port_status) return result # Main agent method def main(): global device device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "3" all_opt["community"]["default"] = "private" all_opt["switch"]["default"] = "1" device = IBMiPDU options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for iPDU over SNMP" docs["longdesc"] = "fence_ipdu is an I/O Fencing agent \ which can be used with the IBM iPDU network power switch. It logs \ into a device via SNMP and reboots a specified outlet. It supports \ SNMP v3 with all combinations of authenticity/privacy settings." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ipmilan/fence_ipmilan.py b/fence/agents/ipmilan/fence_ipmilan.py index 6c43b185..86caf070 100644 --- a/fence/agents/ipmilan/fence_ipmilan.py +++ b/fence/agents/ipmilan/fence_ipmilan.py @@ -1,219 +1,213 @@ #!@PYTHON@ -tt import sys, re, os import atexit from pipes import quote sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(_, options): output = _run_command(options, "status") match = re.search('[Cc]hassis [Pp]ower is [\\s]*([a-zA-Z]{2,3})', str(output)) status = match.group(1) if match else None return status def set_power_status(_, options): _run_command(options, options["--action"]) return def reboot_cycle(_, options): output = _run_command(options, "cycle") return bool(re.search('chassis power control: cycle', str(output).lower())) def reboot_diag(_, options): output = _run_command(options, "diag") return bool(re.search('chassis power control: diag', str(output).lower())) def _run_command(options, action): cmd, log_cmd = create_command(options, action) return run_command(options, cmd, log_command=log_cmd) def create_command(options, action): class Cmd: cmd = "" log = "" @classmethod def append(cls, cmd, log=None): cls.cmd += cmd cls.log += (cmd if log is None else log) # --use-sudo / -d if "--use-sudo" in options: Cmd.append(options["--sudo-path"] + " ") Cmd.append(options["--ipmitool-path"]) # --lanplus / -L if "--lanplus" in options and options["--lanplus"] in ["", "1"]: Cmd.append(" -I lanplus") else: Cmd.append(" -I lan") # --ip / -a Cmd.append(" -H " + options["--ip"]) # --port / -n if "--ipport" in options: Cmd.append(" -p " + options["--ipport"]) # --target if "--target" in options: Cmd.append(" -t " + options["--target"]) # --username / -l if "--username" in options and len(options["--username"]) != 0: Cmd.append(" -U " + quote(options["--username"])) # --auth / -A if "--auth" in options: Cmd.append(" -A " + options["--auth"]) # --password / -p if "--password" in options: Cmd.append(" -P " + quote(options["--password"]), " -P [set]") else: Cmd.append(" -P ''", " -P [set]") # --cipher / -C if "--cipher" in options: Cmd.append(" -C " + options["--cipher"]) if "--privlvl" in options: Cmd.append(" -L " + options["--privlvl"]) - if "--hexadecimal-kg" in options: - Cmd.append(" -y " + options["--hexadecimal-kg"]) + if "--hexadecimal-kg" in options: + Cmd.append(" -y " + options["--hexadecimal-kg"]) # --action / -o Cmd.append(" chassis power " + action) return (Cmd.cmd, Cmd.log) def define_new_opts(): all_opt["lanplus"] = { "getopt" : "P", "longopt" : "lanplus", "help" : "-P, --lanplus Use Lanplus to improve security of connection", "required" : "0", "default" : "0", "shortdesc" : "Use Lanplus to improve security of connection", "order": 1 } all_opt["auth"] = { "getopt" : "A:", "longopt" : "auth", "help" : "-A, --auth=[auth] IPMI Lan Auth type (md5|password|none)", "required" : "0", "shortdesc" : "IPMI Lan Auth type.", "choices" : ["md5", "password", "none"], "order": 1 } all_opt["cipher"] = { "getopt" : "C:", "longopt" : "cipher", "help" : "-C, --cipher=[cipher] Ciphersuite to use (same as ipmitool -C parameter)", "required" : "0", "shortdesc" : "Ciphersuite to use (same as ipmitool -C parameter)", "order": 1 } all_opt["privlvl"] = { "getopt" : "L:", "longopt" : "privlvl", "help" : "-L, --privlvl=[level] " "Privilege level on IPMI device (callback|user|operator|administrator)", "required" : "0", "shortdesc" : "Privilege level on IPMI device", "default" : "administrator", "choices" : ["callback", "user", "operator", "administrator"], "order": 1 } all_opt["ipmitool_path"] = { "getopt" : ":", "longopt" : "ipmitool-path", "help" : "--ipmitool-path=[path] Path to ipmitool binary", "required" : "0", "shortdesc" : "Path to ipmitool binary", "default" : "@IPMITOOL_PATH@", "order": 200 } all_opt["target"] = { "getopt" : ":", "longopt" : "target", "help" : "--target=[targetaddress] Bridge IPMI requests to the remote target address", "required" : "0", "shortdesc" : "Bridge IPMI requests to the remote target address", "order": 1 } all_opt["hexadecimal_kg"] = { "getopt" : ":", "longopt" : "hexadecimal-kg", "help" : "--hexadecimal-kg=[key] Hexadecimal-encoded Kg key for IPMIv2 authentication", "required" : "0", "shortdesc" : "Hexadecimal-encoded Kg key for IPMIv2 authentication", "order": 1 } def main(): atexit.register(atexit_handler) device_opt = ["ipaddr", "login", "no_login", "no_password", "passwd", "diag", "lanplus", "auth", "cipher", "privlvl", "sudo", "ipmitool_path", "method", "target", "hexadecimal_kg"] define_new_opts() all_opt["power_wait"]["default"] = 2 if os.path.basename(sys.argv[0]) == "fence_ilo3": all_opt["power_wait"]["default"] = "4" all_opt["method"]["default"] = "cycle" all_opt["lanplus"]["default"] = "1" elif os.path.basename(sys.argv[0]) == "fence_ilo4": all_opt["lanplus"]["default"] = "1" all_opt["ipport"]["default"] = "623" if all_opt["method"]["default"] == "cycle": all_opt["method"]["help"] = "-m, --method=[method] Method to fence (onoff|cycle) (Default: cycle)\n" \ "WARNING! This fence agent might report success before the node is powered off. " \ "You should use -m/method onoff if your fence device works correctly with that option." options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IPMI" docs["longdesc"] = "fence_ipmilan is an I/O Fencing agent\ which can be used with machines controlled by IPMI.\ This agent calls support software ipmitool (http://ipmitool.sf.net/). \ WARNING! This fence agent might report success before the node is powered off. \ You should use -m/method onoff if your fence device works correctly with that option." docs["vendorurl"] = "" docs["symlink"] = [("fence_ilo3", "Fence agent for HP iLO3"), ("fence_ilo4", "Fence agent for HP iLO4"), ("fence_imm", "Fence agent for IBM Integrated Management Module"), ("fence_idrac", "Fence agent for Dell iDRAC")] show_docs(options, docs) run_delay(options) if not is_executable(options["--ipmitool-path"]): fail_usage("Ipmitool not found or not accessible") reboot_fn = reboot_cycle if options["--action"] == "diag": # Diag is a special action that can't be verified so we will reuse reboot functionality # to minimize impact on generic library options["--action"] = "reboot" options["--method"] = "cycle" reboot_fn = reboot_diag result = fence_action(None, options, set_power_status, get_power_status, None, reboot_fn) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ironic/fence_ironic.py b/fence/agents/ironic/fence_ironic.py index 8e5d5b0f..66d84fca 100644 --- a/fence/agents/ironic/fence_ironic.py +++ b/fence/agents/ironic/fence_ironic.py @@ -1,136 +1,130 @@ #!@PYTHON@ -tt import atexit import logging import os import re import sys from pipes import quote sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_name_or_uuid(options): return options["--uuid"] if "--uuid" in options else options["--plug"] def get_power_status(_, options): output = ironic_run_command(options, "status") stdout = output[1] match = re.search('power[\\s]*([a-zA-Z]{2,3})', str(stdout)) status = match.group(1) if match else None return status def set_power_status(_, options): ironic_run_command(options, options["--action"]) return def get_devices_list(_, options): nodes = {} output = ironic_run_command(options, "list") stdout = output[1] for line in stdout.splitlines(): uuid = "UUID" try: (uuid, name, state) = line.split(',') except ValueError: pass if "UUID" in uuid: continue # skip line header match = re.search('power[\\s]*([a-zA-Z]{2,3})', state) status = match.group(1) if match else None nodes[uuid] = (name, status) return nodes def ironic_run_command(options, action, timeout=None): cmd = options["--openstack-path"] + " baremetal" env = os.environ.copy() # --username / -l if "--username" in options and len(options["--username"]) != 0: env["OS_USERNAME"] = options["--username"] # --password / -p if "--password" in options: env["OS_PASSWORD"] = options["--password"] # --tenant-name -t if "--tenant-name" in options: env["OS_TENANT_NAME"] = options["--tenant-name"] # --auth-url if "--auth-url" in options: env["OS_AUTH_URL"] = options["--auth-url"] # --action / -o if action == "status": cmd += " show %s -c power_state --format value" % (get_name_or_uuid(options)) elif action in ["on", "off"]: cmd += " power %s %s" % (action, get_name_or_uuid(options)) elif action == "list": cmd += " list -c 'Instance UUID' -c Name -c 'Power State' --format csv --quote minimal" logging.debug("cmd -> %s" % cmd) return run_command(options, cmd, timeout, env) def define_new_opts(): all_opt["auth-url"] = { "getopt" : ":", "longopt" : "auth-url", "help" : "--auth-url=[authurl] Auth URL", "required" : "1", "shortdesc" : "Keystone Admin Auth URL", "order": 1 } all_opt["tenant-name"] = { "getopt" : "t:", "longopt" : "tenant-name", "help" : "-t, --tenant-name=[tenant] Tenantname", "required" : "0", "shortdesc" : "Keystone Admin Tenant", "default": "admin", "order": 1 } all_opt["openstack-path"] = { "getopt" : ":", "longopt" : "openstack-path", "help" : "--openstack-path=[path] Path to openstack binary", "required" : "0", "shortdesc" : "Path to the OpenStack binary", "default" : "@OPENSTACK_PATH@", "order": 200 } def main(): atexit.register(atexit_handler) device_opt = ["login", "passwd", "port", "auth-url", "tenant-name", "openstack-path"] define_new_opts() options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for OpenStack's Ironic (Bare Metal as a service) service" docs["longdesc"] = "fence_ironic is a Fencing agent \ which can be used with machines controlled by the Ironic service. \ This agent calls the openstack CLI. \ WARNING! This fence agent is not intended for production use. Relying on a functional ironic service for fencing is not a good design choice." docs["vendorurl"] = "https://wiki.openstack.org/wiki/Ironic" show_docs(options, docs) run_delay(options) if not is_executable(options["--openstack-path"]): fail_usage("openstack tool not found or not accessible") result = fence_action(None, options, set_power_status, get_power_status, get_devices_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/ldom/fence_ldom.py b/fence/agents/ldom/fence_ldom.py index b4d87249..0cb3320b 100644 --- a/fence/agents/ldom/fence_ldom.py +++ b/fence/agents/ldom/fence_ldom.py @@ -1,108 +1,102 @@ #!@PYTHON@ -tt ## ## The Following Agent Has Been Tested On - LDOM 1.0.3 ## The interface is backward compatible so it will work ## with 1.0, 1.0.1 and .2 too. ## ##### import sys, re, pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Logical Domains (LDoms) fence Agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - COMMAND_PROMPT_REG = r"\[PEXPECT\]$" COMMAND_PROMPT_NEW = "[PEXPECT]" # Start comunicating after login. Prepare good environment. def start_communication(conn, options): conn.send_eol("PS1='" + COMMAND_PROMPT_NEW + "'") res = conn.expect([pexpect.TIMEOUT, COMMAND_PROMPT_REG], int(options["--shell-timeout"])) if res == 0: #CSH stuff conn.send_eol("set prompt='" + COMMAND_PROMPT_NEW + "'") conn.log_expect(COMMAND_PROMPT_REG, int(options["--shell-timeout"])) def get_power_status(conn, options): start_communication(conn, options) conn.send_eol("ldm ls") conn.log_expect(COMMAND_PROMPT_REG, int(options["--shell-timeout"])) result = {} #This is status of mini finite automata. 0 = we didn't found NAME and STATE, 1 = we did fa_status = 0 for line in conn.before.splitlines(): domain = re.search(r"^(\S+)\s+(\S+)\s+.*$", line) if domain != None: if fa_status == 0 and domain.group(1) == "NAME" and domain.group(2) == "STATE": fa_status = 1 elif fa_status == 1: result[domain.group(1)] = ("", (domain.group(2).lower() == "bound" and "off" or "on")) if not options["--action"] in ['monitor', 'list']: if not options["--plug"] in result: fail_usage("Failed: You have to enter existing logical domain!") else: return result[options["--plug"]][1] else: return result def set_power_status(conn, options): start_communication(conn, options) cmd_line = "ldm "+ (options["--action"] == "on" and "start" or "stop -f") + " \"" + options["--plug"] + "\"" conn.send_eol(cmd_line) conn.log_expect(COMMAND_PROMPT_REG, int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", "port"] atexit.register(atexit_handler) all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = [r"\ $"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Sun LDOM" docs["longdesc"] = "fence_ldom is an I/O Fencing agent \ which can be used with LDoms virtual machines. This agent works \ so, that run ldm command on host machine. So ldm must be directly \ runnable.\ \n.P\n\ Very useful parameter is -c (or cmd_prompt in stdin mode). This \ must be set to something, what is displayed after successful login \ to host machine. Default string is space on end of string (default \ for root in bash). But (for example) csh use ], so in that case you \ must use parameter -c with argument ]. Very similar situation is, \ if you use bash and login to host machine with other user than \ root. Than prompt is $, so again, you must use parameter -c." docs["vendorurl"] = "http://www.sun.com" show_docs(options, docs) ## ## Operate the fencing device #### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "logout") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/lib/fencing.py.py b/fence/agents/lib/fencing.py.py index 4001787d..45b27894 100644 --- a/fence/agents/lib/fencing.py.py +++ b/fence/agents/lib/fencing.py.py @@ -1,1451 +1,1511 @@ #!@PYTHON@ -tt import sys, getopt, time, os, uuid, pycurl, stat import pexpect, re, syslog import logging import subprocess import threading import shlex import socket import textwrap import __main__ -## do not add code here. -#BEGIN_VERSION_GENERATION -RELEASE_VERSION = "New fence lib agent - test release on steroids" -REDHAT_COPYRIGHT = "" -BUILD_DATE = "March, 2008" -#END_VERSION_GENERATION +RELEASE_VERSION = "@RELEASE_VERSION@" __all__ = ['atexit_handler', 'check_input', 'process_input', 'all_opt', 'show_docs', 'fence_login', 'fence_action', 'fence_logout'] EC_OK = 0 EC_GENERIC_ERROR = 1 EC_BAD_ARGS = 2 EC_LOGIN_DENIED = 3 EC_CONNECTION_LOST = 4 EC_TIMED_OUT = 5 EC_WAITING_ON = 6 EC_WAITING_OFF = 7 EC_STATUS = 8 EC_STATUS_HMC = 9 EC_PASSWORD_MISSING = 10 EC_INVALID_PRIVILEGES = 11 +LOG_FORMAT = "%(asctime)-15s %(levelname)s: %(message)s" + all_opt = { "help" : { "getopt" : "h", "longopt" : "help", "help" : "-h, --help Display this help and exit", "required" : "0", "shortdesc" : "Display help and exit", "order" : 54}, "version" : { "getopt" : "V", "longopt" : "version", "help" : "-V, --version Display version information and exit", "required" : "0", "shortdesc" : "Display version information and exit", "order" : 53}, "verbose" : { "getopt" : "v", "longopt" : "verbose", "help" : "-v, --verbose Verbose mode", "required" : "0", "order" : 51}, "debug" : { "getopt" : "D:", "longopt" : "debug-file", "help" : "-D, --debug-file=[debugfile] Debugging to output file", "required" : "0", "shortdesc" : "Write debug information to given file", "order" : 52}, "delay" : { "getopt" : ":", "longopt" : "delay", "type" : "second", "help" : "--delay=[seconds] Wait X seconds before fencing is started", "required" : "0", "default" : "0", "order" : 200}, "agent" : { "getopt" : "", "help" : "", "order" : 1}, "web" : { "getopt" : "", "help" : "", "order" : 1}, "force_on" : { "getopt" : "", "help" : "", "order" : 1}, "action" : { "getopt" : "o:", "longopt" : "action", "help" : "-o, --action=[action] Action: status, reboot (default), off or on", "required" : "1", "shortdesc" : "Fencing action", "default" : "reboot", "order" : 1}, "fabric_fencing" : { "getopt" : "", "help" : "", "order" : 1}, "ipaddr" : { "getopt" : "a:", "longopt" : "ip", "help" : "-a, --ip=[ip] IP address or hostname of fencing device", "required" : "1", "order" : 1}, "ipport" : { "getopt" : "u:", "longopt" : "ipport", "type" : "integer", "help" : "-u, --ipport=[port] TCP/UDP port to use for connection", "required" : "0", "shortdesc" : "TCP/UDP port to use for connection with device", "order" : 1}, "login" : { "getopt" : "l:", "longopt" : "username", "help" : "-l, --username=[name] Login name", "required" : "?", "order" : 1}, "no_login" : { "getopt" : "", "help" : "", "order" : 1}, "no_password" : { "getopt" : "", "help" : "", "order" : 1}, "no_port" : { "getopt" : "", "help" : "", "order" : 1}, "no_status" : { "getopt" : "", "help" : "", "order" : 1}, "no_on" : { "getopt" : "", "help" : "", "order" : 1}, "no_off" : { "getopt" : "", "help" : "", "order" : 1}, "telnet" : { "getopt" : "", "help" : "", "order" : 1}, "diag" : { "getopt" : "", "help" : "", "order" : 1}, "passwd" : { "getopt" : "p:", "longopt" : "password", "help" : "-p, --password=[password] Login password or passphrase", "required" : "0", "order" : 1}, "passwd_script" : { "getopt" : "S:", "longopt" : "password-script", "help" : "-S, --password-script=[script] Script to run to retrieve password", "required" : "0", "order" : 1}, "identity_file" : { "getopt" : "k:", "longopt" : "identity-file", "help" : "-k, --identity-file=[filename] Identity file (private key) for SSH", "required" : "0", "order" : 1}, "cmd_prompt" : { "getopt" : "c:", "longopt" : "command-prompt", "help" : "-c, --command-prompt=[prompt] Force Python regex for command prompt", "required" : "0", "order" : 1}, "secure" : { "getopt" : "x", "longopt" : "ssh", "help" : "-x, --ssh Use SSH connection", "required" : "0", "order" : 1}, "ssh_options" : { "getopt" : ":", "longopt" : "ssh-options", "help" : "--ssh-options=[options] SSH options to use", "required" : "0", "order" : 1}, "ssl" : { "getopt" : "z", "longopt" : "ssl", "help" : "-z, --ssl Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "ssl_insecure" : { "getopt" : "", "longopt" : "ssl-insecure", "help" : "--ssl-insecure Use SSL connection without verifying certificate", "required" : "0", "order" : 1}, "ssl_secure" : { "getopt" : "", "longopt" : "ssl-secure", "help" : "--ssl-secure Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "notls" : { "getopt" : "t", "longopt" : "notls", "help" : "-t, --notls " "Disable TLS negotiation and force SSL3.0. " "This should only be used for devices that do not support TLS1.0 and up.", "required" : "0", "order" : 1}, "tls1.0" : { "getopt" : "", "longopt" : "tls1.0", "help" : "--tls1.0 " "Disable TLS negotiation and force TLS1.0. " "This should only be used for devices that do not support TLS1.1 and up.", "required" : "0", "order" : 1}, "port" : { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[id] " "Physical plug number on device, UUID or identification of machine", "required" : "1", "order" : 1}, "switch" : { "getopt" : "s:", "longopt" : "switch", "help" : "-s, --switch=[id] Physical switch number on device", "required" : "0", "order" : 1}, "exec" : { "getopt" : "e:", "longopt" : "exec", "help" : "-e, --exec=[command] Command to execute", "required" : "0", "order" : 1}, "vmware_type" : { "getopt" : "d:", "longopt" : "vmware_type", "help" : "-d, --vmware_type=[type] Type of VMware to connect", "required" : "0", "order" : 1}, "vmware_datacenter" : { "getopt" : "s:", "longopt" : "vmware-datacenter", "help" : "-s, --vmware-datacenter=[dc] VMWare datacenter filter", "required" : "0", "order" : 2}, "snmp_version" : { "getopt" : "d:", "longopt" : "snmp-version", "help" : "-d, --snmp-version=[version] Specifies SNMP version to use (1|2c|3)", "required" : "0", "shortdesc" : "Specifies SNMP version to use", "choices" : ["1", "2c", "3"], "order" : 1}, "community" : { "getopt" : "c:", "longopt" : "community", "help" : "-c, --community=[community] Set the community string", "required" : "0", "order" : 1}, "snmp_auth_prot" : { "getopt" : "b:", "longopt" : "snmp-auth-prot", "help" : "-b, --snmp-auth-prot=[prot] Set authentication protocol (MD5|SHA)", "required" : "0", "shortdesc" : "Set authentication protocol", "choices" : ["MD5", "SHA"], "order" : 1}, "snmp_sec_level" : { "getopt" : "E:", "longopt" : "snmp-sec-level", "help" : "-E, --snmp-sec-level=[level] " "Set security level (noAuthNoPriv|authNoPriv|authPriv)", "required" : "0", "shortdesc" : "Set security level", "choices" : ["noAuthNoPriv", "authNoPriv", "authPriv"], "order" : 1}, "snmp_priv_prot" : { "getopt" : "B:", "longopt" : "snmp-priv-prot", "help" : "-B, --snmp-priv-prot=[prot] Set privacy protocol (DES|AES)", "required" : "0", "shortdesc" : "Set privacy protocol", "choices" : ["DES", "AES"], "order" : 1}, "snmp_priv_passwd" : { "getopt" : "P:", "longopt" : "snmp-priv-passwd", "help" : "-P, --snmp-priv-passwd=[pass] Set privacy protocol password", "required" : "0", "order" : 1}, "snmp_priv_passwd_script" : { "getopt" : "R:", "longopt" : "snmp-priv-passwd-script", "help" : "-R, --snmp-priv-passwd-script Script to run to retrieve privacy password", "required" : "0", "order" : 1}, "inet4_only" : { "getopt" : "4", "longopt" : "inet4-only", "help" : "-4, --inet4-only Forces agent to use IPv4 addresses only", "required" : "0", "order" : 1}, "inet6_only" : { "getopt" : "6", "longopt" : "inet6-only", "help" : "-6, --inet6-only Forces agent to use IPv6 addresses only", "required" : "0", "order" : 1}, "separator" : { "getopt" : "C:", "longopt" : "separator", "help" : "-C, --separator=[char] Separator for CSV created by 'list' operation", "default" : ",", "required" : "0", "order" : 100}, "login_timeout" : { "getopt" : ":", "longopt" : "login-timeout", "type" : "second", "help" : "--login-timeout=[seconds] Wait X seconds for cmd prompt after login", "default" : "5", "required" : "0", "order" : 200}, "shell_timeout" : { "getopt" : ":", "longopt" : "shell-timeout", "type" : "second", "help" : "--shell-timeout=[seconds] Wait X seconds for cmd prompt after issuing command", "default" : "3", "required" : "0", "order" : 200}, "power_timeout" : { "getopt" : ":", "longopt" : "power-timeout", "type" : "second", "help" : "--power-timeout=[seconds] Test X seconds for status change after ON/OFF", "default" : "20", "required" : "0", "order" : 200}, "power_wait" : { "getopt" : ":", "longopt" : "power-wait", "type" : "second", "help" : "--power-wait=[seconds] Wait X seconds after issuing ON/OFF", "default" : "0", "required" : "0", "order" : 200}, "missing_as_off" : { "getopt" : "", "longopt" : "missing-as-off", "help" : "--missing-as-off Missing port returns OFF instead of failure", "required" : "0", "order" : 200}, "retry_on" : { "getopt" : ":", "longopt" : "retry-on", "type" : "integer", "help" : "--retry-on=[attempts] Count of attempts to retry power on", "default" : "1", "required" : "0", "order" : 201}, "session_url" : { "getopt" : "s:", "longopt" : "session-url", "help" : "-s, --session-url URL to connect to XenServer on", "required" : "1", "order" : 1}, "sudo" : { "getopt" : "", "longopt" : "use-sudo", "help" : "--use-sudo Use sudo (without password) when calling 3rd party software", "required" : "0", "order" : 205}, "method" : { "getopt" : "m:", "longopt" : "method", "help" : "-m, --method=[method] Method to fence (onoff|cycle) (Default: onoff)", "required" : "0", "shortdesc" : "Method to fence", "default" : "onoff", "choices" : ["onoff", "cycle"], "order" : 1}, "telnet_path" : { "getopt" : ":", "longopt" : "telnet-path", "help" : "--telnet-path=[path] Path to telnet binary", "required" : "0", "default" : "@TELNET_PATH@", "order": 300}, "ssh_path" : { "getopt" : ":", "longopt" : "ssh-path", "help" : "--ssh-path=[path] Path to ssh binary", "required" : "0", "default" : "@SSH_PATH@", "order": 300}, "gnutlscli_path" : { "getopt" : ":", "longopt" : "gnutlscli-path", "help" : "--gnutlscli-path=[path] Path to gnutls-cli binary", "required" : "0", "default" : "@GNUTLSCLI_PATH@", "order": 300}, "sudo_path" : { "getopt" : ":", "longopt" : "sudo-path", "help" : "--sudo-path=[path] Path to sudo binary", "required" : "0", "default" : "@SUDO_PATH@", "order": 300}, "snmpwalk_path" : { "getopt" : ":", "longopt" : "snmpwalk-path", "help" : "--snmpwalk-path=[path] Path to snmpwalk binary", "required" : "0", "default" : "@SNMPWALK_PATH@", "order" : 300}, "snmpset_path" : { "getopt" : ":", "longopt" : "snmpset-path", "help" : "--snmpset-path=[path] Path to snmpset binary", "required" : "0", "default" : "@SNMPSET_PATH@", "order" : 300}, "snmpget_path" : { "getopt" : ":", "longopt" : "snmpget-path", "help" : "--snmpget-path=[path] Path to snmpget binary", "required" : "0", "default" : "@SNMPGET_PATH@", "order" : 300}, "snmp": { "getopt" : "", "help" : "", "order" : 1}, "port_as_ip": { "getopt" : "", "longopt" : "port-as-ip", "help" : "--port-as-ip Make \"port/plug\" to be an alias to IP address", "required" : "0", "order" : 200}, "on_target": { "getopt" : "", "help" : "", "order" : 1}, "quiet": { "getopt" : "q", "longopt": "quiet", - "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug logging to syslog.", + "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.", "required" : "0", "order" : 50} } # options which are added automatically if 'key' is encountered ("default" is always added) DEPENDENCY_OPT = { "default" : ["help", "debug", "verbose", "version", "action", "agent", \ "power_timeout", "shell_timeout", "login_timeout", "power_wait", "retry_on", \ "delay", "quiet"], "passwd" : ["passwd_script"], "sudo" : ["sudo_path"], "secure" : ["identity_file", "ssh_options", "ssh_path"], "telnet" : ["telnet_path"], "ipaddr" : ["ipport", "inet4_only", "inet6_only"], "port" : ["separator"], "ssl" : ["ssl_secure", "ssl_insecure", "gnutlscli_path"], "snmp" : ["snmp_auth_prot", "snmp_sec_level", "snmp_priv_prot", \ "snmp_priv_passwd", "snmp_priv_passwd_script", "community", \ "snmpset_path", "snmpget_path", "snmpwalk_path"] } class fspawn(pexpect.spawn): - def __init__(self, options, command): + def __init__(self, options, command, **kwargs): + if sys.version_info[0] > 2: + kwargs.setdefault('encoding', 'utf-8') logging.info("Running command: %s", command) - pexpect.spawn.__init__(self, command) + pexpect.spawn.__init__(self, command, **kwargs) self.opt = options def log_expect(self, pattern, timeout): result = self.expect(pattern, timeout) logging.debug("Received: %s", self.before + self.after) return result def send(self, message): logging.debug("Sent: %s", message) return pexpect.spawn.send(self, message) # send EOL according to what was detected in login process (telnet) def send_eol(self, message): return self.send(message + self.opt["eol"]) +def frun(command, timeout=30, withexitstatus=False, events=None, + extra_args=None, logfile=None, cwd=None, env=None, **kwargs): + if sys.version_info[0] > 2: + kwargs.setdefault('encoding', 'utf-8') + return pexpect.run(command, timeout=timeout, + withexitstatus=withexitstatus, events=events, + extra_args=extra_args, logfile=logfile, cwd=cwd, + env=env, **kwargs) + def atexit_handler(): try: sys.stdout.close() os.close(1) except IOError: logging.error("%s failed to close standard output\n", sys.argv[0]) sys.exit(EC_GENERIC_ERROR) def _add_dependency_options(options): ## Add also options which are available for every fence agent added_opt = [] for opt in options + ["default"]: if opt in DEPENDENCY_OPT: added_opt.extend([y for y in DEPENDENCY_OPT[opt] if options.count(y) == 0]) if not "port" in (options + added_opt) and \ not "nodename" in (options + added_opt) and \ "ipaddr" in (options + added_opt): added_opt.append("port_as_ip") all_opt["port"]["help"] = "-n, --plug=[ip] IP address or hostname of fencing device " \ "(together with --port-as-ip)" return added_opt def fail_usage(message="", stop=True): if len(message) > 0: logging.error("%s\n", message) if stop: logging.error("Please use '-h' for usage\n") sys.exit(EC_GENERIC_ERROR) def fail(error_code): message = { EC_LOGIN_DENIED : "Unable to connect/login to fencing device", EC_CONNECTION_LOST : "Connection lost", EC_TIMED_OUT : "Connection timed out", EC_WAITING_ON : "Failed: Timed out waiting to power ON", EC_WAITING_OFF : "Failed: Timed out waiting to power OFF", EC_STATUS : "Failed: Unable to obtain correct plug status or plug is not available", EC_STATUS_HMC : "Failed: Either unable to obtain correct plug status, " "partition is not available or incorrect HMC version used", EC_PASSWORD_MISSING : "Failed: You have to set login password", EC_INVALID_PRIVILEGES : "Failed: The user does not have the correct privileges to do the requested action." }[error_code] + "\n" logging.error("%s\n", message) sys.exit(EC_GENERIC_ERROR) def usage(avail_opt): print("Usage:") print("\t" + os.path.basename(sys.argv[0]) + " [options]") print("Options:") sorted_list = [(key, all_opt[key]) for key in avail_opt] sorted_list.sort(key=lambda x: x[1]["order"]) for key, value in sorted_list: if len(value["help"]) != 0: print(" " + _join_wrap([value["help"]], first_indent=3)) def metadata(avail_opt, docs): # avail_opt has to be unique, if there are duplicities then they should be removed sorted_list = [(key, all_opt[key]) for key in list(set(avail_opt)) if "longopt" in all_opt[key]] # Find keys that are going to replace inconsistent names mapping = dict([(opt["longopt"].replace("-", "_"), key) for (key, opt) in sorted_list if (key != opt["longopt"].replace("-", "_"))]) new_options = [(key, all_opt[mapping[key]]) for key in mapping] sorted_list.extend(new_options) sorted_list.sort(key=lambda x: (x[1]["order"], x[0])) print("") print("") for (symlink, desc) in docs.get("symlink", []): print("") print("" + docs["longdesc"] + "") print("" + docs["vendorurl"] + "") print("") for (key, opt) in sorted_list: info = "" if key in all_opt: if key != all_opt[key].get('longopt', key).replace("-", "_"): info = "deprecated=\"1\"" else: info = "obsoletes=\"%s\"" % (mapping.get(key)) if "help" in opt and len(opt["help"]) > 0: if info != "": info = " " + info print("\t") default = "" if "default" in opt: default = "default=\"" + _encode_html_entities(str(opt["default"])) + "\" " mixed = opt["help"] ## split it between option and help text res = re.compile(r"^(.*?--\S+)\s+", re.IGNORECASE | re.S).search(mixed) if None != res: mixed = res.group(1) mixed = _encode_html_entities(mixed) if not "shortdesc" in opt: shortdesc = re.sub("\s\s+", " ", opt["help"][31:]) else: shortdesc = opt["shortdesc"] print("\t\t") if "choices" in opt: print("\t\t") for choice in opt["choices"]: print("\t\t\t") elif opt["getopt"].count(":") > 0: t = opt.get("type", "string") print("\t\t") else: print("\t\t") print("\t\t" + shortdesc + "") print("\t") print("") print("") (available_actions, _) = _get_available_actions(avail_opt) if "on" in available_actions: available_actions.remove("on") on_target = ' on_target="1"' if avail_opt.count("on_target") else '' print("\t" % (on_target, avail_opt.count("fabric_fencing"))) for action in available_actions: print("\t" % (action)) print("") print("") def process_input(avail_opt): avail_opt.extend(_add_dependency_options(avail_opt)) # @todo: this should be put elsewhere? os.putenv("LANG", "C") os.putenv("LC_ALL", "C") if "port_as_ip" in avail_opt: avail_opt.append("port") if len(sys.argv) > 1: opt = _parse_input_cmdline(avail_opt) else: opt = _parse_input_stdin(avail_opt) if "--port-as-ip" in opt and "--plug" in opt: opt["--ip"] = opt["--plug"] return opt ## ## This function checks input and answers if we want to have same answers ## in each of the fencing agents. It looks for possible errors and run ## password script to set a correct password ###### def check_input(device_opt, opt, other_conditions = False): device_opt.extend(_add_dependency_options(device_opt)) options = dict(opt) options["device_opt"] = device_opt _update_metadata(options) options = _set_default_values(options) options["--action"] = options["--action"].lower() ## In special cases (show help, metadata or version) we don't need to check anything ##### # OCF compatibility if options["--action"] == "meta-data": options["--action"] = "metadata" if options["--action"] == "metadata" or any(k in options for k in ("--help", "--version")): return options if "--verbose" in options: logging.getLogger().setLevel(logging.DEBUG) + formatter = logging.Formatter(LOG_FORMAT) + ## add logging to syslog logging.getLogger().addHandler(SyslogLibHandler()) if "--quiet" not in options: ## add logging to stderr - logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) + stderrHandler = logging.StreamHandler(sys.stderr) + stderrHandler.setFormatter(formatter) + logging.getLogger().addHandler(stderrHandler) (acceptable_actions, _) = _get_available_actions(device_opt) if 1 == device_opt.count("fabric_fencing"): acceptable_actions.extend(["enable", "disable"]) if 0 == acceptable_actions.count(options["--action"]): fail_usage("Failed: Unrecognised action '" + options["--action"] + "'") ## Compatibility layer ##### if options["--action"] == "enable": options["--action"] = "on" if options["--action"] == "disable": options["--action"] = "off" if options["--action"] == "validate-all" and not other_conditions: if not _validate_input(options, False): fail_usage("validate-all failed") sys.exit(EC_OK) else: _validate_input(options, True) if "--debug-file" in options: try: debug_file = logging.FileHandler(options["--debug-file"]) debug_file.setLevel(logging.DEBUG) + debug_file.setFormatter(formatter) logging.getLogger().addHandler(debug_file) except IOError: logging.error("Unable to create file %s", options["--debug-file"]) fail_usage("Failed: Unable to create file " + options["--debug-file"]) if "--snmp-priv-passwd-script" in options: options["--snmp-priv-passwd"] = os.popen(options["--snmp-priv-passwd-script"]).read().rstrip() if "--password-script" in options: options["--password"] = os.popen(options["--password-script"]).read().rstrip() return options ## Obtain a power status from possibly more than one plug ## "on" is returned if at least one plug is ON ###### def get_multi_power_fn(connection, options, get_power_fn): status = "off" plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug plug_status = get_power_fn(connection, options) if plug_status != "off": status = plug_status return status -def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts=1): +def async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts): plugs = options["--plugs"] if "--plugs" in options else [""] for _ in range(retry_attempts): for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug set_power_fn(connection, options) time.sleep(int(options["--power-wait"])) for _ in range(int(options["--power-timeout"])): if get_multi_power_fn(connection, options, get_power_fn) != options["--action"]: time.sleep(1) else: return True return False +def sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts): + success = True + plugs = options["--plugs"] if "--plugs" in options else [""] + + for plug in plugs: + try: + options["--uuid"] = str(uuid.UUID(plug)) + except ValueError: + pass + except KeyError: + pass + + options["--plug"] = plug + for retry in range(retry_attempts): + if sync_set_power_fn(connection, options): + break + if retry == retry_attempts-1: + success = False + time.sleep(int(options["--power-wait"])) + + return success + + +def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, retry_attempts=1): + + if set_power_fn != None: + if get_power_fn != None: + return async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts) + elif sync_set_power_fn != None: + return sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts) + + return False + + def show_docs(options, docs=None): device_opt = options["device_opt"] if docs == None: docs = {} docs["shortdesc"] = "Fence agent" docs["longdesc"] = "" if "--help" in options: usage(device_opt) sys.exit(0) if options.get("--action", "") == "metadata": if "port_as_ip" in device_opt: device_opt.remove("separator") metadata(device_opt, docs) sys.exit(0) if "--version" in options: - print(__main__.RELEASE_VERSION, __main__.BUILD_DATE) - print(__main__.REDHAT_COPYRIGHT) + print(RELEASE_VERSION) sys.exit(0) -def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None): +def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None, sync_set_power_fn=None): result = 0 try: if "--plug" in options: options["--plugs"] = options["--plug"].split(",") ## Process options that manipulate fencing device ##### if (options["--action"] in ["list", "list-status"]) or \ ((options["--action"] == "monitor") and 1 == options["device_opt"].count("port") and \ 0 == options["device_opt"].count("port_as_ip")): if 0 == options["device_opt"].count("port"): print("N/A") elif get_outlet_list == None: ## @todo: exception? ## This is just temporal solution, we will remove default value ## None as soon as all existing agent will support this operation print("NOTICE: List option is not working on this device yet") else: options["--original-action"] = options["--action"] options["--action"] = "list" outlets = get_outlet_list(connection, options) options["--action"] = options["--original-action"] del options["--original-action"] ## keys can be numbers (port numbers) or strings (names of VM, UUID) for outlet_id in list(outlets.keys()): (alias, status) = outlets[outlet_id] if status is None or (not status.upper() in ["ON", "OFF"]): status = "UNKNOWN" status = status.upper() if options["--action"] == "list": print(outlet_id + options["--separator"] + alias) elif options["--action"] == "list-status": print(outlet_id + options["--separator"] + alias + options["--separator"] + status) return if options["--action"] == "monitor" and not "port" in options["device_opt"] and "no_status" in options["device_opt"]: # Unable to do standard monitoring because 'status' action is not available return 0 status = None if not "no_status" in options["device_opt"]: status = get_multi_power_fn(connection, options, get_power_fn) if status != "on" and status != "off": fail(EC_STATUS) if options["--action"] == status: if not (status == "on" and "force_on" in options["device_opt"]): print("Success: Already %s" % (status.upper())) return 0 if options["--action"] == "on": - if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, 1 + int(options["--retry-on"])): + if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, 1 + int(options["--retry-on"])): print("Success: Powered ON") else: fail(EC_WAITING_ON) elif options["--action"] == "off": - if set_multi_power_fn(connection, options, set_power_fn, get_power_fn): + if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn): print("Success: Powered OFF") else: fail(EC_WAITING_OFF) elif options["--action"] == "reboot": power_on = False if options.get("--method", "").lower() == "cycle" and reboot_cycle_fn is not None: for _ in range(1, 1 + int(options["--retry-on"])): if reboot_cycle_fn(connection, options): power_on = True break if not power_on: fail(EC_TIMED_OUT) else: if status != "off": options["--action"] = "off" - if not set_multi_power_fn(connection, options, set_power_fn, get_power_fn): + if not set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn): fail(EC_WAITING_OFF) options["--action"] = "on" try: - power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, int(options["--retry-on"])) + power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, int(options["--retry-on"])) except Exception as ex: # an error occured during power ON phase in reboot # fence action was completed succesfully even in that case logging.warning("%s", str(ex)) + # switch back to original action for the case it is used lateron + options["--action"] = "reboot" + if power_on == False: # this should not fail as node was fenced succesfully logging.error('Timed out waiting to power ON\n') print("Success: Rebooted") elif options["--action"] == "status": print("Status: " + status.upper()) if status.upper() == "OFF": result = 2 elif options["--action"] == "monitor": pass except pexpect.EOF: fail(EC_CONNECTION_LOST) except pexpect.TIMEOUT: fail(EC_TIMED_OUT) except pycurl.error as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) except socket.timeout as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) return result def fence_login(options, re_login_string=r"(login\s*: )|((?!Last )Login Name: )|(username: )|(User Name :)"): run_delay(options) if "eol" not in options: options["eol"] = "\r\n" if "--command-prompt" in options and type(options["--command-prompt"]) is not list: options["--command-prompt"] = [options["--command-prompt"]] try: if "--ssl" in options: conn = _open_ssl_connection(options) elif "--ssh" in options and "--identity-file" not in options: conn = _login_ssh_with_password(options, re_login_string) elif "--ssh" in options and "--identity-file" in options: conn = _login_ssh_with_identity_file(options) else: conn = _login_telnet(options, re_login_string) except pexpect.EOF as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) return conn def is_executable(path): if os.path.exists(path): stats = os.stat(path) if stat.S_ISREG(stats.st_mode) and os.access(path, os.X_OK): return True return False def run_command(options, command, timeout=None, env=None, log_command=None): if timeout is None and "--power-timeout" in options: timeout = options["--power-timeout"] if timeout is not None: timeout = float(timeout) logging.info("Executing: %s\n", log_command or command) try: process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) except OSError: fail_usage("Unable to run %s\n" % command) thread = threading.Thread(target=process.wait) thread.start() thread.join(timeout) if thread.is_alive(): process.kill() fail(EC_TIMED_OUT) status = process.wait() (pipe_stdout, pipe_stderr) = process.communicate() process.stdout.close() process.stderr.close() logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr)) return (status, pipe_stdout, pipe_stderr) -def run_delay(options): - ## Delay is important for two-node clusters fencing but we do not need to delay 'status' operations - if options["--action"] in ["off", "reboot"]: - logging.info("Delay %s second(s) before logging in to the fence device", options["--delay"]) - time.sleep(int(options["--delay"])) +def run_delay(options, reserve=0, result=0): + ## Delay is important for two-node clusters fencing + ## but we do not need to delay 'status' operations + ## and get us out quickly if we already know that we are gonna fail + ## still wanna do something right before fencing? - reserve some time + if options["--action"] in ["off", "reboot"] \ + and options["--delay"] != "0" \ + and result == 0 \ + and reserve >= 0: + time_left = 1 + int(options["--delay"]) - (time.time() - run_delay.time_start) - reserve + if time_left > 0: + logging.info("Delay %d second(s) before logging in to the fence device", time_left) + time.sleep(time_left) +# mark time when fence-agent is started +run_delay.time_start = time.time() def fence_logout(conn, logout_string, sleep=0): # Logout is not required part of fencing but we should attempt to do it properly # In some cases our 'exit' command is faster and we can not close connection as it # was already closed by fencing device try: conn.send_eol(logout_string) time.sleep(sleep) conn.close() except OSError: pass except pexpect.ExceptionPexpect: pass # Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is # in format a.b.c.d...z and returned dict has key only z def array_to_dict(array): return dict([[x[0].split(".")[-1], x[1]] for x in array]) ## Own logger handler that uses old-style syslog handler as otherwise everything is sourced ## from /dev/syslog class SyslogLibHandler(logging.StreamHandler): """ A handler class that correctly push messages into syslog """ def emit(self, record): syslog_level = { logging.CRITICAL:syslog.LOG_CRIT, logging.ERROR:syslog.LOG_ERR, logging.WARNING:syslog.LOG_WARNING, logging.INFO:syslog.LOG_INFO, logging.DEBUG:syslog.LOG_DEBUG, logging.NOTSET:syslog.LOG_DEBUG, }[record.levelno] msg = self.format(record) # syslos.syslog can not have 0x00 character inside or exception is thrown syslog.syslog(syslog_level, msg.replace("\x00", "\n")) return def _open_ssl_connection(options): gnutls_opts = "" ssl_opts = "" if "--notls" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:-VERS-TLS1.0:+VERS-SSL3.0\"" elif "--tls1.0" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:+VERS-TLS1.0:%LATEST_RECORD_VERSION\"" # --ssl is same as the --ssl-secure; it means we want to verify certificate in these cases if "--ssl-insecure" in options: ssl_opts = "--insecure" command = '%s %s %s --crlf -p %s %s' % \ (options["--gnutlscli-path"], gnutls_opts, ssl_opts, options["--ipport"], options["--ip"]) try: conn = fspawn(options, command) except pexpect.ExceptionPexpect as ex: logging.error("%s\n", str(ex)) sys.exit(EC_GENERIC_ERROR) return conn def _login_ssh_with_identity_file(options): if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -i %s -p %s' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], \ options["--identity-file"], options["--ipport"]) if "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) result = conn.log_expect(["Enter passphrase for key '" + options["--identity-file"] + "':", \ "Are you sure you want to continue connecting (yes/no)?"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") result = conn.log_expect( ["Enter passphrase for key '" + options["--identity-file"]+"':"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 0: if "--password" in options: conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) else: fail_usage("Failed: You have to enter passphrase (-p) for identity file") return conn def _login_telnet(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.log_expect(re_login, int(options["--login-timeout"])) conn.send_eol(options["--username"]) ## automatically change end of line separator screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) if re_login.search(screen) != None: options["eol"] = "\n" conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) elif re_pass.search(screen) == None: conn.log_expect(re_pass, int(options["--shell-timeout"])) try: conn.send_eol(options["--password"]) valid_password = conn.log_expect([re_login] + \ options["--command-prompt"], int(options["--shell-timeout"])) if valid_password == 0: ## password is invalid or we have to change EOL separator options["eol"] = "\r" conn.send_eol("") screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) ## after sending EOL the fence device can either show 'Login' or 'Password' if re_login.search(conn.after + screen) != None: conn.send_eol("") conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) conn.send_eol(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) except KeyError: fail(EC_PASSWORD_MISSING) return conn def _login_ssh_with_password(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -p %s -o PubkeyAuthentication=no' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], options["--ipport"]) if "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) if "telnet_over_ssh" in options: # This is for stupid ssh servers (like ALOM) which behave more like telnet # (ignore name and display login prompt) result = conn.log_expect( \ [re_login, "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") # Host identity confirm conn.log_expect(re_login, int(options["--login-timeout"])) conn.sendline(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) else: result = conn.log_expect( \ ["ssword:", "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") conn.log_expect("ssword:", int(options["--login-timeout"])) conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) return conn # # To update metadata, we change values in all_opt def _update_metadata(options): device_opt = options["device_opt"] if device_opt.count("login") and device_opt.count("no_login") == 0: all_opt["login"]["required"] = "1" else: all_opt["login"]["required"] = "0" if device_opt.count("port_as_ip"): all_opt["ipaddr"]["required"] = "0" all_opt["port"]["required"] = "0" (available_actions, default_value) = _get_available_actions(device_opt) all_opt["action"]["default"] = default_value actions_with_default = \ [x if not x == all_opt["action"]["default"] else x + " (default)" for x in available_actions] all_opt["action"]["help"] = \ "-o, --action=[action] Action: %s" % (_join_wrap(actions_with_default, last_separator=" or ")) if device_opt.count("ipport"): default_value = None default_string = None if "default" in all_opt["ipport"]: default_value = all_opt["ipport"]["default"] elif device_opt.count("web") and device_opt.count("ssl"): default_value = "80" default_string = "(default 80, 443 if --ssl option is used)" elif device_opt.count("telnet") and device_opt.count("secure"): default_value = "23" default_string = "(default 23, 22 if --ssh option is used)" else: tcp_ports = {"community" : "161", "secure" : "22", "telnet" : "23", "web" : "80", "ssl" : "443"} # all cases where next command returns multiple results are covered by previous blocks protocol = [x for x in ["community", "secure", "ssl", "web", "telnet"] if device_opt.count(x)][0] default_value = tcp_ports[protocol] if default_string is None: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use (default %s)" % \ (default_value) else: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use\n" + " "*40 + default_string def _set_default_values(options): if "ipport" in options["device_opt"]: if not "--ipport" in options: if "default" in all_opt["ipport"]: options["--ipport"] = all_opt["ipport"]["default"] elif "community" in options["device_opt"]: options["--ipport"] = "161" elif "--ssh" in options or all_opt["secure"].get("default", "0") == "1": options["--ipport"] = "22" elif "--ssl" in options or all_opt["ssl"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-secure" in options or all_opt["ssl_secure"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-insecure" in options or all_opt["ssl_insecure"].get("default", "0") == "1": options["--ipport"] = "443" elif "web" in options["device_opt"]: options["--ipport"] = "80" elif "telnet" in options["device_opt"]: options["--ipport"] = "23" if "--ipport" in options: all_opt["ipport"]["default"] = options["--ipport"] for opt in options["device_opt"]: if "default" in all_opt[opt] and not opt == "ipport": getopt_long = "--" + all_opt[opt]["longopt"] if getopt_long not in options: options[getopt_long] = all_opt[opt]["default"] return options # stop = True/False : exit fence agent when problem is encountered def _validate_input(options, stop = True): device_opt = options["device_opt"] valid_input = True if "--username" not in options and \ device_opt.count("login") and (device_opt.count("no_login") == 0): valid_input = False fail_usage("Failed: You have to set login name", stop) if device_opt.count("ipaddr") and "--ip" not in options and "--managed" not in options and "--target" not in options: valid_input = False fail_usage("Failed: You have to enter fence address", stop) if device_opt.count("no_password") == 0: if 0 == device_opt.count("identity_file"): if not ("--password" in options or "--password-script" in options): valid_input = False fail_usage("Failed: You have to enter password or password script", stop) else: if not ("--password" in options or \ "--password-script" in options or "--identity-file" in options): valid_input = False fail_usage("Failed: You have to enter password, password script or identity file", stop) if "--ssh" not in options and "--identity-file" in options: valid_input = False fail_usage("Failed: You have to use identity file together with ssh connection (-x)", stop) if "--identity-file" in options and not os.path.isfile(options["--identity-file"]): valid_input = False fail_usage("Failed: Identity file " + options["--identity-file"] + " does not exist", stop) if (0 == ["list", "list-status", "monitor"].count(options["--action"])) and \ "--plug" not in options and device_opt.count("port") and \ device_opt.count("no_port") == 0 and not device_opt.count("port_as_ip"): valid_input = False fail_usage("Failed: You have to enter plug number or machine identification", stop) if "--plug" in options and len(options["--plug"].split(",")) > 1 and \ "--method" in options and options["--method"] == "cycle": valid_input = False fail_usage("Failed: Cannot use --method cycle for more than 1 plug", stop) for failed_opt in _get_opts_with_invalid_choices(options): valid_input = False fail_usage("Failed: You have to enter a valid choice for %s from the valid values: %s" % \ ("--" + all_opt[failed_opt]["longopt"], str(all_opt[failed_opt]["choices"])), stop) for failed_opt in _get_opts_with_invalid_types(options): valid_input = False if all_opt[failed_opt]["type"] == "second": fail_usage("Failed: The value you have entered for %s is not a valid time in seconds" % \ ("--" + all_opt[failed_opt]["longopt"]), stop) else: fail_usage("Failed: The value you have entered for %s is not a valid %s" % \ ("--" + all_opt[failed_opt]["longopt"], all_opt[failed_opt]["type"]), stop) return valid_input def _encode_html_entities(text): return text.replace("&", "&").replace('"', """).replace('<', "<"). \ replace('>', ">").replace("'", "'") def _prepare_getopt_args(options): getopt_string = "" longopt_list = [] for k in options: if k in all_opt and all_opt[k]["getopt"] != ":": # getopt == ":" means that opt is without short getopt, but has value getopt_string += all_opt[k]["getopt"] elif k not in all_opt: fail_usage("Parse error: unknown option '"+k+"'") if k in all_opt and "longopt" in all_opt[k]: if all_opt[k]["getopt"].endswith(":"): longopt_list.append(all_opt[k]["longopt"] + "=") else: longopt_list.append(all_opt[k]["longopt"]) return (getopt_string, longopt_list) def _parse_input_stdin(avail_opt): opt = {} name = "" mapping_longopt_names = dict([(all_opt[o].get("longopt"), o) for o in avail_opt]) for line in sys.stdin.readlines(): line = line.strip() if (line.startswith("#")) or (len(line) == 0): continue (name, value) = (line + "=").split("=", 1) - name = name.replace("-", "_"); value = value[:-1] - if name in mapping_longopt_names: - name = mapping_longopt_names[name] + if name.replace("-", "_") in mapping_longopt_names: + name = mapping_longopt_names[name.replace("-", "_")] + elif name.replace("_", "-") in mapping_longopt_names: + name = mapping_longopt_names[name.replace("_", "-")] if avail_opt.count(name) == 0 and name in ["nodename"]: continue elif avail_opt.count(name) == 0: logging.warning("Parse error: Ignoring unknown option '%s'\n", line) continue if all_opt[name]["getopt"].endswith(":"): opt["--"+all_opt[name]["longopt"].rstrip(":")] = value elif value.lower() in ["1", "yes", "on", "true"]: opt["--"+all_opt[name]["longopt"]] = "1" else: logging.warning("Parse error: Ignoring option '%s' because it does not have value\n", name) return opt def _parse_input_cmdline(avail_opt): filtered_opts = {} _verify_unique_getopt(avail_opt) (getopt_string, longopt_list) = _prepare_getopt_args(avail_opt) try: (entered_opt, left_arg) = getopt.gnu_getopt(sys.argv[1:], getopt_string, longopt_list) if len(left_arg) > 0: logging.warning("Unused arguments on command line: %s" % (str(left_arg))) except getopt.GetoptError as error: fail_usage("Parse error: " + error.msg) for opt in avail_opt: filtered_opts.update({opt : all_opt[opt]}) # Short and long getopt names are changed to consistent "--" + long name (e.g. --username) long_opts = {} for arg_name in list(dict(entered_opt).keys()): all_key = [key for (key, value) in list(filtered_opts.items()) \ if "--" + value.get("longopt", "") == arg_name or "-" + value.get("getopt", "").rstrip(":") == arg_name][0] long_opts["--" + filtered_opts[all_key]["longopt"]] = dict(entered_opt)[arg_name] # This test is specific because it does not apply to input on stdin if "port_as_ip" in avail_opt and not "--port-as-ip" in long_opts and "--plug" in long_opts: fail_usage("Parser error: option -n/--plug is not recognized") return long_opts # for ["John", "Mary", "Eli"] returns "John, Mary and Eli" def _join2(words, normal_separator=", ", last_separator=" and "): if len(words) <= 1: return "".join(words) else: return last_separator.join([normal_separator.join(words[:-1]), words[-1]]) def _join_wrap(words, normal_separator=", ", last_separator=" and ", first_indent=42): x = _join2(words, normal_separator, last_separator) wrapper = textwrap.TextWrapper() wrapper.initial_indent = " "*first_indent wrapper.subsequent_indent = " "*40 wrapper.width = 85 wrapper.break_on_hyphens = False wrapper.break_long_words = False wrapped_text = "" for line in wrapper.wrap(x): wrapped_text += line + "\n" return wrapped_text.lstrip().rstrip("\n") def _get_opts_with_invalid_choices(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "choices" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] possible_values_upper = [y.upper() for y in all_opt[opt]["choices"]] if longopt in options: options[longopt] = options[longopt].upper() if not options["--" + all_opt[opt]["longopt"]] in possible_values_upper: options_failed.append(opt) return options_failed def _get_opts_with_invalid_types(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "type" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] if longopt in options: if all_opt[opt]["type"] in ["integer", "second"]: try: number = int(options["--" + all_opt[opt]["longopt"]]) except ValueError: options_failed.append(opt) return options_failed def _verify_unique_getopt(avail_opt): used_getopt = set() for opt in avail_opt: getopt_value = all_opt[opt].get("getopt", "").rstrip(":") if getopt_value and getopt_value in used_getopt: fail_usage("Short getopt for %s (-%s) is not unique" % (opt, getopt_value)) else: used_getopt.add(getopt_value) def _get_available_actions(device_opt): available_actions = ["on", "off", "reboot", "status", "list", "list-status", \ "monitor", "metadata", "validate-all"] default_value = "reboot" if device_opt.count("fabric_fencing"): available_actions.remove("reboot") default_value = "off" if device_opt.count("no_status"): available_actions.remove("status") if device_opt.count("no_on"): available_actions.remove("on") if device_opt.count("no_off"): available_actions.remove("off") if not device_opt.count("separator"): available_actions.remove("list") available_actions.remove("list-status") if device_opt.count("diag"): available_actions.append("diag") return (available_actions, default_value) diff --git a/fence/agents/lib/fencing_snmp.py.py b/fence/agents/lib/fencing_snmp.py.py index 9aaf52be..f9e57689 100644 --- a/fence/agents/lib/fencing_snmp.py.py +++ b/fence/agents/lib/fencing_snmp.py.py @@ -1,134 +1,128 @@ #!@PYTHON@ -tt # For example of use please see fence_cisco_mds import re, pexpect import logging from fencing import * -from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay +from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay, frun __all__ = ['FencingSnmp'] ## do not add code here. -#BEGIN_VERSION_GENERATION -RELEASE_VERSION = "" -REDHAT_COPYRIGHT = "" -BUILD_DATE = "" -#END_VERSION_GENERATION - class FencingSnmp: def __init__(self, options): self.options = options run_delay(options) def quote_for_run(self, string): return string.replace(r"'", "'\\''") def complete_missed_params(self): mapping = [[ ['snmp-priv-passwd', 'password', '!snmp-sec-level'], 'self.options["--snmp-sec-level"]="authPriv"' ], [ ['!snmp-version', 'community', '!username', '!snmp-priv-passwd', '!password'], 'self.options["--snmp-version"]="2c"' ]] for val in mapping: e = val[0] res = True for item in e: if item[0] == '!' and "--" + item[1:] in self.options: res = False break if item[0] != '!' and "--" + item[0:] not in self.options: res = False break if res: exec(val[1]) def prepare_cmd(self, command): cmd = "%s -m '' -Oeqn "% (command) self.complete_missed_params() #mapping from our option to snmpcmd option mapping = (('snmp-version', 'v'), ('community', 'c')) for item in mapping: if "--" + item[0] in self.options: cmd += " -%s '%s'"% (item[1], self.quote_for_run(self.options["--" + item[0]])) # Some options make sense only for v3 (and for v1/2c can cause "problems") if ("--snmp-version" in self.options) and (self.options["--snmp-version"] == "3"): # Mapping from our options to snmpcmd options for v3 mapping_v3 = (('snmp-auth-prot', 'a'), ('snmp-sec-level', 'l'), ('snmp-priv-prot', 'x'), \ ('snmp-priv-passwd', 'X'), ('password', 'A'), ('username', 'u')) for item in mapping_v3: if "--"+item[0] in self.options: cmd += " -%s '%s'"% (item[1], self.quote_for_run(self.options["--" + item[0]])) force_ipvx = "" if "--inet6-only" in self.options: force_ipvx = "udp6:" if "--inet4-only" in self.options: force_ipvx = "udp:" cmd += " '%s%s%s'"% (force_ipvx, self.quote_for_run(self.options["--ip"]), "--ipport" in self.options and self.quote_for_run(":" + str(self.options["--ipport"])) or "") return cmd - def run_command(self, command, additional_timemout=0): + def run_command(self, command, additional_timeout=0): try: logging.debug("%s\n", command) - (res_output, res_code) = pexpect.run(command, + (res_output, res_code) = frun(command, int(self.options["--shell-timeout"]) + int(self.options["--login-timeout"]) + - additional_timemout, True) + additional_timeout, True) if res_code == None: fail(EC_TIMED_OUT) logging.debug("%s\n", res_output) if (res_code != 0) or (re.search("^Error ", res_output, re.MULTILINE) != None): fail_usage("Returned %d: %s"% (res_code, res_output)) except pexpect.ExceptionPexpect: fail_usage("Cannot run command %s"%(command)) return res_output - def get(self, oid, additional_timemout=0): + def get(self, oid, additional_timeout=0): cmd = "%s '%s'"% (self.prepare_cmd(self.options["--snmpget-path"]), self.quote_for_run(oid)) - output = self.run_command(cmd, additional_timemout).splitlines() + output = self.run_command(cmd, additional_timeout).splitlines() return output[len(output)-1].split(None, 1) - def set(self, oid, value, additional_timemout=0): + def set(self, oid, value, additional_timeout=0): mapping = ((int, 'i'), (str, 's')) type_of_value = '' for item in mapping: if isinstance(value, item[0]): type_of_value = item[1] break cmd = "%s '%s' %s '%s'" % (self.prepare_cmd(self.options["--snmpset-path"]), self.quote_for_run(oid), type_of_value, self.quote_for_run(str(value))) - self.run_command(cmd, additional_timemout) + self.run_command(cmd, additional_timeout) - def walk(self, oid, additional_timemout=0): + def walk(self, oid, additional_timeout=0): cmd = "%s '%s'"% (self.prepare_cmd(self.options["--snmpwalk-path"]), self.quote_for_run(oid)) - output = self.run_command(cmd, additional_timemout).splitlines() + output = self.run_command(cmd, additional_timeout).splitlines() return [x.split(None, 1) for x in output if x.startswith(".")] diff --git a/fence/agents/lpar/fence_lpar.py b/fence/agents/lpar/fence_lpar.py index 5487b84c..270bbe3b 100644 --- a/fence/agents/lpar/fence_lpar.py +++ b/fence/agents/lpar/fence_lpar.py @@ -1,181 +1,175 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Version ## +---------------------------------------------+ ## Tested on HMC ## ##### import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_STATUS_HMC -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): if options["--hmc-version"] == "3": conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) try: status = re.compile("^" + options["--plug"] + ",(.*?),.*$", re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) except AttributeError: fail(EC_STATUS_HMC) elif options["--hmc-version"] in ["4", "IVM"]: conn.send("lssyscfg -r lpar -m "+ options["--managed"] + " --filter 'lpar_names=" + options["--plug"] + "'\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) try: status = re.compile(",state=(.*?),", re.IGNORECASE).search(conn.before).group(1) except AttributeError: fail(EC_STATUS_HMC) ## ## Transformation to standard ON/OFF status if possible if status in ["Running", "Open Firmware", "Shutting Down", "Starting"]: status = "on" else: status = "off" return status def set_power_status(conn, options): if options["--hmc-version"] == "3": conn.send("chsysstate -o " + options["--action"] + " -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + "\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) elif options["--hmc-version"] in ["4", "IVM"]: if options["--action"] == "on": conn.send("chsysstate -o on -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -f `lssyscfg -r lpar -F curr_profile " + " -m " + options["--managed"] + " --filter \"lpar_names=" + options["--plug"] + "\"`\n") else: conn.send("chsysstate -o shutdown -r lpar --immed" + " -m " + options["--managed"] + " -n " + options["--plug"] + "\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def get_lpar_list(conn, options): outlets = {} if options["--hmc-version"] == "3": conn.send("query_partition_names -m " + options["--managed"] + "\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) ## We have to remove first 3 lines (command + header) and last line (part of new prompt) #### res = re.search("^.+?\n(.+?\n){2}(.*)\n.*$", conn.before, re.S) if res == None: fail_usage("Unable to parse output of list command") lines = res.group(2).split("\n") for outlet_line in lines: outlets[outlet_line.rstrip()] = ("", "") elif options["--hmc-version"] == "4": conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -F name:state\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) ## We have to remove first line (command) and last line (part of new prompt) #### res = re.search("^.+?\n(.*)\n.*$", conn.before, re.S) if res == None: fail_usage("Unable to parse output of list command") lines = res.group(1).split("\n") for outlet_line in lines: try: (port, status) = outlet_line.split(":") except ValueError: fail_usage('Output does not match expected HMC version, try different one'); outlets[port] = ("", status) elif options["--hmc-version"] == "IVM": conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -F name,state\n") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) ## We have to remove first line (command) and last line (part of new prompt) #### res = re.search("^.+?\n(.*)\n.*$", conn.before, re.S) if res == None: fail_usage("Unable to parse output of list command") lines = res.group(1).split("\n") for outlet_line in lines: try: (port, status) = outlet_line.split(",") except ValueError: fail_usage('Output does not match expected HMC version, try different one'); outlets[port] = ("", status) return outlets def define_new_opts(): all_opt["managed"] = { "getopt" : "s:", "longopt" : "managed", "help" : "-s, --managed=[id] Name of the managed system", "required" : "0", "shortdesc" : "Managed system name", "order" : 1} all_opt["hmc_version"] = { "getopt" : "H:", "longopt" : "hmc-version", "help" : "-H, --hmc-version=[version] Force HMC version to use: (3|4|ivm) (default: 4)", "required" : "0", "shortdesc" : "Force HMC version to use", "default" : "4", "choices" : ["3", "4", "ivm"], "order" : 1} def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", \ "port", "managed", "hmc_version"] atexit.register(atexit_handler) define_new_opts() all_opt["login_timeout"]["default"] = "15" all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = [r":~>", r"]\$", r"\$ "] options = check_input(device_opt, process_input(device_opt), other_conditions = True) docs = {} docs["shortdesc"] = "Fence agent for IBM LPAR" docs["longdesc"] = "" docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) if "--managed" not in options: fail_usage("Failed: You have to enter name of managed system") if options["--action"] == "validate-all": sys.exit(0) ## ## Operate the fencing device #### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_lpar_list) fence_logout(conn, "quit\r\n") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/mpath/fence_mpath.py b/fence/agents/mpath/fence_mpath.py index 5426a18d..183c38a0 100644 --- a/fence/agents/mpath/fence_mpath.py +++ b/fence/agents/mpath/fence_mpath.py @@ -1,251 +1,245 @@ #!@PYTHON@ -tt import sys import stat import re import os import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail_usage, run_command, atexit_handler, check_input, process_input, show_docs from fencing import fence_action, all_opt, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_status(conn, options): del conn status = "off" for dev in options["devices"]: is_block_device(dev) if options["--key"] in get_registration_keys(options, dev): status = "on" else: logging.debug("No registration for key "\ + options["--key"] + " on device " + dev + "\n") if options["--action"] == "monitor": dev_read(options) return status def set_status(conn, options): del conn count = 0 if options["--action"] == "on": for dev in options["devices"]: is_block_device(dev) register_dev(options, dev) if options["--key"] not in get_registration_keys(options, dev): count += 1 logging.debug("Failed to register key "\ + options["--key"] + "on device " + dev + "\n") continue dev_write(options, dev) if get_reservation_key(options, dev) is None \ and not reserve_dev(options, dev) \ and get_reservation_key(options, dev) is None: count += 1 logging.debug("Failed to create reservation (key="\ + options["--key"] + ", device=" + dev + ")\n") else: dev_keys = dev_read(options) for dev in options["devices"]: is_block_device(dev) if options["--key"] in get_registration_keys(options, dev): preempt_abort(options, dev_keys[dev], dev) for dev in options["devices"]: if options["--key"] in get_registration_keys(options, dev): count += 1 logging.debug("Failed to remove key "\ + options["--key"] + " on device " + dev + "\n") continue if not get_reservation_key(options, dev): count += 1 logging.debug("No reservation exists on device " + dev + "\n") if count: logging.error("Failed to verify " + str(count) + " device(s)") sys.exit(1) #run command, returns dict, ret["err"] = exit code; ret["out"] = output def run_cmd(options, cmd): ret = {} if "--use-sudo" in options: prefix = options["--sudo-path"] + " " else: prefix = "" (ret["err"], ret["out"], _) = run_command(options, prefix + cmd) ret["out"] = "".join([i for i in ret["out"] if i is not None]) return ret # check if device exist and is block device def is_block_device(dev): if not os.path.exists(dev): fail_usage("Failed: device \"" + dev + "\" does not exist") if not stat.S_ISBLK(os.stat(dev).st_mode): fail_usage("Failed: device \"" + dev + "\" is not a block device") # cancel registration def preempt_abort(options, host, dev): cmd = options["--mpathpersist-path"] + " -o --preempt-abort --prout-type=5 --param-rk=" + host +" --param-sark=" + options["--key"] +"-d " + dev return not bool(run_cmd(options, cmd)["err"]) def register_dev(options, dev): cmd = options["--mpathpersist-path"] + " -o --register --param-sark=" + options["--key"] + " -d " + dev #cmd return code != 0 but registration can be successful return not bool(run_cmd(options, cmd)["err"]) def reserve_dev(options, dev): cmd = options["--mpathpersist-path"] + " -o --reserv --prout-type=5 --param-rk=" + options["--key"] + " -d " + dev return not bool(run_cmd(options, cmd)["err"]) def get_reservation_key(options, dev): cmd = options["--mpathpersist-path"] + " -i -r -d " + dev out = run_cmd(options, cmd) if out["err"]: fail_usage("Cannot get reservation key") match = re.search(r"\s+key\s*=\s*0x(\S+)\s+", out["out"], re.IGNORECASE) return match.group(1) if match else None def get_registration_keys(options, dev): keys = [] cmd = options["--mpathpersist-path"] + " -i -k -d " + dev out = run_cmd(options, cmd) if out["err"]: fail_usage("Cannot get registration keys") for line in out["out"].split("\n"): match = re.search(r"\s+0x(\S+)\s*", line) if match: keys.append(match.group(1)) return keys def dev_write(options, dev): file_path = options["--store-path"] + "/mpath.devices" if not os.path.isdir(options["--store-path"]): os.makedirs(options["--store-path"]) try: store_fh = open(file_path, "a+") except IOError: fail_usage("Failed: Cannot open file \""+ file_path + "\"") out = store_fh.read() if not re.search(r"^" + dev + r"\s+", out): store_fh.write(dev + "\t" + options["--key"] + "\n") store_fh.close() def dev_read(options): dev_key = {} file_path = options["--store-path"] + "/mpath.devices" try: store_fh = open(file_path, "r") except IOError: fail_usage("Failed: Cannot open file \"" + file_path + "\"") # get not empty lines from file for (device, key) in [line.strip().split() for line in store_fh if line.strip()]: dev_key[device] = key store_fh.close() return dev_key def define_new_opts(): all_opt["devices"] = { "getopt" : "d:", "longopt" : "devices", "help" : "-d, --devices=[devices] List of devices to use for current operation", "required" : "1", "shortdesc" : "List of devices to use for current operation. Devices can \ be comma-separated list of device-mapper multipath devices (eg. /dev/mapper/3600508b400105df70000e00000ac0000 or /dev/mapper/mpath1). \ Each device must support SCSI-3 persistent reservations.", "order": 1 } all_opt["key"] = { "getopt" : "k:", "longopt" : "key", "help" : "-k, --key=[key] Key to use for the current operation", "required" : "1", "shortdesc" : "Key to use for the current operation. This key should be \ unique to a node and have to be written in /etc/multipath.conf. For the \"on\" action, the key specifies the key use to \ register the local node. For the \"off\" action, this key specifies the key to \ be removed from the device(s).", "order": 1 } all_opt["mpathpersist_path"] = { "getopt" : ":", "longopt" : "mpathpersist-path", "help" : "--mpathpersist-path=[path] Path to mpathpersist binary", "required" : "0", "shortdesc" : "Path to mpathpersist binary", "default" : "@MPATH_PATH@", "order": 200 } all_opt["store_path"] = { "getopt" : ":", "longopt" : "store-path", "help" : "--store-path=[path] Path to directory containing cached keys", "required" : "0", "shortdesc" : "Path to directory where fence agent can store information", "default" : "@STORE_PATH@", "order": 200 } def main(): atexit.register(atexit_handler) device_opt = ["no_login", "no_password", "devices", "key", "sudo", \ "fabric_fencing", "on_target", "store_path", "mpathpersist_path", "force_on"] define_new_opts() options = check_input(device_opt, process_input(device_opt), other_conditions=True) docs = {} docs["shortdesc"] = "Fence agent for multipath persistent reservation" docs["longdesc"] = "fence_mpath is an I/O fencing agent that uses SCSI-3 \ persistent reservations to control access multipath devices. Underlying \ devices must support SCSI-3 persistent reservations (SPC-3 or greater) as \ well as the \"preempt-and-abort\" subcommand.\nThe fence_mpath agent works by \ having a unique key for each node that has to be set in /etc/multipath.conf. \ Once registered, a single node will become the reservation holder \ by creating a \"write exclusive, registrants only\" reservation on the \ device(s). The result is that only registered nodes may write to the \ device(s). When a node failure occurs, the fence_mpath agent will remove the \ key belonging to the failed node from the device(s). The failed node will no \ longer be able to write to the device(s). A manual reboot is required." docs["vendorurl"] = "https://www.sourceware.org/dm/" show_docs(options, docs) run_delay(options) # Input control BEGIN if not "--key" in options: fail_usage("Failed: key is required") if options["--action"] == "validate-all": sys.exit(0) options["devices"] = options["--devices"].split(",") if not options["devices"]: fail_usage("Failed: No devices found") # Input control END result = fence_action(None, options, set_status, get_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/netio/fence_netio.py b/fence/agents/netio/fence_netio.py index 6dc6a1ad..4fb59cff 100755 --- a/fence/agents/netio/fence_netio.py +++ b/fence/agents/netio/fence_netio.py @@ -1,100 +1,94 @@ #!@PYTHON@ -tt import sys, re, pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fspawn, fail, EC_LOGIN_DENIED, run_delay -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - def get_power_status(conn, options): conn.send_eol("port %s" % options["--plug"]) re_status = re.compile("250 [01imt]") conn.log_expect(re_status, int(options["--shell-timeout"])) status = { "0" : "off", "1" : "on", "i" : "reboot", "m" : "manual", "t" : "timer" }[conn.after.split()[1]] return status def set_power_status(conn, options): action = { "on" : "1", "off" : "0", "reboot" : "i" }[options["--action"]] conn.send_eol("port %s %s" % (options["--plug"], action)) conn.log_expect("250 OK", int(options["--shell-timeout"])) def get_outlet_list(conn, options): result = {} try: # the NETIO-230B has 4 ports, counting start at 1 for plug in ["1", "2", "3", "4"]: conn.send_eol("port setup %s" % plug) conn.log_expect("250 .+", int(options["--shell-timeout"])) # the name is enclosed in "", drop those with [1:-1] name = conn.after.split()[1][1:-1] result[plug] = (name, "unknown") except Exception as exn: print(str(exn)) return result def main(): device_opt = ["ipaddr", "login", "passwd", "port", "telnet"] atexit.register(atexit_handler) all_opt["ipport"]["default"] = "1234" opt = process_input(device_opt) opt["eol"] = "\r\n" options = check_input(device_opt, opt) docs = {} docs["shortdesc"] = "I/O Fencing agent for Koukaam NETIO-230B" docs["longdesc"] = "fence_netio is an I/O Fencing agent which can be \ used with the Koukaam NETIO-230B Power Distribution Unit. It logs into \ device via telnet and reboots a specified outlet. Lengthy telnet connections \ should be avoided while a GFS cluster is running because the connection will \ block any necessary fencing actions." docs["vendorurl"] = "http://www.koukaam.se/" show_docs(options, docs) ## ## Operate the fencing device ## We can not use fence_login(), username and passwd are sent on one line #### run_delay(options) try: conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) conn.log_expect("100 HELLO .*", int(options["--shell-timeout"])) conn.send_eol("login %s %s" % (options["--username"], options["--password"])) conn.log_expect("250 OK", int(options["--shell-timeout"])) except pexpect.EOF: fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) result = fence_action(conn, options, set_power_status, get_power_status, get_outlet_list) fence_logout(conn, "quit\n") sys.exit(result) if __name__ == "__main__": main() diff --git a/fence/agents/powerman/fence_powerman.py b/fence/agents/powerman/fence_powerman.py index d9ab8495..962fdb8f 100755 --- a/fence/agents/powerman/fence_powerman.py +++ b/fence/agents/powerman/fence_powerman.py @@ -1,265 +1,257 @@ #!/usr/bin/env python import os import time from datetime import datetime import sys import subprocess import re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import is_executable, fail_usage, run_delay import logging - -#BEGIN_VERSION_GENERATION -RELEASE_VERSION="Powerman Fencing Agent" -REDHAT_COPYRIGHT="" -BUILD_DATE="" -#END_VERSION_GENERATION - - #### important!!! ####### class PowerMan: """Python wrapper for calling powerman commands This class makes calls to a powerman deamon for a cluster of computers. The make-up of such a call looks something like: $ pm -h elssd1:10101