├── .gitarchivever ├── .gitattributes ├── .gitignore ├── .travis.yml ├── AUTHORS ├── COPYING ├── Makefile.am ├── README ├── README-testing ├── README.upgrade-from-v0.1 ├── autogen.sh ├── booth-rpmlintrc ├── booth.pc.in ├── booth.spec.in ├── build-aux ├── PKG_CHECK_VAR.m4 ├── git-version-gen ├── gitlog-to-changelog └── release.mk ├── conf ├── Makefile.am ├── booth-arbitrator.service.in ├── booth.conf.example └── booth@.service.in ├── configure.ac ├── contrib └── geo-cluster.firewalld.xml ├── docs ├── Makefile.am ├── booth-keygen.8.txt ├── boothd.8.txt ├── fsm-full.dot ├── fsm-netfail.dot ├── fsm-normal.dot └── geostore.8.txt ├── icons └── booth.svg ├── script ├── booth-keygen ├── lsb │ └── booth-arbitrator ├── ocf │ ├── booth-site │ ├── geo_attr.sh │ ├── geostore │ └── sharedrsc ├── service-runnable.in └── wireshark-dissector.lua ├── src ├── Makefile.am ├── alt │ ├── logging_libqb.c │ ├── logging_libqb.h │ ├── nametag_libsystemd.c │ ├── nametag_libsystemd.h │ ├── range2random_glib.c │ └── range2random_glib.h ├── attr.c ├── attr.h ├── auth.c ├── auth.h ├── booth.h ├── booth_config.h.in ├── config.c ├── config.h ├── handler.c ├── handler.h ├── inline-fn.h ├── log.h ├── main.c ├── manual.c ├── manual.h ├── pacemaker.c ├── pacemaker.h ├── raft.c ├── raft.h ├── request.c ├── request.h ├── ticket.c ├── ticket.h ├── timer.c ├── timer.h ├── transport.c └── transport.h └── test ├── arbtests.py ├── assertions.py ├── booth_path ├── boothrunner.py ├── boothtestenv.py.in ├── clientenv.py ├── clienttests.py ├── live_test.sh ├── runtests.py.in ├── serverenv.py ├── servertests.py ├── sitetests.py └── utils.py /.gitarchivever: -------------------------------------------------------------------------------- 1 | ref names: (HEAD -> main) 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .gitarchivever export-subst 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile.in 2 | Makefile 3 | compile 4 | autom4te.cache 5 | aclocal.m4 6 | config.guess 7 | config.status 8 | config.sub 9 | configure 10 | depcomp 11 | install-sh 12 | missing 13 | test-driver 14 | *.trs 15 | *.log 16 | *.rpm 17 | *.o 18 | .deps 19 | .version 20 | .dirstamp 21 | 22 | booth-*.tar* 23 | 24 | conf/booth*.service 25 | docs/*.8 26 | docs/*.8.html 27 | script/service-runnable 28 | src/b_config.h.in 29 | src/b_config.h 30 | src/booth_config.h 31 | src/boothd 32 | src/stamp-h1 33 | src/stamp-h2 34 | test/boothtestenv.py 35 | test/runtests.py 36 | 37 | booth.spec 38 | booth.pc 39 | 40 | # cscope files 41 | cscope.* 42 | ncscope.* 43 | 44 | # ctags files 45 | tags 46 | 47 | # vim temp files 48 | .*.sw? 49 | *~ 50 | 51 | # test suite random files 52 | *.pyc 53 | __pycache__ 54 | 55 | cov* 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # container-based environment blockers: 2 | # - cluster-glue-dev 3 | # https://github.com/travis-ci/apt-package-whitelist/issues/2936 4 | # - libsystemd-daemon-dev (libsystemd-dev) 5 | # https://github.com/travis-ci/apt-package-whitelist/issues/2449 6 | # - likewise, lack of pacemaker packages (sources contain setuid et al., 7 | # can be worked around with something like (or local dir + CPPFLAGS): 8 | # GHREPO=ClusterLabs/pacemaker INCLFILE=crm/services.h curl --create-dirs \ 9 | # -o "/usr/include/pacemaker/${INCLFILE}" \ 10 | # "https://raw.githubusercontent.com/${GHREPO}/master/include/${INCLFILE}" 11 | sudo: required 12 | dist: trusty 13 | 14 | language: c 15 | compiler: gcc 16 | 17 | env: 18 | - GLUE=1 19 | - GLUE=0 20 | 21 | before_install: 22 | # following command is so as to counterweight unfortunate change 23 | # https://github.com/travis-ci/travis-cookbooks/commit/6c575d5d55c08e3a0c046dc7de2aa5d5b38e0b63 24 | # that made proper hostnames be mapped from 127.0.1.1 address 25 | # rather than 127.0.0.1 (properly assigned to loopback interface), 26 | # hence (likely) caused "hostname -i" return that other address 27 | # that is normally not assigned to loopback (and hence booth 28 | # cannot identify "itself" within configured sites, leading to 29 | # spurious test suite failure) so do that manually as a workaround 30 | - sudo ip addr add 127.0.1.1/8 scope host dev lo 31 | - sudo apt-get update -qq 32 | - sudo apt-get install -qq -y libglib2.0-dev libcrmservice1-dev 33 | # sadly, the newest supported at the moment Ubuntu is "Trusty" 34 | # that carries backdated 1.1.10+git20130802-1ubuntu2.5 pacemaker, 35 | # moreover with broken split of files on packaging level (or was it 36 | # due to inherently inseparable cross-dependencies between headers?), 37 | # hence the following is needed as well (will go away, eventually) 38 | - sudo apt-get install -qq -y libcrmcluster4-dev 39 | - test "${GLUE}" = 0 40 | || sudo apt-get install -qq -y cluster-glue-dev 41 | - test "${GLUE}" != 0 42 | || sudo apt-get install -qq -y libqb-dev libsystemd-daemon-dev 43 | 44 | before_script: 45 | - ./autogen.sh 46 | && ./configure --with-glue=$(test "${GLUE}" = 0 && echo no || echo yes) 47 | - ulimit -S -c unlimited # ensure cores are generated (~after_failure) 48 | 49 | script: VERBOSE=1 make check 50 | 51 | after_failure: 52 | - sudo apt-get install -qq gdb libc6-dbg 53 | # examine core files 54 | - find -name 'core*' -print0 55 | | xargs -0I@ -- sh -xc 56 | 'file "@"; 57 | gdb -se $(file "@" | sed -n "s|.* core file .* from \x27\([^\x27 ]*\).*|\1|p") 58 | -c "@" -batch -ex "t a a bt f" -ex "set pagination 0"' 59 | 60 | notifications: 61 | irc: "irc.freenode.net#clusterlabs-dev" 62 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Adam Spiers 2 | Daniel Gollub 3 | Dejan Muhamedagic 4 | Dirk Mueller 5 | Dongmao Zhang 6 | Ferritt1975 7 | Florian Haas 8 | Guangliang Zhao 9 | Jiaju Zhang 10 | Joerg Frede 11 | Kazunori INOUE 12 | Philipp Marek 13 | seabres 14 | Steven Dake 15 | Xia Li 16 | Yuichi SEINO 17 | Yuusuke Iida 18 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Red Hat, Inc. 2 | # 3 | # Authors: Andrew Beekhof 4 | # Steven Dake (sdake@redhat.com) 5 | # 6 | # This software licensed under BSD license, the text of which follows: 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # - Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # - Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # - Neither the name of the MontaVista Software, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived from this 18 | # software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 30 | # THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | SPEC = $(PACKAGE_NAME).spec 33 | 34 | TARFILE = $(PACKAGE_NAME)-$(VERSION).tar.gz 35 | 36 | EXTRA_DIST = autogen.sh conf/booth.conf.example \ 37 | script/booth-keygen script/lsb script/ocf script/service-runnable.in \ 38 | script/wireshark-dissector.lua \ 39 | test/arbtests.py test/assertions.py test/booth_path test/boothrunner.py \ 40 | test/boothtestenv.py.in test/clientenv.py test/clienttests.py test/live_test.sh \ 41 | test/runtests.py.in test/serverenv.py test/servertests.py test/sitetests.py \ 42 | test/utils.py \ 43 | contrib \ 44 | icons \ 45 | $(SPEC).in booth-rpmlintrc \ 46 | .version build-aux/git-version-gen build-aux/PKG_CHECK_VAR.m4 \ 47 | build-aux/gitlog-to-changelog build-aux/release.mk 48 | 49 | AUTOMAKE_OPTIONS = foreign 50 | 51 | MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure depcomp \ 52 | config.guess config.sub missing install-sh \ 53 | autoheader automake autoconf test_lense.sh \ 54 | compile 55 | 56 | # Don't try to install files outside build directory for "make distcheck". 57 | AM_DISTCHECK_CONFIGURE_FLAGS = --with-ocfdir="$$dc_install_base/lib/ocf" 58 | 59 | dist_doc_DATA = AUTHORS README COPYING README.upgrade-from-v0.1 README-testing 60 | 61 | boothconfdir = ${BOOTHSYSCONFDIR} 62 | 63 | boothconf_DATA = conf/booth.conf.example 64 | 65 | boothsitedir = $(ocfdir)/resource.d/pacemaker 66 | boothsite_SCRIPTS = script/ocf/booth-site 67 | 68 | boothocfdir = $(ocfdir)/resource.d/booth 69 | boothocf_SCRIPTS = script/ocf/sharedrsc script/ocf/geostore 70 | 71 | boothocflibdir = $(ocfdir)/lib/booth 72 | boothocflib_DATA = script/ocf/geo_attr.sh 73 | 74 | bootharbitratordir = ${INITDDIR} 75 | 76 | bootharbitrator_SCRIPTS = script/lsb/booth-arbitrator 77 | 78 | boothnoarchdir = $(datadir)/$(PACKAGE_NAME) 79 | 80 | nodist_boothnoarch_SCRIPTS = script/service-runnable 81 | 82 | sbin_SCRIPTS = script/booth-keygen 83 | 84 | pkgconfigdir = $(datadir)/pkgconfig 85 | pkgconfig_DATA = booth.pc 86 | 87 | TESTS = test/runtests.py 88 | 89 | SUBDIRS = src docs conf 90 | 91 | MOCK_DIR = $(abs_builddir)/mock 92 | MOCK_OPTIONS ?= --resultdir="$(MOCK_DIR)" --no-cleanup-after 93 | 94 | coverity: 95 | cov-build --dir=cov make 96 | cov-analyze --dir cov --concurrency --wait-for-license 97 | cov-format-errors --dir cov 98 | 99 | install-exec-local: 100 | $(INSTALL) -d $(DESTDIR)/${boothconfdir} 101 | $(INSTALL) -d $(DESTDIR)/${bootharbitratordir} 102 | $(INSTALL) -d $(DESTDIR)/${boothsitedir} 103 | $(INSTALL) -d $(DESTDIR)/${boothocfdir} 104 | $(INSTALL) -d $(DESTDIR)/${BOOTH_LIB_DIR} -m 750 105 | -chown $(CRM_DAEMON_USER):$(CRM_DAEMON_GROUP) $(DESTDIR)/${BOOTH_LIB_DIR} 106 | $(INSTALL) -d $(DESTDIR)/${BOOTH_CORE_DIR} -m 750 107 | -chown $(CRM_DAEMON_USER):$(CRM_DAEMON_GROUP) $(DESTDIR)/${BOOTH_CORE_DIR} 108 | 109 | install-exec-hook: 110 | ln -sf ${sbindir}/boothd $(DESTDIR)/${sbindir}/booth 111 | ln -sf ${sbindir}/boothd $(DESTDIR)/${sbindir}/geostore 112 | 113 | uninstall-local: 114 | rmdir $(DESTDIR)/${boothconfdir} || :; 115 | rmdir $(DESTDIR)/${bootharbitratordir} || :; 116 | rmdir $(DESTDIR)/${boothsitedir} || :; 117 | rmdir $(DESTDIR)/${BOOTH_CORE_DIR} || :; 118 | rmdir $(DESTDIR)/${BOOTH_LIB_DIR} || :; 119 | 120 | test: check 121 | 122 | clean-local: 123 | rm -rf test/*.pyc test/__pycache__ test/runtests.py test/boothtestenv.py cov* $(SPEC) 124 | -rm -rf booth-*.rpm $(TARFILE) 125 | 126 | dist-clean-local: 127 | rm -f autoconf automake autoheader 128 | 129 | # release/versioning 130 | BUILT_SOURCES = .version 131 | .version: 132 | echo $(VERSION) > $@-t && mv $@-t $@ 133 | 134 | dist-hook: gen-ChangeLog 135 | echo $(VERSION) > $(distdir)/.tarball-version 136 | 137 | 138 | test/runtests.py: test/runtests.py.in test/boothtestenv.py 139 | rm -f $@-t $@ 140 | mkdir -p ${abs_top_builddir}/test 141 | sed \ 142 | -e 's#PYTHON_SHEBANG#${PYTHON_SHEBANG}#g' \ 143 | -e 's#TEST_SRC_DIR#${abs_top_srcdir}/test#g' \ 144 | -e 's#TEST_BUILD_DIR#${abs_top_builddir}/test#g' \ 145 | $< > $@-t; 146 | chmod a-w $@-t 147 | chmod u+x $@-t 148 | mv $@-t $@ 149 | 150 | test/boothtestenv.py: test/boothtestenv.py.in 151 | rm -f $@-t $@ 152 | mkdir -p ${abs_top_builddir}/test 153 | sed \ 154 | -e 's#TEST_SRC_DIR#${abs_top_srcdir}/test#g' \ 155 | -e 's#TEST_BUILD_DIR#${abs_top_builddir}/test#g' \ 156 | $< > $@-t; 157 | chmod a-w $@-t 158 | mv $@-t $@ 159 | 160 | ## make rpm/srpm section. 161 | $(abs_builddir)/booth-rpmlintrc: 162 | cat $(abs_srcdir)/booth-rpmlintrc > booth-rpmlintrc 163 | 164 | $(SPEC): $(SPEC).in .version config.status $(abs_builddir)/booth-rpmlintrc 165 | rm -f $@-t $@ 166 | date="$(shell LC_ALL=C date "+%a %b %d %Y")" && \ 167 | gvgver="`cd $(abs_srcdir); build-aux/git-version-gen --fallback $(VERSION) .tarball-version .gitarchivever`" && \ 168 | if [ "$$gvgver" = "`echo $$gvgver | sed 's/-/./'`" ];then \ 169 | rpmver="$$gvgver" && \ 170 | alphatag="" && \ 171 | dirty="" && \ 172 | numcomm="0"; \ 173 | else \ 174 | gitver="`echo $$gvgver | sed 's/\(.*\)\./\1-/'`" && \ 175 | rpmver=`echo $$gitver | sed 's/-.*//g'` && \ 176 | alphatag=`echo $$gvgver | sed 's/[^-]*-\([^-]*\).*/\1/'` && \ 177 | numcomm=`echo $$gitver | sed 's/[^-]*-\([^-]*\).*/\1/'` && \ 178 | dirty="" && \ 179 | if [ "`echo $$gitver | sed 's/^.*-dirty$$//g'`" = "" ];then \ 180 | dirty="dirty"; \ 181 | fi \ 182 | fi && \ 183 | if [ "$$numcomm" = "0" ]; then numcomm=""; fi && \ 184 | if [ -n "$$numcomm" ]; then numcomm="%global numcomm $$numcomm"; fi && \ 185 | if [ "$$alphatag" = "$$gitver" ]; then alphatag=""; fi && \ 186 | if [ -n "$$alphatag" ]; then alphatag="%global alphatag $$alphatag"; fi && \ 187 | if [ -n "$$dirty" ]; then dirty="%global dirty dirty"; fi && \ 188 | sed \ 189 | -e "s#@version@#$$rpmver#g" \ 190 | -e "s#@ALPHATAG@#$$alphatag#g" \ 191 | -e "s#@NUMCOMM@#$$numcomm#g" \ 192 | -e "s#@DIRTY@#$$dirty#g" \ 193 | -e "s#@date@#$$date#g" \ 194 | $(abs_srcdir)/$@.in > $@-t; 195 | sed -i -e "s#@uname@#$(CRM_DAEMON_USER)#g" $@-t 196 | sed -i -e "s#@gname@#$(CRM_DAEMON_GROUP)#g" $@-t 197 | if BUILD_ASCIIDOC_HTML_MAN 198 | sed -i -e "s#@bcond_html_man@#bcond_without#g" $@-t 199 | else 200 | sed -i -e "s#@bcond_html_man@#bcond_with#g" $@-t 201 | endif 202 | if IS_ASCIIDOC 203 | sed -i -e "s#@asciidoc@#asciidoc#g" $@-t 204 | else 205 | sed -i -e "s#@asciidoc@#asciidoctor#g" $@-t 206 | endif 207 | if LOGGING_LIBQB 208 | sed -i -e "s#@bcond_glue@#bcond_with#g" $@-t 209 | else 210 | sed -i -e "s#@bcond_glue@#bcond_without#g" $@-t 211 | endif 212 | if PYTHON_IS_VERSION3 213 | sed -i -e "s#@bcond_python3@#bcond_without#g" $@-t 214 | else 215 | sed -i -e "s#@bcond_python3@#bcond_with#g" $@-t 216 | endif 217 | if RUN_BUILD_TESTS 218 | sed -i -e "s#@bcond_run_build_tests@#bcond_without#g" $@-t 219 | else 220 | sed -i -e "s#@bcond_run_build_tests@#bcond_with#g" $@-t 221 | endif 222 | chmod a-w $@-t 223 | mv $@-t $@ 224 | rm -f $@-t* 225 | 226 | $(TARFILE): 227 | $(MAKE) dist 228 | 229 | RPMBUILDOPTS = --define "_sourcedir $(abs_builddir)" \ 230 | --define "_specdir $(abs_builddir)" \ 231 | --define "_builddir $(abs_builddir)" \ 232 | --define "_srcrpmdir $(abs_builddir)" \ 233 | --define "_rpmdir $(abs_builddir)" 234 | 235 | srpm: clean 236 | $(MAKE) $(SPEC) $(TARFILE) 237 | rpmbuild $(RPMBUILDOPTS) --nodeps -bs $(SPEC) 238 | 239 | rpm: clean 240 | $(MAKE) $(SPEC) $(TARFILE) 241 | rpmbuild $(RPMBUILDOPTS) -ba $(SPEC) 242 | 243 | mock-%: clean srpm 244 | mock $(MOCK_OPTIONS) --root=$* --no-cleanup-after --rebuild \ 245 | "$(abs_top_builddir)"/*.src.rpm 246 | 247 | .PHONY: mock-clean 248 | mock-clean: 249 | -rm -rf "$(MOCK_DIR)" 250 | 251 | gen_start_date = 2000-01-01 252 | .PHONY: gen-ChangeLog 253 | gen-ChangeLog: 254 | if test -d $(abs_srcdir)/.git; then \ 255 | LC_ALL=C $(top_srcdir)/build-aux/gitlog-to-changelog \ 256 | --since=$(gen_start_date) > $(distdir)/cl-t; \ 257 | rm -f $(distdir)/ChangeLog; \ 258 | mv $(distdir)/cl-t $(distdir)/ChangeLog; \ 259 | fi 260 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The Booth Cluster Ticket Manager 2 | ============= 3 | 4 | Booth manages tickets which authorize cluster sites located in 5 | geographically dispersed locations to run resources. It 6 | facilitates support of geographically distributed clustering in 7 | Pacemaker. 8 | 9 | Booth is based on the Raft consensus algorithm. Though the 10 | implementation is not complete (there is no log) and there are a 11 | few additions and modifications, booth guarantees that a ticket 12 | is always available at just one site as long as it has exclusive 13 | control of the tickets. 14 | 15 | The git repository is available at github: 16 | 17 | 18 | 19 | github can also track issues or bug reports. 20 | 21 | Description of a booth cluster 22 | ============================== 23 | 24 | Booth cluster is a collection of cooperative servers 25 | communicating using the booth protocol. The purpose of the booth 26 | cluster is to manage cluster tickets. The booth cluster consists 27 | of at least three servers. 28 | 29 | A booth server can be either a site or an arbitrator. Arbitrators 30 | take part in elections and so help resolve ties, but cannot hold 31 | tickets. 32 | 33 | The basic unit in the booth cluster is a ticket. Every 34 | non-granted ticket is in the initial state on all servers. For 35 | granted tickets, the server holding the ticket is the leader and 36 | other servers are followers. The leader issues heartbeats and 37 | ticket updates to the followers. The followers are required to 38 | obey the leader. 39 | 40 | Booth startup 41 | ------------ 42 | 43 | On startup, the booth process first loads tickets, if available, 44 | from the CIB. Afterwards, it broadcasts a query to get tickets' 45 | status from other servers. In-memory copies are updated from 46 | the replies if they contain newer ticket data. 47 | 48 | If the server discovers that itself is the ticket leader, it 49 | tries to establish its authority again by broadcasting heartbeat. 50 | If it succeeds, it continues as the leader for this ticket. The 51 | other booth servers become followers. This procedure is possible 52 | only immediately after the booth startup. It also serves as a 53 | configuration reload. 54 | 55 | Grant and revoke operations 56 | ------------ 57 | 58 | A ticket first has to be granted using the 'booth client grant' 59 | command. 60 | 61 | Obviously, it is not possible to grant a ticket which is 62 | currently granted. 63 | 64 | Ticket revoke is the operation which is the opposite of grant. 65 | An administrative revoke may be started at any server, but the 66 | operation itself happens only at the leader. If the leader is 67 | unreachable, the ticket cannot be revoked. The user will need to 68 | wait until the ticket expires. 69 | 70 | A ticket grant may be delayed if not all sites are reachable. 71 | The delay is the ticket expiry time extended by acquire-after, if 72 | set. This is to ensure that the unreachable site relinquished the 73 | ticket it may have been holding and stopped the corresponding 74 | cluster resources. 75 | 76 | If the user is absolutely sure that the unreachable site does not 77 | hold the ticket, the delay may be skipped by using the '-F' 78 | option of the 'booth grant' command. 79 | 80 | If in effect, the grant delay time is shown in the 'booth list' 81 | command output. 82 | 83 | Ticket management and server operation 84 | ------------ 85 | 86 | A granted ticket is managed by the booth servers so that its 87 | availability is maximized without breaking the basic guarantee 88 | that the ticket is granted to one site only. 89 | 90 | The server where the ticket is granted is the leader, the other 91 | servers are followers. The leader occasionally sends heartbeats, 92 | once every half ticket expiry under normal circumstances. 93 | 94 | If a follower doesn't hear from the leader longer than the ticket 95 | expiry time, it will consider the ticket lost, and try to acquire 96 | it by starting new elections. 97 | 98 | A server starts elections by broadcasting the REQ_VOTE RPC. 99 | Other servers reply with the VOTE_FOR RPC, in which they record 100 | its vote. Normally, the sender of the first REQ_VOTE gets the 101 | vote of the receiver. Whichever server gets a majority of votes 102 | wins the elections. On ties, elections are restarted. To 103 | decrease chance of elections ending in a tie, a server waits for a 104 | short random period before sending out the REQ_VOTE packets. 105 | Everything else being equal, the server which sends REQ_VOTE 106 | first gets elected. 107 | 108 | Elections are described in more detail in the raft paper at 109 | . 110 | 111 | Ticket renewal (or update) is a two-step process. Before actually 112 | writing the ticket to the CIB, the server holding the ticket 113 | first tries to establish that it still has the majority for that 114 | ticket. That is done by broadcasting a heartbeat. If the server 115 | receives enough acknowledgements, it then stores the ticket to 116 | the CIB and broadcasts the UPDATE RPC with updated ticket expiry 117 | time so that the followers can update local ticket copies. Ticket 118 | renewals are configurable and by default set to half ticket 119 | expire time. 120 | 121 | Before ticket renewal, the leader runs one or more external 122 | programs if such are set in 'before-acquire-handler'. This can 123 | point either to a file or a directory. In the former case, that 124 | file is the program, but in the latter there could be a number of 125 | programs in the specified directory. All files which have the 126 | executable bit set and whose names don't start with a '.' are 127 | run sequentially. This program or programs should ensure that the 128 | cluster managed service which is protected by this ticket can run 129 | at this site. If any of them fails, the leader relinquishes the 130 | ticket. It announces its intention to step down by broadcasting 131 | an unsolicited VOTE_FOR with an empty vote. On receiving such RPC 132 | other servers start new elections to elect a new leader. 133 | 134 | Split brain 135 | ------------ 136 | 137 | On split brains two possible issues arise: leader in minority and 138 | follower disconnected from the leader. 139 | 140 | Let's take a look at the first one. The leader in minority 141 | eventually expires the ticket because it cannot receieve majority 142 | of acknowledgements in reply to its heartbeats. The other 143 | partition runs elections (at about the same time, as they find 144 | the ticket lost after its expiry) and, if it can get the 145 | majority, the elections winner becomes a new leader for the 146 | ticket. After split brain gets resolved, the old leader will 147 | become follower as soon as it receives heartbeat from the new 148 | leader. Note the timing: the old leader releases the ticket at 149 | around the same time as when new elections in the other partition 150 | are held. This is because the leader ensures that the ticket 151 | expire time is always the same on all servers in the booth 152 | cluster. 153 | 154 | The second situation, where a follower is disconnected from the 155 | leader, is a bit more difficult to handle. After the ticket 156 | expiry time, the follower will consider the ticket lost and start 157 | new elections. The elections repeatedly get restarted until the 158 | split brain is resolved. Then, the rest of the cluster send 159 | rejects in reply to REQ_VOTE RPC because the ticket is still 160 | valid and therefore couldn't have been lost. They know that 161 | because the reason for elections is included with every REQ_VOTE. 162 | 163 | Short intermittent split brains are handled well because the 164 | leader keeps resending heartbeats until it gets replies from all 165 | servers serving sites. 166 | 167 | Authentication 168 | ============== 169 | 170 | In order to prevent malicious parties from affecting booth 171 | operation, booth server can authenticate both clients (connecting 172 | over TCP) and other booth servers (connecting over UDP). The 173 | authentication is based on SHA1 HMAC (Keyed-Hashing Message 174 | Authentication) and shared key. The HMAC implementation is 175 | provided by the libgcrypt or mhash library. 176 | 177 | Message encryption is not included as the information exchanged 178 | between various booth parties does not seem to justify that. 179 | 180 | Every message (packet) contains a hash code computed from the 181 | combination of payload and the secret key. Whoever has the secret 182 | key can then verify that the message is authentic. 183 | 184 | The shared key is used by both the booth client and the booth 185 | server, hence it needs to be copied to all nodes at each site and 186 | all arbitrators. Of course, a secure channel is required for key 187 | transfer. It is recommended to use csync2 or ssh. 188 | 189 | Timestamps are included and verified to fend against replay 190 | attacks. Certain time skew, 10 minutes by default, is tolerated. 191 | Packets either not older than that or with a timestamp more 192 | recent than the previous one from the same peer are accepted. The 193 | time skew can be configured in the booth configuration file. 194 | 195 | # vim: set ft=asciidoc : 196 | -------------------------------------------------------------------------------- /README-testing: -------------------------------------------------------------------------------- 1 | There's a booth-test package which contains two types of tests. 2 | 3 | It installs the necessary files into `/usr/share/booth/tests`. 4 | 5 | === Live tests (booth operation) 6 | 7 | BEWARE: Run this with _test_ clusters only! 8 | 9 | The live testing utility tests booth operation using the given 10 | `booth.conf`: 11 | 12 | $ /usr/share/booth/tests/test/live_test.sh booth.conf 13 | 14 | It is possible to run only specific tests. Run the script without 15 | arguments to see usage and the list of tests and netem network 16 | emulation functions. 17 | 18 | There are some restrictions on how booth.conf is formatted. 19 | There may be several tickets defined and all of them will be 20 | tested, one after another (they will be tested separately). 21 | The tickets must have expire and timeout parameters configured. 22 | 23 | Example booth.conf: 24 | 25 | ------------ 26 | transport="UDP" 27 | port="9929" 28 | arbitrator="10.2.12.53" 29 | arbitrator="10.2.13.82" 30 | site="10.2.12.101" 31 | site="10.2.13.101" 32 | site="10.121.187.99" 33 | 34 | ticket="ticket-A" 35 | expire = 30 36 | timeout = 3 37 | retries = 3 38 | before-acquire-handler = /usr/share/booth/service-runnable d-src1 39 | ------------ 40 | 41 | A split brain condition is also tested. For that to work, all 42 | sites need `iptables` installed. The supplied script `booth_path` 43 | is used to manipulate iptables rules. 44 | 45 | ==== Pacemaker configuration 46 | 47 | This is a sample pacemaker configuration for a single-node 48 | cluster: 49 | 50 | primitive booth ocf:pacemaker:booth-site 51 | primitive d-src1 ocf:heartbeat:Dummy 52 | rsc_ticket global-d-src1 ticket-A: d-src1 53 | 54 | Additionally, you may also add an ocf:booth:sharedrsc resource to 55 | also check that the ticket is granted always to only one site: 56 | 57 | primitive shared ocf:booth:sharedrsc \ 58 | params dir="10.2.13.82:/var/tmp/boothtestdir" 59 | rsc_ticket global-shared ticket-A: shared 60 | 61 | Please adjust to your environment. 62 | 63 | ==== Network environment emulation 64 | 65 | To introduce packet loss or network delays, set the NETEM_ENV 66 | environment variable. There are currently three netem network 67 | emulation settings supported: 68 | 69 | - loss: all servers emulate packet loss (30% by default) 70 | 71 | - single_loss: the first site in the configuration emulates 72 | packet loss (30% by default) 73 | 74 | - net_delay: all servers emulate packet delay (100ms by default 75 | with random variation of 10%) 76 | 77 | The settings can be supplied by adding ':' to the emulator name. 78 | For instance: 79 | 80 | # NETEM_ENV=loss:50 /usr/share/booth/tests/test/live_test.sh booth.conf 81 | 82 | It is not necessary to run the test script on one of the sites. 83 | Just copy the script and make the test `booth.conf` available 84 | locally: 85 | 86 | $ scp testsite:/usr/share/booth/tests/test/live_test.sh . 87 | $ scp testsite:/etc/booth/booth.conf . 88 | $ sh live_test.sh booth.conf 89 | 90 | You need at least two sites and one arbitrator. 91 | 92 | The configuration can contain just one ticket. 93 | 94 | It is not necessary to configure the `before-acquire-handler`. 95 | 96 | Notes: 97 | 98 | - (BEWARE!) the supplied configuration files is copied to 99 | /etc/booth/booth.conf to all sites/arbitrators thus overwriting 100 | any existing configuration 101 | 102 | - the utility uses ssh to manage booth at all sites/arbitrators 103 | and logs in as user `root` 104 | 105 | - it is required that ssh public authentication works without 106 | providing the passphrase (otherwise it is impractical) 107 | 108 | - the log file is ./test_booth.log (it is actually a shell trace, 109 | with timestamps if you're running bash) 110 | 111 | - in case one of the tests fail, hb_report is created 112 | 113 | If you want to open a bug report, please attach all hb_reports 114 | and `test_booth.log`. 115 | 116 | 117 | 118 | 119 | === Simple tests (commandline, config file) 120 | 121 | 122 | Run (as non-root) 123 | 124 | # make check 125 | 126 | or 127 | 128 | # make test/runtests.py 129 | # python test/runtests.py 130 | 131 | to run the tests written in python. 132 | 133 | It is also possible to run the tests as a root when 134 | "--allow-root-user" parameter is used or if 135 | the BOOTH_RUNTESTS_ROOT_USER environment variable is defined. 136 | 137 | By default tests uses TCP port based on current PID in range 138 | from 9929 to 10937 to allow running multiple instances in parallel. 139 | It is possible to use "--single-instance" parameter or define 140 | BOOTH_RUNTESTS_SINGLE_INSTANCE environment variable to make tests use 141 | only single port (9929), but parallel instances will fail. 142 | 143 | 144 | # vim: set ft=asciidoc : 145 | -------------------------------------------------------------------------------- /README.upgrade-from-v0.1: -------------------------------------------------------------------------------- 1 | Notes on upgrade from PAXOS booth v0.1 2 | ====================== 3 | 4 | Booth v0.1 was a booth version based on the PAXOS algorithm. The 5 | current booth v0.2 is loosely based on raft and incompatible 6 | with the one running v0.1. Therefore, rolling upgrades are not 7 | possible. 8 | 9 | Due to the new multi-tenancy feature, the new arbitrator init 10 | script cannot stop or test status of the paxos v0.1 arbitrator. 11 | On upgrade to v0.2, the arbitrator, if running, will be stopped. 12 | 13 | The OCF resource-agent ocf:pacemaker:booth-site is capable of 14 | stopping and monitoring the booth v0.1 site daemon. 15 | 16 | Update procedure 17 | ---------------- 18 | 19 | The recommended procedure for update from the paxos booth to the 20 | new booth version is as follows: 21 | 22 | - convert the configuration file /etc/booth/booth.conf on all 23 | nodes and arbitrators to the new syntax 24 | 25 | - update booth on all arbitrators and start them 26 | 27 | - update booth on all nodes and restart the resource: 28 | 29 | # crm resource restart booth 30 | 31 | Configuration file syntax changes 32 | ---------------- 33 | 34 | .Note 35 | If you didn't specify expiry time or weights different from the 36 | defaults, then you can skip this section and use the old 37 | 'booth.conf'. 38 | 39 | The new booth configuration has many more options for tickets 40 | and the syntax for 'ticket' got changed. Whereas previously the 41 | optional expiry time and weights could be specified by appending 42 | them to the ticket name with a ';' as a separator, the new syntax 43 | has separate tokens for all ticket options. 44 | 45 | For instance, this ticket specification: 46 | 47 | ticket="tkt-A;600;1,2,2" 48 | 49 | looks in the new syntax like this: 50 | 51 | ticket="tkt-A" 52 | expire="600" 53 | weights="1,2,2" 54 | 55 | See the 'booth(8)' man page for more details. 56 | 57 | # vim: set ft=asciidoc : 58 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run this to generate all the initial makefiles, etc. 3 | 4 | echo Building configuration system... 5 | autoreconf -i && echo Now run ./configure and make 6 | -------------------------------------------------------------------------------- /booth-rpmlintrc: -------------------------------------------------------------------------------- 1 | addFilter("incoherent-init-script-name") 2 | addFilter("init-script-without-%insserv_cleanup-postun") 3 | addFilter("init-script-without-%stop_on_removal-preun") 4 | -------------------------------------------------------------------------------- /booth.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | confdir=@BOOTHSYSCONFDIR@ 4 | 5 | Name: @PACKAGE@ 6 | Version: @VERSION@ 7 | Description: @PACKAGE@ build information required for downstream projects 8 | -------------------------------------------------------------------------------- /booth.spec.in: -------------------------------------------------------------------------------- 1 | # keep around ready for later user 2 | @ALPHATAG@ 3 | @NUMCOMM@ 4 | @DIRTY@ 5 | 6 | %global gitver %{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}} 7 | %global gittarver %{?numcomm:-%{numcomm}}%{?alphatag:-g%{alphatag}}%{?dirty:-%{dirty}} 8 | 9 | %@bcond_html_man@ html_man 10 | %@bcond_glue@ glue 11 | %@bcond_python3@ python3 12 | %@bcond_run_build_tests@ run_build_tests 13 | 14 | ## User and group to use for nonprivileged services (should be in sync with pacemaker) 15 | %global uname @uname@ 16 | %global gname @gname@ 17 | 18 | # Defined on RHEL 8 and Fedora 19 | %{!?__python2: %global __python2 /usr/bin/python2} 20 | %{!?__python3: %global __python3 /usr/bin/python3} 21 | 22 | %if 0%{?with_python3} 23 | %global __python_full_path %{__python3} 24 | %else 25 | %global __python_full_path %{__python2} 26 | %endif 27 | 28 | %if 0%{?suse_version} 29 | %global booth_docdir %{_defaultdocdir}/%{name} 30 | %else 31 | # newer fedora distros have _pkgdocdir, rely on that when 32 | # available 33 | %{!?_pkgdocdir: %global _pkgdocdir %%{_docdir}/%{name}-%{version}} 34 | # Directory where we install documentation 35 | %global booth_docdir %{_pkgdocdir} 36 | %endif 37 | 38 | %global test_path %{_datadir}/booth/tests 39 | 40 | %if 0%{?suse_version} 41 | %define _libexecdir %{_libdir} 42 | %define _fwdefdir %{_libexecdir}/firewalld/services 43 | %endif 44 | %define with_extra_warnings 0 45 | %define with_debugging 0 46 | %define without_fatal_warnings 1 47 | %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} 48 | %define pkg_group System Environment/Daemons 49 | %else 50 | %define pkg_group Productivity/Clustering/HA 51 | %endif 52 | 53 | %global release 1 54 | 55 | Name: booth 56 | Url: https://github.com/ClusterLabs/booth 57 | Summary: Ticket Manager for Multi-site Clusters 58 | License: GPL-2.0-or-later 59 | Group: %{pkg_group} 60 | Version: @version@ 61 | Release: %{?numcomm:%{numcomm}.}%{release}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?alphatag:.git}%{?dist} 62 | Source: https://github.com/ClusterLabs/booth/archive/v%{version}%{?gittarver}/%{name}-%{version}%{?gitver}.tar.gz 63 | Source1: %name-rpmlintrc 64 | BuildRequires: @asciidoc@ 65 | BuildRequires: autoconf 66 | BuildRequires: automake 67 | BuildRequires: gcc 68 | BuildRequires: pkgconfig 69 | %if 0%{?suse_version} 70 | BuildRequires: glib2-devel 71 | # SuSEFirewall2 replaced by Firewalld (fate#323460) 72 | BuildRequires: firewall-macros 73 | %else 74 | BuildRequires: pkgconfig(glib-2.0) 75 | %endif 76 | BuildRequires: gnutls-devel 77 | %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} 78 | BuildRequires: pacemaker-libs-devel 79 | %else 80 | %if 0%{?suse_version} > 1500 81 | BuildRequires: libpacemaker3-devel 82 | %else 83 | BuildRequires: libpacemaker-devel 84 | %endif 85 | %endif 86 | %if 0%{?with_glue} 87 | %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} 88 | BuildRequires: cluster-glue-libs-devel 89 | %else 90 | BuildRequires: libglue-devel 91 | %endif 92 | %else 93 | # logging provider 94 | BuildRequires: pkgconfig(libqb) 95 | # random2range provider 96 | BuildRequires: pkgconfig(glib-2.0) 97 | # nametag provider 98 | BuildRequires: pkgconfig(libsystemd) 99 | %endif 100 | BuildRequires: libxml2-devel 101 | BuildRequires: zlib-devel 102 | %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} 103 | Requires: pacemaker >= 1.1.8 104 | %if 0%{?with_glue} 105 | Requires: cluster-glue-libs >= 1.0.6 106 | %endif 107 | %else 108 | Requires: pacemaker-ticket-support >= 2.0 109 | %endif 110 | 111 | # for check scriptlet 112 | %if 0%{?with_python3} 113 | BuildRequires: python3-devel 114 | %else 115 | BuildRequires: python 116 | %endif 117 | 118 | # For Fedora compatibility 119 | Provides: booth-core 120 | Provides: booth-site 121 | Provides: booth-arbitrator 122 | 123 | %description 124 | Booth manages tickets which authorize cluster sites located in 125 | geographically dispersed locations to run resources. It 126 | facilitates support of geographically distributed clustering in 127 | Pacemaker. 128 | 129 | %prep 130 | %setup -q -n %{name}-%{version}%{?gitver} 131 | 132 | %build 133 | ./autogen.sh 134 | %configure \ 135 | --with-initddir=%{_initrddir} \ 136 | --docdir=%{booth_docdir} \ 137 | %{?with_html_man:--with-html_man} \ 138 | %{!?with_glue:--without-glue} \ 139 | PYTHON=%{__python_full_path} 140 | 141 | make 142 | 143 | %install 144 | make DESTDIR=$RPM_BUILD_ROOT install docdir=%{booth_docdir} 145 | 146 | mkdir -p %{buildroot}/%{_mandir}/man8/ 147 | gzip < docs/boothd.8 > %{buildroot}/%{_mandir}/man8/booth.8.gz 148 | ln %{buildroot}/%{_mandir}/man8/booth.8.gz %{buildroot}/%{_mandir}/man8/boothd.8.gz 149 | 150 | %if %{defined _unitdir} 151 | # systemd 152 | mkdir -p %{buildroot}/%{_unitdir} 153 | cp -a conf/booth@.service %{buildroot}/%{_unitdir}/booth@.service 154 | cp -a conf/booth-arbitrator.service %{buildroot}/%{_unitdir}/booth-arbitrator.service 155 | ln -s /usr/sbin/service %{buildroot}%{_sbindir}/rcbooth-arbitrator 156 | %else 157 | # sysV init 158 | ln -s ../../%{_initddir}/booth-arbitrator %{buildroot}%{_sbindir}/rcbooth-arbitrator 159 | %endif 160 | 161 | #install test-parts 162 | 163 | mkdir -p %{buildroot}/%{test_path}/conf 164 | cp -a test %{buildroot}/%{test_path}/ 165 | cp -a conf/booth.conf.example %{buildroot}/%{test_path}/conf/ 166 | chmod +x %{buildroot}/%{test_path}/test/booth_path 167 | chmod +x %{buildroot}/%{test_path}/test/live_test.sh 168 | 169 | mkdir -p %{buildroot}/%{test_path}/src/ 170 | ln -s %{_sbindir}/boothd %{buildroot}/%{test_path}/src/ 171 | rm -f %{buildroot}/%{test_path}/test/*.pyc 172 | 173 | # Generate runtests.py and boothtestenv.py 174 | sed -e 's#PYTHON_SHEBANG#%{__python_full_path} -Es#g' \ 175 | -e 's#TEST_SRC_DIR#%{test_path}/test#g' \ 176 | -e 's#TEST_BUILD_DIR#%{test_path}/test#g' \ 177 | %{buildroot}/%{test_path}/test/runtests.py.in > %{buildroot}/%{test_path}/test/runtests.py 178 | 179 | chmod +x %{buildroot}/%{test_path}/test/runtests.py 180 | 181 | sed -e 's#PYTHON_SHEBANG#%{__python_full_path} -Es#g' \ 182 | -e 's#TEST_SRC_DIR#%{test_path}/test#g' \ 183 | -e 's#TEST_BUILD_DIR#%{test_path}/test#g' \ 184 | %{buildroot}/%{test_path}/test/boothtestenv.py.in > %{buildroot}/%{test_path}/test/boothtestenv.py 185 | 186 | %if 0%{?suse_version} 187 | #Firewalld rule 188 | mkdir -p $RPM_BUILD_ROOT/%{_fwdefdir} 189 | install -m 644 contrib/geo-cluster.firewalld.xml $RPM_BUILD_ROOT/%{_fwdefdir}/booth.xml 190 | #install -m 644 %{S:2} $RPM_BUILD_ROOT/%{_fwdefdir}/booth 191 | 192 | %post 193 | %firewalld_reload 194 | %endif 195 | 196 | %check 197 | %if 0%{?with_run_build_tests} 198 | echo "%%with_run_build_tests set to %with_run_build_tests; including tests" 199 | make check 200 | %else 201 | echo "%%with_run_build_tests set to %with_run_build_tests; skipping tests" 202 | %endif 203 | 204 | %files 205 | %{_sbindir}/booth 206 | %{_sbindir}/boothd 207 | %{_sbindir}/booth-keygen 208 | %{_sbindir}/geostore 209 | %{_mandir}/man8/booth.8.gz 210 | %{_mandir}/man8/boothd.8.gz 211 | %{_mandir}/man8/booth-keygen.8.gz 212 | %{_mandir}/man8/geostore.8.gz 213 | %dir /usr/lib/ocf 214 | %dir /usr/lib/ocf/resource.d 215 | %dir /usr/lib/ocf/resource.d/pacemaker 216 | %dir /usr/lib/ocf/resource.d/booth 217 | %dir /usr/lib/ocf/lib 218 | %dir /usr/lib/ocf/lib/booth 219 | %dir %{_sysconfdir}/booth 220 | %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/booth/ 221 | %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/booth/cores 222 | %{_sbindir}/rcbooth-arbitrator 223 | /usr/lib/ocf/resource.d/pacemaker/booth-site 224 | /usr/lib/ocf/lib/booth/geo_attr.sh 225 | /usr/lib/ocf/resource.d/booth/geostore 226 | %config %{_sysconfdir}/booth/booth.conf.example 227 | %if 0%{?suse_version} 228 | %dir %{_libexecdir}/firewalld 229 | %dir %{_fwdefdir} 230 | %{_fwdefdir}/booth.xml 231 | %endif 232 | 233 | %if %{defined _unitdir} 234 | %{_unitdir}/booth@.service 235 | %{_unitdir}/booth-arbitrator.service 236 | %exclude %{_initddir}/booth-arbitrator 237 | %else 238 | %{_initddir}/booth-arbitrator 239 | %endif 240 | 241 | %dir %{_datadir}/booth 242 | %{_datadir}/booth/service-runnable 243 | 244 | %dir %{_datadir}/pkgconfig 245 | %{_datadir}/pkgconfig/booth.pc 246 | 247 | %doc AUTHORS README COPYING 248 | %doc README.upgrade-from-v0.1 249 | 250 | %if 0%{?with_html_man} 251 | %{booth_docdir}/booth-keygen.8.html 252 | %{booth_docdir}/boothd.8.html 253 | %{booth_docdir}/geostore.8.html 254 | %endif 255 | 256 | %package test 257 | Summary: Test scripts for Booth 258 | Group: %{pkg_group} 259 | Requires: booth 260 | Requires: gdb 261 | Requires: %{__python_full_path} 262 | %if 0%{?with_python3} 263 | Requires: python3-pexpect 264 | %else 265 | Requires: python-pexpect 266 | %endif 267 | 268 | %description test 269 | This package contains automated tests for Booth, 270 | the Cluster Ticket Manager for Pacemaker. 271 | 272 | %files test 273 | %doc %{booth_docdir}/README-testing 274 | %{test_path} 275 | %dir /usr/lib/ocf 276 | %dir /usr/lib/ocf/resource.d 277 | %dir /usr/lib/ocf/resource.d/booth 278 | /usr/lib/ocf/resource.d/booth/sharedrsc 279 | 280 | %changelog 281 | -------------------------------------------------------------------------------- /build-aux/PKG_CHECK_VAR.m4: -------------------------------------------------------------------------------- 1 | dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, 2 | dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) 3 | dnl ------------------------------------------- 4 | dnl Since: 0.28 5 | dnl 6 | dnl Retrieves the value of the pkg-config variable for the given module. 7 | dnl 8 | dnl Origin (declared license: GPLv2+ with less restrictive exception): 9 | dnl https://cgit.freedesktop.org/pkg-config/tree/pkg.m4.in?h=pkg-config-0.29.1#n261 10 | dnl (AS_VAR_COPY replaced with backward-compatible equivalent and guard 11 | dnl to prefer system-wide variant by Jan Pokorny ) 12 | 13 | m4_ifndef([PKG_CHECK_VAR],[ 14 | AC_DEFUN([PKG_CHECK_VAR], 15 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl 16 | AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl 17 | 18 | _PKG_CONFIG([$1], [variable="][$3]["], [$2]) 19 | dnl AS_VAR_COPY([$1], [pkg_cv_][$1]) 20 | $1=AS_VAR_GET([pkg_cv_][$1]) 21 | 22 | AS_VAR_IF([$1], [""], [$5], [$4])dnl 23 | ])dnl PKG_CHECK_VAR 24 | ])dnl m4_ifndef 25 | -------------------------------------------------------------------------------- /build-aux/git-version-gen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Print a version string. 3 | scriptversion=2018-08-31.20; # UTC 4 | 5 | # Copyright (C) 2012-2020 Red Hat, Inc. 6 | # Copyright (C) 2007-2016 Free Software Foundation, Inc. 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | # This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. 22 | # It may be run two ways: 23 | # - from a git repository in which the "git describe" command below 24 | # produces useful output (thus requiring at least one signed tag) 25 | # - from a non-git-repo directory containing a .tarball-version file, which 26 | # presumes this script is invoked like "./git-version-gen .tarball-version". 27 | 28 | # In order to use intra-version strings in your project, you will need two 29 | # separate generated version string files: 30 | # 31 | # .tarball-version - present only in a distribution tarball, and not in 32 | # a checked-out repository. Created with contents that were learned at 33 | # the last time autoconf was run, and used by git-version-gen. Must not 34 | # be present in either $(srcdir) or $(builddir) for git-version-gen to 35 | # give accurate answers during normal development with a checked out tree, 36 | # but must be present in a tarball when there is no version control system. 37 | # Therefore, it cannot be used in any dependencies. GNUmakefile has 38 | # hooks to force a reconfigure at distribution time to get the value 39 | # correct, without penalizing normal development with extra reconfigures. 40 | # 41 | # .version - present in a checked-out repository and in a distribution 42 | # tarball. Usable in dependencies, particularly for files that don't 43 | # want to depend on config.h but do want to track version changes. 44 | # Delete this file prior to any autoconf run where you want to rebuild 45 | # files to pick up a version string change; and leave it stale to 46 | # minimize rebuild time after unrelated changes to configure sources. 47 | # 48 | # As with any generated file in a VC'd directory, you should add 49 | # /.version to .gitignore, so that you don't accidentally commit it. 50 | # .tarball-version is never generated in a VC'd directory, so needn't 51 | # be listed there. 52 | # 53 | # In order to use git archive versions another two files has to be presented: 54 | # 55 | # .gitarchive-version - present in checked-out repository and git 56 | # archive tarball, but not in the distribution tarball. Used as a last 57 | # option for version. File must contain special string $Format:%d$, 58 | # which is substitued by git on archive operation. 59 | # 60 | # .gitattributes - present in checked-out repository and git archive 61 | # tarball, but not in the distribution tarball. Must set export-subst 62 | # attribute for .gitarchive-version file. 63 | # 64 | # Use the following line in your configure.ac, so that $(VERSION) will 65 | # automatically be up-to-date each time configure is run (and note that 66 | # since configure.ac no longer includes a version string, Makefile rules 67 | # should not depend on configure.ac for version updates). 68 | # 69 | # AC_INIT([GNU project], 70 | # m4_esyscmd([build-aux/git-version-gen .tarball-version]), 71 | # [bug-project@example]) 72 | # 73 | # Then use the following lines in your Makefile.am, so that .version 74 | # will be present for dependencies, and so that .version and 75 | # .tarball-version will exist in distribution tarballs. 76 | # 77 | # EXTRA_DIST = $(top_srcdir)/.version 78 | # BUILT_SOURCES = $(top_srcdir)/.version 79 | # $(top_srcdir)/.version: 80 | # echo $(VERSION) > $@-t && mv $@-t $@ 81 | # dist-hook: 82 | # echo $(VERSION) > $(distdir)/.tarball-version 83 | 84 | 85 | me=$0 86 | 87 | version="git-version-gen $scriptversion 88 | 89 | Copyright 2011 Free Software Foundation, Inc. 90 | There is NO warranty. You may redistribute this software 91 | under the terms of the GNU General Public License. 92 | For more information about these matters, see the files named COPYING." 93 | 94 | usage="\ 95 | Usage: $me [OPTION]... \$srcdir/.tarball-version [\$srcdir/.gitarchive-version] [TAG-NORMALIZATION-SED-SCRIPT] 96 | Print a version string. 97 | 98 | Options: 99 | 100 | --prefix PREFIX prefix of git tags (default 'v') 101 | --fallback VERSION 102 | fallback version to use if \"git --version\" fails 103 | 104 | --help display this help and exit 105 | --version output version information and exit 106 | 107 | Running without arguments will suffice in most cases." 108 | 109 | prefix=v 110 | fallback= 111 | 112 | while test $# -gt 0; do 113 | case $1 in 114 | --help) echo "$usage"; exit 0;; 115 | --version) echo "$version"; exit 0;; 116 | --prefix) shift; prefix="$1";; 117 | --fallback) shift; fallback="$1";; 118 | -*) 119 | echo "$0: Unknown option '$1'." >&2 120 | echo "$0: Try '--help' for more information." >&2 121 | exit 1;; 122 | *) 123 | if test "x$tarball_version_file" = x; then 124 | tarball_version_file="$1" 125 | elif test "x$gitarchive_version_file" = x; then 126 | gitarchive_version_file="$1" 127 | elif test "x$tag_sed_script" = x; then 128 | tag_sed_script="$1" 129 | else 130 | echo "$0: extra non-option argument '$1'." >&2 131 | exit 1 132 | fi;; 133 | esac 134 | shift 135 | done 136 | 137 | if test "x$tarball_version_file" = x; then 138 | echo "$usage" 139 | exit 1 140 | fi 141 | 142 | tag_sed_script="${tag_sed_script:-s/x/x/}" 143 | 144 | nl=' 145 | ' 146 | 147 | # Avoid meddling by environment variable of the same name. 148 | v= 149 | v_from_git= 150 | 151 | # First see if there is a tarball-only version file. 152 | # then try "git describe", then default. 153 | if test -f $tarball_version_file 154 | then 155 | v=`cat $tarball_version_file` || v= 156 | case $v in 157 | *$nl*) v= ;; # reject multi-line output 158 | [0-9]*) ;; 159 | *) v= ;; 160 | esac 161 | test "x$v" = x \ 162 | && echo "$0: WARNING: $tarball_version_file is missing or damaged" 1>&2 163 | fi 164 | 165 | if test "x$v" != x 166 | then 167 | : # use $v 168 | # Otherwise, if there is at least one git commit involving the working 169 | # directory, and "git describe" output looks sensible, use that to 170 | # derive a version string. 171 | elif test "`git log -1 --pretty=format:x . 2>&1`" = x \ 172 | && v=`git describe --abbrev=4 --match="$prefix*" HEAD 2>/dev/null \ 173 | || git describe --abbrev=4 HEAD 2>/dev/null` \ 174 | && v=`printf '%s\n' "$v" | sed "$tag_sed_script"` \ 175 | && case $v in 176 | $prefix[0-9]*) ;; 177 | *) (exit 1) ;; 178 | esac 179 | then 180 | # Is this a new git that lists number of commits since the last 181 | # tag or the previous older version that did not? 182 | # Newer: v6.10-77-g0f8faeb 183 | # Older: v6.10-g0f8faeb 184 | case $v in 185 | *-*-*) : git describe is okay three part flavor ;; 186 | *-*) 187 | : git describe is older two part flavor 188 | # Recreate the number of commits and rewrite such that the 189 | # result is the same as if we were using the newer version 190 | # of git describe. 191 | vtag=`echo "$v" | sed 's/-.*//'` 192 | commit_list=`git rev-list "$vtag"..HEAD 2>/dev/null` \ 193 | || { commit_list=failed; 194 | echo "$0: WARNING: git rev-list failed" 1>&2; } 195 | numcommits=`echo "$commit_list" | wc -l` 196 | v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; 197 | test "$commit_list" = failed && v=UNKNOWN 198 | ;; 199 | esac 200 | 201 | # Change the first '-' to a '.', so version-comparing tools work properly. 202 | # Remove the "g" in git describe's output string, to save a byte. 203 | v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; 204 | v_from_git=1 205 | elif test "x$fallback" = x || git --version >/dev/null 2>&1; then 206 | if test -f $gitarchive_version_file 207 | then 208 | v=`sed "s/^.*tag: \($prefix[0-9)][^,)]*\).*\$/\1/" $gitarchive_version_file \ 209 | | sed "$tag_sed_script"` || exit 1 210 | case $v in 211 | *$nl*) v= ;; # reject multi-line output 212 | $prefix[0-9]*) ;; 213 | *) v= ;; 214 | esac 215 | 216 | if test -z "$v"; then 217 | if test "x$fallback" = x; then 218 | echo "$0: WARNING: $gitarchive_version_file doesn't contain valid version tag" 1>&2 219 | v=UNKNOWN 220 | else 221 | v=$fallback 222 | fi 223 | fi 224 | elif test "x$fallback" = x; then 225 | v=UNKNOWN 226 | else 227 | v=$fallback 228 | fi 229 | else 230 | v=$fallback 231 | fi 232 | 233 | if test "x$fallback" = x -a "$v" = "UNKNOWN" 234 | then 235 | echo "$0: ERROR: Can't find valid version. Please use valid git repository," \ 236 | "released tarball or version tagged archive" 1>&2 237 | 238 | exit 1 239 | fi 240 | 241 | v=`echo "$v" |sed "s/^$prefix//"` 242 | 243 | # Test whether to append the "-dirty" suffix only if the version 244 | # string we're using came from git. I.e., skip the test if it's "UNKNOWN" 245 | # or if it came from .tarball-version. 246 | if test "x$v_from_git" != x; then 247 | # Don't declare a version "dirty" merely because a time stamp has changed. 248 | git update-index --refresh > /dev/null 2>&1 249 | 250 | dirty=`exec 2>/dev/null;git diff-index --name-only HEAD` || dirty= 251 | case "$dirty" in 252 | '') ;; 253 | *) # Append the suffix only if there isn't one already. 254 | case $v in 255 | *-dirty) ;; 256 | *) v="$v-dirty" ;; 257 | esac ;; 258 | esac 259 | fi 260 | 261 | # Omit the trailing newline, so that m4_esyscmd can use the result directly. 262 | printf %s "$v" 263 | 264 | # Local variables: 265 | # eval: (add-hook 'write-file-hooks 'time-stamp) 266 | # time-stamp-start: "scriptversion=" 267 | # time-stamp-format: "%:y-%02m-%02d.%02H" 268 | # time-stamp-time-zone: "UTC0" 269 | # time-stamp-end: "; # UTC" 270 | # End: 271 | -------------------------------------------------------------------------------- /build-aux/gitlog-to-changelog: -------------------------------------------------------------------------------- 1 | eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}' 2 | & eval 'exec perl -wS "$0" $argv:q' 3 | if 0; 4 | # Convert git log output to ChangeLog format. 5 | 6 | my $VERSION = '2009-10-30 13:46'; # UTC 7 | # The definition above must lie within the first 8 lines in order 8 | # for the Emacs time-stamp write hook (at end) to update it. 9 | # If you change this file with Emacs, please let the write hook 10 | # do its job. Otherwise, update this string manually. 11 | 12 | # Copyright (C) 2008-2010 Free Software Foundation, Inc. 13 | 14 | # This program is free software: you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License as published by 16 | # the Free Software Foundation, either version 3 of the License, or 17 | # (at your option) any later version. 18 | 19 | # This program is distributed in the hope that it will be useful, 20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | # GNU General Public License for more details. 23 | 24 | # You should have received a copy of the GNU General Public License 25 | # along with this program. If not, see . 26 | 27 | # Written by Jim Meyering 28 | 29 | use strict; 30 | use warnings; 31 | use Getopt::Long; 32 | use POSIX qw(strftime); 33 | 34 | (my $ME = $0) =~ s|.*/||; 35 | 36 | # use File::Coda; # http://meyering.net/code/Coda/ 37 | END { 38 | defined fileno STDOUT or return; 39 | close STDOUT and return; 40 | warn "$ME: failed to close standard output: $!\n"; 41 | $? ||= 1; 42 | } 43 | 44 | sub usage ($) 45 | { 46 | my ($exit_code) = @_; 47 | my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); 48 | if ($exit_code != 0) 49 | { 50 | print $STREAM "Try `$ME --help' for more information.\n"; 51 | } 52 | else 53 | { 54 | print $STREAM < ChangeLog 75 | $ME -- -n 5 foo > last-5-commits-to-branch-foo 76 | 77 | EOF 78 | } 79 | exit $exit_code; 80 | } 81 | 82 | # If the string $S is a well-behaved file name, simply return it. 83 | # If it contains white space, quotes, etc., quote it, and return the new string. 84 | sub shell_quote($) 85 | { 86 | my ($s) = @_; 87 | if ($s =~ m![^\w+/.,-]!) 88 | { 89 | # Convert each single quote to '\'' 90 | $s =~ s/\'/\'\\\'\'/g; 91 | # Then single quote the string. 92 | $s = "'$s'"; 93 | } 94 | return $s; 95 | } 96 | 97 | sub quoted_cmd(@) 98 | { 99 | return join (' ', map {shell_quote $_} @_); 100 | } 101 | 102 | { 103 | my $since_date = '1970-01-01 UTC'; 104 | my $format_string = '%s%n%b%n'; 105 | GetOptions 106 | ( 107 | help => sub { usage 0 }, 108 | version => sub { print "$ME version $VERSION\n"; exit }, 109 | 'since=s' => \$since_date, 110 | 'format=s' => \$format_string, 111 | ) or usage 1; 112 | 113 | my @cmd = (qw (git log --log-size), "--since=$since_date", 114 | '--pretty=format:%ct %an <%ae>%n%n'.$format_string, @ARGV); 115 | open PIPE, '-|', @cmd 116 | or die ("$ME: failed to run `". quoted_cmd (@cmd) ."': $!\n" 117 | . "(Is your Git too old? Version 1.5.1 or later is required.)\n"); 118 | 119 | my $prev_date_line = ''; 120 | while (1) 121 | { 122 | defined (my $in = ) 123 | or last; 124 | $in =~ /^log size (\d+)$/ 125 | or die "$ME:$.: Invalid line (expected log size):\n$in"; 126 | my $log_nbytes = $1; 127 | 128 | my $log; 129 | my $n_read = read PIPE, $log, $log_nbytes; 130 | $n_read == $log_nbytes 131 | or die "$ME:$.: unexpected EOF\n"; 132 | 133 | my @line = split "\n", $log; 134 | my $author_line = shift @line; 135 | defined $author_line 136 | or die "$ME:$.: unexpected EOF\n"; 137 | $author_line =~ /^(\d+) (.*>)$/ 138 | or die "$ME:$.: Invalid line " 139 | . "(expected date/author/email):\n$author_line\n"; 140 | 141 | my $date_line = sprintf "%s $2\n", strftime ("%F", localtime ($1)); 142 | # If this line would be the same as the previous date/name/email 143 | # line, then arrange not to print it. 144 | if ($date_line ne $prev_date_line) 145 | { 146 | $prev_date_line eq '' 147 | or print "\n"; 148 | print $date_line; 149 | } 150 | $prev_date_line = $date_line; 151 | 152 | # Omit "Signed-off-by..." lines. 153 | @line = grep !/^Signed-off-by: .*>$/, @line; 154 | 155 | # If there were any lines 156 | if (@line == 0) 157 | { 158 | warn "$ME: warning: empty commit message:\n $date_line\n"; 159 | } 160 | else 161 | { 162 | # Remove leading and trailing blank lines. 163 | while ($line[0] =~ /^\s*$/) { shift @line; } 164 | while ($line[$#line] =~ /^\s*$/) { pop @line; } 165 | 166 | # Prefix each non-empty line with a TAB. 167 | @line = map { length $_ ? "\t$_" : '' } @line; 168 | 169 | print "\n", join ("\n", @line), "\n"; 170 | } 171 | 172 | defined ($in = ) 173 | or last; 174 | $in ne "\n" 175 | and die "$ME:$.: unexpected line:\n$in"; 176 | } 177 | 178 | close PIPE 179 | or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; 180 | # FIXME-someday: include $PROCESS_STATUS in the diagnostic 181 | } 182 | 183 | # Local Variables: 184 | # mode: perl 185 | # indent-tabs-mode: nil 186 | # eval: (add-hook 'write-file-hooks 'time-stamp) 187 | # time-stamp-start: "my $VERSION = '" 188 | # time-stamp-format: "%:y-%02m-%02d %02H:%02M" 189 | # time-stamp-time-zone: "UTC" 190 | # time-stamp-end: "'; # UTC" 191 | # End: 192 | -------------------------------------------------------------------------------- /build-aux/release.mk: -------------------------------------------------------------------------------- 1 | # to build official release tarballs, handle tagging and publish. 2 | 3 | # example: 4 | # make -f build-aux/release.mk all version=1.1 release=yes 5 | 6 | project=booth 7 | 8 | all: checks setup tag tarballs sha256 9 | 10 | checks: 11 | ifeq (,$(version)) 12 | @echo ERROR: need to define version= 13 | @exit 1 14 | endif 15 | @if [ ! -d .git ]; then \ 16 | echo This script needs to be executed from top level cluster git tree; \ 17 | exit 1; \ 18 | fi 19 | 20 | @if ! grep "fallback $(version)" configure.ac > /dev/null; then \ 21 | echo "Don't forget update fallback version in configure.ac before release"; \ 22 | exit 1; \ 23 | fi 24 | 25 | setup: checks 26 | ./autogen.sh 27 | ./configure --without-glue 28 | make maintainer-clean 29 | 30 | tag: setup ./tag-$(version) 31 | 32 | tag-$(version): 33 | ifeq (,$(release)) 34 | @echo Building test release $(version), no tagging 35 | else 36 | git tag -a -m "v$(version) release" v$(version) HEAD 37 | @touch $@ 38 | endif 39 | 40 | tarballs: tag 41 | ./autogen.sh 42 | ./configure --without-glue 43 | BOOTH_RUNTESTS_ROOT_USER=1 make distcheck DISTCHECK_CONFIGURE_FLAGS="--without-glue" 44 | 45 | sha256: tarballs $(project)-$(version).sha256 46 | 47 | $(project)-$(version).sha256: 48 | ifeq (,$(release)) 49 | @echo Building test release $(version), no sha256 50 | else 51 | sha256sum $(project)-$(version)*tar* | sort -k2 > $@ 52 | endif 53 | 54 | clean: 55 | rm -rf $(project)-* tag-* 56 | -------------------------------------------------------------------------------- /conf/Makefile.am: -------------------------------------------------------------------------------- 1 | # 2 | # conf: booth configuration and systemd units 3 | # 4 | # Copyright 2016 Jan Pokorny 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | # 20 | MAINTAINERCLEANFILES = Makefile.in 21 | 22 | # XXX do not install for now, later support should be per daemon(7) 23 | nodist_noinst_DATA = booth-arbitrator.service booth@.service 24 | -------------------------------------------------------------------------------- /conf/booth-arbitrator.service.in: -------------------------------------------------------------------------------- 1 | # This file is part of Booth. 2 | 3 | [Unit] 4 | Description=Booth - Ticket Manager for Pacemaker Clusters 5 | Documentation=man:boothd(8) 6 | After=network-online.target 7 | ConditionFileNotEmpty=/etc/booth/booth.conf 8 | Conflicts=pacemaker.service 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | 13 | [Service] 14 | Type=simple 15 | @NOTIFY_ACCESS_SWITCH@NotifyAccess=main 16 | ExecStart=/usr/sbin/boothd daemon -S -c /etc/booth/booth.conf 17 | 18 | # Restart options include: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always 19 | Restart=on-failure 20 | -------------------------------------------------------------------------------- /conf/booth.conf.example: -------------------------------------------------------------------------------- 1 | # The booth configuration file is "/etc/booth/booth.conf". You need to 2 | # prepare the same booth configuration file on each arbitrator and 3 | # each node in the cluster sites where the booth daemon can be launched. 4 | # Here is an example of the configuration file: 5 | 6 | # "transport" means which transport layer booth daemon will use. 7 | # Currently only "UDP" is supported. 8 | transport="UDP" 9 | 10 | # The port that booth daemons will use to talk to each other. 11 | port="9929" 12 | 13 | # The arbitrator IP. If you want to configure several arbitrators, 14 | # you need to configure each arbitrator with a separate line. 15 | arbitrator="147.2.207.14" 16 | 17 | # The site IP. The cluster site uses this IP to talk to other sites. 18 | # Like arbitrator, you need to configure each site with a separate line. 19 | site="147.4.215.19" 20 | site="147.18.2.1" 21 | 22 | # The ticket name, which corresponds to a set of resources which can be 23 | # fail-overed among different sites. 24 | ticket="ticketA" 25 | ticket="ticketB" 26 | expire = 600 27 | weights = 1,2,3 28 | -------------------------------------------------------------------------------- /conf/booth@.service.in: -------------------------------------------------------------------------------- 1 | # This file is part of Booth. 2 | 3 | [Unit] 4 | Description=Booth - Ticket Manager for Pacemaker Clusters 5 | Documentation=man:boothd(8) 6 | After=network-online.target 7 | ConditionFileNotEmpty=/etc/booth/%i.conf 8 | Conflicts=pacemaker.service 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | 13 | [Service] 14 | Type=simple 15 | @NOTIFY_ACCESS_SWITCH@NotifyAccess=main 16 | ExecStart=/usr/sbin/boothd daemon -S -c %i 17 | -------------------------------------------------------------------------------- /contrib/geo-cluster.firewalld.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Booth 4 | This allows you to open ports related to booth. 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/Makefile.am: -------------------------------------------------------------------------------- 1 | # 2 | # docs: booth manual pages 3 | # 4 | # Copyright (C) 2014 Dejan Muhamedagic 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | # 20 | MAINTAINERCLEANFILES = Makefile.in 21 | 22 | EXTRA_DIST = boothd.8.txt booth-keygen.8.txt fsm-full.dot fsm-netfail.dot fsm-normal.dot geostore.8.txt 23 | 24 | asciiman = boothd.8.txt booth-keygen.8.txt geostore.8.txt 25 | doc_DATA = $(generated_docs) 26 | 27 | generated_docs = 28 | generated_mans = 29 | HTML_GENERATOR = 30 | MANPAGE_GENERATOR = 31 | 32 | if BUILD_ASCIIDOC_HTML_MAN 33 | generated_docs += $(ascii:%.txt=%.html) $(asciiman:%.txt=%.html) 34 | endif 35 | if BUILD_ASCIIDOC 36 | generated_mans += $(asciiman:%.8.txt=%.8) 37 | $(generated_mans): $(asciiman) 38 | man8_MANS = $(generated_mans) 39 | endif 40 | 41 | if IS_ASCIIDOC 42 | HTML_GENERATOR += $(ASCIIDOC) --unsafe --backend=xhtml11 43 | else 44 | HTML_GENERATOR += $(ASCIIDOCTOR) -b xhtml5 --destination-dir=$(abs_builddir)/ 45 | endif 46 | 47 | if IS_A2X 48 | MANPAGE_GENERATOR += $(A2X) -f manpage --destination-dir=$(abs_builddir)/ 49 | else 50 | MANPAGE_GENERATOR += $(ASCIIDOCTOR) -b manpage --destination-dir=$(abs_builddir)/ 51 | endif 52 | 53 | %.html: %.txt 54 | if IS_ASCIIDOC 55 | $(HTML_GENERATOR) -o $(abs_builddir)/$@ $< 56 | else 57 | $(HTML_GENERATOR) $< 58 | endif 59 | %.8: %.8.txt 60 | $(MANPAGE_GENERATOR) $< 61 | clean-local: 62 | -rm -rf $(generated_docs) $(generated_mans) 63 | -------------------------------------------------------------------------------- /docs/booth-keygen.8.txt: -------------------------------------------------------------------------------- 1 | BOOTH-KEYGEN(8) 2 | =============== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | booth-keygen - generate authentication key 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | *booth-keygen* ['-h'] ['auth-file'] 14 | 15 | 16 | DESCRIPTION 17 | ----------- 18 | This program generates an authentication key suitable for 'booth' 19 | using '/dev/urandom' as source. 20 | 21 | 22 | PARAMETERS 23 | ---------- 24 | 25 | 'auth-file':: 26 | The file to contain the generated key. Defaults to 27 | '/etc/booth/authkey'. Use absolute paths. 28 | 29 | 30 | OPTIONS 31 | ------- 32 | *-h*, *--help*:: 33 | Print usage. 34 | 35 | 36 | EXIT STATUS 37 | ----------- 38 | *0*:: 39 | Success. 40 | 41 | *!= 0*:: 42 | File already exists or some other error. 43 | 44 | 45 | COPYING 46 | ------- 47 | 48 | Copyright (C) 2015 Dejan Muhamedagic 49 | 50 | Free use of this software is granted under the terms of the GNU 51 | General Public License (GPL) as of version 2 (see `COPYING` file) 52 | or later. 53 | 54 | -------------------------------------------------------------------------------- /docs/fsm-full.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | 3 | label="Booth full FSM"; 4 | fontname="Helvetica"; 5 | fontsize="11"; 6 | compound="true"; 7 | ST_INIT -> ST_CANDIDATE [label="grant"]; 8 | ST_INIT -> ST_FOLLOWER [label="HrtB|UpdE"]; 9 | ST_FOLLOWER -> ST_CANDIDATE [label="VtFr(tkt_drop)\ntkt_lost"]; 10 | ST_LEADER -> ST_FOLLOWER [label="lost_maj"]; 11 | ST_CANDIDATE -> ST_CANDIDATE [label="timeout"]; 12 | ST_CANDIDATE -> ST_LEADER [label="VtFr, timeout"]; 13 | ST_CANDIDATE -> ST_FOLLOWER [label="HrtB|UpdE\nRJC!(outd|valid)"]; 14 | ST_CANDIDATE -> ST_INIT [label="RJC!(outd+no_leader)"]; 15 | ST_LEADER -> ST_INIT [label="Revk"]; 16 | ST_FOLLOWER -> ST_INIT [label="Revk"]; 17 | } 18 | -------------------------------------------------------------------------------- /docs/fsm-netfail.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | 3 | label="Booth network failure FSM"; 4 | fontname="Helvetica"; 5 | fontsize="11"; 6 | compound="true"; 7 | ST_FOLLOWER -> ST_CANDIDATE [label="tkt_lost"]; 8 | ST_LEADER -> ST_FOLLOWER [label="tkt_lost"]; 9 | ST_CANDIDATE -> ST_CANDIDATE [label="timeout"]; 10 | ST_CANDIDATE -> ST_LEADER [label="VtFr, timeout"]; 11 | } 12 | -------------------------------------------------------------------------------- /docs/fsm-normal.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | 3 | label="Booth normal process FSM"; 4 | fontname="Helvetica"; 5 | fontsize="11"; 6 | compound="true"; 7 | ST_INIT -> ST_CANDIDATE [label="grant"]; 8 | ST_INIT -> ST_FOLLOWER [label="HrtB|UpdE"]; 9 | ST_CANDIDATE -> ST_LEADER [label="VtFr"]; 10 | ST_LEADER -> ST_INIT [label="Revk"]; 11 | ST_FOLLOWER -> ST_INIT [label="Revk"]; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /docs/geostore.8.txt: -------------------------------------------------------------------------------- 1 | GEOSTORE(8) 2 | =========== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | geostore - geo cluster attribute manager 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | *geostore* 'set' [-t 'ticket'] [-s 'site'] [-c 'config'] 'attribute' 'value' 14 | 15 | *geostore* 'get' [-t 'ticket'] [-s 'site'] [-c 'config'] 'attribute' 16 | 17 | *geostore* 'delete' [-t 'ticket'] [-s 'site'] [-c 'config'] 'attribute' 18 | 19 | *geostore* 'list' [-t 'ticket'] [-s 'site'] [-c 'config'] 20 | 21 | 22 | DESCRIPTION 23 | ----------- 24 | Applications running in GEO cluster environments may need more 25 | information apart from tickets to make decisions. One example may 26 | be the status of data replication. 27 | 28 | 'geostore' is a helper program to manage site attributes. The 29 | attributes are defined on a per-ticket basis, that is every 30 | ticket may have one or more attributes. 31 | 32 | It can set an attribute value, retrieve an attribute, or delete 33 | it. The attributes are stored in the CIB status section which is 34 | managed by the pacemaker 'cib' process. 'boothd(8)' provides 35 | transport for attributes to other sites. 36 | 37 | 'crm_ticket(8)' is invoked at the target site to manage the 38 | attributes. 39 | 40 | 41 | SHORT EXAMPLES 42 | -------------- 43 | 44 | --------------------- 45 | # geostore set -t ticket-A -s other bigdb-repl-status UPTODATE 46 | 47 | # geostore get -t ticket-A -s other bigdb-repl-status 48 | 49 | # geostore delete -t ticket-A -s 44.0.0.61 bigdb-repl-status 50 | 51 | # geostore list -t ticket-A -s other 52 | --------------------- 53 | 54 | 55 | 56 | OPTIONS 57 | ------- 58 | 59 | *-t*:: 60 | Ticket scope of the attribute (required, if more than one 61 | ticket is configured). 62 | 63 | *-s*:: 64 | Site address or name where the attribute is to be stored/retrieved. 65 | + 66 | The special value 'other' can be used to specify the other 67 | site. Obviously, in that case, the booth configuration must 68 | have exactly two sites defined. 69 | 70 | *-c* 'configfile':: 71 | Configuration to use. 72 | + 73 | Can be a full path to a configuration file, or a short name; in the latter 74 | case, the directory '/etc/booth' and suffix '.conf' are added. 75 | Per default 'booth' is used, which results in the path 76 | '/etc/booth/booth.conf'. 77 | 78 | *-h*, *--help*:: 79 | Give a short usage output. 80 | 81 | 82 | COMMANDS 83 | -------- 84 | 85 | 'set':: 86 | Sets the attribute to the value. 87 | 88 | 89 | 'get':: 90 | Get the attribute value and print it to 'stdout'. If the 91 | attribute doesn't exist, appropriate error message is printed 92 | to 'stderr'. 93 | 94 | 95 | 'delete':: 96 | Delete the attribute. If the attribute doesn't exist, 97 | appropriate error message is printed to 'stderr'. 98 | 99 | 100 | 'list':: 101 | List all attributes and their values stored at the site. 102 | 103 | 104 | 105 | EXIT STATUS 106 | ----------- 107 | *0*:: 108 | Success. 109 | 110 | *1*:: 111 | Request failed or bad usage. 112 | 113 | 114 | RESOURCES 115 | --------- 116 | GitHub: 117 | 118 | 119 | COPYING 120 | ------- 121 | 122 | Copyright (C) 2015 Dejan Muhamedagic 123 | 124 | Free use of this software is granted under the terms of the GNU 125 | General Public License (GPL) as of version 2 (see `COPYING` file) 126 | or later. 127 | 128 | 129 | SEE ALSO 130 | -------- 131 | 132 | 'boothd(8)', 'crm_attribute(8)' 133 | 134 | // vim: set ft=asciidoc : 135 | -------------------------------------------------------------------------------- /script/booth-keygen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Generate authentication key for booth 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of version 2 of the GNU General Public License as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it would be useful, but 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # 13 | # Further, this software is distributed without any warranty that it is 14 | # free of the rightful claim of any third person regarding infringement 15 | # or the like. Any license provided herein, whether implied or 16 | # otherwise, applies only to this software file. Patent licenses, if 17 | # any, provided herein do not apply to combinations of this program with 18 | # other software, or any other product whatsoever. 19 | # 20 | # You should have received a copy of the GNU General Public License along 21 | # with this program; if not, write to the Free Software Foundation, Inc., 22 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23 | # 24 | 25 | DFLT_AUTHFILE=/etc/booth/authkey 26 | KEYSIZE=64 27 | # /dev/urandom should be good enough 28 | RND_SRC=/dev/urandom 29 | 30 | usage() { 31 | cat<&2 38 | } 39 | fatal() { 40 | error $* 41 | exit 1 42 | } 43 | 44 | case "$1" in 45 | "-h"|"--help"|"-?") usage;; 46 | /*|"") : ;; 47 | *) fatal "please use absolute path for the key file" ;; 48 | esac 49 | 50 | keyf=${1:-$DFLT_AUTHFILE} 51 | 52 | if test -f $keyf; then 53 | fatal "file $keyf already exists" 54 | fi 55 | 56 | umask 077 57 | errout=`dd if=$RND_SRC of=$keyf bs=$KEYSIZE count=1 2>&1` 58 | rc=$? 59 | if [ $rc -ne 0 ]; then 60 | echo "$errout" >&2 61 | exit $rc 62 | fi 63 | 64 | chown root:root $keyf 65 | -------------------------------------------------------------------------------- /script/lsb/booth-arbitrator: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # BOOTH daemon init script for SUSE Linux based distributions 4 | # (almost LSB-compliant, except for s/startproc/start_daemon/ etc.) 5 | # 6 | # booth-arbitrator BOOTH arbitrator daemon 7 | # 8 | # chkconfig: - 20 20 9 | # processname: boothd 10 | # pidfile: /var/run/booth.pid 11 | # description: Cluster Ticket Registry 12 | ### BEGIN INIT INFO 13 | # Provides: booth 14 | # Required-Start: $network $syslog 15 | # Required-Stop: $network $syslog 16 | # Should-Start: 17 | # Should-Stop: 18 | # Default-Start: 3 5 19 | # Default-Stop: 0 6 20 | # Short-Description: start and stop BOOTH arbitrator daemon 21 | ### END INIT INFO 22 | 23 | prog="boothd" 24 | exec="/usr/sbin/$prog" 25 | 26 | CONF_DIR=/etc/booth 27 | 28 | 29 | BOOTH_DAEMON_STARTED=0 30 | BOOTH_DAEMON_STARTING=1 31 | BOOTH_DAEMON_EXIST=2 32 | BOOTH_DAEMON_NOT_RUNNING=3 33 | BOOTH_ERROR_GENERIC=4 34 | OCF_ERR_GENERIC=1 35 | OCF_NOT_RUNNING=7 36 | 37 | . /etc/rc.status 38 | 39 | check_status() { 40 | local rc 41 | 42 | rc=$BOOTH_ERROR_GENERIC 43 | eval `"$exec" status "${cnf:+-c$cnf}" ; echo rc=$?` 44 | case $rc in 45 | 0) 46 | # shellcheck disable=SC2154 47 | case "$booth_state" in 48 | started) return $BOOTH_DAEMON_STARTED;; 49 | starting) return $BOOTH_DAEMON_STARTING;; 50 | *) return $BOOTH_ERROR_GENERIC;; 51 | esac 52 | ;; 53 | $OCF_NOT_RUNNING) return $BOOTH_DAEMON_NOT_RUNNING;; 54 | $OCF_ERR_GENERIC) return $BOOTH_ERROR_GENERIC;; 55 | *) return $BOOTH_ERROR_GENERIC;; 56 | esac 57 | } 58 | 59 | status() { 60 | printf "BOOTH daemon is " 61 | if check_status; then 62 | # shellcheck disable=SC2154 63 | echo "running - PID $booth_lockpid for $booth_cfg_name, $booth_addr_string:$booth_port" 64 | return 0 65 | else 66 | echo "stopped" 67 | return 3 68 | fi 69 | } 70 | 71 | start() { 72 | local rc 73 | 74 | [ -x $exec ] || exit 5 75 | check_status; rc=$? 76 | case "$rc" in 77 | $BOOTH_DAEMON_STARTED|$BOOTH_DAEMON_STARTING|$BOOTH_DAEMON_EXIST) 78 | echo "BOOTH daemon is running - PID $booth_lockpid for $booth_cfg_name, $booth_addr_string:$booth_port" 79 | return 0 80 | ;; 81 | $BOOTH_ERROR_GENERIC|$BOOTH_DAEMON_NOT_RUNNING) 82 | printf "Starting BOOTH arbitrator daemon: " 83 | startproc $exec start "${cnf:+-c$cnf}" 84 | rc_status -v 85 | ;; 86 | *) return 1;; 87 | esac 88 | } 89 | 90 | stop() { 91 | local rc wait_time 92 | 93 | wait_time=5 94 | check_status; rc=$? 95 | case $rc in 96 | $BOOTH_DAEMON_STARTED|$BOOTH_DAEMON_STARTING|$BOOTH_DAEMON_EXIST) 97 | ;; 98 | $BOOTH_DAEMON_NOT_RUNNING) 99 | echo "BOOTH arbitrator daemon is not running." 100 | return 0 101 | ;; 102 | *) return 1;; 103 | esac 104 | 105 | printf "Stopping BOOTH arbitrator daemon: " 106 | # $exec stop "${cnf:+-c$cnf}" 107 | # sleep 1 108 | pkill -TERM -s $booth_lockpid boothd 109 | sleep 0.1 110 | check_status; rc=$? 111 | while [ $rc -ne $BOOTH_DAEMON_NOT_RUNNING -a $wait_time -gt 0 ] 112 | do 113 | wait_time=$((wait_time-1)) 114 | sleep 1 115 | check_status; rc=$? 116 | done 117 | if [ $rc -ne $BOOTH_DAEMON_NOT_RUNNING ]; then 118 | pkill -KILL -s $booth_lockpid boothd 119 | sleep 1 120 | check_status; rc=$? 121 | fi 122 | test $rc -eq $BOOTH_DAEMON_NOT_RUNNING 123 | rc_status -v 124 | } 125 | 126 | foreach() { 127 | local cnf 128 | local rc=0 129 | 130 | for cnf in ${BOOTH_CONF_FILE:-$CONF_DIR/*.conf} ; do 131 | "$@" 132 | rc=$((rc|$?)) 133 | done 134 | return $rc 135 | } 136 | 137 | restart() { 138 | stop 139 | start 140 | } 141 | 142 | condrestart() { 143 | local rc 144 | 145 | check_status; rc=$? 146 | 147 | case "$rc" in 148 | $BOOTH_DAEMON_STARTED|$BOOTH_DAEMON_STARTING|$BOOTH_DAEMON_EXIST) 149 | # shellcheck disable=SC2154 150 | [ ! -f "$booth_lockfile" ] || restart 151 | ;; 152 | esac 153 | } 154 | 155 | case "$1" in 156 | start|stop|restart|condrestart|status) 157 | foreach $1 158 | ;; 159 | reload|force-reload) 160 | foreach restart 161 | ;; 162 | try-restart) 163 | foreach condrestart 164 | ;; 165 | *) 166 | echo "Usage: $0 {start|stop|restart|try-restart|condrestart|reload|force-reload|status}" 167 | exit 2 168 | ;; 169 | esac 170 | -------------------------------------------------------------------------------- /script/ocf/booth-site: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # vim: set sw=4 : 3 | # 4 | # Resource Agent for BOOTH site daemon. 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of version 2 of the GNU General Public License as 8 | # published by the Free Software Foundation. 9 | # 10 | # This program is distributed in the hope that it would be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # 14 | # Further, this software is distributed without any warranty that it is 15 | # free of the rightful claim of any third person regarding infringement 16 | # or the like. Any license provided herein, whether implied or 17 | # otherwise, applies only to this software file. Patent licenses, if 18 | # any, provided herein do not apply to combinations of this program with 19 | # other software, or any other product whatsoever. 20 | # 21 | # You should have received a copy of the GNU General Public License along 22 | # with this program; if not, write to the Free Software Foundation, Inc., 23 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24 | # 25 | 26 | ####################################################################### 27 | # Initialization: 28 | 29 | DEFAULT_BIN="boothd" 30 | DEFAULT_CONF="/etc/booth/booth.conf" 31 | 32 | : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} 33 | # shellcheck source=/usr/lib/ocf/lib/heartbeat/ocf-shellfuncs 34 | . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs 35 | 36 | ####################################################################### 37 | 38 | booth_site_meta_data() { 39 | cat < 41 | 42 | 43 | 1.0 44 | 45 | 46 | 47 | This Resource Agent can control the BOOTH site daemon. 48 | It assumes that the binary boothd is in your default PATH. 49 | In most cases, it should be run as a primitive resource. 50 | 51 | BOOTH site daemon 52 | 53 | 54 | 55 | 56 | 57 | The configuration name (or configuration filename) to use. 58 | 59 | BOOTH Options 60 | 61 | 62 | 63 | 64 | 65 | Any additional options to start the BOOTH daemon with 66 | 67 | BOOTH Options 68 | 69 | 70 | 71 | 72 | 73 | The daemon to start 74 | 75 | The daemon to start 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | END 92 | } 93 | 94 | ####################################################################### 95 | 96 | booth_site_usage() { 97 | cat < /dev/null 128 | } 129 | 130 | booth_site_start() { 131 | local rc 132 | 133 | booth_site_status 134 | rc=$? 135 | 136 | case $rc in 137 | 0) 138 | ocf_log info "boothd already running" 139 | return $OCF_SUCCESS 140 | ;; 141 | $OCF_NOT_RUNNING) ;; 142 | esac 143 | 144 | # shellcheck disable=SC2154 145 | # (OCF_RESKEY_args: injected by CRM) 146 | $OCF_RESKEY_daemon daemon -c $OCF_RESKEY_config $OCF_RESKEY_args || 147 | return $OCF_ERR_GENERIC 148 | sleep 1 149 | while ! booth_monitor_basic; do 150 | sleep 1 151 | done 152 | 153 | return $OCF_SUCCESS 154 | } 155 | 156 | booth_site_stop() { 157 | local pid 158 | pid=`get_booth_pid` 159 | if [ -z "$pid" ]; then 160 | ocf_log info "boothd already stopped" 161 | return $OCF_SUCCESS 162 | fi 163 | 164 | ocf_stop_processes TERM 5 $pid 165 | while is_booth_running; do 166 | sleep 1 167 | done 168 | return $OCF_SUCCESS 169 | } 170 | 171 | booth_site_restart() { 172 | booth_site_stop 173 | booth_site_start 174 | } 175 | 176 | booth_site_reload() { 177 | booth_site_restart 178 | } 179 | 180 | booth_site_monitor() { 181 | booth_site_status 182 | case $? in 183 | 0) return $OCF_SUCCESS ;; 184 | $OCF_NOT_RUNNING) return $OCF_NOT_RUNNING ;; 185 | esac 186 | } 187 | 188 | booth_site_validate_all() { 189 | if ! test -f $OCF_RESKEY_config; then 190 | ocf_log err "$OCF_RESKEY_config does not exist" 191 | return $OCF_ERR_INSTALLED 192 | fi 193 | 194 | # shellcheck disable=SC2154 195 | # (OCF_RESKEY_CRM_meta_globally_unique: injected by CRM) 196 | if ocf_is_true $OCF_RESKEY_CRM_meta_globally_unique; then 197 | ocf_log err "$OCF_RESOURCE_INSTANCE must be configured with the globally_unique=false meta attribute" 198 | return $OCF_ERR_CONFIGURED 199 | fi 200 | 201 | return $OCF_SUCCESS 202 | } 203 | 204 | : ${OCF_RESKEY_daemon:=$DEFAULT_BIN} 205 | : ${OCF_RESKEY_config:=$DEFAULT_CONF} 206 | # shellcheck disable=SC2034 207 | # (OCF_REQUIRED_BINARIES consumed by ocf_rarun) 208 | OCF_REQUIRED_BINARIES=${OCF_RESKEY_daemon} 209 | 210 | ocf_rarun $* 211 | -------------------------------------------------------------------------------- /script/ocf/geo_attr.sh: -------------------------------------------------------------------------------- 1 | # 2 | # This program is free software; you can redistribute it and/or modify 3 | # it under the terms of version 2 of the GNU General Public License as 4 | # published by the Free Software Foundation. 5 | # 6 | # This program is distributed in the hope that it would be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 9 | # 10 | # Further, this software is distributed without any warranty that it is 11 | # free of the rightful claim of any third person regarding infringement 12 | # or the like. Any license provided herein, whether implied or 13 | # otherwise, applies only to this software file. Patent licenses, if 14 | # any, provided herein do not apply to combinations of this program with 15 | # other software, or any other product whatsoever. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | # 21 | 22 | # This file is part of the booth project and contains /bin/sh 23 | # code to support GEO attributes 24 | 25 | # USAGE 26 | # 27 | # To use this for updating GEO attributes just follow the 28 | # instructions below. 29 | 30 | # Source this file in your OCF RA script: 31 | # 32 | ## . ${OCF_ROOT}/lib/booth/geo_attr.sh 33 | 34 | # 1) meta-data 35 | # 36 | # geo_attr_meta_data prints descriptions of three parameters. 37 | # Best to invoke it just before printing "". For 38 | # instance: 39 | # 40 | ## cat < 42 | ## ... 43 | ## 44 | ## ... 45 | ## `geo_attr_meta_data` 46 | ## 47 | ## ... 48 | ## EOF 49 | 50 | # 2) validation (validate-all) 51 | # 52 | # Invoke geo_attr_validate_all to test the environment: 53 | # 54 | ## if ! geo_attr_validate_all; then 55 | ## return $OCF_ERR_INSTALL 56 | ## fi 57 | 58 | # 3) Attribute updating 59 | # 60 | # Put something like the following code after the RA updated the 61 | # remote site state (e.g. data replication): 62 | # 63 | ## if [ -n "$OCF_RESKEY_booth_ticket" ]; then 64 | ## if geo_attr_geo_attr $outcome; then 65 | ## # success! 66 | ## else 67 | ## # failed to set the attribute 68 | ## # appropriate error was already logged 69 | ## # normally, more cannot be done at this point 70 | ## # because updating GEO attributes is 71 | ## # essentially a best effort operation 72 | ## fi 73 | ## fi 74 | # 75 | # The outcome variable is a boolean. 76 | # It should reflect the outcome of the operation to update 77 | # data at the site (set to "0" for failure, anything else for 78 | # success). 79 | 80 | # 4) Site name (optional) 81 | # 82 | # We use the special value 'other' to specify the site where the 83 | # attribute is to be updated. That should cover the majority of 84 | # GEO clusters. In case your setup has more than two sites, then 85 | # provide a function named get_site_name which should print the 86 | # appropriate site name (as specified in booth.conf too) to 87 | # stdout. 88 | # 89 | 90 | DEFAULT_BOOTH_CONF="/etc/booth/booth.conf" 91 | : ${OCF_RESKEY_booth_config:=$DEFAULT_BOOTH_CONF} 92 | 93 | geo_attr_meta_data() { 94 | cat < 97 | 98 | Booth ticket. Need to define this to activate GEO attribute 99 | updating. See also the booth_config and geo_attribute parameters. 100 | 101 | Booth ticket 102 | 103 | 104 | 105 | 106 | 107 | Booth configuration name (or configuration filename) to use. 108 | 109 | BOOTH configuration file 110 | 111 | 112 | 113 | 114 | 115 | Attribute name. If not specified, we'll get the name from the 116 | first "attr-prereq" definition for the given ticket. This 117 | normally needs to be used only in case there are multiple 118 | "attr-prereq" directives for the ticket. 119 | 120 | GEO attribute 121 | 122 | 123 | 124 | END 125 | } 126 | 127 | geo_attr_get_attr() { 128 | local tkt cnf attr 129 | tkt=$OCF_RESKEY_booth_ticket 130 | cnf=$OCF_RESKEY_booth_config 131 | attr=$OCF_RESKEY_geo_attribute 132 | 133 | awk -v attr="$attr" ' 134 | n && /^[[:space:]]*attr-prereq = auto .* eq / { 135 | if (attr == "" || attr == $4) { 136 | print $4,$6; exit 137 | } 138 | } 139 | n && (/^$/ || /^ticket.*/) {exit} 140 | /^ticket.*'$tkt'/ {n=1} 141 | ' $cnf 142 | } 143 | 144 | # arguments: 145 | # $1: 0 reset the attribute 146 | # != 0 set the attribute 147 | # 148 | geo_attr_geo_attr() { 149 | local val site 150 | 151 | val=$1 152 | set -- `geo_attr_get_attr` 153 | if test z"`command -v get_site_name`" = z"get_site_name"; then 154 | site=`get_site_name` 155 | else 156 | site="other" 157 | fi 158 | 159 | if [ "$val" = "0" ]; then 160 | geostore delete -s $site $1 >/dev/null 2>&1 161 | else 162 | geostore set -s $site $1 $2 163 | fi 164 | } 165 | 166 | geo_attr_read_attr() { 167 | local site 168 | 169 | set -- `geo_attr_get_attr` 170 | if test z"`command -v get_site_name`" = z"get_site_name"; then 171 | site=`get_site_name` 172 | else 173 | site="other" 174 | fi 175 | 176 | geostore get -s $site $1 177 | } 178 | 179 | # test the environment for geo_attr 180 | # 181 | geo_attr_validate_all() { 182 | if [ -z "$OCF_RESKEY_booth_ticket" ]; then 183 | return 0 184 | fi 185 | 186 | if ! test -f "$OCF_RESKEY_booth_config"; then 187 | ocf_log err "booth configuration $OCF_RESKEY_booth_config doesn't exist" 188 | return 1 189 | fi 190 | 191 | if ! grep -qs "^ticket[[:space:]]*=[[:space:]]*\"$OCF_RESKEY_booth_ticket\"" $OCF_RESKEY_booth_config; then 192 | ocf_log err "ticket $OCF_RESKEY_booth_ticket not found in $OCF_RESKEY_booth_config" 193 | return 1 194 | fi 195 | 196 | set -- `geo_attr_get_attr` 197 | if [ $# -eq 0 ]; then 198 | ocf_log err "no attr-prereq defined in $OCF_RESKEY_booth_ticket" 199 | return 1 200 | fi 201 | 202 | return 0 203 | } 204 | -------------------------------------------------------------------------------- /script/ocf/geostore: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # 4 | # geostore OCF RA. Just an example on how to use 5 | # geo-attr.sh 6 | # 7 | # Copyright (c) 2015 Dejan Muhamedagic 8 | # All Rights Reserved. 9 | # 10 | # This program is free software; you can redistribute it and/or modify 11 | # it under the terms of version 2 of the GNU General Public License as 12 | # published by the Free Software Foundation. 13 | # 14 | # This program is distributed in the hope that it would be useful, but 15 | # WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | # 18 | # Further, this software is distributed without any warranty that it is 19 | # free of the rightful claim of any third person regarding infringement 20 | # or the like. Any license provided herein, whether implied or 21 | # otherwise, applies only to this software file. Patent licenses, if 22 | # any, provided herein do not apply to combinations of this program with 23 | # other software, or any other product whatsoever. 24 | # 25 | # You should have received a copy of the GNU General Public License along 26 | # with this program; if not, write to the Free Software Foundation, Inc., 27 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 28 | # 29 | 30 | ####################################################################### 31 | # Initialization: 32 | 33 | : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} 34 | # shellcheck source=/usr/lib/ocf/lib/heartbeat/ocf-shellfuncs 35 | . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs 36 | # shellcheck source=script/ocf/geo_attr.sh 37 | . ${OCF_ROOT}/lib/booth/geo_attr.sh 38 | 39 | ####################################################################### 40 | 41 | geostore_meta_data() { 42 | cat < 44 | 45 | 46 | 1.0 47 | 48 | 49 | This is the geostore Resource Agent. It's a sample for how to use 50 | geo_attr.sh. 51 | 52 | Sample GEO attribute RA 53 | 54 | 55 | `geo_attr_meta_data` 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | END 70 | } 71 | 72 | ####################################################################### 73 | 74 | geostore_usage() { 75 | cat <&1` 25 | if [ $? -ne 0 ]; then 26 | @LOGGER@ "$0: crm_simulate failed" 27 | @LOGGER@ "$0: crm_simulate: $status" 28 | exit 1 29 | fi 30 | 31 | if echo "$status" | 32 | sed -n '/^Revised cluster status:/,$p' | 33 | egrep "^[[:space:]]+${service}[[:space:]]+\(.*\):[[:space:]]+Started ([^[:space:]]+) *$" >/dev/null 34 | then 35 | # can be started - we're done. 36 | exit 0 37 | fi 38 | 39 | # If target-role is Stopped, it judges with being stopped explicitly. 40 | output=$(crm_resource --meta --get-parameter="target-role" --resource=$service 2>/dev/null) 41 | rc=$? 42 | if [ $rc -eq 0 -a "$output" = "Stopped" ]; then 43 | exit 0 44 | fi 45 | 46 | # is ticket in standby? 47 | output=$(crm_ticket --ticket "$BOOTH_TICKET" --get-attr standby) 48 | rc=$? 49 | if [ $rc -eq 0 -a "$output" = true ]; then 50 | exit 0 51 | fi 52 | 53 | # Some error occured. 54 | # Try to help the admin with a bit of diagnostic. 55 | # 56 | # disallow ms-resources, ie. only primitives wanted here 57 | if ! crm_resource -l | grep -v ":" | grep "$service" ; then 58 | @LOGGER@ "Defined resource '$service' in $BOOTH_CONF_PATH is not a primitive??" 59 | fi 60 | 61 | exit 1 62 | -------------------------------------------------------------------------------- /script/wireshark-dissector.lua: -------------------------------------------------------------------------------- 1 | -- dofile("wireshark-dissector.lua") 2 | -- 3 | do 4 | booth_proto = Proto("Booth","Booth") 5 | local hdr_len = 48 6 | 7 | function T32(tree, buffer, start, format) 8 | local b = buffer(start, 4) 9 | return tree:add(b, string.format(format, b:uint())) 10 | end 11 | 12 | function booth_proto.dissector(buffer, pinfo, tree) 13 | local endbuf = buffer:len() 14 | pinfo.cols.protocol = "Booth" 15 | 16 | if (endbuf < hdr_len) then 17 | pinfo.cols.info = "Booth - too small" 18 | else 19 | local hdr = tree:add(booth_proto, buffer(0, hdr_len), "Booth header") 20 | 21 | local cmd = buffer(28, 4) 22 | local tcmd = T32(hdr, cmd, 0, "Cmd \"" .. cmd:string() .. "\""); 23 | 24 | local req = buffer(32, 4) 25 | if (req:uint() > 0) then 26 | local treq = T32(hdr, req, 0, "Req \"" .. req:string() .. "\""); 27 | end 28 | 29 | local reason = buffer(40, 4) 30 | if (reason:uint() > 0) then 31 | local treason = T32(hdr, reason, 0, "Reason \"" .. reason:string() .. "\""); 32 | end 33 | 34 | local from = buffer(20, 4) 35 | local tfrom = T32(hdr, from, 0, "From %08x"); 36 | if bit.band(from:uint(), 0x80000000) > 0 then 37 | tfrom:add_expert_info(PI_PROTOCOL, PI_WARN, "Highest bit set") 38 | end 39 | 40 | local len = buffer(24, 4) 41 | local tlen = T32(hdr, len, 0, "Length %8d"); 42 | if len:uint() > 1000 then 43 | tlen:add_expert_info(PI_PROTOCOL, PI_WARN, "Length too big?") 44 | end 45 | 46 | T32(hdr, buffer, 44, "Result %08x"); 47 | T32(hdr, buffer, 12, "Magic %08x"); 48 | T32(hdr, buffer, 16, "Version %08x"); 49 | 50 | T32(hdr, buffer, 0, "IV %08x"); 51 | T32(hdr, buffer, 4, "Auth1 %08x"); 52 | T32(hdr, buffer, 8, "Auth2 %08x"); 53 | 54 | 55 | 56 | if (endbuf > hdr_len) then 57 | local tick = tree:add(booth_proto, buffer(hdr_len, endbuf-hdr_len), "Booth data") 58 | local name = buffer(hdr_len, 64) 59 | tick:add(name, "Ticket name: ", name:string()) 60 | 61 | T32(tick, buffer, hdr_len+64 + 0, "Leader: %08x") 62 | T32(tick, buffer, hdr_len+64 + 4, "Term: %08x") 63 | T32(tick, buffer, hdr_len+64 + 8, "Term valid for: %08x") 64 | end 65 | 66 | pinfo.cols.info = "Booth, cmd " .. cmd:string() 67 | end 68 | tree:add(booth_proto, buffer(0, endbuf), "data") 69 | end 70 | 71 | local tbl = DissectorTable.get("udp.port") 72 | tbl:add(9929, booth_proto) 73 | 74 | local tbl = DissectorTable.get("tcp.port") 75 | tbl:add(9929, booth_proto) 76 | end 77 | 78 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CFLAGS = -fPIC -Werror -funsigned-char -Wno-pointer-sign 4 | 5 | 6 | AM_CPPFLAGS = -I$(top_builddir)/include 7 | 8 | sbin_PROGRAMS = boothd 9 | 10 | boothd_SOURCES = config.c main.c raft.c ticket.c transport.c \ 11 | pacemaker.c handler.c request.c attr.c manual.c 12 | 13 | noinst_HEADERS = \ 14 | attr.h booth.h handler.h log.h pacemaker.h request.h timer.h \ 15 | auth.h config.h inline-fn.h manual.h raft.h ticket.h transport.h 16 | 17 | if BUILD_TIMER_C 18 | boothd_SOURCES += timer.c 19 | endif 20 | 21 | if BUILD_AUTH_C 22 | boothd_SOURCES += auth.c 23 | endif 24 | 25 | boothd_LDFLAGS = $(OS_DYFLAGS) -L./ 26 | boothd_LDADD = -lm $(GLIB_LIBS) $(ZLIB_LIBS) 27 | boothd_CFLAGS = $(GLIB_CFLAGS) $(PCMK_CFLAGS) 28 | 29 | if !LOGGING_LIBQB 30 | boothd_LDADD += -lplumb 31 | else 32 | boothd_LDADD += $(LIBQB_LIBS) 33 | boothd_SOURCES += alt/logging_libqb.c 34 | noinst_HEADERS += alt/logging_libqb.h 35 | endif 36 | 37 | if !RANGE2RANDOM_GLIB 38 | boothd_LDADD += -lplumb 39 | else 40 | boothd_LDADD += $(GLIB_LIBS) 41 | boothd_SOURCES += alt/range2random_glib.c 42 | noinst_HEADERS += alt/range2random_glib.h 43 | endif 44 | 45 | if !NAMETAG_LIBSYSTEMD 46 | boothd_LDADD += -lplumbgpl 47 | else 48 | boothd_LDADD += $(LIBSYSTEMD_LIBS) 49 | boothd_SOURCES += alt/nametag_libsystemd.c 50 | noinst_HEADERS += alt/nametag_libsystemd.h 51 | endif 52 | 53 | if COREDUMP_NURSING 54 | boothd_LDADD += -lplumb 55 | endif 56 | -------------------------------------------------------------------------------- /src/alt/logging_libqb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jan Pokorny 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "logging_libqb.h" 25 | 26 | int debug_level = 0; 27 | 28 | /* ENV_X definitions based on glue/lib/clplumbing/cl_log.c of glue project: 29 | http://hg.linux-ha.org/glue */ 30 | #define ENV_HADEBUGVAL "HA_debug" 31 | #define ENV_LOGFENV "HA_logfile" /* well-formed log file :-) */ 32 | #define ENV_DEBUGFENV "HA_debugfile" /* Debug log file */ 33 | #define ENV_LOGFACILITY "HA_logfacility"/* Facility to use for logger */ 34 | #define ENV_SYSLOGFMT "HA_syslogmsgfmt"/* TRUE if we should use syslog message formatting */ 35 | 36 | void 37 | alt_qb_inherit_logging_environment(void) 38 | { 39 | char *inherit_env; 40 | 41 | /* Don't need to free the return pointer from getenv */ 42 | inherit_env = getenv(ENV_HADEBUGVAL); 43 | if (inherit_env != NULL && atoi(inherit_env) != 0 ) 44 | debug_level = atoi(inherit_env); 45 | 46 | inherit_env = getenv(ENV_LOGFENV); 47 | if (inherit_env != NULL && *inherit_env != '\0') { 48 | int32_t log_fd = qb_log_file_open(inherit_env); 49 | qb_log_ctl(log_fd, QB_LOG_CONF_ENABLED, QB_TRUE); 50 | /* do not log debug info even if debug_level non-zero */ 51 | qb_log_filter_ctl(log_fd, QB_LOG_FILTER_ADD, 52 | QB_LOG_FILTER_FILE, "*", LOG_INFO); 53 | } 54 | 55 | inherit_env = getenv(ENV_DEBUGFENV); 56 | if (inherit_env != NULL && *inherit_env != '\0') { 57 | int32_t log_fd = qb_log_file_open(inherit_env); 58 | qb_log_ctl(log_fd, QB_LOG_CONF_ENABLED, QB_TRUE); 59 | } 60 | 61 | inherit_env = getenv(ENV_LOGFACILITY); 62 | if (inherit_env != NULL && *inherit_env != '\0') { 63 | int fac = qb_log_facility2int(inherit_env); 64 | if (fac > 0) 65 | qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, fac); 66 | else 67 | qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); 68 | } 69 | 70 | inherit_env = getenv(ENV_SYSLOGFMT); 71 | if (inherit_env != NULL && *inherit_env != '\0' 72 | && ( !strcasecmp(inherit_env, "false") 73 | || !strcasecmp(inherit_env, "off") 74 | || !strcasecmp(inherit_env, "no") 75 | || !strcasecmp(inherit_env, "n") 76 | || !strcasecmp(inherit_env, "0"))){ 77 | enum qb_log_target_slot i; 78 | for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) { 79 | if (i == QB_LOG_SYSLOG || i == QB_LOG_BLACKBOX) 80 | continue; 81 | qb_log_format_set(i, NULL); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/alt/logging_libqb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jan Pokorny 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include 20 | 21 | #include "b_config.h" 22 | 23 | /* qb logging compat definitions */ 24 | #if (!defined LOGGING_LIBQB_MAJOR || (LOGGING_LIBQB_MAJOR < 1)) 25 | enum tmp_log_target_slot { 26 | TMP_LOG_SYSLOG = QB_LOG_SYSLOG, 27 | TMP_LOG_STDERR = QB_LOG_STDERR, 28 | TMP_LOG_BLACKBOX = QB_LOG_BLACKBOX, 29 | TMP_LOG_TARGET_MAX = QB_LOG_TARGET_MAX, 30 | }; 31 | 32 | #undef QB_LOG_SYSLOG 33 | #undef QB_LOG_STDERR 34 | #undef QB_LOG_BLACKBOX 35 | #undef QB_LOG_TARGET_MAX 36 | 37 | enum qb_log_target_slot { 38 | QB_LOG_TARGET_START, 39 | QB_LOG_SYSLOG = TMP_LOG_SYSLOG, 40 | QB_LOG_STDERR = TMP_LOG_STDERR, 41 | QB_LOG_BLACKBOX = TMP_LOG_BLACKBOX, 42 | QB_LOG_TARGET_MAX = TMP_LOG_TARGET_MAX, 43 | }; 44 | 45 | #define QB_LOG_CTL2_S(a) (a) 46 | #define qb_log_ctl2(t, s, a) ((void) 0) 47 | #endif 48 | 49 | 50 | #ifndef HA_LOG_FACILITY 51 | /* based on glue/configure.ac of glue project: http://hg.linux-ha.org/glue */ 52 | #define HA_LOG_FACILITY LOG_DAEMON 53 | #endif 54 | 55 | extern int debug_level; 56 | #define ANYDEBUG (debug_level) 57 | 58 | void alt_qb_inherit_logging_environment(void); 59 | 60 | #define cl_log_set_entity(ent) \ 61 | (void) qb_log_ctl2(QB_LOG_SYSLOG, QB_LOG_CONF_IDENT, QB_LOG_CTL2_S(ent)) 62 | 63 | #define cl_log_enable_stderr(b) \ 64 | (void) qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, b ? QB_TRUE : QB_FALSE) 65 | 66 | #define cl_log_set_facility(f) \ 67 | (void) qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, f) 68 | 69 | #define cl_inherit_logging_environment(logqueuemax) \ 70 | alt_qb_inherit_logging_environment() 71 | -------------------------------------------------------------------------------- /src/alt/nametag_libsystemd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jan Pokorny 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "nametag_libsystemd.h" 27 | #include "booth.h" 28 | #include "log.h" 29 | #include "transport.h" 30 | 31 | /* assume first argument after "fmt" is for DAEMON_NAME, that is 32 | really not of interest in our "nametag" function based on 33 | sd_notify (that very data point is provided implicitly) */ 34 | void sd_notify_wrapper(const char *fmt, ...) 35 | { 36 | /* assume that first %s in fmt is intended for DAEMON_NAME, 37 | i.e., for first argument following fmt in original 38 | set_proc_title invocation, which has already been dropped 39 | before it boils down here (using the wrapping macro trick); 40 | we now simply append the reset after that first %s 41 | (with whitespace stripped) to the "Running: " prefix */ 42 | int rv; 43 | char buffer[255]; 44 | char *fmt_iter; 45 | char *suffix = NULL; 46 | va_list ap; 47 | 48 | switch (local->type) { 49 | case ARBITRATOR: 50 | case GEOSTORE: 51 | break; 52 | default: 53 | return; /* not expected to be run as system service */ 54 | } 55 | 56 | fmt_iter = strchr(fmt, '%'); 57 | while (fmt_iter) { 58 | switch (*++fmt_iter) { 59 | case 's': suffix = fmt_iter; 60 | /* fall through */ 61 | default: fmt_iter = NULL; 62 | } 63 | } 64 | if (!suffix) { 65 | log_warn("%s:%d: invalid format: %s", __FILE__, __LINE__, fmt); 66 | return; 67 | } 68 | while (isspace(*++suffix)) /* noop */ ; 69 | 70 | va_start(ap, fmt); 71 | fmt_iter = va_arg(ap, char *); /* just shift by one */ 72 | assert(!strcmp(fmt_iter, DAEMON_NAME)); 73 | rv = vsnprintf(buffer, sizeof(buffer), suffix, ap); 74 | va_end(ap); 75 | 76 | if (rv < 0) { 77 | log_warn("%s:%d: vsnprintf fail", __FILE__, __LINE__); 78 | } 79 | 80 | rv = sd_notifyf(0, "READY=1\n" 81 | "STATUS=Running: %s", 82 | buffer); 83 | if (rv < 0) 84 | log_warn("%s:%d: sd_notifyf fail", __FILE__, __LINE__); 85 | } 86 | -------------------------------------------------------------------------------- /src/alt/nametag_libsystemd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jan Pokorny 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | void 20 | sd_notify_wrapper(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); 21 | 22 | #define init_set_proc_title(c, a, e) /* omitted */ 23 | #define set_proc_title sd_notify_wrapper 24 | -------------------------------------------------------------------------------- /src/alt/range2random_glib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jan Pokorny 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "range2random_glib.h" 25 | #include "ticket.h" 26 | 27 | int 28 | alt_glib_rand_from_interval(int from, int to) 29 | { 30 | assert(from >= 0 && from < to); 31 | assert(sizeof(to) <= sizeof(gint32) || (to < 0x7fffffff)); 32 | return (int) g_random_int_range(from, to); 33 | } 34 | -------------------------------------------------------------------------------- /src/alt/range2random_glib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Jan Pokorny 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | int alt_glib_rand_from_interval(int from, int to); 20 | 21 | #define cl_rand_from_interval(from, to) \ 22 | alt_glib_rand_from_interval(from, to) 23 | -------------------------------------------------------------------------------- /src/attr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _ATTR_H 20 | #define _ATTR_H 21 | 22 | #define ATTR_PROG "geostore" 23 | 24 | #include "b_config.h" 25 | #include "log.h" 26 | #include 27 | #include 28 | #include "booth.h" 29 | #include "timer.h" 30 | #include 31 | 32 | void print_geostore_usage(void); 33 | int test_attr_reply(cmd_result_t reply_code, cmd_request_t cmd); 34 | 35 | /** 36 | * @internal 37 | * Carry out a geo-atribute related command 38 | * 39 | * @param[in,out] conf config object to refer to 40 | * @param[in] cmd what to perform 41 | * 42 | * @return 0 or negative value (-1 or -errno) on error 43 | */ 44 | int do_attr_command(struct booth_config *conf, cmd_request_t cmd); 45 | 46 | /** 47 | * @internal 48 | * Handle geostore related operations 49 | * 50 | * @param[in,out] conf config object to refer to 51 | * @param[in] req_client client structure of the sender 52 | * @param[in] buf client message 53 | * 54 | * @return 1 or see #attr_list, #attr_get, #attr_set, and #attr_del 55 | */ 56 | int process_attr_request(struct booth_config *conf, struct client *req_client, 57 | void *buf); 58 | 59 | /** 60 | * @internal 61 | * Second stage of incoming message handling (after authentication) 62 | * 63 | * @param[in,out] conf config object to refer to 64 | * @param[in] buf incoming message 65 | * @param[in] source site of the sender 66 | * 67 | * @return -1 on error, 0 otherwise 68 | */ 69 | int attr_recv(struct booth_config *conf, void *buf, struct booth_site *source); 70 | 71 | int store_geo_attr(struct ticket_config *tk, const char *name, const char *val, int notime); 72 | 73 | #endif /* _ATTR_H */ 74 | -------------------------------------------------------------------------------- /src/auth.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include "auth.h" 20 | 21 | #if HAVE_LIBGNUTLS 22 | /* calculate the HMAC of the message in data and store it in result 23 | * it is up to the caller to make sure that there's enough space 24 | * at result for the MAC 25 | */ 26 | int calc_hmac(const void *data, size_t datalen, 27 | int hid, unsigned char *result, char *key, unsigned int keylen) 28 | { 29 | int rc; 30 | 31 | /* 32 | * Only SHA1 is supported so we can hardcode GNUTLS_MAC_SHA1 33 | */ 34 | if (hid != BOOTH_COMPAT_MHASH_SHA1) { 35 | log_error("calc_hmac unsupported HMAC algorithm %u", hid); 36 | return -1; 37 | } 38 | 39 | /* 40 | * This shouldn't happen but gnutls_hmac_fast segfault if key or 41 | * data are NULL so it is better to check beforehand. 42 | */ 43 | if (data == NULL || key == NULL) { 44 | log_error("calc_hmac data or key is NULL"); 45 | return -1; 46 | } 47 | 48 | rc = gnutls_hmac_fast(GNUTLS_MAC_SHA1, key, keylen, data, datalen, result); 49 | if (rc) { 50 | log_error("gnutls_hmac_fast: %s", gnutls_strerror(rc)); 51 | return -1; 52 | } 53 | 54 | return rc; 55 | } 56 | 57 | /* test HMAC 58 | */ 59 | int verify_hmac(const void *data, size_t datalen, 60 | int hid, unsigned char *hmac, char *key, int keylen) 61 | { 62 | unsigned char *our_hmac; 63 | int rc; 64 | unsigned int hlen; 65 | 66 | /* 67 | * Only SHA1 is supported so we can hardcode GNUTLS_MAC_SHA1 68 | */ 69 | if (hid != BOOTH_COMPAT_MHASH_SHA1) { 70 | log_error("verify_hmac unsupported HMAC algorithm %u", hid); 71 | return -1; 72 | } 73 | 74 | if (data == NULL || key == NULL) { 75 | log_error("verify_hmac data or key is NULL"); 76 | return -1; 77 | } 78 | 79 | hlen = gnutls_hmac_get_len(GNUTLS_MAC_SHA1); 80 | if (!hlen) 81 | return -1; 82 | 83 | our_hmac = calloc(hlen, 1); 84 | if (!our_hmac) 85 | return -1; 86 | 87 | rc = calc_hmac(data, datalen, hid, our_hmac, key, keylen); 88 | if (rc) 89 | goto out_free; 90 | rc = memcmp(our_hmac, hmac, hlen); 91 | 92 | out_free: 93 | if (our_hmac) 94 | free(our_hmac); 95 | return rc; 96 | } 97 | #endif 98 | 99 | #if HAVE_LIBGCRYPT 100 | /* calculate the HMAC of the message in data and store it in result 101 | * it is up to the caller to make sure that there's enough space 102 | * at result for the MAC 103 | */ 104 | int calc_hmac(const void *data, size_t datalen, 105 | int hid, unsigned char *result, char *key, unsigned int keylen) 106 | { 107 | static gcry_md_hd_t digest; 108 | gcry_error_t err; 109 | int hlen; 110 | 111 | hlen = gcry_md_get_algo_dlen(hid); 112 | if (!hlen) 113 | return -1; 114 | 115 | if (!digest) { 116 | err = gcry_md_open(&digest, hid, GCRY_MD_FLAG_HMAC); 117 | if (err) { 118 | log_error("gcry_md_open: %s", gcry_strerror(err)); 119 | return -1; 120 | } 121 | err = gcry_md_setkey(digest, key, keylen); 122 | if (err) { 123 | log_error("gcry_md_open: %s", gcry_strerror(err)); 124 | return -1; 125 | } 126 | } 127 | gcry_md_write(digest, data, datalen); 128 | memcpy(result, gcry_md_read(digest, 0), hlen); 129 | gcry_md_reset(digest); 130 | return 0; 131 | } 132 | 133 | /* test HMAC 134 | */ 135 | int verify_hmac(const void *data, size_t datalen, 136 | int hid, unsigned char *hmac, char *key, int keylen) 137 | { 138 | unsigned char *our_hmac; 139 | int rc; 140 | int hlen; 141 | 142 | hlen = gcry_md_get_algo_dlen(hid); 143 | if (!hlen) 144 | return -1; 145 | 146 | our_hmac = malloc(hlen); 147 | if (!our_hmac) 148 | return -1; 149 | 150 | rc = calc_hmac(data, datalen, hid, our_hmac, key, keylen); 151 | if (rc) 152 | goto out_free; 153 | rc = memcmp(our_hmac, hmac, hlen); 154 | 155 | out_free: 156 | if (our_hmac) 157 | free(our_hmac); 158 | return rc; 159 | } 160 | #endif 161 | 162 | #if HAVE_LIBMHASH 163 | /* calculate the HMAC of the message in data and store it in result 164 | * it is up to the caller to make sure that there's enough space 165 | * at result for the MAC 166 | */ 167 | int calc_hmac(const void *data, size_t datalen, 168 | hashid hid, unsigned char *result, char *key, int keylen) 169 | { 170 | MHASH td; 171 | size_t block_size; 172 | 173 | block_size = mhash_get_hash_pblock(hid); 174 | if (!block_size) 175 | return -1; 176 | 177 | td = mhash_hmac_init(hid, key, keylen, block_size); 178 | if (!td) 179 | return -1; 180 | 181 | (void)mhash(td, data, datalen); 182 | if (mhash_hmac_deinit(td, result)) 183 | return -1; 184 | 185 | return 0; 186 | } 187 | 188 | /* test HMAC 189 | */ 190 | int verify_hmac(const void *data, size_t datalen, 191 | hashid hid, unsigned char *hmac, char *key, int keylen) 192 | { 193 | MHASH td; 194 | unsigned char *our_hmac = NULL; 195 | int rc = -1; 196 | 197 | td = mhash_hmac_init(hid, key, keylen, 198 | mhash_get_hash_pblock(hid)); 199 | if (!td) 200 | return -1; 201 | 202 | our_hmac = malloc(mhash_get_block_size(hid)); 203 | if (!our_hmac) 204 | return -1; 205 | 206 | (void)mhash(td, data, datalen); 207 | if (mhash_hmac_deinit(td, our_hmac)) 208 | goto out_free; 209 | 210 | rc = memcmp(our_hmac, hmac, mhash_get_block_size(hid)); 211 | 212 | out_free: 213 | if (our_hmac) 214 | free(our_hmac); 215 | return rc; 216 | } 217 | 218 | #endif 219 | -------------------------------------------------------------------------------- /src/auth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include "b_config.h" 20 | #include "log.h" 21 | #include 22 | 23 | #if HAVE_LIBGNUTLS 24 | 25 | #include 26 | #include 27 | 28 | /* 29 | * We need to stay backwards compatible. Both gcrypt and mhash defines 30 | * SHA1 algorithm as 2. but GNUTLS_MAC_SHA1 is defined as 3, so hardcode 31 | * 2 here and use correct value in auth.c 32 | */ 33 | #define BOOTH_COMPAT_MHASH_SHA1 2 34 | #define BOOTH_HASH BOOTH_COMPAT_MHASH_SHA1 35 | 36 | int calc_hmac(const void *data, size_t datalen, 37 | int hid, unsigned char *result, char *key, unsigned int keylen); 38 | int verify_hmac(const void *data, size_t datalen, 39 | int hid, unsigned char *hmac, char *key, int keylen); 40 | #endif 41 | 42 | #if HAVE_LIBGCRYPT 43 | 44 | #include 45 | 46 | #define BOOTH_HASH GCRY_MD_SHA1 47 | 48 | int calc_hmac(const void *data, size_t datalen, 49 | int hid, unsigned char *result, char *key, unsigned int keylen); 50 | int verify_hmac(const void *data, size_t datalen, 51 | int hid, unsigned char *hmac, char *key, int keylen); 52 | #endif 53 | 54 | #if HAVE_LIBMHASH 55 | 56 | #include 57 | 58 | #define BOOTH_HASH MHASH_SHA1 59 | 60 | int calc_hmac(const void *data, size_t datalen, 61 | hashid hid, unsigned char *result, char *key, int keylen); 62 | int verify_hmac(const void *data, size_t datalen, 63 | hashid hid, unsigned char *hmac, char *key, int keylen); 64 | #endif 65 | -------------------------------------------------------------------------------- /src/booth_config.h.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/booth/9391e6a31e272e3c269e0a78d61ed27d12bf2668/src/booth_config.h.in -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Jiaju Zhang 3 | * Copyright (C) 2013-2014 Philipp Marek 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #ifndef _CONFIG_H 21 | #define _CONFIG_H 22 | 23 | #include 24 | #include 25 | #include "booth.h" 26 | #include "timer.h" 27 | #include "raft.h" 28 | 29 | /* Forward declaration of the booth config structure that will be defined later. 30 | * This is necessary here because transport.h references booth_config, but this 31 | * file also references transport_layer_t. We need some way to break the 32 | * circular dependency. 33 | * 34 | * This also means that config.h must always be included before transport.h in 35 | * any source files. 36 | */ 37 | struct booth_config; 38 | 39 | #include "transport.h" 40 | 41 | 42 | /** @{ */ 43 | /** Definitions for in-RAM data. */ 44 | 45 | #define MAX_NODES 16 46 | #define MAX_ARGS 16 47 | #define TICKET_ALLOC 16 48 | 49 | #define OTHER_SITE "other" 50 | 51 | 52 | typedef enum { 53 | EXTPROG_IDLE, 54 | EXTPROG_RUNNING, 55 | EXTPROG_EXITED, 56 | EXTPROG_IGNORE, 57 | } extprog_state_e; 58 | 59 | #define tk_test tk->clu_test 60 | 61 | typedef enum { 62 | ATTR_OP_EQ = 1, 63 | ATTR_OP_NE, 64 | } attr_op_e; 65 | 66 | typedef enum { 67 | GRANT_AUTO = 1, 68 | GRANT_MANUAL, 69 | } grant_type_e; 70 | 71 | typedef enum { 72 | TICKET_MODE_AUTO = 1, 73 | TICKET_MODE_MANUAL, 74 | } ticket_mode_e; 75 | 76 | struct toktab { 77 | const char *str; 78 | int val; 79 | }; 80 | 81 | struct attr_prereq { 82 | grant_type_e grant_type; /* grant type */ 83 | attr_op_e op; /* attribute operation */ 84 | char *attr_name; 85 | char *attr_val; 86 | }; 87 | 88 | struct ticket_config { 89 | /** \name Configuration items. 90 | * @{ */ 91 | /** Name of ticket. */ 92 | boothc_ticket name; 93 | 94 | /** How long a term lasts if not refreshed (in ms) */ 95 | int term_duration; 96 | 97 | /** Network related timeouts (in ms) */ 98 | int timeout; 99 | 100 | /** Retries before giving up. */ 101 | int retries; 102 | 103 | /** If >0, time to wait for a site to get fenced. 104 | * The ticket may be acquired after that timespan by 105 | * another site. */ 106 | int acquire_after; 107 | 108 | /* How often to renew the ticket (in ms) 109 | */ 110 | int renewal_freq; 111 | 112 | 113 | /* Program to ask whether it makes sense to 114 | * acquire the ticket */ 115 | struct clu_test { 116 | char *path; 117 | int is_dir; 118 | char *argv[MAX_ARGS]; 119 | pid_t pid; 120 | int status; /* child exit status */ 121 | extprog_state_e progstate; /* program running/idle/waited on */ 122 | } clu_test; 123 | 124 | /** Node weights. */ 125 | int weight[MAX_NODES]; 126 | 127 | /* Mode operation of the ticket. 128 | * Set to MANUAL to make sure that the ticket will be manipulated 129 | * only by manual commands of the administrator. In such a case 130 | * automatic elections will be disabled. 131 | * Manual tickets do not have to be renewed every some time. 132 | * The leader will continue to send heartbeat messages to other sites. 133 | */ 134 | ticket_mode_e mode; 135 | /** @} */ 136 | 137 | 138 | /** \name Runtime values. 139 | * @{ */ 140 | /** Current state. */ 141 | server_state_e state; 142 | 143 | /** Next state. Used at startup. */ 144 | server_state_e next_state; 145 | 146 | /** When something has to be done */ 147 | timetype next_cron; 148 | 149 | /** Current leader. This is effectively the log[] in Raft. */ 150 | struct booth_site *leader; 151 | 152 | /** Leader that got lost. */ 153 | struct booth_site *lost_leader; 154 | 155 | /** Is the ticket granted? */ 156 | int is_granted; 157 | 158 | /** Which site considered itself a leader. 159 | * For manual tickets it is possible, that 160 | * more than one site will act as a leader. 161 | * This array is used for tracking that situation 162 | * and notifying the user about the issue. 163 | * 164 | * Possible values for every site: 165 | * 0: the site does not claim to be the leader 166 | * 1: the site considers itself a leader and 167 | * is sending or used to send heartbeat messages 168 | * 169 | * The site will be marked as '1' until this site 170 | * receives revoke confirmation. 171 | * 172 | * If more than one site has '1', the geo cluster is 173 | * considered to have multiple leadership and proper 174 | * warning are generated. 175 | */ 176 | int sites_where_granted[MAX_NODES]; 177 | 178 | /** Timestamp of leadership expiration */ 179 | timetype term_expires; 180 | /** End of election period */ 181 | timetype election_end; 182 | struct booth_site *voted_for; 183 | 184 | 185 | /** Who the various sites vote for. 186 | * NO_OWNER = no vote yet. */ 187 | struct booth_site *votes_for[MAX_NODES]; 188 | /* bitmap */ 189 | uint64_t votes_received; 190 | 191 | /** Last voting round that was seen. */ 192 | uint32_t current_term; 193 | 194 | /** Do ticket updates whenever we get enough heartbeats. 195 | * But do that only once. 196 | * This is reset to 0 whenever we broadcast heartbeat and set 197 | * to 1 once enough acks are received. 198 | * Increased to 2 when the ticket is commited to the CIB (see 199 | * delay_commit). 200 | */ 201 | uint32_t ticket_updated; 202 | 203 | /** Outcome of whatever ticket request was processed. 204 | * Can also be an intermediate stage. 205 | */ 206 | uint32_t outcome; 207 | /** @} */ 208 | 209 | 210 | /** */ 211 | uint32_t last_applied; 212 | uint32_t next_index[MAX_NODES]; 213 | uint32_t match_index[MAX_NODES]; 214 | 215 | 216 | /* Why did we start the elections? 217 | */ 218 | cmd_reason_t election_reason; 219 | 220 | /* if it is potentially dangerous to grant the ticket 221 | * immediately, then this is set to some point in time, 222 | * usually (now + term_duration + acquire_after) 223 | */ 224 | timetype delay_commit; 225 | 226 | /* the last request RPC we sent 227 | */ 228 | uint32_t last_request; 229 | /* if we expect some acks, then set this to the id of 230 | * the RPC which others will send us; it is cleared once all 231 | * replies were received 232 | */ 233 | uint32_t acks_expected; 234 | /* bitmask of servers which sent acks 235 | */ 236 | uint64_t acks_received; 237 | /* timestamp of the request */ 238 | timetype req_sent_at; 239 | /* we need to wait for MY_INDEX from other servers, 240 | * hold the ticket processing for a while until they reply 241 | */ 242 | int start_postpone; 243 | 244 | /** Last renewal time */ 245 | timetype last_renewal; 246 | 247 | /* Do we need to update the copy in the CIB? 248 | * Normally, the ticket is written only when it changes via 249 | * the UPDATE RPC (for followers) and on expiration update 250 | * (for leaders) 251 | */ 252 | int update_cib; 253 | 254 | /* Is this ticket in election? 255 | */ 256 | int in_election; 257 | 258 | /* don't log warnings unnecessarily 259 | */ 260 | int expect_more_rejects; 261 | /** \name Needed while proposals are being done. 262 | * @{ */ 263 | /* Need to keep the previous valid ticket in case we moved to 264 | * start new elections and another server asks for the ticket 265 | * status. It would be wrong to send our candidate ticket. 266 | */ 267 | struct ticket_config *last_valid_tk; 268 | 269 | /** Attributes, user defined 270 | */ 271 | GHashTable *attr; 272 | 273 | /** Attribute prerequisites 274 | */ 275 | GList *attr_prereqs; 276 | 277 | /** Whom to vote for the next time. 278 | * Needed to push a ticket to someone else. */ 279 | 280 | 281 | 282 | #if 0 283 | /** Bitmap of sites that acknowledge that state. */ 284 | uint64_t proposal_acknowledges; 285 | 286 | /** When an incompletely acknowledged proposal gets done. 287 | * If all peers agree, that happens sooner. 288 | * See switch_state_to(). */ 289 | struct timeval proposal_switch; 290 | 291 | /** Timestamp of proposal expiration. */ 292 | time_t proposal_expires; 293 | 294 | #endif 295 | 296 | /** Number of send retries left. 297 | * Used on the new owner. 298 | * Starts at 0, counts up. */ 299 | int retry_number; 300 | /** @} */ 301 | }; 302 | 303 | struct booth_config { 304 | char name[BOOTH_NAME_LEN]; 305 | 306 | /** File containing the authentication file. */ 307 | char authfile[BOOTH_PATH_LEN]; 308 | struct stat authstat; 309 | char authkey[BOOTH_MAX_KEY_LEN]; 310 | int authkey_len; 311 | /** Maximum time skew between peers allowed */ 312 | int maxtimeskew; 313 | 314 | transport_layer_t proto; 315 | uint16_t port; 316 | 317 | /** Stores the OR of sites bitmasks. */ 318 | uint64_t sites_bits; 319 | /** Stores the OR of all members' bitmasks. */ 320 | uint64_t all_bits; 321 | 322 | char site_user[BOOTH_NAME_LEN]; 323 | char site_group[BOOTH_NAME_LEN]; 324 | char arb_user[BOOTH_NAME_LEN]; 325 | char arb_group[BOOTH_NAME_LEN]; 326 | uid_t uid; 327 | gid_t gid; 328 | 329 | int site_count; 330 | struct booth_site site[MAX_NODES]; 331 | 332 | int ticket_count; 333 | int ticket_allocated; 334 | struct ticket_config *ticket; 335 | }; 336 | 337 | extern struct booth_config *booth_conf; 338 | 339 | #define is_auth_req() (booth_conf->authkey[0] != '\0') 340 | 341 | /** 342 | * @internal 343 | * Parse booth configuration file and store as structured data 344 | * 345 | * @param[in,out] conf config object to free-alloc cycle & fill accordingly 346 | * @param[in] path where the configuration file is expected 347 | * @param[in] type role currently being acted as 348 | * 349 | * @return 0 or negative value (-1 or -errno) on error 350 | */ 351 | int read_config(struct booth_config **conf, const char *path, int type); 352 | 353 | /** 354 | * @internal 355 | * Check booth configuration 356 | * 357 | * Checks include: 358 | * 359 | * - Verifying that the login user and group exist, and converting them to 360 | * numeric values 361 | * 362 | * @param[in,out] conf_ptr config object to check 363 | * @param[in] type role currently being acted as 364 | * 365 | * @return 0 or negative value (-1 or -errno) on error 366 | */ 367 | int check_config(struct booth_config *conf, int type); 368 | 369 | /** 370 | * @internal 371 | * Find site in booth configuration by resolved host name 372 | * 373 | * @param[in,out] conf config object to refer to 374 | * @param[in] site name to match against previously resolved host names 375 | * @param[out] node relevant tracked data when found 376 | * @param[in] any_type whether or not to consider also non-site members 377 | * 378 | * @return 0 if nothing found, or 1 when found (node assigned accordingly) 379 | */ 380 | int find_site_by_name(struct booth_config *conf, const char *site, 381 | struct booth_site **node, int any_type); 382 | 383 | 384 | /** 385 | * @internal 386 | * Find site in booth configuration by a hash (id) 387 | * 388 | * @param[in,out] conf config object to refer to 389 | * @param[in] site_id hash (id) to match against previously resolved ones 390 | * @param[out] node relevant tracked data when found 391 | * 392 | * @return 0 if nothing found, or 1 when found (node assigned accordingly) 393 | */ 394 | int find_site_by_id(struct booth_config *conf, uint32_t site_id, 395 | struct booth_site **node); 396 | 397 | const char *type_to_string(int type); 398 | 399 | #endif /* _CONFIG_H */ 400 | -------------------------------------------------------------------------------- /src/handler.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Philipp Marek 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "ticket.h" 31 | #include "config.h" 32 | #include "inline-fn.h" 33 | #include "log.h" 34 | #include "pacemaker.h" 35 | #include "booth.h" 36 | #include "handler.h" 37 | 38 | static int set_booth_env(struct ticket_config *tk) 39 | { 40 | int rv; 41 | char expires[16]; 42 | 43 | sprintf(expires, "%" PRId64, (int64_t)wall_ts(&tk->term_expires)); 44 | rv = setenv("BOOTH_TICKET", tk->name, 1) || 45 | setenv("BOOTH_LOCAL", local->addr_string, 1) || 46 | setenv("BOOTH_CONF_NAME", booth_conf->name, 1) || 47 | setenv("BOOTH_CONF_PATH", cl.configfile, 1) || 48 | setenv("BOOTH_TICKET_EXPIRES", expires, 1); 49 | 50 | if (rv) { 51 | log_error("Cannot set environment: %s", strerror(errno)); 52 | } 53 | return rv; 54 | } 55 | 56 | static void 57 | closefiles(void) 58 | { 59 | int fd; 60 | 61 | /* close all descriptors except stdin/out/err */ 62 | for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) { 63 | close(fd); 64 | } 65 | } 66 | 67 | static void 68 | run_ext_prog(struct ticket_config *tk, char *prog) 69 | { 70 | if (set_booth_env(tk)) { 71 | _exit(1); 72 | } 73 | closefiles(); /* don't leak open files */ 74 | tk_log_debug("running handler %s", prog); 75 | execv(prog, tk_test.argv); 76 | tk_log_error("%s: execv failed (%s)", prog, strerror(errno)); 77 | _exit(1); 78 | } 79 | 80 | static int 81 | prog_filter(const struct dirent *dp) 82 | { 83 | return (*dp->d_name != '.'); 84 | } 85 | 86 | static pid_t curr_pid; 87 | static int ignore_status; 88 | 89 | static int 90 | test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg) 91 | { 92 | int rv = -1; 93 | 94 | if (WIFEXITED(status)) { 95 | rv = WEXITSTATUS(status); 96 | } else if (WIFSIGNALED(status)) { 97 | rv = 128 + WTERMSIG(status); 98 | } 99 | if (rv) { 100 | if (log_msg) { 101 | tk_log_warn("handler \"%s\" failed: %s", 102 | prog, interpret_rv(status)); 103 | tk_log_warn("we are not allowed to acquire ticket"); 104 | } 105 | } else { 106 | tk_log_debug("handler \"%s\" exited with success", 107 | prog); 108 | } 109 | return rv; 110 | } 111 | 112 | static void 113 | reset_test_state(struct ticket_config *tk) 114 | { 115 | tk_test.pid = 0; 116 | set_progstate(tk, EXTPROG_IDLE); 117 | } 118 | 119 | int tk_test_exit_status(struct ticket_config *tk) 120 | { 121 | int rv; 122 | 123 | rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir); 124 | reset_test_state(tk); 125 | return rv; 126 | } 127 | 128 | void wait_child(int sig) 129 | { 130 | int i, status; 131 | struct ticket_config *tk; 132 | 133 | /* use waitpid(2) and not wait(2) in order not to interfere 134 | * with popen(2)/pclose(2) and system(2) used in pacemaker.c 135 | */ 136 | _FOREACH_TICKET(i, tk) { 137 | if (tk_test.path && tk_test.pid > 0 && 138 | (tk_test.progstate == EXTPROG_RUNNING || 139 | tk_test.progstate == EXTPROG_IGNORE) && 140 | waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) { 141 | if (tk_test.progstate == EXTPROG_IGNORE) { 142 | /* not interested in the outcome */ 143 | reset_test_state(tk); 144 | } else { 145 | tk_test.status = status; 146 | set_progstate(tk, EXTPROG_EXITED); 147 | } 148 | } 149 | } 150 | } 151 | 152 | /* the parent may want to have us stop processing scripts, say 153 | * when the ticket gets revoked 154 | */ 155 | static void ignore_rest(int sig) 156 | { 157 | signal(SIGTERM, SIG_IGN); 158 | ignore_status = 1; 159 | if (curr_pid > 0) { 160 | (void)kill(curr_pid, SIGTERM); 161 | } 162 | } 163 | 164 | void ext_prog_timeout(struct ticket_config *tk) 165 | { 166 | tk_log_warn("handler timed out"); 167 | } 168 | 169 | int is_ext_prog_running(struct ticket_config *tk) 170 | { 171 | if (!tk_test.path) 172 | return 0; 173 | return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING); 174 | } 175 | 176 | void ignore_ext_test(struct ticket_config *tk) 177 | { 178 | if (is_ext_prog_running(tk)) { 179 | (void)kill(tk_test.pid, SIGTERM); 180 | set_progstate(tk, EXTPROG_IGNORE); 181 | } else if (tk_test.progstate == EXTPROG_EXITED) { 182 | /* external prog exited, but the status not yet examined; 183 | * we're not interested in checking the status anymore */ 184 | reset_test_state(tk); 185 | } 186 | } 187 | 188 | static void 189 | process_ext_dir(struct ticket_config *tk) 190 | { 191 | char prog[FILENAME_MAX+1]; 192 | int rv, n_progs, i, status; 193 | struct dirent **proglist, *dp; 194 | 195 | signal(SIGTERM, (__sighandler_t)ignore_rest); 196 | signal(SIGCHLD, SIG_DFL); 197 | signal(SIGUSR1, SIG_DFL); 198 | signal(SIGINT, SIG_DFL); 199 | tk_log_debug("running programs in directory %s", tk_test.path); 200 | n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort); 201 | if (n_progs == -1) { 202 | tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno)); 203 | _exit(1); 204 | } 205 | for (i = 0; i < n_progs; i++) { 206 | if (ignore_status) 207 | break; 208 | dp = proglist[i]; 209 | if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) { 210 | tk_log_error("%s: name exceeds max length (%s)", 211 | tk_test.path, dp->d_name); 212 | _exit(1); 213 | } 214 | strcpy(prog, tk_test.path); 215 | strcat(prog, "/"); 216 | strcat(prog, dp->d_name); 217 | switch(curr_pid=fork()) { 218 | case -1: 219 | log_error("fork: %s", strerror(errno)); 220 | _exit(1); 221 | case 0: /* child */ 222 | run_ext_prog(tk, prog); 223 | break; /* run_ext_prog effectively noreturn */ 224 | default: /* parent */ 225 | while (waitpid(curr_pid, &status, 0) != curr_pid) 226 | ; 227 | curr_pid = 0; 228 | if (!ignore_status) { 229 | rv = test_exit_status(tk, prog, status, 1); 230 | if (rv) 231 | _exit(rv); 232 | } else { 233 | /* 234 | * To make ignore_rest function signal safe log_info 235 | * must be removed from signal function. Information 236 | * about signal delivery is important so put it here. 237 | */ 238 | log_info("external programs handler caught TERM, ignoring " 239 | "status of external test programs"); 240 | } 241 | } 242 | } 243 | _exit(0); 244 | } 245 | 246 | /* run some external program 247 | * return codes: 248 | * RUNCMD_ERR: executing program failed (or some other failure) 249 | * RUNCMD_MORE: program forked, results later 250 | */ 251 | int run_handler(struct ticket_config *tk) 252 | { 253 | int rv = 0; 254 | pid_t pid; 255 | struct stat stbuf; 256 | 257 | if (!tk_test.path) 258 | return 0; 259 | 260 | if (stat(tk_test.path, &stbuf)) { 261 | tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno)); 262 | return RUNCMD_ERR; 263 | } 264 | tk_test.is_dir = (stbuf.st_mode & S_IFDIR); 265 | 266 | switch(pid=fork()) { 267 | case -1: 268 | log_error("fork: %s", strerror(errno)); 269 | return RUNCMD_ERR; 270 | case 0: /* child */ 271 | if (tk_test.is_dir) { 272 | process_ext_dir(tk); 273 | } else { 274 | run_ext_prog(tk, tk_test.path); 275 | } 276 | default: /* parent */ 277 | tk_test.pid = pid; 278 | set_progstate(tk, EXTPROG_RUNNING); 279 | rv = RUNCMD_MORE; /* program runs */ 280 | } 281 | 282 | return rv; 283 | } 284 | -------------------------------------------------------------------------------- /src/handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Philipp Marek 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _HANDLER_H 20 | #define _HANDLER_H 21 | 22 | enum { 23 | RUNCMD_ERR = -1, 24 | RUNCMD_MORE = -2, 25 | }; 26 | 27 | int run_handler(struct ticket_config *tk); 28 | int tk_test_exit_status(struct ticket_config *tk); 29 | void ignore_ext_test(struct ticket_config *tk); 30 | int is_ext_prog_running(struct ticket_config *tk); 31 | void ext_prog_timeout(struct ticket_config *tk); 32 | void wait_child(int sig); 33 | 34 | #define set_progstate(tk, newst) do { \ 35 | if (!(newst)) tk_log_debug("progstate reset"); \ 36 | else tk_log_debug("progstate set to %d", newst); \ 37 | tk->clu_test.progstate = newst; \ 38 | } while(0) 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/inline-fn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2014 Philipp Marek 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _INLINE_FN_H 20 | #define _INLINE_FN_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "timer.h" 27 | #include "config.h" 28 | #include "transport.h" 29 | 30 | 31 | 32 | inline static int get_local_id(void) 33 | { 34 | return local ? local->site_id : -1; 35 | } 36 | 37 | 38 | inline static uint32_t get_node_id(struct booth_site *node) 39 | { 40 | return node ? node->site_id : 0; 41 | } 42 | 43 | 44 | /** Returns number of seconds left, if any. */ 45 | inline static int term_time_left(struct ticket_config *tk) 46 | { 47 | int left = 0; 48 | 49 | if (is_time_set(&tk->term_expires)) { 50 | left = time_left(&tk->term_expires); 51 | } 52 | return (left < 0) ? 0 : left; 53 | } 54 | 55 | 56 | inline static int leader_and_valid(struct ticket_config *tk) 57 | { 58 | if (tk->leader != local) 59 | return 0; 60 | 61 | return term_time_left(tk); 62 | } 63 | 64 | 65 | /** Is this some leader? */ 66 | inline static int is_owned(const struct ticket_config *tk) 67 | { 68 | return (tk->leader && tk->leader != no_leader); 69 | } 70 | 71 | inline static int is_resend(struct ticket_config *tk) 72 | { 73 | timetype now; 74 | 75 | get_time(&now); 76 | return time_sub_int(&now, &tk->req_sent_at) >= tk->timeout; 77 | } 78 | 79 | 80 | static inline void init_header_bare(struct boothc_header *h) { 81 | timetype now; 82 | 83 | assert(local && local->site_id); 84 | h->magic = htonl(BOOTHC_MAGIC); 85 | h->version = htonl(BOOTHC_VERSION); 86 | h->from = htonl(local->site_id); 87 | if (is_auth_req()) { 88 | get_time(&now); 89 | h->opts = htonl(BOOTH_OPT_AUTH); 90 | h->secs = htonl(secs_since_epoch(&now)); 91 | h->usecs = htonl(get_usecs(&now)); 92 | } else { 93 | h->opts = htonl(0); 94 | h->secs = htonl(0); 95 | h->usecs = htonl(0); 96 | } 97 | } 98 | 99 | /* get the _real_ message length out of the header 100 | */ 101 | #define sendmsglen(msg) ntohl((msg)->header.length) 102 | 103 | static inline void init_header(struct boothc_header *h, 104 | int cmd, int request, int options, 105 | int result, int reason, int data_len) 106 | { 107 | init_header_bare(h); 108 | h->length = htonl(data_len - 109 | (is_auth_req() ? 0 : sizeof(struct hmac))); 110 | h->cmd = htonl(cmd); 111 | h->request = htonl(request); 112 | h->options = htonl(options); 113 | h->result = htonl(result); 114 | h->reason = htonl(reason); 115 | } 116 | 117 | #define my_last_term(tk) \ 118 | (((tk)->state == ST_CANDIDATE && (tk)->last_valid_tk) ? \ 119 | (tk)->last_valid_tk->current_term : (tk)->current_term) 120 | 121 | extern int TIME_RES, TIME_MULT; 122 | 123 | #define msg_term_time(msg) \ 124 | ntohl((msg)->ticket.term_valid_for)*TIME_RES/TIME_MULT 125 | #define set_msg_term_time(msg, tk) \ 126 | (msg)->ticket.term_valid_for = htonl(term_time_left(tk)*TIME_MULT/TIME_RES) 127 | 128 | static inline void init_ticket_msg(struct boothc_ticket_msg *msg, 129 | int cmd, int request, int rv, int reason, 130 | struct ticket_config *tk) 131 | { 132 | assert(sizeof(msg->ticket.id) == sizeof(tk->name)); 133 | 134 | init_header(&msg->header, cmd, request, 0, rv, reason, sizeof(*msg)); 135 | 136 | if (!tk) { 137 | memset(&msg->ticket, 0, sizeof(msg->ticket)); 138 | } else { 139 | memcpy(msg->ticket.id, tk->name, sizeof(msg->ticket.id)); 140 | 141 | msg->ticket.leader = htonl(get_node_id( 142 | (tk->leader && tk->leader != no_leader) ? tk->leader : 143 | (tk->voted_for ? tk->voted_for : no_leader))); 144 | msg->ticket.term = htonl(tk->current_term); 145 | set_msg_term_time(msg, tk); 146 | } 147 | } 148 | 149 | 150 | static inline struct booth_transport const *transport(void) 151 | { 152 | return booth_transport + booth_conf->proto; 153 | } 154 | 155 | 156 | static inline const char *site_string(const struct booth_site *site) 157 | { 158 | return site ? site->addr_string : "NONE"; 159 | } 160 | 161 | 162 | static inline uint16_t site_port(const struct booth_site *site) 163 | { 164 | assert(site != NULL); 165 | 166 | if (site->family == AF_INET) { 167 | return ntohs(site->sa4.sin_port); 168 | } else if (site->family == AF_INET6) { 169 | return ntohs(site->sa6.sin6_port); 170 | } else { 171 | return 0; 172 | } 173 | } 174 | 175 | 176 | static inline const char *ticket_leader_string(struct ticket_config *tk) 177 | { 178 | return site_string(tk->leader); 179 | } 180 | 181 | 182 | /* We allow half of the uint32_t to be used; 183 | * half of that below, half of that above the current known "good" value. 184 | * 0 UINT32_MAX 185 | * |--------------------------+----------------+------------| 186 | * | | | 187 | * |--------+-------| allowed range 188 | * | 189 | * current commit index 190 | * 191 | * So, on overflow it looks like that: 192 | * UINT32_MAX 0 193 | * |--------------------------+-----------||---+------------| 194 | * | | | 195 | * |--------+-------| allowed range 196 | * | 197 | * current commit index 198 | * 199 | * This should be possible by using the same datatype and relying 200 | * on the under/overflow semantics. 201 | * 202 | * 203 | * Having 30 bits available, and assuming an expire time of 204 | * one minute and a (high) commit index step of 64 == 2^6 (because 205 | * of weights), we get 2^24 minutes of range - which is ~750 206 | * years. "Should be enough for everybody." 207 | */ 208 | static inline int index_is_higher_than(uint32_t c_high, uint32_t c_low) 209 | { 210 | uint32_t diff; 211 | 212 | if (c_high == c_low) 213 | return 0; 214 | 215 | diff = c_high - c_low; 216 | if (diff < UINT32_MAX/4) 217 | return 1; 218 | 219 | diff = c_low - c_high; 220 | if (diff < UINT32_MAX/4) 221 | return 0; 222 | 223 | assert(!"commit index out of range - invalid"); 224 | } 225 | 226 | 227 | static inline uint32_t index_max2(uint32_t a, uint32_t b) 228 | { 229 | return index_is_higher_than(a, b) ? a : b; 230 | } 231 | 232 | static inline uint32_t index_max3(uint32_t a, uint32_t b, uint32_t c) 233 | { 234 | return index_max2( index_max2(a, b), c); 235 | } 236 | 237 | 238 | /* only invoked when ticket leader */ 239 | static inline void get_next_election_time(struct ticket_config *tk, timetype *next) 240 | { 241 | assert(tk->leader == local); 242 | 243 | /* if last_renewal is not set, which is unusual, it may mean 244 | * that the ticket never got updated, i.e. nobody acked 245 | * ticket updates (say, due to a temporary connection 246 | * problem) 247 | * we may try a bit later again */ 248 | if (!is_time_set(&tk->last_renewal)) { 249 | time_reset(next); 250 | } else { 251 | interval_add(&tk->last_renewal, tk->renewal_freq, next); 252 | } 253 | 254 | /* if delay_commit is earlier than next, then set next to 255 | * delay_commit */ 256 | if (is_time_set(&tk->delay_commit) && 257 | time_cmp(next, &tk->delay_commit, >)) { 258 | copy_time(&tk->delay_commit, next); 259 | } 260 | } 261 | 262 | 263 | static inline void expect_replies(struct ticket_config *tk, 264 | int reply_type) 265 | { 266 | tk->retry_number = 0; 267 | tk->acks_expected = reply_type; 268 | tk->acks_received = local->bitmask; 269 | get_time(&tk->req_sent_at); 270 | } 271 | 272 | static inline void no_resends(struct ticket_config *tk) 273 | { 274 | tk->retry_number = 0; 275 | tk->acks_expected = 0; 276 | } 277 | 278 | static inline struct booth_site *my_vote(struct ticket_config *tk) 279 | { 280 | return tk->votes_for[ local->index ]; 281 | } 282 | 283 | 284 | static inline int count_bits(uint64_t val) { 285 | return __builtin_popcount(val); 286 | } 287 | 288 | static inline int majority_of_bits(struct ticket_config *tk, uint64_t val) 289 | { 290 | /* Use ">" to get majority decision, even for an even number 291 | * of participants. */ 292 | return count_bits(val) * 2 > 293 | booth_conf->site_count; 294 | } 295 | 296 | 297 | static inline int all_replied(struct ticket_config *tk) 298 | { 299 | return !(tk->acks_received ^ booth_conf->all_bits); 300 | } 301 | 302 | static inline int all_sites_replied(struct ticket_config *tk) 303 | { 304 | return !((tk->acks_received & booth_conf->sites_bits) ^ booth_conf->sites_bits); 305 | } 306 | 307 | 308 | #endif 309 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2011 Red Hat, Inc. All rights reserved. 3 | * (This code is borrowed from the sanlock project which is hosted on 4 | * fedorahosted.org.) 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program; if not, write to the Free Software Foundation, Inc., 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | */ 20 | 21 | #ifndef _LOG_H 22 | #define _LOG_H 23 | 24 | #include "b_config.h" 25 | 26 | #ifndef LOGGING_LIBQB 27 | #include 28 | #include 29 | #define priv_log(prio, ...) cl_log(prio, __VA_ARGS__) 30 | #else 31 | #include "alt/logging_libqb.h" 32 | #define priv_log(prio, ...) qb_log(prio, __VA_ARGS__) 33 | #endif 34 | 35 | #include "inline-fn.h" 36 | 37 | 38 | #define log_debug(fmt, args...) do { \ 39 | if (ANYDEBUG) priv_log(LOG_DEBUG, fmt, ##args); } \ 40 | while (0) 41 | #define log_info(fmt, args...) priv_log(LOG_INFO, fmt, ##args) 42 | #define log_warn(fmt, args...) priv_log(LOG_WARNING, fmt, ##args) 43 | #define log_error(fmt, args...) priv_log(LOG_ERR, fmt, ##args) 44 | 45 | /* all tk_* macros prepend "%(tk->name): " (the caller needs to 46 | * have the ticket named tk!) 47 | */ 48 | #define tk_cl_log(sev, fmt, args...) \ 49 | priv_log(sev, "%s (%s/%d/%d): " fmt, \ 50 | tk->name, state_to_string(tk->state), tk->current_term, term_time_left(tk), \ 51 | ##args) 52 | #define tk_cl_log_src(sev, fmt, args...) \ 53 | priv_log(sev, "%s:%d: %s (%s/%d/%d): " fmt, \ 54 | __FUNCTION__, __LINE__, \ 55 | tk->name, state_to_string(tk->state), tk->current_term, term_time_left(tk), \ 56 | ##args) 57 | 58 | #define tk_log_debug(fmt, args...) do { \ 59 | if (ANYDEBUG) tk_cl_log_src(LOG_DEBUG, fmt, ##args); } \ 60 | while (0) 61 | #define tk_log_info(fmt, args...) tk_cl_log(LOG_INFO, fmt, ##args) 62 | #define tk_log_warn(fmt, args...) tk_cl_log(LOG_WARNING, fmt, ##args) 63 | #define tk_log_error(fmt, args...) tk_cl_log(LOG_ERR, fmt, ##args) 64 | 65 | #endif /* _LOG_H */ 66 | -------------------------------------------------------------------------------- /src/manual.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Chris Kowalczyk 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include "manual.h" 20 | 21 | #include "config.h" 22 | #include "transport.h" 23 | #include "ticket.h" 24 | #include "log.h" 25 | #include "request.h" 26 | 27 | /* For manual tickets, manual_selection function is an equivalent 28 | * of new_election function used for assigning automatic tickets. 29 | * The workflow here is much simplier, as no voting is performed, 30 | * and the current node doesn't have to wait for any responses 31 | * from other sites. 32 | */ 33 | int manual_selection(struct booth_config *conf, struct ticket_config *tk, 34 | struct booth_site *preference, int update_term, 35 | cmd_reason_t reason) 36 | { 37 | if (local->type != SITE) { 38 | return 0; 39 | } 40 | 41 | tk_log_debug("starting manual selection (caused by %s %s)", 42 | state_to_string(reason), 43 | reason == OR_AGAIN ? state_to_string(tk->election_reason) : "" ); 44 | 45 | // Manual selection is done without any delay, the leader is assigned 46 | set_leader(tk, local); 47 | set_state(tk, ST_LEADER); 48 | 49 | // Manual tickets never expire, we don't specify expiration time 50 | 51 | // Make sure that election_end field is empty 52 | time_reset(&tk->election_end); 53 | 54 | // Make sure that delay commit is empty, as manual tickets don't 55 | // wait for any kind of confirmation from other nodes 56 | time_reset(&tk->delay_commit); 57 | 58 | save_committed_tkt(tk); 59 | 60 | // Inform others about the new leader 61 | ticket_broadcast(conf, tk, OP_HEARTBEAT, OP_ACK, RLT_SUCCESS, 0); 62 | tk->ticket_updated = 0; 63 | 64 | return 0; 65 | } 66 | 67 | /* This function is called for manual tickets that were 68 | * revoked from another site, which this site doesn't 69 | * consider as a leader. 70 | */ 71 | int process_REVOKE_for_manual_ticket(struct booth_config *conf, 72 | struct ticket_config *tk, 73 | struct booth_site *sender, 74 | struct boothc_ticket_msg *msg) 75 | { 76 | int rv; 77 | 78 | // For manual tickets, we may end up having two leaders. 79 | // If one of them is revoked, it will send information 80 | // to all members of the GEO cluster. 81 | 82 | // We may find ourselves here if this particular site 83 | // has not been following the leader which had been revoked 84 | // (and which had sent this message). 85 | 86 | // We send the ACK, to satisfy the requestor. 87 | rv = send_msg(conf, OP_ACK, tk, sender, msg); 88 | 89 | // Mark this ticket as not granted to the sender anymore. 90 | mark_ticket_as_revoked(tk, sender); 91 | 92 | if (tk->state == ST_LEADER) { 93 | tk_log_warn("%s wants to revoke ticket, " 94 | "but this site is itself a leader", 95 | site_string(sender)); 96 | 97 | // Because another leader is presumably stepping down, 98 | // let's notify other sites that now we are the only leader. 99 | ticket_broadcast(conf, tk, OP_HEARTBEAT, OP_ACK, RLT_SUCCESS, 0); 100 | } else { 101 | tk_log_warn("%s wants to revoke ticket, " 102 | "but this site is not following it", 103 | site_string(sender)); 104 | } 105 | 106 | return rv; 107 | } 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/manual.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Chris Kowalczyk 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _MANUAL_H 20 | #define _MANUAL_H 21 | 22 | #include "booth.h" 23 | 24 | struct ticket_config; 25 | 26 | int manual_selection(struct booth_config *conf, struct ticket_config *tk, 27 | struct booth_site *new_leader, int update_term, 28 | cmd_reason_t reason); 29 | 30 | int process_REVOKE_for_manual_ticket(struct booth_config *conf, 31 | struct ticket_config *tk, 32 | struct booth_site *sender, 33 | struct boothc_ticket_msg *msg); 34 | 35 | 36 | #endif /* _MANUAL_H */ 37 | -------------------------------------------------------------------------------- /src/pacemaker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Jiaju Zhang 3 | * Copyright (C) 2013-2014 Philipp Marek 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #ifndef _PACEMAKER_H 21 | #define _PACEMAKER_H 22 | 23 | #include 24 | #include "config.h" 25 | 26 | struct ticket_handler { 27 | int (*grant_ticket) (struct ticket_config *tk); 28 | int (*revoke_ticket) (struct ticket_config *tk); 29 | int (*load_ticket) (struct booth_config *conf, struct ticket_config *tk); 30 | int (*set_attr) (struct ticket_config *tk, const char *a, const char *v); 31 | int (*get_attr) (struct ticket_config *tk, const char *a, const char **vp); 32 | int (*del_attr) (struct ticket_config *tk, const char *a); 33 | }; 34 | 35 | extern struct ticket_handler pcmk_handler; 36 | const char * interpret_rv(int rv); 37 | 38 | 39 | #endif /* _PACEMAKER_H */ 40 | -------------------------------------------------------------------------------- /src/raft.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Philipp Marek 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _RAFT_H 20 | #define _RAFT_H 21 | 22 | #include "booth.h" 23 | 24 | typedef enum { 25 | ST_INIT = CHAR2CONST('I', 'n', 'i', 't'), 26 | ST_FOLLOWER = CHAR2CONST('F', 'l', 'l', 'w'), 27 | ST_CANDIDATE = CHAR2CONST('C', 'n', 'd', 'i'), 28 | ST_LEADER = CHAR2CONST('L', 'e', 'a', 'd'), 29 | } server_state_e; 30 | 31 | struct ticket_config; 32 | 33 | int raft_answer(struct booth_config *conf, struct ticket_config *tk, 34 | struct booth_site *from, struct booth_site *leader, 35 | struct boothc_ticket_msg *msg); 36 | 37 | int new_election(struct booth_config *conf, struct ticket_config *tk, 38 | struct booth_site *new_leader, int update_term, 39 | cmd_reason_t reason); 40 | void elections_end(struct booth_config *conf, struct ticket_config *tk); 41 | 42 | 43 | #endif /* _RAFT_H */ 44 | -------------------------------------------------------------------------------- /src/request.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "booth.h" 24 | #include "ticket.h" 25 | #include "request.h" 26 | #include "log.h" 27 | 28 | static GList *req_l = NULL; 29 | static int req_id_cnt; 30 | 31 | /* add request to the queue; it is up to the caller to manage 32 | * memory for the three parameters 33 | */ 34 | 35 | void *add_req( 36 | struct ticket_config *tk, 37 | struct client *req_client, 38 | struct boothc_ticket_msg *msg) 39 | { 40 | struct request *rp; 41 | 42 | rp = g_new(struct request, 1); 43 | if (!rp) 44 | return NULL; 45 | rp->id = req_id_cnt++; 46 | rp->tk = tk; 47 | rp->client_fd = req_client->fd; 48 | rp->msg = msg; 49 | req_l = g_list_append(req_l, rp); 50 | return rp; 51 | } 52 | 53 | int get_req_id(const void *rp) 54 | { 55 | if (!rp) 56 | return -1; 57 | return ((struct request *)rp)->id; 58 | } 59 | 60 | static void del_req(GList *lp) 61 | { 62 | if (!lp) 63 | return; 64 | req_l = g_list_delete_link(req_l, lp); 65 | } 66 | 67 | void foreach_tkt_req(struct booth_config *conf, struct ticket_config *tk, 68 | req_fp f) 69 | { 70 | GList *lp, *next; 71 | struct request *rp; 72 | 73 | lp = g_list_first(req_l); 74 | while (lp) { 75 | next = g_list_next(lp); 76 | rp = (struct request *)lp->data; 77 | if (rp->tk == tk && 78 | (f)(conf, rp->tk, rp->client_fd, rp->msg) == 0) { 79 | log_debug("remove request for client %d", rp->client_fd); 80 | del_req(lp); /* don't need this request anymore */ 81 | } 82 | lp = next; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/request.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _REQUEST_H 20 | #define _REQUEST_H 21 | 22 | #include "booth.h" 23 | #include "config.h" 24 | 25 | /* Requests are coming from clients and get queued in a 26 | * round-robin queue (fixed size) 27 | * 28 | * This is one way to make the server more responsive and less 29 | * dependent on misbehaving clients. The requests are queued and 30 | * later served from the server loop. 31 | */ 32 | 33 | struct request { 34 | /** Request ID */ 35 | int id; 36 | 37 | /** The ticket. */ 38 | struct ticket_config *tk; 39 | 40 | /** The client which sent the request */ 41 | int client_fd; 42 | 43 | /** The message containing the request */ 44 | void *msg; 45 | }; 46 | 47 | typedef int (*req_fp)(struct booth_config *, struct ticket_config *, int, 48 | struct boothc_ticket_msg *); 49 | 50 | void *add_req(struct ticket_config *tk, struct client *req_client, 51 | struct boothc_ticket_msg *msg); 52 | 53 | /** 54 | * @internal 55 | * Handle all pending requests for given ticket using function @p f 56 | * 57 | * @param[in,out] conf config object to refer to 58 | * @param[in] tk ticket at hand 59 | * @param[in] f handling function 60 | * 61 | * @return 1 on success, 0 when not done with the message, yet 62 | */ 63 | void foreach_tkt_req(struct booth_config *conf, struct ticket_config *tk, 64 | req_fp f); 65 | 66 | int get_req_id(const void *rp); 67 | 68 | #endif /* _REQUEST_H */ 69 | -------------------------------------------------------------------------------- /src/ticket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Jiaju Zhang 3 | * Copyright (C) 2013-2014 Philipp Marek 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #ifndef _TICKET_H 21 | #define _TICKET_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "timer.h" 28 | #include "config.h" 29 | #include "log.h" 30 | 31 | extern int TIME_RES; 32 | 33 | #define DEFAULT_TICKET_EXPIRY (600*TIME_RES) 34 | #define DEFAULT_TICKET_TIMEOUT (5*TIME_RES) 35 | #define DEFAULT_RETRIES 10 36 | 37 | 38 | #define FOREACH_TICKET(b_, i_, t_) \ 39 | for (i_ = 0; \ 40 | (t_ = (b_)->ticket + i_, i_ < (b_)->ticket_count); \ 41 | i_++) 42 | 43 | #define FOREACH_NODE(b_, i_, n_) \ 44 | for (i_ = 0; \ 45 | (n_ = (b_)->site + i_, i_ < (b_)->site_count); \ 46 | i_++) 47 | 48 | 49 | #define _FOREACH_TICKET(i_, t_) \ 50 | for (i_ = 0; \ 51 | (t_ = booth_conf->ticket + i_, i_ < booth_conf->ticket_count); \ 52 | i_++) 53 | 54 | #define _FOREACH_NODE(i_, n_) \ 55 | for (i_ = 0; \ 56 | (n_ = booth_conf->site + i_, i_ < booth_conf->site_count); \ 57 | i_++) 58 | 59 | #define set_leader(tk, who) do { \ 60 | if (who == NULL) { \ 61 | mark_ticket_as_revoked_from_leader(tk); \ 62 | } \ 63 | \ 64 | tk->leader = who; \ 65 | tk_log_debug("ticket leader set to %s", ticket_leader_string(tk)); \ 66 | \ 67 | if (tk->leader) { \ 68 | mark_ticket_as_granted(tk, tk->leader); \ 69 | } \ 70 | } while(0) 71 | 72 | #define mark_ticket_as_granted(tk, who) do { \ 73 | if (is_manual(tk) && (who->index > -1)) { \ 74 | tk->sites_where_granted[who->index] = 1; \ 75 | tk_log_debug("manual ticket marked as granted to %s", ticket_leader_string(tk)); \ 76 | } \ 77 | } while(0) 78 | 79 | #define mark_ticket_as_revoked(tk, who) do { \ 80 | if (is_manual(tk) && who && (who->index > -1)) { \ 81 | tk->sites_where_granted[who->index] = 0; \ 82 | tk_log_debug("manual ticket marked as revoked from %s", site_string(who)); \ 83 | } \ 84 | } while(0) 85 | 86 | #define mark_ticket_as_revoked_from_leader(tk) do { \ 87 | if (tk->leader) { \ 88 | mark_ticket_as_revoked(tk, tk->leader); \ 89 | } \ 90 | } while(0) 91 | 92 | #define set_state(tk, newst) do { \ 93 | tk_log_debug("state transition: %s -> %s", \ 94 | state_to_string(tk->state), state_to_string(newst)); \ 95 | tk->state = newst; \ 96 | } while(0) 97 | 98 | #define set_next_state(tk, newst) do { \ 99 | if (!(newst)) tk_log_debug("next state reset"); \ 100 | else tk_log_debug("next state set to %s", state_to_string(newst)); \ 101 | tk->next_state = newst; \ 102 | } while(0) 103 | 104 | #define is_term_invalid(tk, term) \ 105 | ((tk)->last_valid_tk && (tk)->last_valid_tk->current_term > (term)) 106 | 107 | void save_committed_tkt(struct ticket_config *tk); 108 | void disown_ticket(struct ticket_config *tk); 109 | 110 | /** 111 | * @internal 112 | * Like @find_ticket_by_name, but perform sanity checks on the found ticket 113 | * 114 | * @param[in,out] conf config object to refer to 115 | * @param[in] ticket name of the ticket to search for 116 | * @param[out] found place the reference here when found 117 | * 118 | * @return 0 on failure, see @find_ticket_by_name otherwise 119 | */ 120 | int check_ticket(struct booth_config *conf, char *ticket, struct ticket_config **tc); 121 | 122 | int grant_ticket(struct ticket_config *ticket); 123 | int revoke_ticket(struct ticket_config *ticket); 124 | 125 | /** 126 | * @internal 127 | * Second stage of incoming datagram handling (after authentication) 128 | * 129 | * @param[in,out] conf config object to refer to 130 | * @param[in] buf raw message to act upon 131 | * @param[in] source member originating this message 132 | * 133 | * @return 0 on success or negative value (-1 or -errno) on error 134 | */ 135 | int ticket_recv(struct booth_config *conf, void *buf, struct booth_site *source); 136 | 137 | void reset_ticket(struct ticket_config *tk); 138 | void reset_ticket_and_set_no_leader(struct ticket_config *tk); 139 | void update_ticket_state(struct ticket_config *tk, struct booth_site *sender); 140 | 141 | /** 142 | * @internal 143 | * Broadcast the initial state query 144 | * 145 | * @param[in,out] conf config object to use as a starting point 146 | * 147 | * @return 0 (for the time being) 148 | */ 149 | int setup_ticket(struct booth_config *conf); 150 | 151 | int check_max_len_valid(const char *s, int max); 152 | 153 | /** 154 | * @internal 155 | * Find a ticket based on a given name 156 | * 157 | * @param[in,out] conf config object to refer to 158 | * @param[in] ticket name of the ticket to search for 159 | * @param[out] found place the reference here when found 160 | * 161 | * @return see @list_ticket and @send_header_plus 162 | */ 163 | int find_ticket_by_name(struct booth_config *conf, 164 | const char *ticket, struct ticket_config **found); 165 | 166 | void set_ticket_wakeup(struct ticket_config *tk); 167 | 168 | /** 169 | * @internal 170 | * Implementation of ticket listing 171 | * 172 | * @param[in,out] conf config object to refer to 173 | * @param[in] fd file descriptor of the socket to respond to 174 | * 175 | * @return see @list_ticket and @send_header_plus 176 | */ 177 | int ticket_answer_list(struct booth_config *conf, int fd); 178 | 179 | /** 180 | * @internal 181 | * Process request from the client (as opposed to the peer daemon) 182 | * 183 | * @param[in,out] conf config object to refer to 184 | * @param[in] req_client client structure of the sender 185 | * @param[in] buf client message 186 | * 187 | * @return 1 on success, or 0 when not yet done with the message 188 | */ 189 | int process_client_request(struct booth_config *conf, struct client *req_client, 190 | void *buf); 191 | 192 | int ticket_write(struct ticket_config *tk); 193 | 194 | /** 195 | * @internal 196 | * Mainloop of booth ticket handling 197 | * 198 | * @param[in,out] conf config object to refer to 199 | */ 200 | void process_tickets(struct booth_config *conf); 201 | 202 | /** 203 | * @internal 204 | * Log properties of all tickets 205 | * 206 | * @param[in,out] conf config object to refer to 207 | */ 208 | void tickets_log_info(struct booth_config *conf); 209 | 210 | char *state_to_string(uint32_t state_ho); 211 | 212 | /** 213 | * @internal 214 | * For a given ticket and recipient site, send a rejection 215 | * 216 | * @param[in,out] conf config object to refer to 217 | * @param[in] dest site structure of the recipient 218 | * @param[in] tk ticket at hand 219 | * @param[in] code further detail for the rejection 220 | * @param[in] in_msg message this is going to be a response to 221 | */ 222 | int send_reject(struct booth_config *conf, struct booth_site *dest, 223 | struct ticket_config *tk, cmd_result_t code, 224 | struct boothc_ticket_msg *in_msg); 225 | 226 | /** 227 | * @internal 228 | * For a given ticket, recipient site and possibly its message, send a response 229 | * 230 | * @param[in,out] conf config object to refer to 231 | * @param[in] cmd what type of message is to be sent 232 | * @param[in] dest site structure of the recipient 233 | * @param[in] in_msg message this is going to be a response to 234 | */ 235 | int send_msg(struct booth_config *conf, int cmd, struct ticket_config *tk, 236 | struct booth_site *dest, struct boothc_ticket_msg *in_msg); 237 | 238 | /** 239 | * @internal 240 | * Notify client at particular socket, regarding particular ticket 241 | * 242 | * @param[in,out] conf config object to refer to 243 | * @param[in] tk ticket at hand 244 | * @param[in] fd file descriptor of the socket to respond to 245 | * @param[in] msg input message being responded to 246 | */ 247 | int notify_client(struct booth_config *conf, struct ticket_config *tk, 248 | int client_fd, struct boothc_ticket_msg *msg); 249 | 250 | int ticket_broadcast(struct booth_config *conf, struct ticket_config *tk, 251 | cmd_request_t cmd, cmd_request_t expected_reply, 252 | cmd_result_t res, cmd_reason_t reason); 253 | 254 | int leader_update_ticket(struct booth_config *conf, struct ticket_config *tk); 255 | void add_random_delay(struct ticket_config *tk); 256 | void schedule_election(struct ticket_config *tk, cmd_reason_t reason); 257 | 258 | int is_manual(struct ticket_config *tk); 259 | 260 | int check_attr_prereq(struct ticket_config *tk, grant_type_e grant_type); 261 | 262 | static inline void ticket_next_cron_at(struct ticket_config *tk, timetype *when) 263 | { 264 | copy_time(when, &tk->next_cron); 265 | } 266 | 267 | static inline void ticket_next_cron_in(struct ticket_config *tk, int interval) 268 | { 269 | timetype tv; 270 | 271 | set_future_time(&tv, interval); 272 | ticket_next_cron_at(tk, &tv); 273 | } 274 | 275 | 276 | static inline void ticket_activate_timeout(struct ticket_config *tk) 277 | { 278 | /* TODO: increase timeout when no answers */ 279 | tk_log_debug("activate ticket timeout in %d", tk->timeout); 280 | ticket_next_cron_in(tk, tk->timeout); 281 | } 282 | 283 | 284 | #endif /* _TICKET_H */ 285 | -------------------------------------------------------------------------------- /src/timer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include "timer.h" 20 | 21 | /* which time resolution makes most sense? 22 | * the factors are clock resolution and network latency 23 | */ 24 | int TIME_RES = 1000; 25 | int TIME_MULT = 1; 26 | 27 | int time_sub_int(timetype *a, timetype *b) 28 | { 29 | timetype res; 30 | 31 | time_sub(a, b, &res); 32 | return res.tv_sec*TIME_RES + res.SUBSEC/TIME_FAC; 33 | } 34 | 35 | /* interval (b) is in ms (1/TIME_RES) */ 36 | void interval_add(timetype *a, int b, timetype *res) 37 | { 38 | /* need this to allow interval_add(a, b, a); */ 39 | long tmp_subsec = a->SUBSEC + (long)b*TIME_FAC; 40 | 41 | res->SUBSEC = tmp_subsec%NSECS; 42 | res->tv_sec = a->tv_sec + tmp_subsec/NSECS; 43 | } 44 | 45 | int is_time_set(timetype *p) 46 | { 47 | return (p->tv_sec != 0) || (p->SUBSEC != 0); 48 | } 49 | 50 | int is_past(timetype *p) 51 | { 52 | timetype now; 53 | 54 | /*if (!is_time_set(p)) 55 | return 1;*/ 56 | assert(p->tv_sec || p->SUBSEC); 57 | get_time(&now); 58 | return time_cmp(&now, p, >); 59 | } 60 | 61 | void secs2tv(time_t secs, timetype *p) 62 | { 63 | memset(p, 0, sizeof(timetype)); 64 | p->tv_sec = secs; 65 | } 66 | 67 | int time_left(timetype *p) 68 | { 69 | timetype now; 70 | 71 | assert(p->tv_sec || p->SUBSEC); 72 | get_time(&now); 73 | return time_sub_int(p, &now); 74 | } 75 | 76 | void set_future_time(timetype *a, int b) 77 | { 78 | timetype now; 79 | 80 | get_time(&now); 81 | interval_add(&now, b, a); 82 | } 83 | 84 | void time_reset(timetype *p) 85 | { 86 | memset(p, 0, sizeof(timetype)); 87 | } 88 | 89 | void copy_time(timetype *src, timetype *dst) 90 | { 91 | dst->SUBSEC = src->SUBSEC; 92 | dst->tv_sec = src->tv_sec; 93 | } 94 | 95 | #if _POSIX_TIMERS > 0 96 | 97 | void time_sub(struct timespec *a, struct timespec *b, struct timespec *res) 98 | { 99 | if (a->tv_nsec < b->tv_nsec) { 100 | res->tv_sec = a->tv_sec - b->tv_sec - 1L; 101 | res->tv_nsec = a->tv_nsec + (NSECS - b->tv_nsec); 102 | } else { 103 | res->tv_sec = a->tv_sec - b->tv_sec; 104 | res->tv_nsec = a->tv_nsec - b->tv_nsec; 105 | } 106 | } 107 | 108 | void time_add(struct timespec *a, struct timespec *b, struct timespec *res) 109 | { 110 | res->tv_nsec = a->tv_nsec + b->tv_nsec; 111 | res->tv_sec = a->tv_sec + b->tv_sec; 112 | if (res->tv_nsec >= NSECS) { 113 | res->tv_sec++; 114 | res->tv_nsec %= NSECS; 115 | } 116 | } 117 | 118 | time_t get_secs(struct timespec *p) 119 | { 120 | 121 | if (p) { 122 | get_time(p); 123 | return p->tv_sec; 124 | } else { 125 | struct timespec tv; 126 | get_time(&tv); 127 | return tv.tv_sec; 128 | } 129 | } 130 | 131 | /* time booth_clk_t is a time since boot or similar, convert that 132 | * to time since epoch (Jan 1, 1970) 133 | */ 134 | static void clock2epochtime(struct timespec *booth_clk_t, struct timespec *res) 135 | { 136 | struct timespec booth_clk_now, now_tv; 137 | struct timeval now; 138 | 139 | get_time(&booth_clk_now); 140 | gettimeofday(&now, NULL); 141 | TIMEVAL_TO_TIMESPEC(&now, &now_tv); 142 | time_sub(&now_tv, &booth_clk_now, res); 143 | time_add(booth_clk_t, res, res); 144 | } 145 | 146 | /* time booth_clk_t is a time since boot or similar, return 147 | * something humans can understand (rounded seconds only) */ 148 | time_t wall_ts(struct timespec *booth_clk_t) 149 | { 150 | struct timespec res; 151 | 152 | clock2epochtime(booth_clk_t, &res); 153 | return round2secs(&res); 154 | } 155 | 156 | /* time booth_clk_t is a time since boot or similar, get here 157 | * seconds since epoch 158 | */ 159 | time_t secs_since_epoch(struct timespec *booth_clk_t) 160 | { 161 | struct timespec res; 162 | 163 | clock2epochtime(booth_clk_t, &res); 164 | return res.tv_sec; 165 | } 166 | 167 | /* time t is wall clock time, convert to time compatible 168 | * with our clock_gettime clock */ 169 | time_t unwall_ts(time_t t) 170 | { 171 | struct timespec booth_clk_now, now_tv, res; 172 | struct timeval now; 173 | 174 | get_time(&booth_clk_now); 175 | gettimeofday(&now, NULL); 176 | TIMEVAL_TO_TIMESPEC(&now, &now_tv); 177 | time_sub(&now_tv, &booth_clk_now, &res); 178 | return t - res.tv_sec; 179 | } 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /src/timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Dejan Muhamedagic 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This software is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License along 15 | * with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #ifndef _TIMER_H 20 | #define _TIMER_H 21 | 22 | #include "b_config.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if _POSIX_TIMERS > 0 31 | 32 | #if defined(CLOCK_MONOTONIC) 33 | # define BOOTH_CLOCK CLOCK_MONOTONIC 34 | #else 35 | # define BOOTH_CLOCK CLOCK_REALTIME 36 | #endif 37 | 38 | #define NSECS 1000000000L /* nanoseconds */ 39 | #define TIME_FAC (NSECS/TIME_RES) 40 | #define SUBSEC tv_nsec 41 | #define SUBSEC_FAC NSECS 42 | 43 | typedef struct timespec timetype; 44 | 45 | #define get_time(p) clock_gettime(BOOTH_CLOCK, p) 46 | #define get_usecs(p) ((p)->tv_nsec/1000L) 47 | 48 | #define time_cmp(a, b, CMP) \ 49 | (((a)->tv_sec == (b)->tv_sec) ? \ 50 | ((a)->tv_nsec CMP (b)->tv_nsec) : \ 51 | ((a)->tv_sec CMP (b)->tv_sec)) 52 | 53 | void time_sub(struct timespec *a, struct timespec *b, struct timespec *res); 54 | void time_add(struct timespec *a, struct timespec *b, struct timespec *res); 55 | time_t get_secs(struct timespec *p); 56 | time_t wall_ts(struct timespec *p); 57 | time_t secs_since_epoch(struct timespec *booth_clk_t); 58 | time_t unwall_ts(time_t t); 59 | 60 | #else 61 | 62 | #define MUSECS 1000000L /* microseconds */ 63 | #define SUBSEC_FAC MUSECS 64 | #define TIME_FAC (MUSECS/TIME_RES) 65 | #define SUBSEC tv_usec 66 | 67 | typedef struct timeval timetype; 68 | #define get_time(p) gettimeofday(p, NULL) 69 | #define secs_since_epoch(p) ((p)->tv_sec) 70 | #define get_usecs(p) ((p)->tv_usec) 71 | #define time_sub timersub 72 | #define time_add timeradd 73 | #define time_cmp timercmp 74 | #define get_secs time 75 | 76 | #define wall_ts round2secs 77 | #define unwall_ts(t) (t) 78 | 79 | #endif 80 | 81 | int is_past(timetype *p); 82 | void secs2tv(time_t secs, timetype *p); 83 | void time_reset(timetype *p); 84 | int time_sub_int(timetype *a, timetype *b); 85 | void set_future_time(timetype *a, int b); 86 | int time_left(timetype *p); 87 | void copy_time(timetype *src, timetype *dst); 88 | void interval_add(timetype *p, int interval, timetype *res); 89 | int is_time_set(timetype *p); 90 | #define intfmt(t) "%d.%03d", (t)/TIME_RES, ((t)<0?-(t):(t))%TIME_RES 91 | 92 | /* random time from 0 to t ms (1/TIME_RES) */ 93 | #define rand_time(t) cl_rand_from_interval(0, t*(TIME_RES/1000)) 94 | 95 | #define round2secs(p) \ 96 | ((p)->tv_sec + ((p)->SUBSEC + SUBSEC_FAC/2)/SUBSEC_FAC) 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /src/transport.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Jiaju Zhang 3 | * Copyright (C) 2013 Philipp Marek 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #ifndef _TRANSPORT_H 21 | #define _TRANSPORT_H 22 | 23 | #include "b_config.h" 24 | #include "booth.h" 25 | 26 | typedef enum { 27 | TCP = 1, 28 | UDP, 29 | SCTP, 30 | TRANSPORT_ENTRIES, 31 | } transport_layer_t; 32 | 33 | typedef enum { 34 | ARBITRATOR = 0x50, 35 | SITE, 36 | CLIENT, 37 | DAEMON, 38 | STATUS, 39 | GEOSTORE, 40 | } action_t; 41 | 42 | /* when allocating space for messages 43 | */ 44 | #define MAX_MSG_LEN 1024 45 | 46 | struct booth_transport { 47 | const char *name; 48 | int (*init) (void *); 49 | int (*open) (struct booth_site *); 50 | int (*send) (struct booth_config *, struct booth_site *, void *, int); 51 | int (*send_auth) (struct booth_config *, struct booth_site *, void *, int); 52 | int (*recv) (struct booth_site *, void *, int); 53 | int (*recv_auth) (struct booth_config *, struct booth_site *, void *, int); 54 | int (*broadcast) (void *, int); 55 | int (*broadcast_auth) (struct booth_config *, void *, int); 56 | int (*close) (struct booth_site *); 57 | int (*exit) (void); 58 | }; 59 | 60 | extern const struct booth_transport booth_transport[TRANSPORT_ENTRIES]; 61 | 62 | /** 63 | * @internal 64 | * Attempts to pick identity of self from config-tracked enumeration of sites 65 | * 66 | * @param[in,out] conf config object to refer to 67 | * @param[out] mep when self-discovery successful, site pointer is stored here 68 | * @param[in] fuzzy_allowed whether it's OK to approximate the match 69 | * 70 | * @return 0 on success or negative value (-1 or -errno) on error 71 | */ 72 | int find_myself(struct booth_config *conf, struct booth_site **me, 73 | int fuzzy_allowed); 74 | 75 | int read_client(struct client *req_cl); 76 | int check_boothc_header(struct boothc_header *data, int len_incl_data); 77 | 78 | int setup_tcp_listener(int test_only); 79 | 80 | /** 81 | * @internal 82 | * Send data, with authentication added 83 | * 84 | * @param[in,out] conf config object to refer to 85 | * @param[in] to site structure of the recipient 86 | * @param[in] buf message itself 87 | * @param[in] len length of #buf 88 | * 89 | * @return see @add_hmac and @booth_udp_send 90 | */ 91 | int booth_udp_send_auth(struct booth_config *conf, struct booth_site *to, 92 | void *buf, int len); 93 | 94 | /** 95 | * @internal 96 | * First stage of incoming datagram handling (authentication) 97 | * 98 | * @param[in,out] conf config object to refer to 99 | * @param[in] msg raw message to act upon 100 | * @param[in] msglen length of #msg 101 | * 102 | * @return 0 on success or negative value (-1 or -errno) on error 103 | */ 104 | int message_recv(struct booth_config *conf, void *msg, int msglen); 105 | 106 | inline static void * node_to_addr_pointer(struct booth_site *node) { 107 | switch (node->family) { 108 | case AF_INET: return &node->sa4.sin_addr; 109 | case AF_INET6: return &node->sa6.sin6_addr; 110 | } 111 | return NULL; 112 | } 113 | 114 | /** 115 | * @internal 116 | * Send data, with authentication added 117 | * 118 | * @param[in,out] conf config object to refer to 119 | * @param[in] fd descriptor of the socket to respond to 120 | * @param[in] data message itself 121 | * @param[in] datalen length of #data 122 | * 123 | * @return 0 on success or negative value (-1 or -errno) on error 124 | */ 125 | int send_data(struct booth_config *conf, int fd, void *data, int datalen); 126 | 127 | /** 128 | * @internal 129 | * First stage of incoming datagram handling (authentication) 130 | * 131 | * @param[in,out] conf config object to refer to 132 | * @param[in] fd descriptor of the socket to respond to 133 | * @param[in] hdr message header 134 | * @param[in] data message itself 135 | * @param[in] len length of @data 136 | * 137 | * @return see #send_data and #do_write 138 | */ 139 | int send_header_plus(struct booth_config *conf, int fd, 140 | struct boothc_hdr_msg *hdr, void *data, int len); 141 | 142 | #define send_client_msg(conf, fd, msg) send_data(conf, fd, msg, sendmsglen(msg)) 143 | 144 | /** 145 | * @internal 146 | * First stage of incoming datagram handling (authentication) 147 | * 148 | * @param[in,out] conf config object to refer to 149 | * @param[in] from site structure of the sender 150 | * @param[in] buf message to check 151 | * @param[in] len length of @buf 152 | * 153 | * @return see #send_data and #do_write 154 | */ 155 | int check_auth(struct booth_config *conf, struct booth_site *from, void *buf, 156 | int len); 157 | 158 | #endif /* _TRANSPORT_H */ 159 | -------------------------------------------------------------------------------- /test/arbtests.py: -------------------------------------------------------------------------------- 1 | from servertests import ServerTests 2 | 3 | class ArbitratorConfigTests(ServerTests): 4 | mode = 'arbitrator' 5 | -------------------------------------------------------------------------------- /test/assertions.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class BoothAssertions: 4 | def configFileMissingMyIP(self, config_file=None, lock_file=None): 5 | (pid, ret, stdout, stderr, runner) = \ 6 | self.run_booth(config_file=config_file, lock_file=lock_file, 7 | expected_exitcode=1, expected_daemon=False) 8 | 9 | expected_error = "(ERROR|error): Cannot find myself in the configuration" 10 | self.assertRegexpMatches(stderr, expected_error) 11 | 12 | def assertLockFileError(self, config_file=None, config_text=None, 13 | lock_file=True, args=()): 14 | (pid, ret, stdout, stderr, runner) = \ 15 | self.run_booth(config_text=config_text, config_file=config_file, 16 | lock_file=lock_file, args=args, expected_exitcode=1) 17 | expected_error = 'lockfile open error %s: Permission denied' % runner.lock_file_used() 18 | self.assertRegexpMatches(self.read_log(), expected_error) 19 | 20 | ###################################################################### 21 | # backported from 2.7 just in case we're running on an older Python 22 | def assertRegexpMatches(self, text, expected_regexp, msg=None): 23 | """Fail the test unless the text matches the regular expression.""" 24 | if isinstance(expected_regexp, str): 25 | expected_regexp = re.compile(expected_regexp) 26 | if not expected_regexp.search(text): 27 | msg = msg or "Regexp didn't match" 28 | msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text) 29 | raise self.failureException(msg) 30 | 31 | def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None): 32 | """Fail the test if the text matches the regular expression.""" 33 | if isinstance(unexpected_regexp, str): 34 | unexpected_regexp = re.compile(unexpected_regexp) 35 | match = unexpected_regexp.search(text) 36 | if match: 37 | msg = msg or "Regexp matched" 38 | msg = '%s: %r matches %r in %r' % (msg, 39 | text[match.start():match.end()], 40 | unexpected_regexp.pattern, 41 | text) 42 | raise self.failureException(msg) 43 | ###################################################################### 44 | -------------------------------------------------------------------------------- /test/booth_path: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # manage iptables rules for the given port 4 | # 5 | 6 | [ $# -lt 1 ] && exit 7 | action=$1 8 | port=${2:-9929} 9 | testip() { 10 | local chain=$1 11 | iptables -L $chain | grep -wq ^DROP.*$port 12 | } 13 | logcmd() { 14 | logger -p local7.info "$*" 15 | eval $* 16 | } 17 | 18 | case "$action" in 19 | start) 20 | logcmd iptables -D INPUT -p udp --dport $port -j DROP 21 | logcmd iptables -D OUTPUT -p udp --dport $port -j DROP 22 | logcmd iptables -D INPUT -p udp --sport $port -j DROP 23 | logcmd iptables -D OUTPUT -p udp --sport $port -j DROP 24 | ;; 25 | stop) 26 | testip INPUT && { 27 | echo "packets from/to $port already being dropped!" 28 | exit 29 | } 30 | logcmd iptables -A INPUT -p udp --dport $port -j DROP 31 | logcmd iptables -A OUTPUT -p udp --dport $port -j DROP 32 | logcmd iptables -A INPUT -p udp --sport $port -j DROP 33 | logcmd iptables -A OUTPUT -p udp --sport $port -j DROP 34 | ;; 35 | esac 36 | -------------------------------------------------------------------------------- /test/boothrunner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | import time 4 | 5 | class BoothRunner: 6 | default_config_file = '/etc/booth/booth.conf' 7 | default_lock_file = '/var/run/booth.pid' 8 | 9 | def __init__(self, boothd_path, mode, args): 10 | self.boothd_path = boothd_path 11 | self.args = (mode, ) 12 | self.final_args = tuple(args) # will be appended to self.args 13 | self.mode = mode 14 | self.config_file = None 15 | self.lock_file = None 16 | 17 | def set_config_file_arg(self): 18 | self.args += ('-c', self.config_file) 19 | 20 | def set_config_file(self, config_file): 21 | self.config_file = config_file 22 | self.set_config_file_arg() 23 | 24 | def set_lock_file(self, lock_file): 25 | self.lock_file = lock_file 26 | self.args += ('-l', self.lock_file) 27 | 28 | def set_debug(self): 29 | self.args += ('-D', ) 30 | 31 | def set_foreground(self): 32 | self.args += ('-S', ) 33 | 34 | def all_args(self): 35 | return (self.boothd_path, ) + self.args + self.final_args 36 | 37 | def show_output(self, stdout, stderr): 38 | if stdout: 39 | print("STDOUT:") 40 | print("------") 41 | print(stdout.rstrip('\n')) 42 | if stderr: 43 | print("STDERR: (N.B. crm_ticket failures indicate daemon started correctly)") 44 | print("------") 45 | print(stderr.rstrip('\n')) 46 | print("-" * 70) 47 | 48 | def subproc_completed_within(self, p, timeout): 49 | start = time.time() 50 | wait = 0.1 51 | while True: 52 | if p.poll() is not None: 53 | return True 54 | elapsed = time.time() - start 55 | if elapsed + wait > timeout: 56 | wait = timeout - elapsed 57 | print("Waiting on %d for %.1fs ..." % (p.pid, wait)) 58 | time.sleep(wait) 59 | elapsed = time.time() - start 60 | if elapsed >= timeout: 61 | return False 62 | wait *= 2 63 | 64 | def lock_file_used(self): 65 | return self.lock_file or self.default_lock_file 66 | 67 | def config_file_used(self): 68 | return self.config_file or self.default_config_file 69 | 70 | def config_text_used(self): 71 | config_file = self.config_file_used() 72 | try: 73 | c = open(config_file) 74 | except: 75 | return None 76 | text = "".join(c.readlines()) 77 | c.close() 78 | 79 | text = text.replace('\t', '') 80 | text = text.replace('\n', '|\n') 81 | 82 | return text 83 | 84 | def show_args(self): 85 | print("\n") 86 | print("-" * 70) 87 | print("Running", ' '.join(self.all_args())) 88 | msg = "with config from %s" % self.config_file_used() 89 | config_text = self.config_text_used() 90 | if config_text is not None: 91 | msg += ": [%s]" % config_text 92 | print(msg) 93 | 94 | def run(self, expected_exitcode = None): 95 | p = subprocess.Popen(self.all_args(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) 96 | if not p: 97 | raise RuntimeError("failed to start subprocess") 98 | 99 | print("Started subprocess pid %d" % p.pid) 100 | 101 | # Wait for end of process for short time when daemonize expected 102 | # and for longer time when exit is expected - to avoid false 103 | # negatives for overloaded machines 104 | timeout = 2 105 | if expected_exitcode is not None: 106 | timeout = 30 107 | 108 | completed = self.subproc_completed_within(p, timeout) 109 | 110 | if completed: 111 | (stdout, stderr) = p.communicate() 112 | if sys.version_info[0] >= 3: 113 | # only expect ASCII/UTF-8 encodings for the obtained input bytes 114 | stdout, stderr = str(stdout, 'UTF-8'), str(stderr, 'UTF-8') 115 | self.show_output(stdout, stderr) 116 | return (p.pid, p.returncode, stdout, stderr) 117 | 118 | return (p.pid, None, None, None) 119 | -------------------------------------------------------------------------------- /test/boothtestenv.py.in: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import time 4 | import tempfile 5 | import unittest 6 | 7 | from assertions import BoothAssertions 8 | from utils import use_single_instance 9 | 10 | class BoothTestEnvironment(unittest.TestCase, BoothAssertions): 11 | abs_test_src_path = os.path.abspath('TEST_SRC_DIR') 12 | example_config_path = os.path.join(abs_test_src_path, '../conf/booth.conf.example') 13 | abs_test_build_path = os.path.abspath('TEST_BUILD_DIR') 14 | boothd_path = os.path.join(abs_test_build_path, '../src/boothd') 15 | 16 | def setUp(self): 17 | if not self._testMethodName.startswith('test_'): 18 | raise RuntimeError("unexpected test method name: " + self._testMethodName) 19 | self.test_name = self._testMethodName[5:] 20 | self.test_path = os.path.join(self.test_run_path, self.test_name) 21 | os.makedirs(self.test_path) 22 | # Give all users permisions for temp directory so boothd running as "hacluster" 23 | # can delete the lock file 24 | if os.geteuid() == 0: 25 | os.chmod(self.test_path, 0o777) 26 | 27 | # It's not good idea to kill other instancies so call following function 28 | # only if single_instance mode is used 29 | if use_single_instance(): 30 | self.ensure_boothd_not_running() 31 | 32 | def ensure_boothd_not_running(self): 33 | # Need to redirect STDERR in case we're not root, in which 34 | # case netstat's -p option causes a warning (ss doesn't). However we only 35 | # want to kill boothd processes which we own; -p will list the 36 | # pid for those and only those, which is exactly what we want 37 | # here. 38 | subprocess.call("(netstat -tlnp || ss -tlnp) 2>&1 | " + 39 | "perl -lne '(m,LISTEN\\s+(\\d+)/boothd, || /\"boothd\".*pid=(\\d+)/) and kill 15, $1'", 40 | shell=True) 41 | 42 | def get_tempfile(self, identity): 43 | tf = tempfile.NamedTemporaryFile( 44 | prefix='%s.%d.' % (identity, time.time()), 45 | dir=self.test_path, 46 | delete=False 47 | ) 48 | return tf.name 49 | 50 | def init_log(self): 51 | self.log_file = self.get_tempfile('log') 52 | os.putenv('HA_debugfile', self.log_file) # See cluster-glue/lib/clplumbing/cl_log.c 53 | 54 | def read_log(self): 55 | if not os.path.exists(self.log_file): 56 | return '' 57 | 58 | l = open(self.log_file) 59 | msgs = ''.join(l.readlines()) 60 | l.close() 61 | return msgs 62 | 63 | def check_return_code(self, pid, return_code, expected_exitcode): 64 | if return_code is None: 65 | print("pid %d still running" % pid) 66 | if expected_exitcode is not None: 67 | self.fail("expected exit code %d, not long-running process" % expected_exitcode) 68 | else: 69 | print("pid %d exited with code %d" % (pid, return_code)) 70 | if expected_exitcode is None: 71 | msg = "should not exit" 72 | else: 73 | msg = "should exit with code %s" % expected_exitcode 74 | msg += "\nLog follows (see %s)" % self.log_file 75 | msg += "\nN.B. expect mlockall/setscheduler errors when running tests non-root" 76 | msg += "\n-----------\n%s" % self.read_log() 77 | self.assertEqual(return_code, expected_exitcode, msg) 78 | -------------------------------------------------------------------------------- /test/clientenv.py: -------------------------------------------------------------------------------- 1 | from boothtestenv import BoothTestEnvironment 2 | from boothrunner import BoothRunner 3 | 4 | class ClientTestEnvironment(BoothTestEnvironment): 5 | mode = 'client' 6 | 7 | def run_booth(self, config_text=None, config_file=None, lock_file=True, 8 | args=(), expected_exitcode=0, debug=False): 9 | ''' 10 | Runs boothd. 11 | 12 | Returns a (pid, return_code, stdout, stderr, runner) tuple, 13 | where return_code/stdout/stderr are None iff pid is still running. 14 | ''' 15 | self.init_log() 16 | 17 | runner = BoothRunner(self.boothd_path, self.mode, args) 18 | runner.show_args() 19 | (pid, return_code, stdout, stderr) = runner.run(expected_exitcode) 20 | self.check_return_code(pid, return_code, expected_exitcode) 21 | 22 | return (pid, return_code, stdout, stderr, runner) 23 | 24 | def _test_buffer_overflow(self, expected_error, **args): 25 | (pid, ret, stdout, stderr, runner) = \ 26 | self.run_booth(expected_exitcode=1, **args) 27 | self.assertRegexpMatches(stderr, expected_error) 28 | -------------------------------------------------------------------------------- /test/clienttests.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | from clientenv import ClientTestEnvironment 4 | 5 | class ClientConfigTests(ClientTestEnvironment): 6 | mode = 'client' 7 | 8 | def test_site_buffer_overflow(self): 9 | # https://bugzilla.novell.com/show_bug.cgi?id=750256 10 | longfile = (string.ascii_lowercase * 3)[:63] 11 | expected_error = "'%s' exceeds maximum site name length" % longfile 12 | args = [ 'grant', '-s', longfile, '-t', 'ticket' ] 13 | self._test_buffer_overflow(expected_error, args=args) 14 | 15 | def test_ticket_buffer_overflow(self): 16 | # https://bugzilla.novell.com/show_bug.cgi?id=750256 17 | longfile = (string.ascii_lowercase * 3)[:63] 18 | expected_error = "'%s' exceeds maximum ticket name length" % longfile 19 | args = [ 'grant', '-s', 'site', '-t', longfile ] 20 | self._test_buffer_overflow(expected_error, args=args) 21 | -------------------------------------------------------------------------------- /test/runtests.py.in: -------------------------------------------------------------------------------- 1 | #!PYTHON_SHEBANG 2 | 3 | import os 4 | import shutil 5 | import sys 6 | import tempfile 7 | import time 8 | import unittest 9 | 10 | sys.path.append('TEST_SRC_DIR') 11 | sys.path.append('TEST_BUILD_DIR') 12 | 13 | from clienttests import ClientConfigTests 14 | from sitetests import SiteConfigTests 15 | #from arbtests import ArbitratorConfigTests 16 | 17 | from utils import use_single_instance 18 | 19 | if __name__ == '__main__': 20 | # Likely assumption for the root exclusion is the amount of risk 21 | # associated with what naturally accompanies root privileges: 22 | # - accidental overwrite (eventually also deletion) of unrelated, 23 | # legitimate and perhaps vital files 24 | # - accidental termination of unrelated, legitimate and perhaps 25 | # vital processes 26 | # - and so forth, possibly amplified with awkward parallel test 27 | # suite run scenarios (containers partly sharing state, etc.) 28 | # 29 | # Nonetheless, there are cases like self-contained CI runs where 30 | # all these concerns are absent, so allow opt-in relaxing of this. 31 | # Alternatively, the config generator could inject particular 32 | # credentials for a booth proces to use, but that might come too 33 | # late to address the above concerns reliably. 34 | if (os.geteuid() == 0 35 | and "--allow-root-user" not in sys.argv 36 | and not(os.environ.get("BOOTH_RUNTESTS_ROOT_USER"))): 37 | sys.stderr.write("Must be run non-root; aborting.\n") 38 | sys.exit(1) 39 | 40 | tmp_path = '/tmp/booth-tests' 41 | if not os.path.exists(tmp_path): 42 | os.makedirs(tmp_path) 43 | test_run_path = tempfile.mkdtemp(prefix='%d.' % time.time(), dir=tmp_path) 44 | if os.geteuid() == 0: 45 | # Give all users at least rx permisions for temp directory so hacluster running booth 46 | # can delete lock file 47 | os.chmod(test_run_path, 0o755) 48 | 49 | suite = unittest.TestSuite() 50 | testclasses = [ 51 | SiteConfigTests, 52 | #ArbitratorConfigTests, 53 | ClientConfigTests, 54 | ] 55 | for testclass in testclasses: 56 | testclass.test_run_path = test_run_path 57 | suite.addTests(unittest.TestLoader().loadTestsFromTestCase(testclass)) 58 | 59 | runner_args = { 60 | #'verbosity' : 2, 61 | } 62 | major, minor, micro, releaselevel, serial = sys.version_info 63 | if major > 2 or (major == 2 and minor >= 7): 64 | # New in 2.7 65 | runner_args['buffer'] = True 66 | runner_args['failfast'] = True 67 | pass 68 | 69 | if os.geteuid() != 0 and use_single_instance(): 70 | # not root, so safe 71 | # needed because old instances might still use the UDP port. 72 | os.system("killall boothd") 73 | 74 | runner = unittest.TextTestRunner(**runner_args) 75 | result = runner.run(suite) 76 | 77 | if result.wasSuccessful(): 78 | shutil.rmtree(test_run_path) 79 | sys.exit(0) 80 | else: 81 | print("Left %s for debugging" % test_run_path) 82 | sys.exit(1) 83 | -------------------------------------------------------------------------------- /test/serverenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import time 4 | 5 | from boothrunner import BoothRunner 6 | from boothtestenv import BoothTestEnvironment 7 | from utils import get_IP, use_single_instance 8 | 9 | class ServerTestEnvironment(BoothTestEnvironment): 10 | ''' 11 | boothd site/arbitrator will hang in setup phase while attempting to connect 12 | to an unreachable peer during ticket_catchup(). In a test environment we don't 13 | have any reachable peers. Fortunately, we can still successfully launch a 14 | daemon by only listing our own IP in the config file. 15 | ''' 16 | typical_config = """\ 17 | # This is like the config in the manual 18 | transport="UDP" 19 | port="9929" 20 | # Here's another comment 21 | #arbitrator="147.2.207.14" 22 | site="147.4.215.19" 23 | #site="147.18.2.1" 24 | ticket="ticketA" 25 | ticket="ticketB" 26 | """ 27 | site_re = re.compile('^site=".+"', re.MULTILINE) 28 | working_config = re.sub(site_re, 'site="%s"' % get_IP(), typical_config, 1) 29 | 30 | if not use_single_instance(): 31 | # use port based on pid 32 | port_re = re.compile('^port=".+"', re.MULTILINE) 33 | working_config = re.sub(port_re, 'port="%s"' % (9929 + (os.getpid() % 1009)), working_config, 1) 34 | 35 | def run_booth(self, expected_exitcode, expected_daemon, 36 | config_text=None, config_file=None, lock_file=True, 37 | args=(), debug=False, foreground=False): 38 | ''' 39 | Runs boothd. Defaults to using a temporary lock file and the 40 | standard config file path. There are four possible types of 41 | outcome: 42 | 43 | - boothd exits non-zero without launching a daemon (setup phase failed, 44 | e.g. due to invalid configuration file) 45 | - boothd exits zero after launching a daemon (successful operation) 46 | - boothd does not exit (running in foreground mode) 47 | - boothd does not exit (setup phase hangs, e.g. while attempting 48 | to connect to peer during ticket_catchup()) 49 | 50 | Arguments: 51 | config_text 52 | a string containing the contents of a configuration file to use 53 | config_file 54 | path to a configuration file to use 55 | lock_file 56 | False: don't pass a lockfile parameter to booth via -l 57 | True: pass a temporary lockfile parameter to booth via -l 58 | string: pass the given lockfile path to booth via -l 59 | args 60 | iterable of extra args to pass to booth 61 | expected_exitcode 62 | an integer, or False if booth is not expected to terminate 63 | within the timeout 64 | expected_daemon 65 | True iff a daemon is expected to be launched (this means 66 | running the server in foreground mode via -S; 67 | even though in this case the server's not technically not a daemon, 68 | we still want to treat it like one by checking the lockfile 69 | before and after we kill it) 70 | debug 71 | True means pass the -D parameter 72 | foreground 73 | True means pass the -S parameter 74 | 75 | Returns a (pid, return_code, stdout, stderr, runner) tuple, 76 | where return_code/stdout/stderr are None iff pid is still running. 77 | ''' 78 | if expected_daemon and expected_exitcode is not None and expected_exitcode != 0: 79 | raise RuntimeError("Shouldn't ever expect daemon to start and then failure") 80 | 81 | if not expected_daemon and expected_exitcode == 0: 82 | raise RuntimeError("Shouldn't ever expect success without starting daemon") 83 | 84 | self.init_log() 85 | 86 | runner = BoothRunner(self.boothd_path, self.mode, args) 87 | 88 | if config_text: 89 | config_file = self.write_config_file(config_text) 90 | if config_file: 91 | runner.set_config_file(config_file) 92 | 93 | if lock_file is True: 94 | lock_file = os.path.join(self.test_path, 'boothd-lock.pid') 95 | if lock_file: 96 | runner.set_lock_file(lock_file) 97 | 98 | if debug: 99 | runner.set_debug() 100 | 101 | if foreground: 102 | runner.set_foreground() 103 | 104 | runner.show_args() 105 | (pid, return_code, stdout, stderr) = runner.run(expected_exitcode) 106 | self.check_return_code(pid, return_code, expected_exitcode) 107 | 108 | if expected_daemon: 109 | self.check_daemon_handling(runner, expected_daemon) 110 | elif return_code is None: 111 | # This isn't strictly necessary because we ensure no 112 | # daemon is running from within test setUp(), but it's 113 | # probably a good idea to tidy up after ourselves anyway. 114 | self.kill_pid(pid) 115 | 116 | return (pid, return_code, stdout, stderr, runner) 117 | 118 | def write_config_file(self, config_text): 119 | config_file = self.get_tempfile('config') 120 | c = open(config_file, 'w') 121 | c.write(config_text) 122 | c.close() 123 | return config_file 124 | 125 | def kill_pid(self, pid): 126 | print("killing %d ..." % pid) 127 | os.kill(pid, 15) 128 | print("killed") 129 | 130 | # Wait for lock file to appear if must_exist is True, or disappear if 131 | # must_exist is False for maximum of timeout seconds 132 | def wait_for_lock_file(self, lock_file, must_exist = True, timeout = 30): 133 | start = time.time() 134 | wait = 0.1 135 | while True: 136 | if must_exist and os.path.exists(lock_file): 137 | # Lock file must contain single line 138 | l = open(lock_file) 139 | lines = l.readlines() 140 | l.close() 141 | 142 | if len(lines) == 1: 143 | return True 144 | if not must_exist and not os.path.exists(lock_file): 145 | return True 146 | elapsed = time.time() - start 147 | if elapsed + wait > timeout: 148 | wait = timeout - elapsed 149 | 150 | appear_str = "appear" if must_exist else "disappear" 151 | print("Waiting for lock file %s to %s for %.1fs ..." % (lock_file, appear_str, wait)) 152 | 153 | time.sleep(wait) 154 | elapsed = time.time() - start 155 | if elapsed >= timeout: 156 | return False 157 | wait *= 2 158 | 159 | def check_daemon_handling(self, runner, expected_daemon): 160 | ''' 161 | Check that the lock file contains a pid referring to a running 162 | daemon. Then kill the daemon, and ensure that the lock file 163 | vanishes (bnc#749763). 164 | ''' 165 | self.wait_for_lock_file(runner.lock_file, True, 30) 166 | daemon_pid = self.get_daemon_pid_from_lock_file(runner.lock_file) 167 | err = "lock file should contain pid" 168 | if not expected_daemon: 169 | err += ", even though we didn't expect a daemon" 170 | self.assertTrue(daemon_pid is not None, err) 171 | 172 | daemon_running = self.is_pid_running_daemon(daemon_pid) 173 | err = "pid in lock file should refer to a running daemon" 174 | self.assertTrue(daemon_running, err) 175 | 176 | if daemon_running: 177 | self.kill_pid(int(daemon_pid)) 178 | self.wait_for_lock_file(runner.lock_file, False, 30) 179 | time.sleep(1) 180 | daemon_pid = self.get_daemon_pid_from_lock_file(runner.lock_file) 181 | self.assertTrue(daemon_pid is None, 182 | 'bnc#749763: lock file should vanish after daemon is killed') 183 | 184 | def get_daemon_pid_from_lock_file(self, lock_file): 185 | ''' 186 | Returns the pid contained in lock_file, or None if it doesn't exist. 187 | ''' 188 | if not os.path.exists(lock_file): 189 | print("%s does not exist" % lock_file) 190 | return None 191 | 192 | l = open(lock_file) 193 | lines = l.readlines() 194 | l.close() 195 | self.assertEqual(len(lines), 1, "Lock file should contain one line") 196 | pid = re.search('\\bbooth_pid="?(\\d+)"?', lines[0]).group(1) 197 | print("lockfile contains: <%s>" % pid) 198 | return pid 199 | 200 | def is_pid_running_daemon(self, pid): 201 | ''' 202 | Returns true iff the given pid refers to a running boothd process. 203 | ''' 204 | 205 | path = "/proc/%s" % pid 206 | pid_running = os.path.isdir(path) 207 | 208 | # print "======" 209 | # import subprocess 210 | # print subprocess.check_output(['lsof', '-p', pid]) 211 | # print subprocess.check_output(['ls', path]) 212 | # print subprocess.check_output(['cat', "/proc/%s/cmdline" % pid]) 213 | # print "======" 214 | 215 | if not pid_running: 216 | return False 217 | 218 | c = open("/proc/%s/cmdline" % pid) 219 | cmdline = "".join(c.readlines()) 220 | print(cmdline) 221 | c.close() 222 | 223 | if cmdline.find('boothd') == -1: 224 | print('no boothd in cmdline:', cmdline) 225 | return False 226 | 227 | # self.assertRegexpMatches( 228 | # cmdline, 229 | # 'boothd', 230 | # "lock file should refer to pid of running boothd" 231 | # ) 232 | 233 | return True 234 | 235 | def _test_buffer_overflow(self, expected_error, **args): 236 | (pid, ret, stdout, stderr, runner) = \ 237 | self.run_booth(expected_exitcode=1, expected_daemon=False, **args) 238 | self.assertRegexpMatches(stderr, expected_error) 239 | -------------------------------------------------------------------------------- /test/servertests.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import re 3 | import string 4 | 5 | from serverenv import ServerTestEnvironment 6 | 7 | class ServerTests(ServerTestEnvironment): 8 | # We don't know enough about the build/test system to rely on the 9 | # existence, permissions, contents of the default config file. So 10 | # we can't predict (and hence test) how booth will behave when -c 11 | # is not specified. 12 | # 13 | # def test_no_args(self): 14 | # # If do_server() called lockfile() first then this would be 15 | # # the appropriate test: 16 | # #self.assertLockFileError(lock_file=False) 17 | # 18 | # # If do_server() called setup() first, and the default 19 | # # config file was readable non-root, then this would be the 20 | # # appropriate test: 21 | # self.configFileMissingMyIP(lock_file=False) 22 | # 23 | # def test_custom_lock_file(self): 24 | # (pid, ret, stdout, stderr, runner) = \ 25 | # self.run_booth(expected_exitcode=1, expected_daemon=False) 26 | # self.assertRegexpMatches( 27 | # stderr, 28 | # 'failed to open %s: ' % runner.config_file_used(), 29 | # 'should fail to read default config file' 30 | # ) 31 | 32 | def test_example_config(self): 33 | self.configFileMissingMyIP(config_file=self.example_config_path) 34 | 35 | def test_config_file_buffer_overflow(self): 36 | # https://bugzilla.novell.com/show_bug.cgi?id=750256 37 | longfile = string.ascii_lowercase * (8192 // len(string.ascii_lowercase)) 38 | expected_error = "'%s' exceeds maximum config name length" % longfile 39 | self._test_buffer_overflow(expected_error, config_file=longfile) 40 | 41 | def test_lock_file_buffer_overflow(self): 42 | # https://bugzilla.novell.com/show_bug.cgi?id=750256 43 | longfile = string.ascii_lowercase * (8192 // len(string.ascii_lowercase)) 44 | expected_error = "'%s' exceeds maximum lock file length" % longfile 45 | self._test_buffer_overflow(expected_error, lock_file=longfile) 46 | 47 | def test_working_config(self): 48 | (pid, ret, stdout, stderr, runner) = \ 49 | self.run_booth(expected_exitcode=0, expected_daemon=True, 50 | config_text=self.working_config) 51 | 52 | def test_missing_quotes(self): 53 | # quotes no longer required 54 | return True 55 | orig_lines = self.working_config.split("\n") 56 | for (i, line) in enumerate(orig_lines): 57 | new_lines = copy.copy(orig_lines) 58 | new_lines[i] = line.replace('"', '') 59 | new_config = "\n".join(new_lines) 60 | 61 | line_contains_IP = re.search('^\s*(site|arbitrator)=.*[0-9]\.', line) 62 | if line_contains_IP: 63 | # IP addresses need to be surrounded by quotes, 64 | # so stripping them should cause it to fail 65 | expected_exitcode = 1 66 | expected_daemon = False 67 | else: 68 | expected_exitcode = 0 69 | expected_daemon = True 70 | 71 | (pid, ret, stdout, stderr, runner) = \ 72 | self.run_booth(config_text=new_config, 73 | expected_exitcode=expected_exitcode, 74 | expected_daemon=expected_daemon) 75 | 76 | if line_contains_IP: 77 | self.assertRegexpMatches( 78 | self.read_log(), 79 | "(ERROR|error): invalid config file format: unquoted '.'", 80 | 'IP addresses need to be quoted' 81 | ) 82 | 83 | def test_debug_mode(self): 84 | (pid, ret, stdout, stderr, runner) = \ 85 | self.run_booth(config_text=self.working_config, debug=True, 86 | expected_exitcode=0, expected_daemon=True) 87 | 88 | def test_foreground_mode(self): 89 | (pid, ret, stdout, stderr, runner) = \ 90 | self.run_booth(config_text=self.working_config, foreground=True, 91 | expected_exitcode=None, expected_daemon=True) 92 | 93 | def test_debug_and_foreground_mode(self): 94 | (pid, ret, stdout, stderr, runner) = \ 95 | self.run_booth(config_text=self.working_config, debug=True, foreground=True, 96 | expected_exitcode=None, expected_daemon=True) 97 | 98 | def test_missing_transport(self): 99 | # UDP is default -- TODO? 100 | return True 101 | config = re.sub('transport=.+\n', '', self.typical_config) 102 | (pid, ret, stdout, stderr, runner) = \ 103 | self.run_booth(config_text=config, expected_exitcode=1, expected_daemon=False) 104 | self.assertRegexpMatches( 105 | self.read_log(), 106 | 'config file was missing transport line' 107 | ) 108 | 109 | def test_invalid_transport_protocol(self): 110 | config = re.sub('transport=.+', 'transport=SNEAKERNET', self.typical_config) 111 | (pid, ret, stdout, stderr, runner) = \ 112 | self.run_booth(config_text=config, expected_exitcode=1, expected_daemon=False) 113 | self.assertRegexpMatches(stderr, 'invalid transport protocol "SNEAKERNET"') 114 | 115 | def test_missing_final_newline(self): 116 | config = re.sub('\n$', '', self.working_config) 117 | (pid, ret, stdout, stderr, runner) = \ 118 | self.run_booth(config_text=config, expected_exitcode=0, expected_daemon=True) 119 | 120 | def test_a_few_trailing_whitespaces(self): 121 | for ws in (' ', ' '): 122 | new_config = self.working_config.replace("\n", ws + "\n", 3) 123 | (pid, ret, stdout, stderr, runner) = \ 124 | self.run_booth(config_text=new_config, 125 | expected_exitcode=0, expected_daemon=True) 126 | 127 | def test_trailing_space_everywhere(self): 128 | for ws in (' ', ' '): 129 | new_config = self.working_config.replace("\n", ws + "\n") 130 | (pid, ret, stdout, stderr, runner) = \ 131 | self.run_booth(config_text=new_config, 132 | expected_exitcode=0, expected_daemon=True) 133 | 134 | def test_unquoted_space(self): 135 | for ticket in ('unquoted space', 'unquoted space man'): 136 | new_config = re.sub('ticket=.+', 'ticket=' + ticket, 137 | self.working_config, 1) 138 | (pid, ret, stdout, stderr, runner) = \ 139 | self.run_booth(config_text=new_config, expected_exitcode=1, expected_daemon=False) 140 | self.assertRegexpMatches(stderr, 'ticket name "' + ticket + '" invalid') 141 | 142 | def test_unreachable_peer(self): 143 | # what should this test do? daemon not expected, but no exitcode either? 144 | # booth would now just run, and try to reach that peer... 145 | # TCP reachability is not required during startup anymore. 146 | return True 147 | config = re.sub('#(.+147.+)', lambda m: m.group(1), self.working_config) 148 | self.run_booth(config_text=config, 149 | expected_exitcode=None, expected_daemon=False) 150 | 151 | def test_unknown_keyword(self): 152 | # Test unexpected keyword before tickets definition 153 | keyword='unknown-keyword' 154 | config = re.sub('transport=', keyword + '=', self.typical_config) 155 | (pid, ret, stdout, stderr, runner) = \ 156 | self.run_booth(config_text=config, expected_exitcode=1, expected_daemon=False) 157 | self.assertRegexpMatches(stderr, 'Unexpected keyword "' + keyword + '"') 158 | 159 | # Test unexpected keyword in tickets definition 160 | config = re.sub('\n$', '\n' + keyword + '=value', self.typical_config) 161 | (pid, ret, stdout, stderr, runner) = \ 162 | self.run_booth(config_text=config, expected_exitcode=1, expected_daemon=False) 163 | self.assertRegexpMatches(stderr, 'Unknown keyword "' + keyword + '"') 164 | -------------------------------------------------------------------------------- /test/sitetests.py: -------------------------------------------------------------------------------- 1 | from servertests import ServerTests 2 | 3 | class SiteConfigTests(ServerTests): 4 | mode = 'site' 5 | -------------------------------------------------------------------------------- /test/utils.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import sys 4 | 5 | def get_IP(): 6 | # IPv4 only for now 7 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 8 | try: 9 | s.connect(('147.4.215.19', 9929)) 10 | ret = s.getsockname()[0] 11 | except: 12 | ret = '127.0.0.1' 13 | finally: 14 | s.close() 15 | 16 | return ret 17 | 18 | def use_single_instance(): 19 | return ("--single-instance" in sys.argv) or (os.environ.get("BOOTH_RUNTESTS_SINGLE_INSTANCE") != None) 20 | --------------------------------------------------------------------------------