diff options
-rw-r--r-- | .gitignore | 26 | ||||
-rw-r--r-- | AUTHORS | 0 | ||||
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | ChangeLog | 0 | ||||
-rw-r--r-- | Makefile.am | 78 | ||||
-rw-r--r-- | NEWS | 0 | ||||
-rw-r--r-- | README | 28 | ||||
-rwxr-xr-x | autogen.sh | 30 | ||||
-rw-r--r-- | build-aux/Makefile.subs | 34 | ||||
-rwxr-xr-x | build-aux/gitlog-to-changelog | 194 | ||||
-rwxr-xr-x | build-aux/pkg-version | 52 | ||||
-rw-r--r-- | configure.ac | 92 | ||||
-rw-r--r-- | gluster-nagios-common.spec.in | 75 | ||||
-rw-r--r-- | glusternagios/Makefile.am | 4 | ||||
-rw-r--r-- | glusternagios/__init__.py | 19 | ||||
-rw-r--r-- | glusternagios/utils.py | 438 | ||||
-rw-r--r-- | m4/ax_python_module.m4 | 49 | ||||
-rwxr-xr-x | rfc.sh | 114 | ||||
-rw-r--r-- | tests/Makefile.am | 51 | ||||
-rw-r--r-- | tests/README | 11 | ||||
-rw-r--r-- | tests/run_tests.sh.in | 9 | ||||
-rw-r--r-- | tests/run_tests_local.sh.in | 6 | ||||
-rw-r--r-- | tests/testValidation.py | 141 | ||||
-rw-r--r-- | tests/testrunner.py | 308 | ||||
-rw-r--r-- | tests/utilsTests.py | 78 |
25 files changed, 2177 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f5708b --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +*.iml +*.o +*.pyc +*.swp +*.tmp +*~ +.deps +.idea +.project +.pydevproject +INSTALL +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +build-aux/depcomp +build-aux/install-sh +build-aux/missing +build-aux/py-compile +config.log +config.status +configure +tests/run_tests.sh +tests/run_tests_local.sh +gluster-nagios-common-*.tar.gz +gluster-nagios-common.spec @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..cdd4ea9 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,78 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +# keep sorted +SUBDIRS = \ + glusternagios \ + $(NULL) + +# The tests should be always last as they need the rest of the source to be +# prepared before running. +SUBDIRS += tests + +EXTRA_DIST = \ + build-aux/pkg-version \ + gluster-nagios-common.spec \ + gluster-nagios-common.spec.in + +CLEANFILES = \ + gluster-nagios-common.spec \ + $(DIST_ARCHIVES) \ + $(NULL) + +check-local: + find . -path './.git' -prune -type f -o \ + -name '*.py' -o -name '*.py.in' | xargs $(PYFLAKES) | \ + while read LINE; do echo "$$LINE"; false; done + $(PEP8) --version + $(PEP8) --filename '*.py,*.py.in' $(top_srcdir) + @if test -f .gitignore; then \ + for i in `git ls-files \*.in`; do \ + if ! grep -q -x $${i%%.in} .gitignore; then \ + echo "Missing $${i%%.in} in .gitignore"; exit 1; fi; \ + done; \ + fi; + +all-local: \ + gluster-nagios-common.spec + +.PHONY: srpm rpm + +srpm: dist + rpmbuild -ts $(if $(BUILDID),--define="extra_release .$(BUILDID)") $(DIST_ARCHIVES) + +rpm: dist + rpmbuild -ta $(if $(BUILDID),--define="extra_release .$(BUILDID)") \ + $(WITH_HOOKS) $(DIST_ARCHIVES) + +dist-hook: gen-VERSION gen-ChangeLog +.PHONY: gen-VERSION gen-ChangeLog + +gen-ChangeLog: + if test -d .git; then \ + $(top_srcdir)/build-aux/gitlog-to-changelog \ + > $(distdir)/ChangeLog; \ + fi + +gen-VERSION: + if test -d .git; then \ + $(top_srcdir)/build-aux/pkg-version --full \ + > $(distdir)/VERSION; \ + fi @@ -0,0 +1,28 @@ + +Nagios Gluster Add-ons: +======================= +Nagios plugin, scripts, configuration files etc for gluster nodes. + + +Installation +============ +The Nagios Gluster Add-ons can be used by following the standard +autotools installation process, documented in the INSTALL file. As a +quick start you can do + + ./configure --prefix=/usr --sysconfdir=/etc \ + --localstatedir=/var --libdir=/usr/lib + make + sudo make install + + +Packaging +========= +The 'nagios-gluster-addons.spec' file demonstrates how to distribute +this as an RPM package. + + +Getting Help +============ + +-- End of readme diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..4979329 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +autoreconf -if + +if test "x$1" = "x--system"; then + shift + prefix=/usr + libdir=$prefix/lib + sysconfdir=/etc + localstatedir=/var + if [ -d /usr/lib64 ]; then + libdir=$prefix/lib64 + fi + EXTRA_ARGS="--prefix=$prefix --sysconfdir=$sysconfdir --localstatedir=$localstatedir --libdir=$libdir" + echo "Running ./configure with $EXTRA_ARGS $@" +else + if test -z "$*" && test ! -f "$THEDIR/config.status"; then + echo "I am going to run ./configure with no arguments - if you wish " + echo "to pass any to it, please specify them on the $0 command line." + fi +fi + +if test -z "$*" && test -f config.status; then + ./config.status --recheck +else + ./configure $EXTRA_ARGS "$@" +fi && { + echo + echo "Now type 'make' to compile." +} diff --git a/build-aux/Makefile.subs b/build-aux/Makefile.subs new file mode 100644 index 0000000..ff395f7 --- /dev/null +++ b/build-aux/Makefile.subs @@ -0,0 +1,34 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +SUFFIXES: .in + +# Reference: +# http://www.gnu.org/software/automake/manual/html_node/Scripts.html +PATHSUBST = sed \ + -e "s,[@]NAGIOSPLUGINSDIR[@],$(nagiospluginsdir),g" \ + -e "s,[@]GLUSTERHOSTPLUGINSDIR[@],$(glusterhostpluginsdir),g" \ + -e "s,[@]GLUSTERADDONSTESTSDIR[@],$(glusteraddonstestsdir),g" \ + -e "s,[@]GLUSTERADDONSPYLIBDIR[@],$(glusteraddonspylibdir),g" + +CONFIGSUBST = $(top_builddir)/config.status --file=- + +%: %.in + @echo " SED $@"; $(PATHSUBST) $< |$(CONFIGSUBST) >$@ diff --git a/build-aux/gitlog-to-changelog b/build-aux/gitlog-to-changelog new file mode 100755 index 0000000..b0db305 --- /dev/null +++ b/build-aux/gitlog-to-changelog @@ -0,0 +1,194 @@ +eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}' + & eval 'exec perl -wS "$0" $argv:q' + if 0; +# Convert git log output to ChangeLog format. + +my $VERSION = '2009-10-30 13:46'; # UTC +# The definition above must lie within the first 8 lines in order +# for the Emacs time-stamp write hook (at end) to update it. +# If you change this file with Emacs, please let the write hook +# do its job. Otherwise, update this string manually. + +# Copyright (C) 2008-2011 Free Software Foundation, Inc. + +# 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/>. + +# Written by Jim Meyering + +use strict; +use warnings; +use Getopt::Long; +use POSIX qw(strftime); + +(my $ME = $0) =~ s|.*/||; + +# use File::Coda; # http://meyering.net/code/Coda/ +END { + defined fileno STDOUT or return; + close STDOUT and return; + warn "$ME: failed to close standard output: $!\n"; + $? ||= 1; +} + +sub usage ($) +{ + my ($exit_code) = @_; + my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); + if ($exit_code != 0) + { + print $STREAM "Try `$ME --help' for more information.\n"; + } + else + { + print $STREAM <<EOF; +Usage: $ME [OPTIONS] [ARGS] + +Convert git log output to ChangeLog format. If present, any ARGS +are passed to "git log". To avoid ARGS being parsed as options to +$ME, they may be preceded by '--'. + +OPTIONS: + + --since=DATE convert only the logs since DATE; + the default is to convert all log entries. + --format=FMT set format string for commit subject and body; + see 'man git-log' for the list of format metacharacters; + the default is '%s%n%b%n' + + --help display this help and exit + --version output version information and exit + +EXAMPLE: + + $ME --since=2008-01-01 > ChangeLog + $ME -- -n 5 foo > last-5-commits-to-branch-foo + +EOF + } + exit $exit_code; +} + +# If the string $S is a well-behaved file name, simply return it. +# If it contains white space, quotes, etc., quote it, and return the new string. +sub shell_quote($) +{ + my ($s) = @_; + if ($s =~ m![^\w+/.,-]!) + { + # Convert each single quote to '\'' + $s =~ s/\'/\'\\\'\'/g; + # Then single quote the string. + $s = "'$s'"; + } + return $s; +} + +sub quoted_cmd(@) +{ + return join (' ', map {shell_quote $_} @_); +} + +{ + my $since_date = '1970-01-01 UTC'; + my $format_string = '%s%n%b%n'; + GetOptions + ( + help => sub { usage 0 }, + version => sub { print "$ME version $VERSION\n"; exit }, + 'since=s' => \$since_date, + 'format=s' => \$format_string, + ) or usage 1; + + my @cmd = (qw (git log --log-size), "--since=$since_date", + '--pretty=format:%ct %an <%ae>%n%n'.$format_string, @ARGV); + open PIPE, '-|', @cmd + or die ("$ME: failed to run `". quoted_cmd (@cmd) ."': $!\n" + . "(Is your Git too old? Version 1.5.1 or later is required.)\n"); + + my $prev_date_line = ''; + while (1) + { + defined (my $in = <PIPE>) + or last; + $in =~ /^log size (\d+)$/ + or die "$ME:$.: Invalid line (expected log size):\n$in"; + my $log_nbytes = $1; + + my $log; + my $n_read = read PIPE, $log, $log_nbytes; + $n_read == $log_nbytes + or die "$ME:$.: unexpected EOF\n"; + + my @line = split "\n", $log; + my $author_line = shift @line; + defined $author_line + or die "$ME:$.: unexpected EOF\n"; + $author_line =~ /^(\d+) (.*>)$/ + or die "$ME:$.: Invalid line " + . "(expected date/author/email):\n$author_line\n"; + + my $date_line = sprintf "%s $2\n", strftime ("%F", localtime ($1)); + # If this line would be the same as the previous date/name/email + # line, then arrange not to print it. + if ($date_line ne $prev_date_line) + { + $prev_date_line eq '' + or print "\n"; + print $date_line; + } + $prev_date_line = $date_line; + + # Omit "Signed-off-by..." lines. + @line = grep !/^Signed-off-by: .*>$/, @line; + + # Omit gerrit lines. + @line = grep !/^(Change-Id|Reviewed-(on|by)|Tested-by): .*$/, @line; + + # If there were any lines + if (@line == 0) + { + warn "$ME: warning: empty commit message:\n $date_line\n"; + } + else + { + # Remove leading and trailing blank lines. + while ($line[0] =~ /^\s*$/) { shift @line; } + while ($line[$#line] =~ /^\s*$/) { pop @line; } + + # Prefix each non-empty line with a TAB. + @line = map { length $_ ? "\t$_" : '' } @line; + + print "\n", join ("\n", @line), "\n"; + } + + defined ($in = <PIPE>) + or last; + $in ne "\n" + and die "$ME:$.: unexpected line:\n$in"; + } + + close PIPE + or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; + # FIXME-someday: include $PROCESS_STATUS in the diagnostic +} + +# Local Variables: +# mode: perl +# indent-tabs-mode: nil +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "my $VERSION = '" +# time-stamp-format: "%:y-%02m-%02d %02H:%02M" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "'; # UTC" +# End: diff --git a/build-aux/pkg-version b/build-aux/pkg-version new file mode 100755 index 0000000..2be2a97 --- /dev/null +++ b/build-aux/pkg-version @@ -0,0 +1,52 @@ +#!/bin/sh + +# To override version/release from git, +# create VERSION file containing text with version/release +# eg. v3.4.0-1 +PKG_VERSION=`cat VERSION 2> /dev/null || git describe --tags --match "v[0-9]*"` + +function get_version () +{ + # tags and output versions: + # - v3.4.0 => 3.4.0 (upstream clean) + # - v3.4.0-1 => 3.4.0 (downstream clean) + # - v3.4.0-2-g34e62f => 3.4.0 (upstream dirty) + # - v3.4.0-1-2-g34e62f => 3.4.0 (downstream dirty) + AWK_VERSION=' + BEGIN { FS="-" } + /^v[0-9]/ { + sub(/^v/,"") ; print $1 + }' + + echo $PKG_VERSION | awk "$AWK_VERSION" | tr -cd '[:alnum:].' +} + +function get_release () +{ + # tags and output releases: + # - v3.4.0 => 0 (upstream clean) + # - v3.4.0-1 => 1 (downstream clean) + # - v3.4.0-2-g34e62f1 => 2.git34e62f1 (upstream dirty) + # - v3.4.0-1-2-g34e62f1 => 1.2.git34e62f1 (downstream dirty) + AWK_RELEASE=' + BEGIN { FS="-"; OFS="." } + /^v[0-9]/ { + if (NF == 1) print 0 + else if (NF == 2) print $2 + else if (NF == 3) print $2, "git" substr($3, 2) + else if (NF == 4) print $2, $3, "git" substr($4, 2) + }' + + echo $PKG_VERSION | awk "$AWK_RELEASE" | tr -cd '[:alnum:].' +} + +if test "x$1" = "x--full"; then + echo -n "v$(get_version)-$(get_release)" +elif test "x$1" = "x--version"; then + get_version +elif test "x$1" = "x--release"; then + get_release +else + echo "usage: $0 [--full|--version|--release]" + exit 1 +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..67d3f32 --- /dev/null +++ b/configure.ac @@ -0,0 +1,92 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +# Autoconf initialization +AC_INIT([gluster-nagios-common], + [m4_esyscmd([build-aux/pkg-version --version])], + [rhs-bugs@redhat.com]) +AC_CONFIG_AUX_DIR([build-aux]) + +m4_include([m4/ax_python_module.m4]) + +# Package release +AC_SUBST([PACKAGE_RELEASE], + [m4_esyscmd([build-aux/pkg-version --release])]) + +# Testing for version and release +AS_IF([test "x$PACKAGE_VERSION" = x], + AC_MSG_ERROR([package version not defined])) +AS_IF([test "x$PACKAGE_RELEASE" = x], + AC_MSG_ERROR([package release not defined])) + +# Automake initialization +AM_INIT_AUTOMAKE([-Wno-portability]) + +# Checking for build tools +AC_PROG_CC +AC_PROG_LN_S +AM_PATH_PYTHON([2.6]) + +# default paths +AC_SUBST([glusternagioscommonpylibdir], ['${pyexecdir}/glusternagios']) +AC_SUBST([glusternagioscommontestsdir], ['${datarootdir}/${PACKAGE_NAME}/tests']) + +# Checking for pyflakes +AC_PATH_PROG([PYFLAKES], [pyflakes]) +if test "x$PYFLAKES" = "x"; then + AC_MSG_WARN([pyflakes not found]) +fi + +# Checking for pep8 +AC_PATH_PROG([PEP8], [pep8]) +if test "x$PEP8" = "x"; then + AC_MSG_WARN([python-pep8 not found]) +fi + +# Checking for python-devel +AC_PATH_PROG([PYTHON_CONFIG], [python-config]) +if test "x$PYTHON_CONFIG" = "x"; then + AC_MSG_ERROR([python-devel not found, please install it.]) +fi + +# Checking for nosetests +AC_PATH_PROG([NOSETESTS], [nosetests]) +if test "x$NOSETESTS" = "x"; then + AC_MSG_ERROR([python-nose not found, please install it.]) +fi + +# Checking for python modules (sorted, please keep in order) +AX_PYTHON_MODULE([cpopen], [fatal]) +AX_PYTHON_MODULE([pthreading], [fatal]) + +# Keep sorted +AC_CONFIG_FILES([ + Makefile + gluster-nagios-common.spec + glusternagios/Makefile + tests/Makefile + tests/run_tests_local.sh + tests/run_tests.sh +]) + +AC_OUTPUT([], [ + chmod +x tests/run_tests.sh + chmod +x tests/run_tests_local.sh +]) diff --git a/gluster-nagios-common.spec.in b/gluster-nagios-common.spec.in new file mode 100644 index 0000000..290471a --- /dev/null +++ b/gluster-nagios-common.spec.in @@ -0,0 +1,75 @@ +%global _hardened_build 1 + +%global _for_fedora_koji_builds 0 + +%if ( 0%{?fedora} && 0%{?fedora} > 16 ) || ( 0%{?rhel} && 0%{?rhel} > 6 ) +%global _with_systemd true +%endif + +# From https://fedoraproject.org/wiki/Packaging:Python#Macros +%if ( 0%{?rhel} && 0%{?rhel} <= 5 ) +%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +%{!?python_sitearch: %global python_sitearch %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} +%endif + +Summary: Common libraries, tools, configurations for Gluster node and Nagios server add-ons +Name: @PACKAGE_NAME@ +Version: @PACKAGE_VERSION@ +Release: @PACKAGE_RELEASE@%{?dist} +License: GPLv2+ +Group: Applications/System +URL: http://www.redhat.com +Vendor: Red Hat, Inc. +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-root +BuildRequires: pyflakes +BuildRequires: python-pep8 +BuildRequires: python-nose +BuildRequires: python-devel +Requires: python-argparse +Requires: python-cpopen +Requires: python-pthreading + +%description +Common libraries, tools, configurations for Gluster node and Nagios server add-ons + +%package tests +Summary: Unit/functional tests of Common libraries, tools, configurations for Gluster node and Nagios server add-ons +Group: Development/Tools +Requires: %{name} = %{version}-%{release} +Requires: pyflakes +Requires: python-pep8 +Requires: python-nose +Requires: python-devel + +%description tests +Unit/functional tests of Common libraries, tools, configurations for Gluster node and Nagios server add-ons + +%prep +%setup -q + +%build +%{configure} +make + +%check +make check + +%install +rm -rf %{buildroot} +make install DESTDIR=%{buildroot} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{python_sitearch}/glusternagios/* + +%files tests +%defattr(-,root,root,-) +%{_datadir}/%{name}/tests/* + +%changelog +* Sat Mar 08 2014 Bala FA <barumuga@redhat.com> +- Initial build. diff --git a/glusternagios/Makefile.am b/glusternagios/Makefile.am new file mode 100644 index 0000000..55f8642 --- /dev/null +++ b/glusternagios/Makefile.am @@ -0,0 +1,4 @@ +dist_glusternagioscommonpylib_PYTHON = \ + __init__.py \ + utils.py \ + $(NULL) diff --git a/glusternagios/__init__.py b/glusternagios/__init__.py new file mode 100644 index 0000000..ff21495 --- /dev/null +++ b/glusternagios/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# diff --git a/glusternagios/utils.py b/glusternagios/utils.py new file mode 100644 index 0000000..b4d77c6 --- /dev/null +++ b/glusternagios/utils.py @@ -0,0 +1,438 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +## most of the code is copied from vdsm project + +import logging +import subprocess +from cpopen import CPopen +import io +import select +import threading +from StringIO import StringIO +from weakref import proxy +import time +import os +import errno +import signal + + +class CommandPath(object): + def __init__(self, name, *args): + self.name = name + self.paths = args + self._cmd = None + + @property + def cmd(self): + if not self._cmd: + for path in self.paths: + if os.path.exists(path): + self._cmd = path + break + else: + raise OSError(os.errno.ENOENT, + os.strerror(os.errno.ENOENT) + ': ' + self.name) + return self._cmd + + def __repr__(self): + return str(self.cmd) + + def __str__(self): + return str(self.cmd) + + def __unicode__(self): + return unicode(self.cmd) + + +ioniceCmdPath = CommandPath("ionice", + "/bin/ionice", + ) +killCmdPath = CommandPath("kill", + "/bin/kill", + ) +niceCmdPath = CommandPath("nice", + "/bin/nice", + ) +setsidCmdPath = CommandPath("setsid", + "/bin/setsid", + ) +sudoCmdPath = CommandPath("sudo", + "/bin/sudo", + ) +# Buffsize is 1K because I tested it on some use cases and 1K was fastest. If +# you find this number to be a bottleneck in any way you are welcome to change +# it +BUFFSIZE = 1024 +SUDO_NON_INTERACTIVE_FLAG = "-n" + + +# NOTE: it would be best to try and unify NoIntrCall and NoIntrPoll. +# We could do so defining a new object that can be used as a placeholer +# for the changing timeout value in the *args/**kwargs. This would +# lead us to rebuilding the function arguments at each loop. +def NoIntrPoll(pollfun, timeout=-1): + """ + This wrapper is used to handle the interrupt exceptions that might + occur during a poll system call. The wrapped function must be defined + as poll([timeout]) where the special timeout value 0 is used to return + immediately and -1 is used to wait indefinitely. + """ + # When the timeout < 0 we shouldn't compute a new timeout after an + # interruption. + endtime = None if timeout < 0 else time.time() + timeout + + while True: + try: + return pollfun(timeout) + except (IOError, select.error) as e: + if e.args[0] != errno.EINTR: + raise + + if endtime is not None: + timeout = max(0, endtime - time.time()) + + +class AsyncProc(object): + """ + AsyncProc is a funky class. It wraps a standard subprocess.Popen + Object and gives it super powers. Like the power to read from a stream + without the fear of deadlock. It does this by always sampling all + stream while waiting for data. By doing this the other process can freely + write data to all stream without the fear of it getting stuck writing + to a full pipe. + """ + class _streamWrapper(io.RawIOBase): + def __init__(self, parent, streamToWrap, fd): + io.IOBase.__init__(self) + self._stream = streamToWrap + self._parent = proxy(parent) + self._fd = fd + self._closed = False + + def close(self): + if not self._closed: + self._closed = True + while not self._streamClosed: + self._parent._processStreams() + + @property + def closed(self): + return self._closed + + @property + def _streamClosed(self): + return (self.fileno() in self._parent._closedfds) + + def fileno(self): + return self._fd + + def seekable(self): + return False + + def readable(self): + return True + + def writable(self): + return True + + def _readNonBlock(self, length): + hasNewData = (self._stream.len - self._stream.pos) + if hasNewData < length and not self._streamClosed: + self._parent._processStreams() + + with self._parent._streamLock: + res = self._stream.read(length) + if self._stream.pos == self._stream.len: + self._stream.truncate(0) + + if res == "" and not self._streamClosed: + return None + else: + return res + + def read(self, length): + if not self._parent.blocking: + return self._readNonBlock(length) + else: + res = None + while res is None: + res = self._readNonBlock(length) + + return res + + def readinto(self, b): + data = self.read(len(b)) + if data is None: + return None + + bytesRead = len(data) + b[:bytesRead] = data + + return bytesRead + + def write(self, data): + if hasattr(data, "tobytes"): + data = data.tobytes() + with self._parent._streamLock: + oldPos = self._stream.pos + self._stream.pos = self._stream.len + self._stream.write(data) + self._stream.pos = oldPos + + while self._stream.len > 0 and not self._streamClosed: + self._parent._processStreams() + + if self._streamClosed: + self._closed = True + + if self._stream.len != 0: + raise IOError(errno.EPIPE, + "Could not write all data to stream") + + return len(data) + + def __init__(self, popenToWrap): + self._streamLock = threading.Lock() + self._proc = popenToWrap + + self._stdout = StringIO() + self._stderr = StringIO() + self._stdin = StringIO() + + fdout = self._proc.stdout.fileno() + fderr = self._proc.stderr.fileno() + self._fdin = self._proc.stdin.fileno() + + self._closedfds = [] + + self._poller = select.epoll() + self._poller.register(fdout, select.EPOLLIN | select.EPOLLPRI) + self._poller.register(fderr, select.EPOLLIN | select.EPOLLPRI) + self._poller.register(self._fdin, 0) + self._fdMap = {fdout: self._stdout, + fderr: self._stderr, + self._fdin: self._stdin} + + self.stdout = io.BufferedReader(self._streamWrapper(self, + self._stdout, fdout), BUFFSIZE) + + self.stderr = io.BufferedReader(self._streamWrapper(self, + self._stderr, fderr), BUFFSIZE) + + self.stdin = io.BufferedWriter(self._streamWrapper(self, + self._stdin, self._fdin), BUFFSIZE) + + self._returncode = None + + self.blocking = False + + def _processStreams(self): + if len(self._closedfds) == 3: + return + + if not self._streamLock.acquire(False): + self._streamLock.acquire() + self._streamLock.release() + return + try: + if self._stdin.len > 0 and self._stdin.pos == 0: + # Polling stdin is redundant if there is nothing to write + # turn on only if data is waiting to be pushed + self._poller.modify(self._fdin, select.EPOLLOUT) + + pollres = NoIntrPoll(self._poller.poll, 1) + + for fd, event in pollres: + stream = self._fdMap[fd] + if event & select.EPOLLOUT and self._stdin.len > 0: + buff = self._stdin.read(BUFFSIZE) + written = os.write(fd, buff) + stream.pos -= len(buff) - written + if stream.pos == stream.len: + stream.truncate(0) + self._poller.modify(fd, 0) + + elif event & (select.EPOLLIN | select.EPOLLPRI): + data = os.read(fd, BUFFSIZE) + oldpos = stream.pos + stream.pos = stream.len + stream.write(data) + stream.pos = oldpos + + elif event & (select.EPOLLHUP | select.EPOLLERR): + self._poller.unregister(fd) + self._closedfds.append(fd) + # I don't close the fd because the original Popen + # will do it. + + if self.stdin.closed and self._fdin not in self._closedfds: + self._poller.unregister(self._fdin) + self._closedfds.append(self._fdin) + self._proc.stdin.close() + + finally: + self._streamLock.release() + + @property + def pid(self): + return self._proc.pid + + @property + def returncode(self): + if self._returncode is None: + self._returncode = self._proc.poll() + return self._returncode + + def kill(self): + try: + self._proc.kill() + except OSError as ex: + if ex.errno != errno.EPERM: + raise + execCmd([killCmdPath.cmd, "-%d" % (signal.SIGTERM,), + str(self.pid)], sudo=True) + + def wait(self, timeout=None, cond=None): + startTime = time.time() + while self.returncode is None: + if timeout is not None and (time.time() - startTime) > timeout: + return False + if cond is not None and cond(): + return False + self._processStreams() + return True + + def communicate(self, data=None): + if data is not None: + self.stdin.write(data) + self.stdin.flush() + self.stdin.close() + + self.wait() + return "".join(self.stdout), "".join(self.stderr) + + def __del__(self): + self._poller.close() + + +def execCmd(command, sudo=False, cwd=None, data=None, raw=False, logErr=True, + printable=None, env=None, sync=True, nice=None, ioclass=None, + ioclassdata=None, setsid=False, execCmdLogger=logging.root, + deathSignal=0, childUmask=None): + """ + Executes an external command, optionally via sudo. + + IMPORTANT NOTE: the new process would receive `deathSignal` when the + controlling thread dies, which may not be what you intended: if you create + a temporary thread, spawn a sync=False sub-process, and have the thread + finish, the new subprocess would die immediately. + """ + if ioclass is not None: + cmd = command + command = [ioniceCmdPath.cmd, '-c', str(ioclass)] + if ioclassdata is not None: + command.extend(("-n", str(ioclassdata))) + + command = command + cmd + + if nice is not None: + command = [niceCmdPath.cmd, '-n', str(nice)] + command + + if setsid: + command = [setsidCmdPath.cmd] + command + + if sudo: + command = [sudoCmdPath.cmd, SUDO_NON_INTERACTIVE_FLAG] + command + + if not printable: + printable = command + + cmdline = repr(subprocess.list2cmdline(printable)) + execCmdLogger.debug("%s (cwd %s)", cmdline, cwd) + + p = CPopen(command, close_fds=True, cwd=cwd, env=env, + deathSignal=deathSignal, childUmask=childUmask) + p = AsyncProc(p) + if not sync: + if data is not None: + p.stdin.write(data) + p.stdin.flush() + + return p + + (out, err) = p.communicate(data) + + if out is None: + # Prevent splitlines() from barfing later on + out = "" + + execCmdLogger.debug("%s: <err> = %s; <rc> = %d", + {True: "SUCCESS", False: "FAILED"}[p.returncode == 0], + repr(err), p.returncode) + + if not raw: + out = out.splitlines(False) + err = err.splitlines(False) + + return (p.returncode, out, err) + + +def retry(func, expectedException=Exception, tries=None, + timeout=None, sleep=1, stopCallback=None): + """ + Retry a function. Wraps the retry logic so you don't have to + implement it each time you need it. + + :param func: The callable to run. + :param expectedException: The exception you expect to receive when the + function fails. + :param tries: The number of times to try. None\0,-1 means infinite. + :param timeout: The time you want to spend waiting. This **WILL NOT** stop + the method. It will just not run it if it ended after the + timeout. + :param sleep: Time to sleep between calls in seconds. + :param stopCallback: A function that takes no parameters and causes the + method to stop retrying when it returns with a + positive value. + """ + if tries in [0, None]: + tries = -1 + + if timeout in [0, None]: + timeout = -1 + + startTime = time.time() + + while True: + tries -= 1 + try: + return func() + except expectedException: + if tries == 0: + raise + + if (timeout > 0) and ((time.time() - startTime) > timeout): + raise + + if stopCallback is not None and stopCallback(): + raise + + time.sleep(sleep) diff --git a/m4/ax_python_module.m4 b/m4/ax_python_module.m4 new file mode 100644 index 0000000..bd70a06 --- /dev/null +++ b/m4/ax_python_module.m4 @@ -0,0 +1,49 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_python_module.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_MODULE(modname[, fatal]) +# +# DESCRIPTION +# +# Checks for Python module. +# +# If fatal is non-empty then absence of a module will trigger an error. +# +# LICENSE +# +# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 5 + +AU_ALIAS([AC_PYTHON_MODULE], [AX_PYTHON_MODULE]) +AC_DEFUN([AX_PYTHON_MODULE],[ + if test -z $PYTHON; + then + PYTHON="python" + fi + PYTHON_NAME=`basename $PYTHON` + AC_MSG_CHECKING($PYTHON_NAME module: $1) + $PYTHON -c "import $1" 2>/dev/null + if test $? -eq 0; + then + AC_MSG_RESULT(yes) + eval AS_TR_CPP(HAVE_PYMOD_$1)=yes + else + AC_MSG_RESULT(no) + eval AS_TR_CPP(HAVE_PYMOD_$1)=no + # + if test -n "$2" + then + AC_MSG_ERROR(failed to find required module $1) + exit 1 + fi + fi +]) @@ -0,0 +1,114 @@ +#!/bin/sh -e + + +branch="master"; + + +set_hooks_commit_msg() +{ + f=".git/hooks/commit-msg"; + u="https://10.70.35.186:8443/tools/hooks/commit-msg"; + + if [ -x "$f" ]; then + return; + fi + + curl -k -o $f $u || wget --no-check-certificate -O $f $u; + + chmod +x .git/hooks/commit-msg; + + # Let the 'Change-Id: ' header get assigned on first run of rfc.sh + GIT_EDITOR=true git commit --amend; +} + + +is_num() +{ + local num; + + num="$1"; + + [ -z "$(echo $num | sed -e 's/[0-9]//g')" ] +} + + +rebase_changes() +{ + git fetch origin; + + GIT_EDITOR=$0 git rebase -i origin/$branch; +} + + +editor_mode() +{ + if [ $(basename "$1") = "git-rebase-todo" ]; then + sed 's/^pick /reword /g' "$1" > $1.new && mv $1.new $1; + return; + fi + + if [ $(basename "$1") = "COMMIT_EDITMSG" ]; then + if grep -qi '^BUG: ' $1; then + return; + fi + while true; do + echo Commit: "\"$(head -n 1 $1)\"" + echo -n "Enter Bug ID: " + read bug + if [ -z "$bug" ]; then + return; + fi + if ! is_num "$bug"; then + echo "Invalid Bug ID ($bug)!!!"; + continue; + fi + + sed "/^Change-Id:/{p; s/^.*$/BUG: $bug/;}" $1 > $1.new && \ + mv $1.new $1; + return; + done + fi + + cat <<EOF +$0 - editor_mode called on unrecognized file $1 with content: +$(cat $1) +EOF + return 1; +} + + +assert_diverge() +{ + git diff origin/$branch..HEAD | grep -q .; +} + + +main() +{ + set_hooks_commit_msg; + + if [ -e "$1" ]; then + editor_mode "$@"; + return; + fi + + rebase_changes; + + assert_diverge; + + bug=$(git show --format='%b' | grep -i '^BUG: ' | awk '{print $2}'); + + if [ "$DRY_RUN" = 1 ]; then + drier='echo -e Please use the following command to send your commits to review:\n\n' + else + drier= + fi + + if [ -z "$bug" ]; then + $drier git push origin HEAD:refs/for/$branch/rfc; + else + $drier git push origin HEAD:refs/for/$branch/bug-$bug; + fi +} + +main "$@" diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..a4fc465 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,51 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +test_modules = \ + utilsTests.py \ + $(NULL) + +dist_glusternagioscommontests_DATA = \ + $(NULL) + +dist_glusternagioscommontests_PYTHON = \ + $(test_modules) \ + testrunner.py \ + testValidation.py \ + $(NULL) + +dist_glusternagioscommontests_SCRIPTS = \ + run_tests.sh \ + $(NULL) + +dist_noinst_DATA = \ + run_tests_local.sh \ + $(NULL) + +CLEANFILES = \ + $(NULL) + +all-local: \ + $(nodist_glusternagioscommontests_PYTHON) + +check-local: + @echo '*** Running tests. To skip this step place NOSE_EXCLUDE=.* ***' + @echo '*** into your environment. Do not submit untested code! ***' + $(top_srcdir)/tests/run_tests_local.sh $(test_modules) diff --git a/tests/README b/tests/README new file mode 100644 index 0000000..3681917 --- /dev/null +++ b/tests/README @@ -0,0 +1,11 @@ +This test suite is built on the Nose framework. For more information see: + + http://readthedocs.org/docs/nose/en/latest/ + +Running the test suite: +----------------------- + +Running these tests is easy. + +~$ python testrunner.py +~$ python testrunner.py *.py diff --git a/tests/run_tests.sh.in b/tests/run_tests.sh.in new file mode 100644 index 0000000..ff91cdb --- /dev/null +++ b/tests/run_tests.sh.in @@ -0,0 +1,9 @@ +#!/bin/sh +if [ -z "$PYTHON_EXE" ]; then + PYTHON_EXE="@PYTHON@" +fi + +prefix="@prefix@" +exec_prefix="@exec_prefix@" +pyexecdir="@pyexecdir@" +LC_ALL=C PYTHONPATH="@glusternagioscommonpylibdir@:" "$PYTHON_EXE" @top_srcdir@/tests/testrunner.py $@ diff --git a/tests/run_tests_local.sh.in b/tests/run_tests_local.sh.in new file mode 100644 index 0000000..39fc36a --- /dev/null +++ b/tests/run_tests_local.sh.in @@ -0,0 +1,6 @@ +#!/bin/sh +if [ -z "$PYTHON_EXE" ]; then + PYTHON_EXE="@PYTHON@" +fi + +PYTHONDONTWRITEBYTECODE=1 LC_ALL=C PYTHONPATH="@top_srcdir@:$PYTHONPATH" "$PYTHON_EXE" @top_srcdir@/tests/testrunner.py $@ diff --git a/tests/testValidation.py b/tests/testValidation.py new file mode 100644 index 0000000..b46c9f8 --- /dev/null +++ b/tests/testValidation.py @@ -0,0 +1,141 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +## This framework is mostly copied from vdsm test framework + +import os +from nose.plugins.skip import SkipTest +from functools import wraps +from nose.plugins import Plugin +import subprocess + + +class SlowTestsPlugin(Plugin): + """Skips tests that might be too slow to be run for quick iteration + builds""" + name = 'slowtests' + enabled = False + + def add_options(self, parser, env=os.environ): + env_opt = 'NOSE_SKIP_SLOW_TESTS' + if env is None: + default = False + else: + default = env.get(env_opt) + + parser.add_option('--without-slow-tests', + action='store_true', + default=default, + dest='disable_slow_tests', + help='Some tests might take a long time to run, ' + + 'use this to skip slow tests automatically.' + + ' [%s]' % env_opt) + + def configure(self, options, conf): + Plugin.configure(self, options, conf) + if options.disable_slow_tests: + SlowTestsPlugin.enabled = True + + +class StressTestsPlugin(Plugin): + """ + Denotes a test which stresses the resources of the system under test. Such + tests should probably not be run in parallel. This plugin provides a + mechanism for parallel testing applications to skip stress tests. + """ + name = 'nonparalleltests' + enabled = False + + def add_options(self, parser, env=os.environ): + env_opt = 'NOSE_SKIP_STRESS_TESTS' + if env is None: + default = False + else: + default = env.get(env_opt) + + parser.add_option('--without-stress-tests', + action='store_true', + default=default, + dest='disable_stress_tests', + help='Some tests stress the resources of the ' + + 'system under test. Use this option to skip' + + 'these tests (eg. when doing parallel' + + 'testing [%s]' % env_opt) + + def configure(self, options, conf): + Plugin.configure(self, options, conf) + if options.disable_stress_tests: + StressTestsPlugin.enabled = True + + +def ValidateRunningAsRoot(f): + @wraps(f) + def wrapper(*args, **kwargs): + if os.geteuid() != 0: + raise SkipTest("This test must be run as root") + + return f(*args, **kwargs) + + return wrapper + + +def slowtest(f): + @wraps(f) + def wrapper(*args, **kwargs): + if SlowTestsPlugin.enabled: + raise SkipTest("Slow tests have been disabled") + + return f(*args, **kwargs) + + return wrapper + + +def brokentest(msg="Test failed but it is known to be broken"): + def wrap(f): + @wraps(f) + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except: + raise SkipTest(msg) + return wrapper + + return wrap + + +def stresstest(f): + @wraps(f) + def wrapper(*args, **kwargs): + if StressTestsPlugin.enabled: + raise SkipTest("Stress tests have been disabled") + + return f(*args, **kwargs) + + return wrapper + + +def checkSudo(cmd): + p = subprocess.Popen(['sudo', '-l', '-n'] + cmd, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = p.communicate() + + if p.returncode != 0: + raise SkipTest("Test requires SUDO configuration (%s)" % err.strip()) diff --git a/tests/testrunner.py b/tests/testrunner.py new file mode 100644 index 0000000..56f1416 --- /dev/null +++ b/tests/testrunner.py @@ -0,0 +1,308 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +## This framework is mostly copied from vdsm test framework + +import logging +import sys +import os +import unittest +import re +import shutil +import tempfile +from contextlib import contextmanager +from glusternagios import utils +# Monkey patch pthreading in necessary +if sys.version_info[0] == 2: + # as long as we work with Python 2, we need to monkey-patch threading + # module before it is ever used. + import pthreading + pthreading.monkey_patch() +from nose import config +from nose import core +from nose import result + +from testValidation import SlowTestsPlugin, StressTestsPlugin + + +class TermColor(object): + black = 30 + red = 31 + green = 32 + yellow = 33 + blue = 34 + magenta = 35 + cyan = 36 + white = 37 + + +def colorWrite(stream, text, color): + if os.isatty(stream.fileno()) or os.environ.get("NOSE_COLOR", False): + stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) + else: + stream.write(text) + + +@contextmanager +def temporaryPath(perms=None, data=None): + fd, src = tempfile.mkstemp() + if data is not None: + f = os.fdopen(fd, "wb") + f.write(data) + f.flush() + f.close() + else: + os.close(fd) + if perms is not None: + os.chmod(src, perms) + try: + yield src + finally: + os.unlink(src) + + +@contextmanager +def namedTemporaryDir(): + tmpDir = tempfile.mkdtemp() + try: + yield tmpDir + finally: + shutil.rmtree(tmpDir) + + +# FIXME: This is a forward port of the assertRaises from python +# 2.7, remove when no longer supporting earlier versions +class _AssertRaisesContext(object): + """A context manager used to implement TestCase.assertRaises* methods.""" + + def __init__(self, expected, test_case, expected_regexp=None): + self.expected = expected + self.failureException = test_case.failureException + self.expected_regexp = expected_regexp + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + try: + exc_name = self.expected.__name__ + except AttributeError: + exc_name = str(self.expected) + raise self.failureException( + "{0} not raised".format(exc_name)) + if not issubclass(exc_type, self.expected): + # let unexpected exceptions pass through + return False + self.exception = exc_value # store for later retrieval + if self.expected_regexp is None: + return True + + expected_regexp = self.expected_regexp + if isinstance(expected_regexp, basestring): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(str(exc_value)): + raise self.failureException('"%s" does not match "%s"' % + (expected_regexp.pattern, + str(exc_value))) + return True + + +# FIXME: This is a forward port of the assertIn from python +# 2.7, remove when no longer supporting earlier versions +def safe_repr(obj, short=False): + _MAX_LENGTH = 80 + try: + result = repr(obj) + except Exception: + result = object.__repr__(obj) + if not short or len(result) < _MAX_LENGTH: + return result + return result[:_MAX_LENGTH] + ' [truncated]...' + + +class GlusterNagiosTestCase(unittest.TestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + self.log = logging.getLogger(self.__class__.__name__) + + def retryAssert(self, *args, **kwargs): + '''Keep retrying an assertion if AssertionError is raised. + See function utils.retry for the meaning of the arguments. + ''' + return utils.retry(expectedException=AssertionError, *args, **kwargs) + + # FIXME: This is a forward port of the assertRaises from python + # 2.7, remove when no longer supporting earlier versions + def assertRaises(self, excClass, callableObj=None, *args, **kwargs): + context = _AssertRaisesContext(excClass, self) + if callableObj is None: + return context + with context: + callableObj(*args, **kwargs) + + # FIXME: This is a forward port of the assertIn from python + # 2.7, remove when no longer supporting earlier versions + def assertIn(self, member, container, msg=None): + """ + Just like self.assertTrue(a in b), but with a nicer default message. + """ + if member not in container: + if msg is None: + msg = '%s not found in %s' % (safe_repr(member), + safe_repr(container)) + raise self.failureException(msg) + + # FIXME: This is a forward port of the assertNotIn from python + # 2.7, remove when no longer supporting earlier versions + def assertNotIn(self, member, container, msg=None): + """ + Just like self.assertTrue(a not in b), but with a nicer default message + """ + if member in container: + if msg is None: + msg = '%s unexpectedly found in %s' % (safe_repr(member), + safe_repr(container)) + raise self.failureException(msg) + + +class GlusterNagiosTestResult(result.TextTestResult): + def __init__(self, *args, **kwargs): + result.TextTestResult.__init__(self, *args, **kwargs) + self._last_case = None + + def getDescription(self, test): + return str(test) + + def _writeResult(self, test, long_result, color, short_result, success): + if self.showAll: + colorWrite(self.stream, long_result, color) + self.stream.writeln() + elif self.dots: + self.stream.write(short_result) + self.stream.flush() + + def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) + self._writeResult(test, 'OK', TermColor.green, '.', True) + + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self._writeResult(test, 'FAIL', TermColor.red, 'F', False) + + def addSkip(self, test, reason): + # 2.7 skip compat + from nose.plugins.skip import SkipTest + if SkipTest in self.errorClasses: + storage, label, isfail = self.errorClasses[SkipTest] + storage.append((test, reason)) + self._writeResult(test, 'SKIP : %s' % reason, TermColor.blue, 'S', + True) + + def addError(self, test, err): + stream = getattr(self, 'stream', None) + ec, ev, tb = err + try: + exc_info = self._exc_info_to_string(err, test) + except TypeError: + # 2.3 compat + exc_info = self._exc_info_to_string(err) + for cls, (storage, label, isfail) in self.errorClasses.items(): + if result.isclass(ec) and issubclass(ec, cls): + if isfail: + test.passed = False + storage.append((test, exc_info)) + # Might get patched into a streamless result + if stream is not None: + if self.showAll: + message = [label] + detail = result._exception_detail(err[1]) + if detail: + message.append(detail) + stream.writeln(": ".join(message)) + elif self.dots: + stream.write(label[:1]) + return + self.errors.append((test, exc_info)) + test.passed = False + if stream is not None: + self._writeResult(test, 'ERROR', TermColor.red, 'E', False) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + current_case = test.test.__class__.__name__ + + if self.showAll: + if current_case != self._last_case: + self.stream.writeln(current_case) + self._last_case = current_case + + self.stream.write( + ' %s' % str(test.test._testMethodName).ljust(60)) + self.stream.flush() + + +class GlusterNagiosTestRunner(core.TextTestRunner): + def __init__(self, *args, **kwargs): + core.TextTestRunner.__init__(self, *args, **kwargs) + + def _makeResult(self): + return GlusterNagiosTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + def run(self, test): + result_ = core.TextTestRunner.run(self, test) + return result_ + + +def run(): + argv = sys.argv + stream = sys.stdout + verbosity = 3 + testdir = os.path.dirname(os.path.abspath(__file__)) + + conf = config.Config(stream=stream, + env=os.environ, + verbosity=verbosity, + workingDir=testdir, + plugins=core.DefaultPluginManager()) + conf.plugins.addPlugin(SlowTestsPlugin()) + conf.plugins.addPlugin(StressTestsPlugin()) + + runner = GlusterNagiosTestRunner(stream=conf.stream, + verbosity=conf.verbosity, + config=conf) + + sys.exit(not core.run(config=conf, testRunner=runner, argv=argv)) + + +def findRemove(listR, value): + """used to test if a value exist, if it is, return true and remove it.""" + try: + listR.remove(value) + return True + except ValueError: + return False + + +if __name__ == '__main__': + run() diff --git a/tests/utilsTests.py b/tests/utilsTests.py new file mode 100644 index 0000000..a608dd9 --- /dev/null +++ b/tests/utilsTests.py @@ -0,0 +1,78 @@ +# +# Copyright 2014 Red Hat, Inc. +# +# 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 +# +# Refer to the README and COPYING files for full details of the license +# + +import errno + +from testrunner import GlusterNagiosTestCase as TestCaseBase +from glusternagios import utils + + +class RetryTests(TestCaseBase): + def testStopCallback(self): + counter = [0] + limit = 4 + + def stopCallback(): + counter[0] += 1 + if counter[0] == limit: + return True + + return False + + def foo(): + raise RuntimeError("If at first you don't succeed, try, try again." + "Then quit. There's no point in being a damn" + "fool about it.") + # W. C. Fields + + self.assertRaises(RuntimeError, utils.retry, foo, tries=(limit + 10), + sleep=0, stopCallback=stopCallback) + # Make sure we had the proper amount of iterations before failing + self.assertEquals(counter[0], limit) + + +class CommandPathTests(TestCaseBase): + def testExisting(self): + cp = utils.CommandPath('sh', 'utter nonsense', '/bin/sh') + self.assertEquals(cp.cmd, '/bin/sh') + + def testMissing(self): + NAME = 'nonsense' + try: + utils.CommandPath(NAME, 'utter nonsense').cmd + except OSError as e: + self.assertEquals(e.errno, errno.ENOENT) + self.assertIn(NAME, e.strerror) + + +class ExecCmdTests(TestCaseBase): + def testSuccess(self): + (rc, out, err) = utils.execCmd(["true"]) + self.assertEquals(rc, 0) + + def testFailure(self): + (rc, out, err) = utils.execCmd(["false"]) + self.assertEquals(rc, 1) + + def testOSError(self): + def _runUnknown(): + (rc, out, err) = utils.execCmd(["unknown"]) + + self.assertRaises(OSError, _runUnknown) |