diff --git a/.travis.yml b/.travis.yml index 15689533..6dfcb65c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,36 @@ language: python -sudo: false +sudo: required python: - "2.7" addons: apt: packages: - python-pexpect - xsltproc - time + - libpam0g-dev + - libcimcclient0-dev before_install: - pip install suds - pip install pycurl - pip install requests - pip install pexpect +before_script: + - wget https://github.com/Openwsman/openwsman/archive/v2.6.2.tar.gz -O /tmp/openwsman-2.6.2.tar.gz + - tar zxvf /tmp/openwsman-2.6.2.tar.gz + - cd openwsman-2.6.2 + - mkdir build && cd build + - cmake .. + - make + - sudo make install + script: - ./autogen.sh - ./configure - make - make check - PYTHONPATH=fence/agents/lib python fence/agents/lib/tests/test_fencing.py diff --git a/configure.ac b/configure.ac index 0a5eb363..b81d9184 100644 --- a/configure.ac +++ b/configure.ac @@ -1,324 +1,325 @@ # 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]) 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 if test "x$AGENTS_LIST" != xall; then for j in $AGENTS_LIST; do if ! test -d fence/agents/$j; then AC_ERROR([Agent $j does not exists]) fi done fi if test "x$AGENTS_LIST" = xall; then AGENTS_LIST=`find $srcdir/fence/agents -mindepth 2 -maxdepth 2 -name Makefile.am -printf '%h ' | sed -e 's#'$srcdir'/fence/agents/##g' -e 's#lib ##g' -e 's#nss_wrapper ##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 AC_PYTHON_MODULE(suds, 1) AC_PYTHON_MODULE(pexpect, 1) AC_PYTHON_MODULE(pycurl, 1) AC_PYTHON_MODULE(requests, 1) ## path to 3rd-party binaries AC_PATH_PROG([IPMITOOL_PATH], [ipmitool], [/usr/bin/ipmitool]) 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]) ## 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_XENAPILIB, test $XENAPILIB -eq 1) AC_SUBST([IPMITOOL_PATH]) AC_SUBST([AMTTOOL_PATH]) AC_SUBST([COROSYNC_CMAPCTL_PATH]) AC_SUBST([SG_PERSIST_PATH]) AC_SUBST([SG_TURS_PATH]) AC_SUBST([VGS_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" AC_CONFIG_FILES([Makefile fence/Makefile fence/agents/Makefile fence/agents/alom/Makefile fence/agents/apc/Makefile fence/agents/apc_snmp/Makefile - fence/agents/amt/Makefile + fence/agents/amt/Makefile + fence/agents/amt_ws/Makefile fence/agents/bladecenter/Makefile fence/agents/brocade/Makefile fence/agents/cisco_mds/Makefile fence/agents/cisco_ucs/Makefile fence/agents/compute/Makefile fence/agents/docker/Makefile fence/agents/drac/Makefile fence/agents/drac5/Makefile fence/agents/dummy/Makefile fence/agents/eaton_snmp/Makefile fence/agents/emerson/Makefile fence/agents/eps/Makefile fence/agents/hpblade/Makefile fence/agents/ibmblade/Makefile fence/agents/ipdu/Makefile fence/agents/ifmib/Makefile fence/agents/ilo/Makefile fence/agents/ilo_moonshot/Makefile fence/agents/ilo_mp/Makefile fence/agents/ilo_ssh/Makefile fence/agents/intelmodular/Makefile fence/agents/ipmilan/Makefile fence/agents/kdump/Makefile fence/agents/ldom/Makefile fence/agents/lib/Makefile fence/agents/lpar/Makefile fence/agents/manual/Makefile fence/agents/mpath/Makefile fence/agents/netio/Makefile fence/agents/ovh/Makefile fence/agents/pve/Makefile fence/agents/raritan/Makefile fence/agents/rcd_serial/Makefile fence/agents/rhevm/Makefile fence/agents/rsa/Makefile fence/agents/rsb/Makefile fence/agents/sbd/Makefile fence/agents/sanbox2/Makefile fence/agents/scsi/Makefile fence/agents/vbox/Makefile fence/agents/virsh/Makefile fence/agents/vmware/Makefile fence/agents/vmware_soap/Makefile fence/agents/wti/Makefile fence/agents/xenapi/Makefile fence/agents/hds_cb/Makefile fence/agents/zvm/Makefile doc/Makefile]) AC_OUTPUT diff --git a/fence/agents/amt_ws/Makefile.am b/fence/agents/amt_ws/Makefile.am new file mode 100644 index 00000000..0876c176 --- /dev/null +++ b/fence/agents/amt_ws/Makefile.am @@ -0,0 +1,17 @@ +MAINTAINERCLEANFILES = Makefile.in + +TARGET = fence_amt_ws + +SRC = $(TARGET).py + +EXTRA_DIST = $(SRC) + +sbin_SCRIPTS = $(TARGET) + +man_MANS = $(TARGET).8 + +FENCE_TEST_ARGS = -p test -a test + +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk +include $(top_srcdir)/make/agentpycheck.mk diff --git a/fence/agents/amt_ws/fence_amt_ws.py b/fence/agents/amt_ws/fence_amt_ws.py new file mode 100755 index 00000000..d092f8fa --- /dev/null +++ b/fence/agents/amt_ws/fence_amt_ws.py @@ -0,0 +1,247 @@ +#!/usr/bin/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 + +from xml.etree import ElementTree + +try: + import pywsman +except ImportError: + fail_usage("pywsman not found or not accessible") + + +#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 options.has_key("--boot-option"): + 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/tests/data/metadata/fence_amt_ws.xml b/tests/data/metadata/fence_amt_ws.xml new file mode 100644 index 00000000..16b2035d --- /dev/null +++ b/tests/data/metadata/fence_amt_ws.xml @@ -0,0 +1,130 @@ + + +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/). +http://www.intel.com/ + + + + + Fencing action + + + + + + Change the default boot behavior of the machine. + + + + + Forces agent to use IPv4 addresses only + + + + + Forces agent to use IPv6 addresses only + + + + + IP address or hostname of fencing device + + + + + TCP/UDP port to use for connection with device + + + + + + Method to fence + + + + + Login password or passphrase + + + + + Script to run to retrieve password + + + + + IP address or hostname of fencing device (together with --port-as-ip) + + + + + Verbose mode + + + + + Write debug information to given file + + + + + Display version information and exit + + + + + Display help and exit + + + + + Wait X seconds before fencing is started + + + + + Wait X seconds for cmd prompt after login + + + + + Make "port/plug" to be an alias to IP address + + + + + Test X seconds for status change after ON/OFF + + + + + Wait X seconds after issuing ON/OFF + + + + + Wait X seconds for cmd prompt after issuing command + + + + + Count of attempts to retry power on + + + + + + + + + + + +