diff options
34 files changed, 994 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4572ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*~ +.*.swp +/aclocal.m4 +/autom4te.cache +/build-stamp +/ChangeLog +/config +/config.log +/config.guess +/config.status +/config.sub +/configure +/debian/files +/debian/vyatta-cluster +/debian/vyatta-cluster.postinst +/debian/vyatta-cluster.postrm +/INSTALL +/Makefile.in +/Makefile + @@ -0,0 +1 @@ +eng@vyatta.com @@ -0,0 +1,26 @@ +/* + * Package: vyatt-cluster + * + * **** License **** + * Version: VPL 1.0 + * + * The contents of this file are subject to the Vyatta Public License + * Version 1.0 ("License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.vyatta.com/vpl + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * This code was originally developed by Vyatta, Inc. + * Portions created by Vyatta are Copyright (C) "YEAR" Vyatta, Inc. + * All Rights Reserved. + * + * Author: An-Cheng Huang + * Date: 2007 + * Description: Vyatta configuration/operational commands for clustering + * + * **** End License **** + */ diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..21df18b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,17 @@ +cfgdir = $(datadir)/vyatta-cfg/templates +opdir = $(datadir)/vyatta-op/templates +share_perl5dir = $(datadir)/perl5 + +sbin_SCRIPTS = scripts/vyatta-update-cluster.pl +sbin_SCRIPTS += scripts/vyatta-show-cluster.pl + +share_perl5_DATA = scripts/VyattaClusterConfig.pm + +cpiop = find . ! -regex '\(.*~\|.*\.bak\|.*\.swp\|.*\#.*\#\)' -print0 | \ + cpio -0pd + +install-exec-hook: + mkdir -p $(DESTDIR)$(cfgdir) + cd templates-cfg; $(cpiop) $(DESTDIR)$(cfgdir) + mkdir -p $(DESTDIR)$(opdir) + cd templates-op; $(cpiop) $(DESTDIR)$(opdir) @@ -0,0 +1 @@ +see http://www.vyatta.com/news/ @@ -0,0 +1 @@ +This package has the Vyatta configuration and operational templates and scripts for clustering. diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..38b3cd2 --- /dev/null +++ b/configure.ac @@ -0,0 +1,30 @@ +# Process this file with autoconf to produce a configure script. +AC_PREREQ(2.59) + +m4_define([VERSION_ID], [m4_esyscmd([ + if test -f .version ; then + head -n 1 .version | tr -d \\n + else + echo -n 2.4 + fi])]) +AC_INIT([vyatta-cluster], VERSION_ID, [vyatta-support@vyatta.com]) + +test -n "$VYATTA_VERSION" || VYATTA_VERSION=$PACKAGE_VERSION + +AC_CONFIG_AUX_DIR([config]) +AM_INIT_AUTOMAKE([gnu no-dist-gzip dist-bzip2 subdir-objects]) +AC_PREFIX_DEFAULT([/opt/vyatta]) + +AC_ARG_ENABLE([nostrip], + AC_HELP_STRING([--enable-nostrip], + [include -nostrip option during packaging]), + [NOSTRIP=-nostrip], [NOSTRIP=]) + +AC_CONFIG_FILES( + [Makefile] + [debian/vyatta-cluster.postinst]) + +AC_SUBST(NOSTRIP) + +AC_OUTPUT + diff --git a/debian/README b/debian/README new file mode 100644 index 0000000..ae0cf33 --- /dev/null +++ b/debian/README @@ -0,0 +1,6 @@ +The Debian Package vyatta-cluster +---------------------------- + +This package has the Vyatta configuration and operational templates and scripts for clustering. + + -- An-Cheng Huang <ancheng@vyatta.com> Fri, 14 Dec 2007 17:02:02 -0700 diff --git a/debian/autogen.sh b/debian/autogen.sh new file mode 100755 index 0000000..ff125d1 --- /dev/null +++ b/debian/autogen.sh @@ -0,0 +1,37 @@ +#!/bin/sh + + +if [ -d .git ] ; then +# generate GNU/Debian format ChangeLog from git log + + rm -f ChangeLog + + if which git2cl >/dev/null ; then + git-log --pretty --numstat --summary | git2cl >> ChangeLog + else + git-log --pretty=short >> ChangeLog + fi + +# append repository reference + + url=` git repo-config --get remote.origin.url` + test "x$url" = "x" && url=`pwd` + + branch=`git-branch --no-color | sed '/^\* /!d; s/^\* //'` + test "x$branch" = "x" && branch=master + + sha=`git log --pretty=oneline --no-color -n 1 | cut -c-8` + test "x$sha" = "x" && sha=00000000 + + echo "$url#$branch-$sha" >> ChangeLog + +fi + +rm -rf config +rm -f aclocal.m4 config.guess config.statusconfig.sub configure INSTALL + +autoreconf --force --install + +rm -f config.sub config.guess +ln -s /usr/share/misc/config.sub . +ln -s /usr/share/misc/config.guess . diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..6f5cb8e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +vyatta-cluster (0.1) unstable; urgency=low + + * Initial Release. + + -- An-Cheng Huang <ancheng@vyatta.com> Fri, 14 Dec 2007 17:02:02 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..4ee0ef2 --- /dev/null +++ b/debian/control @@ -0,0 +1,14 @@ +Source: vyatta-cluster +Section: contrib/net +Priority: extra +Maintainer: An-Cheng Huang <ancheng@vyatta.com> +Build-Depends: debhelper (>= 5), autotools-dev +Standards-Version: 3.7.2 + +Package: vyatta-cluster +Architecture: all +Depends: vyatta-cfg, + vyatta-op, + heartbeat(>=2.1.2-1) +Description: Vyatta configuration/operational commands for clustering + Vyatta configuration and operational templates and scripts for clustering diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..ad2bbbf --- /dev/null +++ b/debian/copyright @@ -0,0 +1,34 @@ +This package was debianized by An-Cheng Huang <ancheng@vyatta.com> on +Fri, 14 Dec 2007 17:02:02 -0700. + +It's original content from the GIT repository <http://vyatt.com/git/vyatta-cluster> + +Upstream Author: + + <eng@vyatta.com> + +Copyright: + + Copyright (C) 2007 Vyatta, Inc. + All Rights Reserved. + +License: + + The contents of this package are subject to the Vyatta Public License + Version 1.0 ("License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.vyatta.com/vpl + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + This code was originally developed by Vyatta, Inc. + Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc. + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. + +The Debian packaging is (C) 2007, Tom Grennan <tgrennan@vyatta.com> and +is licensed under the GPL, see above. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..50bd824 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +NEWS +README diff --git a/debian/linda b/debian/linda new file mode 100644 index 0000000..0381d9d --- /dev/null +++ b/debian/linda @@ -0,0 +1 @@ +Tag: file-in-opt diff --git a/debian/lintian b/debian/lintian new file mode 100644 index 0000000..d024a1d --- /dev/null +++ b/debian/lintian @@ -0,0 +1,2 @@ +vyatta-cluster: file-in-unusual-dir +vyatta-cluster: dir-or-file-in-opt diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..406cf53 --- /dev/null +++ b/debian/rules @@ -0,0 +1,102 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) +PACKAGE=vyatta-cluster +PKGDIR=$(CURDIR)/debian/$(PACKAGE) + +CFLAGS = -Wall -g + +configure = ./configure +configure += --host=$(DEB_HOST_GNU_TYPE) +configure += --build=$(DEB_BUILD_GNU_TYPE) +configure += --prefix=/opt/vyatta +configure += --mandir=\$${prefix}/share/man +configure += --infodir=\$${prefix}/share/info +configure += CFLAGS="$(CFLAGS)" +configure += LDFLAGS="-Wl,-z,defs" + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure.ac Makefile.am + chmod +x debian/autogen.sh + debian/autogen.sh + +config.status: configure + dh_testdir + rm -f config.cache + $(configure) + +build: build-stamp + +build-stamp: config.status + dh_testdir + $(MAKE) + touch $@ + +clean: clean-patched + +# Clean everything up, including everything auto-generated +# at build time that needs not to be kept around in the Debian diff +clean-patched: + dh_testdir + dh_testroot + if test -f Makefile ; then $(MAKE) clean distclean ; fi + rm -f build-stamp + rm -f config.status config.sub config.guess config.log + rm -f aclocal.m4 configure Makefile.in Makefile INSTALL + rm -f etc/default/vyatta + rm -rf config + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + $(MAKE) DESTDIR=$(PKGDIR) install + + install -D --mode=0644 debian/lintian $(PKGDIR)/usr/share/lintian/overrides/$(PACKAGE) + install -D --mode=0644 debian/linda $(PKGDIR)/usr/share/linda/overrides/$(PACKAGE) + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_install + dh_installdebconf + dh_link + dh_strip + dh_compress + dh_fixperms + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture-dependent files here. +binary-arch: build install +# This is an architecture independent package +# so; we have nothing to do by default. + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/debian/vyatta-cluster.postinst.in b/debian/vyatta-cluster.postinst.in new file mode 100644 index 0000000..9d7d866 --- /dev/null +++ b/debian/vyatta-cluster.postinst.in @@ -0,0 +1,4 @@ +#!/bin/bash + +update-rc.d -f heartbeat remove >/dev/null + diff --git a/scripts/VyattaClusterConfig.pm b/scripts/VyattaClusterConfig.pm new file mode 100644 index 0000000..affb3e8 --- /dev/null +++ b/scripts/VyattaClusterConfig.pm @@ -0,0 +1,312 @@ +package VyattaClusterConfig; + +use strict; +use lib "/opt/vyatta/share/perl5/"; +use VyattaConfig; + +my $DEFAULT_INITDEAD = 120; +my $DEFAULT_LOG_FACILITY = 'daemon'; +my $SERVICE_DIR = "/etc/init.d"; + +my %fields = ( + _interface => undef, + _pre_shared => undef, + _keepalive_itvl => undef, + _dead_itvl => undef, + _groups => {}, + _is_empty => 1, +); + +sub new { + my $that = shift; + my $class = ref ($that) || $that; + my $self = { + %fields, + }; + + bless $self, $class; + return $self; +} + +sub setup { + my ( $self, $level ) = @_; + my $config = new VyattaConfig; + + $config->setLevel("$level"); + my @nodes = $config->listNodes(); + if (scalar(@nodes) <= 0) { + $self->{_is_empty} = 1; + return 0; + } else { + $self->{_is_empty} = 0; + } + + my @tmp = $config->returnValues("interface"); + $self->{_interface} = [ @tmp ]; + $self->{_pre_shared} = $config->returnValue("pre-shared-secret"); + $self->{_keepalive_itvl} = $config->returnValue("keepalive-interval"); + $self->{_dead_itvl} = $config->returnValue("dead-interval"); + + $config->setLevel("$level group"); + my @groups = $config->listNodes(); + my $group; + for $group (@groups) { + my $hashref = {}; + $config->setLevel("$level group $group"); + $hashref->{_primary} = $config->returnValue("primary"); + @tmp = $config->returnValues("secondary"); + $hashref->{_secondary} = [ @tmp ]; + $hashref->{_auto_failback} = $config->returnValue("auto-failback"); + @tmp = $config->returnValues("monitor"); + $hashref->{_monitor} = [ @tmp ]; + @tmp = $config->returnValues("service"); + $hashref->{_service} = [ @tmp ]; + $self->{_groups}->{$group} = $hashref; + } + + return 0; +} + +sub setupOrig { + my ( $self, $level ) = @_; + my $config = new VyattaConfig; + + $config->setLevel("$level"); + my @nodes = $config->listOrigNodes(); + if (scalar(@nodes) <= 0) { + $self->{_is_empty} = 1; + return 0; + } else { + $self->{_is_empty} = 0; + } + + my @tmp = $config->returnOrigValues("interface"); + $self->{_interface} = [ @tmp ]; + $self->{_pre_shared} = $config->returnOrigValue("pre-shared-secret"); + $self->{_keepalive_itvl} = $config->returnOrigValue("keepalive-interval"); + $self->{_dead_itvl} = $config->returnOrigValue("dead-interval"); + + $config->setLevel("$level group"); + my @groups = $config->listOrigNodes(); + my $group; + for $group (@groups) { + my $hashref = {}; + $config->setLevel("$level group $group"); + $hashref->{_primary} = $config->returnOrigValue("primary"); + @tmp = $config->returnOrigValues("secondary"); + $hashref->{_secondary} = [ @tmp ]; + $hashref->{_auto_failback} = $config->returnOrigValue("auto-failback"); + @tmp = $config->returnOrigValues("monitor"); + $hashref->{_monitor} = [ @tmp ]; + @tmp = $config->returnOrigValues("service"); + $hashref->{_service} = [ @tmp ]; + $self->{_groups}->{$group} = $hashref; + } + + return 0; +} + +sub primaryNode { + my ($self) = @_; + my @groups = keys %{$self->{_groups}}; + my $hashref = $self->{_groups}->{$groups[0]}; + return $hashref->{_primary}; +} + +sub secondaryNode { + my ($self) = @_; + my @groups = keys %{$self->{_groups}}; + my $hashref = $self->{_groups}->{$groups[0]}; + return ${$hashref->{_secondary}}[0]; +} + +sub monitorNodes { + my ($self) = @_; + my @groups = keys %{$self->{_groups}}; + my $hashref = $self->{_groups}->{$groups[0]}; + return @{$hashref->{_monitor}}; +} + +sub serviceStr { + my ($self) = @_; + my @groups = keys %{$self->{_groups}}; + my $hashref = $self->{_groups}->{$groups[0]}; + return (join " ", @{$hashref->{_service}}); +} + +sub isEmpty { + my ($self) = @_; + return $self->{_is_empty}; +} + +sub authkeys { + my ($self) = @_; + my $key = $self->{_pre_shared}; + return (undef, "pre-shared secret not defined") if (!defined($key)); + my $str =<<EOS; +auth 1 +1 sha1 $key +EOS + return ($str, undef); +} + +sub check_interfaces { + my @interfaces = @_; + foreach (@interfaces) { + system("ip addr show $_ >& /dev/null"); + if ($? >> 8) { + return "interface $_ does not exist"; + } + } + return undef; +} + +sub ha_cf { + my ($self) = @_; + my @groups = keys %{$self->{_groups}}; + return (undef, "no resource group defined") if ($#groups < 0); + return (undef, "using multiple resource groups is not supported yet") + if ($#groups > 0); + + my $ierr = check_interfaces(@{$self->{_interface}}); + if (defined($ierr)) { + return (undef, $ierr); + } + my $interfaces = join " ", @{$self->{_interface}}; + my $kitvl = $self->{_keepalive_itvl}; + my $ditvl = $self->{_dead_itvl}; + + my $hashref = $self->{_groups}->{$groups[0]}; + my $primary = $hashref->{_primary}; + my @secondaries = @{$hashref->{_secondary}}; + my $pings = join " ", @{$hashref->{_monitor}}; + my $auto_failback = ($hashref->{_auto_failback} eq "true") ? + "on" : "off"; + my $my_name = `uname -n`; + chomp $my_name; + + return (undef, "heartbeat interface(s) not defined") if ($interfaces eq ""); + return (undef, "keepalive interval not defined") if (!defined($kitvl)); + return (undef, "dead interval not defined") if (!defined($ditvl)); + return (undef, "cluster primary system not defined") + if (!defined($primary)); + return (undef, "cluster secondary node(s) not defined") + if ($#secondaries < 0); + return (undef, "using multiple secondary nodes is not supported yet") + if ($#secondaries > 0); + return (undef, + "dead interval must be more than twice the keepalive interval") + if ($ditvl <= (2 * $kitvl)); + return (undef, + "dead interval must be smaller than $DEFAULT_INITDEAD seconds") + if ($ditvl >= $DEFAULT_INITDEAD); + return (undef, + "the current node '$my_name' is not defined in the configuration") + if (($my_name ne $primary) && ($my_name ne $secondaries[0])); + + my $monitor_str = ""; + if ($pings ne "") { + $monitor_str = "\nping $pings\n" + . "respawn hacluster /usr/lib/heartbeat/ipfail"; + } + + my $wtime = int($kitvl * 2); + if ($wtime > $ditvl) { + $wtime = $ditvl; + } + + my $str =<<EOS; +keepalive $kitvl +deadtime $ditvl +warntime $wtime +initdead $DEFAULT_INITDEAD +logfacility $DEFAULT_LOG_FACILITY +bcast $interfaces +auto_failback $auto_failback +node $primary $secondaries[0]$monitor_str +EOS + + return ($str, undef); +} + +sub isValidIPv4 { + my $str = shift; + return 0 if (!($str =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)); + my ($a1, $a2, $a3, $a4) = ($1, $2, $3, $4); + return 1 if (($a1 >= 0 && $a1 < 256) && ($a2 >= 0 && $a2 < 256) + && ($a3 >= 0 && $a3 < 256) && ($a4 >= 0 && $a4 < 256)); + return 0; +} + +my @service_list = (); +sub isValidService { + my $service = shift; + if (scalar(@service_list) == 0) { + opendir(SDIR, "$SERVICE_DIR") + or (print STDERR "Error: can't open $SERVICE_DIR" && return 0); + @service_list = grep !/^\./, readdir SDIR; + } + return 1 if (grep /^$service$/, @service_list); + return 0; +} + +sub haresources { + my ($self) = @_; + my @groups = keys %{$self->{_groups}}; + return (undef, "no resource group defined") if ($#groups < 0); + return (undef, "using multiple resource groups is not supported yet") + if ($#groups > 0); + + my $hashref = $self->{_groups}->{$groups[0]}; + my $primary = $hashref->{_primary}; + + my @init_services = (); + my @ip_addresses = (); + foreach (@{$hashref->{_service}}) { + if (!isValidIPv4($_)) { + if (isValidService($_)) { + push @init_services, $_; + } else { + return (undef, "\"$_\" is not a valid IP address or service name"); + } + } else { + push @ip_addresses, $_; + } + } + # this forces all ip addresses to be before all services, which may not + # be the desirable behavior in all cases. + my $ip_str = join " ", @ip_addresses; + my $serv_str = join " ", @init_services; + my $services = join " ", ($ip_str, $serv_str); + return (undef, "cluster primary system not defined") if (!defined($primary)); + return (undef, "cluster service(s) not defined") if ($services eq ""); + + my $str =<<EOS; +$primary $services +EOS + + return ($str, undef, @init_services); +} + +sub print_str { + my ($self) = @_; + my $str = "cluster"; + $str .= "\n interface " . (join ",", @{$self->{_interface}}); + $str .= "\n pre-shared-secret $self->{_pre_shared}"; + $str .= "\n keepalive-interval $self->{_keepalive_itvl}"; + $str .= "\n dead-interval $self->{_dead_itvl}"; + my $group; + foreach $group (keys %{$self->{_groups}}) { + $str .= "\n group $group"; + my $hashref = $self->{_groups}->{$group}; + $str .= "\n primary $hashref->{_primary}"; + $str .= "\n secondary " . (join ",", @{$hashref->{_secondary}}); + $str .= "\n monitor " . (join ",", @{$hashref->{_monitor}}); + $str .= "\n service " . (join ",", @{$hashref->{_service}}); + } + + return $str; +} + +1; + diff --git a/scripts/vyatta-show-cluster.pl b/scripts/vyatta-show-cluster.pl new file mode 100755 index 0000000..2ad29ce --- /dev/null +++ b/scripts/vyatta-show-cluster.pl @@ -0,0 +1,267 @@ +#!/usr/bin/perl + +use strict; +use lib "/opt/vyatta/share/perl5/"; +use VyattaClusterConfig; + +my $CL_STATUS = "/usr/bin/cl_status"; + +my $config = new VyattaClusterConfig; +$config->setupOrig("cluster"); +if ($config->isEmpty()) { + # config is empty. + print "Clustering is not configured\n"; + exit 0; +} + +my $primary = $config->primaryNode(); +my $secondary = $config->secondaryNode(); +my @monitors = $config->monitorNodes(); +my $services = $config->serviceStr(); + +my $pri_st = `$CL_STATUS nodestatus $primary 2>/dev/null`; +chomp $pri_st; +my $sec_st = `$CL_STATUS nodestatus $secondary 2>/dev/null`; +chomp $sec_st; +my %mon_st = (); +my $non_reach = 0; +foreach (@monitors) { + my $st = `$CL_STATUS nodestatus $_ 2>/dev/null`; + chomp $st; + if ($st ne "ping") { + $non_reach = 1; + } + $mon_st{$_} = $st; +} +my $res_st = `$CL_STATUS rscstatus 2>/dev/null`; +chomp $res_st; + +my $my_name = `uname -n`; +chomp $my_name; + +if ($my_name eq $primary) { + printPrimaryStatus(); + exit 0; +} elsif ($my_name eq $secondary) { + printSecondaryStatus(); + exit 0; +} else { + print "Error: this node ($my_name) is neither primary ($primary) " + . "nor secondary ($secondary)\n"; + exit 1; +} + +sub printPrimaryStatus { + my $all_reachable = 1; + foreach (keys %mon_st) { + if ($mon_st{$_} eq "dead") { + $all_reachable = 0; + last; + } + } + + print "=== Status report on primary node $my_name ===\n\n"; + print " Primary $primary (this node): "; + my $other_init = 0; + if ($pri_st eq "up") { + print "Started (waiting for secondary)"; + $other_init = 1; + } elsif (($res_st eq "all") || ($res_st eq "local")) { + print "Active"; + } elsif ($res_st eq "transition") { + print "Node status in transition"; + } elsif ($res_st eq "none") { + if ($pri_st eq "active") { + if ($all_reachable) { + # work around heartbeat state problem + print "Active (standby)"; + } else { + print "Down (at least 1 monitor not reachable)"; + } + } elsif ($pri_st eq "dead") { + # this should be unreachable + print "Down"; + } else { + print "Unknown"; + } + } else { + print "Unknown"; + } + print "\n\n"; + + print " Secondary $secondary: "; + if ($other_init) { + print "Initializing"; + } elsif ($res_st eq "all") { + if ($sec_st eq "active") { + # this could also be "Down (at least 1 monitor node not reachable)" + # we might want to just say "Unknown". + print "Active (standby)"; + } elsif ($sec_st eq "dead") { + print "Down"; + } elsif ($sec_st eq "up") { + print "Initializing"; + } else { + print "Unknown"; + } + } elsif ($res_st eq "local") { + if ($sec_st eq "active") { + print "Active (standby)"; + } else { + print "Unknown"; + } + } elsif ($res_st eq "transition") { + print "Node status in transition"; + } elsif ($res_st eq "none") { + if ($sec_st eq "active") { + print "Active"; + } else { + print "Unknown"; + } + } else { + print "Unknown"; + } + print "\n\n"; + + foreach (keys %mon_st) { + print " Monitor $_: "; + if ($other_init) { + print "Initializing"; + } elsif ($res_st eq "transition") { + print "Node status in transition"; + } elsif ($mon_st{$_} eq "ping") { + print "Reachable"; + } elsif ($mon_st{$_} eq "dead") { + print "Unreachable"; + } else { + print "Unknown"; + } + print "\n"; + } + print "\n"; + + print " Resources [$services]:\n "; + if ($other_init) { + print "Initializing"; + } elsif (($res_st eq "all") || ($res_st eq "local")) { + print "Active on primary $my_name (this node)"; + } elsif ($res_st eq "transition") { + print "Resource status in transition"; + } elsif ($res_st eq "none") { + print "Active on secondary $secondary"; + } else { + print "Unknown"; + } + print "\n\n"; +} + +sub printSecondaryStatus { + print "=== Status report on secondary node $my_name ===\n\n"; + my $other_init = 0; + if ($sec_st eq "up") { + $other_init = 1; + } + + my $all_reachable = 1; + foreach (keys %mon_st) { + if ($mon_st{$_} eq "dead") { + $all_reachable = 0; + last; + } + } + + print " Primary $primary: "; + if ($other_init) { + print "Initializing"; + } elsif ($res_st eq "all") { + if ($pri_st eq "active") { + # this could also be "Down (at least 1 monitor node not reachable)". + # we might want to just say "Unknown". + print "Active (standby)"; + } elsif ($pri_st eq "dead") { + print "Down"; + } elsif ($pri_st eq "up") { + print "Initializing"; + } else { + print "Unknown"; + } + } elsif ($res_st eq "local") { + if ($pri_st eq "active") { + print "Active"; + } else { + print "Unknown"; + } + } elsif ($res_st eq "transition") { + print "Node status in transition"; + } elsif ($res_st eq "none") { + if ($pri_st eq "active") { + print "Active"; + } else { + print "Unknown"; + } + } else { + print "Unknown"; + } + print "\n\n"; + + print " Secondary $my_name (this node): "; + if ($sec_st eq "up") { + print "Started (waiting for primary)"; + } elsif ($res_st eq "all") { + print "Active"; + } elsif ($res_st eq "local") { + print "Active (standby)"; + } elsif ($res_st eq "transition") { + print "Node status in transition"; + } elsif ($res_st eq "none") { + if ($sec_st eq "active") { + if ($all_reachable) { + # work around heartbeat state problem + print "Active (standby)"; + } else { + print "Down (at least 1 monitor not reachable)"; + } + } elsif ($sec_st eq "dead") { + # this should be unreachable + print "Down"; + } else { + print "Unknown"; + } + } else { + print "Unknown"; + } + print "\n\n"; + + foreach (keys %mon_st) { + print " Monitor $_: "; + if ($other_init) { + print "Initializing"; + } elsif ($res_st eq "transition") { + print "Node status in transition"; + } elsif ($mon_st{$_} eq "ping") { + print "Reachable"; + } elsif ($mon_st{$_} eq "dead") { + print "Unreachable"; + } else { + print "Unknown"; + } + print "\n"; + } + print "\n"; + + print " Resources [$services]:\n "; + if ($other_init) { + print "Initializing"; + } elsif ($res_st eq "all") { + print "Active on secondary $my_name (this node)"; + } elsif ($res_st eq "transition") { + print "Resource status in transition"; + } elsif (($res_st eq "none") || ($res_st eq "local")) { + print "Active on primary $primary"; + } else { + print "Unknown"; + } + print "\n\n"; +} + +exit 0; diff --git a/scripts/vyatta-update-cluster.pl b/scripts/vyatta-update-cluster.pl new file mode 100755 index 0000000..c90dcf8 --- /dev/null +++ b/scripts/vyatta-update-cluster.pl @@ -0,0 +1,78 @@ +#!/usr/bin/perl + +use strict; +use lib "/opt/vyatta/share/perl5/"; +use VyattaClusterConfig; + +my $HA_DIR = "/etc/ha.d"; +my $HA_INIT = "/etc/init.d/heartbeat"; +my $SERVICE_DIR = "/etc/init.d"; + +my $config = new VyattaClusterConfig; +$config->setup("cluster"); +if ($config->isEmpty()) { + # config is empty => deleted. + # shutdown clustering. + system("$HA_INIT stop"); + exit 0; +} + +open(OUT, ">>/tmp/cl.log") or exit 1; + +my ($authkeys, $haresources, $ha_cf, $err, @init_services); +while (1) { + ($authkeys, $err) = $config->authkeys(); + last if (!defined($authkeys)); + ($haresources, $err, @init_services) = $config->haresources(); + last if (!defined($haresources)); + ($ha_cf, $err) = $config->ha_cf(); + last; +} +if (defined($err)) { + print STDERR "Cluster configuration error: $err\n"; + exit 1; +} + +my $ret = system("mkdir -p $HA_DIR"); +if ($ret >> 8) { + print STDERR "Error: cannot create $HA_DIR\n"; + exit 1; +} + +if (!open(CONF_AUTH, ">$HA_DIR/authkeys")) { + print STDERR "Error: cannot create $HA_DIR/authkeys\n"; + exit 1; +} +if (!open(CONF_RES, ">$HA_DIR/haresources")) { + print STDERR "Error: cannot create $HA_DIR/haresources\n"; + exit 1; +} +if (!open(CONF_CF, ">$HA_DIR/ha.cf")) { + print STDERR "Error: cannot create $HA_DIR/ha.cf\n"; + exit 1; +} +print CONF_AUTH $authkeys; +print CONF_RES $haresources; +print CONF_CF $ha_cf; +close CONF_AUTH; +close CONF_RES; +close CONF_CF; +if (!chmod(0600, "$HA_DIR/authkeys")) { + print STDERR "Error: cannot change $HA_DIR/authkeys permissions\n"; + exit 1; +} + +# stop each service in case it is already started +foreach (@init_services) { + system("$SERVICE_DIR/$_ stop"); +} + +# restart clustering. +# using "stop" + "start" ("restart" will cause a long wait). +# (may need to change to "restart".) +system("$HA_INIT stop"); +system("$HA_INIT start"); + +close OUT; +exit 0; + diff --git a/templates-cfg/cluster/dead-interval/node.def b/templates-cfg/cluster/dead-interval/node.def new file mode 100644 index 0000000..edf946f --- /dev/null +++ b/templates-cfg/cluster/dead-interval/node.def @@ -0,0 +1,3 @@ +type: u32 +help: "How long until a node is considered dead after missing heartbeats (seconds)" +default: 20 diff --git a/templates-cfg/cluster/group/node.def b/templates-cfg/cluster/group/node.def new file mode 100644 index 0000000..641c8a7 --- /dev/null +++ b/templates-cfg/cluster/group/node.def @@ -0,0 +1,3 @@ +tag: +type: txt +help: "Name of resource group for clustering" diff --git a/templates-cfg/cluster/group/node.tag/auto-failback/node.def b/templates-cfg/cluster/group/node.tag/auto-failback/node.def new file mode 100644 index 0000000..7dac862 --- /dev/null +++ b/templates-cfg/cluster/group/node.tag/auto-failback/node.def @@ -0,0 +1,3 @@ +type: bool +default: false +help: "Fail back to primary node if it recovers from failure" diff --git a/templates-cfg/cluster/group/node.tag/monitor/node.def b/templates-cfg/cluster/group/node.tag/monitor/node.def new file mode 100644 index 0000000..fdf9437 --- /dev/null +++ b/templates-cfg/cluster/group/node.tag/monitor/node.def @@ -0,0 +1,3 @@ +multi: +type: ipv4 +help: "IP address(es) for monitoring connectivity" diff --git a/templates-cfg/cluster/group/node.tag/primary/node.def b/templates-cfg/cluster/group/node.tag/primary/node.def new file mode 100644 index 0000000..707bea9 --- /dev/null +++ b/templates-cfg/cluster/group/node.tag/primary/node.def @@ -0,0 +1,2 @@ +type: txt +help: "Host name of the primary node" diff --git a/templates-cfg/cluster/group/node.tag/secondary/node.def b/templates-cfg/cluster/group/node.tag/secondary/node.def new file mode 100644 index 0000000..63793ee --- /dev/null +++ b/templates-cfg/cluster/group/node.tag/secondary/node.def @@ -0,0 +1,3 @@ +multi: +type: txt +help: "Host name(s) of the secondary node(s)" diff --git a/templates-cfg/cluster/group/node.tag/service/node.def b/templates-cfg/cluster/group/node.tag/service/node.def new file mode 100644 index 0000000..839c702 --- /dev/null +++ b/templates-cfg/cluster/group/node.tag/service/node.def @@ -0,0 +1,3 @@ +multi: +type: txt +help: "IP address(es) or service name(s) in this resource group" diff --git a/templates-cfg/cluster/interface/node.def b/templates-cfg/cluster/interface/node.def new file mode 100644 index 0000000..448ed6a --- /dev/null +++ b/templates-cfg/cluster/interface/node.def @@ -0,0 +1,3 @@ +multi: +type: txt +help: "Interface(s) for sending/receiving heartbeat packets" diff --git a/templates-cfg/cluster/keepalive-interval/node.def b/templates-cfg/cluster/keepalive-interval/node.def new file mode 100644 index 0000000..525dbce --- /dev/null +++ b/templates-cfg/cluster/keepalive-interval/node.def @@ -0,0 +1,3 @@ +type: u32 +help: "Time interval between heartbeat packets (seconds)" +default: 5 diff --git a/templates-cfg/cluster/node.def b/templates-cfg/cluster/node.def new file mode 100644 index 0000000..b2cacb1 --- /dev/null +++ b/templates-cfg/cluster/node.def @@ -0,0 +1,2 @@ +help: "Configure clustering" +end: "sudo /opt/vyatta/sbin/vyatta-update-cluster.pl" diff --git a/templates-cfg/cluster/pre-shared-secret/node.def b/templates-cfg/cluster/pre-shared-secret/node.def new file mode 100644 index 0000000..a70da79 --- /dev/null +++ b/templates-cfg/cluster/pre-shared-secret/node.def @@ -0,0 +1,2 @@ +type: txt +help: "Pre-shared secret for authentication between cluster nodes" diff --git a/templates-op/show/cluster/node.def b/templates-op/show/cluster/node.def new file mode 100644 index 0000000..a4386d8 --- /dev/null +++ b/templates-op/show/cluster/node.def @@ -0,0 +1 @@ +help: Show clustering information diff --git a/templates-op/show/cluster/status/node.def b/templates-op/show/cluster/status/node.def new file mode 100644 index 0000000..a9ff771 --- /dev/null +++ b/templates-op/show/cluster/status/node.def @@ -0,0 +1,2 @@ +help: Show the current clustering status +run: sudo /opt/vyatta/sbin/vyatta-show-cluster.pl |