summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore20
-rw-r--r--AUTHORS1
-rw-r--r--COPYING26
-rw-r--r--Makefile.am17
-rw-r--r--NEWS1
-rw-r--r--README1
-rw-r--r--configure.ac30
-rw-r--r--debian/README6
-rwxr-xr-xdebian/autogen.sh37
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control14
-rw-r--r--debian/copyright34
-rw-r--r--debian/docs2
-rw-r--r--debian/linda1
-rw-r--r--debian/lintian2
-rwxr-xr-xdebian/rules102
-rw-r--r--debian/vyatta-cluster.postinst.in4
-rw-r--r--scripts/VyattaClusterConfig.pm312
-rwxr-xr-xscripts/vyatta-show-cluster.pl267
-rwxr-xr-xscripts/vyatta-update-cluster.pl78
-rw-r--r--templates-cfg/cluster/dead-interval/node.def3
-rw-r--r--templates-cfg/cluster/group/node.def3
-rw-r--r--templates-cfg/cluster/group/node.tag/auto-failback/node.def3
-rw-r--r--templates-cfg/cluster/group/node.tag/monitor/node.def3
-rw-r--r--templates-cfg/cluster/group/node.tag/primary/node.def2
-rw-r--r--templates-cfg/cluster/group/node.tag/secondary/node.def3
-rw-r--r--templates-cfg/cluster/group/node.tag/service/node.def3
-rw-r--r--templates-cfg/cluster/interface/node.def3
-rw-r--r--templates-cfg/cluster/keepalive-interval/node.def3
-rw-r--r--templates-cfg/cluster/node.def2
-rw-r--r--templates-cfg/cluster/pre-shared-secret/node.def2
-rw-r--r--templates-op/show/cluster/node.def1
-rw-r--r--templates-op/show/cluster/status/node.def2
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
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..ee635b2
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+eng@vyatta.com
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..c1e79ac
--- /dev/null
+++ b/COPYING
@@ -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)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..78fdaa6
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+see http://www.vyatta.com/news/
diff --git a/README b/README
new file mode 100644
index 0000000..41ab9d8
--- /dev/null
+++ b/README
@@ -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