diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:52:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:52:36 +0000 |
commit | 7de03e4e519705301265c0415b3c0af85263a7ac (patch) | |
tree | 29d819c5227e3619d18a67d2a5dde963b3229dbe /tools | |
parent | Initial commit. (diff) | |
download | resource-agents-7de03e4e519705301265c0415b3c0af85263a7ac.tar.xz resource-agents-7de03e4e519705301265c0415b3c0af85263a7ac.zip |
Adding upstream version 1:4.13.0.upstream/1%4.13.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
57 files changed, 12123 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..55e292c --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,85 @@ +# +# heartbeat: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = ocft + +AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include + +halibdir = $(libexecdir)/heartbeat + +EXTRA_DIST = ocf-tester.8 sfex_init.8 + +sbin_PROGRAMS = +sbin_SCRIPTS = ocf-tester +halib_PROGRAMS = findif \ + storage_mon +halib_SCRIPTS = + +man8_MANS = ocf-tester.8 + +if BUILD_SFEX +halib_PROGRAMS += sfex_daemon +sbin_PROGRAMS += sfex_init sfex_stat +man8_MANS += sfex_init.8 +endif + +if USE_LIBNET +halib_PROGRAMS += send_arp +send_arp_SOURCES = send_arp.libnet.c +send_arp_CFLAGS = @LIBNETDEFINES@ +send_arp_LDADD = $(GLIBLIB) -lplumb @LIBNETLIBS@ +else + +if SENDARP_LINUX +halib_PROGRAMS += send_arp +send_arp_SOURCES = send_arp.linux.c +endif + +if NFSCONVERT +halib_SCRIPTS += nfsconvert +endif + +endif + +sfex_daemon_SOURCES = sfex_daemon.c sfex.h sfex_lib.c sfex_lib.h +sfex_daemon_CFLAGS = -D_GNU_SOURCE +sfex_daemon_LDADD = $(GLIBLIB) -lplumb -lplumbgpl + +sfex_init_SOURCES = sfex_init.c sfex.h sfex_lib.c sfex_lib.h +sfex_init_CFLAGS = -D_GNU_SOURCE +sfex_init_LDADD = $(GLIBLIB) -lplumb -lplumbgpl + +sfex_stat_SOURCES = sfex_stat.c sfex.h sfex_lib.c sfex_lib.h +sfex_stat_CFLAGS = -D_GNU_SOURCE +sfex_stat_LDADD = $(GLIBLIB) -lplumb -lplumbgpl + +findif_SOURCES = findif.c + +storage_mon_SOURCES = storage_mon.c +storage_mon_CFLAGS = -D_GNU_SOURCE ${LIBQB_CFLAGS} +storage_mon_LDADD = ${LIBQB_LIBS} + +if BUILD_TICKLE +halib_PROGRAMS += tickle_tcp +tickle_tcp_SOURCES = tickle_tcp.c +endif + +.PHONY: install-exec-hook diff --git a/tools/README.sfex b/tools/README.sfex new file mode 100644 index 0000000..ff850d1 --- /dev/null +++ b/tools/README.sfex @@ -0,0 +1,336 @@ +Shared Disk File EXclusiveness Control Program version 1.3 +OCF Resource Agent for Heartbeat v2 +FOR USE IN LINUX 2.6 KERNEL OPERATING SYSTEM ENVIRONMENTS ONLY. + +Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION + +Note: Before using this information and the product it supports, +read the general information in section 4.0 "Trademarks and Notices" +in this document. + +Last Update Date: 10/10/2007 + + +======================================================================= +CONTENTS +-------- +1.0 Overview +2.0 Installation and Setup Instructions +3.0 Configuration Information +4.0 Trademarks and Notices +5.0 Disclaimer + +======================================================================= + +1.0 Overview +-------------- +Shared Disk File EXclusiveness Control Program, called "SF-EX" for short, +can prevent a destruction of data on shared disk file system due to +Split-Brain. + +======================================================================= + +1.1 Limitations +--------------------- +This program is tested on the following environment. + + Heartbeat 2.1.2-2 + Red Hat Enterprise Linux ES release 4 (Nahant Update 5) EM64T + +======================================================================= + +2.0 Installation and Setup Instructions +----------------------------------------- + + 2.1.1 Prerequisites + SF-EX is released as a source-code package in the format + of a gunzip compressed tar file. To unpack the source + package, type the following command in the Linux console + window: + + $ tar zxf sfex-1.3.tar.gz + + The source files will uncompress to the "sf-ex-x.x" + directory. + + 2.1.3 Build and Installation + + Change unpacked directory first. + + $ cd sfex-1.3 + + Type the following command in the Linux console window: + Press Enter after each command. + + $ ./configure + $ make + $ su + (you need root's password) + # make install + + "make install" will copy the modules to /usr/lib64/heartbeat + + NOTE: "make install" should be done on all nodes + which Heartbeat would run. + + NOTE: in case of 32bit system + If you want to run SF-EX on 32bit system, the modules + should be setup on /usr/lib/heartbeat. + Use the following configure option on 32bit system. + + $ ./configure --with-lib-dir=/usr/lib/heartbeat + + 2.1.3 Initialization of a device + Before running SF-EX, one device should be initialized + as below. + + sfex_init [-b <blocksize>] [-n <numlocks>] <device> + + Example: + # /usr/lib/heartbeat/sfex_init -b 512 -n 10 /dev/sdb1 + + Initialized device is going to be used as a control area + for SF-EX. + See 3.2.2, if further information is necessary. + + 2.1.4 Access without O_DIRECT + If you are planning to access a device without using + O_DIRECT, the following option is available. + + Example: + $ ./configure -enable-directio=no + + Default value for --enable-directio is "yes". + +======================================================================= + +3.0 Configuration Information +----------------------------- + +3.1 Configuration Settings +-------------------------- + + 3.1.1 Edit your cib.xml + The following example shows a typical configuration + for SF-EX and Filesystem. + + 3.1.2 Example for cib.xml + + /dev/sda1 control area for SF-EX + /dev/sda2 Filesystem + +--- skip --- +<resources> + <group id="grp"> + <primitive id="prmEx" class="ocf" type="sfex" provider="heartbeat"> + <operations> + <op id="ex_start" name="start" timeout="180s" on_fail="fence"/> + <op id="ex_monitor" name="monitor" timeout="60s" on_fail="fence" interval="10s" /> + <op id="ex_stop" name="stop" timeout="60s" on_fail="fence"/> + </operations> + <instance_attributes id="atrEx"> + <attributes> + <nvpair id="dsk" name="device" value="/dev/sda1"/> + <nvpair id="idx" name="index" value="1"/> + <nvpair id="clt" name="collision_timeout" value="1"/> + <nvpair id="lct" name="lock_timeout" value="70"/> + <nvpair id="mnt" name="monitor_interval" value="10"/> + <nvpair id="fck" name="fsck" value="/sbin/fsck -p /dev/sdb2"/> + <nvpair id="fcm" name="fsck_mode" value="check"/> + <nvpair id="hlt" name="halt" value="/sbin/halt -f -n -p"/> + </attributes> + </instance_attributes> + </primitive> + <primitive id="prmFs" class="ocf" type="Filesystem" provider="heartbeat"> + <operations> + <op id="fs_start" name="start" timeout="60s" on_fail="fence"/> + <op id="fs_monitor" name="monitor" timeout="60s" on_fail="fence" interval="10s" /> + <op id="fs_stop" name="stop" timeout="60s" on_fail="fence"/> + </operations> + <instance_attributes id="atrFs"> + <attributes> + <nvpair id="dev" name="device" value="/dev/sdb2"/> + <nvpair id="dir" name="directory" value="/mnt/shared-disk"/> + <nvpair id="fst" name="fstype" value="ext3"/> + </attributes> + </instance_attributes> + </primitive> + </group> +</resources> +--- skip --- + + +3.2 Outline of each module +-------------------------- + 3.2.1 sfex + Resource Agent script for Heartbeat. + + 3.2.2 sfex_init + sfex_init [-b <blocksize>] [-n <numlocks>] <device> + + -b <blocksize> --- The size of the block is specified + by the number of bytes. In general, to prevent a partial + writing to the disk, the size of block is set to 512 + bytes etc. + Note a set value because this value is used also for + the alignment adjustment in the input-output buffer in + the program when direct I/O is used(When you specify + --enable-directio option for configure script). + (In Linux kernel 2.6, "direct I/O " does not work if this + value is not a multiple of 512.) Default is 512 bytes. + + -n <numlocks> --- The number of storing lock data is + specified by integer of one or more. When you want to + control two or more resources by one meta-data, you set + the value of two or more to numlocks. A necessary disk + area for meta data are (blocksize*(1+numlocks))bytes. + Default is 1. + + <device> --- This is file path which stored mata-data. + It is usually expressed in "/dev/...", because it is + partition on the shared disk. + + exit code --- + 0 - Normal end. + 3 - Error occurs while processing it. + The content of the error is displayed into stderr. + 4 - The mistake is found in the command line parameter. + + 3.2.3 sfex_stat + sfex_stat [-i <index>] <device> + + -i <index> --- The index is number of the resource that + display the lock. This number is specified by the integer + of one or more. When two or more resources are exclusively + controlled by one meta-data, this option is used. + Default is 1. + + <device> --- This is file path which stored mata-data. + It is usually expressed in "/dev/...", because it is + partition on the shared disk. + + exit code --- + 0 - Normal end. Own node is holding lock. + 2 - Normal end. Own node does not hold a lock. + 3 - Error occurs while processing it. + The content of the error is displayed into stderr. + 4 - The mistake is found in the command line parameter. + + 3.2.4 sfex_lock + sfex_lock + [-i <index>] + [-c <collision_timeout>] + [-t <lock_timeout>] + <device> + + -i <index> --- The index is number of the resource that + acquire the lock. This number is specified by the integer + of one or more. When two or more resources are exclusively + controlled by one meta-data, this option is used. + Default is 1. + + -c <collision_timeout> --- The waiting time to detect + the collision of the lock with other nodes is specified. + Time that is very longer than "once synchronous read from + device which stored meta-data + once + synchronous write" is specified usually. Default is 1 second. + This value need not be changed by using this option usually. + Because it is not thought to take one second or more to + synchronous read and write. + + -t <lock_timeout> --- This specifies the validity term + of lock. The unit is a second. This timer prevents the + resource being locked for a long time when node crashes + with the lock acquired. Therefore, the lock holding node + must update lock data at intervals that are shorter than + this timer. The sfex_update command is used for updating + lock. Default is 60 seconds. + + <device> --- This is file path which stored mata-data. + It is usually expressed in "/dev/...", because it is + partition on the shared disk. + + exit code --- + 0 - Acquire a lock from unlock status. + 1 - Acquire a lock from lock timeout status. + 2 - Lock acquisition failed. + 3 - Error occurs while processing it. The content of the + error is displayed into stderr. + 4 - The mistake is found in the command line parameter. + + 3.2.5 sfex_unlock + sfex_unlock [-i <index>] <device> + + -i <index> --- The index is number of the resource that + releases the lock. This number is specified by the integer + of one or more. When two or more resources are exclusively + controlled by one meta-data, this option is used. + Default is 1. + + <device> --- This is file path which stored mata-data. + It is usually expressed in "/dev/...", because it is + partition on the shared disk. + + exit code --- + 0 - Lock release success. + 1 - Lock release done already. + The lock has already been acquired by other nodes. + 3 - Error occurs while processing it. + The content of the error is displayed into stderr. + 4 - The mistake is found in the command line parameter. + + 3.2.6 sfex_update + sfex_update [-i <index>] <device> + + -i <index> --- The index is number of the resource that + update the lock. This number is specified by the integer + of one or more. When two or more resources are exclusively + controlled by one meta-data, this option is used. + Default is 1. + + <device> --- This is file path which stored mata-data. + It is usually expressed in "/dev/...", because it is + partition on the shared disk. + + exit code --- + 0 - Lock update success. + 2 - Lock update failed. + The lock is acquired by other nodes. + 3 - Error occurs while processing it. + The content of the error is displayed into stderr. + 4 - The mistake is found in the command line parameter. + +======================================================================= + +4.0 Trademarks and Notices +---------------------------- + + Heartbeat is a registered trademark of The High Availability + Linux Project. + + Linux is a registered trademark of Linus Torvalds. + + Other company, product, and service names may be + trademarks or service marks of others. + +======================================================================= + +5.0 Disclaimer +---------------- + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND + PARTICULARLY THE NON-INFRINGEMENT OF ANY THIRD PARTY'S + INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + diff --git a/tools/findif.c b/tools/findif.c new file mode 100644 index 0000000..a25395f --- /dev/null +++ b/tools/findif.c @@ -0,0 +1,845 @@ +/* + * findif.c: Finds an interface which can route a given address + * + * It's really simple to write in C, but hard to write in the shell... + * + * This code is dependent on IPV4 addressing conventions... + * Sorry. + * + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * Copyright (C) 2001 Matt Soffen <matt@soffen.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * + *********************************************************** + * + * All our arguments come through the environment as OCF + * environment variables as below: + * + * OCF_RESKEY_ip + * OCF_RESKEY_broadcast + * OCF_RESKEY_nic + * OCF_RESKEY_cidr_netmask + * + * If the CIDR netmask is omitted, we choose the netmask associated with + * the route we selected. + * + * If the broadcast address was omitted, we assume the highest address + * in the subnet. + * + * If the interface is omitted, we choose the interface associated with + * the route we selected. + * + * + * See http://www.doom.net/docs/netmask.html for a table explaining + * CIDR address format and their relationship to life, the universe + * and everything. + * + */ + +#include <config.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/types.h> +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif +#include <net/if.h> +#include <sys/ioctl.h> +#include <errno.h> +#ifdef __linux__ +#undef __OPTIMIZE__ +/* + * This gets rid of some silly -Wtraditional warnings on Linux + * because the netinet header has some slightly funky constants + * in it. + */ +#endif /* __linux__ */ + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <agent_config.h> +#include <config.h> + +#define DEBUG 0 +#define EOS '\0' +#define PROCROUTE "/proc/net/route" +#define ROUTEPARM "-n get" + +#ifndef HAVE_STRNLEN +/* Any system that don't provide strnlen() only has itself to blame */ +#define strnlen(str, max) strlen(str) +#endif + +/* + * "route -n get iii.jjj.kkk.lll" can, on Solaris at least, + * return the word "default" as the value from "mask" and "dest", + * typically if the host is remote, reached over a default route. + * We should probably treat such a mask as "0.0.0.0". + * + * Define "MASK_DEFAULT_TO_ZERO" to enable this interpretation. + * + * This is better for Solaris and is probably suitable (or irrelevant) + * for others OSes also. But if it breaks another OS, then reduce the + * "hash-if 1" below to exclude that OS. + * (David Lee, Jan 2006) + */ +#if 1 +# define MASK_DEFAULT_TO_ZERO +#endif + +static int OutputInCIDR=0; + + +/* + * Different OSes offer different mechnisms to obtain this information. + * Not all this can be determined at configure-time; need a run-time element. + * + * typedef ... SearchRoute ...: + * For routines that interface on these mechanisms. + * Return code: + * <0: mechanism invalid, so try next mechanism + * 0: mechanism worked: good answer + * >0: mechanism worked: bad answer + * On non-zero, errmsg may have been filled with an error message + */ +typedef int SearchRoute (char *address, struct in_addr *in +, struct in_addr *addr_out, char *best_if, size_t best_iflen +, unsigned long *best_netmask, char *errmsg +, int errmsglen); + +static SearchRoute SearchUsingProcRoute; +static SearchRoute SearchUsingRouteCmd; + +static SearchRoute *search_mechs[] = { + &SearchUsingProcRoute, + &SearchUsingRouteCmd, + NULL +}; + +void GetAddress (char **address, char **netmaskbits +, char **bcast_arg, char **if_specified); + +int ConvertNetmaskBitsToInt(char *netmaskbits); + +void ValidateNetmaskBits(int bits, unsigned long *netmask); + +int ValidateIFName (const char *ifname, struct ifreq *ifr); + +int netmask_bits (unsigned long netmask); + +char * get_first_loopback_netdev(char * ifname); +int is_loopback_interface(char * ifname); +char * get_ifname(char * buf, char * ifname); + +int ConvertQuadToInt(char *dest); + +static const char *cmdname = "findif"; +#define OCF_SUCCESS 0 +#define OCF_ERR_GENERIC 1 +#define OCF_ERR_ARGS 2 +#define OCF_ERR_UNIMPLEMENTED 3 +#define OCF_ERR_PERM 4 +#define OCF_ERR_INSTALLED 5 +#define OCF_ERR_CONFIGURED 6 +#define OCF_NOT_RUNNING 7 +void usage(int ec); + +#define PATH_PROC_NET_DEV "/proc/net/dev" +#define DELIM '/' +#define BAD_BROADCAST (0L) +#define MAXSTR 128 + +static int +SearchUsingProcRoute (char *address, struct in_addr *in +, struct in_addr *addr_out, char *best_if, size_t best_iflen +, unsigned long *best_netmask +, char *errmsg, int errmsglen) +{ + unsigned long flags, refcnt, use, gw, mask; + unsigned long dest; + long metric = LONG_MAX; + long best_metric = LONG_MAX; + int rc = OCF_SUCCESS; + + char buf[2048]; + char interface[MAXSTR]; + FILE *routefd = NULL; + + if ((routefd = fopen(PROCROUTE, "r")) == NULL) { + snprintf(errmsg, errmsglen + , "Cannot open %s for reading" + , PROCROUTE); + rc = OCF_ERR_GENERIC; goto out; + } + + /* Skip first (header) line */ + if (fgets(buf, sizeof(buf), routefd) == NULL) { + snprintf(errmsg, errmsglen + , "Cannot skip first line from %s" + , PROCROUTE); + rc = OCF_ERR_GENERIC; goto out; + } + *best_netmask = 0; + while (fgets(buf, sizeof(buf), routefd) != NULL) { + if (sscanf(buf, "%[^\t]\t%lx%lx%lx%lx%lx%lx%lx" + , interface, &dest, &gw, &flags, &refcnt, &use + , &metric, &mask) + != 8) { + snprintf(errmsg, errmsglen, "Bad line in %s: %s" + , PROCROUTE, buf); + rc = OCF_ERR_GENERIC; goto out; + } + if ( (in->s_addr&mask) == (in_addr_t)(dest&mask) + && metric <= best_metric && mask >= *best_netmask) { + best_metric = metric; + *best_netmask = mask; + strncpy(best_if, interface, best_iflen); + } + } + + if (best_metric == LONG_MAX) { + snprintf(errmsg, errmsglen, "No route to %s\n", address); + rc = OCF_ERR_GENERIC; + } + + out: + if (routefd) { + fclose(routefd); + } + + return(rc); +} + +static int +SearchUsingRouteCmd (char *address, struct in_addr *in +, struct in_addr *addr_out, char *best_if, size_t best_iflen +, unsigned long *best_netmask +, char *errmsg, int errmsglen) +{ + char mask[20]; + char routecmd[MAXSTR]; + int best_metric = INT_MAX; + char buf[2048]; + char interface[MAXSTR]; + char *cp, *sp; + int done = 0; + FILE *routefd = NULL; + uint32_t maskbits; + + + /* Open route and get the information */ + snprintf (routecmd, sizeof(routecmd), "%s %s %s" + , ROUTE, ROUTEPARM, address); + routefd = popen (routecmd, "r"); + if (routefd == NULL) + return (OCF_ERR_GENERIC); + mask[0] = EOS; + interface[0] = EOS; + + + while ((done < 3) && fgets(buf, sizeof(buf), routefd)) { + int buflen = strnlen(buf, sizeof(buf)); + /*cp = buf;*/ + + sp = buf + buflen; + while (sp!=buf && isspace((int)*(sp-1))) { + --sp; + } + *sp = EOS; + + if (strstr (buf, "mask:")) { + /*strsep(&cp, ":");cp++;*/ + strtok(buf, ":"); + cp = strtok(NULL, ":"); + if (cp) { + cp++; + strncpy(mask, cp, sizeof(mask) - 1); + *(mask + sizeof(mask) - 1) = '\0'; + done++; + } + } + + if (strstr (buf, "interface:")) { + /*strsep(&cp, ":");cp++;*/ + strtok(buf, ":"); + cp = strtok(NULL, ":"); + if (cp) { + cp++; + strncpy(interface, cp, sizeof(interface) - 1); + *(interface + sizeof(interface) - 1) = '\0'; + done++; + } + } + } + pclose(routefd); + + /* + * Check to see if mask isn't available. It may not be + * returned if multiple IP's are defined. + * use 255.255.255.255 for mask then + */ + /* I'm pretty sure this is the wrong behavior... + * I think the right behavior is to declare an error and give up. + * The admin didn't define his routes correctly. Fix them. + * It's useless to take over an IP address with no way to + * return packets to the originator. Without the right subnet + * mask, you can't reply to any packets you receive. + */ + if (strnlen(mask, sizeof(mask)) == 0) { + strncpy (mask, "255.255.255.255", sizeof(mask)); + } + + /* + * Solaris (at least) can return the word "default" for mask and dest. + * For the moment, let's interpret this as: + * mask: 0.0.0.0 + * This was manifesting itself under "BasicSanityCheck", which tries + * to use a remote IP number; these typically use the "default" route. + * Better schemes are warmly invited... + */ +#ifdef MASK_DEFAULT_TO_ZERO + if (strncmp(mask, "default", sizeof("default")) == 0) { + strncpy (mask, "0.0.0.0", sizeof(mask)); + } +#endif + + if (inet_pton(AF_INET, mask, &maskbits) <= 0) { + snprintf(errmsg, errmsglen, + "mask [%s] not valid.", mask); + return(OCF_ERR_CONFIGURED); + } + + if (inet_pton(AF_INET, address, addr_out) <= 0) { + snprintf(errmsg, errmsglen + , "IP address [%s] not valid.", address); + return(OCF_ERR_CONFIGURED); + } + + if ((in->s_addr & maskbits) == (addr_out->s_addr & maskbits)) { + if (interface[0] == EOS) { + snprintf(errmsg, errmsglen, "No interface found."); + return(OCF_ERR_GENERIC); + } + best_metric = 0; + *best_netmask = maskbits; + strncpy(best_if, interface, best_iflen); + } + + if (best_metric == INT_MAX) { + snprintf(errmsg, errmsglen, "No route to %s\n", address); + return(OCF_ERR_GENERIC); + } + + return (OCF_SUCCESS); +} + +/* + * Getaddress gets all its real parameters from the OCF environment + * variables that its callers already use. + */ +void +GetAddress (char **address, char **netmaskbits +, char **bcast_arg, char **if_specified) +{ + /* + * Here are out input environment variables: + * + * OCF_RESKEY_ip ip address + * OCF_RESKEY_cidr_netmask netmask of interface + * OCF_RESKEY_broadcast broadcast address for interface + * OCF_RESKEY_nic interface to assign to + * + */ + *address = getenv("OCF_RESKEY_ip"); + *netmaskbits = getenv("OCF_RESKEY_cidr_netmask"); + if (*netmaskbits == NULL || **netmaskbits == EOS) { + *netmaskbits = getenv("OCF_RESKEY_netmask"); + } + *bcast_arg = getenv("OCF_RESKEY_broadcast"); + *if_specified = getenv("OCF_RESKEY_nic"); +} + +int +ConvertNetmaskBitsToInt(char *netmaskbits) +{ + size_t nmblen = strnlen(netmaskbits, 3); + + /* Maximum netmask is 32 */ + + if (nmblen > 2 || nmblen == 0 + || (strspn(netmaskbits, "0123456789") != nmblen)) + return -1; + else + return atoi(netmaskbits); +} + +void +ValidateNetmaskBits(int bits, unsigned long *netmask) +{ + /* Maximum netmask is 32 */ + + if (bits < 1 || bits > 32) { + fprintf(stderr + , "Invalid netmask specification [%d]" + , bits); + usage(OCF_ERR_CONFIGURED); + /*not reached */ + } + + bits = 32 - bits; + *netmask = (1L<<(bits))-1L; + *netmask = ((~(*netmask))&0xffffffffUL); + *netmask = htonl(*netmask); +} + +int +ValidateIFName(const char *ifname, struct ifreq *ifr) +{ + int skfd = -1; + char *colonptr; + + if ( (skfd = socket(PF_INET, SOCK_DGRAM, 0)) == -1 ) { + fprintf(stderr, "%s\n", strerror(errno)); + return -2; + } + + strncpy(ifr->ifr_name, ifname, IFNAMSIZ - 1); + *(ifr->ifr_name + sizeof(ifr->ifr_name) - 1) = '\0'; + + /* Contain a ":"? Probably an error, but treat as warning at present */ + if ((colonptr = strchr(ifname, ':')) != NULL) { + fprintf(stderr, "%s: warning: name may be invalid\n", + ifr->ifr_name); + } + + if (ioctl(skfd, SIOCGIFFLAGS, ifr) < 0) { + fprintf(stderr, "%s: unknown interface: %s\n" + , ifr->ifr_name, strerror(errno)); + close(skfd); + /* return -1 only if ifname is known to be invalid */ + return -1; + } + close(skfd); + return 0; +} + +int +netmask_bits(unsigned long netmask) +{ + int j; + + netmask = netmask & 0xFFFFFFFFUL; + + for (j=0; j <= 32; ++j) { + if ((netmask >> j)&0x1) { + break; + } + } + + return 32 - j; +} + +char * +get_first_loopback_netdev(char * output) +{ + char buf[512]; + FILE * fd = NULL; + char *rc = NULL; + + if (!output) { + fprintf(stderr, "output buf is a null pointer.\n"); + goto out; + } + + fd = fopen(PATH_PROC_NET_DEV, "r"); + if (!fd) { + fprintf(stderr, "Warning: cannot open %s (%s).\n", + PATH_PROC_NET_DEV, strerror(errno)); + goto out; + } + + /* Skip the first two lines */ + if (!fgets(buf, sizeof(buf), fd) || !fgets(buf, sizeof(buf), fd)) { + fprintf(stderr, "Warning: cannot read header from %s.\n", + PATH_PROC_NET_DEV); + goto out; + } + + while (fgets(buf, sizeof(buf), fd)) { + char name[IFNAMSIZ]; + if (NULL == get_ifname(buf, name)) { + /* Maybe somethin is wrong, anyway continue */ + continue; + } + if (is_loopback_interface(name)) { + strncpy(output, name, IFNAMSIZ); + rc = output; + goto out; + } + } + +out: + if (fd) { + fclose(fd); + } + return rc; +} + +int +is_loopback_interface(char * ifname) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + if (ValidateIFName(ifname, &ifr) < 0) + return 0; + + if (ifr.ifr_flags & IFF_LOOPBACK) { + /* this is a loopback device. */ + return 1; + } else { + return 0; + } +} + +char * +get_ifname(char * buf, char * ifname) +{ + char * start, * end, * buf_border; + + buf_border = buf + strnlen(buf, 512); + + start = buf; + while (isspace((int) *start) && (start != buf_border)) { + start++; + } + end = start; + while ((*end != ':') && (end != buf_border)) { + end++; + } + + if ( start == buf_border || end == buf_border ) { + /* Over the border of buf */ + return NULL; + } + + *end = '\0'; + strncpy(ifname, start, IFNAMSIZ); + + return ifname; +} + +int ConvertQuadToInt(char *dest) +{ + struct in_addr ad; + + if (inet_pton(AF_INET, dest, &ad) <= 0) + return -1; + + return netmask_bits(ntohl(ad.s_addr)); +} + +int +main(int argc, char ** argv) { + + char * address = NULL; + char * bcast_arg = NULL; + char * netmaskbits = NULL; + struct in_addr in; + struct in_addr addr_out; + unsigned long netmask = 0; + char best_if[MAXSTR]; + char * if_specified = NULL; + struct ifreq ifr; + unsigned long best_netmask = UINT_MAX; + int argerrs = 0; + int nmbits; + + cmdname=argv[0]; + + + memset(&addr_out, 0, sizeof(addr_out)); + memset(&in, 0, sizeof(in)); + memset(&ifr, 0, sizeof(ifr)); + + switch (argc) { + case 1: /* No -C argument */ + break; + case 2: /* Hopefully a -C argument */ + if (strncmp(argv[1], "-C", sizeof("-C")) != 0) { + argerrs=1; + } + OutputInCIDR=1; + break; + default: + argerrs=1; + break; + } + if (argerrs) { + usage(OCF_ERR_ARGS); + /* not reached */ + return(1); + } + + GetAddress (&address, &netmaskbits, &bcast_arg + , &if_specified); + if (address == NULL || *address == EOS) { + fprintf(stderr, "ERROR: IP address parameter is mandatory."); + usage(OCF_ERR_CONFIGURED); + /* not reached */ + } + + /* Is the IP address we're supposed to find valid? */ + + if (inet_pton(AF_INET, address, (void *)&in) <= 0) { + fprintf(stderr, "IP address [%s] not valid.", address); + usage(OCF_ERR_CONFIGURED); + /* not reached */ + } + + if (netmaskbits != NULL && *netmaskbits != EOS) { + if (strchr(netmaskbits, '.') != NULL) { + nmbits = ConvertQuadToInt(netmaskbits); + fprintf(stderr, "Converted dotted-quad netmask to CIDR as: %d\n", nmbits); + }else{ + nmbits = ConvertNetmaskBitsToInt(netmaskbits); + } + + if (nmbits < 0) { + fprintf(stderr, "Invalid netmask specification" + " [%s]", netmaskbits); + usage(OCF_ERR_CONFIGURED); + /*not reached */ + } + + /* Validate the netmaskbits field */ + ValidateNetmaskBits (nmbits, &netmask); + } + + + if (if_specified != NULL && *if_specified != EOS) { + if(ValidateIFName(if_specified, &ifr) < 0) { + usage(OCF_ERR_CONFIGURED); + /* not reached */ + } + strncpy(best_if, if_specified, sizeof(best_if) - 1); + *(best_if + sizeof(best_if) - 1) = '\0'; + }else{ + SearchRoute **sr = search_mechs; + char errmsg[MAXSTR] = "No valid mechanisms"; + int rc = OCF_ERR_GENERIC; + + strcpy(best_if, "UNKNOWN"); + + while (*sr) { + errmsg[0] = '\0'; + rc = (*sr) (address, &in, &addr_out, best_if + , sizeof(best_if) + , &best_netmask, errmsg, sizeof(errmsg)); + if (!rc) { /* Mechanism worked */ + break; + } + sr++; + } + if (rc != 0) { /* No route, or all mechanisms failed */ + if (*errmsg) { + fprintf(stderr, "%s", errmsg); + } + return(rc); + } + } + + if (netmaskbits) { + best_netmask = netmask; + }else if (best_netmask == 0L) { + /* + On some distributions, there is no loopback related route + item, this leads to the error here. + My fix may be not good enough, please FIXME + */ + if (0 == strncmp(address, "127", 3)) { + if (NULL != get_first_loopback_netdev(best_if)) { + best_netmask = 0x000000ff; + } else { + fprintf(stderr, "No loopback interface found.\n"); + return(OCF_ERR_GENERIC); + } + } else { + fprintf(stderr + , "ERROR: Cannot use default route w/o netmask [%s]\n" + , address); + return(OCF_ERR_GENERIC); + } + } + + /* Did they tell us the broadcast address? */ + + if (bcast_arg && *bcast_arg != EOS) { + /* Yes, they gave us a broadcast address. + * It at least should be a valid IP address + */ + struct in_addr bcast_addr; + if (inet_pton(AF_INET, bcast_arg, (void *)&bcast_addr) <= 0) { + fprintf(stderr, "Invalid broadcast address [%s].", bcast_arg); + usage(OCF_ERR_CONFIGURED); + /* not reached */ + } + + best_netmask = htonl(best_netmask); + if (!OutputInCIDR) { + printf("%s\tnetmask %d.%d.%d.%d\tbroadcast %s\n" + , best_if + , (int)((best_netmask>>24) & 0xff) + , (int)((best_netmask>>16) & 0xff) + , (int)((best_netmask>>8) & 0xff) + , (int)(best_netmask & 0xff) + , bcast_arg); + }else{ + printf("%s\tnetmask %d\tbroadcast %s\n" + , best_if + , netmask_bits(best_netmask) + , bcast_arg); + } + }else{ + /* No, we use a common broadcast address convention */ + unsigned long def_bcast; + + /* Common broadcast address */ + def_bcast = (in.s_addr | (~best_netmask)); +#if DEBUG + fprintf(stderr, "best_netmask = %08lx, def_bcast = %08lx\n" + , best_netmask, def_bcast); +#endif + + /* Make things a bit more machine-independent */ + best_netmask = htonl(best_netmask); + def_bcast = htonl(def_bcast); + if (!OutputInCIDR) { + printf("%s\tnetmask %d.%d.%d.%d\tbroadcast %d.%d.%d.%d\n" + , best_if + , (int)((best_netmask>>24) & 0xff) + , (int)((best_netmask>>16) & 0xff) + , (int)((best_netmask>>8) & 0xff) + , (int)(best_netmask & 0xff) + , (int)((def_bcast>>24) & 0xff) + , (int)((def_bcast>>16) & 0xff) + , (int)((def_bcast>>8) & 0xff) + , (int)(def_bcast & 0xff)); + }else{ + printf("%s\tnetmask %d\tbroadcast %d.%d.%d.%d\n" + , best_if + , netmask_bits(best_netmask) + , (int)((def_bcast>>24) & 0xff) + , (int)((def_bcast>>16) & 0xff) + , (int)((def_bcast>>8) & 0xff) + , (int)(def_bcast & 0xff)); + } + } + return(0); +} + +void +usage(int ec) +{ + fprintf(stderr, "\n" + "%s version 2.99.1 Copyright Alan Robertson\n" + "\n" + "Usage: %s [-C]\n" + "Options:\n" + " -C: Output netmask as the number of bits rather " + "than as 4 octets.\n" + "Environment variables:\n" + "OCF_RESKEY_ip ip address (mandatory!)\n" + "OCF_RESKEY_cidr_netmask netmask of interface\n" + "OCF_RESKEY_broadcast broadcast address for interface\n" + "OCF_RESKEY_nic interface to assign to\n" + , cmdname, cmdname); + exit(ec); +} + +/* +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth0 33D60987 00000000 0005 0 0 0 FFFFFFFF 0 0 0 +eth0 00D60987 00000000 0001 0 0 0 00FFFFFF 0 0 0 +lo 0000007F 00000000 0001 0 0 0 000000FF 0 0 0 +eth0 00000000 FED60987 0003 0 0 0 00000000 0 0 0 + +netstat -rn outpug from RedHat Linux 6.0 +Kernel IP routing table +Destination Gateway Genmask Flags MSS Window irtt Iface +192.168.85.2 0.0.0.0 255.255.255.255 UH 0 0 0 eth1 +10.0.0.2 0.0.0.0 255.255.255.255 UH 0 0 0 eth2 +208.132.134.61 0.0.0.0 255.255.255.255 UH 0 0 0 eth0 +208.132.134.32 0.0.0.0 255.255.255.224 U 0 0 0 eth0 +192.168.85.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1 +10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth2 +127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo +0.0.0.0 208.132.134.33 0.0.0.0 UG 0 0 0 eth0 + +|-------------------------------------------------------------------------------- +netstat -rn output from FreeBSD 3.3 +Routing tables + +Internet: +Destination Gateway Flags Refs Use Netif Expire +default 209.61.94.161 UGSc 3 8 pn0 +192.168 link#1 UC 0 0 xl0 +192.168.0.2 0:60:8:a4:91:fd UHLW 0 38 lo0 +192.168.0.255 ff:ff:ff:ff:ff:ff UHLWb 1 7877 xl0 +209.61.94.160/29 link#2 UC 0 0 pn0 +209.61.94.161 0:a0:cc:26:c2:ea UHLW 6 17265 pn0 1105 +209.61.94.162 0:a0:cc:27:1c:fb UHLW 1 568 pn0 1098 +209.61.94.163 0:a0:cc:29:1f:86 UHLW 0 4749 pn0 1095 +209.61.94.166 0:a0:cc:27:2d:e1 UHLW 0 12 lo0 +209.61.94.167 ff:ff:ff:ff:ff:ff UHLWb 0 10578 pn0 + +|-------------------------------------------------------------------------------- +netstat -rn output from FreeBSD 4.2 +Routing tables + +Internet: +Destination Gateway Flags Refs Use Netif Expire +default 64.65.195.1 UGSc 1 11 dc0 +64.65.195/24 link#1 UC 0 0 dc0 => +64.65.195.1 0:3:42:3b:0:dd UHLW 2 0 dc0 1131 +64.65.195.184 0:a0:cc:29:1f:86 UHLW 2 18098 dc0 1119 +64.65.195.194 0:a0:cc:27:2d:e1 UHLW 3 335161 dc0 943 +64.65.195.200 52:54:0:db:33:b3 UHLW 0 13 dc0 406 +64.65.195.255 ff:ff:ff:ff:ff:ff UHLWb 1 584 dc0 +127.0.0.1 127.0.0.1 UH 0 0 lo0 +192.168/16 link#2 UC 0 0 vx0 => +192.168.0.1 0:20:af:e2:f0:36 UHLW 0 2 lo0 +192.168.255.255 ff:ff:ff:ff:ff:ff UHLWb 0 1 vx0 + +Internet6: +Destination Gateway Flags Netif Expire +::1 ::1 UH lo0 +fe80::%dc0/64 link#1 UC dc0 +fe80::%vx0/64 link#2 UC vx0 +fe80::%lo0/64 fe80::1%lo0 Uc lo0 +ff01::/32 ::1 U lo0 +ff02::%dc0/32 link#1 UC dc0 +ff02::%vx0/32 link#2 UC vx0 +ff02::%lo0/32 fe80::1%lo0 UC lo0 +*/ + diff --git a/tools/nfsconvert.in b/tools/nfsconvert.in new file mode 100644 index 0000000..c58a16a --- /dev/null +++ b/tools/nfsconvert.in @@ -0,0 +1,324 @@ +#!@PYTHON@ -tt +""" +Read in the deprecated /etc/sysconfig/nfs file and +set the corresponding values in nfs.conf +""" + +from __future__ import print_function +import os +import sys +import getopt +import subprocess +import configparser + +CONF_NFS = '/etc/nfs.conf' +CONF_IDMAP = '/etc/idmapd.conf' +SYSCONF_NFS = '/etc/sysconfig/nfs' +SYSCONF_BACKUP = ".rpmsave" +CONF_TOOL = '/usr/sbin/nfsconf' + +# options for nfsd found in RPCNFSDARGS +OPTS_NFSD = 'dH:p:rR:N:V:stTuUG:L:' +LONG_NFSD = ['debug', 'host=', 'port=', 'rdma=', 'nfs-version=', 'no-nfs-version=', + 'tcp', 'no-tcp', 'udp', 'no-udp', 'grace-time=', 'lease-time='] +CONV_NFSD = {'-d': (CONF_NFS, 'nfsd', 'debug', 'all'), + '-H': (CONF_NFS, 'nfsd', 'host', ','), + '-p': (CONF_NFS, 'nfsd', 'port', '$1'), + '-r': (CONF_NFS, 'nfsd', 'rdma', 'nfsrdma'), + '-R': (CONF_NFS, 'nfsd', 'rdma', '$1'), + '-N': (CONF_NFS, 'nfsd', 'vers$1', 'n'), + '-V': (CONF_NFS, 'nfsd', 'vers$1', 'y'), + '-t': (CONF_NFS, 'nfsd', 'tcp', '1'), + '-T': (CONF_NFS, 'nfsd', 'tcp', '0'), + '-u': (CONF_NFS, 'nfsd', 'udp', '1'), + '-U': (CONF_NFS, 'nfsd', 'udp', '0'), + '-G': (CONF_NFS, 'nfsd', 'grace-time', '$1'), + '-L': (CONF_NFS, 'nfsd', 'lease-time', '$1'), + '$1': (CONF_NFS, 'nfsd', 'threads', '$1'), + '--debug': (CONF_NFS, 'nfsd', 'debug', 'all'), + '--host': (CONF_NFS, 'nfsd', 'host', ','), + '--port': (CONF_NFS, 'nfsd', 'port', '$1'), + '--rdma': (CONF_NFS, 'nfsd', 'rdma', '$1'), + '--no-nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'n'), + '--nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'y'), + '--tcp': (CONF_NFS, 'nfsd', 'tcp', '1'), + '--no-tcp': (CONF_NFS, 'nfsd', 'tcp', '0'), + '--udp': (CONF_NFS, 'nfsd', 'udp', '1'), + '--no-udp': (CONF_NFS, 'nfsd', 'udp', '0'), + '--grace-time': (CONF_NFS, 'nfsd', 'grace-time', '$1'), + '--lease-time': (CONF_NFS, 'nfsd', 'lease-time', '$1'), + } + +# options for mountd found in RPCMOUNTDOPTS +OPTS_MOUNTD = 'go:d:H:p:N:nrs:t:V:' +LONG_MOUNTD = ['descriptors=', 'debug=', 'nfs-version=', 'no-nfs-version=', + 'port=', 'no-tcp', 'ha-callout=', 'state-directory-path=', + 'num-threads=', 'reverse-lookup', 'manage-gids', 'no-udp'] + +CONV_MOUNTD = {'-g': (CONF_NFS, 'mountd', 'manage-gids', '1'), + '-o': (CONF_NFS, 'mountd', 'descriptors', '$1'), + '-d': (CONF_NFS, 'mountd', 'debug', '$1'), + '-H': (CONF_NFS, 'mountd', 'ha-callout', '$1'), + '-p': (CONF_NFS, 'mountd', 'port', '$1'), + '-N': (CONF_NFS, 'nfsd', 'vers$1', 'n'), + '-V': (CONF_NFS, 'nfsd', 'vers$1', 'y'), + '-n': (CONF_NFS, 'nfsd', 'tcp', '0'), + '-s': (CONF_NFS, 'mountd', 'stat-directory-path', '$1'), + '-t': (CONF_NFS, 'mountd', 'threads', '$1'), + '-r': (CONF_NFS, 'mountd', 'reverse-lookup', '1'), + '-u': (CONF_NFS, 'nfsd', 'udp', '0'), + '--manage-gids': (CONF_NFS, 'mountd', 'manage-gids', '1'), + '--descriptors': (CONF_NFS, 'mountd', 'descriptors', '$1'), + '--debug': (CONF_NFS, 'mountd', 'debug', '$1'), + '--ha-callout': (CONF_NFS, 'mountd', 'ha-callout', '$1'), + '--port': (CONF_NFS, 'mountd', 'port', '$1'), + '--nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'y'), + '--no-nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'n'), + '--no-tcp': (CONF_NFS, 'nfsd', 'tcp', '0'), + '--state-directory-path': (CONF_NFS, 'mountd', 'state-directory-path', '$1'), + '--num-threads': (CONF_NFS, 'mountd', 'threads', '$1'), + '--reverse-lookup': (CONF_NFS, 'mountd', 'reverse-lookup', '1'), + '--no-udp': (CONF_NFS, 'nfsd', 'udp', '0'), + } + +# options for statd found in STATDARG +OPTS_STATD = 'o:p:T:U:n:P:H:L' +LONG_STATD = ['outgoing-port=', 'port=', 'name=', 'state-directory-path=', + 'ha-callout=', 'nlm-port=', 'nlm-udp-port=', 'no-notify'] +CONV_STATD = {'-o': (CONF_NFS, 'statd', 'outgoing-port', '$1'), + '-p': (CONF_NFS, 'statd', 'port', '$1'), + '-T': (CONF_NFS, 'lockd', 'port', '$1'), + '-U': (CONF_NFS, 'lockd', 'udp-port', '$1'), + '-n': (CONF_NFS, 'statd', 'name', '$1'), + '-P': (CONF_NFS, 'statd', 'state-directory-path', '$1'), + '-H': (CONF_NFS, 'statd', 'ha-callout', '$1'), + '-L': (CONF_NFS, 'statd', 'no-notify', '1'), + '--outgoing-port': (CONF_NFS, 'statd', 'outgoing-port', '$1'), + '--port': (CONF_NFS, 'statd', 'port', '$1'), + '--name': (CONF_NFS, 'statd', 'name', '$1'), + '--state-directory-path': (CONF_NFS, 'statd', 'state-directory-path', '$1'), + '--ha-callout': (CONF_NFS, 'statd', 'ha-callout', '$1'), + '--nlm-port': (CONF_NFS, 'lockd', 'port', '$1'), + '--nlm-udp-port': (CONF_NFS, 'lockd', 'udp-port', '$1'), + '--no-notify': (CONF_NFS, 'statd', 'no-notify', '1'), + } + +# options for sm-notify found in SMNOTIFYARGS +OPTS_SMNOTIFY = 'dm:np:v:P:f' +CONV_SMNOTIFY = {'-d': (CONF_NFS, 'sm-notify', 'debug', 'all'), + '-m': (CONF_NFS, 'sm-notify', 'retry-time', '$1'), + '-n': (CONF_NFS, 'sm-notify', 'update-state', '1'), + '-p': (CONF_NFS, 'sm-notify', 'outgoing-port', '$1'), + '-v': (CONF_NFS, 'sm-notify', 'outgoing-addr', '$1'), + '-f': (CONF_NFS, 'sm-notify', 'force', '1'), + '-P': (CONF_NFS, 'statd', 'state-directory-path', '$1'), + } + +# options for idmapd found in RPCIDMAPDARGS +OPTS_IDMAPD = 'vp:CS' +CONV_IDMAPD = {'-v': (CONF_IDMAP, 'general', 'verbosity', '+'), + '-p': (CONF_NFS, 'general', 'pipefs-directory', '$1'), + '-C': (CONF_IDMAP, 'general', 'client-only', '1'), + '-S': (CONF_IDMAP, 'general', 'server-only', '1'), + } + +# options for gssd found in RPCGSSDARGS +OPTS_GSSD = 'Mnvrp:k:d:t:T:R:lD' +CONV_GSSD = {'-M': (CONF_NFS, 'gssd', 'use-memcache', '1'), + '-n': (CONF_NFS, 'gssd', 'root_uses_machine_creds', '0'), + '-v': (CONF_NFS, 'gssd', 'verbosity', '+'), + '-r': (CONF_NFS, 'gssd', 'rpc-verbosity', '+'), + '-p': (CONF_NFS, 'general', 'pipefs-directory', '$1'), + '-k': (CONF_NFS, 'gssd', 'keytab-file', '$1'), + '-d': (CONF_NFS, 'gssd', 'cred-cache-directory', '$1'), + '-t': (CONF_NFS, 'gssd', 'context-timeout', '$1'), + '-T': (CONF_NFS, 'gssd', 'rpc-timeout', '$1'), + '-R': (CONF_NFS, 'gssd', 'preferred-realm', '$1'), + '-l': (CONF_NFS, 'gssd', 'limit-to-legacy-enctypes', '0'), + '-D': (CONF_NFS, 'gssd', 'avoid-dns', '0'), + } + +# options for blkmapd found in BLKMAPDARGS +OPTS_BLKMAPD = '' +CONV_BLKMAPD = {} + +# meta list of all the getopt lists +GETOPT_MAPS = [('RPCNFSDARGS', OPTS_NFSD, LONG_NFSD, CONV_NFSD), + ('RPCMOUNTDOPTS', OPTS_MOUNTD, LONG_MOUNTD, CONV_MOUNTD), + ('STATDARG', OPTS_STATD, LONG_STATD, CONV_STATD), + ('STATDARGS', OPTS_STATD, LONG_STATD, CONV_STATD), + ('SMNOTIFYARGS', OPTS_SMNOTIFY, [], CONV_SMNOTIFY), + ('RPCIDMAPDARGS', OPTS_IDMAPD, [], CONV_IDMAPD), + ('RPCGSSDARGS', OPTS_GSSD, [], CONV_GSSD), + ('BLKMAPDARGS', OPTS_BLKMAPD, [], CONV_BLKMAPD), + ] + +# any fixups we need to apply first +GETOPT_FIXUP = {'RPCNFSDARGS': ('--rdma', '--rdma=nfsrdma'), + } + +# map for all of the single option values +VALUE_MAPS = {'LOCKD_TCPPORT': (CONF_NFS, 'lockd', 'port', '$1'), + 'LOCKD_UDPPORT': (CONF_NFS, 'lockd', 'udp-port', '$1'), + 'RPCNFSDCOUNT': (CONF_NFS, 'nfsd', 'threads', '$1'), + 'NFSD_V4_GRACE': (CONF_NFS, 'nfsd', 'grace-time', '$1'), + 'NFSD_V4_LEASE': (CONF_NFS, 'nfsd', 'lease-time', '$1'), + 'MOUNTD_PORT': (CONF_NFS, 'mountd', 'port', '$1'), + 'STATD_PORT': (CONF_NFS, 'statd', 'port', '$1'), + 'STATD_OUTGOING_PORT': (CONF_NFS, 'statd', 'outgoing-port', '$1'), + 'STATD_HA_CALLOUT': (CONF_NFS, 'statd', 'ha-callout', '$1'), + 'GSS_USE_PROXY': (CONF_NFS, 'gssd', 'use-gss-proxy', '$1') + } + +def eprint(*args, **kwargs): + """ Print error to stderr """ + print(*args, file=sys.stderr, **kwargs) + +def makesub(param, value): + """ Variable substitution """ + return param.replace('$1', value) + +def set_value(value, entry): + """ Set a configuration value by running nfsconf tool""" + cfile, section, tag, param = entry + + tag = makesub(tag, value) + param = makesub(param, value) + if param == '+': + param = value + if param == ',': + param = value + args = [CONF_TOOL, "--file", cfile, "--set", section, tag, param] + + try: + subprocess.check_output(args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + print("Error running nfs-conf tool:\n %s" % (e.output.decode())) + print("Args: %s\n" % args) + raise Exception + +def convert_getopt(optname, options, optstring, longopts, conversions): + """ Parse option string into seperate config items + + Take a getopt string and a table of conversions + parse it all and spit out the converted config + + Keyword arguments: + options -- the argv string to convert + optstring -- getopt format option list + conversions -- table of translations + """ + optcount = 0 + try: + args = options.strip('\"').split() + if optname in GETOPT_FIXUP: + (k, v) = GETOPT_FIXUP[optname] + for i, opt in enumerate(args): + if opt == k: + args[i] = v + elif opt == '--': + break + optlist, optargs = getopt.gnu_getopt(args, optstring, longopts=longopts) + except getopt.GetoptError as err: + eprint(err) + raise Exception + + setlist = {} + for (k, v) in optlist: + if k in conversions: + # it's already been set once + param = conversions[k][3] + tag = k + makesub(conversions[k][2], v) + if tag in setlist: + value = setlist[tag][0] + # is it a cummulative entry + if param == '+': + value = str(int(value) + 1) + if param == ',': + value += "," + v + else: + if param == '+': + value = "1" + elif param == ',': + value = v + else: + value = v + setlist[tag] = (value, conversions[k]) + else: + if v: + eprint("Ignoring unrecognised option %s=%s in %s" % (k, v, optname)) + else: + eprint("Ignoring unrecognised option %s in %s" % (k, optname)) + + + for v, c in setlist.values(): + try: + set_value(v, c) + optcount += 1 + except Exception: + raise + + i = 1 + for o in optargs: + opname = '$' + str(i) + if opname in conversions: + try: + set_value(o, conversions[opname]) + optcount += 1 + except Exception: + raise + else: + eprint("Unrecognised trailing arguments") + raise Exception + i += 1 + + return optcount + +def map_values(): + """ Main function """ + mapcount = 0 + + # Lets load the old config + with open(SYSCONF_NFS) as cfile: + file_content = '[sysconf]\n' + cfile.read() + sysconfig = configparser.RawConfigParser() + sysconfig.read_string(file_content) + + # Map all the getopt option lists + for (name, opts, lopts, conv) in GETOPT_MAPS: + if name in sysconfig['sysconf']: + try: + mapcount += convert_getopt(name, sysconfig['sysconf'][name], opts, + lopts, conv) + except Exception: + eprint("Error whilst converting %s to nfsconf options." % (name)) + raise + + # Map the single value options + for name, opts in VALUE_MAPS.items(): + if name in sysconfig['sysconf']: + try: + value = sysconfig['sysconf'][name] + set_value(value.strip('\"'), opts) + mapcount += 1 + except Exception: + raise + + # All went well, move aside the old file + # but dont bother if there were no changes and + # an old config file already exists + backupfile = SYSCONF_NFS + SYSCONF_BACKUP + if mapcount > 0 or not os.path.exists(backupfile): + try: + os.replace(SYSCONF_NFS, backupfile) + except OSError as err: + eprint("Error moving old config %s: %s" % (SYSCONF_NFS, err)) + raise + +# Main routine +try: + map_values() +except Exception as e: + eprint(e) + eprint("Conversion failed. Please correct the error and try again.") + exit(1) diff --git a/tools/ocf-tester.8 b/tools/ocf-tester.8 new file mode 100644 index 0000000..3f39828 --- /dev/null +++ b/tools/ocf-tester.8 @@ -0,0 +1,48 @@ +.TH OCF-TESTER "8" "July 2022" "Tool for testing if a cluster resource is OCF compliant" "System Administration Utilities" +.SH NAME +ocf-tester \- Part of the Linux-HA project +.SH SYNOPSIS +.B ocf-tester +[\fI-hvqdX\fR] \fI-n resource_name \fR[\fI-o name=value\fR]\fI* /full/path/to/resource/agent\fR +.SH DESCRIPTION +Tool for testing if a cluster resource is OCF compliant +.SH OPTIONS +.TP +\fB\-h\fR +This text +.TP +\fB\-v\fR +Be verbose while testing +.TP +\fB\-q\fR +Be quiet while testing +.TP +\fB\-d\fR +Turn on RA debugging +.TP +\fB\-n\fR name +Name of the resource +.TP +\fB\-o\fR name=value +Name and value of any parameters required by the agent +.TP +\fB\-h\fR +This text +.TP +\fB\-v\fR +Be verbose while testing +.TP +\fB\-q\fR +Be quiet while testing +.TP +\fB\-d\fR +Turn on RA debugging +.TP +\fB\-X\fR +Turn on RA tracing (expect large output) +.TP +\fB\-n\fR name +Name of the resource +.TP +\fB\-o\fR name=value +Name and value of any parameters required by the agent diff --git a/tools/ocf-tester.in b/tools/ocf-tester.in new file mode 100755 index 0000000..cfdfd70 --- /dev/null +++ b/tools/ocf-tester.in @@ -0,0 +1,351 @@ +#!/bin/sh +# +# $Id: ocf-tester,v 1.2 2006/08/14 09:38:20 andrew Exp $ +# +# Copyright (c) 2006 Novell Inc, Andrew Beekhof +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +DATADIR=@datadir@ +METADATA_LINT="xmllint --noout --valid -" + +# set some common meta attributes, which are expected to be +# present by resource agents +export OCF_RESKEY_CRM_meta_timeout=20000 # 20 seconds timeout +export OCF_RESKEY_CRM_meta_interval=10000 # reset this for probes + +num_errors=0 + +info() { + [ "$quiet" -eq 1 ] && return + echo "$*" +} +debug() { + [ "$verbose" -eq 0 ] && return + echo "$*" +} +usage() { + # make sure to output errors on stderr + [ "x$1" = "x0" ] || exec >&2 + + echo "Tool for testing if a cluster resource is OCF compliant" + echo "" + echo "Usage: ocf-tester [-hvqdX] -n resource_name [-o name=value]* /full/path/to/resource/agent" + echo "" + echo "Options:" + echo " -h This text" + echo " -v Be verbose while testing" + echo " -q Be quiet while testing" + echo " -d Turn on RA debugging" + echo " -X Turn on RA tracing (expect large output)" + echo " -n name Name of the resource" + echo " -o name=value Name and value of any parameters required by the agent" + exit $1 +} + +assert() { + rc=$1; shift + target=$1; shift + msg=$1; shift + local targetrc matched + + if [ $# = 0 ]; then + exit_code=0 + else + exit_code=$1; shift + fi + + for targetrc in `echo $target | tr ':' ' '`; do + [ $rc -eq $targetrc ] && matched=1 + done + if [ "$matched" != 1 ]; then + num_errors=`expr $num_errors + 1` + echo "* rc=$rc: $msg" + if [ $exit_code != 0 ]; then + [ -n "$command_output" ] && cat<<EOF +$command_output +EOF + echo "Aborting tests" + exit $exit_code + fi + fi + command_output="" +} + +done=0 +ra_args="" +verbose=0 +quiet=0 +while test "$done" = "0"; do + case "$1" in + -n) OCF_RESOURCE_INSTANCE=$2; ra_args="$ra_args OCF_RESOURCE_INSTANCE=$2"; shift; shift;; + -o) name=${2%%=*}; value=${2#*=}; + lrm_ra_args="$lrm_ra_args $2"; + ra_args="$ra_args OCF_RESKEY_$name='$value'"; shift; shift;; + -v) verbose=1; shift;; + -d) export HA_debug=1; shift;; + -X) export OCF_TRACE_RA=1; verbose=1; shift;; + -q) quiet=1; shift;; + -?|--help) usage 0;; + --version) echo "@PACKAGE_VERSION@"; exit 0;; + -*) echo "unknown option: $1" >&2; usage 1;; + *) done=1;; + esac +done + +if [ "x" = "x$OCF_ROOT" ]; then + if [ -d /usr/lib/ocf ]; then + export OCF_ROOT=/usr/lib/ocf + else + echo "You must supply the location of OCF_ROOT (common location is /usr/lib/ocf)" >&2 + usage 1 + fi +fi + +if [ "x" = "x$OCF_RESOURCE_INSTANCE" ]; then + echo "You must give your resource a name, set OCF_RESOURCE_INSTANCE" >&2 + usage 1 +fi + +agent=$1 +if [ ! -e $agent ]; then + echo "You must provide the full path to your resource agent" >&2 + usage 1 +fi +installed_rc=5 +stopped_rc=7 +has_demote=1 +has_promote=1 + +test_permissions() { + action=meta-data + debug ${1:-"Testing permissions with uid nobody"} + su nobody -s /bin/sh -c "$agent $action" > /dev/null +} + +test_metadata() { + action=meta-data + msg=${1:-"Testing: $action"} + debug $msg + $agent $action | (cd $DATADIR/resource-agents && $METADATA_LINT) + rc=$? + #echo rc: $rc + return $rc +} + +test_command() { + action=$1; shift + export __OCF_ACTION=$action + msg=${1:-"Testing: $action"} + #echo Running: "export $ra_args; $agent $action 2>&1 > /dev/null" + if [ $verbose -eq 0 ]; then + command_output=`$agent $action 2>&1` + else + debug $msg + $agent $action + fi + rc=$? + #echo rc: $rc + return $rc +} + +# Begin tests +info "Beginning tests for $agent..." + +if [ ! -f $agent ]; then + assert 7 0 "Could not find file: $agent" +fi + +if [ `id -u` = 0 ]; then + test_permissions + assert $? 0 "Your agent has too restrictive permissions: should be 755" +else + echo "WARN: Can't check agent's permissions because we're not root; they should be 755" +fi + +test_metadata +assert $? 0 "Your agent produces meta-data which does not conform to ra-api-1.dtd" + +OCF_TESTER_FAIL_HAVE_BINARY=1 +export OCF_TESTER_FAIL_HAVE_BINARY +test_command meta-data +rc=$? +if [ $rc -eq 3 ]; then + assert $rc 0 "Your agent does not support the meta-data action" +else + assert $rc 0 "The meta-data action cannot fail and must return 0" +fi +unset OCF_TESTER_FAIL_HAVE_BINARY + +ra_args="export $ra_args" +eval $ra_args +test_command validate-all +rc=$? +if [ $rc -eq 3 ]; then + assert $rc 0 "Your agent does not support the validate-all action" +elif [ $rc -ne 0 ]; then + assert $rc 0 "Validation failed. Did you supply enough options with -o ?" 1 + usage $rc +fi + +test_command monitor "Checking current state" +rc=$? +if [ $rc -eq 3 ]; then + assert $rc 7 "Your agent does not support the monitor action" 1 + +elif [ $rc -eq 8 ]; then + test_command demote "Cleanup, demote" + assert $? 0 "Your agent was promoted and could not be demoted" 1 + + test_command stop "Cleanup, stop" + assert $? 0 "Your agent was promoted and could not be stopped" 1 + +elif [ $rc -ne 7 ]; then + test_command stop + assert $? 0 "Your agent was active and could not be stopped" 1 +fi + +test_command monitor +assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc" + +OCF_TESTER_FAIL_HAVE_BINARY=1 +export OCF_TESTER_FAIL_HAVE_BINARY +OCF_RESKEY_CRM_meta_interval=0 +test_command monitor +assert $? $stopped_rc:$installed_rc "The initial probe for a stopped resource should return $stopped_rc or $installed_rc even if all binaries are missing" +unset OCF_TESTER_FAIL_HAVE_BINARY +OCF_RESKEY_CRM_meta_interval=20000 + +test_command start +assert $? 0 "Start failed. Did you supply enough options with -o ?" 1 + +test_command monitor +assert $? 0 "Monitoring an active resource should return 0" + +OCF_RESKEY_CRM_meta_interval=0 +test_command monitor +assert $? 0 "Probing an active resource should return 0" +OCF_RESKEY_CRM_meta_interval=20000 + +test_command notify +rc=$? +if [ $rc -eq 3 ]; then + info "* Your agent does not support the notify action (optional)" +else + assert $rc 0 "The notify action cannot fail and must return 0" +fi + +test_command demote "Checking for demote action" +if [ $? -eq 3 ]; then + has_demote=0 + info "* Your agent does not support the demote action (optional)" +fi + +test_command promote "Checking for promote action" +if [ $? -eq 3 ]; then + has_promote=0 + info "* Your agent does not support the promote action (optional)" +fi + +if [ $has_promote -eq 1 -a $has_demote -eq 1 ]; then + test_command demote "Testing: demotion of started resource" + assert $? 0 "Demoting a start resource should not fail" + + test_command promote + assert $? 0 "Promote failed" + + test_command demote + assert $? 0 "Demote failed" 1 + + test_command demote "Testing: demotion of demoted resource" + assert $? 0 "Demoting a demoted resource should not fail" + + test_command promote "Promoting resource" + assert $? 0 "Promote failed" 1 + + test_command promote "Testing: promotion of promoted resource" + assert $? 0 "Promoting a promoted resource should not fail" + + test_command demote "Demoting resource" + assert $? 0 "Demote failed" 1 + +elif [ $has_promote -eq 0 -a $has_demote -eq 0 ]; then + info "* Your agent does not support promotable clones (optional)" + +else + echo "* Your agent partially supports promotable clones" + num_errors=`expr $num_errors + 1` +fi + +test_command stop +assert $? 0 "Stop failed" 1 + +test_command monitor +assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc" + +test_command start "Restarting resource..." +assert $? 0 "Start failed" 1 + +test_command monitor +assert $? 0 "Monitoring an active resource should return 0" + +test_command start "Testing: starting a started resource" +assert $? 0 "Starting a running resource is required to succeed" + +test_command monitor +assert $? 0 "Monitoring an active resource should return 0" + +test_command stop "Stopping resource" +assert $? 0 "Stop could not clean up after multiple starts" 1 + +test_command monitor +assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc" + +test_command stop "Testing: stopping a stopped resource" +assert $? 0 "Stopping a stopped resource is required to succeed" + +test_command monitor +assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc" + +test_command migrate_to "Checking for migrate_to action" +rc=$? +if [ $rc -ne 3 ]; then + test_command migrate_from "Checking for migrate_from action" +fi +if [ $? -eq 3 ]; then + info "* Your agent does not support the migrate action (optional)" +fi + +test_command reload "Checking for reload action" +if [ $? -eq 3 ]; then + info "* Your agent does not support the reload action (optional)" +fi + +if [ $num_errors -gt 0 ]; then + echo "Tests failed: $agent failed $num_errors tests" >&2 + exit 1 +else + echo $agent passed all tests + exit 0 +fi + +# vim:et:ts=8:sw=4 diff --git a/tools/ocft/ChangeLog b/tools/ocft/ChangeLog new file mode 100644 index 0000000..8b00210 --- /dev/null +++ b/tools/ocft/ChangeLog @@ -0,0 +1,57 @@ +0.43: + - Add an option 'Agent' in 'CONFIG' + - Fix a bug about remote shell. + - Add top level option 'VARIABLE'. + - Add top level option 'CLEANUP-AGENT'. + - Rename Var & Unvar to Env & Unenv. + - Fix a bug about agent installation. + - Modified configs of agent according the new syntax. +0.42: + - Fix a bug about agent installation. + - The tests stop early if the basic functionality it not there. + - Fix a bug about 'config parser'. + - Keep the resource stat between the runs to avoid there are multiple stop/start per run. + - Add a function with warning output. + - Adjust the sequence of cases running. + - Change the default timeout. + - Replace the crm_master command in Agent scripts, it is no use in testing, + because the agent runs in local when test. + - Support drbd testing if the configuration is correct. + - Fix a 'Include' bug. + - Fix a bug about configuration of 'mysql'. +0.41: + - Fixed a remote shell bug. + - Improved 'Include' option, now it supports remote shell, invoked just like 'Include@ip_address'. + - Show line number, if syntax error occurs in config file. + - Add a 'AgentRoot' sub-option. + - Fix a bug with config parsing. + - Rename ACTION "run" to "test". + - Add a simple output mode for "test" ACTION. + - Add a verbose ouput mode for "help" ACTION. + - The output will be recorded to log file. + - Add double stop and double start to tests. + - Exit with 1 if test script failed. + - The output of agents will be exposed if the test fails. + - Run zypper with -q + - Use 127.0.0.x(and lo for interface) for agents IP* +0.4: + - Add a 'CASE-BLOCK' top option, it can be included by 'CASE' + - Add a 'SETUP-AGENT' top option, you can initialize agents before testing. + - Add a 'Include' sub-option. + - Add a 'Unvar' sub-option. + - Rename 'GLOBAL' to 'CONFIG'. + - Modify a part of syntax. + - Imporve the 'InstallPackage' option, now it supports SUSE, Redhat and Debian. + - Add README & README.zh_CN. +0.3: + - All statements in CASE can be executed by remote shell. +0.2: + - Implement options: BashAtExit, GLOBAL, HangTimeout, InstallPackage. + - Delete option: Name. + - Modify a part of syntax. + - Fix some bugs. + - Print testing message in human readable format. + - Improve 'make' and 'run' option. + - Add 'clean' option. +0.1: + - Implement options: CASE, Bash, Run, Var, Name. diff --git a/tools/ocft/Filesystem b/tools/ocft/Filesystem new file mode 100644 index 0000000..2b22d1b --- /dev/null +++ b/tools/ocft/Filesystem @@ -0,0 +1,105 @@ +# Filesystem +# by dejan@suse.de on +# Tue Feb 15 18:50:04 CET 2011 +# +CONFIG + Agent Filesystem + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +VARIABLE + OCFT_rundir="`get_rundir`" + OCFT_fs="$OCFT_rundir/resource-agents/ocft-Filesystem-fs" + OCFT_dir="$OCFT_rundir/resource-agents/ocft-Filesystem-mnt" + OCFT_loop="`loopbackeddev make $OCFT_fs 16M`" + +SETUP-AGENT + mke2fs -j -Fq -m 0 $OCFT_loop + +CLEANUP-AGENT + loopbackeddev unmake $OCFT_fs + rmdir $OCFT_dir + +CASE-BLOCK required_args + Env OCF_RESKEY_device=$OCFT_loop + Env OCF_RESKEY_fstype=ext3 + Env OCF_RESKEY_directory=$OCFT_dir + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_device'" + Include prepare + Env OCF_RESKEY_device=/dev/no_such_device + AgentRun start OCF_ERR_INSTALLED + +CASE "check base env: unset 'OCF_RESKEY_device'" + Include prepare + Unenv OCF_RESKEY_device + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "monitor depth 10 when running" + Include prepare + AgentRun start + Env OCF_CHECK_LEVEL=10 + AgentRun monitor OCF_SUCCESS + +CASE "monitor depth 20 with running" + Include prepare + AgentRun start + Env OCF_CHECK_LEVEL=20 + AgentRun monitor OCF_SUCCESS + +CASE "start insert failure (remove device)" + Include prepare + Bash losetup -d $OCFT_loop + BashAtExit losetup $OCFT_loop $OCFT_fs + AgentRun start OCF_ERR_GENERIC + +CASE "monitor depth 20 insert failure (r/o fs)" + Include prepare + AgentRun start + Bash mount -o remount,ro $OCFT_dir + BashAtExit mount -o remount,rw $OCFT_dir + Env OCF_CHECK_LEVEL=20 + AgentRun monitor OCF_ERR_GENERIC + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + diff --git a/tools/ocft/IPaddr2 b/tools/ocft/IPaddr2 new file mode 100644 index 0000000..04698a0 --- /dev/null +++ b/tools/ocft/IPaddr2 @@ -0,0 +1,137 @@ +# IPaddr2 + +CONFIG + Agent IPaddr2 + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +SETUP-AGENT + ip addr add 192.168.144.1/24 dev eth0 brd 192.168.144.255 + +CLEANUP-AGENT + ip addr del 192.168.144.1/24 dev eth0 + +CASE-BLOCK required_args + Env OCF_RESKEY_ip=192.168.144.2 + +CASE-BLOCK check_iflabel_assigned + Bash ip -4 -o addr show eth0 | grep -w 192.168.144.2/24 | grep -w eth0:iflabel >/dev/null # checking iflabel was assigned correctly + +CASE-BLOCK check_iflabel_removed + Bash ! ip -4 -o addr show eth0 | grep -w 192.168.144.2/24 | grep -w eth0:iflabel >/dev/null # checking iflabel was removed correctly + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_ip'" + Include prepare + Unenv OCF_RESKEY_ip + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set invalid 'OCF_RESKEY_ip'" + Include prepare + Env OCF_RESKEY_ip=not_ip_address + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set 'OCF_RESKEY_cidr_netmask'" + Include prepare + Env OCF_RESKEY_cidr_netmask=24 + AgentRun start OCF_SUCCESS + +CASE "check base env: set invalid 'OCF_RESKEY_cidr_netmask'" + Include prepare + Env OCF_RESKEY_cidr_netmask=not_netmask + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set 'OCF_RESKEY_broadcast'" + Include prepare + Env OCF_RESKEY_broadcast=192.168.144.255 + AgentRun start OCF_SUCCESS + +CASE "check base env: set invalid 'OCF_RESKEY_broadcast'" + Include prepare + Env OCF_RESKEY_broadcast=not_broadcast + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set 'OCF_RESKEY_nic'" + Include prepare + Env OCF_RESKEY_nic=eth0 + AgentRun start OCF_SUCCESS + +CASE "check base env: set invalid 'OCF_RESKEY_nic'" + Include prepare + Env OCF_RESKEY_nic=not_nic + AgentRun start OCF_ERR_CONFIGURED + AgentRun validate-all OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "Attachment to loopback interface" + Env OCF_RESKEY_ip=127.0.0.3 + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + +CASE "check additional env: set 'OCF_RESKEY_iflabel'" + Include prepare + Env OCF_RESKEY_nic=eth0 + Env OCF_RESKEY_iflabel=iflabel + AgentRun start OCF_SUCCESS + Include check_iflabel_assigned + AgentRun stop OCF_SUCCESS + Include check_iflabel_removed + +# This is deprecated but still supported for the compatibility. +CASE "check additional env: specify iflabel in 'OCF_RESKEY_nic'" + Include prepare + Env OCF_RESKEY_nic=eth0:iflabel + AgentRun start OCF_SUCCESS + Include check_iflabel_assigned + AgentRun stop OCF_SUCCESS + Include check_iflabel_removed + +# monitor should return OCF_ERR_GENERIC rather than OCF_ERR_CONFIGURED +# when the specified OCF_RESKEY_nic is vanished by a failure. +# This has been changed as of 3.9.6. +CASE "monitor failure when 'OCF_RESKEY_nic' is vanished" + Include prepare + Env OCF_RESKEY_nic=ethVanished + Env OCF_RESKEY_CRM_meta_interval=10 # not in probe + AgentRun monitor OCF_ERR_GENERIC diff --git a/tools/ocft/IPaddr2v4 b/tools/ocft/IPaddr2v4 new file mode 100644 index 0000000..4d37168 --- /dev/null +++ b/tools/ocft/IPaddr2v4 @@ -0,0 +1,323 @@ +# IPaddr2v4 + +# Note: This test case uses two NICs(eth0, eth1) and +# a IPv4 address prefix (192.168.144.0/24). +# Adjust them according to your environment at VARIABLE section if needed. + +CONFIG + Agent IPaddr2 + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +VARIABLE + OCFT_target_ip=192.168.144.2 + OCFT_target_nic=eth0 + OCFT_target_prefix=24 + OCFT_target_netaddr=192.168.144.1/$OCFT_target_prefix + OCFT_target_brd=192.168.144.255 + OCFT_wrong_ip=192.168.120.1 + OCFT_force_nic=eth1 + OCFT_force_prefix=16 + OCFT_force_prefix2=28 + OCFT_force_brd=192.168.255.255 + +SETUP-AGENT + ip addr add $OCFT_target_netaddr dev $OCFT_target_nic brd $OCFT_target_brd + +CLEANUP-AGENT + ip addr del $OCFT_target_netaddr dev $OCFT_target_nic + +CASE-BLOCK required_args + Env OCF_RESKEY_ip=$OCFT_target_ip + Env OCFT_check_ip=$OCFT_target_ip + Env OCFT_check_prefix=$OCFT_target_prefix + Env OCFT_check_nic=$OCFT_target_nic + +CASE-BLOCK check_ip_assigned + Bash ip -4 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv4 address was assigned correctly + +CASE-BLOCK check_ip_removed + Bash ! ip -4 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv4 address was removed correctly + +CASE-BLOCK base_ip_assigned + Bash ip addr add $OCFT_target_netaddr dev $OCFT_target_nic brd $OCFT_target_brd + +CASE-BLOCK base_ip_removed + Bash ip addr del $OCFT_target_netaddr dev $OCFT_target_nic + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +# CASE No.0 +# +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + Include check_ip_assigned + +# CASE No.1 +# +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.2 +# +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +# CASE No.3 +# +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +# CASE No.4 +# +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +# CASE No.5 +# +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +# CASE No.6 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 succeeds if the ip matched based on the netmask of the subnet +# or fails if it did not match to any. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 was using a wrong subnet mask (constant of 32) in this case. +# +CASE "params with nic, no cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_target_nic + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.7 +# +CASE "params with nic, cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_target_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.8 +# +CASE "error params with wrong ip" + Include prepare + Env OCF_RESKEY_ip=$OCFT_wrong_ip + AgentRun start OCF_ERR_GENERIC + +# CASE No.9 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 fails when it could not determine the correct subnet mask. +# When it could not get base ip, it becomes the error. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 was using a wrong subnet mask (constant of 32) in this case. +# +CASE "params with force nic" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCFT_check_nic=$OCFT_force_nic + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + Unenv OCF_RESKEY_nic + +# CASE No.10 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 fails when it could not determine the broadcast. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 succeeded but it's considered ambiguous. +# +CASE "params with force cidr_netmask (base netmask > assigned netmask)" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + Env OCFT_check_prefix=$OCFT_force_prefix + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + +# CASE No.11 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 succeeds but the broadcast is not set. +# This is because findif.sh can not calculate a broadcast from a netmask. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 succeeded with using a calculated broadcast. +# +CASE "force to use the specified nic and cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix + AgentRun start OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + Unenv OCF_RESKEY_cidr_netmask + + +# CASE No.12 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 fails when it could not determine the correct subnet mask. +# When it could not get base ip, it becomes the error. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 was using a wrong subnet mask (constant of 32) in this case. +# +CASE "error params with wrong ip and nic (not exist base_ip)" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_nic=$OCFT_target_nic + Env OCFT_check_nic=$OCFT_target_nic + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + Include base_ip_assigned + +# CASE No.13 +# +CASE "params with cidr_netmask" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.14 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 does not override the broadcast by cidr_netmask. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 overrode the broadcast calculated by cidr_netmask. +# +CASE "params with force cidr_netmask (base netmask < assigned netmask)" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCFT_check_prefix=$OCFT_force_prefix2 + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.15 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 fails when it could not determine the broadcast. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 succeeded but it's considered ambiguous. +# +CASE "error params with wrong ip and cidr_netmask (not exist base_ip)" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + AgentRun start OCF_ERR_GENERIC + Include base_ip_assigned + +# CASE No.16 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 succeeds but the broadcast is not set. +# This is because findif.sh can not calculate a broadcast from a netmask. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 succeeded with using a calculated broadcast. +# +CASE "force to use the specified nic and cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCFT_check_prefix=$OCFT_force_prefix2 + Env OCFT_check_nic=$OCFT_force_nic + AgentRun start OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.17 +# Note: this result is different from IPaddr2 in 3.9.3. +# IPaddr2 succeeds but the broadcast is not set. +# This is because findif.sh can not calculate a broadcast from a netmask. +# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed. +# IPaddr2 in 3.9.3 succeeded with using a calculated broadcast. +# +CASE "force to use the specified nic and cidr_netmask (not exist base_ip)" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCFT_check_prefix=$OCFT_force_prefix2 + Env OCFT_check_nic=$OCFT_force_nic + AgentRun start OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Unenv OCF_RESKEY_nic + Unenv OCF_RESKEY_cidr_netmask + Include base_ip_assigned + +# CASE No.18 +# +CASE "params with broadcast, no nic, no cidr_netmask" + Include prepare + Env OCF_RESKEY_broadcast=$OCFT_force_brd + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.19 +# +CASE "params with broadcast, no nic, no cidr_netmask" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_broadcast=$OCFT_force_brd + AgentRun start OCF_ERR_GENERIC + Include base_ip_assigned + +# CASE No.20 +# +CASE "force to use the specified nic and cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + Env OCF_RESKEY_broadcast=$OCFT_force_brd + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + Unenv OCF_RESKEY_cidr_netmask + +# CASE No.21 +# +CASE "force to use the specified nic and cidr_netmask" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCF_RESKEY_broadcast=$OCFT_target_brd + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix2 + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + Include base_ip_assigned + diff --git a/tools/ocft/IPaddr2v6 b/tools/ocft/IPaddr2v6 new file mode 100644 index 0000000..d24d890 --- /dev/null +++ b/tools/ocft/IPaddr2v6 @@ -0,0 +1,250 @@ +# IPaddr2v6 + +# Note: This test case uses two NICs(eth0, eth1) and +# a IPv6 address prefix (2001:db8::/32, RFC3849). +# Adjust them according to your environment at VARIABLE section if needed. + +CONFIG + Agent IPaddr2 + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +VARIABLE + OCFT_target_ip=2001:db8:1234::2 + OCFT_target_nic=eth0 + OCFT_target_prefix=64 + OCFT_target_netaddr=2001:db8:1234::1/$OCFT_target_prefix + OCFT_target_linklocal=fe80::2 + OCFT_wrong_ip=2001:db8:5678::2 + OCFT_force_nic=eth1 + OCFT_force_prefix=80 + OCFT_force_prefix2=48 + +SETUP-AGENT + ip addr add $OCFT_target_netaddr dev $OCFT_target_nic + +CLEANUP-AGENT + ip addr del $OCFT_target_netaddr dev $OCFT_target_nic + +CASE-BLOCK required_args + Env OCF_RESKEY_ip=$OCFT_target_ip + Env OCFT_check_ip=$OCFT_target_ip + Env OCFT_check_prefix=$OCFT_target_prefix + Env OCFT_check_nic=$OCFT_target_nic + +CASE-BLOCK check_ip_assigned + Bash ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was assigned correctly + +CASE-BLOCK check_ip_removed + Bash ! ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was removed correctly + +CASE-BLOCK base_ip_assigned + Bash ip addr add $OCFT_target_netaddr dev $OCFT_target_nic + +CASE-BLOCK base_ip_removed + Bash ip addr del $OCFT_target_netaddr dev $OCFT_target_nic + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +# CASE No.0 +# +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + Include check_ip_assigned + +# CASE No.1 +# +CASE "normal stop" + Include prepare + AgentRun start OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.2 +# +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +# CASE No.3 +# +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +# CASE No.4 +# +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +# CASE No.5 +# +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +# CASE No.6 +# +CASE "error params with wrong ip" + Include prepare + Env OCF_RESKEY_ip=$OCFT_wrong_ip + AgentRun start OCF_ERR_GENERIC + +# CASE No.7 +# +CASE "error params with no nic for a link-local IPv6 address" + Include prepare + Env OCF_RESKEY_ip=$OCFT_target_linklocal + Env OCFT_check_ip=$OCFT_target_linklocal + # nic is mandatory for a link-local address + AgentRun start OCF_ERR_CONFIGURED + +# CASE No.8 +# +CASE "params with nic, no cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_target_nic + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.9 +# +CASE "normal usage for a link-local IPv6 address, params with nic" + Include prepare + Env OCF_RESKEY_ip=$OCFT_target_linklocal + Env OCFT_check_ip=$OCFT_target_linklocal + # nic is mandatory for a link-local address + Env OCF_RESKEY_nic=$OCFT_target_nic + Env OCFT_check_nic=$OCFT_target_nic + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.10 +# +CASE "error params with wrong ip and nic (not exist base_ip)" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_nic=$OCFT_target_nic + Env OCFT_check_nic=$OCFT_target_nic + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + Include base_ip_assigned + +# CASE No.11 +# +CASE "params with force nic" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCFT_check_nic=$OCFT_force_nic + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + Unenv OCF_RESKEY_nic + +# CASE No.12 +# +CASE "params with force cidr_netmask" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + Env OCFT_check_prefix=$OCFT_target_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.13 +# +CASE "params with force cidr_netmask (base netmask < assigned netmask)" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + Env OCFT_check_prefix=$OCFT_force_prefix + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + +# CASE No.14 +# +CASE "params with force cidr_netmask (base netmask > assigned netmask)" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCFT_check_prefix=$OCFT_force_prefix2 + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + +# CASE No.15 +# +CASE "params with cidr_netmask" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + Env OCFT_check_prefix=$OCFT_target_prefix + AgentRun start OCF_ERR_GENERIC + Include base_ip_assigned + +# CASE No.16 +# +CASE "params with nic, cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_target_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + Env OCFT_check_nic=$OCFT_target_nic + Env OCFT_check_prefix=$OCFT_target_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.17 +# +CASE "force to use the specified nic and cidr_netmask (base netmask < assigned netmask)" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.18 +# This use case is now valid. It was not allowed until v3.9.2. +# +CASE "force to use the specified nic and cidr_netmask (base netmask > assigned netmask)" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix2 + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +# CASE No.19 +# +CASE "force to use the specified nic and cidr_netmask (base netmask > assigned netmask)" + Include prepare + Include base_ip_removed + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2 + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix2 + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun stop OCF_SUCCESS + Include check_ip_removed + Include base_ip_assigned + diff --git a/tools/ocft/IPsrcaddr b/tools/ocft/IPsrcaddr new file mode 100644 index 0000000..d95142e --- /dev/null +++ b/tools/ocft/IPsrcaddr @@ -0,0 +1,63 @@ +# IPsrcaddr + +CONFIG + Agent IPsrcaddr + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage iproute2 + HangTimeout 20 + +CASE-BLOCK required_args + Env OCF_RESKEY_ipaddress= # put here your IP + Env OCF_RESKEY_cidr_netmask= # and the netmask + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_ipaddress'" + Include prepare + Unenv OCF_RESKEY_ipaddress + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set invalid 'OCF_RESKEY_ipaddress'" + Include prepare + Env OCF_RESKEY_ipaddress=not_ip_address + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/IPv6addr b/tools/ocft/IPv6addr new file mode 100644 index 0000000..36a9642 --- /dev/null +++ b/tools/ocft/IPv6addr @@ -0,0 +1,150 @@ +# IPv6addr + +# Note: This test case uses two NICs(eth0, eth1) and +# a IPv6 address prefix (2001:db8::/32, RFC3849). +# Adjust them according to your environment at VARIABLE section if needed. + +CONFIG + Agent IPv6addr + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +VARIABLE + OCFT_target_ipv6addr=2001:db8:1234::2 + OCFT_target_nic=eth0 + OCFT_target_prefix=64 + OCFT_target_netaddr=2001:db8:1234::1/$OCFT_target_prefix + OCFT_target_linklocal=fe80::2 + OCFT_wrong_ipv6addr=2001:db8:5678::2 + OCFT_force_nic=eth1 + OCFT_force_prefix=80 + +SETUP-AGENT + ip addr add $OCFT_target_netaddr dev $OCFT_target_nic + +CLEANUP-AGENT + ip addr del $OCFT_target_netaddr dev $OCFT_target_nic + +CASE-BLOCK required_args + Env OCF_RESKEY_ipv6addr=$OCFT_target_ipv6addr + Env OCFT_check_ipv6addr=$OCFT_target_ipv6addr + Env OCFT_check_prefix=$OCFT_target_prefix + Env OCFT_check_nic=$OCFT_target_nic + +CASE-BLOCK check_ip_assigned + Bash ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ipv6addr/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was assigned correctly + +CASE-BLOCK check_ip_removed + Bash ! ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ipv6addr/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was removed correctly + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + Include check_ip_assigned + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "params with nic, no cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_target_nic + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +CASE "params with nic, cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_target_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +CASE "normal usage for a link-local IPv6 address" + Include prepare + Env OCF_RESKEY_ipv6addr=$OCFT_target_linklocal + Env OCFT_check_ipv6addr=$OCFT_target_linklocal + # nic is mandatory for a link-local address + Env OCF_RESKEY_nic=$OCFT_target_nic + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed + +CASE "error start for a link-local IPv6 address when no nic" + Include prepare + Env OCF_RESKEY_ipv6addr=$OCFT_target_linklocal + # nic is mandatory for a link-local address + Unenv OCF_RESKEY_nic + AgentRun start OCF_ERR_GENERIC + Include check_ip_removed + +CASE "error params with wrong ipv6addr" + Include prepare + Env OCF_RESKEY_ipv6addr=$OCFT_wrong_ipv6addr + AgentRun start OCF_ERR_GENERIC + +# Note: this result is different from IPaddr2/findif +# IPaddr2 succeeds if the ip matched based on the netmask of the subnet +# or fails if it did not match to any. +# Recommended to always specify both nic and cidr_netmask when you needed. +CASE "error params with wrong cidr_netmask" + Include prepare + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + AgentRun start OCF_ERR_GENERIC + +# Note: this result is different from IPaddr2/findif +# IPaddr2 succeeds but it uses /32 as a guessed cidr_netmask which +# does not seem to be expected. +# Recommended to always specify both nic and cidr_netmask when you needed. +CASE "error params with wrong nic" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + AgentRun start OCF_ERR_GENERIC + +# Note: This use case is now valid. It was not allowed until v3.9.2. +CASE "force to use the specified nic and cidr_netmask" + Include prepare + Env OCF_RESKEY_nic=$OCFT_force_nic + Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix + Env OCFT_check_nic=$OCFT_force_nic + Env OCFT_check_prefix=$OCFT_force_prefix + AgentRun start OCF_SUCCESS + Include check_ip_assigned + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + Include check_ip_removed diff --git a/tools/ocft/LVM b/tools/ocft/LVM new file mode 100644 index 0000000..00b69ee --- /dev/null +++ b/tools/ocft/LVM @@ -0,0 +1,83 @@ +# LVM +# by dejan@suse.de on +# Wed Feb 16 13:15:01 CET 2011 + +CONFIG + Agent LVM + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +VARIABLE + OCFT_rundir="`get_rundir`" + OCFT_pv=$OCFT_rundir/resource-agents/ocft-LVM-pv + OCFT_vg=ocft-vg + OCFT_lv=ocft-lv + OCFT_loop="`loopbackeddev make $OCFT_pv 16M`" + +SETUP-AGENT + pvcreate $OCFT_loop + vgcreate -s 4K $OCFT_vg $OCFT_loop + lvcreate -n $OCFT_lv -L 600K $OCFT_vg + +CLEANUP-AGENT + vgchange -an $OCFT_vg + lvremove -f /dev/$OCFT_vg/$OCFT_lv + vgremove -f $OCFT_vg + pvremove $OCFT_loop + loopbackeddev unmake $OCFT_pv + +CASE-BLOCK required_args + Env OCF_RESKEY_volgrpname=$OCFT_vg + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_volgrpname'" + Include prepare + Env OCF_RESKEY_volgrpname=/dev/no_such_device + AgentRun start OCF_ERR_GENERIC + +CASE "check base env: unset 'OCF_RESKEY_volgrpname'" + Include prepare + Unenv OCF_RESKEY_volgrpname + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + diff --git a/tools/ocft/MailTo b/tools/ocft/MailTo new file mode 100644 index 0000000..8754035 --- /dev/null +++ b/tools/ocft/MailTo @@ -0,0 +1,57 @@ +# MailTo + +CONFIG + Agent MailTo + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage mailx + HangTimeout 20 + +CASE-BLOCK required_args + Env OCF_RESKEY_email=root@localhost + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_email'" + Include prepare + Unenv OCF_RESKEY_email + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/Makefile.am b/tools/ocft/Makefile.am new file mode 100644 index 0000000..69c59ee --- /dev/null +++ b/tools/ocft/Makefile.am @@ -0,0 +1,63 @@ +# Author: John Shi +# jshi@suse.de +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = $(ocftcfgs_DATA) $(ocft_DATA) $(ocft_SCRIPTS) + +sbin_SCRIPTS = ocft + +ocftcfgsdir = $(datadir)/$(PACKAGE_NAME)/ocft/configs +ocftcfgs_DATA = apache \ + IPaddr2 \ + IPaddr2v4 \ + IPaddr2v6 \ + IPv6addr \ + Filesystem \ + LVM \ + Raid1 \ + IPsrcaddr \ + MailTo \ + jboss \ + mysql \ + mysql-proxy \ + pgsql \ + db2 \ + oracle \ + drbd.linbit \ + exportfs \ + exportfs-multidir \ + nfsserver \ + portblock \ + iscsi \ + named \ + postfix \ + sg_persist \ + tomcat \ + Xinetd \ + Xen \ + VirtualDomain \ + SendArp + +ocftdir = $(datadir)/$(PACKAGE_NAME)/ocft +ocft_DATA = README \ + README.zh_CN \ + caselib \ + helpers.sh \ + runocft.prereq +ocft_SCRIPTS = runocft + diff --git a/tools/ocft/README.in b/tools/ocft/README.in new file mode 100644 index 0000000..1c4ae12 --- /dev/null +++ b/tools/ocft/README.in @@ -0,0 +1,147 @@ +INTRODUCTION & DESIGN +~~~~~~~~~~~~~~~~~~~~~ + + - Ocft is a testing tool for resource agents. Instead of the policy of HA, + it mainly concerns whether resource agents run correct locally. It can + design types of complicated environments to test the reliability of + resource agents. Precisely, it is to display whether resource agents can + return to correct or expected value. The advantage of the tool provides + us with competence to design conditions which can be recorded or reproduced. + Hence it is useful to debuggers. + +* Components + ** Test case generator (@sbindir@/ocft) + - Turning configuration files of test case to executable scripts. + + ** Configuration file (@datadir@/@PACKAGE_NAME@/ocft/configs/) + - Every configuration file directs only one resource agent and share the same + name with resource agent but contains more test cases. + + ** The testing script (/var/lib/@PACKAGE_NAME@/ocft/cases/) + - After the generator reads configuration files and generates many testing + scripts and the script is underway, the test begins. + +* How to customize the environment of testing + - Ocft designs the running conditions through two ways, one is changing the + environment variables of resource agents (it is the interface left by OCF itself), + the other is modifying the OS environment of resource agents, such as altering + the permission of some key file or IP address of the machine. + +* How to test + - Firstly, you need to sketch the all complex and uncommon environments against + a certain resource agent and keep in mind what consequences may be caused by + these uncommon environments. + Secondly, write the designed conditions and foreknown consequences into + configuration files, and then run the generator to translate the test case to + executable scripts. + Finally, you need running these scripts to observe the output and learn + the running status of each test case, which will compares the predicated result + with the actual one. If they differ, you will be able to find the bugs of the + resource agent. + - All of the output with test will be recorded into the log files, you can find them + in /var/lib/@PACKAGE_NAME@/ocft/cases/logs. + + +HOW TO WRITE CONFIGURATION FILE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - There are only 6 top level options that are all spelled by capital letters and "-". + Every top level option contains sub-options that they are initials. + +* 'CONFIG' (top level option) + - Grammar: CONFIG + - The design in this option is global and influences every test case. + + ** 'Agent' (sub-option) + - Grammar: Agent AGENT_NAME + - The agent name you want to test. + + ** 'AgentRoot' (sub-option) + - Grammar: AgentRoot /usr/lib/ocf/resource.d/xxx + - A few agents will go to "linbit" or "pacemaker" directory, if you define this option, + ocft will use it to replace the default directory "heartbeat". + + ** 'InstallPackage' (sub-option) + - Grammar: InstallPackage package [package2 [...]] + - It will test whether the system have installed the service of the resource agent. + If not, it will download from Internet and have it installed automatically. + + ** 'HangTimeout' (sub-option) + - Grammar: HangTimeout secs + - If you alter some key options, some resource agents will get puzzled and stop, + which will influence the running of the following test case. Hence timeout setting is + needed, if the resource agent stops timeout, the scripts will kill this resource agent. + +* 'VARIABLE' (top level option) + - Grammar: + VARIABLE + VAR1=value1 + VAR2=value2 + ... + - Define the global variable here, the variables can be visited everywhere, they can be referenced + using $VAR_NAME. Note, the variables in VARIABLE are different from 'Env VAR1=value1', 'Env' can + affect the activity of agent, but the variables in VARIABLE just be shared with top level option. + +* 'SETUP-AGENT' (top level option) + - Grammar: + SETUP-AGENT + bash scripts... + ... + - Some of Agents may need to be initialized before testing, you can do it here with bash script. + +* 'CLEANUP-AGENT' (top level option) + - Grammar: + CLEANUP-AGENT + bash scripts... + ... + - If SETUP-AGENT set, usually you might be use this option do some cleaning work after test. + +* 'CASE' & 'CASE-BLOCK' (top level option) + - Grammar: CASE "description" & CASE-BLOCK macro_name + - Usually, the conditions you designed are more than one and a few 'CASE "..."' will + appear in configuration file. It is worth noting that the following sub-options + have 2 spellings: + One is general, where shell affects the local environment; the other is special, + where each options added "@ipaddr". It can remotely execute shell codes. In other words, + it is to execute the shell codes from a remote host, which is meaningful when a resource + agent needs 2 hosts. This remote shell is not a remote execution only through "ssh", but + running a remote shell in the background while the test case is running. The remote shell + runs in the background till the end and saves the results during the process. That is to + say, you can alternatively carry out local and remote shell code segments. + The "CASE-BLOCK" option is a macro definer, the statements in "CASE-BLOCK" will be inserted + into "CASE" if you "Include" the "macro_name". + + ** 'Env' (sub-option) + - Grammar: Env VARIABLE=value + - It is to set up an environment variable of the resource agent. They usually appear to + be OCF_RESKEY_xxx. One point is to be noted is there is no blank by both sides of "=". + + ** 'Unenv' (sub-option) + - Grammar: Unenv VARIABLE [VARIABLE2 [...]] + - Remove the environment variable. + + ** 'Include' (sub-option) + - Grammar: Include macro_name + - It will be replaced by statements in 'macro_name', of course, you should define the + content of 'macro_name' with 'CASE-BLOCK' first. + + ** 'Bash' (sub-option) + - Grammar: Bash bash_codes + - This option is to set up the environment of OS, where you can insert BASH code to + customize the system randomly. Note, do not cause unrecoverable consequences to the + system. + + ** 'BashAtExit' (sub-option) + - Grammar: BashAtExit bash_codes + - This option is to recover the OS environment in order to run another test case + correctly. Of cause you can use 'Bash' option to recover it. However, if mistakes occur + in the process, the script will quit directly instead of running your recovery codes. + If it happens, you ought to use BashAtExit which can restore the system environment + before you quit. + + ** 'AgentRun' (sub-option) + - Grammar: AgentRun cmd [ret_value] + - This option is to run resource agent. "cmd" is the parameter of the resource agent, + such as "start, status, stop ...". The second parameter is optional. It will compare the + actual returned value with the expected value when the script has run recourse agent. + If differs, bugs will be found. diff --git a/tools/ocft/README.zh_CN.in b/tools/ocft/README.zh_CN.in new file mode 100644 index 0000000..5138cf1 --- /dev/null +++ b/tools/ocft/README.zh_CN.in @@ -0,0 +1,124 @@ +1 介绍和设计 +ocft是一个测试resource agents的工具。它并不关注HA的策略,而是关注resource agents +是否正常运行在本机。他能对resource agents设计各种复杂环境,来考验resource agnets +是否能正常应对,也就是看resouce agents是否能返回正确的或者说我们所期望的值。 +这个工具给我们带来的好处就是我们可以集中的批量的来设计环境,而且这种环境是可被我们 +记录、重现的,对Debug人员来说是很有用的。 + +1.1 组成 +1.1.1 解释器 (@sbindir@/ocft) +将test case配置文件转换成可执行的测试脚本。 + +1.1.2 配置文件 (@datadir@/@PACKAGE_NAME@/ocft/configs/) +每一个configuration file只针对一个resouce agent,配置文件名与resouce agent名相同, +但是它可以容纳很多test case。 + +1.1.3 测试脚本 (/var/lib/@PACKAGE_NAME@/ocft/cases/) +由generator读取configuration file生成测试脚本,直接运行此脚本就可以开始测试了。 + +1.2 如何定制环境 +ocft 通过两种手段来设计resouce agents的运行环境,一是更改resouce agents的环境变量, +当然这是ocf本身就留给我们的接口。二是更改resource agents所处的系统环境, +比如更改某个关键文件的权限,更改本机ip地址等等... + +1.3 如何进行测试 +首先你需要针对某个resource agent在脑中勾画各种复杂且异常的环境,并且你能清楚预知 +这些异常环境会给resource agent带来什么结果,然后将这些设计好的环境和你预知的结果 +都写入配置文件,然后运行generator,将你刚写的test case转换成可执行的脚本。最后运行 +这些脚本,观察它们的输出,你可以清楚看到每个test case运行状况,他会比较你的预知结 +果和resource agent的实际结果,如果不一样,说明你找到resource agent的bug了。 +所有的测试输出都会被记录到日志文件中,你可以在 /var/lib/@PACKAGE_NAME@/ocft/cases/logs +中找到他们. + +2 配置 +只有6个top level option,它们是由大写字母和'-'构成的,每个top level option都有若干sub-option, +它们是首字母大写。 + +2.1 'CONFIG' 选项 +语法:CONFIG +此option中的设计是全局的,对每个test case都有所影响。 + +2.1.1 'Agent' 选项 +语法:Agent AGENT_NAME +你要测试的Agent的名字。 + +2.1.2 'AgentRoot' 选项 +语法:AgentRoot /usr/lib/ocf/resource.d/xxx +一些agent将会被移到 "pacemaker" 或 "linbit" 目录,如果你定义了这个选项,ocft将会用它来 +替代默认的目录"heartbeat"。 + +2.1.3 'InstallPackage' 选项 +语法:InstallPackage package [package2 [...]] +他会检测系统是否安装了此resource agent的service,如果没有安装,会自动从网络进行安装。 +package_name是某个resouce agent在操作系统中必须安装的包。 + +2.1.4 'HangTimeout' 选项 +语法:HangTimeout secs +如果你更改了一些很关键的东西,有些resouce agent会不知所措,停在那里不动,那么就会影响 +到后面test case的运行,所以你需要设定超时,如果一旦resouce agent停在那超时了,脚本会 +杀死这个resouce agent。 + +2.2 'VARIABLE' 选项 +语法: +VARIABLE + VAR1=value1 + VAR2=value2 + ... +在此定义全局变量,这些变量可以用于配置文件中的任何地方,引用时在变量名前加上$。请注意,这些 +变量不同于Env定义的变量,Env是定义环境变量,可以改变agent的行为,而这些变量只是用于配置文件中 +共享。 + +2.3 'SETUP-AGENT' 选项 +语法: +SETUP-AGENT + bash scripts... + ... +一些Agent在测试前可能需要初始化,你可以用bash脚本在这初始化。 + +2.4 'CLEANUP-AGENT' 选项 +语法: +CLEANUP-AGENT + bash scripts... + ... +如果之前定义了SETUP-AGENT, 你可能还需要此选项在测试完后来作一些清除。 + +2.5 'CASE' & 'CASE-BLOCK' 选项 +语法:CASE "description" & CASE-BLOCK macro_name +通常你设计的环境不止一个,那么配置文件中应该会出现许多 'CASE "..."',值得注意的是,以下子 +选项每个都有两种写法,一是普通写法,产生的shell代码对本地产生作用,二是特殊写法,就是在每 +个选项后加上 "@ipaddr",他可以远程执行shell代码,也就是控制远程机器执行你给的shell代码, +这对需要两台机器以上的resouce agent很有用的,这个远程shell并非简单的用ssh去远程执行,而是在 +test case生存期内,后台运行一个远程shell,他始终在后台运行并且保存你的中间结果,也就是说你 +可以交替执行本地shell代码段和远程shell代码段。 +'CASE-BLOCK'选项是一个宏定义器,它定义的内容将会被插入到"CASE"内容中。 + +2.5.1 'Env' 选项 +语法:Env VARIABLE=value +这是设置resouce agent的环境变量,他们通常是OCF_RESKEY_xxx,注意=号两边不要有空格。 + +2.5.2 'Unenv' 选项 +语法:Unenv VARIABLE +此选项用于删除环境变量。 + +2.5.3 'Include' 选项 +语法:Include macro_name +此选项将会被宏"macro_name"的内容所替代,当然,你得预先用"CASE-BLOCK"来定义宏"macro_name" +的内容。 + +2.5.4 'Bash' 选项 +语法:Bash bash_codes +此选项是用来设置os的环境,你可以嵌入bash代码来对系统作任意设置,不过要注意不要对系统造成 +不可恢复的后果就行了。 + +2.5.5 'BashAtExit' 选项 +语法:BashAtExit bash_codes +此选项是用来恢复os的环境,以便另一个test case能正常运行,当然你也可以直接用'Bash'选项来 +恢复,但是如果脚本在执行过程中产生某些错误,那么脚本会直接退出,而不会去执行你的恢复代码, +那么你就要用到BashAtExit,他能在你退出前去恢复系统环境。 + +2.5.6 'AgentRun' 选项 +语法:AgentRun cmd [ret_value] +此option会去运行resouce agent,'cmd' 就是resouce agent的参数,如 start,status,stop ... +第二个参数是可选的,它是你对系统环境作出特殊设定以后,你预计resouce agent会返回的值, +如果给出此参数,那么脚本会在运行resouce agent后,会比较此时的返回值和你预期的返回值, +如果不一致,那么说明找到bug了。 diff --git a/tools/ocft/Raid1 b/tools/ocft/Raid1 new file mode 100644 index 0000000..462b9b9 --- /dev/null +++ b/tools/ocft/Raid1 @@ -0,0 +1,134 @@ +# Raid1 +# by dejan@suse.de on +# Fri Aug 24 17:01:40 CEST 2012 + +CONFIG + Agent Raid1 + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage mdadm + HangTimeout 20 + +VARIABLE + OCFT_rundir="`get_rundir`" + OCFT_disk0=$OCFT_rundir/resource-agents/ocft-Raid1-disk0 + OCFT_disk1=$OCFT_rundir/resource-agents/ocft-Raid1-disk1 + OCFT_disk2=$OCFT_rundir/resource-agents/ocft-Raid1-disk2 + OCFT_disk3=$OCFT_rundir/resource-agents/ocft-Raid1-disk3 + OCFT_raidconf=$OCFT_rundir/resource-agents/ocft-mdadm.conf + OCFT_raiddev=/dev/md8 + OCFT_raiddev2=/dev/md9 + OCFT_loop0="`loopbackeddev make $OCFT_disk0 16M`" + OCFT_loop1="`loopbackeddev make $OCFT_disk1 16M`" + OCFT_loop2="`loopbackeddev make $OCFT_disk2 16M`" + OCFT_loop3="`loopbackeddev make $OCFT_disk3 16M`" + +SETUP-AGENT + mdadm --create $OCFT_raiddev -l 0 --raid-devices=2 $OCFT_loop0 $OCFT_loop1 + mdadm --create $OCFT_raiddev2 -l 0 --raid-devices=2 $OCFT_loop2 $OCFT_loop3 + echo DEVICE $OCFT_loop0 $OCFT_loop1 > $OCFT_raidconf + echo DEVICE $OCFT_loop2 $OCFT_loop3 >> $OCFT_raidconf + echo ARRAY $OCFT_raiddev devices=$OCFT_loop0,$OCFT_loop1 >> $OCFT_raidconf + echo ARRAY $OCFT_raiddev2 devices=$OCFT_loop2,$OCFT_loop3 >> $OCFT_raidconf + +CLEANUP-AGENT + mdadm --zero-superblock $OCFT_loop0 + mdadm --zero-superblock $OCFT_loop1 + mdadm --zero-superblock $OCFT_loop2 + mdadm --zero-superblock $OCFT_loop3 + mdadm --remove $OCFT_raiddev 2>/dev/null + mdadm --remove $OCFT_raiddev2 2>/dev/null + loopbackeddev unmake $OCFT_disk0 + loopbackeddev unmake $OCFT_disk1 + loopbackeddev unmake $OCFT_disk2 + loopbackeddev unmake $OCFT_disk3 + rm -f $OCFT_raidconf + +CASE-BLOCK required_args + Env OCF_RESKEY_raidconf=$OCFT_raidconf + Env OCF_RESKEY_raiddev=$OCFT_raiddev + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE-BLOCK prepare_auto + Include required_args + Env OCF_RESKEY_raiddev="auto" + Include default_status + +CASE-BLOCK prepare_multiple + Include required_args + Env OCF_RESKEY_raiddev="$OCFT_raiddev $OCFT_raiddev2" + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_raiddev'" + Include prepare + Env OCF_RESKEY_raiddev=/dev/no_such_device + AgentRun start OCF_ERR_GENERIC + +CASE "check base env: unset 'OCF_RESKEY_raiddev'" + Include prepare + Unenv OCF_RESKEY_raiddev + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "normal start (auto)" + Include prepare_auto + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + +CASE "normal stop (auto)" + Include prepare_auto + AgentRun start + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + +CASE "normal start (multiple)" + Include prepare + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + +CASE "normal stop (multiple)" + Include prepare + Env OCF_RESKEY_raiddev="$OCFT_raiddev $OCFT_raiddev2" + AgentRun start + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + diff --git a/tools/ocft/SendArp b/tools/ocft/SendArp new file mode 100644 index 0000000..7880388 --- /dev/null +++ b/tools/ocft/SendArp @@ -0,0 +1,74 @@ +# SendArp + +CONFIG + Agent SendArp + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage resource-agents + HangTimeout 15 + +CASE-BLOCK required_args + Env OCF_RESKEY_ip=127.0.0.1 + Env OCF_RESKEY_nic=lo + Env OCF_RESKEY_background=false + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_ip'" + Include prepare + Unenv OCF_RESKEY_ip + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set invalid 'OCF_RESKEY_ip'" + Include prepare + Env OCF_RESKEY_ip=not_ip_address + AgentRun start OCF_ERR_GENERIC + +CASE "check base env: unset 'OCF_RESKEY_nic'" + Include prepare + Unenv OCF_RESKEY_nic + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: set invalid 'OCF_RESKEY_nic'" + Include prepare + Env OCF_RESKEY_nic=not_nic + AgentRun start OCF_ERR_GENERIC + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/VirtualDomain b/tools/ocft/VirtualDomain new file mode 100644 index 0000000..3a44f20 --- /dev/null +++ b/tools/ocft/VirtualDomain @@ -0,0 +1,71 @@ +# VirtualDomain +# by dejan@suse.de on +# Tue Jul 8 12:48:03 CEST 2014 + +CONFIG + Agent VirtualDomain + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +# set OCFT_config to the libvirt configuration file +# the guest is going to be stopped and started +VARIABLE + OCFT_config=/etc/libvirt/qemu/sle11-sp3.xml + +CASE-BLOCK required_args + Env OCF_RESKEY_config=$OCFT_config + +CASE-BLOCK unset_utilization + Env OCF_RESKEY_autoset_utilization_host_memory=false + Env OCF_RESKEY_autoset_utilization_hv_memory=false + Env OCF_RESKEY_autoset_utilization_cpu=false + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include unset_utilization + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_config'" + Include prepare + Env OCF_RESKEY_config=/no_such_file + AgentRun start OCF_ERR_INSTALLED + +CASE "check base env: unset 'OCF_RESKEY_config'" + Include prepare + Unenv OCF_RESKEY_config + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + diff --git a/tools/ocft/Xen b/tools/ocft/Xen new file mode 100644 index 0000000..731564b --- /dev/null +++ b/tools/ocft/Xen @@ -0,0 +1,65 @@ +# Xen +# by dejan@suse.de on +# Tue Jul 8 12:20:23 CEST 2014 + +CONFIG + Agent Xen + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 20 + +# set OCFT_xmfile to the xen-xm format file +# the guest is going to be stopped and started +VARIABLE + OCFT_xmfile=/etc/xen/vm/xen-f + +CASE-BLOCK required_args + Env OCF_RESKEY_xmfile=$OCFT_xmfile + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_xmfile'" + Include prepare + Env OCF_RESKEY_xmfile=/no_such_file + AgentRun start OCF_ERR_INSTALLED + +CASE "check base env: unset 'OCF_RESKEY_xmfile'" + Include prepare + Unenv OCF_RESKEY_xmfile + AgentRun start OCF_ERR_INSTALLED + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + diff --git a/tools/ocft/Xinetd b/tools/ocft/Xinetd new file mode 100644 index 0000000..53f4f65 --- /dev/null +++ b/tools/ocft/Xinetd @@ -0,0 +1,64 @@ +# Xinetd + +CONFIG + Agent Xinetd + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage xinetd + +SETUP-AGENT + sed -i /disable/s/yes/no/ /etc/xinetd.d/echo + if which /etc/init.d/xinetd >/dev/null 2>&1; then + /etc/init.d/xinetd start + elif systemctl list-unit-files | grep -qs xinetd; then + systemctl start xinetd + fi + +CASE-BLOCK required_args + Env OCF_RESKEY_service=discard + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_protocol'" + Include prepare + Unenv OCF_RESKEY_service + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/apache b/tools/ocft/apache new file mode 100644 index 0000000..fe4f193 --- /dev/null +++ b/tools/ocft/apache @@ -0,0 +1,68 @@ +# apache +# make sure that your apache configuration loads mod_status + +CONFIG + Agent apache + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage apache2 + HangTimeout 20 + +SETUP-AGENT + if systemctl list-unit-files 2>/dev/null | fgrep -q apache2.service; then + systemctl start apache2.service + systemctl stop apache2.service + else + /etc/init.d/apache2 start + /etc/init.d/apache2 stop + fi + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: set non-existing OCF_RESKEY_statusurl" + Include prepare + Env OCF_RESKEY_statusurl="yoyoyoyo" + AgentRun start OCF_ERR_GENERIC + +CASE "check base env: set non-existing OCF_RESKEY_configfile" + Include prepare + Env OCF_RESKEY_configfile="/yoyoyoyo/nosuchfile" + AgentRun start OCF_ERR_INSTALLED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "running monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not running monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/caselib.in b/tools/ocft/caselib.in new file mode 100644 index 0000000..44165d6 --- /dev/null +++ b/tools/ocft/caselib.in @@ -0,0 +1,299 @@ +# +# Copyright (c) 2010-2011 Novell Inc, John Shi +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + + +quit() +{ + local ret + ret="$1" + + while [ $__OCFT__atexit_num -gt 0 ]; do + atexit$__OCFT__atexit_num + let __OCFT__atexit_num-- + done + + rm -rf $__OCFT__fakebin + + exit $ret +} + +agent_install() +{ + local pkg + + if [ $# -eq 0 ]; then + return 0 + fi + + for pkg in "$@"; do + if [ -e /etc/SuSE-release ]; then + if ! zypper -q install -y "$pkg" >/dev/null 2>&1; then + echo + echo "${__OCFT__showhost}ERROR: Install '$pkg' failed." + quit 2 + fi + elif [ -e /etc/debian_version ]; then + if ! apt-get -y install "$pkg" >/dev/null 2>&1; then + echo + echo "${__OCFT__showhost}ERROR: Install '$pkg' failed." + quit 2 + fi + elif [ -e /etc/redhat-release ]; then + if ! yum -y install "$pkg" >/dev/null 2>&1; then + echo + echo "${__OCFT__showhost}ERROR: Install '$pkg' failed." + quit 2 + fi + else + echo "${__OCFT__showhost}ERROR: Cannot detect your OS type." + quit 2 + fi + done +} + +set_ocf_env() +{ + export OCF_RA_VERSION_MAJOR=1 + export OCF_RA_VERSION_MINOR=0 + export OCF_RESOURCE_TYPE=$1 + export OCF_RESOURCE_INSTANCE=${OCF_RESOURCE_INSTANCE:-"ocft"} +} + +agent_run() +{ + local agent cmd timeout pid i ret aroot + agent="$1" + cmd="$2" + timeout="$3" + + set_ocf_env $agent + + export OCF_RESKEY_CRM_meta_timeout + : ${OCF_RESKEY_CRM_meta_timeout:=$((timeout*1000))} + + aroot=${__OCFT__MYROOT:-$__OCFT__AGENT_ROOT} + + setsid $aroot/$agent $cmd >${HA_RSCTMP}/.ocft_runlog 2>&1 & + pid=$! + + i=0 + while [ $i -lt $timeout ]; do + if [ ! -e /proc/$pid ]; then + break + fi + sleep 1 + let i++ + done + + if [ $i -ge $timeout ]; then + kill -SIGTERM -$pid >/dev/null 2>&1 + sleep 3 + kill -SIGKILL -$pid >/dev/null 2>&1 + echo -n "${__OCFT__showhost}ERROR: The agent was hanging, killed it, " + echo "maybe you damaged the agent or system's environment, see details below:" + cat ${HA_RSCTMP}/.ocft_runlog + echo + quit 1 + fi + + wait $pid +} + +check_success() +{ + local ret msg + ret="$1" + msg="$2" + + if [ $ret -ne 0 ]; then + echo "${__OCFT__showhost}ERROR: '${msg}' failed, the return code is ${ret}." + quit 1 + fi +} + +__maxfd() +{ + (echo 0; ls -1 /proc/$$/fd) | sort -rn | head -1 +} + +__getfd() +{ + local host rw fd file + host="$1" + rw="$2" + + for fd in /proc/$$/fd/*; do + file=$(basename "$(readlink $fd)") + if [ "$file" = "${host}_$rw" ]; then + basename $fd + break + fi + done +} + +backbash_start() +{ + local host fd rfd wfd + host="$1" + + if [ ! -d "$__OCFT__CASES_DIR" ]; then + echo "${__OCFT__showhost}ERROR: Could not found Directory: ${__OCFT__CASES_DIR}." + quit 1 + fi + + if lsof $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w >/dev/null 2>&1; then + echo "${__OCFT__showhost}ERROR: Connection exist with $host." + quit 1 + fi + if [ ! -p "$__OCFT__CASES_DIR/${host}_r" ] || [ ! -p "$__OCFT__CASES_DIR/${host}_w" ]; then + rm -f $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w + if ! mkfifo $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w >/dev/null 2>&1; then + echo "${__OCFT__showhost}ERROR: Could not create pipe file: $__OCFT__CASES_DIR/${host}_*." + quit 1 + fi + fi + + ssh root@$host '@BASH_SHELL@ 2>&1 + sed "s/00/001/g" ${HA_RSCTMP}/.backbash-log + echo 000 + echo 1' >$__OCFT__CASES_DIR/${host}_r <$__OCFT__CASES_DIR/${host}_w & + + fd=$(__maxfd) + rfd=$(expr $fd + 1) + wfd=$(expr $fd + 2) + eval "exec ${rfd}<$__OCFT__CASES_DIR/${host}_r ${wfd}>$__OCFT__CASES_DIR/${host}_w" +} + +backbash() +{ + local host rfd wfd ret + host="$1" + + rfd=$(__getfd $host r) + wfd=$(__getfd $host w) + + if [ -z "$rfd" -o -z "$wfd" ]; then + echo "${__OCFT__showhost}ERROR: Could not found connection with $host." + fi + + cat >&$wfd <<EOF +{ +true +EOF + cat >&$wfd + cat >&$wfd <<EOF + +} >&${HA_RSCTMP}/.backbash-log +sed 's/00/001/g' ${HA_RSCTMP}/.backbash-log +echo 000 +echo 0 +EOF + if [ $? -ne 0 ]; then + echo "${__OCFT__showhost}ERROR: Broken connection with $host." + quit 1 + fi + + awk -vlive=2 '{ + if (sub(/000$/, "")) { + if ($0 != "") { + gsub("001", "00"); + printf("%s", $0); + } + getline live; + exit; + } + gsub("001", "00"); + print; + } END { + exit(live); + }' <&$rfd + case $? in + 1) + quit 1 + ;; + 2) + echo "${__OCFT__showhost}ERROR: Broken connection with $host." + quit 1 + ;; + esac +} + +backbash_stop() +{ + local host rfd wfd + host="$1" + + wfd=$(__getfd $host w) + if [ -n "$wfd" ]; then + cat >&$wfd <<<'quit 0' + fi + rm -f $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w +} + + +export OCF_ROOT=@OCF_ROOT_DIR@ +export OCF_LIB=@OCF_LIB_DIR@/heartbeat +__OCFT__AGENT_ROOT=@OCF_RA_DIR@/heartbeat +__OCFT__CASES_DIR=/var/lib/@PACKAGE_NAME@/ocft/cases +OCFT_DIR=@datadir@/@PACKAGE_NAME@/ocft + +. $OCFT_DIR/helpers.sh + +__OCFT__atexit_num=0 + +if [ $EUID -ne 0 ]; then + echo "${__OCFT__showhost}ERROR: '$0' needs to be run by root." + quit 3 +fi + +__OCFT__fakebin=./fakebin + +mkdir -p $__OCFT__fakebin >/dev/null 2>&1 && +ln -sf /bin/true $__OCFT__fakebin/crm_master >/dev/null 2>&1 && +ln -sf /bin/true $__OCFT__fakebin/crm_mon >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "${__OCFT__showhost}ERROR: initialize 'fakebin' failed." + quit 3 +fi +export HA_SBIN_DIR=$__OCFT__fakebin + +. $OCF_LIB/ocf-returncodes || { + echo "${__OCFT__showhost}ERROR: $OCF_LIB/ocf-returncodes not found." + quit 3 +} + +. $OCF_LIB/ocf-directories || { + echo "${__OCFT__showhost}ERROR: $OCF_LIB/ocf-directories not found." + quit 3 +} + +while read __OCFT__line; do + if [ -n "$__OCFT__line" ]; then + __OCFT__retn=${__OCFT__line%%=*} + __OCFT__reti=$(eval echo \$$__OCFT__retn) + __OCFT__retval[__OCFT__reti]=$__OCFT__retn + fi +done <<<"$(sed 's/#.*//' $OCF_LIB/ocf-returncodes)" + + +# vim:ts=2:sw=2:et: diff --git a/tools/ocft/db2 b/tools/ocft/db2 new file mode 100644 index 0000000..5c4d6ea --- /dev/null +++ b/tools/ocft/db2 @@ -0,0 +1,164 @@ +# db2 +# +# This test assumes a db2 ESE instance with two partitions and a database. +# Default is instance=db2inst1, database=ocft +# adapt this in set_testenv below +# +# Simple steps to generate a test environment (if you don't have one): +# +# A virtual machine with 1200MB RAM is sufficient +# +# - download an eval version of DB2 server from IBM +# - create an user "db2inst1" in group "db2inst1" +# +# As root +# - install DB2 software in some location +# - create instance +# cd <this_location>/instance +# ./db2icrt -s ese -u db2inst1 db2inst1 +# - adapt profile of db2inst1 as instructed by db2icrt +# +# As db2inst1 +# # allow to run with small memory footprint +# db2set DB2_FCM_SETTINGS=FCM_MAXIMIZE_SET_SIZE:FALSE +# db2start +# db2start dbpartitionnum 1 add dbpartitionnum hostname $(uname -n) port 1 without tablespaces +# db2stop +# db2start +# db2 create database ocft +# Done +# In order to install a real cluster refer to http://www.linux-ha.org/wiki/db2_(resource_agent) + +CONFIG + Agent db2 + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 40 + +SETUP-AGENT + # nothing + +CASE-BLOCK set_testenv + Env OCFT_instance=db2inst1 + Env OCFT_db=ocft + +CASE-BLOCK crm_setting + Env OCF_RESKEY_instance=$OCFT_instance + Env OCF_RESKEY_CRM_meta_timeout=30000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include set_testenv + Include crm_setting + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_instance'" + Include prepare + Env OCF_RESKEY_instance=no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "invalid instance config" + Include prepare + Bash eval mv ~$OCFT_instance/sqllib ~$OCFT_instance/sqllib- + BashAtExit eval mv ~$OCFT_instance/sqllib- ~$OCFT_instance/sqllib + AgentRun start OCF_ERR_INSTALLED + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "started: monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not started: monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "killed instance: monitor" + Include prepare + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + BashAtExit rm /tmp/ocft-helper1 + Bash echo "su $OCFT_instance -c '. ~$OCFT_instance/sqllib/db2profile; db2nkill 0 >/dev/null 2>&1'" > /tmp/ocft-helper1 + Bash sh -x /tmp/ocft-helper1 + AgentRun monitor OCF_NOT_RUNNING + +CASE "overload param instance by admin" + Include prepare + Env OCF_RESKEY_instance=no_such + Env OCF_RESKEY_admin=$OCFT_instance + AgentRun start OCF_SUCCESS + +CASE "check start really activates db" + Include prepare + AgentRun start OCF_SUCCESS + + BashAtExit rm /tmp/ocft-helper2 + Bash echo "su $OCFT_instance -c '. ~$OCFT_instance/sqllib/db2profile; db2 get snapshot for database on $OCFT_db>/dev/null'" > /tmp/ocft-helper2 + Bash sh -x /tmp/ocft-helper2 + +CASE "multipartion test" + Include prepare + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + + # start does not start partion 1 + Env OCF_RESKEY_dbpartitionnum=1 + AgentRun monitor OCF_NOT_RUNNING + + # now start 1 + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + + # now stop 1 + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + + # does not affect 0 + Env OCF_RESKEY_dbpartitionnum=0 + AgentRun monitor OCF_SUCCESS + +# fault injection does not work on the 1.0.4 client due to a hardcoded path +CASE "simulate hanging db2stop (not meaningful for 1.0.4 agent)" + Include prepare + AgentRun start OCF_SUCCESS + Bash [ ! -f /usr/local/bin/db2stop ] + BashAtExit rm /usr/local/bin/db2stop + Bash echo -e "#!/bin/sh\necho fake db2stop\nsleep 10000" > /usr/local/bin/db2stop + Bash chmod +x /usr/local/bin/db2stop + AgentRun stop OCF_SUCCESS + +# fault injection does not work on the 1.0.4 client due to a hardcoded path +CASE "simulate not stopping db2stop (not meaningful for 1.0.4 agent)" + Include prepare + AgentRun start OCF_SUCCESS + Bash [ ! -f /usr/local/bin/db2stop ] + BashAtExit rm /usr/local/bin/db2stop + Bash echo -e "#!/bin/sh\necho fake db2stop\nexit 0" > /usr/local/bin/db2stop + Bash chmod +x /usr/local/bin/db2stop + AgentRun stop OCF_SUCCESS diff --git a/tools/ocft/drbd.linbit b/tools/ocft/drbd.linbit new file mode 100644 index 0000000..57fa088 --- /dev/null +++ b/tools/ocft/drbd.linbit @@ -0,0 +1,183 @@ +# linbit: drbd + +CONFIG + Agent drbd + AgentRoot /usr/lib/ocf/resource.d/linbit + InstallPackage drbd + HangTimeout 20 + +VARIABLE + DRBDCONF=${HA_RSCTMP}/ocft_drbd_tmp.conf + + # should be this machine's hostname/ip, please modify it by yourself. + NAME_1=HOSTNAME1 + IP_1=IP_ADDRESS1 + + # the block device just for test, please modify it by yourself. + DISK_1=/dev/DEVICE1 + + PORT_1=5735 + DEVICE_1=/dev/drbd0 + + #################################################################### + + # please modify it by yourself. + NAME_2=HOSTNAME2 + IP_2=IP_ADDRESS2 + + # the block device just for test, please modify it by yourself. + DISK_2=/dev/DEVICE2 + + PORT_2=5735 + DEVICE_2=/dev/drbd0 + + + +SETUP-AGENT + cat >$DRBDCONF <<EOF + global { + usage-count no; + } + + resource ocft0 { + protocol C; + disk { + on-io-error detach; + } + on $NAME_1 { + device $DEVICE_1; + address $IP_1:$PORT_1; + meta-disk internal; + disk $DISK_1; + } + on $NAME_2 { + device $DEVICE_2; + address $IP_2:$PORT_2; + meta-disk internal; + disk $DISK_2; + } + } +EOF + HOST=$(uname -n) + DRBDADM="drbdadm -c $DRBDCONF" + + # prepare + modprobe drbd $(drbdadm sh-mod-parms) + $DRBDADM down ocft0 + + # create meta data block if necessary + $DRBDADM dump-md ocft0 >/dev/null 2>&1 + if [ $? -eq 255 ]; then + $DRBDADM create-md ocft0 + fi + + # start drbd + $DRBDADM up ocft0 + + # UpToDate + if [ "$HOST" = "$NAME_1" ]; then + $DRBDADM wait-connect ocft0 + echo "drbd Syncing .." + $DRBDADM primary --force ocft0 + while true; do + CSTATE=$($DRBDADM cstate ocft0) + DSTATE=$($DRBDADM dstate ocft0) + if [ "$CSTATE" = "Connected" -a "$DSTATE" = "UpToDate/UpToDate" ]; then + break + else + sleep 3 + fi + done + echo "done" + fi + +CLEANUP-AGENT + drbdadm -c $DRBDCONF down ocft0 + rm -f $DRBDCONF + +CASE-BLOCK required_args + Env OCF_RESKEY_drbdconf=$DRBDCONF + Env OCF_RESKEY_drbd_resource=ocft0 + Env OCF_RESKEY_CRM_meta_notify=true + Env OCF_RESKEY_CRM_meta_clone_max=2 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include required_args + AgentRun validate-all OCF_SUCCESS + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "wrong path of config file" + Include prepare + Env OCF_RESKEY_drbdconf=no_such_file + AgentRun start OCF_ERR_INSTALLED + +CASE "wrong resource name" + Include prepare + Env OCF_RESKEY_drbd_resource=no_such_src + # OCF_RESKEY_drbd_resource is a required parameter in agent meta-data, + # if wrong, I think the agent should return OCF_ERR_CONFIGURED. + AgentRun start OCF_ERR_CONFIGURED + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "running monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not running monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "Primary/Secondary monitor" + Include prepare + AgentRun start + AgentRun promote + AgentRun monitor OCF_RUNNING_MASTER + AgentRun demote + AgentRun monitor OCF_SUCCESS + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "try to 'promote' in single-primary mode" + Include prepare + Include@$IP_2 prepare + + # start drbd + AgentRun start + AgentRun@$IP_2 start + + # promote local drbd first + AgentRun promote OCF_SUCCESS + + # demote local drbd prepare for remote drbd promote + AgentRun demote + # remote drbd promote + AgentRun@$IP_2 promote OCF_SUCCESS + + # promote fails, because remote drbd promote first. + AgentRun promote OCF_ERR_GENERIC diff --git a/tools/ocft/exportfs b/tools/ocft/exportfs new file mode 100644 index 0000000..285a4b8 --- /dev/null +++ b/tools/ocft/exportfs @@ -0,0 +1,80 @@ +# exportfs +# +# + +CONFIG + Agent exportfs + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage nfs-kernel-server + HangTimeout 40 + +SETUP-AGENT + # nothing + +CASE-BLOCK set_testenv + Env OCF_RESKEY_directory=/usr + Env OCF_RESKEY_fsid=105 + Env OCF_RESKEY_clientspec="*" + Env OCF_RESKEY_CRM_meta_timeout=30000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include set_testenv + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: no 'OCF_RESKEY_fsid'" + Include prepare + Env OCF_RESKEY_fsid= + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: invalid 'OCF_RESKEY_directory'" + Include prepare + Env OCF_RESKEY_directory=/no_such + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: non-decimal 'OCF_RESKEY_fsid'" + Include prepare + Env OCF_RESKEY_fsid="4f838db14f838db14f838db14f838db1" + AgentRun start OCF_SUCCESS + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "stop with no env" + Include prepare + Env OCF_RESKEY_directory=/no_such + AgentRun stop OCF_SUCCESS + +CASE "started: monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not started: monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING diff --git a/tools/ocft/exportfs-multidir b/tools/ocft/exportfs-multidir new file mode 100644 index 0000000..00e41f0 --- /dev/null +++ b/tools/ocft/exportfs-multidir @@ -0,0 +1,80 @@ +# exportfs +# +# + +CONFIG + Agent exportfs + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage nfs-kernel-server + HangTimeout 40 + +SETUP-AGENT + # nothing + +CASE-BLOCK set_testenv + Env OCF_RESKEY_directory="/usr /var" + Env OCF_RESKEY_fsid=105 + Env OCF_RESKEY_clientspec="*" + Env OCF_RESKEY_CRM_meta_timeout=30000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include set_testenv + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: no 'OCF_RESKEY_fsid'" + Include prepare + Env OCF_RESKEY_fsid= + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: invalid 'OCF_RESKEY_directory'" + Include prepare + Env OCF_RESKEY_directory=/no_such + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: invalid 'OCF_RESKEY_fsid'" + Include prepare + Env OCF_RESKEY_fsid=root + AgentRun start OCF_ERR_CONFIGURED + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "stop with no env" + Include prepare + Env OCF_RESKEY_directory="/usr /no_such" + AgentRun stop OCF_SUCCESS + +CASE "started: monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not started: monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING diff --git a/tools/ocft/helpers.sh b/tools/ocft/helpers.sh new file mode 100644 index 0000000..9848934 --- /dev/null +++ b/tools/ocft/helpers.sh @@ -0,0 +1,43 @@ +get_rundir() { + local rundir + rundir="`mount | grep '/run ' | awk '{print $3}'`" + echo ${rundir:-"/var/run"} +} + +loopbackeddev() { + local action file size ctlfile + + action="$1" + file="$2" + size="$3" + ctlfile=$HA_RSCTMP/`echo $file | tr / _` + + case "$action" in + start|setup|make) + if [ ! -f "$ctlfile" ]; then + if [ -z "$size" ]; then + echo "usage: $0 action file size" >&2 + exit 1 + fi + loopdev=`losetup -f` + if ! dd if=/dev/zero of=$file bs=1 count=0 seek=$size 2>/dev/null; then + echo "$0: dd failed" >&2 + exit 1 + fi + if ! losetup $loopdev $file; then + echo "$0: losetup failed" >&2 + exit 1 + fi + echo $loopdev | tee $ctlfile + else + cat $ctlfile + fi + ;; + stop|undo|unmake) + if [ -f "$ctlfile" ]; then + losetup -d `cat $ctlfile` + rm -f $file $ctlfile + fi + ;; + esac +} diff --git a/tools/ocft/iscsi b/tools/ocft/iscsi new file mode 100644 index 0000000..3c5d524 --- /dev/null +++ b/tools/ocft/iscsi @@ -0,0 +1,108 @@ +# iscsi + +CONFIG + Agent iscsi + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage open-iscsi targetcli + HangTimeout 20 + +VARIABLE + OCFT_rundir="`get_rundir`" + OCFT_disk="$OCFT_rundir/resource-agents/ocft-iscsi" + OCFT_target="iqn.2003-01.org.linux-iscsi:ocft" + OCFT_portal="127.0.0.1:3260" + OCFT_loop="`loopbackeddev make $OCFT_disk 16M`" + OCF_RESKEY_iqn=$OCFT_target + OCF_RESKEY_portals=$OCFT_portal + OCF_RESKEY_target_iqn=$OCFT_target + OCF_RESKEY_path=$OCFT_loop + OCF_RESKEY_lun=1 + +SETUP-AGENT + if systemctl list-unit-files 2>/dev/null | fgrep -q iscsid.service; then + systemctl start iscsid.service + else + /etc/init.d/open-iscsi start + fi + if systemctl list-unit-files 2>/dev/null | fgrep -q target.service; then + systemctl start target.service + else + /etc/init.d/target start + fi + export OCF_RESKEY_iqn=$OCFT_target + export OCF_RESKEY_portals=$OCFT_portal + export OCF_RESKEY_target_iqn=$OCFT_target + export OCF_RESKEY_path=$OCFT_loop + export OCF_RESKEY_lun=1 + /usr/lib/ocf/resource.d/heartbeat/iSCSITarget start + /usr/lib/ocf/resource.d/heartbeat/iSCSILogicalUnit start + +CLEANUP-AGENT + export OCF_RESKEY_iqn=$OCFT_target + export OCF_RESKEY_portals=$OCFT_portal + export OCF_RESKEY_target_iqn=$OCFT_target + export OCF_RESKEY_path=$OCFT_loop + export OCF_RESKEY_lun=1 + /usr/lib/ocf/resource.d/heartbeat/iSCSILogicalUnit stop + /usr/lib/ocf/resource.d/heartbeat/iSCSITarget stop + if systemctl list-unit-files 2>/dev/null | fgrep -q target.service; then + systemctl stop target.service + fi + loopbackeddev unmake $OCFT_disk + +CASE-BLOCK required_args + Env OCF_RESKEY_portal=$OCFT_portal + Env OCF_RESKEY_target=$OCFT_target + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_portal'" + Include prepare + Unenv OCF_RESKEY_portal + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: unset 'OCF_RESKEY_target'" + Include prepare + Unenv OCF_RESKEY_target + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + diff --git a/tools/ocft/jboss b/tools/ocft/jboss new file mode 100644 index 0000000..bc99d8f --- /dev/null +++ b/tools/ocft/jboss @@ -0,0 +1,83 @@ +# jboss +# +# NOTE: Clean up $jboss_home/standalone/log before running this test +# otherwise creating the pid/log files may fail +# in the test case with a different user. + +CONFIG + Agent jboss + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 120 + +# Note : Change setting by a version of JBoss. +# +VARIABLE + # JBoss5 Environment require +# jboss_version=5 +# jboss_home=/opt/jboss5/current +# java_home=/usr/lib/jvm/java-1.6.0-openjdk.x86_64 +# user=jboss5 + # JBoss6 Environment require + jboss_version=6 + jboss_home=/opt/jboss6/current + java_home=/usr/lib/jvm/java-1.7.0-openjdk.x86_64 + user=jboss6 + +CASE-BLOCK required_args_jboss + Env OCF_RESKEY_jboss_home=${jboss_home} + Env OCF_RESKEY_java_home=${java_home} + Env OCF_RESKEY_jboss_version=${jboss_version} + Env OCF_RESKEY_user=${user} + +CASE-BLOCK args_clear + Unenv OCF_RESKEY_jboss_home + Unenv OCF_RESKEY_java_home + Unenv OCF_RESKEY_jboss_version + Unenv OCF_RESKEY_user + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare_jboss + Include required_args_jboss + Include default_status + +# Test CASE +# +CASE "normal start jboss require_args (user:user)" + Include prepare_jboss + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + Include args_clear + +CASE "normal start jboss require_args (user:root)" + Include prepare_jboss + Unenv OCF_RESKEY_user + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + Include args_clear + +CASE "error start jboss no jboss_home" + Include prepare_jboss + Unenv OCF_RESKEY_jboss_home + AgentRun start OCF_ERR_INSTALLED + Include args_clear + +CASE "error start jboss no java_home" + Include prepare_jboss + Unenv OCF_RESKEY_java_home + AgentRun start OCF_ERR_INSTALLED + Include args_clear + +CASE "error start jboss no java command" + Include prepare_jboss + Env OCF_RESKEY_java_home=/var + AgentRun start OCF_ERR_INSTALLED + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + Include args_clear + diff --git a/tools/ocft/mysql b/tools/ocft/mysql new file mode 100644 index 0000000..305a900 --- /dev/null +++ b/tools/ocft/mysql @@ -0,0 +1,82 @@ +# mysql + +CONFIG + Agent mysql + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage mysql + HangTimeout 20 + +SETUP-AGENT + if systemctl list-unit-files 2>/dev/null | fgrep -q mysql.service; then + systemctl start mysql.service + systemctl stop mysql.service + else + /etc/init.d/mysql start + /etc/init.d/mysql stop + fi + +CASE-BLOCK crm_setting + Env OCF_RESKEY_CRM_meta_timeout=15000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include crm_setting + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_binary'" + Include prepare + Env OCF_RESKEY_binary=no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "running monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not running monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "check lib file" + Include prepare + Bash chmod u-w /var/lib/mysql + BashAtExit chmod u+w /var/lib/mysql + AgentRun start OCF_ERR_PERM + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "non-existent user" + Include prepare + Env OCF_RESKEY_user=no_user + AgentRun start OCF_ERR_INSTALLED + +CASE "invalid user" + Include prepare + Env OCF_RESKEY_user=nobody + AgentRun start OCF_ERR_PERM diff --git a/tools/ocft/mysql-proxy b/tools/ocft/mysql-proxy new file mode 100644 index 0000000..e16d52b --- /dev/null +++ b/tools/ocft/mysql-proxy @@ -0,0 +1,83 @@ +# mysql-proxy +# by r.bhatia@ipax.at +# +# test cases (to implement): +# +# * /usr/sbin/ocf-tester -n mp -o binary="/usr/sbin/mysql-proxy" -o defaults_file="" -o parameters="--proxy-skip-profiling" \ +# -o admin_address="127.0.0.1:4041" -o admin_username="root" -o admin_password="la" -o admin_lua_script="/usr/lib/mysql-proxy/lua/admin.lua" \ +# -o proxy_backend_addresses="192.168.100.200:42006" -o proxy_address="/var/run/mysqld/mysqld.sock" /usr/lib/ocf/resource.d/heartbeat/mysql-proxy +# +# * OCF_CHECK_LEVEL 20 check + +CONFIG + Agent mysql-proxy + AgentRoot /usr/lib/ocf/resource.d/heartbeat/ + InstallPackage mysql-proxy + HangTimeout 20 + +SETUP-AGENT + # nothing + +CASE-BLOCK crm_setting + Env OCF_RESKEY_CRM_meta_timeout=15000 + Env OCF_RESKEY_binary=/tmp/mysql-proxy + Env OCF_RESKEY_admin_username=root + Env OCF_RESKEY_admin_password=test123 + Env OCF_RESKEY_admin_lua_script=/usr/lib/mysql-proxy/lua/admin.lua + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Bash [ ! -x /tmp/mysql-proxy ] && ln -s `which mysql-proxy` /tmp/mysql-proxy || true + Include crm_setting + +CASE-BLOCK teardown + AgentRun stop + BashAtExit rm -f /tmp/mysql-proxy + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + Include teardown + +CASE "check base env: invalid 'OCF_RESKEY_binary'" + Include prepare + Env OCF_RESKEY_binary=no_such + AgentRun start OCF_ERR_INSTALLED + BashAtExit rm -f /tmp/mysql-proxy + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + Include teardown + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + Include teardown + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + Include teardown + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "running monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + Include teardown + +CASE "not running monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/named b/tools/ocft/named new file mode 100644 index 0000000..90a4351 --- /dev/null +++ b/tools/ocft/named @@ -0,0 +1,69 @@ +#named + +# To work properly this test requires that standard bind and bin-utils +# packages installed. + +CONFIG + Agent named + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage bind + InstallPackage bind-utils + +SETUP-AGENT + /etc/init.d/named start + /etc/init.d/named stop + +CASE-BLOCK crm_setting + Env OCF_RESKEY_CRM_meta_timeout=15000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include crm_setting + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_named'" + Include prepare + Env OCF_RESKEY_named=no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "running monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not running monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "non-existent user" + Include prepare + Env OCF_RESKEY_named_user=no_user + AgentRun start OCF_ERR_INSTALLED diff --git a/tools/ocft/nfsserver b/tools/ocft/nfsserver new file mode 100644 index 0000000..cf2ac89 --- /dev/null +++ b/tools/ocft/nfsserver @@ -0,0 +1,69 @@ +# nfsserver + +CONFIG + Agent nfsserver + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage nfs-kernel-server + HangTimeout 20 + +SETUP-AGENT + INITSCRIPT="" + if test -f /etc/init.d/nfsserver; then + INITSCRIPT="/etc/init.d/nfsserver" + fi + true + +CASE-BLOCK required_args + Env OCF_RESKEY_nfs_init_script=$INITSCRIPT + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_nfs_init_script'" + Include prepare + Env OCF_RESKEY_nfs_init_script=no_such_script + AgentRun start OCF_ERR_INSTALLED + +CASE "check base env: invalid 'OCF_RESKEY_nfs_notify_cmd'" + Include prepare + Env OCF_RESKEY_nfs_notify_cmd=no_such_program + AgentRun start OCF_ERR_INSTALLED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/ocft.in b/tools/ocft/ocft.in new file mode 100644 index 0000000..0d7f645 --- /dev/null +++ b/tools/ocft/ocft.in @@ -0,0 +1,893 @@ +#!@BASH_SHELL@ + +# Copyright (c) 2010-2013 Novell Inc, John Shi +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + +die() +{ + local str + str="$1" + + echo "ERROR: $str" >&2 + exit 1 +} + +warn() +{ + local str + str="$1" + + echo "WARNING: $str" >&2 +} + +parse_die() +{ + local str + str="$1" + + agent_parse_finish + die "${agent}: line ${line_num}: ${str}" +} + +# add quotes to string for Here Documents +add_quotes() +{ + local typ str a b + typ="$1" + str="$2" + + case "$typ" in + 1) a=\'; b=\";; + 2) a=\"; b=\';; + esac + + echo "$str" | sed "s/$a/$a$b$a$b$a/g; 1 s/^/$a/; $ s/$/$a/" +} + +# split strings +explode() +{ + local str + str="$1" + + echo "$str" | awk -F'"' '{ + if (NF > 0 && NF%2 == 0) + exit(1); + for (i=1; i<=NF; i++) { + if (i%2 == 0) + print $i; + else { + len = split($i, str, /[ \t]+/); + for (j=1; j<=len; j++) { + sb = sub(/#.*/, "", str[j]); + if (str[j] != "") + print str[j]; + if (sb) + exit(0); + } + } + } + }' +} + +# phase 1: parse the string to 'command' and 'argument collection'. +line2trunk() +{ + trunk[0]="${line%%[[:blank:]]*}" + trunk[1]="${line#*[[:blank:]]}" +} + +# phase 2: split the argument collection. +trunk2branch() +{ + local IFS + + # Some of statements need one parameter at least. + if [ "$line" = "${trunk[0]}" ]; then + parse_die "missing parameter." + fi + + IFS=$'\n' + branch=($(explode "${trunk[1]}")) + if [ $? -ne 0 ]; then + parse_die "missing '\"'." + fi +} + +preparse_cfg() +{ + local agent line trunk branch macro num host + agent="$1" + + if [ ! -r "$opt_cfgsdir/$agent" ]; then + die "${agent}: configuration file not found." + fi + + line_num=0 + while read -r line; do + let line_num++ + num=" $line_num" + + case "$line" in + ""|\#*) continue;; + esac + + line2trunk + case "${trunk[0]}" in + CASE-BLOCK) + trunk2branch + macro="$CASES_DIR/${agent}_macro.${branch[0]}" + continue + ;; + Include|Include@*) + host=$(echo "${trunk[0]}" | awk -F@ '{print $2}') + trunk2branch + if [ ! -r "$CASES_DIR/${agent}_macro.${branch[0]}" ]; then + parse_die "Macro '${branch[0]}' not found." + fi + if [ -n "$host" ]; then + line="$(sed -e 's/^\([^[:blank:]]*\)@[^[:blank:]]*/\1/' -e "s/^[^[:blank:]]*/&@$host/" "$CASES_DIR/${agent}_macro.${branch[0]}")" + else + line="$(<"$CASES_DIR/${agent}_macro.${branch[0]}")" + fi + num= + ;; + *[!A-Z-]*) + : + ;; + *) + macro= + ;; + esac + + if [ -n "$macro" ]; then + echo "$line$num" >>"$macro" + else + echo "$line$num" >>"$CASES_DIR/${agent}_preparse" + fi + done <"$opt_cfgsdir/$agent" +} + +case_parse_finish() +{ + local host + + if [ -n "$sh" ]; then + cat >>$sh <<EOF +if [ -n "\$__OCFT__VERBOSE" ]; then + echo +fi +# Cleanup and exit +EOF + for host in $hosts; do + echo "backbash_stop $host" >>$sh + done + echo "quit 0" >>$sh + fi + + atexit_num=0 + hosts= + sh= +} + +init_cfg_vars() +{ + cfg_agent= + cfg_agent_root= + cfg_install_package=() + cfg_hang_timeout=20 +} + +agent_parse_finish() +{ + local suf + + for suf in preparse setup cleanup var hosts; do + rm -f $CASES_DIR/${agent}_$suf + done + rm -f $CASES_DIR/${agent}_macro.* + init_cfg_vars +} + +need_make() +{ + local src_time obj_time + + if [ ! -f "$CASES_DIR/0_${agent}.sh" ]; then + return 0 + fi + + src_time=$(stat -c '%Y' "$opt_cfgsdir/$agent") + obj_time=$(stat -c '%Y' "$CASES_DIR/0_${agent}.sh") + + test $src_time -ge $obj_time +} + +parse_cfg() +{ + local agents i line stat sh trunk branch atexit_num host hosts + + if [ $# -eq 0 ]; then + agents=($opt_cfgsdir/*) + else + agents=("$@") + fi + + for agent in "${agents[@]}"; do + agent="$(basename "$agent")" + + if ! need_make; then + continue + fi + + agent_obj_clean $agent + agent_parse_finish + + i=0 + + echo "Making '$agent': " + preparse_cfg "$agent" + while read -r line; do + line_num="${line##* }" + line="${line% *}" + line2trunk + + # state switch + case "${trunk[0]}" in + CONFIG) + case_parse_finish + stat=1 + continue + ;; + VARIABLE) + case_parse_finish + stat=2 + continue + ;; + SETUP-AGENT) + case_parse_finish + stat=3 + continue + ;; + CLEANUP-AGENT) + case_parse_finish + stat=4 + continue + ;; + CASE) + case_parse_finish + trunk2branch + echo " - case ${i}: ${branch[0]}" + sh="$CASES_DIR/${i}_${agent}.sh" + cat >$sh <<EOF +#!@BASH_SHELL@ + +# Agent: $cfg_agent +# Summary: ${branch[0]} + +. $OCFT_DIR/caselib || { + echo "ERROR: '$OCFT_DIR/caselib' not found." + exit 2 +} + +$(test -r $CASES_DIR/${agent}_var && cat $CASES_DIR/${agent}_var) + +__OCFT__MYROOT="$cfg_agent_root" + +$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}") + +if [ -n "\$__OCFT__VERBOSE" ]; then + echo -e $(add_quotes 1 "Starting '\\033[33m${agent}\\033[0m' case $i '\\033[33m${branch[0]}\\033[0m':") +else + echo -n "${agent}: ${branch[0]} - " +fi +EOF + chmod a+x $sh + + let i++ + stat=5 + continue + ;; + esac + + case "$stat" in + 1) + case "${trunk[0]}" in + Agent) + trunk2branch + cfg_agent="${branch[0]}" + ;; + AgentRoot) + trunk2branch + cfg_agent_root="${branch[0]}" + ;; + InstallPackage) + trunk2branch + cfg_install_package=(${cfg_install_package[@]} ${branch[@]}) + ;; + HangTimeout) + trunk2branch + if ! echo "${branch[0]}" | grep -qxE '[0-9]+'; then + parse_die "numeric argument required." + fi + cfg_hang_timeout="${branch[0]}" + ;; + *) + parse_die "unimplemented statement: ${trunk[0]}" + ;; + esac + ;; + 2) + if echo "$line" | grep -q '^__OCFT__'; then + parse_die "reserved key word '__OCFT__'." + fi + echo "declare $line" >>$CASES_DIR/${agent}_var + ;; + 3) + echo "$line" >>$CASES_DIR/${agent}_setup + ;; + 4) + echo "$line" >>$CASES_DIR/${agent}_cleanup + ;; + 5) + host=$(echo ${trunk[0]} | awk -F@ '{print $2}') + if [ -n "$host" ]; then + if ! echo "$hosts" | grep -q "$host"; then + echo "$host" >>$CASES_DIR/${agent}_hosts + hosts=$hosts$'\n'$host + cat >>$sh <<EOF +# Initialize remote shell +backbash_start $host +backbash $host <<CMD +__OCFT__VERBOSE=\$__OCFT__VERBOSE +CMD +backbash $host <$OCFT_DIR/caselib +backbash $host <<'CMD' +$(test -r $CASES_DIR/${agent}_var && cat $CASES_DIR/${agent}_var) + +__OCFT__MYROOT="$cfg_agent_root" +__OCFT__showhost="${host}: " + +$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}") +CMD +EOF + fi + fi + + echo " +# CASE statement: $line" >>$sh + + if [ -n "$host" ]; then + echo "backbash $host <<'CMD'" >>$sh + fi + + case "${trunk[0]}" in + Env|Env@*) + cat >>$sh <<EOF +if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Setting agent environment: export ${trunk[1]}") +fi +export ${trunk[1]} +check_success \$? $(add_quotes 1 "export ${trunk[1]}") +EOF + ;; + Unenv|Unenv@*) + cat >>$sh <<EOF +if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Removing agent environment: unset ${trunk[1]}") +fi +unset ${trunk[1]} +check_success \$? $(add_quotes 1 "unset ${trunk[1]}") +EOF + ;; + AgentRun|AgentRun@*) + trunk2branch + if [ -z "${branch[1]}" ]; then + if [ "${branch[0]}" = "start" ]; then + cat >>$sh <<EOF +agent_run $(add_quotes 1 "$cfg_agent") monitor $cfg_hang_timeout +__OCFT__rc=\$? + +if [ \$__OCFT__rc -eq \$OCF_SUCCESS -o \$__OCFT__rc -eq \$OCF_RUNNING_MASTER ]; then + : #The status I want, so I can do nothing. +elif [ \$__OCFT__rc -eq \$OCF_NOT_RUNNING ]; then + if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}") + fi + agent_run $(add_quotes 1 "$cfg_agent") start $cfg_hang_timeout + check_success \$? $(add_quotes 1 "./$cfg_agent ${branch[0]}") +else + check_success \$__OCFT__rc $(add_quotes 1 "./$cfg_agent monitor") +fi +EOF + elif [ "${branch[0]}" = "stop" ]; then + cat >>$sh <<EOF +agent_run $(add_quotes 1 "$cfg_agent") monitor $cfg_hang_timeout +__OCFT__rc=\$? + +if [ \$__OCFT__rc -eq \$OCF_NOT_RUNNING ]; then + : #The status I want, so I can do nothing. +elif [ \$__OCFT__rc -eq \$OCF_SUCCESS -o \$__OCFT__rc -eq \$OCF_RUNNING_MASTER ]; then + if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}") + fi + agent_run $(add_quotes 1 "$cfg_agent") stop $cfg_hang_timeout + check_success \$? $(add_quotes 1 "./$cfg_agent ${branch[0]}") +else + check_success \$__OCFT__rc $(add_quotes 1 "./$cfg_agent monitor") +fi +EOF + elif [ "${branch[0]}" = "monitor" ]; then + cat >>$sh <<EOF +if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}") +fi +agent_run $(add_quotes 1 "$cfg_agent") $(add_quotes 1 "${branch[0]}") $cfg_hang_timeout +EOF + else + cat >>$sh <<EOF +if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}") +fi +agent_run $(add_quotes 1 "$cfg_agent") $(add_quotes 1 "${branch[0]}") $cfg_hang_timeout +check_success \$? $(add_quotes 1 "./$cfg_agent ${branch[0]}") +EOF + fi + else + cat >>$sh <<EOF +test -n $(add_quotes 2 "\$${branch[1]}") +check_success \$? $(add_quotes 1 "test -n \"\$${branch[1]}\"") +if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}") +fi +agent_run $(add_quotes 1 "$cfg_agent") $(add_quotes 1 "${branch[0]}") $cfg_hang_timeout +__OCFT__ret=\$? +if [ -n "\$__OCFT__VERBOSE" ]; then + echo -n " \${__OCFT__showhost}Checking return value:" +fi +if [ -n "\${__OCFT__retval[__OCFT__ret]}" ]; then + __OCFT__retstr="\${__OCFT__retval[__OCFT__ret]}" +else + __OCFT__retstr=\$__OCFT__ret +fi +if [ \$__OCFT__ret -eq \$${branch[1]} ]; then + if [ -n "\$__OCFT__VERBOSE" ]; then + echo -e $(add_quotes 2 " \\033[32mOK\\033[0m. The return value '\\033[34m\$__OCFT__retstr\\033[0m' == '\\033[34m${branch[1]}\\033[0m'") + else + echo -e "\\033[32mOK\\033[0m." + fi +else + if [ -n "\$__OCFT__VERBOSE" ]; then + echo -en $(add_quotes 2 " \\033[31mFAILED\\033[0m. The return value '\\033[34m\$__OCFT__retstr\\033[0m' != '\\033[34m${branch[1]}\\033[0m'. ") + else + echo -en "\\033[31mFAILED\\033[0m. Agent returns unexpected value: '\$__OCFT__retstr'. " + fi + echo "See details below:" + cat /tmp/.ocft_runlog + echo + quit 1 +fi +EOF + fi + ;; + Bash|Bash@*) + cat >>$sh <<EOF +if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Setting system environment: ${trunk[1]}") +fi +${trunk[1]} +check_success \$? $(add_quotes 1 "${trunk[1]}") +EOF + ;; + BashAtExit|BashAtExit@*) + let atexit_num++ + cat >>$sh <<EOF +atexit${atexit_num}() +{ + if [ -n "\$__OCFT__VERBOSE" ]; then + echo $(add_quotes 2 " \${__OCFT__showhost}Setting system environment: ${trunk[1]}") + fi + ${trunk[1]} +} +let __OCFT__atexit_num++ +EOF + ;; + *) + parse_die "unimplemented statement: ${trunk[0]}" + ;; + esac + if [ -n "$host" ]; then + echo 'CMD' >>$sh + fi + ;; + *) + parse_die "unimplemented statement: ${trunk[0]}" + ;; + esac + done <$CASES_DIR/${agent}_preparse + + if [ -r "$CASES_DIR/${agent}_setup" ]; then + cat >$CASES_DIR/setup_${agent}.sh <<EOF +#!@BASH_SHELL@ + +# Agent: $cfg_agent +# Summary: SETUP before test + +echo "Initializing '$cfg_agent' ..." + +. $OCFT_DIR/caselib || { + echo "ERROR: '$OCFT_DIR/caselib' not found." + exit 2 +} + +$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var) +$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}") + +EOF + for host in $(test -r $CASES_DIR/${agent}_hosts && cat $CASES_DIR/${agent}_hosts); do + cat >>$CASES_DIR/setup_${agent}.sh <<EOF +# Initialize remote shell +backbash_start $host +backbash $host <<CMD +__OCFT__VERBOSE=\$__OCFT__VERBOSE +CMD +backbash $host <$OCFT_DIR/caselib +backbash $host <<'CMD' +$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var) + +__OCFT__MYROOT="$cfg_agent_root" +__OCFT__showhost="${host}: " + +$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}") + +$(cat $CASES_DIR/${agent}_setup) +check_success \$? "SETUP-AGENT" +CMD +backbash_stop $host +EOF + done + cat >>$CASES_DIR/setup_${agent}.sh <<EOF +$(cat $CASES_DIR/${agent}_setup) +check_success \$? "SETUP-AGENT" + +echo "Done." +echo +quit 0 +EOF + chmod a+x $CASES_DIR/setup_${agent}.sh + fi + + if [ -r "$CASES_DIR/${agent}_cleanup" ]; then + cat >$CASES_DIR/cleanup_${agent}.sh <<EOF +#!@BASH_SHELL@ + +# Agent: $cfg_agent +# Summary: CLEANUP after test + +echo "Cleaning '$cfg_agent' ..." + +. $OCFT_DIR/caselib || { + echo "ERROR: '$OCFT_DIR/caselib' not found." + exit 2 +} + +$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var) +$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}") + +$(cat $CASES_DIR/${agent}_cleanup) +check_success \$? "CLEANUP-AGENT" + +EOF + for host in $(test -r $CASES_DIR/${agent}_hosts && cat $CASES_DIR/${agent}_hosts); do + cat >>$CASES_DIR/cleanup_${agent}.sh <<EOF +# Initialize remote shell +backbash_start $host +backbash $host <<CMD +__OCFT__VERBOSE=\$__OCFT__VERBOSE +CMD +backbash $host <$OCFT_DIR/caselib +backbash $host <<'CMD' +$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var) + +__OCFT__MYROOT="$cfg_agent_root" +__OCFT__showhost="${host}: " + +$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}") + +$(cat $CASES_DIR/${agent}_cleanup) +check_success \$? "CLEANUP-AGENT" +CMD +backbash_stop $host +EOF + done + cat >>$CASES_DIR/cleanup_${agent}.sh <<EOF +echo "Done." +echo +quit 0 +EOF + chmod a+x $CASES_DIR/cleanup_${agent}.sh + fi + case_parse_finish + agent_parse_finish + + done +} + +start_test() +{ + local sh shs testsh agents line ret + local rc=0 + local varlib + local rc_f + + if ! cd $CASES_DIR >/dev/null 2>&1; then + die "cases directory not found." + fi + + if [ ! -d logs ]; then + mkdir logs + fi + + export __OCFT__VERBOSE=$opt_verbose + + if [ $# -eq 0 ]; then + agents=($(ls -1 *.sh 2>/dev/null | sed 's/.*_\([^_]*\)\.sh$/\1/' | sort | uniq)) + else + agents=("$@") + fi + + for shs in "${agents[@]}"; do + if [ -z "$opt_incremental" ]; then + testsh="setup_${shs}.sh + $(ls -1 [0-9]*_${shs}.sh 2>/dev/null | sort -n) + cleanup_${shs}.sh" + else + testsh="setup_${shs}.sh + $(ls -1 [0-9]*_${shs}.retest 2>/dev/null | sed 's/retest$/sh/' | sort -n) + cleanup_${shs}.sh" + fi + + if [ -n "$opt_trace_ra" ]; then + varlib=${HA_VARLIB:="/var/lib/heartbeat"} + export OCF_RESKEY_trace_ra=1 + echo "RA trace on, output in $varlib/trace_ra" + fi + + rc_f=`mktemp` + (for sh in $testsh; do + if [ -r "$sh" ]; then + if [ -n "$opt_trace_ra" ]; then + export OCF_RESOURCE_INSTANCE="`echo $sh | sed 's/_.*//'`" + fi + ./$sh + ret=$? + + case "$sh" in + setup*) + rc=$((rc|ret)) + if [ $ret -ne 0 ]; then + warn "SETUP failed, break all tests of '$shs'." + break + fi + ;; + cleanup*) + if [ $ret -ne 0 ]; then + warn "CLEANUP failed." + fi + ;; + [0-9]*) + case $ret in + 3) die "core function failed, break all tests." ;; + 2) warn "core function failed, break all tests of '$shs'."; break ;; + 1) touch ${sh%.*}.retest ;; + 0) rm -f ${sh%.*}.retest ;; + esac + rc=$((rc|ret)) + ;; + esac + fi + done 2>&1; echo $rc > $rc_f) | while read -r line; do + echo "$line" + echo "$(date '+%F %T'): $line" | cat -A | + sed -r 's/\^\[\[[0-9]+m|\^I|.$//g' >>logs/$shs.log + done + done + rc=`cat $rc_f` + rm -f $rc_f + return $rc +} + +agent_clean() +{ + local typ ra + typ=$1 + + shift + + if [ $# -eq 0 ]; then + rm -f $CASES_DIR/*.$typ + else + for ra in "$@"; do + rm -f $CASES_DIR/*_${ra}.$typ + done + fi +} + +agent_retest_clean() +{ + agent_clean retest "$@" +} + +agent_obj_clean() +{ + agent_clean sh "$@" +} + +usage() +{ + cat <<EOF +$0 ACTION [OPTION] [agent1 [agent2] [...]] +ACTIONs include: + make [-d dir] Generate the testing shell scripts. + -d The directory that contains + configuration of cases. + test [-v|-i|-X] Execute the testing shell scripts. + -v Verbose output mode. + -i Incremental mode, skip case + which succeeded. If cleaning + the status of incremental mode + is needed, try to '$0 clean RA_NAME'. + -X Trace the RA + clean Delete the testing shell scripts. + help [-v] Show this help and exit. + -v Show HOWTO and exit. +Version 0.44 +See '$OCFT_DIR/README' for detail. +EOF +} + +howto() +{ + cat <<EOF +HOW TO USE THIS TOOL + + - Ocft is a testing tool for resource agents. Instead of the policy of HA, + it mainly concerns whether resource agents run correct locally. It can + design types of complicated environments to test the reliability of + resource agents. Precisely, it is to display whether resource agents can + return to correct or expected value. The advantage of the tool provides + us with competence to design conditions which can be recorded or reproduced. + Hence it is useful to debuggers. + +* Components + ** Test case generator (/usr/sbin/ocft) + - Turning configuration files of test case to executable scripts. + + ** Configuration file ($CONFIGS_DIR/) + - Every configuration file directs only one resource agent and share the same + name with resource agent but contains more test cases. + + ** The testing script ($CASES_DIR/) + - After the generator reads configuration files and generates many testing + scripts and the script is underway, the test begins. + +* How to customize the environment of testing + - Ocft designs the running conditions through two ways, one is changing the + environment variables of resource agents (it is the interface left by OCF itself), + the other is modifying the OS environment of resource agents, such as altering + the permission of some key file or IP address of the machine. + +* How to test + - Firstly, you need to sketch the all complex and uncommon environments against + a certain resource agent and keep in mind what consequences may be caused by + these uncommon environments. + Secondly, write the designed conditions and foreknown consequences into + configuration files, and then run the generator to translate the test case to + executable scripts. + Finally, you need running these scripts to observe the output and learn + the running status of each test case, which will compares the predicated result + with the actual one. If they differ, you will be able to find the bugs of the + resource agent. + - All of the output with test will be recorded into the log files, you can find them + in $CASES_DIR/logs. +EOF +} + + +export LANG=C + +# system variable +OCFT_DIR=@datadir@/@PACKAGE_NAME@/ocft +CONFIGS_DIR=@datadir@/@PACKAGE_NAME@/ocft/configs +CASES_DIR=/var/lib/@PACKAGE_NAME@/ocft/cases + +# global variable +agent= +line_num= + +# default configuration +init_cfg_vars + +# default option +opt_verbose= +opt_incremental= +opt_cfgsdir=$CONFIGS_DIR + +command="$1" +shift + +case "$command" in + make) + if [ "$1" = "-d" ]; then + if [ ! -d "$2" ]; then + usage + exit 1 + fi + opt_cfgsdir="$2" + shift 2 + fi + if [ ! -d "$CASES_DIR" ]; then + mkdir -p "$CASES_DIR" || die "Can not create directory: ${CASES_DIR}." + fi + parse_cfg "$@" + ;; + test) + for v in 1 2 3; do + case "$1" in + -v) + opt_verbose=1 + shift + ;; + -X) + opt_trace_ra=1 + shift + ;; + -i) + opt_incremental=1 + shift + ;; + -*) + die "bad option $1" + ;; + esac + done + start_test "$@" + ;; + clean) + agent_obj_clean "$@" + agent_retest_clean "$@" + ;; + help) + if [ "$1" = "-v" ]; then + howto + else + usage + fi + exit 0 + ;; + *) + usage + exit 1 + ;; +esac + +# vim:ts=2:sw=2:et: diff --git a/tools/ocft/oracle b/tools/ocft/oracle new file mode 100644 index 0000000..6f145c7 --- /dev/null +++ b/tools/ocft/oracle @@ -0,0 +1,81 @@ +# oracle +# (based on db2) +# +# Created on an SLE11SP2 running oracle 11g +# database sid is orcl +# adapt this in set_testenv below +# TODO: need oracle expert to break it, then test it +# + +CONFIG + Agent oracle + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 40 + +SETUP-AGENT + # nothing + +CASE-BLOCK set_testenv + Env OCFT_sid=orcl + +CASE-BLOCK crm_setting + Env OCF_RESKEY_sid=$OCFT_sid + Env OCF_RESKEY_CRM_meta_timeout=30000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include set_testenv + Include crm_setting + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: no 'OCF_RESKEY_sid'" + Include prepare + Env OCF_RESKEY_sid= + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: invalid 'OCF_RESKEY_home'" + Include prepare + Env OCF_RESKEY_home=/no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "started: monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not started: monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "try different ipcrm method" + Include prepare + Env OCF_RESKEY_ipcrm=none + AgentRun start OCF_SUCCESS diff --git a/tools/ocft/pgsql b/tools/ocft/pgsql new file mode 100644 index 0000000..9944b09 --- /dev/null +++ b/tools/ocft/pgsql @@ -0,0 +1,71 @@ +# pgsql + +CONFIG + Agent pgsql + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage postgresql-server + HangTimeout 20 + +SETUP-AGENT + /etc/init.d/postgresql start + /etc/init.d/postgresql stop + +CASE-BLOCK crm_setting + Env OCF_RESKEY_CRM_meta_timeout=15000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include crm_setting + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_pgctl'" + Include prepare + Env OCF_RESKEY_pgctl=no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "running monitor" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "not running monitor" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED + +CASE "non-existent user" + Include prepare + Env OCF_RESKEY_pgdba=no_user + AgentRun start OCF_ERR_INSTALLED + +CASE "invalid user" + Include prepare + Env OCF_RESKEY_pgdba=nobody + AgentRun start OCF_ERR_PERM diff --git a/tools/ocft/portblock b/tools/ocft/portblock new file mode 100644 index 0000000..3475c63 --- /dev/null +++ b/tools/ocft/portblock @@ -0,0 +1,69 @@ +# portblock + +CONFIG + Agent portblock + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage iptables + HangTimeout 15 + +CASE-BLOCK required_args + Env OCF_RESKEY_protocol=tcp + Env OCF_RESKEY_portno=80 + Env OCF_RESKEY_action=block + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_protocol'" + Include prepare + Unenv OCF_RESKEY_protocol + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: unset 'OCF_RESKEY_portno'" + Include prepare + Unenv OCF_RESKEY_portno + AgentRun start OCF_ERR_CONFIGURED + +CASE "check base env: unset 'OCF_RESKEY_action'" + Include prepare + Unenv OCF_RESKEY_action + AgentRun start OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor with running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor with not running" + Include prepare + AgentRun monitor OCF_NOT_RUNNING + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/postfix b/tools/ocft/postfix new file mode 100644 index 0000000..f17e968 --- /dev/null +++ b/tools/ocft/postfix @@ -0,0 +1,102 @@ +# postfix +# by r.bhatia@ipax.at +# +# test cases (to implement): +# +# * /usr/sbin/ocf-tester -n post1 /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? -> DONE +# * /usr/sbin/ocf-tester -n post2 -o binary="/usr/sbin/postfix" \ +# -o config_dir="" /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? -> DONE +# * /usr/sbin/ocf-tester -n post3 -o binary="/usr/sbin/postfix" \ +# -o config_dir="/etc/postfix" /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? -> DONE +# * /usr/sbin/ocf-tester -n post4 -o binary="/usr/sbin/postfix" \ +# -o config_dir="/root/postfix/" /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? + +CONFIG + Agent postfix + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage postfix + HangTimeout 20 + +SETUP-AGENT + # nothing + +CASE-BLOCK crm_setting + Env OCF_RESKEY_CRM_meta_timeout=15000 + Env OCF_RESKEY_CRM_meta_interval=10000 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include crm_setting + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: invalid 'OCF_RESKEY_binary'" + Include prepare + Env OCF_RESKEY_binary=no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "check base env: invalid 'OCF_RESKEY_config_dir'" + Include prepare + Env OCF_RESKEY_config_dir=no_such + AgentRun start OCF_ERR_INSTALLED + +CASE "check base env: 'OCF_RESKEY_binary'" + Include prepare + Env OCF_RESKEY_binary=/usr/sbin/postfix + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "check base env: 'OCF_RESKEY_config_dir' without trailing slash" + Include prepare + Env OCF_RESKEY_config_dir="/etc/postfix" + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "check base env: 'OCF_RESKEY_config_dir' with trailing slash" + Include prepare + Env OCF_RESKEY_config_dir="/etc/postfix/" + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop OCF_SUCCESS + +CASE "monitor a running resource" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "(initial) probe a stopped resource" + Include prepare + Env OCF_RESKEY_CRM_meta_interval=0 + AgentRun monitor OCF_NOT_RUNNING + +CASE "(re-)probe a running resource" + Include prepare + Env OCF_RESKEY_CRM_meta_interval=0 + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "unimplemented command" + Include prepare + AgentRun no_cmd OCF_ERR_UNIMPLEMENTED diff --git a/tools/ocft/runocft b/tools/ocft/runocft new file mode 100755 index 0000000..d269a6b --- /dev/null +++ b/tools/ocft/runocft @@ -0,0 +1,38 @@ +#!/bin/sh +OCFTDIR=/usr/share/resource-agents/ocft +CONFDIR=$OCFTDIR/configs + +prereq_run() { + eval "$@" +} +prereq_prog() { + which $@ +} + +test_prereq() { + local tp arg + tp=`echo $prereq|sed 's/:.*//'` + arg=`echo $prereq|sed 's/[a-z]*://'` + prereq_$tp $arg >/dev/null 2>&1 +} + +rm -f ocft.FAILED +rc=0 +while read f prereq; do + if [ -n "$prereq" ] && ! test_prereq; then + echo "$f: prerequisite not fulfilled, skipping" + continue + fi + ocft make $f + if ! ocft test $f; then + echo $f >> ocft.FAILED + rc=1 + fi +done < $OCFTDIR/runocft.prereq + +if [ -f ocft.FAILED ]; then + echo "The following ocft tests failed:" + cat ocft.FAILED +fi + +exit $rc diff --git a/tools/ocft/runocft.prereq b/tools/ocft/runocft.prereq new file mode 100644 index 0000000..3bc222c --- /dev/null +++ b/tools/ocft/runocft.prereq @@ -0,0 +1,30 @@ +apache +db2 prog:~db2inst1/sqllib/bin/db2 +drbd.linbit run:false +exportfs +exportfs-multidir +Filesystem +IPaddr2 run:ip addr show dev eth0 +IPaddr2v4 run:ip addr show dev eth0 && ip addr show dev eth1 +IPaddr2v6 run:ip addr show dev eth0 && ip addr show dev eth1 +IPsrcaddr run:false +IPv6addr run:ip addr show dev eth0 && ip addr show dev eth1 +iscsi +jboss run:false +LVM +MailTo +mysql +mysql-proxy run:false +named +nfsserver +oracle prog:sqlplus +pgsql +portblock +postfix +Raid1 +SendArp +sg_persist run:false +tomcat run:false +VirtualDomain run:false +Xen run:false +Xinetd diff --git a/tools/ocft/sg_persist b/tools/ocft/sg_persist new file mode 100644 index 0000000..f11d36d --- /dev/null +++ b/tools/ocft/sg_persist @@ -0,0 +1,225 @@ +# sg_persist +# Before use sg_persist ocft test case you have to prepare to make pacemaker +# can be started successfully because sg_persist RA needs to use the result of +# `crm_node -i` as sg_persist register key. + +CONFIG + Agent sg_persist + AgentRoot /usr/lib/ocf/resource.d/heartbeat + InstallPackage sg3_utils + HangTimeout 20 + +VARIABLE + + # Please set the disk you want to test + #OCFT_DEVS="/dev/disk/by-id/scsi-14945540000000000844965720e6e555176b19461345d68d5" + OCFT_DEVS= + + # please set the IP addrees of the other server. + IP_2= + +SETUP-AGENT + # start pacemaker to get node_id through crm_node -i + systemctl start pacemaker + +CLEANUP-AGENT + +CASE-BLOCK required_args + Env HA_SBIN_DIR="" + Env OCF_RESKEY_devs="$OCFT_DEVS" + Env OCF_RESKEY_reservation_type=1 + Env OCF_RESKEY_CRM_meta_notify=true + Env OCF_RESKEY_CRM_meta_master_max=1 + +CASE-BLOCK required_args_error + Env HA_SBIN_DIR="" + Env OCF_RESKEY_devs="$OCFT_DEVS" + Env OCF_RESKEY_reservation_type=1 + Env OCF_RESKEY_CRM_meta_notify=true + Env OCF_RESKEY_CRM_meta_master_max=2 + +CASE-BLOCK required_args_type_5 + Env HA_SBIN_DIR="" + Env OCF_RESKEY_devs="$OCFT_DEVS" + Env OCF_RESKEY_reservation_type=5 + Env OCF_RESKEY_CRM_meta_notify=true + Env OCF_RESKEY_CRM_meta_master_max=1 + +CASE-BLOCK required_args_type_7 + Env HA_SBIN_DIR="" + Env OCF_RESKEY_devs="$OCFT_DEVS" + Env OCF_RESKEY_reservation_type=7 + Env OCF_RESKEY_CRM_meta_notify=true + Env OCF_RESKEY_CRM_meta_master_max=2 + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare + Include required_args + Include default_status + +CASE-BLOCK prepare_type_5 + Include required_args_type_5 + Include default_status + +CASE-BLOCK prepare_type_7 + Include required_args_type_7 + Include default_status + +CASE "check base env" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "check base env: unset 'OCF_RESKEY_sg_persist_devs'" + Include prepare + Unenv OCF_RESKEY_devs + AgentRun start OCF_ERR_INSTALLED + +CASE "validate all" + Include required_args + AgentRun validate-all OCF_SUCCESS + +CASE "validate all--type:5" + Include required_args_type_5 + AgentRun validate-all OCF_SUCCESS + +CASE "validate all--type:7" + Include required_args_type_7 + AgentRun validate-all OCF_SUCCESS + +CASE "validate all--error configured" + Include required_args_error + AgentRun validate-all OCF_ERR_CONFIGURED + +CASE "normal start" + Include prepare + AgentRun start OCF_SUCCESS + +CASE "normal stop" + Include prepare + AgentRun start + AgentRun stop OCF_SUCCESS + +CASE "double start" + Include prepare + AgentRun start + AgentRun start OCF_SUCCESS + +CASE "double stop" + Include prepare + AgentRun stop + AgentRun stop OCF_SUCCESS + +CASE "monitor when running" + Include prepare + AgentRun start + AgentRun monitor OCF_SUCCESS + +CASE "monitor when not running" + Include prepare + AgentRun stop + AgentRun monitor OCF_NOT_RUNNING + +CASE "Primary/Secondary monitor" + Include prepare + AgentRun start + AgentRun promote + AgentRun monitor OCF_RUNNING_MASTER + AgentRun demote + AgentRun monitor OCF_SUCCESS + AgentRun stop + +CASE "promote/demote test in single-primary mode" + Include prepare + Include@$IP_2 prepare + + # start + AgentRun start + AgentRun@$IP_2 start + + # promote local first + AgentRun promote OCF_SUCCESS + + # demote local prepare for remote promote + AgentRun demote + + # remote promote + AgentRun@$IP_2 promote OCF_SUCCESS + AgentRun@$IP_2 monitor OCF_RUNNING_MASTER + AgentRun monitor OCF_SUCCESS + + # promote local then promote(by preempt) remote, which will cause local OCF_NOT_RUNNING + AgentRun promote OCF_SUCCESS + AgentRun monitor OCF_RUNNING_MASTER + AgentRun@$IP_2 monitor OCF_NOT_RUNNING + + # remove all reservation registration + AgentRun demote + AgentRun stop + AgentRun@$IP_2 stop + + +CASE "normal start--type 5" + Include prepare_type_5 + Include@$IP_2 prepare_type_5 + AgentRun start OCF_SUCCESS + AgentRun@$IP_2 start OCF_SUCCESS + +CASE "promote/demote test in single-primary mode" + Include prepare_type_5 + Include@$IP_2 prepare_type_5 + + # start + AgentRun start + AgentRun@$IP_2 start + + # promote local first + AgentRun promote OCF_SUCCESS + AgentRun monitor OCF_RUNNING_MASTER + + # demote local prepare for remote promote + AgentRun demote + # remote promote + AgentRun@$IP_2 promote OCF_SUCCESS + AgentRun@$IP_2 monitor OCF_RUNNING_MASTER + AgentRun monitor OCF_SUCCESS + + # promote local then promote(by preempt) remote, which will cause local OCF_NOT_RUNNING + AgentRun promote OCF_SUCCESS + AgentRun monitor OCF_RUNNING_MASTER + AgentRun@$IP_2 monitor OCF_NOT_RUNNING + + # remove all reservation registration + AgentRun demote + AgentRun stop + AgentRun@$IP_2 stop + +CASE "normal start--type 7: 2 masters" + Include prepare_type_7 + Include@$IP_2 prepare_type_7 + AgentRun start OCF_SUCCESS + AgentRun@$IP_2 start OCF_SUCCESS + + +CASE "Primary/Primary monitor--type 7" + Include prepare_type_7 + Include@$IP_2 prepare_type_7 + + AgentRun start OCF_SUCCESS + AgentRun@$IP_2 start OCF_SUCCESS + + AgentRun promote OCF_SUCCESS + AgentRun monitor OCF_RUNNING_MASTER + + AgentRun@$IP_2 promote OCF_SUCCESS + AgentRun monitor OCF_RUNNING_MASTER + AgentRun@$IP_2 monitor OCF_RUNNING_MASTER + + AgentRun demote OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + AgentRun@$IP_2 demote OCF_SUCCESS + AgentRun@$IP_2 monitor OCF_NOT_RUNNING + + AgentRun stop + AgentRun@$IP_2 stop diff --git a/tools/ocft/tomcat b/tools/ocft/tomcat new file mode 100644 index 0000000..56adf86 --- /dev/null +++ b/tools/ocft/tomcat @@ -0,0 +1,73 @@ +# tomcat +# +# NOTE: Clean up $catalina_home/logs before running this test +# otherwise creating the pid/log files may fail +# in the test case with a different user. + +CONFIG + Agent tomcat + AgentRoot /usr/lib/ocf/resource.d/heartbeat + HangTimeout 120 + +VARIABLE + # Adjust accrding to your configuration + catalina_home=/opt/tomcat7 + tomcat_user=tomcat7 + java_home=/usr/lib/jvm/java-1.6.0-openjdk.x86_64 + +CASE-BLOCK required_args_tomcat + Env OCF_RESKEY_catalina_home=${catalina_home} + Env OCF_RESKEY_tomcat_user=${tomcat_user} + Env OCF_RESKEY_java_home=${java_home} + +CASE-BLOCK args_clear + Unenv OCF_RESKEY_catalina_home + Unenv OCF_RESKEY_tomcat_user + Unenv OCF_RESKEY_java_home + +CASE-BLOCK default_status + AgentRun stop + +CASE-BLOCK prepare_tomcat + Include required_args_tomcat + Include default_status + +# Test CASE +# +CASE "normal start tomcat require_args (user:user)" + Include prepare_tomcat + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + Include args_clear + +CASE "normal start tomcat require_args (user:root)" + Include prepare_tomcat + Unenv OCF_RESKEY_tomcat_user + AgentRun start OCF_SUCCESS + AgentRun monitor OCF_SUCCESS + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + Include args_clear + +CASE "error start tomcat no catalina_home" + Include prepare_tomcat + Unenv OCF_RESKEY_catalina_home + AgentRun start OCF_ERR_INSTALLED + Include args_clear + +CASE "error start tomcat no java_home" + Include prepare_tomcat + Unenv OCF_RESKEY_java_home + AgentRun start OCF_ERR_INSTALLED + Include args_clear + +CASE "error start tomcat no java command" + Include prepare_tomcat + Env OCF_RESKEY_java_home=/var + AgentRun start OCF_ERR_INSTALLED + AgentRun stop OCF_SUCCESS + AgentRun monitor OCF_NOT_RUNNING + Include args_clear + diff --git a/tools/send_arp.libnet.c b/tools/send_arp.libnet.c new file mode 100644 index 0000000..d7bca99 --- /dev/null +++ b/tools/send_arp.libnet.c @@ -0,0 +1,764 @@ +/* + * send_arp + * + * This program sends out one ARP packet with source/target IP and Ethernet + * hardware addresses suuplied by the user. It uses the libnet libary from + * Packet Factory (http://www.packetfactory.net/libnet/ ). It has been tested + * on Linux, FreeBSD, and on Solaris. + * + * This inspired by the sample application supplied by Packet Factory. + + * Matt Soffen + + * Copyright (C) 2001 Matt Soffen <matt@soffen.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Needs to be defined before any other includes, otherwise some system + * headers do not behave as expected! Major black magic... */ +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE + +#include <config.h> +#include <sys/param.h> + +#define USE_GNU +#if defined(ANSI_ONLY) && !defined(inline) +# define inline /* nothing */ +#endif + +#include <limits.h> +#include <libnet.h> +#include <libgen.h> +#include <clplumbing/timers.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/cl_log.h> + +#ifdef HAVE_LIBNET_1_0_API +# define LTYPE struct libnet_link_int + static u_char *mk_packet(u_int32_t ip, u_char *device, u_char *macaddr, u_char *broadcast, u_char *netmask, u_short arptype); + static int send_arp(struct libnet_link_int *l, u_char *device, u_char *buf); +#endif +#ifdef HAVE_LIBNET_1_1_API +# define LTYPE libnet_t + static libnet_t *mk_packet(libnet_t* lntag, u_int32_t ip, u_char *device, u_char macaddr[6], u_char *broadcast, u_char *netmask, u_short arptype); + int send_arp(libnet_t* lntag); +#endif + +#define PIDDIR HA_VARRUNDIR "/" PACKAGE +#define PIDFILE_BASE PIDDIR "/send_arp-" + +static char print_usage[]={ +"send_arp: sends out custom ARP packet.\n" +" usage: send_arp [-i repeatinterval-ms] [-r repeatcount] [-p pidfile] \\\n" +" device src_ip_addr src_hw_addr broadcast_ip_addr netmask\n" +"\n" +" where:\n" +" repeatinterval-ms: timing, in milliseconds of sending arp packets\n" +" For each ARP announcement requested, a pair of ARP packets is sent,\n" +" an ARP request, and an ARP reply. This is because some systems\n" +" ignore one or the other, and this combination gives the greatest\n" +" chance of success.\n" +"\n" +" Each time an ARP is sent, if another ARP will be sent then\n" +" the code sleeps for half of repeatinterval-ms.\n" +"\n" +" repeatcount: how many pairs of ARP packets to send.\n" +" See above for why pairs are sent\n" +"\n" +" pidfile: pid file to use\n" +"\n" +" device: network interface to use\n" +"\n" +" src_ip_addr: source ip address\n" +"\n" +" src_hw_addr: source hardware address.\n" +" If \"auto\" then the address of device\n" +"\n" +" broadcast_ip_addr: ignored\n" +"\n" +" netmask: ignored\n" +}; + +static const char * SENDARPNAME = "send_arp"; + +static void convert_macaddr (u_char *macaddr, u_char enet_src[6]); +static int get_hw_addr(char *device, u_char mac[6]); +int write_pid_file(const char *pidfilename); +int create_pid_directory(const char *piddirectory); + +#define AUTO_MAC_ADDR "auto" + + +#ifndef LIBNET_ERRBUF_SIZE +# define LIBNET_ERRBUF_SIZE 256 +#endif + + +/* + * For use logd, should keep identical with the same const variables defined + * in heartbeat.h. + */ +#define ENV_PREFIX "HA_" +#define KEY_LOGDAEMON "use_logd" + +static void +byebye(int nsig) +{ + (void)nsig; + /* Avoid an "error exit" log message if we're killed */ + exit(0); +} + + +int +main(int argc, char *argv[]) +{ + int c = -1; + char errbuf[LIBNET_ERRBUF_SIZE]; + char* device; + char* ipaddr; + char* macaddr; + char* broadcast; + char* netmask; + u_int32_t ip; + u_char src_mac[6]; + int repeatcount = 1; + int j; + long msinterval = 1000; + int flag; + char pidfilenamebuf[64]; + char *pidfilename = NULL; + struct sigaction act; + +#ifdef HAVE_LIBNET_1_0_API + LTYPE* l; + u_char *request, *reply; +#elif defined(HAVE_LIBNET_1_1_API) + LTYPE *request, *reply; +#endif + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_flags &= ~SA_RESTART; /* redundant - to stress syscalls should fail */ + act.sa_handler = byebye; + if ((sigemptyset(&act.sa_mask) < 0) || (sigaction(SIGTERM, &act, NULL) < 0)) { + cl_log(LOG_ERR, "Could not set handler for signal: %s", strerror(errno)); + return 1; + } + + cl_log_set_entity(SENDARPNAME); + cl_log_enable_stderr(TRUE); + cl_log_set_facility(LOG_USER); + cl_inherit_logging_environment(0); + + while ((flag = getopt(argc, argv, "i:r:p:")) != EOF) { + switch(flag) { + + case 'i': msinterval= atol(optarg); + break; + + case 'r': repeatcount= atoi(optarg); + break; + + case 'p': pidfilename= optarg; + break; + + default: fprintf(stderr, "%s\n\n", print_usage); + return 1; + break; + } + } + if (argc-optind != 5) { + fprintf(stderr, "%s\n\n", print_usage); + return 1; + } + + /* + * argv[optind+1] DEVICE dc0,eth0:0,hme0:0, + * argv[optind+2] IP 192.168.195.186 + * argv[optind+3] MAC ADDR 00a0cc34a878 + * argv[optind+4] BROADCAST 192.168.195.186 + * argv[optind+5] NETMASK ffffffffffff + */ + + device = argv[optind]; + ipaddr = argv[optind+1]; + macaddr = argv[optind+2]; + broadcast = argv[optind+3]; + netmask = argv[optind+4]; + + if (!pidfilename) { + if (snprintf(pidfilenamebuf, sizeof(pidfilenamebuf), "%s%s", + PIDFILE_BASE, ipaddr) >= + (int)sizeof(pidfilenamebuf)) { + cl_log(LOG_INFO, "Pid file truncated"); + return EXIT_FAILURE; + } + pidfilename = pidfilenamebuf; + } + + if(write_pid_file(pidfilename) < 0) { + return EXIT_FAILURE; + } + + if (!strcasecmp(macaddr, AUTO_MAC_ADDR)) { + if (get_hw_addr(device, src_mac) < 0) { + cl_log(LOG_ERR, "Cannot find mac address for %s", + device); + unlink(pidfilename); + return EXIT_FAILURE; + } + } + else { + convert_macaddr((unsigned char *)macaddr, src_mac); + } + +/* + * We need to send both a broadcast ARP request as well as the ARP response we + * were already sending. All the interesting research work for this fix was + * done by Masaki Hasegawa <masaki-h@pp.iij4u.or.jp> and his colleagues. + */ + +#if defined(HAVE_LIBNET_1_0_API) +#ifdef ON_DARWIN + if ((ip = libnet_name_resolve((unsigned char*)ipaddr, 1)) == -1UL) { +#else + if ((ip = libnet_name_resolve(ipaddr, 1)) == -1UL) { +#endif + cl_log(LOG_ERR, "Cannot resolve IP address [%s]", ipaddr); + unlink(pidfilename); + return EXIT_FAILURE; + } + + l = libnet_open_link_interface(device, errbuf); + if (!l) { + cl_log(LOG_ERR, "libnet_open_link_interface on %s: %s" + , device, errbuf); + unlink(pidfilename); + return EXIT_FAILURE; + } + request = mk_packet(ip, (unsigned char*)device, src_mac + , (unsigned char*)broadcast, (unsigned char*)netmask + , ARPOP_REQUEST); + reply = mk_packet(ip, (unsigned char*)device, src_mac + , (unsigned char *)broadcast + , (unsigned char *)netmask, ARPOP_REPLY); + if (!request || !reply) { + cl_log(LOG_ERR, "could not create packets"); + unlink(pidfilename); + return EXIT_FAILURE; + } + for (j=0; j < repeatcount; ++j) { + c = send_arp(l, (unsigned char*)device, request); + if (c < 0) { + break; + } + mssleep(msinterval / 2); + c = send_arp(l, (unsigned char*)device, reply); + if (c < 0) { + break; + } + if (j != repeatcount-1) { + mssleep(msinterval / 2); + } + } +#elif defined(HAVE_LIBNET_1_1_API) + if ((request=libnet_init(LIBNET_LINK, device, errbuf)) == NULL) { + cl_log(LOG_ERR, "libnet_init failure on %s: %s", device, errbuf); + unlink(pidfilename); + return EXIT_FAILURE; + } + if ((reply=libnet_init(LIBNET_LINK, device, errbuf)) == NULL) { + cl_log(LOG_ERR, "libnet_init failure on %s: %s", device, errbuf); + unlink(pidfilename); + return EXIT_FAILURE; + } + if ((signed)(ip = libnet_name2addr4(request, ipaddr, 1)) == -1) { + cl_log(LOG_ERR, "Cannot resolve IP address [%s]", ipaddr); + unlink(pidfilename); + return EXIT_FAILURE; + } + request = mk_packet(request, ip, (unsigned char*)device, src_mac + , (unsigned char*)broadcast, (unsigned char*)netmask + , ARPOP_REQUEST); + reply = mk_packet(reply, ip, (unsigned char*)device, src_mac + , (unsigned char *)broadcast + , (unsigned char *)netmask, ARPOP_REPLY); + if (!request || !reply) { + cl_log(LOG_ERR, "could not create packets"); + unlink(pidfilename); + return EXIT_FAILURE; + } + for (j=0; j < repeatcount; ++j) { + c = send_arp(request); + if (c < 0) { + break; + } + mssleep(msinterval / 2); + c = send_arp(reply); + if (c < 0) { + break; + } + if (j != repeatcount-1) { + mssleep(msinterval / 2); + } + } +#else +# error "Must have LIBNET API version defined." +#endif + + unlink(pidfilename); + return c < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + + +void +convert_macaddr (u_char *macaddr, u_char enet_src[6]) +{ + int i, pos; + u_char bits[3]; + + pos = 0; + for (i = 0; i < 6; i++) { + /* Inserted to allow old-style MAC addresses */ + if (*macaddr == ':') { + pos++; + } + bits[0] = macaddr[pos++]; + bits[1] = macaddr[pos++]; + bits[2] = '\0'; + + enet_src[i] = strtol((const char *)bits, (char **)NULL, 16); + } + +} + +#ifdef HAVE_LIBNET_1_0_API +int +get_hw_addr(char *device, u_char mac[6]) +{ + struct ether_addr *mac_address; + struct libnet_link_int *network; + char err_buf[LIBNET_ERRBUF_SIZE]; + + network = libnet_open_link_interface(device, err_buf); + if (!network) { + fprintf(stderr, "libnet_open_link_interface: %s\n", err_buf); + return -1; + } + + mac_address = libnet_get_hwaddr(network, device, err_buf); + if (!mac_address) { + fprintf(stderr, "libnet_get_hwaddr: %s\n", err_buf); + return -1; + } + + memcpy(mac, mac_address->ether_addr_octet, 6); + + return 0; +} +#endif + +#ifdef HAVE_LIBNET_1_1_API +int +get_hw_addr(char *device, u_char mac[6]) +{ + struct libnet_ether_addr *mac_address; + libnet_t *ln; + char err_buf[LIBNET_ERRBUF_SIZE]; + + ln = libnet_init(LIBNET_LINK, device, err_buf); + if (!ln) { + fprintf(stderr, "libnet_open_link_interface: %s\n", err_buf); + return -1; + } + + mac_address = libnet_get_hwaddr(ln); + if (!mac_address) { + fprintf(stderr, "libnet_get_hwaddr: %s\n", err_buf); + return -1; + } + + memcpy(mac, mac_address->ether_addr_octet, 6); + + return 0; +} +#endif + + +/* + * Notes on send_arp() behaviour. Horms, 15th June 2004 + * + * 1. Target Hardware Address + * (In the ARP portion of the packet) + * + * a) ARP Reply + * + * Set to the MAC address we want associated with the VIP, + * as per RFC2002 (4.6). + * + * Previously set to ff:ff:ff:ff:ff:ff + * + * b) ARP Request + * + * Set to 00:00:00:00:00:00. According to RFC2002 (4.6) + * this value is not used in an ARP request, so the value should + * not matter. However, I observed that typically (always?) this value + * is set to 00:00:00:00:00:00. It seems harmless enough to follow + * this trend. + * + * Previously set to ff:ff:ff:ff:ff:ff + * + * 2. Source Hardware Address + * (Ethernet Header, not in the ARP portion of the packet) + * + * Set to the MAC address of the interface that the packet is being + * sent to. Actually, due to the way that send_arp is called this would + * usually (always?) be the case anyway. Although this value should not + * really matter, it seems sensible to set the source address to where + * the packet is really coming from. The other obvious choice would be + * the MAC address that is being associated for the VIP. Which was the + * previous values. Again, these are typically the same thing. + * + * Previously set to MAC address being associated with the VIP + */ + +#ifdef HAVE_LIBNET_1_0_API +u_char * +mk_packet(u_int32_t ip, u_char *device, u_char *macaddr, u_char *broadcast, u_char *netmask, u_short arptype) +{ + u_char *buf; + u_char *target_mac; + u_char device_mac[6]; + u_char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + u_char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + + if (libnet_init_packet(LIBNET_ARP_H + LIBNET_ETH_H, &buf) == -1) { + cl_log(LOG_ERR, "libnet_init_packet memory:"); + return NULL; + } + + /* Convert ASCII Mac Address to 6 Hex Digits. */ + + /* Ethernet header */ + if (get_hw_addr((char*)device, device_mac) < 0) { + cl_log(LOG_ERR, "Cannot find mac address for %s", + device); + return NULL; + } + + if (libnet_build_ethernet(bcast_mac, device_mac, ETHERTYPE_ARP, NULL, 0 + , buf) == -1) { + cl_log(LOG_ERR, "libnet_build_ethernet failed:"); + libnet_destroy_packet(&buf); + return NULL; + } + + if (arptype == ARPOP_REQUEST) { + target_mac = zero_mac; + } + else if (arptype == ARPOP_REPLY) { + target_mac = macaddr; + } + else { + cl_log(LOG_ERR, "unknown arptype"); + return NULL; + } + + /* + * ARP header + */ + if (libnet_build_arp(ARPHRD_ETHER, /* Hardware address type */ + ETHERTYPE_IP, /* Protocol address type */ + 6, /* Hardware address length */ + 4, /* Protocol address length */ + arptype, /* ARP operation */ + macaddr, /* Source hardware addr */ + (u_char *)&ip, /* Target hardware addr */ + target_mac, /* Destination hw addr */ + (u_char *)&ip, /* Target protocol address */ + NULL, /* Payload */ + 0, /* Payload length */ + buf + LIBNET_ETH_H) == -1) { + cl_log(LOG_ERR, "libnet_build_arp failed:"); + libnet_destroy_packet(&buf); + return NULL; + } + return buf; +} +#endif /* HAVE_LIBNET_1_0_API */ + + + + +#ifdef HAVE_LIBNET_1_1_API +libnet_t* +mk_packet(libnet_t* lntag, u_int32_t ip, u_char *device, u_char macaddr[6], u_char *broadcast, u_char *netmask, u_short arptype) +{ + u_char *target_mac; + u_char device_mac[6]; + u_char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + u_char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + if (arptype == ARPOP_REQUEST) { + target_mac = zero_mac; + } + else if (arptype == ARPOP_REPLY) { + target_mac = macaddr; + } + else { + cl_log(LOG_ERR, "unkonwn arptype:"); + return NULL; + } + + /* + * ARP header + */ + if (libnet_build_arp(ARPHRD_ETHER, /* hardware address type */ + ETHERTYPE_IP, /* protocol address type */ + 6, /* Hardware address length */ + 4, /* protocol address length */ + arptype, /* ARP operation type */ + macaddr, /* sender Hardware address */ + (u_int8_t *)&ip, /* sender protocol address */ + target_mac, /* target hardware address */ + (u_int8_t *)&ip, /* target protocol address */ + NULL, /* Payload */ + 0, /* Length of payload */ + lntag, /* libnet context pointer */ + 0 /* packet id */ + ) == -1 ) { + cl_log(LOG_ERR, "libnet_build_arp failed:"); + return NULL; + } + + /* Ethernet header */ + if (get_hw_addr((char *)device, device_mac) < 0) { + cl_log(LOG_ERR, "Cannot find mac address for %s", + device); + return NULL; + } + + if (libnet_build_ethernet(bcast_mac, device_mac, ETHERTYPE_ARP, NULL, 0 + , lntag, 0) == -1 ) { + cl_log(LOG_ERR, "libnet_build_ethernet failed:"); + return NULL; + } + return lntag; +} +#endif /* HAVE_LIBNET_1_1_API */ + +#ifdef HAVE_LIBNET_1_0_API +int +send_arp(struct libnet_link_int *l, u_char *device, u_char *buf) +{ + int n; + + n = libnet_write_link_layer(l, (char*)device, buf, LIBNET_ARP_H + LIBNET_ETH_H); + if (n == -1) { + cl_log(LOG_ERR, "libnet_write_link_layer failed"); + } + return (n); +} +#endif /* HAVE_LIBNET_1_0_API */ + +#ifdef HAVE_LIBNET_1_1_API +int +send_arp(libnet_t* lntag) +{ + int n; + + n = libnet_write(lntag); + if (n == -1) { + cl_log(LOG_ERR, "libnet_write failed"); + } + return (n); +} +#endif /* HAVE_LIBNET_1_1_API */ + + +int +create_pid_directory(const char *pidfilename) +{ + int status; + struct stat stat_buf; + char *pidfilename_cpy; + char *dir; + + pidfilename_cpy = strdup(pidfilename); + if (!pidfilename_cpy) { + cl_log(LOG_INFO, "Memory allocation failure: %s\n", + strerror(errno)); + return -1; + } + + dir = dirname(pidfilename_cpy); + + status = stat(dir, &stat_buf); + + if (status < 0 && errno != ENOENT && errno != ENOTDIR) { + cl_log(LOG_INFO, "Could not stat pid-file directory " + "[%s]: %s", dir, strerror(errno)); + free(pidfilename_cpy); + return -1; + } + + if (status >= 0) { + if (S_ISDIR(stat_buf.st_mode)) { + return 0; + } + cl_log(LOG_INFO, "Pid-File directory exists but is " + "not a directory [%s]", dir); + free(pidfilename_cpy); + return -1; + } + + if (mkdir(dir, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP) < 0) { + /* Did someone else make it while we were trying ? */ + if (errno == EEXIST && stat(dir, &stat_buf) >= 0 + && S_ISDIR(stat_buf.st_mode)) { + return 0; + } + cl_log(LOG_INFO, "Could not create pid-file directory " + "[%s]: %s", dir, strerror(errno)); + free(pidfilename_cpy); + return -1; + } + + free(pidfilename_cpy); + return 0; +} + + +int +write_pid_file(const char *pidfilename) +{ + + int pidfilefd; + char pidbuf[11]; + unsigned long pid; + ssize_t bytes; + + if (*pidfilename != '/') { + cl_log(LOG_INFO, "Invalid pid-file name, must begin with a " + "'/' [%s]\n", pidfilename); + return -1; + } + + if (create_pid_directory(pidfilename) < 0) { + return -1; + } + + while (1) { + pidfilefd = open(pidfilename, O_CREAT|O_EXCL|O_RDWR, + S_IRUSR|S_IWUSR); + if (pidfilefd < 0) { + if (errno != EEXIST) { /* Old PID file */ + cl_log(LOG_INFO, "Could not open pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + } + else { + break; + } + + pidfilefd = open(pidfilename, O_RDONLY, S_IRUSR|S_IWUSR); + if (pidfilefd < 0) { + cl_log(LOG_INFO, "Could not open pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + + while (1) { + bytes = read(pidfilefd, pidbuf, sizeof(pidbuf)-1); + if (bytes < 0) { + if (errno == EINTR) { + continue; + } + cl_log(LOG_INFO, "Could not read pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + pidbuf[bytes] = '\0'; + break; + } + + if(unlink(pidfilename) < 0) { + cl_log(LOG_INFO, "Could not delete pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + + if (!bytes) { + cl_log(LOG_INFO, "Invalid pid in pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + + close(pidfilefd); + + pid = strtoul(pidbuf, NULL, 10); + if (pid == ULONG_MAX && errno == ERANGE) { + cl_log(LOG_INFO, "Invalid pid in pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + + if (kill(pid, SIGKILL) < 0 && errno != ESRCH) { + cl_log(LOG_INFO, "Error killing old process [%lu] " + "from pid-file [%s]: %s", pid, + pidfilename, strerror(errno)); + return -1; + } + + cl_log(LOG_INFO, "Killed old send_arp process [%lu]\n", + pid); + } + + if (snprintf(pidbuf, sizeof(pidbuf), "%u" + , getpid()) >= (int)sizeof(pidbuf)) { + cl_log(LOG_INFO, "Pid too long for buffer [%u]", getpid()); + return -1; + } + + while (1) { + bytes = write(pidfilefd, pidbuf, strlen(pidbuf)); + if (bytes != (ssize_t)strlen(pidbuf)) { + if (bytes < 0 && errno == EINTR) { + continue; + } + cl_log(LOG_INFO, "Could not write pid-file " + "[%s]: %s", pidfilename, + strerror(errno)); + return -1; + } + break; + } + + close(pidfilefd); + + return 0; +} + + diff --git a/tools/send_arp.linux.c b/tools/send_arp.linux.c new file mode 100644 index 0000000..2aa9b5d --- /dev/null +++ b/tools/send_arp.linux.c @@ -0,0 +1,1336 @@ +/* + * arping.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> + */ + +/* Andrew Beekhof, Lars Ellenberg: + * Based on arping from iputils, + * adapted to the command line conventions established by the libnet based + * send_arp tool as used by the IPaddr and IPaddr2 resource agents. + * The libnet based send_arp, and its command line argument convention, + * was first added to the heartbeat project by Matt Soffen. + * + * Latest "resync" with iputils as of: + * git://git.linux-ipv6.org/gitroot/iputils.git + * 511f8356e22615479c3cc16bca64d72d204f6df3 + * Fri Jul 24 10:48:47 2015 + * To get various bugfixes and support for infiniband and other link layer + * addresses which do not fit into plain "sockaddr_ll", and broadcast addresses + * that may be different from memset(,0xff,). + */ + +#include <stdlib.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <linux/sockios.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/signal.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#include <net/if_arp.h> +#include <sys/uio.h> +#ifdef CAPABILITIES +#include <sys/prctl.h> +#include <sys/capability.h> +#endif + +#include <netdb.h> +#include <unistd.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#ifdef USE_SYSFS +#include <sysfs/libsysfs.h> +struct sysfs_devattr_values; +#endif + +#ifndef WITHOUT_IFADDRS +#include <ifaddrs.h> +#endif + +#ifdef USE_IDN +#include <idna.h> +#include <locale.h> +#endif + +static char SNAPSHOT[] = "s20121221"; + +static void usage(void) __attribute__((noreturn)); + +#ifndef DEFAULT_DEVICE +#define DEFAULT_DEVICE "eth0" +#endif +#ifdef DEFAULT_DEVICE +# define DEFAULT_DEVICE_STR DEFAULT_DEVICE +#else +# define DEFAULT_DEVICE NULL +#endif + +struct device { + const char *name; + int ifindex; +#ifndef WITHOUT_IFADDRS + struct ifaddrs *ifa; +#endif +#ifdef USE_SYSFS + struct sysfs_devattr_values *sysfs; +#endif +}; + +int quit_on_reply=0; +struct device device = { + .name = DEFAULT_DEVICE, +}; +char *source; +struct in_addr src, dst; +char *target; +int dad, unsolicited, advert; +int quiet; +int count=-1; +int timeout; +int unicasting; +int s; +int broadcast_only; + +struct sockaddr_storage me; +struct sockaddr_storage he; + +struct timeval start, last; + +int sent, brd_sent; +int received, brd_recv, req_recv; + +#ifndef CAPABILITIES +static uid_t euid; +#endif + +#define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + \ + ((tv1).tv_usec-(tv2).tv_usec)/1000 ) + +#define OFFSET_OF(name,ele) ((size_t)&((name *)0)->ele) + +static socklen_t sll_len(size_t halen) +{ + socklen_t len = OFFSET_OF(struct sockaddr_ll, sll_addr) + halen; + if (len < sizeof(struct sockaddr_ll)) + len = sizeof(struct sockaddr_ll); + return len; +} + +#define SLL_LEN(hln) sll_len(hln) + +#if 1 /* hb_mode: always print hb_mode usage in this binary */ +static char print_usage[]={ +"send_arp: sends out custom ARP packet.\n" +" usage: send_arp [-i repeatinterval-ms] [-r repeatcount] [-p pidfile] \\\n" +" device src_ip_addr src_hw_addr broadcast_ip_addr netmask\n" +"\n" +" where:\n" +" repeatinterval-ms: ignored\n" +"\n" +" repeatcount: how many ARP packets to send.\n" +"\n" +" pidfile: pid file to use\n" +"\n" +" device: network interface to use\n" +"\n" +" src_ip_addr: source ip address\n" +"\n" +" src_hw_addr: only \"auto\" is supported.\n" +" If other specified, it will exit without sending any ARP packets.\n" +"\n" +" broadcast_ip_addr: ignored\n" +"\n" +" netmask: ignored\n" +"\n" +" Notes: Other options of iputils-arping may be accepted but it's not\n" +" intended to be supported in this binary.\n" +"\n" +}; + +void usage(void) +{ + fprintf(stderr, "%s\n", print_usage); + exit(2); +} + +#else /* hb_mode */ + +void usage(void) +{ + fprintf(stderr, + "Usage: arping [-fqbDUAV] [-c count] [-w timeout] [-I device] [-s source] destination\n" + " -f : quit on first reply\n" + " -q : be quiet\n" + " -b : keep broadcasting, don't go unicast\n" + " -D : duplicate address detection mode\n" + " -U : Unsolicited ARP mode, update your neighbours\n" + " -A : ARP answer mode, update your neighbours\n" + " -V : print version and exit\n" + " -c count : how many packets to send\n" + " -w timeout : how long to wait for a reply\n" + " -I device : which ethernet device to use" +#ifdef DEFAULT_DEVICE_STR + " (" DEFAULT_DEVICE_STR ")" +#endif + "\n" + " -s source : source ip address\n" + " destination : ask for what ip address\n" + ); + exit(2); +} +#endif /* hb_mode */ + +static void set_signal(int signo, void (*handler)(void)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = (void (*)(int))handler; + sa.sa_flags = SA_RESTART; + sigaction(signo, &sa, NULL); +} + +#ifdef CAPABILITIES +static const cap_value_t caps[] = { CAP_NET_RAW, }; +static cap_flag_value_t cap_raw = CAP_CLEAR; +#endif + +static void limit_capabilities(void) +{ +#ifdef CAPABILITIES + cap_t cap_p; + + cap_p = cap_get_proc(); + if (!cap_p) { + perror("arping: cap_get_proc"); + exit(-1); + } + + cap_get_flag(cap_p, CAP_NET_RAW, CAP_PERMITTED, &cap_raw); + + if (cap_raw != CAP_CLEAR) { + if (cap_clear(cap_p) < 0) { + perror("arping: cap_clear"); + exit(-1); + } + + cap_set_flag(cap_p, CAP_PERMITTED, 1, caps, CAP_SET); + + if (cap_set_proc(cap_p) < 0) { + perror("arping: cap_set_proc"); + if (errno != EPERM) + exit(-1); + } + } + + if (prctl(PR_SET_KEEPCAPS, 1) < 0) { + perror("arping: prctl"); + exit(-1); + } + + if (setuid(getuid()) < 0) { + perror("arping: setuid"); + exit(-1); + } + + if (prctl(PR_SET_KEEPCAPS, 0) < 0) { + perror("arping: prctl"); + exit(-1); + } + + cap_free(cap_p); +#else + euid = geteuid(); +#endif +} + +static int modify_capability_raw(int on) +{ +#ifdef CAPABILITIES + cap_t cap_p; + + if (cap_raw != CAP_SET) + return on ? -1 : 0; + + cap_p = cap_get_proc(); + if (!cap_p) { + perror("arping: cap_get_proc"); + return -1; + } + + cap_set_flag(cap_p, CAP_EFFECTIVE, 1, caps, on ? CAP_SET : CAP_CLEAR); + + if (cap_set_proc(cap_p) < 0) { + perror("arping: cap_set_proc"); + return -1; + } + + cap_free(cap_p); +#else + if (setuid(on ? euid : getuid())) { + perror("arping: setuid"); + return -1; + } +#endif + return 0; +} + +static int enable_capability_raw(void) +{ + return modify_capability_raw(1); +} + +static int disable_capability_raw(void) +{ + return modify_capability_raw(0); +} + +static void drop_capabilities(void) +{ +#ifdef CAPABILITIES + cap_t cap_p = cap_init(); + + if (!cap_p) { + perror("arping: cap_init"); + exit(-1); + } + + if (cap_set_proc(cap_p) < 0) { + perror("arping: cap_set_proc"); + exit(-1); + } + + cap_free(cap_p); +#else + if (setuid(getuid()) < 0) { + perror("arping: setuid"); + exit(-1); + } +#endif +} + +static int send_pack(int s, struct in_addr src, struct in_addr dst, + struct sockaddr_ll *ME, struct sockaddr_ll *HE) +{ + int err; + struct timeval now; + unsigned char buf[256]; + struct arphdr *ah = (struct arphdr*)buf; + unsigned char *p = (unsigned char *)(ah+1); + + ah->ar_hrd = htons(ME->sll_hatype); + if (ah->ar_hrd == htons(ARPHRD_FDDI)) + ah->ar_hrd = htons(ARPHRD_ETHER); + ah->ar_pro = htons(ETH_P_IP); + ah->ar_hln = ME->sll_halen; + ah->ar_pln = 4; + ah->ar_op = advert ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST); + + memcpy(p, &ME->sll_addr, ah->ar_hln); + p+=ME->sll_halen; + + memcpy(p, &src, 4); + p+=4; + + if (advert) + memcpy(p, &ME->sll_addr, ah->ar_hln); + else + memcpy(p, &HE->sll_addr, ah->ar_hln); + p+=ah->ar_hln; + + memcpy(p, &dst, 4); + p+=4; + + gettimeofday(&now, NULL); + err = sendto(s, buf, p-buf, 0, (struct sockaddr*)HE, SLL_LEN(ah->ar_hln)); + if (err == p-buf) { + last = now; + sent++; + if (!unicasting) + brd_sent++; + } + return err; +} + +static void finish(void) +{ + if (!quiet) { + printf("Sent %d probes (%d broadcast(s))\n", sent, brd_sent); + printf("Received %d response(s)", received); + if (brd_recv || req_recv) { + printf(" ("); + if (req_recv) + printf("%d request(s)", req_recv); + if (brd_recv) + printf("%s%d broadcast(s)", + req_recv ? ", " : "", + brd_recv); + printf(")"); + } + printf("\n"); + fflush(stdout); + } + fflush(stdout); + if (dad) + exit(!!received); + if (unsolicited) + exit(0); + exit(!received); +} + +static void catcher(void) +{ + struct timeval tv, tv_s, tv_o; + + gettimeofday(&tv, NULL); + + if (start.tv_sec==0) + start = tv; + + timersub(&tv, &start, &tv_s); + tv_o.tv_sec = timeout; + tv_o.tv_usec = 500 * 1000; + + if (count-- == 0 || (timeout && timercmp(&tv_s, &tv_o, >))) + finish(); + + timersub(&tv, &last, &tv_s); + tv_o.tv_sec = 0; + + if (last.tv_sec==0 || timercmp(&tv_s, &tv_o, >)) { + send_pack(s, src, dst, + (struct sockaddr_ll *)&me, (struct sockaddr_ll *)&he); + if (count == 0 && unsolicited) + finish(); + } + alarm(1); +} + +static void print_hex(unsigned char *p, int len) +{ + int i; + for (i=0; i<len; i++) { + printf("%02X", p[i]); + if (i != len-1) + printf(":"); + } +} + +static int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) +{ + struct timeval tv; + struct arphdr *ah = (struct arphdr*)buf; + unsigned char *p = (unsigned char *)(ah+1); + struct in_addr src_ip, dst_ip; + + gettimeofday(&tv, NULL); + + /* Filter out wild packets */ + if (FROM->sll_pkttype != PACKET_HOST && + FROM->sll_pkttype != PACKET_BROADCAST && + FROM->sll_pkttype != PACKET_MULTICAST) + return 0; + + /* Only these types are recognised */ + if (ah->ar_op != htons(ARPOP_REQUEST) && + ah->ar_op != htons(ARPOP_REPLY)) + return 0; + + /* ARPHRD check and this darned FDDI hack here :-( */ + if (ah->ar_hrd != htons(FROM->sll_hatype) && + (FROM->sll_hatype != ARPHRD_FDDI || ah->ar_hrd != htons(ARPHRD_ETHER))) + return 0; + + /* Protocol must be IP. */ + if (ah->ar_pro != htons(ETH_P_IP)) + return 0; + if (ah->ar_pln != 4) + return 0; + if (ah->ar_hln != ((struct sockaddr_ll *)&me)->sll_halen) + return 0; + if (len < sizeof(*ah) + 2*(4 + ah->ar_hln)) + return 0; + memcpy(&src_ip, p+ah->ar_hln, 4); + memcpy(&dst_ip, p+ah->ar_hln+4+ah->ar_hln, 4); + if (!dad) { + if (src_ip.s_addr != dst.s_addr) + return 0; + if (src.s_addr != dst_ip.s_addr) + return 0; + if (memcmp(p+ah->ar_hln+4, ((struct sockaddr_ll *)&me)->sll_addr, ah->ar_hln)) + return 0; + } else { + /* DAD packet was: + src_ip = 0 (or some src) + src_hw = ME + dst_ip = tested address + dst_hw = <unspec> + + We fail, if receive request/reply with: + src_ip = tested_address + src_hw != ME + if src_ip in request was not zero, check + also that it matches to dst_ip, otherwise + dst_ip/dst_hw do not matter. + */ + if (src_ip.s_addr != dst.s_addr) + return 0; + if (memcmp(p, ((struct sockaddr_ll *)&me)->sll_addr, ((struct sockaddr_ll *)&me)->sll_halen) == 0) + return 0; + if (src.s_addr && src.s_addr != dst_ip.s_addr) + return 0; + } + if (!quiet) { + int s_printed = 0; + printf("%s ", FROM->sll_pkttype==PACKET_HOST ? "Unicast" : "Broadcast"); + printf("%s from ", ah->ar_op == htons(ARPOP_REPLY) ? "reply" : "request"); + printf("%s [", inet_ntoa(src_ip)); + print_hex(p, ah->ar_hln); + printf("] "); + if (dst_ip.s_addr != src.s_addr) { + printf("for %s ", inet_ntoa(dst_ip)); + s_printed = 1; + } + if (memcmp(p+ah->ar_hln+4, ((struct sockaddr_ll *)&me)->sll_addr, ah->ar_hln)) { + if (!s_printed) + printf("for "); + printf("["); + print_hex(p+ah->ar_hln+4, ah->ar_hln); + printf("]"); + } + if (last.tv_sec) { + long usecs = (tv.tv_sec-last.tv_sec) * 1000000 + + tv.tv_usec-last.tv_usec; + long msecs = (usecs+500)/1000; + usecs -= msecs*1000 - 500; + printf(" %ld.%03ldms\n", msecs, usecs); + } else { + printf(" UNSOLICITED?\n"); + } + fflush(stdout); + } + received++; + if (FROM->sll_pkttype != PACKET_HOST) + brd_recv++; + if (ah->ar_op == htons(ARPOP_REQUEST)) + req_recv++; + if (quit_on_reply || (count == 0 && received == sent)) + finish(); + if(!broadcast_only) { + memcpy(((struct sockaddr_ll *)&he)->sll_addr, p, ((struct sockaddr_ll *)&me)->sll_halen); + unicasting=1; + } + return 1; +} + +#ifdef USE_SYSFS +union sysfs_devattr_value { + unsigned long ulong; + void *ptr; +}; + +enum { + SYSFS_DEVATTR_IFINDEX, + SYSFS_DEVATTR_FLAGS, + SYSFS_DEVATTR_ADDR_LEN, +#if 0 + SYSFS_DEVATTR_TYPE, + SYSFS_DEVATTR_ADDRESS, +#endif + SYSFS_DEVATTR_BROADCAST, + SYSFS_DEVATTR_NUM +}; + +struct sysfs_devattr_values +{ + char *ifname; + union sysfs_devattr_value value[SYSFS_DEVATTR_NUM]; +}; + +static int sysfs_devattr_ulong_dec(char *ptr, struct sysfs_devattr_values *v, unsigned idx); +static int sysfs_devattr_ulong_hex(char *ptr, struct sysfs_devattr_values *v, unsigned idx); +static int sysfs_devattr_macaddr(char *ptr, struct sysfs_devattr_values *v, unsigned idx); + +struct sysfs_devattrs { + const char *name; + int (*handler)(char *ptr, struct sysfs_devattr_values *v, unsigned int idx); + int free; +} sysfs_devattrs[SYSFS_DEVATTR_NUM] = { + [SYSFS_DEVATTR_IFINDEX] = { + .name = "ifindex", + .handler = sysfs_devattr_ulong_dec, + }, + [SYSFS_DEVATTR_ADDR_LEN] = { + .name = "addr_len", + .handler = sysfs_devattr_ulong_dec, + }, + [SYSFS_DEVATTR_FLAGS] = { + .name = "flags", + .handler = sysfs_devattr_ulong_hex, + }, +#if 0 + [SYSFS_DEVATTR_TYPE] = { + .name = "type", + .handler = sysfs_devattr_ulong_dec, + }, + [SYSFS_DEVATTR_ADDRESS] = { + .name = "address", + .handler = sysfs_devattr_macaddr, + .free = 1, + }, +#endif + [SYSFS_DEVATTR_BROADCAST] = { + .name = "broadcast", + .handler = sysfs_devattr_macaddr, + .free = 1, + }, +}; +#endif + +static void byebye(int nsig) +{ + /* Avoid an "error exit" log message if we're killed */ + nsig = 0; + exit(nsig); +} + +/* + * find_device() + * + * This function checks 1) if the device (if given) is okay for ARP, + * or 2) find fist appropriate device on the system. + * + * Return value: + * >0 : Succeeded, and appropriate device not found. + * device.ifindex remains 0. + * 0 : Succeeded, and approptiate device found. + * device.ifindex is set. + * <0 : Failed. Support not found, or other + * : system error. Try other method. + * + * If an appropriate device found, it is recorded inside the + * "device" variable for later reference. + * + * We have several implementations for this. + * by_ifaddrs(): requires getifaddr() in glibc, and rtnetlink in + * kernel. default and recommended for recent systems. + * by_sysfs(): requires libsysfs , and sysfs in kernel. + * by_ioctl(): unable to list devices without ipv4 address; this + * means, you need to supply the device name for + * DAD purpose. + */ +/* Common check for ifa->ifa_flags */ +static int check_ifflags(unsigned int ifflags, int fatal) +{ + if (!(ifflags & IFF_UP)) { + if (fatal) { + if (!quiet) + printf("Interface \"%s\" is down\n", device.name); + exit(2); + } + return -1; + } + if (ifflags & (IFF_NOARP | IFF_LOOPBACK)) { + if (fatal) { + if (!quiet) + printf("Interface \"%s\" is not ARPable\n", device.name); + exit(dad ? 0 : 2); + } + return -1; + } + return 0; +} + +static int find_device_by_ifaddrs(void) +{ +#ifndef WITHOUT_IFADDRS + int rc; + struct ifaddrs *ifa0, *ifa; + int count = 0; + + rc = getifaddrs(&ifa0); + if (rc) { + perror("getifaddrs"); + return -1; + } + + for (ifa = ifa0; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + if (ifa->ifa_addr->sa_family != AF_PACKET) + continue; + if (device.name && ifa->ifa_name && strcmp(ifa->ifa_name, device.name)) + continue; + + if (check_ifflags(ifa->ifa_flags, device.name != NULL) < 0) + continue; + + if (!((struct sockaddr_ll *)ifa->ifa_addr)->sll_halen) + continue; + if (!ifa->ifa_broadaddr) + continue; + + device.ifa = ifa; + + if (count++) + break; + } + + if (count == 1 && device.ifa) { + device.ifindex = if_nametoindex(device.ifa->ifa_name); + if (!device.ifindex) { + perror("arping: if_nametoindex"); + freeifaddrs(ifa0); + return -1; + } + device.name = device.ifa->ifa_name; + return 0; + } + return 1; +#else + return -1; +#endif +} + +#ifdef USE_SYSFS +static void sysfs_devattr_values_init(struct sysfs_devattr_values *v, int do_free) +{ + int i; + if (do_free) { + free(v->ifname); + for (i = 0; i < SYSFS_DEVATTR_NUM; i++) { + if (sysfs_devattrs[i].free) + free(v->value[i].ptr); + } + } + memset(v, 0, sizeof(*v)); +} + +static int sysfs_devattr_ulong(char *ptr, struct sysfs_devattr_values *v, unsigned int idx, + unsigned int base) +{ + unsigned long *p; + char *ep; + + if (!ptr || !v) + return -1; + + p = &v->value[idx].ulong; + errno = 0; + *p = strtoul(ptr, &ep, base); + if ((*ptr && isspace(*ptr & 0xff)) || errno || (*ep != '\0' && *ep != '\n')) + goto out; + + return 0; +out: + return -1; +} + +static int sysfs_devattr_ulong_dec(char *ptr, struct sysfs_devattr_values *v, unsigned int idx) +{ + int rc = sysfs_devattr_ulong(ptr, v, idx, 10); + return rc; +} + +static int sysfs_devattr_ulong_hex(char *ptr, struct sysfs_devattr_values *v, unsigned int idx) +{ + int rc = sysfs_devattr_ulong(ptr, v, idx, 16); + return rc; +} + +static int sysfs_devattr_macaddr(char *ptr, struct sysfs_devattr_values *v, unsigned int idx) +{ + unsigned char *m; + int i; + unsigned int addrlen; + + if (!ptr || !v) + return -1; + + addrlen = v->value[SYSFS_DEVATTR_ADDR_LEN].ulong; + m = malloc(addrlen); + + for (i = 0; i < addrlen; i++) { + if (i && *(ptr + i * 3 - 1) != ':') + goto out; + if (sscanf(ptr + i * 3, "%02hhx", &m[i]) != 1) + goto out; + } + + v->value[idx].ptr = m; + return 0; +out: + free(m); + return -1; +} +#endif + +static int find_device_by_sysfs(void) +{ + int rc = -1; +#ifdef USE_SYSFS + struct sysfs_class *cls_net; + struct dlist *dev_list; + struct sysfs_class_device *dev; + struct sysfs_attribute *dev_attr; + struct sysfs_devattr_values sysfs_devattr_values; + int count = 0; + + if (!device.sysfs) { + device.sysfs = malloc(sizeof(*device.sysfs)); + sysfs_devattr_values_init(device.sysfs, 0); + } + + cls_net = sysfs_open_class("net"); + if (!cls_net) { + perror("sysfs_open_class"); + return -1; + } + + dev_list = sysfs_get_class_devices(cls_net); + if (!dev_list) { + perror("sysfs_get_class_devices"); + goto out; + } + + sysfs_devattr_values_init(&sysfs_devattr_values, 0); + + dlist_for_each_data(dev_list, dev, struct sysfs_class_device) { + int i; + int rc = -1; + + if (device.name && strcmp(dev->name, device.name)) + goto do_next; + + sysfs_devattr_values_init(&sysfs_devattr_values, 1); + + for (i = 0; i < SYSFS_DEVATTR_NUM; i++) { + + dev_attr = sysfs_get_classdev_attr(dev, sysfs_devattrs[i].name); + if (!dev_attr) { + perror("sysfs_get_classdev_attr"); + rc = -1; + break; + } + if (sysfs_read_attribute(dev_attr)) { + perror("sysfs_read_attribute"); + rc = -1; + break; + } + rc = sysfs_devattrs[i].handler(dev_attr->value, &sysfs_devattr_values, i); + + if (rc < 0) + break; + } + + if (rc < 0) + goto do_next; + + if (check_ifflags(sysfs_devattr_values.value[SYSFS_DEVATTR_FLAGS].ulong, + device.name != NULL) < 0) + goto do_next; + + if (!sysfs_devattr_values.value[SYSFS_DEVATTR_ADDR_LEN].ulong) + goto do_next; + + if (device.sysfs->value[SYSFS_DEVATTR_IFINDEX].ulong) { + if (device.sysfs->value[SYSFS_DEVATTR_FLAGS].ulong & IFF_RUNNING) + goto do_next; + } + + sysfs_devattr_values.ifname = strdup(dev->name); + if (!sysfs_devattr_values.ifname) { + perror("malloc"); + goto out; + } + + sysfs_devattr_values_init(device.sysfs, 1); + memcpy(device.sysfs, &sysfs_devattr_values, sizeof(*device.sysfs)); + sysfs_devattr_values_init(&sysfs_devattr_values, 0); + + if (count++) + break; + + continue; +do_next: + sysfs_devattr_values_init(&sysfs_devattr_values, 1); + } + + if (count == 1) { + device.ifindex = device.sysfs->value[SYSFS_DEVATTR_IFINDEX].ulong; + device.name = device.sysfs->ifname; + } + rc = !device.ifindex; +out: + sysfs_close_class(cls_net); +#endif + return rc; +} + +static int check_device_by_ioctl(int s, struct ifreq *ifr) +{ + if (ioctl(s, SIOCGIFFLAGS, ifr) < 0) { + perror("ioctl(SIOCGIFINDEX"); + return -1; + } + + if (check_ifflags(ifr->ifr_flags, device.name != NULL) < 0) + return 1; + + if (ioctl(s, SIOCGIFINDEX, ifr) < 0) { + perror("ioctl(SIOCGIFINDEX"); + return -1; + } + + return 0; +} + +static int find_device_by_ioctl(void) +{ + int s; + struct ifreq *ifr0, *ifr, *ifr_end; + size_t ifrsize = sizeof(*ifr); + struct ifconf ifc; + static struct ifreq ifrbuf; + int count = 0; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + return -1; + } + + memset(&ifrbuf, 0, sizeof(ifrbuf)); + + if (device.name) { + strncpy(ifrbuf.ifr_name, device.name, sizeof(ifrbuf.ifr_name) - 1); + if (check_device_by_ioctl(s, &ifrbuf)) + goto out; + count++; + } else { + do { + int rc; + ifr0 = malloc(ifrsize); + if (!ifr0) { + perror("malloc"); + goto out; + } + + ifc.ifc_buf = (char *)ifr0; + ifc.ifc_len = ifrsize; + + rc = ioctl(s, SIOCGIFCONF, &ifc); + if (rc < 0) { + perror("ioctl(SIOCFIFCONF"); + goto out; + } + + if (ifc.ifc_len + sizeof(*ifr0) + sizeof(struct sockaddr_storage) - sizeof(struct sockaddr) <= ifrsize) + break; + ifrsize *= 2; + free(ifr0); + ifr0 = NULL; + } while(ifrsize < INT_MAX / 2); + + if (!ifr0) { + fprintf(stderr, "arping: too many interfaces!?\n"); + goto out; + } + + ifr_end = (struct ifreq *)(((char *)ifr0) + ifc.ifc_len - sizeof(*ifr0)); + for (ifr = ifr0; ifr <= ifr_end; ifr++) { + if (check_device_by_ioctl(s, &ifrbuf)) + continue; + memcpy(&ifrbuf.ifr_name, ifr->ifr_name, sizeof(ifrbuf.ifr_name)); + if (count++) + break; + } + } + + close(s); + + if (count == 1) { + device.ifindex = ifrbuf.ifr_ifindex; + device.name = ifrbuf.ifr_name; + } + return !device.ifindex; +out: + close(s); + return -1; +} + +static int find_device(void) +{ + int rc; + rc = find_device_by_ifaddrs(); + if (rc >= 0) + goto out; + rc = find_device_by_sysfs(); + if (rc >= 0) + goto out; + rc = find_device_by_ioctl(); +out: + return rc; +} + +/* + * set_device_broadcast() + * + * This fills the device "broadcast address" + * based on information found by find_device() funcion. + */ +static int set_device_broadcast_ifaddrs_one(struct device *device, unsigned char *ba, size_t balen, int fatal) +{ +#ifndef WITHOUT_IFADDRS + struct ifaddrs *ifa; + struct sockaddr_ll *sll; + + if (!device) + return -1; + + ifa = device->ifa; + if (!ifa) + return -1; + + sll = (struct sockaddr_ll *)ifa->ifa_broadaddr; + + if (sll->sll_halen != balen) { + if (fatal) { + if (!quiet) + printf("Address length does not match...\n"); + exit(2); + } + return -1; + } + memcpy(ba, sll->sll_addr, sll->sll_halen); + return 0; +#else + return -1; +#endif +} +static int set_device_broadcast_sysfs(struct device *device, unsigned char *ba, size_t balen) +{ +#ifdef USE_SYSFS + struct sysfs_devattr_values *v; + if (!device) + return -1; + v = device->sysfs; + if (!v) + return -1; + if (v->value[SYSFS_DEVATTR_ADDR_LEN].ulong != balen) + return -1; + memcpy(ba, v->value[SYSFS_DEVATTR_BROADCAST].ptr, balen); + return 0; +#else + return -1; +#endif +} + +static int set_device_broadcast_fallback(struct device *device, unsigned char *ba, size_t balen) +{ + if (!quiet) + fprintf(stderr, "WARNING: using default broadcast address.\n"); + memset(ba, -1, balen); + return 0; +} + +static void set_device_broadcast(struct device *dev, unsigned char *ba, size_t balen) +{ + if (!set_device_broadcast_ifaddrs_one(dev, ba, balen, 0)) + return; + if (!set_device_broadcast_sysfs(dev, ba, balen)) + return; + set_device_broadcast_fallback(dev, ba, balen); +} + +int +main(int argc, char **argv) +{ + int socket_errno; + int ch; + int hb_mode = 0; + + signal(SIGTERM, byebye); + signal(SIGPIPE, byebye); + + limit_capabilities(); + +#ifdef USE_IDN + setlocale(LC_ALL, ""); +#endif + + enable_capability_raw(); + + s = socket(PF_PACKET, SOCK_DGRAM, 0); + socket_errno = errno; + + disable_capability_raw(); + + while ((ch = getopt(argc, argv, "h?bfDUAqc:w:s:I:Vr:i:p:")) != EOF) { + switch(ch) { + case 'b': + broadcast_only=1; + break; + case 'D': + dad++; + quit_on_reply=1; + break; + case 'U': + unsolicited++; + break; + case 'A': + advert++; + unsolicited++; + break; + case 'q': + quiet++; + break; + case 'r': /* send_arp.libnet compatibility option */ + hb_mode = 1; + /* fall-through */ + case 'c': + count = atoi(optarg); + break; + case 'w': + timeout = atoi(optarg); + break; + case 'I': + device.name = optarg; + break; + case 'f': + quit_on_reply=1; + break; + case 's': + source = optarg; + break; + case 'V': + printf("send_arp utility, based on arping from iputils-%s\n", SNAPSHOT); + exit(0); + case 'p': + case 'i': + hb_mode = 1; + /* send_arp.libnet compatibility options, ignore */ + break; + case 'h': + case '?': + default: + usage(); + } + } + + if(hb_mode) { + /* send_arp.libnet compatibility mode */ + if (argc - optind != 5) { + usage(); + return 1; + } + /* + * argv[optind+1] DEVICE dc0,eth0:0,hme0:0, + * argv[optind+2] IP 192.168.195.186 + * argv[optind+3] MAC ADDR 00a0cc34a878 + * argv[optind+4] BROADCAST 192.168.195.186 + * argv[optind+5] NETMASK ffffffffffff + */ + + unsolicited = 1; + device.name = argv[optind]; + target = argv[optind+1]; + if (strcmp(argv[optind+2], "auto")) { + fprintf(stderr, "send_arp.linux: Gratuitous ARPs are not sent in the Cluster IP configuration\n"); + /* return success to suppress an error log by the RA */ + exit(0); + } + + } else { + argc -= optind; + argv += optind; + if (argc != 1) + usage(); + + target = *argv; + } + + if (device.name && !*device.name) + device.name = NULL; + + if (s < 0) { + errno = socket_errno; + perror("arping: socket"); + exit(2); + } + + if (find_device() < 0) + exit(2); + + if (!device.ifindex) { + if (device.name) { + fprintf(stderr, "arping: Device %s not available.\n", device.name); + exit(2); + } + fprintf(stderr, "arping: device (option -I) is required.\n"); + usage(); + } + + if (inet_aton(target, &dst) != 1) { + struct hostent *hp; + char *idn = target; +#ifdef USE_IDN + int rc; + + rc = idna_to_ascii_lz(target, &idn, 0); + + if (rc != IDNA_SUCCESS) { + fprintf(stderr, "arping: IDN encoding failed: %s\n", idna_strerror(rc)); + exit(2); + } +#endif + + hp = gethostbyname2(idn, AF_INET); + if (!hp) { + fprintf(stderr, "arping: unknown host %s\n", target); + exit(2); + } + +#ifdef USE_IDN + free(idn); +#endif + + memcpy(&dst, hp->h_addr, 4); + } + + if (source && inet_aton(source, &src) != 1) { + fprintf(stderr, "arping: invalid source %s\n", source); + exit(2); + } + + if (!dad && unsolicited && src.s_addr == 0) + src = dst; + + if (!dad || src.s_addr) { + struct sockaddr_in saddr; + int probe_fd = socket(AF_INET, SOCK_DGRAM, 0); + + if (probe_fd < 0) { + perror("socket"); + exit(2); + } + if (device.name) { + enable_capability_raw(); + + if (setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device.name, strlen(device.name)+1) == -1) + perror("WARNING: interface is ignored"); + + disable_capability_raw(); + } + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + if (src.s_addr) { + saddr.sin_addr = src; + if (bind(probe_fd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) { + perror("bind"); + exit(2); + } + } else if (!dad) { + int on = 1; + socklen_t alen = sizeof(saddr); + + saddr.sin_port = htons(1025); + saddr.sin_addr = dst; + + if (setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, (char*)&on, sizeof(on)) == -1) + perror("WARNING: setsockopt(SO_DONTROUTE)"); + if (connect(probe_fd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) { + perror("connect"); + exit(2); + } + if (getsockname(probe_fd, (struct sockaddr*)&saddr, &alen) == -1) { + perror("getsockname"); + exit(2); + } + src = saddr.sin_addr; + } + close(probe_fd); + }; + + ((struct sockaddr_ll *)&me)->sll_family = AF_PACKET; + ((struct sockaddr_ll *)&me)->sll_ifindex = device.ifindex; + ((struct sockaddr_ll *)&me)->sll_protocol = htons(ETH_P_ARP); + if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) { + perror("bind"); + exit(2); + } + + if (1) { + socklen_t alen = sizeof(me); + if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) { + perror("getsockname"); + exit(2); + } + } + if (((struct sockaddr_ll *)&me)->sll_halen == 0) { + if (!quiet) + printf("Interface \"%s\" is not ARPable (no ll address)\n", device.name); + exit(dad?0:2); + } + + he = me; + + set_device_broadcast(&device, ((struct sockaddr_ll *)&he)->sll_addr, + ((struct sockaddr_ll *)&he)->sll_halen); + + if (!quiet) { + printf("ARPING %s ", inet_ntoa(dst)); + printf("from %s %s\n", inet_ntoa(src), device.name ? : ""); + } + + if (!src.s_addr && !dad) { + fprintf(stderr, "arping: no source address in not-DAD mode\n"); + exit(2); + } + + drop_capabilities(); + + set_signal(SIGINT, finish); + set_signal(SIGALRM, catcher); + + catcher(); + + while(1) { + sigset_t sset, osset; + unsigned char packet[4096]; + struct sockaddr_storage from; + socklen_t alen = sizeof(from); + int cc; + + if ((cc = recvfrom(s, packet, sizeof(packet), 0, + (struct sockaddr *)&from, &alen)) < 0) { + perror("arping: recvfrom"); + continue; + } + + sigemptyset(&sset); + sigaddset(&sset, SIGALRM); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, &osset); + recv_pack(packet, cc, (struct sockaddr_ll *)&from); + sigprocmask(SIG_SETMASK, &osset, NULL); + } +} + + diff --git a/tools/sfex.h b/tools/sfex.h new file mode 100644 index 0000000..3795c29 --- /dev/null +++ b/tools/sfex.h @@ -0,0 +1,176 @@ +/*------------------------------------------------------------------------- + * + * Shared Disk File EXclusiveness Control Program(SF-EX) + * + * sfex.h --- Primary include file for SF-EX *.c files. + * + * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * $Id$ + * + *-------------------------------------------------------------------------*/ + +#ifndef SFEX_H +#define SFEX_H + +#include <clplumbing/cl_log.h> +#include <clplumbing/coredumps.h> +#include <clplumbing/realtime.h> + +#include <stdint.h> + +/* version, revision */ +/* These numbers are integer and, max number is 999. + If these numbers change, version numbers in the configure.ac + (AC_INIT, AM_INIT_AUTOMAKE) must change together. + */ +#define SFEX_VERSION 1 +#define SFEX_REVISION 3 + +#if 0 +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#endif + +/* for Linux >= 2.6, the alignment should be 512 + for Linux < 2.6, the alignment should be sysconf(_SC_PAGESIZE) + we default to _SC_PAGESIZE + */ +#define SFEX_ODIRECT_ALIGNMENT sysconf(_SC_PAGESIZE) + +/* + * sfex_controldata --- control data + * + * This is allocated the head of sfex mata-data area. + * + * magic number --- 4 bytes. This is fixed in {0x01, 0x1f, 0x71, 0x7f}. + * + * version number --- 4 bytes. This is printable integer number and + * range is from 0 to 999. This must be left-justify, null(0x00) padding, and + * make a last byte null. + * + * revision number --- 4 bytes. This is printable integer number and + * range is from 0 to 999. This must be left-justify, null(0x00) padding, and + * make a last byte null. + * + * blocksize --- 8bytes. This is printable integer number and range is from + * 512 to 9999999. This must be left-justify, null(0x00) padding, and make a + * last byte null. This is a size of control data and lock data(one lock data + * size when there are plural), and it is shown by number of bytes. + * For avoiding partial writing, usually block size is set 512 byte etc. + * If you use direct I/O(if you spacificate --enable-directio for configure + * script), note that this value is used for input and output buffer alignment. + * (In the Linux kernel 2.6, if this value is not 512 multibles, direct I/O + * does not work) + + * number of locks --- 4 bytes. This is printable integer number and range + * is from 1 to 999. This must be left-justify, null(0x00) padding, and make + * a last byte null. This is the number of locks following this control data. + * + * padding --- The size of this member depend on blocksize. It is adjusted so + * that the whole of the control data including this padding area becomes + * blocksize. The contents of padding area are all 0x00. + */ +typedef struct sfex_controldata { + char magic[4]; /* magic number */ + int version; /* version number */ + int revision; /* revision number */ + size_t blocksize; /* block size */ + int numlocks; /* number of locks */ +} sfex_controldata; + +typedef struct sfex_controldata_ondisk { + uint8_t magic[4]; + uint8_t version[4]; + uint8_t revision[4]; + uint8_t blocksize[8]; + uint8_t numlocks[4]; +} sfex_controldata_ondisk; + +/* + * sfex_lockdata --- lock data + * + * This data(number is sfex_controldata.numlocks) are allocated behind of + * sfex_controldata in the sfex meta-data area. The meaning of each member + * and the storage method to mata data area are following; + * + * lock status --- 1 byte. printable character. Content is either one of + * following; + * SFEX_STATUS_UNLOCK: It show the status that no node locks. + * SFEX_STATUS_LOCK: It show the status that nodename node is holding lock. + * (But there is an exception. Refer to explanation of "count" member.) + * + * increment counter --- 4 bytes. This is printable integer number and range + * is from 1 to 999. This must be left-justify, null(0x00) padding, and make + * a last byte null. The node holding a lock increments this counter + * periodically. If this counter does not increment for a certain period of + * time, we consider that the lock is invalid. If it overflow, return to 0. + * Initial value is 0. + * + * node name --- 256bytes. This is printable string. This must be left-justify, + * null(0x00) padding, and make a last byte null. This is node name that update + * lock data last. The node name must be same to get uname(2). Initial values + * are white spaces. + * + * padding --- The size of this member depend on blocksize. It is adjusted so + * that the whole of the control data including this padding area becomes + * blocksize. The contents of padding area are all 0x00. + */ +typedef struct sfex_lockdata { + char status; /* status of lock */ + int count; /* increment counter */ + char nodename[256]; /* node name */ +} sfex_lockdata; + +typedef struct sfex_lockdata_ondisk { + uint8_t status; + uint8_t count[4]; + uint8_t nodename[256]; +} sfex_lockdata_ondisk; + +/* character for lock status. This is used in sfex_lockdata.status */ +#define SFEX_STATUS_UNLOCK 'u' /* unlock */ +#define SFEX_STATUS_LOCK 'l' /* lock */ + +/* features of each member of control data and lock data */ +#define SFEX_MAGIC "SFEX" +#define SFEX_MIN_NUMLOCKS 1 +#define SFEX_MAX_NUMLOCKS 999 +#define SFEX_MIN_COUNT 0 +#define SFEX_MAX_COUNT 999 +#define SFEX_MAX_NODENAME (sizeof(((sfex_lockdata *)0)->nodename) - 1) + +/* update macro for increment counter */ +#define SFEX_NEXT_COUNT(c) (c >= SFEX_MAX_COUNT ? c - SFEX_MAX_COUNT : c + 1) + +/* extern variables */ +extern const char *progname; +extern char *nodename; +extern unsigned long sector_size; + +#endif /* SFEX_H */ diff --git a/tools/sfex_daemon.c b/tools/sfex_daemon.c new file mode 100644 index 0000000..9a761b8 --- /dev/null +++ b/tools/sfex_daemon.c @@ -0,0 +1,345 @@ +#include <config.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <sys/mman.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <syslog.h> +#include "sfex.h" +#include "sfex_lib.h" + +#if HAVE_GLUE_CONFIG_H +#include <glue_config.h> /* for HA_LOG_FACILITY */ +#endif + +static int sysrq_fd; +static int lock_index = 1; /* default 1st lock */ +static time_t collision_timeout = 1; /* default 1 sec */ +static time_t lock_timeout = 60; /* default 60 sec */ +time_t unlock_timeout = 60; +static time_t monitor_interval = 10; + +static sfex_controldata cdata; +static sfex_lockdata ldata; +static sfex_lockdata ldata_new; + +static const char *device; +const char *progname; +char *nodename; +static const char *rsc_id = "sfex"; + +static void usage(FILE *dist) { + fprintf(dist, "usage: %s [-i <index>] [-c <collision_timeout>] [-t <lock_timeout>] <device>\n", progname); +} + +static void acquire_lock(void) +{ + if (read_lockdata(&cdata, &ldata, lock_index) == -1) { + cl_log(LOG_ERR, "read_lockdata failed in acquire_lock\n"); + exit(EXIT_FAILURE); + } + + if ((ldata.status == SFEX_STATUS_LOCK) && (strncmp(nodename, (const char*)(ldata.nodename), sizeof(ldata.nodename)))) { + unsigned int t = lock_timeout; + while (t > 0) + t = sleep(t); + read_lockdata(&cdata, &ldata_new, lock_index); + if (ldata.count != ldata_new.count) { + cl_log(LOG_ERR, "can\'t acquire lock: the lock's already hold by some other node.\n"); + exit(2); + } + } + + /* The lock acquisition is possible because it was not updated. */ + ldata.status = SFEX_STATUS_LOCK; + ldata.count = SFEX_NEXT_COUNT(ldata.count); + strncpy((char*)(ldata.nodename), nodename, sizeof(ldata.nodename) - 1); + if (write_lockdata(&cdata, &ldata, lock_index) == -1) { + cl_log(LOG_ERR, "write_lockdata failed\n"); + exit(EXIT_FAILURE); + } + + /* detect the collision of lock */ + /* The collision occurs when two or more nodes do the reservation + processing of the lock at the same time. It waits for collision_timeout + seconds to detect this,and whether the superscription of lock data by + another node is done is checked. If the superscription was done by + another node, the lock acquisition with the own node is given up. + */ + { + unsigned int t = collision_timeout; + while (t > 0) + t = sleep(t); + if (read_lockdata(&cdata, &ldata_new, lock_index) == -1) { + cl_log(LOG_ERR, "read_lockdata failed in collision detection\n"); + } + if (strncmp((char*)(ldata.nodename), (const char*)(ldata_new.nodename), sizeof(ldata.nodename))) { + cl_log(LOG_ERR, "can\'t acquire lock: collision detected in the air.\n"); + exit(2); + } + } + + /* extension of lock */ + /* Validly time of the lock is extended. It is because of spending at + the collision_timeout seconds to detect the collision. */ + ldata.count = SFEX_NEXT_COUNT(ldata.count); + if (write_lockdata(&cdata, &ldata, lock_index) == -1) { + cl_log(LOG_ERR, "write_lockdata failed in extension of lock\n"); + exit(EXIT_FAILURE); + } + cl_log(LOG_INFO, "lock acquired\n"); +} + +static void error_todo (void) +{ + if (fork() == 0) { + cl_log(LOG_INFO, "Execute \"crm_resource -F -r %s --node %s\" command\n", rsc_id, nodename); + execl("/usr/sbin/crm_resource", "crm_resource", "-F", "-r", rsc_id, "--node", nodename, NULL); + } else { + exit(EXIT_FAILURE); + } +} + +static void failure_todo(void) +{ +#ifdef SFEX_TESTING + exit(EXIT_FAILURE); +#else + /*execl("/usr/sbin/crm_resource", "crm_resource", "-F", "-r", rsc_id, "--node", nodename, NULL); */ + int ret; + + cl_log(LOG_INFO, "Force reboot node %s\n", nodename); + ret = write(sysrq_fd, "b\n", 2); + if (ret == -1) { + cl_log(LOG_ERR, "%s\n", strerror(errno)); + } + close(sysrq_fd); + exit(EXIT_FAILURE); +#endif +} + +static void update_lock(void) +{ + /* read lock data */ + if (read_lockdata(&cdata, &ldata, lock_index) == -1) { + cl_log(LOG_ERR, "read_lockdata failed in update_lock\n"); + error_todo(); + exit(EXIT_FAILURE); + } + + /* check current lock status */ + /* if own node is not locking, lock update is failed */ + if (ldata.status != SFEX_STATUS_LOCK || strncmp((const char*)(ldata.nodename), nodename, sizeof(ldata.nodename))) { + cl_log(LOG_ERR, "can't update lock.\n"); + failure_todo(); + exit(EXIT_FAILURE); + } + + /* lock update */ + ldata.count = SFEX_NEXT_COUNT(ldata.count); + if (write_lockdata(&cdata, &ldata, lock_index) == -1) { + cl_log(LOG_ERR, "write_lockdata failed in update_lock\n"); + error_todo(); + exit(EXIT_FAILURE); + } +} + +static void release_lock(void) +{ + /* The only thing I care about in release_lock(), is to terminate the process */ + + /* read lock data */ + if (read_lockdata(&cdata, &ldata, lock_index) == -1) { + cl_log(LOG_ERR, "read_lockdata failed in release_lock\n"); + exit(EXIT_FAILURE); + } + + /* check current lock status */ + /* if own node is not locking, we judge that lock has been released already */ + if (ldata.status != SFEX_STATUS_LOCK || strncmp((const char*)(ldata.nodename), nodename, sizeof(ldata.nodename))) { + cl_log(LOG_ERR, "lock was already released.\n"); + exit(EXIT_FAILURE); + } + + /* lock release */ + ldata.status = SFEX_STATUS_UNLOCK; + if (write_lockdata(&cdata, &ldata, lock_index) == -1) { + /*FIXME: We are going to self-stop */ + cl_log(LOG_ERR, "write_lockdata failed in release_lock\n"); + exit(EXIT_FAILURE); + } + cl_log(LOG_INFO, "lock released\n"); +} + +static void quit_handler(int signo, siginfo_t *info, void *context) +{ + cl_log(LOG_INFO, "quit_handler called. now releasing lock\n"); + release_lock(); + cl_log(LOG_INFO, "Shutdown sfex_daemon with EXIT_SUCCESS\n"); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + + int ret; + + progname = get_progname(argv[0]); + nodename = get_nodename(); + + cl_log_set_entity(progname); + cl_log_set_facility(HA_LOG_FACILITY); + cl_inherit_logging_environment(0); + + /* read command line option */ + opterr = 0; + while (1) { + int c = getopt(argc, argv, "hi:c:t:m:n:r:"); + if (c == -1) + break; + switch (c) { + case 'h': /* help*/ + usage(stdout); + exit(EXIT_SUCCESS); + case 'i': /* -i <index> */ + { + unsigned long l = strtoul(optarg, NULL, 10); + if (l < SFEX_MIN_NUMLOCKS || l > SFEX_MAX_NUMLOCKS) { + cl_log(LOG_ERR, + "index %s is out of range or invalid. it must be integer value between %lu and %lu.\n", + optarg, + (unsigned long)SFEX_MIN_NUMLOCKS, + (unsigned long)SFEX_MAX_NUMLOCKS); + exit(4); + } + lock_index = l; + } + break; + case 'c': /* -c <collision_timeout> */ + { + unsigned long l = strtoul(optarg, NULL, 10); + if (l < 1 || l > INT_MAX) { + cl_log(LOG_ERR, + "collision_timeout %s is out of range or invalid. it must be integer value between %lu and %lu.\n", + optarg, + (unsigned long)1, + (unsigned long)INT_MAX); + exit(4); + } + collision_timeout = l; + } + break; + case 'm': /* -m <monitor_interval> */ + { + unsigned long l = strtoul(optarg, NULL, 10); + if (l < 1 || l > INT_MAX) { + cl_log(LOG_ERR, + "monitor_interval %s is out of range or invalid. it must be integer value between %lu and %lu.\n", + optarg, + (unsigned long)1, + (unsigned long)INT_MAX); + exit(4); + } + monitor_interval = l; + } + break; + case 't': /* -t <lock_timeout> */ + { + unsigned long l = strtoul(optarg, NULL, 10); + if (l < 1 || l > INT_MAX) { + cl_log(LOG_ERR, + "lock_timeout %s is out of range or invalid. it must be integer value between %lu and %lu.\n", + optarg, + (unsigned long)1, + (unsigned long)INT_MAX); + exit(4); + } + lock_timeout = l; + } + break; + case 'n': + { + free(nodename); + if (strlen(optarg) > SFEX_MAX_NODENAME) { + cl_log(LOG_ERR, "nodename %s is too long. must be less than %d byte.\n", + optarg, + (unsigned int)SFEX_MAX_NODENAME); + exit(EXIT_FAILURE); + } + nodename = strdup(optarg); + } + break; + case 'r': + { + rsc_id = strdup(optarg); + } + break; + case '?': /* error */ + usage(stderr); + exit(4); + } + } + /* check parameter except the option */ + if (optind >= argc) { + cl_log(LOG_ERR, "no device specified.\n"); + usage(stderr); + exit(EXIT_FAILURE); + } else if (optind + 1 < argc) { + cl_log(LOG_ERR, "too many arguments.\n"); + usage(stderr); + exit(EXIT_FAILURE); + } + device = argv[optind]; + + prepare_lock(device); +#if !SFEX_TESTING + sysrq_fd = open("/proc/sysrq-trigger", O_WRONLY); + if (sysrq_fd == -1) { + cl_log(LOG_ERR, "failed to open /proc/sysrq-trigger due to %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +#endif + + ret = lock_index_check(&cdata, lock_index); + if (ret == -1) + exit(EXIT_FAILURE); + + { + struct sigaction sig_act; + sigemptyset (&sig_act.sa_mask); + sig_act.sa_flags = SA_SIGINFO; + + sig_act.sa_sigaction = quit_handler; + ret = sigaction(SIGTERM, &sig_act, NULL); + if (ret == -1) { + cl_log(LOG_ERR, "sigaction failed\n"); + exit(EXIT_FAILURE); + } + } + + cl_log(LOG_INFO, "Starting SFeX Daemon...\n"); + + /* acquire lock first.*/ + acquire_lock(); + + if (daemon(0, 1) != 0) { + cl_perror("%s::%d: daemon() failed.", __FUNCTION__, __LINE__); + release_lock(); + exit(EXIT_FAILURE); + } + + cl_make_realtime(-1, -1, 128, 128); + + cl_log(LOG_INFO, "SFeX Daemon started.\n"); + while (1) { + sleep (monitor_interval); + update_lock(); + } +} diff --git a/tools/sfex_init.8 b/tools/sfex_init.8 new file mode 100644 index 0000000..bdcf8b9 --- /dev/null +++ b/tools/sfex_init.8 @@ -0,0 +1,19 @@ +.TH SFEX_INIT "8" "May 2010" "sfex_init 1.0.3" "System Administration Utilities" +.SH NAME +sfex_init \- Part of the Linux-HA project +.SH SYNOPSIS +.B sfex_init +[\fI-Lh\fR] \fR[\fI-n numlocks\fR]\fI device +.SH DESCRIPTION +Initialize Shared Disk File EXclusiveness Control Program (SF-EX) meta-data. +.SH OPTIONS +.TP +\fB\-n\fR numlocks +The number of storing lock data is specified by integer +of one or more. When you want to control two or more resources by one +meta-data, you set the value of two or more to numlocks. +Default is 1. +.TP +\fBdevice\fR +This is file path which stored meta-data. +It is usually expressed in "/dev/...", because it is partition on the shared disk. diff --git a/tools/sfex_init.c b/tools/sfex_init.c new file mode 100644 index 0000000..fed24dd --- /dev/null +++ b/tools/sfex_init.c @@ -0,0 +1,177 @@ +/*------------------------------------------------------------------------- + * + * Shared Disk File EXclusiveness Control Program(SF-EX) + * + * sfex_init.c --- Initialize SF-EX meta-data. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + * $Id$ + * + *------------------------------------------------------------------------- + * + * sfex_init [-b <blocksize>] [-n <numlocks>] <device> + * + * -b <blocksize> --- The size of the block is specified by the number of + * bytes. In general, to prevent a partial writing to the disk, the size + * of block is set to 512 bytes etc. + * Note a set value because this value is used also for the alignment + * adjustment in the input-output buffer in the program when direct I/O + * is used(When you specify --enable-directio option for configure script). + * (In Linux kernel 2.6, "direct I/O " does not work if this value is not + * a multiple of 512.) Default is 512 bytes. + * + * -n <numlocks> --- The number of storing lock data is specified by integer + * of one or more. When you want to control two or more resources by one + * meta-data, you set the value of two or more to numlocks. A necessary disk + * area for meta data are (blocksize*(1+numlocks))bytes. Default is 1. + * + * <device> --- This is file path which stored meta-data. It is usually + * expressed in "/dev/...", because it is partition on the shared disk. + * + * exit code --- 0 - Normal end. 3 - Error occurs while processing it. + * The content of the error is displayed into stderr. 4 - The mistake is + * found in the command line parameter. + * + *-------------------------------------------------------------------------*/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> + +#include "sfex.h" +#include "sfex_lib.h" + +const char *progname; +char *nodename; + +/* + * usage --- display command line syntax + * + * display command line syntax. By the purpose, it can specify destination + * stream. stdout or stderr is set usually. + * + * dist --- destination stream of the command line syntax, such as stderr. + * + * return value --- void + */ +static void usage(FILE *dist) { + fprintf(dist, "usage: %s [-n <numlocks>] <device>\n", progname); +} + +/* + * main --- main function + * + * entry point of sfex_init command. + * + * exit code --- 0 - Normal end. 3 - Error occurs while processing it. + * The content of the error is displayed into stderr. 4 - The mistake is + * found in the command line parameter. + */ +int +main(int argc, char *argv[]) { + sfex_controldata cdata; + sfex_lockdata ldata; + + /* command line parameter */ + int numlocks = 1; /* default 1 locks */ + const char *device; + + /* + * startup process + */ + + /* get a program name */ + progname = get_progname(argv[0]); + + /* enable the cl_log output from the sfex library */ + cl_log_set_entity(progname); + /* The cl_log is output only to the standard error output */ + cl_log_enable_stderr(TRUE); + + /* read command line option */ + opterr = 0; + while (1) { + int c = getopt(argc, argv, "hn:"); + if (c == -1) + break; + switch (c) { + case 'h': /* help */ + usage(stdout); + exit(0); + case 'n': /* -n <numlocks> */ + { + unsigned long l = strtoul(optarg, NULL, 10); + if (l < SFEX_MIN_NUMLOCKS || l > SFEX_MAX_NUMLOCKS) { + fprintf(stderr, + "%s: ERROR: numlocks %s is out of range or invalid. it must be integer value between %lu and %lu.\n", + progname, optarg, + (unsigned long)SFEX_MIN_NUMLOCKS, + (unsigned long)SFEX_MAX_NUMLOCKS); + exit(4); + } + numlocks = l; + } + break; + case '?': /* error */ + usage(stderr); + exit(4); + } + } + + /* check parameter except the option */ + if (optind >= argc) { + fprintf(stderr, "%s: ERROR: no device specified.\n", progname); + usage(stderr); + exit(4); + } else if (optind + 1 < argc) { + fprintf(stderr, "%s: ERROR: too many arguments.\n", progname); + usage(stderr); + exit(4); + } + device = argv[optind]; + + prepare_lock(device); + + /* main processes start */ + + /* get a node name */ + nodename = get_nodename(); + + /* create and control data and lock data */ + init_controldata(&cdata, sector_size, numlocks); + init_lockdata(&ldata); + + /* write out control data and lock data */ + write_controldata(&cdata); + { + int index; + for (index = 1; index <= numlocks; index++) + if (write_lockdata(&cdata, &ldata, index) == -1) { + fprintf(stderr, "%s: ERROR: cannot write lock data (index=%d).\n", + progname, index); + exit(3); + } + } + + exit(0); +} diff --git a/tools/sfex_lib.c b/tools/sfex_lib.c new file mode 100644 index 0000000..2d01602 --- /dev/null +++ b/tools/sfex_lib.c @@ -0,0 +1,474 @@ +/*------------------------------------------------------------------------- + * + * Shared Disk File EXclusiveness Control Program(SF-EX) + * + * sfex_lib.c --- Libraries for other SF-EX modules. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + * $Id$ + * + *-------------------------------------------------------------------------*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/utsname.h> +#include <sys/ioctl.h> +#include <syslog.h> +#include <linux/fs.h> + +#include "sfex.h" +#include "sfex_lib.h" + +static void *locked_mem; +static int dev_fd; +unsigned long sector_size = 0; + +int +prepare_lock (const char *device) +{ + int sec_tmp = 0; + + do { + dev_fd = open (device, O_RDWR | O_DIRECT | O_SYNC); + if (dev_fd == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + cl_log(LOG_ERR, "can't open device %s: %s\n", + device, strerror (errno)); + exit (3); + } + break; + } + while (1); + + ioctl(dev_fd, BLKSSZGET, &sec_tmp); + sector_size = (unsigned long)sec_tmp; + if (sector_size == 0) { + cl_log(LOG_ERR, "Get sector size failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (posix_memalign + ((void **) (&locked_mem), SFEX_ODIRECT_ALIGNMENT, + sector_size) != 0) { + cl_log(LOG_ERR, "Failed to allocate aligned memory\n"); + exit (3); + } + memset (locked_mem, 0, sector_size); + + return 0; +} + +/* + * get_progname --- a program name + * + * We get program name from directory path. It does not include delimiter + * characters. Return value is pointer that point string of program name. + * We assume delimiter is '/'. + */ +const char * +get_progname (const char *argv0) +{ + char *p; + + p = strrchr (argv0, '/'); + if (p) + return p + 1; + else + return argv0; +} + +/* + * get_nodename --- get a node name(hostname) + * + * We get a node name by using uname(2) and return pointer of it. + * The error checks are done in this function. The caller does not have + * to check return value. + */ +char * +get_nodename (void) +{ + struct utsname u; + char *n; + + if (uname (&u)) { + cl_log(LOG_ERR, "%s\n", strerror (errno)); + exit (3); + } + if (strlen (u.nodename) > SFEX_MAX_NODENAME) { + cl_log(LOG_ERR, + "nodename %s is too long. must be less than %lu byte.\n", + u.nodename, (unsigned long)SFEX_MAX_NODENAME); + exit (3); + } + n = strdup (&u.nodename[0]); + if (!n) { + cl_log(LOG_ERR, "%s\n", strerror (errno)); + exit (3); + } + return n; +} + +/* + * init_controldata --- initialize control data + * + * We initialize each member of sfex_controldata structure. + */ +void +init_controldata (sfex_controldata * cdata, size_t blocksize, int numlocks) +{ + memcpy (cdata->magic, SFEX_MAGIC, sizeof (cdata->magic)); + cdata->version = SFEX_VERSION; + cdata->revision = SFEX_REVISION; + cdata->blocksize = blocksize; + cdata->numlocks = numlocks; +} + +/* + * init_lockdata --- initialize lock data + * + * We initialize each member of sfex_lockdata structure. + */ +void +init_lockdata (sfex_lockdata * ldata) +{ + ldata->status = SFEX_STATUS_UNLOCK; + ldata->count = 0; + ldata->nodename[0] = 0; +} + +/* + * write_controldata --- write control data into file + * + * We write sfex_controldata struct into file. We open a file with + * synchronization mode and write out control data. + * + * cdata --- pointer of control data + * + * device --- name of target file + */ +void +write_controldata (const sfex_controldata * cdata) +{ + sfex_controldata_ondisk *block; + int fd; + + block = (sfex_controldata_ondisk *) (locked_mem); + + /* We write control data into the buffer with given format. */ + /* We write the offset value of each field of the control data directly. + * Because a point using this value is limited to two places, we do not + * use macro. If you change the following offset values, you must change + * values in the read_controldata() function. + */ + memset (block, 0, cdata->blocksize); + memcpy (block->magic, cdata->magic, sizeof (block->magic)); + snprintf ((char *) (block->version), sizeof (block->version), "%d", + cdata->version); + snprintf ((char *) (block->revision), sizeof (block->revision), "%d", + cdata->revision); + snprintf ((char *) (block->blocksize), sizeof (block->blocksize), "%u", + (unsigned)cdata->blocksize); + snprintf ((char *) (block->numlocks), sizeof (block->numlocks), "%d", + cdata->numlocks); + + fd = dev_fd; + if (lseek (fd, 0, SEEK_SET) == -1) { + cl_log(LOG_ERR, "can't seek file pointer: %s\n", + strerror (errno)); + exit (3); + } + + /* write buffer into a file */ + do { + ssize_t s = write (fd, block, cdata->blocksize); + if (s == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + cl_log(LOG_ERR, "can't write meta-data: %s\n", + strerror (errno)); + exit (3); + } + else + break; + } + while (1); +} + +/* + * write_lockdata --- write lock data into file + * + * We write sfex_lockdata into file and seek file pointer to the given + * position of lock data. + * + * cdata --- pointer for control data + * + * ldata --- pointer for lock data + * + * device --- file name for write + * + * index --- index number for lock data. 1 origine. + */ +int +write_lockdata (const sfex_controldata * cdata, const sfex_lockdata * ldata, + int index) +{ + sfex_lockdata_ondisk *block; + int fd; + + block = (sfex_lockdata_ondisk *) locked_mem; + /* We write lock data into buffer with given format */ + /* We write the offset value of each field of the control data directly. + * Because a point using this value is limited to two places, we do not + * use macro. If you chage the following offset values, you must change + * values in the read_lockdata() function. + */ + memset (block, 0, cdata->blocksize); + block->status = ldata->status; + snprintf ((char *) (block->count), sizeof (block->count), "%d", + ldata->count); + snprintf ((char *) (block->nodename), sizeof (block->nodename), "%s", + ldata->nodename); + + fd = dev_fd; + + /* seek a file pointer to given position */ + if (lseek (fd, cdata->blocksize * index, SEEK_SET) == -1) { + cl_log(LOG_ERR, "can't seek file pointer: %s\n", + strerror (errno)); + return -1; + } + + /* write buffer into file */ + do { + ssize_t s = write (fd, block, cdata->blocksize); + if (s == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + cl_log(LOG_ERR, "can't write meta-data: %s\n", + strerror (errno)); + return -1; + } + else if (s != cdata->blocksize) { + /* if writing atomically failed, this process is error */ + cl_log(LOG_ERR, "can't write meta-data atomically.\n"); + return -1; + } + break; + } + while (1); + return 0; +} + +/* + * read_controldata --- read control data from file + * + * read sfex_controldata structure from file. + * + * cdata --- pointer for control data + * + * device --- file name for reading + */ +int +read_controldata (sfex_controldata * cdata) +{ + sfex_controldata_ondisk *block; + + block = (sfex_controldata_ondisk *) (locked_mem); + + if (lseek (dev_fd, 0, SEEK_SET) == -1) { + cl_log(LOG_ERR, "can't seek file pointer: %s\n", + strerror (errno)); + return -1; + } + + /* read data from file */ + do { + ssize_t s = read (dev_fd, block, sector_size); + if (s == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + cl_log(LOG_ERR, + "can't read controldata meta-data: %s\n", + strerror (errno)); + return -1; + } + else + break; + } while (1); + + /* read control data from buffer */ + /* 1. check the magic number. 2. check null terminator of each field + 3. check the version number. 4. Unmuch of revision number is allowed */ + /* We write the offset value of each field of the control data directly. + * Because a point using this value is limited to two places, we do not + * use macro. If you chage the following offset values, you must change + * values in the write_controldata() function. + */ + memcpy (cdata->magic, block->magic, 4); + if (memcmp (cdata->magic, SFEX_MAGIC, sizeof (cdata->magic))) { + cl_log(LOG_ERR, "magic number mismatched. %c%c%c%c <-> %s\n", block->magic[0], block->magic[1], block->magic[2], block->magic[3], SFEX_MAGIC); + return -1; + } + if (block->version[sizeof (block->version)-1] + || block->revision[sizeof (block->revision)-1] + || block->blocksize[sizeof (block->blocksize)-1] + || block->numlocks[sizeof (block->numlocks)-1]) { + cl_log(LOG_ERR, "control data format error.\n"); + return -1; + } + cdata->version = atoi ((char *) (block->version)); + if (cdata->version != SFEX_VERSION) { + cl_log(LOG_ERR, + "version number mismatched. program is %d, data is %d.\n", + SFEX_VERSION, cdata->version); + return -1; + } + cdata->revision = atoi ((char *) (block->revision)); + cdata->blocksize = atoi ((char *) (block->blocksize)); + cdata->numlocks = atoi ((char *) (block->numlocks)); + + return 0; +} + +/* + * read_lockdata --- read lock data from file + * + * read sfex_lockdata from file and seek file pointer to head position of the + * file. + * + * cdata --- pointer for control data + * + * ldata --- pointer for lock data. Read lock data are stored into this + * pointed area. + * + * device --- file name of source file + * + * index --- index number. 1 origin. + */ +int +read_lockdata (const sfex_controldata * cdata, sfex_lockdata * ldata, + int index) +{ + sfex_lockdata_ondisk *block; + int fd; + + block = (sfex_lockdata_ondisk *) (locked_mem); + + fd = dev_fd; + + /* seek a file pointer to given position */ + if (lseek (fd, cdata->blocksize * index, SEEK_SET) == -1) { + cl_log(LOG_ERR, "can't seek file pointer: %s\n", + strerror (errno)); + return -1; + } + + /* read from file */ + do { + ssize_t s = read (fd, block, cdata->blocksize); + if (s == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + cl_log(LOG_ERR, "can't read lockdata meta-data: %s\n", + strerror (errno)); + return -1; + } + else if (s != cdata->blocksize) { + cl_log(LOG_ERR, "can't read meta-data atomically.\n"); + return -1; + } + break; + } + while (1); + + /* read control data form buffer */ + /* 1. check null terminator of each field 2. check the status */ + /* We write the offset value of each field of the control data directly. + * Because a point using this value is limited to two places, we do not + * use macro. If you chage the following offset values, you must change + * values in the write_lockdata() function. + */ + if (block->count[sizeof(block->count)-1] || block->nodename[sizeof(block->nodename)-1]) { + cl_log(LOG_ERR, "lock data format error.\n"); + return -1; + } + ldata->status = block->status; + if (ldata->status != SFEX_STATUS_UNLOCK + && ldata->status != SFEX_STATUS_LOCK) { + cl_log(LOG_ERR, "lock data format error.\n"); + return -1; + } + ldata->count = atoi ((char *) (block->count)); + strncpy ((char *) (ldata->nodename), (const char *) (block->nodename), sizeof(ldata->nodename)); + +#ifdef SFEX_DEBUG + cl_log(LOG_INFO, "status: %c\n", ldata->status); + cl_log(LOG_INFO, "count: %d\n", ldata->count); + cl_log(LOG_INFO, "nodename: %s\n", ldata->nodename); +#endif + return 0; +} + +/* + * lock_index_check --- check the value of index + * + * The lock_index_check function checks whether the value of index exceeds + * the number of lock data on the shared disk. + * + * cdata --- pointer for control data + * + * index --- index number + */ +int +lock_index_check(sfex_controldata * cdata, int index) +{ + if (read_controldata(cdata) == -1) { + cl_log(LOG_ERR, "%s\n", "read_controldata failed in lock_index_check"); + return -1; + } +#ifdef SFEX_DEBUG + cl_log(LOG_INFO, "version: %d\n", cdata->version); + cl_log(LOG_INFO, "revision: %d\n", cdata->revision); + cl_log(LOG_INFO, "blocksize: %d\n", cdata->blocksize); + cl_log(LOG_INFO, "numlocks: %d\n", cdata->numlocks); +#endif + + if (index > cdata->numlocks) { + cl_log(LOG_ERR, "index %d is too large. %d locks are stored.\n", + index, cdata->numlocks); + return -1; + } + + if (cdata->blocksize != sector_size) { + cl_log(LOG_ERR, "sector_size is not the same as the blocksize.\n"); + return -1; + } + return 0; +} diff --git a/tools/sfex_lib.h b/tools/sfex_lib.h new file mode 100644 index 0000000..f71281f --- /dev/null +++ b/tools/sfex_lib.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * Shared Disk File EXclusiveness Control Program(SF-EX) + * + * sfex_lib.h --- Prototypes for lib.c. + * + * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * $Id$ + * + *-------------------------------------------------------------------------*/ + +#ifndef LIB_H +#define LIB_H + +const char *get_progname(const char *argv0); +char *get_nodename(void); +void init_controldata(sfex_controldata *cdata, size_t blocksize, int numlocks); +void init_lockdata(sfex_lockdata *ldata); +void write_controldata(const sfex_controldata *cdata); +int write_lockdata(const sfex_controldata *cdata, const sfex_lockdata *ldata, int index); +int read_controldata(sfex_controldata *cdata); +int read_lockdata(const sfex_controldata *cdata, sfex_lockdata *ldata, int index); +int prepare_lock(const char *device); +int lock_index_check(sfex_controldata * cdata, int index); + +#endif /* LIB_H */ diff --git a/tools/sfex_stat.c b/tools/sfex_stat.c new file mode 100644 index 0000000..8be679c --- /dev/null +++ b/tools/sfex_stat.c @@ -0,0 +1,218 @@ +/*------------------------------------------------------------------------- + * + * Shared Disk File EXclusiveness Control Program(SF-EX) + * + * sfex_stat.c --- Display lock status. This is a part of the SF-EX. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + * $Id$ + * + *------------------------------------------------------------------------- + * + * sfex_stat [-i <index>] <device> + * + * -i <index> --- The index is number of the resource that display the lock. + * This number is specified by the integer of one or more. When two or more + * resources are exclusively controlled by one meta-data, this option is used. + * Default is 1. + * + * <device> --- This is file path which stored meta-data. It is usually + * expressed in "/dev/...", because it is partition on the shared disk. + * + * exit code --- 0 - Normal end. Own node is holding lock. 2 - Normal + * end. Own node does not hold a lock. 3 - Error occurs while processing + * it. The content of the error is displayed into stderr. 4 - The mistake + * is found in the command line parameter. + * + *-------------------------------------------------------------------------*/ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> +#include <limits.h> +#if HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include "sfex.h" +#include "sfex_lib.h" + +const char *progname; +char *nodename; + +void print_controldata(const sfex_controldata *cdata); +void print_lockdata(const sfex_lockdata *ldata, int index); + +/* + * print_controldata --- print sfex control data to the display + * + * print sfex_controldata to the display. + * + * cdata --- pointer for control data + */ +void +print_controldata(const sfex_controldata *cdata) +{ + printf("control data:\n"); + printf(" magic: 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + cdata->magic[0], cdata->magic[1], cdata->magic[2], cdata->magic[3]); + printf(" version: %d\n", cdata->version); + printf(" revision: %d\n", cdata->revision); + printf(" blocksize: %d\n", (int)cdata->blocksize); + printf(" numlocks: %d\n", cdata->numlocks); +} + +/* + * print_lockdata --- print sfex lock data to the display + * + * print sfex_lockdata to the display. + * + * ldata --- pointer for lock data + * + * index --- index number + */ +void +print_lockdata(const sfex_lockdata *ldata, int index) +{ + printf("lock data #%d:\n", index); + printf(" status: %s\n", ldata->status == SFEX_STATUS_UNLOCK ? "unlock" : "lock"); + printf(" count: %d\n", ldata->count); + printf(" nodename: %s\n",ldata->nodename); +} + +/* + * usage --- display command line syntax + * + * display command line syntax. By the purpose, it can specify destination + * stream. stdout or stderr is set usually. + * + * dist --- destination stream of the command line syntax, such as stderr. + * + * retrun value --- void + */ +static void usage(FILE *dist) { + fprintf(dist, "usage: %s [-i <index>] <device>\n", progname); +} + +/* + * main --- main function + * + * entry point of sfex_stat command. + * + * exit code --- 0 - Normal end. Own node is holding lock. 2 - Normal + * end. Own node dose not hold a lock. 3 - Error occurs while processing + * it. The content of the error is displayed into stderr. 4 - The mistake + * is found in the command line parameter. + */ +int +main(int argc, char *argv[]) { + sfex_controldata cdata; + sfex_lockdata ldata; + int ret = 0; + + /* command line parameter */ + int index = 1; /* default 1st lock */ + const char *device; + + /* + * startup process + */ + + /* get a program name */ + progname = get_progname(argv[0]); + + /* enable the cl_log output from the sfex library */ + cl_log_set_entity(progname); + /* The cl_log is output only to the standard error output */ + cl_log_enable_stderr(TRUE); + + /* read command line option */ + opterr = 0; + while (1) { + int c = getopt(argc, argv, "hi:"); + if (c == -1) + break; + switch (c) { + case 'h': /* help */ + usage(stdout); + exit(0); + case 'i': /* -i <index> */ + { + unsigned long l = strtoul(optarg, NULL, 10); + if (l < SFEX_MIN_NUMLOCKS || l > SFEX_MAX_NUMLOCKS) { + fprintf(stderr, + "%s: ERROR: index %s is out of range or invalid. it must be integer value between %lu and %lu.\n", + progname, optarg, + (unsigned long)SFEX_MIN_NUMLOCKS, + (unsigned long)SFEX_MAX_NUMLOCKS); + exit(4); + } + index = l; + } + break; + case '?': /* error */ + usage(stderr); + exit(4); + } + } + + /* check parameter except the option */ + if (optind >= argc) { + fprintf(stderr, "%s: ERROR: no device specified.\n", progname); + usage(stderr); + exit(4); + } else if (optind + 1 < argc) { + fprintf(stderr, "%s: ERROR: too many arguments.\n", progname); + usage(stderr); + exit(4); + } + device = argv[optind]; + + /* + * main processes start + */ + + /* get a node name */ + nodename = get_nodename(); + + prepare_lock(device); + + ret = lock_index_check(&cdata, index); + if (ret == -1) + exit(EXIT_FAILURE); + + /* read lock data */ + read_lockdata(&cdata, &ldata, index); + + /* display status */ + print_controldata(&cdata); + print_lockdata(&ldata, index); + + /* check current lock status */ + if (ldata.status != SFEX_STATUS_LOCK || strcmp(ldata.nodename, nodename)) { + fprintf(stdout, "status is UNLOCKED.\n"); + exit(2); + } else { + fprintf(stdout, "status is LOCKED.\n"); + exit(0); + } +} diff --git a/tools/storage_mon.c b/tools/storage_mon.c new file mode 100644 index 0000000..1aae29e --- /dev/null +++ b/tools/storage_mon.c @@ -0,0 +1,891 @@ +#include <stdio.h> +#include <getopt.h> +#include <stdlib.h> +#include <stdint.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#ifdef __FreeBSD__ +#include <sys/disk.h> +#endif +#include <config.h> +#include <glib.h> +#include <libgen.h> + +#include <qb/qbdefs.h> +#include <qb/qblog.h> +#include <qb/qbloop.h> +#include <qb/qbutil.h> +#include <qb/qbipcs.h> +#include <qb/qbipcc.h> + +#define MAX_DEVICES 25 +#define DEFAULT_TIMEOUT 10 +#define DEFAULT_INTERVAL 30 +#define DEFAULT_PIDFILE HA_VARRUNDIR "storage_mon.pid" +#define DEFAULT_ATTRNAME "#health-storage_mon" +#define SMON_GET_RESULT_COMMAND "get_check_value" +#define SMON_RESULT_OK "green" +#define SMON_RESULT_NG "red" +#define SMON_RESULT_COMMAND_ERROR "unknown command" +#define SMON_BUFF_1MEG 1048576 +#define SMON_MAX_IPCSNAME 256 +#define SMON_MAX_MSGSIZE 128 +#define SMON_MAX_RESP_SIZE 100 + +#define PRINT_STORAGE_MON_ERR(fmt, ...) if (!daemonize) { \ + fprintf(stderr, fmt"\n", __VA_ARGS__); \ + } else { \ + syslog(LOG_ERR, fmt, __VA_ARGS__); \ + } +#define PRINT_STORAGE_MON_ERR_NOARGS(str) if (!daemonize) { \ + fprintf(stderr, str"\n"); \ + } else { \ + syslog(LOG_ERR, str); \ + } + +#define PRINT_STORAGE_MON_INFO(fmt, ...) if (!daemonize) { \ + printf(fmt"\n", __VA_ARGS__); \ + } else { \ + syslog(LOG_INFO, fmt, __VA_ARGS__); \ + } + +struct storage_mon_timer_data { + int interval; +}; + +struct storage_mon_check_value_req { + struct qb_ipc_request_header hdr; + char message[SMON_MAX_MSGSIZE]; +}; + + +struct storage_mon_check_value_res { + struct qb_ipc_response_header hdr; + char message[SMON_MAX_MSGSIZE]; +}; + + +char *devices[MAX_DEVICES]; +int scores[MAX_DEVICES]; +size_t device_count = 0; +int timeout = DEFAULT_TIMEOUT; +int verbose = 0; +int inject_error_percent = 0; +const char *attrname = DEFAULT_ATTRNAME; +gboolean daemonize = FALSE; +int shutting_down = FALSE; +static qb_ipcs_service_t *ipcs; +int final_score = 0; +int response_final_score = 0; +pid_t test_forks[MAX_DEVICES]; +size_t finished_count = 0; +gboolean daemon_check_first_all_devices = FALSE; + +static qb_loop_t *storage_mon_poll_handle; +static qb_loop_timer_handle timer_handle; +static qb_loop_timer_handle expire_handle; +static struct storage_mon_timer_data timer_d; + +static int test_device_main(gpointer data); +static void wrap_test_device_main(void *data); + +static void usage(char *name, FILE *f) +{ + fprintf(f, "usage: %s [-hv] [-d <device>]... [-s <score>]... [-t <secs>]\n", name); + fprintf(f, " --device <dev> device to test, up to %d instances\n", MAX_DEVICES); + fprintf(f, " --score <n> score if device fails the test. Must match --device count\n"); + fprintf(f, " --timeout <n> max time to wait for a device test to come back. in seconds (default %d)\n", DEFAULT_TIMEOUT); + fprintf(f, " --inject-errors-percent <n> Generate EIO errors <n>%% of the time (for testing only)\n"); + fprintf(f, " --daemonize test run in daemons.\n"); + fprintf(f, " --client client connection to daemon. requires the attrname option.\n"); + fprintf(f, " --interval <n> interval to test. in seconds (default %d)(for daemonize only)\n", DEFAULT_INTERVAL); + fprintf(f, " --pidfile <path> file path to record pid (default %s)(for daemonize only)\n", DEFAULT_PIDFILE); + fprintf(f, " --attrname <attr> attribute name to update test result (default %s)(for daemonize/client only)\n", DEFAULT_ATTRNAME); + fprintf(f, " --verbose emit extra output to stdout\n"); + fprintf(f, " --help print this message\n"); +} + +/* Check one device */ +static void *test_device(const char *device, int verbose, int inject_error_percent) +{ + uint64_t devsize; + int flags = O_RDONLY | O_DIRECT; + int device_fd; + int res; + off_t seek_spot; + + if (verbose) { + printf("Testing device %s\n", device); + } + + device_fd = open(device, flags); + if (device_fd < 0) { + if (errno != EINVAL) { + PRINT_STORAGE_MON_ERR("Failed to open %s: %s", device, strerror(errno)); + exit(-1); + } + flags &= ~O_DIRECT; + device_fd = open(device, flags); + if (device_fd < 0) { + PRINT_STORAGE_MON_ERR("Failed to open %s: %s", device, strerror(errno)); + exit(-1); + } + } +#ifdef __FreeBSD__ + res = ioctl(device_fd, DIOCGMEDIASIZE, &devsize); +#else + res = ioctl(device_fd, BLKGETSIZE64, &devsize); +#endif + if (res < 0) { + PRINT_STORAGE_MON_ERR("Failed to get device size for %s: %s", device, strerror(errno)); + goto error; + } + if (verbose) { + PRINT_STORAGE_MON_INFO("%s: opened %s O_DIRECT, size=%zu", device, (flags & O_DIRECT)?"with":"without", devsize); + } + + /* Don't fret about real randomness */ + srand(time(NULL) + getpid()); + /* Pick a random place on the device - sector aligned */ + seek_spot = (rand() % (devsize-1024)) & 0xFFFFFFFFFFFFFE00; + res = lseek(device_fd, seek_spot, SEEK_SET); + if (res < 0) { + PRINT_STORAGE_MON_ERR("Failed to seek %s: %s", device, strerror(errno)); + goto error; + } + if (verbose) { + PRINT_STORAGE_MON_INFO("%s: reading from pos %ld", device, seek_spot); + } + + if (flags & O_DIRECT) { + int sec_size = 0; + void *buffer; + +#ifdef __FreeBSD__ + res = ioctl(device_fd, DIOCGSECTORSIZE, &sec_size); +#else + res = ioctl(device_fd, BLKSSZGET, &sec_size); +#endif + if (res < 0) { + PRINT_STORAGE_MON_ERR("Failed to get block device sector size for %s: %s", device, strerror(errno)); + goto error; + } + + if (posix_memalign(&buffer, sysconf(_SC_PAGESIZE), sec_size) != 0) { + PRINT_STORAGE_MON_ERR("Failed to allocate aligned memory: %s", strerror(errno)); + goto error; + } + res = read(device_fd, buffer, sec_size); + free(buffer); + if (res < 0) { + PRINT_STORAGE_MON_ERR("Failed to read %s: %s", device, strerror(errno)); + goto error; + } + if (res < sec_size) { + PRINT_STORAGE_MON_ERR("Failed to read %d bytes from %s, got %d", sec_size, device, res); + goto error; + } + } else { + char buffer[512]; + + res = read(device_fd, buffer, sizeof(buffer)); + if (res < 0) { + PRINT_STORAGE_MON_ERR("Failed to read %s: %s", device, strerror(errno)); + goto error; + } + if (res < (int)sizeof(buffer)) { + PRINT_STORAGE_MON_ERR("Failed to read %ld bytes from %s, got %d", sizeof(buffer), device, res); + goto error; + } + } + + /* Fake an error */ + if (inject_error_percent && ((rand() % 100) < inject_error_percent)) { + PRINT_STORAGE_MON_ERR_NOARGS("People, please fasten your seatbelts, injecting errors!"); + goto error; + } + res = close(device_fd); + if (res != 0) { + PRINT_STORAGE_MON_ERR("Failed to close %s: %s", device, strerror(errno)); + exit(-1); + } + + if (verbose) { + PRINT_STORAGE_MON_INFO("%s: done", device); + } + exit(0); + +error: + close(device_fd); + exit(-1); +} + +static gboolean is_child_runnning(void) +{ + size_t i; + + for (i=0; i<device_count; i++) { + if (test_forks[i] != 0) { + return TRUE; + } + } + return FALSE; +} + +static void stop_child(pid_t pid, int signal) +{ + errno = 0; + + if (kill(pid, signal) == 0) { + syslog(LOG_DEBUG, "Stopping chilg sent signal %d to process %lld", signal, (long long) pid); + } else { + syslog(LOG_ERR, "Could not stop child (process %lld) with signal %d: %s", (long long) pid, signal, strerror(errno)); + } +} + +static int32_t sigterm_handler(int num, void *data) +{ + size_t i; + shutting_down = TRUE; + + /* If there is an unfired timer, stop it. */ + qb_loop_timer_del(storage_mon_poll_handle, timer_handle); + + /* Send SIGTERM to non-terminating device monitoring processes. */ + if (is_child_runnning()) { + /* See if threads have finished */ + for (i=0; i<device_count; i++) { + if (test_forks[i] > 0 ) { + stop_child(test_forks[i], SIGTERM); + } + } + + } + + /* Set a timer for termination. */ + qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_HIGH, 0, NULL, wrap_test_device_main, &timer_handle); + + return 0; +} + +static size_t find_child_pid(int pid) +{ + size_t i; + + for (i=0; i<device_count; i++) { + if (test_forks[i] > 0 ) { + if (test_forks[i] == pid) { + return i; + } + } + } + return -1; +} + +static int32_t sigchld_handler(int32_t sig, void *data) +{ + pid_t pid; + size_t index; + int status; + + if (is_child_runnning()) { + while(1) { + pid = waitpid(-1, &status, WNOHANG); + if (pid > 0) { + if (WIFEXITED(status)) { + index = find_child_pid(pid); + if (index >= 0) { + /* If the expire timer is running, no timeout has occurred, */ + /* so add the final_score from the exit code of the terminated child process. */ + if (qb_loop_timer_is_running(storage_mon_poll_handle, expire_handle)) { + if (WEXITSTATUS(status) !=0) { + final_score += scores[index]; + + /* Update response values immediately in preparation for inquiries from clients. */ + response_final_score = final_score; + + /* Even in the first demon mode check, if there is an error device, clear */ + /* the flag to return the response to the client without waiting for all devices to finish. */ + daemon_check_first_all_devices = TRUE; + } + } + + finished_count++; + test_forks[index] = 0; + + } + } + } else { + break; + } + } + } + return 0; +} + +static void child_shutdown(int nsig) +{ + exit(1); +} + +static int write_pid_file(const char *pidfile) +{ + char *pid; + char *dir, *str = NULL; + int fd = -1; + int rc = -1; + int i, len; + + if (asprintf(&pid, "%jd", (intmax_t)getpid()) < 0) { + syslog(LOG_ERR, "Failed to allocate memory to store PID"); + pid = NULL; + goto done; + } + + str = strdup(pidfile); + if (str == NULL) { + syslog(LOG_ERR, "Failed to duplicate string ['%s']", pidfile); + goto done; + } + dir = dirname(str); + for (i = 1, len = strlen(dir); i < len; i++) { + if (dir[i] == '/') { + dir[i] = 0; + if ((mkdir(dir, 0640) < 0) && (errno != EEXIST)) { + syslog(LOG_ERR, "Failed to create directory %s: %s", dir, strerror(errno)); + goto done; + } + dir[i] = '/'; + } + } + if ((mkdir(dir, 0640) < 0) && (errno != EEXIST)) { + syslog(LOG_ERR, "Failed to create directory %s: %s", dir, strerror(errno)); + goto done; + } + + fd = open(pidfile, O_CREAT | O_WRONLY, 0640); + if (fd < 0) { + syslog(LOG_ERR, "Failed to open %s: %s", pidfile, strerror(errno)); + goto done; + } + + if (write(fd, pid, strlen(pid)) != strlen(pid)) { + syslog(LOG_ERR, "Failed to write '%s' to %s: %s", pid, pidfile, strerror(errno)); + goto done; + } + close(fd); + rc = 0; +done: + if (pid != NULL) { + free(pid); + } + if (str != NULL) { + free(str); + } + return rc; +} + +static void child_timeout_handler(void *data) +{ + size_t i; + + if (is_child_runnning()) { + for (i=0; i<device_count; i++) { + if (test_forks[i] > 0) { + /* If timeout occurs before SIGCHLD, add child process failure score to final_score. */ + final_score += scores[i]; + + /* Update response values immediately in preparation for inquiries from clients. */ + response_final_score = final_score; + + /* Even in the first demon mode check, if there is an error device, clear */ + /* the flag to return the response to the client without waiting for all devices to finish. */ + daemon_check_first_all_devices = TRUE; + } + } + } +} + +static void wrap_test_device_main(void *data) +{ + struct storage_mon_timer_data *timer_data = (struct storage_mon_timer_data*)data; + test_device_main((timer_data != NULL) ? &timer_data->interval : NULL); +} + +static int test_device_main(gpointer data) +{ + size_t i; + struct timespec ts; + time_t start_time; + gboolean device_check = TRUE; + + if (daemonize) { + if (shutting_down == TRUE) { + goto done; + } + + /* In the case of daemon mode, it is avoided that the timer is triggered and the number of */ + /* child processes increases while the device monitoring child process is not completed. */ + if (is_child_runnning()) { + device_check = FALSE; + } + + if (device_count == finished_count && device_check) { + /* Update the result value for the client response once all checks have completed. */ + response_final_score = final_score; + + if (!daemon_check_first_all_devices) { + daemon_check_first_all_devices = TRUE; + } + } + } + + if (device_check) { + /* Reset final_score, finished_count, test_forks[] */ + final_score = 0; + finished_count = 0; + + memset(test_forks, 0, sizeof(test_forks)); + for (i=0; i<device_count; i++) { + test_forks[i] = fork(); + if (test_forks[i] < 0) { + PRINT_STORAGE_MON_ERR("Error spawning fork for %s: %s\n", devices[i], strerror(errno)); + /* Just test the devices we have */ + break; + } + /* child */ + if (test_forks[i] == 0) { + if (daemonize) { + signal(SIGTERM, &child_shutdown); + } + test_device(devices[i], verbose, inject_error_percent); + } + } + + if (!daemonize) { + /* See if they have finished */ + clock_gettime(CLOCK_REALTIME, &ts); + start_time = ts.tv_sec; + + while ((finished_count < device_count) && ((start_time + timeout) > ts.tv_sec)) { + for (i=0; i<device_count; i++) { + int wstatus; + pid_t w; + + if (test_forks[i] > 0) { + w = waitpid(test_forks[i], &wstatus, WUNTRACED | WNOHANG | WCONTINUED); + if (w < 0) { + PRINT_STORAGE_MON_ERR("waitpid on %s failed: %s", devices[i], strerror(errno)); + return -1; + } + + if (w == test_forks[i]) { + if (WIFEXITED(wstatus)) { + if (WEXITSTATUS(wstatus) != 0) { + syslog(LOG_ERR, "Error reading from device %s", devices[i]); + final_score += scores[i]; + } + + finished_count++; + test_forks[i] = 0; + } + } + } + } + + usleep(100000); + + clock_gettime(CLOCK_REALTIME, &ts); + } + + /* See which threads have not finished */ + for (i=0; i<device_count; i++) { + if (test_forks[i] != 0) { + syslog(LOG_ERR, "Reading from device %s did not complete in %d seconds timeout", devices[i], timeout); + fprintf(stderr, "Thread for device %s did not complete in time\n", devices[i]); + final_score += scores[i]; + } + } + } else { + /* Run the child process timeout watch timer. */ + qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_MED, timeout * QB_TIME_NS_IN_SEC, NULL, child_timeout_handler, &expire_handle); + } + } + if (!daemonize) { + if (verbose) { + printf("Final score is %d\n", final_score); + } + return final_score; + } else { + if (data != NULL) { + /* Sets the device check to run on the next timer. */ + qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_MED, timer_d.interval * QB_TIME_NS_IN_SEC, &timer_d, wrap_test_device_main, &timer_handle); + } + return TRUE; + } +done: + qb_loop_stop(storage_mon_poll_handle); + return FALSE; +} + +static int32_t +storage_mon_job_add(enum qb_loop_priority p, void *data, qb_loop_job_dispatch_fn fn) +{ + return qb_loop_job_add(storage_mon_poll_handle, p, data, fn); +} + +static int32_t +storage_mon_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts, + void *data, qb_ipcs_dispatch_fn_t fn) +{ + return qb_loop_poll_add(storage_mon_poll_handle, p, fd, evts, data, fn); +} + +static int32_t +storage_mon_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts, + void *data, qb_ipcs_dispatch_fn_t fn) +{ + return qb_loop_poll_mod(storage_mon_poll_handle, p, fd, evts, data, fn); +} + +static int32_t +storage_mon_dispatch_del(int32_t fd) +{ + return qb_loop_poll_del(storage_mon_poll_handle, fd); +} + +static int32_t +storage_mon_ipcs_connection_accept_fn(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + return 0; +} + +static void +storage_mon_ipcs_connection_created_fn(qb_ipcs_connection_t *c) +{ + struct qb_ipcs_stats srv_stats; + + qb_ipcs_stats_get(ipcs, &srv_stats, QB_FALSE); + syslog(LOG_DEBUG, "Connection created (active:%d, closed:%d)", + srv_stats.active_connections, srv_stats.closed_connections); +} + +static void +storage_mon_ipcs_connection_destroyed_fn(qb_ipcs_connection_t *c) +{ + syslog(LOG_DEBUG, "Connection about to be freed"); +} + +static int32_t +storage_mon_ipcs_connection_closed_fn(qb_ipcs_connection_t *c) +{ + struct qb_ipcs_connection_stats stats; + struct qb_ipcs_stats srv_stats; + + qb_ipcs_stats_get(ipcs, &srv_stats, QB_FALSE); + qb_ipcs_connection_stats_get(c, &stats, QB_FALSE); + + syslog(LOG_DEBUG, + "Connection to pid:%d destroyed (active:%d, closed:%d)", + stats.client_pid, srv_stats.active_connections, + srv_stats.closed_connections); + + return 0; +} + +static int32_t +storage_mon_ipcs_msg_process_fn(qb_ipcs_connection_t *c, void *data, size_t size) +{ + struct storage_mon_check_value_req *request; + struct qb_ipc_response_header resps; + ssize_t res; + struct iovec iov[2]; + char resp[SMON_MAX_RESP_SIZE]; + int32_t rc; + int send_score = response_final_score; + + request = (struct storage_mon_check_value_req *)data; + syslog(LOG_DEBUG, "msg received (id:%d, size:%d, data:%s)", + request->hdr.id, request->hdr.size, request->message); + + if (strcmp(request->message, SMON_GET_RESULT_COMMAND) != 0) { + syslog(LOG_DEBUG, "request command is unknown."); + send_score = -1; + } else if (!daemon_check_first_all_devices) { + send_score = -2; + } + + resps.size = sizeof(struct qb_ipc_response_header); + resps.id = 13; + resps.error = 0; + + rc = snprintf(resp, SMON_MAX_RESP_SIZE, "%d", send_score) + 1; + iov[0].iov_len = sizeof(resps); + iov[0].iov_base = &resps; + iov[1].iov_len = rc; + iov[1].iov_base = resp; + resps.size += rc; + + res = qb_ipcs_response_sendv(c, iov, 2); + if (res < 0) { + errno = -res; + syslog(LOG_ERR, "qb_ipcs_response_send : errno = %d", errno); + } + return 0; +} + +static int32_t +storage_mon_client(void) +{ + struct storage_mon_check_value_req request; + struct storage_mon_check_value_res response; + qb_ipcc_connection_t *conn; + char ipcs_name[SMON_MAX_IPCSNAME]; + int32_t rc; + + + snprintf(ipcs_name, SMON_MAX_IPCSNAME, "storage_mon_%s", attrname); + conn = qb_ipcc_connect(ipcs_name, 0); + if (conn == NULL) { + syslog(LOG_ERR, "qb_ipcc_connect error\n"); + return(-1); + } + + snprintf(request.message, SMON_MAX_MSGSIZE, "%s", SMON_GET_RESULT_COMMAND); + request.hdr.id = 0; + request.hdr.size = sizeof(struct storage_mon_check_value_req); + rc = qb_ipcc_send(conn, &request, request.hdr.size); + if (rc < 0) { + syslog(LOG_ERR, "qb_ipcc_send error : %d\n", rc); + return(-1); + } + if (rc > 0) { + rc = qb_ipcc_recv(conn, &response, sizeof(response), -1); + if (rc < 0) { + syslog(LOG_ERR, "qb_ipcc_recv error : %d\n", rc); + return(-1); + } + } + + qb_ipcc_disconnect(conn); + + /* Set score to result */ + /* 0 : Normal. */ + /* greater than 0 : monitoring error. */ + /* -1 : communication system error. */ + /* -2 : Not all checks completed for first device in daemon mode. */ + rc = atoi(response.message); + + syslog(LOG_DEBUG, "daemon response[%d]: %s \n", response.hdr.id, response.message); + + return(rc); +} + +static int32_t +storage_mon_daemon(int interval, const char *pidfile) +{ + int32_t rc; + char ipcs_name[SMON_MAX_IPCSNAME]; + + struct qb_ipcs_service_handlers service_handle = { + .connection_accept = storage_mon_ipcs_connection_accept_fn, + .connection_created = storage_mon_ipcs_connection_created_fn, + .msg_process = storage_mon_ipcs_msg_process_fn, + .connection_destroyed = storage_mon_ipcs_connection_destroyed_fn, + .connection_closed = storage_mon_ipcs_connection_closed_fn, + }; + + struct qb_ipcs_poll_handlers poll_handle = { + .job_add = storage_mon_job_add, + .dispatch_add = storage_mon_dispatch_add, + .dispatch_mod = storage_mon_dispatch_mod, + .dispatch_del = storage_mon_dispatch_del, + }; + + if (daemon(0, 0) < 0) { + syslog(LOG_ERR, "Failed to daemonize: %s", strerror(errno)); + return -1; + } + + umask(S_IWGRP | S_IWOTH | S_IROTH); + + if (write_pid_file(pidfile) < 0) { + return -1; + } + + snprintf(ipcs_name, SMON_MAX_IPCSNAME, "storage_mon_%s", attrname); + ipcs = qb_ipcs_create(ipcs_name, 0, QB_IPC_NATIVE, &service_handle); + if (ipcs == 0) { + syslog(LOG_ERR, "qb_ipcs_create"); + return -1; + } + + qb_ipcs_enforce_buffer_size(ipcs, SMON_BUFF_1MEG); + + storage_mon_poll_handle = qb_loop_create(); + + qb_ipcs_poll_handlers_set(ipcs, &poll_handle); + rc = qb_ipcs_run(ipcs); + if (rc != 0) { + errno = -rc; + syslog(LOG_ERR, "qb_ipcs_run"); + return -1; + } + + qb_loop_signal_add(storage_mon_poll_handle, QB_LOOP_HIGH, + SIGTERM, NULL, sigterm_handler, NULL); + + qb_loop_signal_add(storage_mon_poll_handle, QB_LOOP_MED, + SIGCHLD, NULL, sigchld_handler, NULL); + + timer_d.interval = interval; + qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_MED, 0, &timer_d, wrap_test_device_main, &timer_handle); + + qb_loop_run(storage_mon_poll_handle); + qb_loop_destroy(storage_mon_poll_handle); + + unlink(pidfile); + + return 0; +} + +int main(int argc, char *argv[]) +{ + size_t score_count = 0; + int opt, option_index; + int interval = DEFAULT_INTERVAL; + const char *pidfile = DEFAULT_PIDFILE; + gboolean client = FALSE; + struct option long_options[] = { + {"timeout", required_argument, 0, 't' }, + {"device", required_argument, 0, 'd' }, + {"score", required_argument, 0, 's' }, + {"inject-errors-percent", required_argument, 0, 0 }, + {"daemonize", no_argument, 0, 0 }, + {"client", no_argument, 0, 0 }, + {"interval", required_argument, 0, 'i' }, + {"pidfile", required_argument, 0, 'p' }, + {"attrname", required_argument, 0, 'a' }, + {"verbose", no_argument, 0, 'v' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; + + while ( (opt = getopt_long(argc, argv, "hvt:d:s:i:p:a:", + long_options, &option_index)) != -1 ) { + switch (opt) { + case 0: /* Long-only options */ + if (strcmp(long_options[option_index].name, "inject-errors-percent") == 0) { + inject_error_percent = atoi(optarg); + if (inject_error_percent < 1 || inject_error_percent > 100) { + fprintf(stderr, "inject_error_percent should be between 1 and 100\n"); + return -1; + } + } + if (strcmp(long_options[option_index].name, "daemonize") == 0) { + daemonize = TRUE; + } + if (strcmp(long_options[option_index].name, "client") == 0) { + client = TRUE; + } + if (daemonize && client) { + fprintf(stderr,"The daemonize option and client option cannot be specified at the same time."); + return -1; + } + break; + case 'd': + if (device_count < MAX_DEVICES) { + devices[device_count++] = strdup(optarg); + } else { + fprintf(stderr, "too many devices, max is %d\n", MAX_DEVICES); + return -1; + } + break; + case 's': + if (score_count < MAX_DEVICES) { + int score = atoi(optarg); + if (score < 1 || score > 10) { + fprintf(stderr, "Score must be between 1 and 10 inclusive\n"); + return -1; + } + scores[score_count++] = score; + } else { + fprintf(stderr, "too many scores, max is %d\n", MAX_DEVICES); + return -1; + } + break; + case 'v': + verbose++; + break; + case 't': + timeout = atoi(optarg); + if (timeout < 1) { + fprintf(stderr, "invalid timeout %d. Min 1, recommended %d (default)\n", timeout, DEFAULT_TIMEOUT); + return -1; + } + break; + case 'h': + usage(argv[0], stdout); + return 0; + break; + case 'i': + interval = atoi(optarg); + if (interval < 1) { + fprintf(stderr, "invalid interval %d. Min 1, default is %d\n", interval, DEFAULT_INTERVAL); + return -1; + } + break; + case 'p': + pidfile = strdup(optarg); + if (pidfile == NULL) { + fprintf(stderr, "Failed to duplicate string ['%s']\n", optarg); + return -1; + } + break; + case 'a': + attrname = strdup(optarg); + if (attrname == NULL) { + fprintf(stderr, "Failed to duplicate string ['%s']\n", optarg); + return -1; + } + break; + default: + usage(argv[0], stderr); + return -1; + break; + } + + } + + if (client) { + return(storage_mon_client()); + } + + if (device_count == 0) { + fprintf(stderr, "No devices to test, use the -d or --device argument\n"); + return -1; + } + + if (device_count != score_count) { + fprintf(stderr, "There must be the same number of devices and scores\n"); + return -1; + } + + openlog("storage_mon", 0, LOG_DAEMON); + + if (!daemonize) { + final_score = test_device_main(NULL); + } else { + return(storage_mon_daemon(interval, pidfile)); + } + return final_score; +} diff --git a/tools/test-findif.sh b/tools/test-findif.sh new file mode 100755 index 0000000..1eb9d5d --- /dev/null +++ b/tools/test-findif.sh @@ -0,0 +1,352 @@ +#!/bin/sh + +# Easy peasy test for findif, configuration via direct edit... +# Jan Pokorny <jpokorny@redhat.com> + +export LC_ALL=C +test -n "$BASH_VERSION" && set -o posix +set -u +COLOR=0 +if [ -t 1 ] && echo -e foo | grep -Eqv "^-e"; then + COLOR=1 +else + COLOR=0 +fi +ok () { + [ $COLOR -eq 1 ] \ + && echo -en "[\033[32m OK \033[0m]" \ + || echo -n "[ OK ]" + echo " $*" +} +fail () { + [ $COLOR -eq 1 ] \ + && echo -en "[\033[31mFAIL\033[0m]" \ + || echo -n "[FAIL]" + echo " $*" +} +info () { + [ $COLOR -eq 1 ] \ + && echo -e "\033[34m$@\033[0m" \ + || echo "$*" +} +die() { echo "$*"; exit 255; } +warn() { echo "> $*"; } +mimic_return () { return $1; } +verbosely () { echo "$1..."; $1; } + +HERE="$(dirname "$0")" +. "${HERE}/../heartbeat/ocf-returncodes" # obtain OCF_ERR_CONFIGURED et al. + +# +# soft-config +# + +: ${DEBUG_IN:=0} +: ${DEBUG_OUT:=0} + +: "${PRG:=${HERE}/findif}" +: "${SCRIPT:=${HERE}/../heartbeat/findif.sh}" + +: ${LO_IF:=lo} +: ${LO_IP4:=127.0.0.1} +: ${LO_NM4:=8} +: ${LO_BC4:=127.255.255.255} +: ${LO_IP6:=::1} + +: ${DUMMY_IF:=dummy0} +# carefully selected to fit TEST-NET-2 +# http://en.wikipedia.org/wiki/Reserved_IP_addresses#Reserved_IPv4_addresses +: ${DUMMY_IP4:=198.51.100.1} +: ${DUMMY_NM4:=24} +: ${DUMMY_BC4:=198.51.100.255} + +: ${DUMMY_IP6:=2001:db8::1} +: ${DUMMY_NM6:=32} +: ${DUMMY_BC6:=198.51.100.255} + +# +# hard-wired +# + +PRG_CMD="${PRG} -C" +SCRIPT_CMD="$(head -n1 "${SCRIPT}" | sed 's|#!||') \ + -c \"export OCF_FUNCTIONS_DIR=$(dirname "${SCRIPT}"); . $(dirname "${SCRIPT}")/ocf-shellfuncs; . ${SCRIPT}; findif\"" + +DUMMY_USER=test-findif + +LO_IP4_13=${LO_IP4%.[0-9]*} +LO_IP4_4=${LO_IP4##[0-9][0-9]*.} +LO_IP4_INC=${LO_IP4_13}.$((LO_IP4_4 + 1)) + +DUMMY_IP4_13=${DUMMY_IP4%.[0-9]*} +DUMMY_IP4_4=${DUMMY_IP4##[0-9][0-9]*.} +DUMMY_IP4_INC=${DUMMY_IP4_13}.$((DUMMY_IP4_4 + 1)) + +DUMMY_IP6_17=${DUMMY_IP6%:[0-9A-Fa-f:][0-9A-Fa-f:]*} +DUMMY_IP6_8=${DUMMY_IP6##[0-9A-Fa-f][0-9A-Fa-f]*:} +DUMMY_IP6_INC=${DUMMY_IP6_17}::$((DUMMY_IP6_8 + 1)) + +# +# command-line config +# + +CMD="${PRG_CMD}" +TESTS="4" + +# +# test declarations +# + +TEST_FORMAT=\ +" OCF_RESKEY_ip , OCF_RESKEY_cidr_netmask , expected_ec , expected_dev , expected_nm , expected_bc" + +TEST_DATA4=\ +" # valid: 1-9: loopback, 10-19: dummy if; invalid cases: 20-29: ip, 30-39: netmask bits + # 1) LO4_IP + ${LO_IP4} , , $OCF_SUCCESS , ${LO_IF} , ${LO_NM4} , ${LO_BC4} + # 2) LO4_IP+1 + ${LO_IP4_INC} , , $OCF_SUCCESS , ${LO_IF} , ${LO_NM4} , ${LO_BC4} + # + # 10) DUMMY4_IP+1 + ${DUMMY_IP4_INC} , , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM4} , ${DUMMY_BC4} + # 11) DUMMY4_IP+1, explicit netmask + ${DUMMY_IP4_INC} , ${DUMMY_NM4} , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM4} , ${DUMMY_BC4} + # + # 20) *invalid* IPv4 (missing last item in quad) + ${DUMMY_IP4_13}. , , $OCF_ERR_CONFIGURED , NA , NA , NA + # 21) *invalid* IP (random string) + foobar , , $OCF_ERR_CONFIGURED , NA , NA , NA + # + # 30) DUMMY4_IP+1, explicit *invalid* netmask (33) + ${DUMMY_IP4_INC} , 33 , $OCF_ERR_CONFIGURED , NA , NA , NA + # 31) DUMMY4_IP+1, explicit *invalid* netmask (-1) + ${DUMMY_IP4_INC} , -1 , $OCF_ERR_CONFIGURED , NA , NA , NA + # 32) DUMMY4_IP+1, explicit *invalid* netmask (random string) + ${DUMMY_IP4_INC} , foobar , $OCF_ERR_CONFIGURED , NA , NA , NA +" + +TEST_DATA6=\ +" # valid: A0-A9: loopback, B0-B9: dummy if; invalid cases: C0-C9: ip, D0-D9: netmask bits + # A0) LO6_IP + ${LO_IP6} , , $OCF_SUCCESS , ${LO_IF} , ${LO_NM4} , + # + # B0) DUMMY6_IP+1 + ${DUMMY_IP6_INC} , , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM6} , + # B1) DUMMY4_IP+1, explicit netmask + ${DUMMY_IP6_INC} , ${DUMMY_NM6} , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM6} , +" + +# +# private routines +# + +_get_test_field () { + echo "$1" | cut -s -d, -f$2 | sed 's|^ \?\(.*[^ ]\) \?$|\1|g' +} + +# +# public routines +# + +setup () { + if [ "$(uname -o)" != "GNU/Linux" ]; then + die "Only tested with Linux, feel free to edit the condition." + fi + + if [ "${CMD}" = "${PRG_CMD}" ]; then + [ -x "${PRG}" ] || die "Forgot to compile ${PRG} for me to test?" + fi + + if [ $(id -u) -ne 0 ]; then + die "Due to (unobtrusive) juggling with routing, run as root." + fi + + if ! useradd -M ${DUMMY_USER} 2>/dev/null; then + if ! getent passwd ${DUMMY_USER} >/dev/null; then + die "Cannot add user ${DUMMY_USER} for testing purposes." + fi + warn "User ${DUMMY_USER} already exists, be careful not" \ + " to confirm its removal." + fi + + if lsmod | grep -Eq "^dummy[ ]+"; then + warn "Looks like you already use dummy network device." + else + modprobe dummy || die "No dummy kernel module (per name) at hand." + fi + + #if ! ifconfig ${DUMMY_IF} ${DUMMY_IP4}/${DUMMY_NM4}; then + #if ! ip addr add ${DUMMY_IP4}/${DUMMY_NM4} broadcast + dev ${DUMMY_IF}; then + if ! ip addr add ${DUMMY_IP4}/${DUMMY_NM4} dev ${DUMMY_IF}; then + die "Cannot add IPv4 address (${DUMMY_IP4}/${DUMMY_NM4}) to ${DUMMY_IF}." + fi + + # TODO: IPv6 support check first, disabling 6'ish tests if negative? + if ! ip addr add ${DUMMY_IP6}/${DUMMY_NM6} dev ${DUMMY_IF}; then + die "Cannot add IPv6 address (${DUMMY_IP6}/${DUMMY_NM6}) to ${DUMMY_IF}." + fi + + if ! ip link set ${DUMMY_IF} up; then + die "Cannot bring ${DUMMY_IF} up." + fi +} + +teardown () { + while true; do + echo -n "Remove user ${DUMMY_USER}? [y/n] " + case $(read l < /dev/tty; echo "${l}") in + n) + break + ;; + y) + [ "${DUMMY_USER}" = "$(whoami)" ] && break + userdel ${DUMMY_USER} || warn "Cannot kick user ${DUMMY_USER} out." + break + ;; + esac + done + + rmmod dummy || warn "Cannot kick dummy kernel module out." +} + +proceed () { + err_cnt=0 + (for test in ${TESTS}; do eval "echo \"\${TEST_DATA${test}}\""; done + tty 2>&1 >/dev/null || cat | sed 's|^\(.*\)$|# stdin: \1\n\1|') \ + | while read curline; do + if echo "${curline}" | grep -Eqv -e '^[ \t]*#' -e '^$'; then + err_this=0 + curline="$(echo "${curline}" | tr '\t' ' ' | tr -s ' ')" + info "$(echo "${lastline}" | sed 's|^#|------|')" + + ip="$( _get_test_field "${curline}" 1)" + [ -z "${ip}" ] && die "test spec.: empty ip" + mask="$( _get_test_field "${curline}" 2)" + [ -z "${mask}" ] && warn "test spec.: empty mask" + exp_ec="$( _get_test_field "${curline}" 3)" + [ -z "${exp_ec}" ] && warn "test spec.: empty exp_ec" + exp_dev="$(_get_test_field "${curline}" 4)" + [ -z "${exp_dev}" ] && warn "test spec.: empty exp_dev" + exp_nm="$( _get_test_field "${curline}" 5)" + [ -z "${exp_nm}" ] && warn "test spec.: empty exp_nm" + exp_bc="$( _get_test_field "${curline}" 6)" + [ -z "${exp_bc}" ] && warn "test spec.: empty exp_bc" + [ $DEBUG_IN -ne 0 ] \ + && echo "${ip}, ${mask}, ${exp_ec}, ${exp_dev}, ${exp_nm}, ${exp_bc}" + + env="OCF_RESKEY_ip=${ip} OCF_RESKEY_cidr_netmask=${mask}" + echo "${env}" + res="$(su ${DUMMY_USER} -c "${env} ${CMD} 2>&1")" + got_ec=$? + + if [ ${got_ec} -ne ${exp_ec} ]; then + warn "FAIL exit code: ${got_ec} vs ${exp_ec}" + err_this=$((err_this+1)) + fi + + res="$(echo "${res}" | tr '\t' ' ' | tr -s ' ')" + + res_check= + stop=0 + echo "${res}" | while read res_line && [ $stop -eq 0 ]; do + echo "${res_line}" | grep -Eq "^[0-9A-Za-z_-]+ netmask [0-9]" + if [ $? -ne 0 ]; then + if [ -z "${res_line}" ] || echo "${res_line}" \ + | grep -Fq "Copyright Alan Robertson"; then + # findif finishes w/ (C)+usage on failure + stop=1 + else + echo "${res_line}" + fi + elif [ -n "${res_check}" ]; then + warn "More than one line with results." + stop=1 + else + res_check=${res_line} + got_dev="$(echo "${res_check}" | cut -d' ' -f1)" + got_nm="$(echo "${res_check}" | cut -d' ' -f3)" + got_bc="$(echo "${res_check}" | cut -d' ' -f5)" + [ ${DEBUG_OUT} -ne 0 ] \ + && echo "${got_dev}, ${got_nm}, ${got_bc}" + + if [ "${got_dev}" != "${exp_dev}" ]; then + warn "FAIL device: ${got_dev} vs ${exp_dev}" + err_this=$((err_this+1)) + fi + if [ "${got_nm}" != "${exp_nm}" ]; then + warn "FAIL netmask: ${got_nm} vs ${exp_nm}" + err_this=$((err_this+1)) + fi + if [ "${got_bc}" != "${exp_bc}" ]; then + warn "FAIL broadcast: ${got_bc} vs ${exp_bc}" + err_this=$((err_this+1)) + fi + fi + + mimic_return ${err_this} # workaround separate process limitation + done + + err_this=$? + if [ ${err_this} -eq 0 ]; then + ok + else + fail + err_cnt=$((err_cnt+1)) + fi + fi + lastline="${curline}" + mimic_return ${err_cnt} # workaround separate process limitation + done + err_cnt=$? + + echo "--- TOTAL ---" + [ $err_cnt -eq 0 ] && ok || fail $err_cnt + return $err_cnt +} + +if [ $# -ge 1 ]; then + while true; do + case $1 in + -script) + CMD="${SCRIPT_CMD}" + [ $# -eq 1 ] && break + ;; + --) + TESTS= + [ $# -eq 1 ] && break + ;; + -4) + TESTS="4" + [ $# -eq 1 ] && break + ;; + -6) + TESTS="6" + [ $# -eq 1 ] && break + ;; + -46) + TESTS="4 6" + [ $# -eq 1 ] && break + ;; + setup|proceed|teardown) + verbosely $1 + ret=$? + [ $ret -ne 0 ] && exit $ret + ;; + *) + echo "usage: ./$0 [-script] [--|-4|-6|-46] (setup,proceed,teardown)" + echo "additional tests may be piped to standard input, format:" + echo "${TEST_FORMAT}" | tr '\t' ' ' | tr -s ' ' + exit 0 + ;; + esac + [ $# -eq 1 ] && exit 0 + shift + done +fi + +verbosely setup +verbosely proceed +ret=$? +verbosely teardown + +exit $ret diff --git a/tools/tickle_tcp.c b/tools/tickle_tcp.c new file mode 100644 index 0000000..46a6cfe --- /dev/null +++ b/tools/tickle_tcp.c @@ -0,0 +1,380 @@ +/* + Tickle TCP connections tool + + Author: Jiaju Zhang + Based on the code in CTDB http://ctdb.samba.org/ written by + Andrew Tridgell and Ronnie Sahlberg + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/tcp.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <net/if.h> + +#define discard_const(ptr) ((void *)((intptr_t)(ptr))) + +typedef union { + struct sockaddr sa; + struct sockaddr_in ip; + struct sockaddr_in6 ip6; +} sock_addr; + +void set_nonblocking(int fd); +void set_close_on_exec(int fd); +static int parse_ipv4(const char *s, unsigned port, struct sockaddr_in *sin); +static int parse_ipv6(const char *s, const char *iface, unsigned port, sock_addr *saddr); +int parse_ip(const char *addr, const char *iface, unsigned port, sock_addr *saddr); +int parse_ip_port(const char *addr, sock_addr *saddr); +int send_tickle_ack(const sock_addr *dst, + const sock_addr *src, + uint32_t seq, uint32_t ack, int rst); +static void usage(void); + +static uint32_t uint16_checksum(uint16_t *data, size_t n) +{ + uint32_t sum=0; + while (n >= 2) { + sum += (uint32_t)ntohs(*data); + data++; + n -= 2; + } + if (n == 1) { + sum += (uint32_t)ntohs(*(uint8_t *)data); + } + return sum; +} + +static uint16_t tcp_checksum(uint16_t *data, size_t n, struct iphdr *ip) +{ + uint32_t sum = uint16_checksum(data, n); + uint16_t sum2; + sum += uint16_checksum((uint16_t *)(void *)&ip->saddr, + sizeof(ip->saddr)); + sum += uint16_checksum((uint16_t *)(void *)&ip->daddr, + sizeof(ip->daddr)); + sum += ip->protocol + n; + sum = (sum & 0xFFFF) + (sum >> 16); + sum = (sum & 0xFFFF) + (sum >> 16); + sum2 = htons(sum); + sum2 = ~sum2; + if (sum2 == 0) { + return 0xFFFF; + } + return sum2; +} + +static uint16_t tcp_checksum6(uint16_t *data, size_t n, struct ip6_hdr *ip6) +{ + uint32_t phdr[2]; + uint32_t sum = 0; + uint16_t sum2; + + memset(phdr, 0, sizeof(phdr)); + + sum += uint16_checksum((uint16_t *)(void *)&ip6->ip6_src, 16); + sum += uint16_checksum((uint16_t *)(void *)&ip6->ip6_dst, 16); + + phdr[0] = htonl(n); + phdr[1] = htonl(ip6->ip6_nxt); + sum += uint16_checksum((uint16_t *)phdr, 8); + + sum += uint16_checksum(data, n); + + sum = (sum & 0xFFFF) + (sum >> 16); + sum = (sum & 0xFFFF) + (sum >> 16); + sum2 = htons(sum); + sum2 = ~sum2; + if (sum2 == 0) { + return 0xFFFF; + } + return sum2; +} + +void set_nonblocking(int fd) +{ + unsigned v; + v = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, v | O_NONBLOCK); +} + +void set_close_on_exec(int fd) +{ + unsigned v; + v = fcntl(fd, F_GETFD, 0); + fcntl(fd, F_SETFD, v | FD_CLOEXEC); +} + +static int parse_ipv4(const char *s, unsigned port, struct sockaddr_in *sin) +{ + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + + if (inet_pton(AF_INET, s, &sin->sin_addr) != 1) { + fprintf(stderr, "Failed to translate %s into sin_addr\n", s); + return -1; + } + + return 0; +} + +static int parse_ipv6(const char *s, const char *iface, unsigned port, sock_addr *saddr) +{ + saddr->ip6.sin6_family = AF_INET6; + saddr->ip6.sin6_port = htons(port); + saddr->ip6.sin6_flowinfo = 0; + saddr->ip6.sin6_scope_id = 0; + + if (inet_pton(AF_INET6, s, &saddr->ip6.sin6_addr) != 1) { + fprintf(stderr, "Failed to translate %s into sin6_addr\n", s); + return -1; + } + + if (iface && IN6_IS_ADDR_LINKLOCAL(&saddr->ip6.sin6_addr)) { + saddr->ip6.sin6_scope_id = if_nametoindex(iface); + } + + return 0; +} + +int parse_ip(const char *addr, const char *iface, unsigned port, sock_addr *saddr) +{ + char *p; + int ret; + + p = index(addr, ':'); + if (!p) + ret = parse_ipv4(addr, port, &saddr->ip); + else + ret = parse_ipv6(addr, iface, port, saddr); + + return ret; +} + +int parse_ip_port(const char *addr, sock_addr *saddr) +{ + char *s, *p; + unsigned port; + char *endp = NULL; + int ret; + + s = strdup(addr); + if (!s) { + fprintf(stderr, "Failed strdup()\n"); + return -1; + } + + p = rindex(s, ':'); + if (!p) { + fprintf(stderr, "This addr: %s does not contain a port number\n", s); + free(s); + return -1; + } + + port = strtoul(p+1, &endp, 10); + if (!endp || *endp != 0) { + fprintf(stderr, "Trailing garbage after the port in %s\n", s); + free(s); + return -1; + } + *p = 0; + + ret = parse_ip(s, NULL, port, saddr); + free(s); + return ret; +} + +int send_tickle_ack(const sock_addr *dst, + const sock_addr *src, + uint32_t seq, uint32_t ack, int rst) +{ + int s; + int ret; + uint32_t one = 1; + uint16_t tmpport; + sock_addr *tmpdest; + struct { + struct iphdr ip; + struct tcphdr tcp; + } ip4pkt; + struct { + struct ip6_hdr ip6; + struct tcphdr tcp; + } ip6pkt; + + switch (src->ip.sin_family) { + case AF_INET: + memset(&ip4pkt, 0, sizeof(ip4pkt)); + ip4pkt.ip.version = 4; + ip4pkt.ip.ihl = sizeof(ip4pkt.ip)/4; + ip4pkt.ip.tot_len = htons(sizeof(ip4pkt)); + ip4pkt.ip.ttl = 255; + ip4pkt.ip.protocol = IPPROTO_TCP; + ip4pkt.ip.saddr = src->ip.sin_addr.s_addr; + ip4pkt.ip.daddr = dst->ip.sin_addr.s_addr; + ip4pkt.ip.check = 0; + + ip4pkt.tcp.source = src->ip.sin_port; + ip4pkt.tcp.dest = dst->ip.sin_port; + ip4pkt.tcp.seq = seq; + ip4pkt.tcp.ack_seq = ack; + ip4pkt.tcp.ack = 1; + if (rst) + ip4pkt.tcp.rst = 1; + ip4pkt.tcp.doff = sizeof(ip4pkt.tcp)/4; + ip4pkt.tcp.window = htons(1234); + ip4pkt.tcp.check = tcp_checksum((uint16_t *)&ip4pkt.tcp, sizeof(ip4pkt.tcp), &ip4pkt.ip); + + s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (s == -1) { + fprintf(stderr, "Failed to open raw socket (%s)\n", strerror(errno)); + return -1; + } + + ret = setsockopt(s, SOL_IP, IP_HDRINCL, &one, sizeof(one)); + if (ret != 0) { + fprintf(stderr, "Failed to setup IP headers (%s)\n", strerror(errno)); + close(s); + return -1; + } + + set_nonblocking(s); + set_close_on_exec(s); + + ret = sendto(s, &ip4pkt, sizeof(ip4pkt), 0, + (const struct sockaddr *)&dst->ip, sizeof(dst->ip)); + close(s); + if (ret != sizeof(ip4pkt)) { + fprintf(stderr, "Failed sendto (%s)\n", strerror(errno)); + return -1; + } + break; + + case AF_INET6: + memset(&ip6pkt, 0, sizeof(ip6pkt)); + ip6pkt.ip6.ip6_vfc = 0x60; + ip6pkt.ip6.ip6_plen = htons(20); + ip6pkt.ip6.ip6_nxt = IPPROTO_TCP; + ip6pkt.ip6.ip6_hlim = 64; + ip6pkt.ip6.ip6_src = src->ip6.sin6_addr; + ip6pkt.ip6.ip6_dst = dst->ip6.sin6_addr; + + ip6pkt.tcp.source = src->ip6.sin6_port; + ip6pkt.tcp.dest = dst->ip6.sin6_port; + ip6pkt.tcp.seq = seq; + ip6pkt.tcp.ack_seq = ack; + ip6pkt.tcp.ack = 1; + if (rst) + ip6pkt.tcp.rst = 1; + ip6pkt.tcp.doff = sizeof(ip6pkt.tcp)/4; + ip6pkt.tcp.window = htons(1234); + ip6pkt.tcp.check = tcp_checksum6((uint16_t *)&ip6pkt.tcp, sizeof(ip6pkt.tcp), &ip6pkt.ip6); + + s = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW); + if (s == -1) { + fprintf(stderr, "Failed to open sending socket\n"); + return -1; + } + + tmpdest = discard_const(dst); + tmpport = tmpdest->ip6.sin6_port; + + tmpdest->ip6.sin6_port = 0; + ret = sendto(s, &ip6pkt, sizeof(ip6pkt), 0, (const struct sockaddr *)&dst->ip6, sizeof(dst->ip6)); + tmpdest->ip6.sin6_port = tmpport; + close(s); + + if (ret != sizeof(ip6pkt)) { + fprintf(stderr, "Failed sendto (%s)\n", strerror(errno)); + return -1; + } + break; + + default: + fprintf(stderr, "Not an ipv4/v6 address\n"); + return -1; + } + + return 0; +} + +static void usage(void) +{ + printf("Usage: /usr/lib/heartbeat/tickle_tcp [ -n num ]\n"); + printf("Please note that this program need to read the list of\n"); + printf("{local_ip:port remote_ip:port} from stdin.\n"); + exit(1); +} + +#define OPTION_STRING "n:h" + +int main(int argc, char *argv[]) +{ + int optchar, i, num = 1, cont = 1; + sock_addr src, dst; + char addrline[128], addr1[64], addr2[64]; + + while(cont) { + optchar = getopt(argc, argv, OPTION_STRING); + switch(optchar) { + case 'n': + num = atoi(optarg); + break; + case 'h': + usage(); + exit(EXIT_SUCCESS); + break; + case EOF: + cont = 0; + break; + default: + fprintf(stderr, "unknown option, please use '-h' for usage.\n"); + exit(EXIT_FAILURE); + break; + }; + } + + while(fgets(addrline, sizeof(addrline), stdin)) { + sscanf(addrline, "%s %s", addr1, addr2); + + if (parse_ip_port(addr1, &src)) { + fprintf(stderr, "Bad IP:port '%s'\n", addr1); + return -1; + } + if (parse_ip_port(addr2, &dst)) { + fprintf(stderr, "Bad IP:port '%s'\n", addr2); + return -1; + } + + for (i = 1; i <= num; i++) { + if (send_tickle_ack(&dst, &src, 0, 0, 0)) { + fprintf(stderr, "Error while sending tickle ack from '%s' to '%s'\n", + addr1, addr2); + return -1; + } + } + + } + return 0; +} |