summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Lucina <martin@lucina.net>2011-11-09 18:51:46 +0100
committerMartin Lucina <martin@lucina.net>2011-11-09 18:51:46 +0100
commitff7caa01f47df249066169daa314664c3669842e (patch)
tree0b7750d6c7056e191260afe3c90aef4c5c26a313
Initial commit
-rw-r--r--.gitignore5
-rw-r--r--LICENSE19
-rw-r--r--Makefile13
-rw-r--r--README.markdown15
-rwxr-xr-xconsole.rb474
-rw-r--r--db.test13
-rw-r--r--monitor.c175
-rw-r--r--myclient.c78
-rw-r--r--myservice.c70
-rw-r--r--named.conf.add12
-rw-r--r--notifier.c145
-rwxr-xr-xnsupdate-script32
-rw-r--r--nsupdate.key7
-rw-r--r--nsupdate.test.key1
-rw-r--r--nsupdate.test.private7
-rwxr-xr-xtest-fakehost.rb54
-rwxr-xr-xtest-notifier.rb55
-rwxr-xr-xtest-wdclient.rb51
-rwxr-xr-xwatchdog.rb121
19 files changed, 1347 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..229cce2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+monitor
+myclient
+myservice
+notifier
+.*.swp
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b5241f5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2011 VMware, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7b31b05
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+all: myclient myservice monitor notifier
+
+myclient: myclient.c
+myservice: myservice.c
+monitor: monitor.c
+notifier: notifier.c
+
+clean:
+ $(RM) myclient myservice monitor notifier
+
+CFLAGS=-Wall
+LDFLAGS=-lzmq
+
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..f0aa46a
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,15 @@
+# Flexible topology demonstration
+
+This is a demonstration of transparently modifying a topology using ZeroMQ and
+DNS. More documentation is forthcoming, once I get around to translating it
+from the original OOo document.
+
+Prerequisites needed to get this working:
+
+- zeromq built with `dns://` support, from the `dns` branch of
+ https://github.com/mato/libzmq
+- a machine you can run a test installation of BIND 9 on
+- Ruby 1.9
+- ffi-rzmq from https://github.com/chuckremes/ffi-rzmq/
+
+Martin Lucina <martin@lucina.net>, November 2011
diff --git a/console.rb b/console.rb
new file mode 100755
index 0000000..6589b8a
--- /dev/null
+++ b/console.rb
@@ -0,0 +1,474 @@
+#!/usr/bin/env ruby
+#
+# console.rb: Console GUI component.
+#
+# Copyright (C) 2011 VMware, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# Apologies in advance for the state of this code, my Ruby/Tk-fu is really not
+# up to scratch. The liberal use of $global_variables is to get things working
+# correctly from Tk callbacks, if you know a better way to make this work
+# please let me know.
+
+require 'tk'
+require 'ffi-rzmq'
+require 'thread'
+
+# Convenience method to raise exception on ffi-rzmq error
+def raise_if_error(rc)
+ unless ZMQ::Util.resultcode_ok?(rc)
+ raise "ZMQ Error: #{ZMQ::Util.error_string}"
+ end
+end
+
+# Callable on any Tk widget, adds get/set methods for mytag and mytype,
+# allowing us to keep track of what is what when we get callback events.
+def make_taggable(widget)
+ widget.instance_eval do
+ @mytag = nil
+ @mytype = nil
+ def mytag
+ @mytag
+ end
+ def mytag=(tag)
+ @mytag = tag
+ end
+ def mytype
+ @mytype
+ end
+ def mytype=(type)
+ @mytype = type
+ end
+ end
+end
+
+# Build out the GUI.
+def build_gui
+ $root = TkRoot.new { title "Console" }
+ $workspace = TkFrame.new($root) {
+ relief 'sunken'
+ borderwidth '2'
+ }
+ $workspace.grid :column => 0, :row => 0, :columnspan => 2, :sticky => 'nsew'
+ $buttons = TkFrame.new($root)
+ $buttons.grid :column => 0, :row => 1, :columnspan => 2, :sticky => 'nsew'
+ $start = TkButton.new($buttons) {
+ text 'Start'
+ state :disabled
+ command proc {
+ host = $selected_item.mytag
+ start_service_dialog(host)
+ }
+ }
+ $start.pack :padx => 10, :side => :left
+ $stop = TkButton.new($buttons) {
+ text 'Stop'
+ state :disabled
+ command proc {
+ service = $selected_item.mytag
+ host = $selected_item.winfo_parent.mytag
+ result = Tk::messageBox :type => :yesno,
+ :message => "Are you sure you wish to stop the service " + \
+ "'#{service}' on host '#{host}'?",
+ :icon => :question,
+ :title => "Stop service"
+ stop_service(service, host) if result == 'yes'
+ }
+ }
+ $stop.pack :padx => 10, :side => :left
+ $restart = TkButton.new($buttons) {
+ text 'Restart'
+ state :disabled
+ command proc {
+ service = $selected_item.mytag
+ host = $selected_item.winfo_parent.mytag
+ restart_service_dialog(host, service)
+ }
+ }
+ $restart.pack :padx => 10, :side => :left
+ $dns = TkButton.new($buttons) {
+ text 'Update DNS'
+ command proc {
+ update_dns_dialog()
+ }
+ }
+ $dns.pack :padx => 10, :side => :left
+ $exit = TkButton.new($buttons) {
+ text 'Exit'
+ command proc {
+ result = Tk::messageBox :type => :yesno,
+ :message => "Are you sure you want to exit?",
+ :icon => :question,
+ :title => "Confirm exit"
+ exit if result == 'yes'
+ }
+ }
+ $exit.pack :side => :left
+ $console = TkListbox.new($root) {
+ height 10
+ width 60
+ yscrollcommand proc { |*args| $console_sb.set(*args) }
+ }
+ $console.grid :column => 0, :row => 2, :sticky => 'nwes'
+ $console_sb = TkScrollbar.new($root) {
+ orient :vertical
+ command proc { |*args| $console.yview(*args) }
+ }
+ $console_sb.grid :column => 1, :row => 2, :sticky => 'ns'
+ TkGrid.columnconfigure $root, 0, :weight => 1, :minsize => 600
+ TkGrid.rowconfigure $root, 0, :weight => 1, :minsize => 400
+end
+
+# DNS Update dialog GUI
+def update_dns_dialog
+ w = TkToplevel.new { title "Update DNS record" }
+ w.wm_transient($root)
+ l = TkLabel.new(w) { text "Record" }
+ r = TkEntry.new(w) { width 15 }
+ r.focus
+ l2 = TkLabel.new(w) { text "Value" }
+ v = TkEntry.new(w) { width 15 }
+ ok = TkButton.new(w) {
+ text "OK"
+ command proc {
+ if r.get.length > 0 && v.get.length > 0
+ result = `./nsupdate-script #{r.get} #{v.get}`
+ result.chomp
+ console_add("Updating DNS record '#{r.get}' to value '#{v.get}': #{result}")
+ w.destroy
+ end
+ }
+ default :active
+ }
+ cancel = TkButton.new(w) { text "Cancel"; command proc { w.destroy } }
+ w.bind('Return') { ok.invoke }
+ w.bind('Escape') { cancel.invoke }
+ l.grid :column => 0, :row => 0, :padx => 10, :pady => 10
+ r.grid :column => 1, :row => 0, :padx => 10, :pady => 10
+ l2.grid :column => 0, :row => 1, :padx => 10, :pady => 10
+ v.grid :column => 1, :row => 1, :padx => 10, :pady => 10
+ ok.grid :column => 0, :row => 2, :padx => 10, :pady => 10
+ cancel.grid :column => 1, :row => 2, :padx => 10, :pady => 10
+ w.grab
+ w.tkwait
+end
+
+# Start Service dialog GUI
+def start_service_dialog(host)
+ w = TkToplevel.new { title "Start service on #{host}" }
+ w.wm_transient($root)
+ l = TkLabel.new(w) { text "Command" }
+ e = TkEntry.new(w) { width 40 }
+ e.focus
+ ok = TkButton.new(w) {
+ text "OK"
+ command proc {
+ if e.get.length > 0
+ start_service(host, e.get)
+ w.destroy
+ end
+ }
+ default :active
+ }
+ cancel = TkButton.new(w) { text "Cancel"; command proc { w.destroy } }
+ w.bind('Return') { ok.invoke }
+ w.bind('Escape') { cancel.invoke }
+ l.grid :column => 0, :row => 0, :padx => 10, :pady => 10
+ e.grid :column => 1, :row => 0, :padx => 10, :pady => 10
+ ok.grid :column => 0, :row => 1, :padx => 10, :pady => 10
+ cancel.grid :column => 1, :row => 1, :padx => 10, :pady => 10
+ w.grab
+ w.tkwait
+end
+
+# Restart service dialog GUI
+def restart_service_dialog(host, service)
+ w = TkToplevel.new { title "Restart service #{service} on #{host}" }
+ w.wm_transient($root)
+ l = TkLabel.new(w) { text "Command" }
+ e = TkEntry.new(w) { width 40 }
+ e.set($hosts[host][:services][service][:command])
+ e.focus
+ ok = TkButton.new(w) {
+ text "OK"
+ command proc {
+ if e.get.length > 0
+ stop_service(service, host)
+ start_service(host, e.get)
+ w.destroy
+ end
+ }
+ default :active
+ }
+ cancel = TkButton.new(w) { text "Cancel"; command proc { w.destroy } }
+ w.bind('Return') { ok.invoke }
+ w.bind('Escape') { cancel.invoke }
+ l.grid :column => 0, :row => 0, :padx => 10, :pady => 10
+ e.grid :column => 1, :row => 0, :padx => 10, :pady => 10
+ ok.grid :column => 0, :row => 1, :padx => 10, :pady => 10
+ cancel.grid :column => 1, :row => 1, :padx => 10, :pady => 10
+ w.grab
+ w.tkwait
+end
+
+# Start service 'command' on 'host'
+def start_service(host, command)
+ console_add("Starting command '#{command}' on host #{host}");
+ s = $ctx.socket(ZMQ::PUSH)
+ rc = s.connect("tcp://#{host}:7772")
+ raise_if_error(rc)
+ rc = s.send_string("START #{command}")
+ raise_if_error(rc)
+ rc = s.close
+ raise_if_error(rc)
+end
+
+# Stop service 'service' on 'host'
+def stop_service(service, host)
+ console_add("Stopping service: #{service} on host: #{host}")
+ s = $ctx.socket(ZMQ::PUSH)
+ rc = s.connect("tcp://#{host}:7772")
+ raise_if_error(rc)
+ rc = s.send_string("STOP #{service}")
+ raise_if_error(rc)
+ rc = s.close
+ raise_if_error(rc)
+end
+
+# Log text to console widget
+def console_add(msg)
+ $console.insert 'end', Time.now.to_s + ' ' + msg
+ $console.yview 'end'
+end
+
+# Select an item in the console, highlights it and updates available
+# action buttons
+def select_item(new)
+ old = $selected_item
+ if old
+ target = old.labelwidget if old.mytype == :host
+ target = old if old.mytype == :service
+ target.background('#d9d9d9')
+ end
+ if new.mytype == :host
+ new.labelwidget.background('green')
+ $start.state(:normal)
+ $stop.state(:disabled)
+ $restart.state(:disabled)
+ elsif new.mytype == :service
+ new.background('green')
+ $start.state(:disabled)
+ $stop.state(:normal)
+ $restart.state(:normal)
+ end
+ $selected_item = new
+end
+
+# Run periodically from Tk Timer to pull interesting messages from notifier
+# and update the GUI accordingly.
+def update
+ begin
+ msg = ''
+ rc = 0
+ loop do
+ $semaphore.synchronize do
+ rc = $notifier_sock.recv_string(msg, ZMQ::DONTWAIT)
+ end
+ if rc < 0
+ break if ZMQ::Util.errno == ZMQ::EAGAIN
+ raise_if_error(rc)
+ end
+ if msg =~ /^WATCHDOG (\w+) (\w+) ([\w\.]+) ?(.*)?$/
+ verb = $1
+ host = $2
+ timestamp = $3.to_f
+ command = $4
+ if verb == 'ALIVE'
+ if $hosts.has_key?(host)
+ $hosts[host][:last_seen] = timestamp
+ else
+ label = TkLabel.new($workspace) { text host }
+ make_taggable(label)
+ label.mytag = host
+ label.mytype = :host
+ host_widget = TkLabelFrame.new($workspace) {
+ labelwidget label
+ width '4c'
+ height '4c'
+ relief 'raised'
+ padx 10
+ pady 10
+ }
+ make_taggable(host_widget)
+ host_widget.mytag = host
+ host_widget.mytype = :host
+ host_widget.pack :side => :left,:anchor => 'nw',
+ :padx => 10, :pady => 10
+ host_widget.cursor("hand2")
+ label.cursor("hand2")
+ label.bind("1", proc { |event|
+ host = event.widget.mytag
+ real_widget = $hosts[host][:widget]
+ select_item(real_widget)
+ console_add("Selected host: #{host}")
+ })
+ host_widget.bind("1", proc { |event|
+ select_item(event.widget)
+ console_add("Selected host: #{event.widget.mytag}")
+ })
+ $hosts[host] = {}
+ $hosts[host][:widget] = host_widget
+ $hosts[host][:services] = {}
+ $hosts[host][:last_seen] = timestamp
+ console_add("Registered host: #{host}")
+ end
+ elsif verb == 'RUNNING'
+ service = command.split()[0]
+ svcs = $hosts[host][:services]
+ if svcs.has_key?(service)
+ svcs[service][:last_seen] = timestamp
+ else
+ svcs[service] = {}
+ svcs[service][:command] = command
+ svcs[service][:last_seen] = timestamp
+ host_widget = $hosts[host][:widget]
+ service_widget = TkLabel.new(host_widget) {
+ text service
+ padx '0.25c'
+ pady '0.25c'
+ }
+ make_taggable(service_widget)
+ service_widget.mytype = :service
+ service_widget.mytag = service
+ service_widget.pack { fill 'both' }
+ service_widget.cursor("hand2")
+ service_widget.bind("1", proc { |event|
+ select_item(event.widget)
+ console_add("Selected service: #{event.widget.mytag}")
+ })
+ svcs[service][:widget] = service_widget
+ console_add("Registered service: #{service} on host: #{host}")
+ end
+ end
+ elsif msg =~ /^VALUE (\w+) (\w+) (.*)$/
+ host = $1
+ service = $2
+ value = $3
+ if $hosts.has_key?(host) && $hosts[host][:services].has_key?(service)
+ widget = $hosts[host][:services][service][:widget]
+ widget.text(widget.mytag + "\n" + value)
+ end
+ end
+ end
+ rescue => exception
+ print exception, "\n"
+ print exception.backtrace.join("\n\t from "), "\n"
+ Process.abort
+ end
+end
+
+# Run periodically from Tk Timer to reap dead hosts/services from GUI.
+def reaper
+ begin
+ current_time = Time.now.to_f
+ $hosts.each do |key, host|
+ if current_time - host[:last_seen] > 2
+ host[:services].each do |key, service|
+ if $selected_item == service[:widget]
+ $start.state(:disabled)
+ $stop.state(:disabled)
+ $restart.state(:disabled)
+ $selected_item = nil
+ end
+ service[:widget].destroy
+ host[:services].delete(key)
+ end
+ if $selected_item == host[:widget]
+ $start.state(:disabled)
+ $stop.state(:disabled)
+ $restart.state(:disabled)
+ $selected_item = nil
+ end
+ host[:widget].destroy
+ $hosts.delete(key)
+ console_add("Removed host: #{key}")
+ else
+ host[:services].each do |key, service|
+ if current_time - service[:last_seen] > 2
+ if $selected_item == service[:widget]
+ $start.state(:disabled)
+ $stop.state(:disabled)
+ $restart.state(:disabled)
+ $selected_item = nil
+ end
+ service[:widget].destroy
+ host[:services].delete(key)
+ console_add("Removed service: #{key}")
+ end
+ end
+ end
+ end
+ rescue => exception
+ print exception, "\n"
+ print exception.backtrace.join("\n\t from "), "\n"
+ Process.abort
+ end
+end
+
+#
+# Main program
+#
+
+# Globals used by GUI
+# ZMQ context
+$ctx = ZMQ::Context.new()
+# Currently selected item (host/service)
+$selected_item = nil
+# Map of hosts and their services
+$hosts = {}
+
+# Parse arguments
+unless ARGV[0]
+ print "usage: console.rb <notifier-host>\n"
+ exit 1
+end
+notifier_ep = "tcp://#{ARGV[0]}:7771"
+
+# Create and connect to notifier socket. Synchronized to allow migration to
+# GUI callback thread.
+$semaphore = Mutex.new
+$semaphore.synchronize do
+ $notifier_sock = $ctx.socket(ZMQ::SUB)
+ rc = $notifier_sock.setsockopt(ZMQ::SUBSCRIBE, '')
+ raise_if_error(rc)
+ rc = $notifier_sock.connect(notifier_ep)
+ raise_if_error(rc)
+end
+
+# Build and update GUI immediately on startup
+build_gui
+update
+
+# Setup timers and go into Tk mainloop
+# Update GUI/reap dead objects every 500ms
+TkTimer.new(500, -1, proc { update }).start
+TkTimer.new(500, -1, proc { reaper }).start
+Tk.mainloop
+
diff --git a/db.test b/db.test
new file mode 100644
index 0000000..c2e2879
--- /dev/null
+++ b/db.test
@@ -0,0 +1,13 @@
+$ORIGIN .
+$TTL 300 ; 5 minutes
+test IN SOA localhost. root.localhost. (
+ 1 ; serial
+ 604800 ; refresh (1 week)
+ 86400 ; retry (1 day)
+ 2419200 ; expire (4 weeks)
+ 300 ; minimum (5 minutes)
+ )
+ NS localhost.
+$ORIGIN test.
+$TTL 10 ; 10 seconds
+myservice TXT ""
diff --git a/monitor.c b/monitor.c
new file mode 100644
index 0000000..25c7867
--- /dev/null
+++ b/monitor.c
@@ -0,0 +1,175 @@
+/* monitor.c: Monitoring component.
+ *
+ * Copyright (C) 2011 VMware, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <zmq.h>
+
+#define ERR_EXIT(what) { \
+ fprintf (stderr, "error in %s: %s\n", what, zmq_strerror (errno)); \
+ exit (EXIT_FAILURE); \
+ }
+
+int main (int argc, char *argv [])
+{
+ void *ctx;
+ const char *client_ep, *service_ep, *notifier_host;
+ char notifier_ep[1024];
+ char msg_buf[1024];
+ char hostname[1024];
+ void *client_socket, *service_socket, *notifier_socket;
+ int rc;
+
+ if (argc != 4) {
+ fprintf (stderr,
+ "usage: monitor "\
+ "<notifier-host> <client-bind-ep> <service-connect-ep>\n");
+ exit (EXIT_FAILURE);
+ }
+ notifier_host = argv [1];
+ sprintf (notifier_ep, "tcp://%s:7770", notifier_host);
+ client_ep = argv [2];
+ service_ep = argv [3];
+
+ rc = gethostname(hostname, sizeof hostname);
+ assert (rc == 0);
+
+ ctx = zmq_init (1);
+ if (!ctx)
+ ERR_EXIT ("zmq_init");
+
+ client_socket = zmq_socket (ctx, ZMQ_XPUB);
+ if (!client_socket)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_bind (client_socket, client_ep);
+ if (rc < 0)
+ ERR_EXIT ("zmq_bind");
+
+ service_socket = zmq_socket (ctx, ZMQ_XSUB);
+ if (!service_socket)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_connect (service_socket, service_ep);
+ if (rc < 0)
+ ERR_EXIT ("zmq_connect");
+
+ notifier_socket = zmq_socket (ctx, ZMQ_PUB);
+ if (!notifier_socket)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_connect (notifier_socket, notifier_ep);
+ if (rc < 0)
+ ERR_EXIT ("zmq_bind");
+
+ int64_t more;
+ size_t moresz;
+ uint32_t seq_no = 0;
+
+ zmq_msg_t msg;
+ rc = zmq_msg_init (&msg);
+ assert (rc == 0);
+
+ zmq_pollitem_t items [2];
+ items [0].socket = client_socket;
+ items [0].fd = 0;
+ items [0].events = ZMQ_POLLIN;
+ items [0].revents = 0;
+ items [1].socket = service_socket;
+ items [1].fd = 0;
+ items [1].events = ZMQ_POLLIN;
+ items [1].revents = 0;
+
+ while (1) {
+
+ /* Wait for either upstream subscriptions or messages. */
+ rc = zmq_poll (&items [0], 2, -1);
+ if (rc < 0)
+ ERR_EXIT ("zmq_poll");
+
+ /* Process an upstream subscription. */
+ if (items [0].revents & ZMQ_POLLIN) {
+ while (1) {
+
+ rc = zmq_recvmsg (client_socket, &msg, 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_recvmsg");
+
+ moresz = sizeof (more);
+ rc = zmq_getsockopt (client_socket, ZMQ_RCVMORE, &more, &moresz);
+ if (rc < 0)
+ ERR_EXIT ("zmq_getsockopt");
+
+ rc = zmq_sendmsg (service_socket, &msg, more ? ZMQ_SNDMORE : 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_sendmsg");
+
+ if (!more)
+ break;
+ }
+ }
+
+ /* Process a downstream message. */
+ if (items [1].revents & ZMQ_POLLIN) {
+ while (1) {
+
+ rc = zmq_recvmsg (service_socket, &msg, 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_recvmsg");
+
+ moresz = sizeof (more);
+ rc = zmq_getsockopt (service_socket, ZMQ_RCVMORE, &more, &moresz);
+ if (rc < 0)
+ ERR_EXIT ("zmq_getsockopt");
+
+ rc = zmq_sendmsg (client_socket, &msg, more ? ZMQ_SNDMORE : 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_sendmsg");
+
+ if (!more) {
+ /* For each complete message, publish the sequence number
+ (# of messages sent) to the notifier socket. */
+ uint32_t seq_no_wire;
+
+ seq_no++;
+ seq_no_wire = htonl (seq_no);
+ sprintf(msg_buf, "VALUE %s monitor %u", hostname, seq_no);
+ rc = zmq_send (notifier_socket, msg_buf, strlen(msg_buf), 0);
+ if (rc != strlen(msg_buf))
+ ERR_EXIT ("zmq_send");
+
+ break;
+ }
+ }
+ }
+
+ }
+
+ exit (EXIT_SUCCESS);
+}
+
diff --git a/myclient.c b/myclient.c
new file mode 100644
index 0000000..5eba6a7
--- /dev/null
+++ b/myclient.c
@@ -0,0 +1,78 @@
+/* myclient.c: "External" client component.
+ *
+ * Copyright (C) 2011 VMware, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <zmq.h>
+#include <zmq_utils.h>
+
+#define ERR_EXIT(what) { \
+ fprintf (stderr, "error in %s: %s\n", what, zmq_strerror (errno)); \
+ exit (EXIT_FAILURE); \
+ }
+
+int main (int argc, char *argv [])
+{
+ const char *connect_to = "dns://myservice.test";
+ void *ctx;
+ void *s;
+ int rc;
+ zmq_msg_t msg;
+
+ if (argc != 1) {
+ fprintf (stderr, "usage: myclient\n");
+ exit (EXIT_FAILURE);
+ }
+
+ ctx = zmq_init (1);
+ if (!ctx)
+ ERR_EXIT ("zmq_init");
+
+ s = zmq_socket (ctx, ZMQ_SUB);
+ if (!s)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_setsockopt (s, ZMQ_SUBSCRIBE, "", 0);
+ if (rc != 0)
+ ERR_EXIT ("zmq_setsockopt");
+
+ rc = zmq_connect (s, connect_to);
+ if (rc != 0)
+ ERR_EXIT ("zmq_connect");
+
+ rc = zmq_msg_init (&msg);
+ if (rc != 0)
+ ERR_EXIT ("zmq_msg_init");
+
+ for (;;) {
+ rc = zmq_recvmsg (s, &msg, 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_recvmsg");
+
+ printf ("+");
+ fflush (stdout);
+ }
+
+ exit (EXIT_SUCCESS);
+}
+
diff --git a/myservice.c b/myservice.c
new file mode 100644
index 0000000..89a031b
--- /dev/null
+++ b/myservice.c
@@ -0,0 +1,70 @@
+/* myservice.c: Service (publisher) component.
+ *
+ * Copyright (C) 2011 VMware, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <zmq.h>
+
+#define ERR_EXIT(what) { \
+ fprintf (stderr, "error in %s: %s\n", what, zmq_strerror (errno)); \
+ exit (EXIT_FAILURE); \
+ }
+
+int main (int argc, char *argv [])
+{
+ const char *bind_to;
+ const char *message = "MESSAGE";
+ void *ctx;
+ void *s;
+ int rc;
+
+ if (argc != 2) {
+ fprintf (stderr, "usage: myservice <service-bind-ep>\n");
+ exit (EXIT_FAILURE);
+ }
+ bind_to = argv [1];
+
+ ctx = zmq_init (1);
+ if (!ctx)
+ ERR_EXIT ("zmq_init");
+
+ s = zmq_socket (ctx, ZMQ_PUB);
+ if (!s)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_bind (s, bind_to);
+ if (rc != 0)
+ ERR_EXIT ("zmq_bind");
+
+ for (;;) {
+ rc = zmq_send (s, message, sizeof message, 0);
+ if (rc != sizeof message)
+ ERR_EXIT ("zmq_send");
+
+ sleep (1);
+ }
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/named.conf.add b/named.conf.add
new file mode 100644
index 0000000..a216588
--- /dev/null
+++ b/named.conf.add
@@ -0,0 +1,12 @@
+key test {
+ algorithm HMAC-MD5;
+ secret "iAKtzkTWm+dOJjjNEHSHMTivZe6vtzzlRfFoP4yM20oCEWRrt6oW+XhzU1b3NJETErQo3xNALU6gsPHGzGYghg==";
+};
+
+zone "test" {
+ type master;
+ file "db.test";
+ update-policy {
+ grant test name myservice.test. TXT;
+ };
+};
diff --git a/notifier.c b/notifier.c
new file mode 100644
index 0000000..775cf50
--- /dev/null
+++ b/notifier.c
@@ -0,0 +1,145 @@
+/* monitor.c: Notifier (console mini-broker) component.
+ *
+ * Copyright (C) 2011 VMware, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <zmq.h>
+
+#define ERR_EXIT(what) { \
+ fprintf (stderr, "error in %s: %s\n", what, zmq_strerror (errno)); \
+ exit (EXIT_FAILURE); \
+ }
+
+int main (int argc, char *argv [])
+{
+ void *ctx;
+ const char *app_ep = "tcp://*:7770",
+ *console_ep = "tcp://*:7771";
+ void *app_socket, *console_socket;
+ int rc;
+
+ if (argc != 1) {
+ fprintf (stderr, "usage: notifier\n");
+ fprintf (stderr, " binds to ports 7770 (application port)\n");
+ fprintf (stderr, " and 7771 (console port)\n");
+ exit (EXIT_FAILURE);
+ }
+
+ ctx = zmq_init (1);
+ if (!ctx)
+ ERR_EXIT ("zmq_init");
+
+ app_socket = zmq_socket (ctx, ZMQ_XSUB);
+ if (!app_socket)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_bind (app_socket, app_ep);
+ if (rc < 0)
+ ERR_EXIT ("zmq_bind");
+
+ console_socket = zmq_socket (ctx, ZMQ_XPUB);
+ if (!console_socket)
+ ERR_EXIT ("zmq_socket");
+
+ rc = zmq_bind (console_socket, console_ep);
+ if (rc < 0)
+ ERR_EXIT ("zmq_bind");
+
+ int64_t more;
+ size_t moresz;
+
+ zmq_msg_t msg;
+ rc = zmq_msg_init (&msg);
+ assert (rc == 0);
+
+ zmq_pollitem_t items [2];
+ items [0].socket = app_socket;
+ items [0].fd = 0;
+ items [0].events = ZMQ_POLLIN;
+ items [0].revents = 0;
+ items [1].socket = console_socket;
+ items [1].fd = 0;
+ items [1].events = ZMQ_POLLIN;
+ items [1].revents = 0;
+
+ while (1) {
+
+ /* Wait for either upstream subscriptions or messages. */
+ rc = zmq_poll (&items [0], 2, -1);
+ if (rc < 0)
+ ERR_EXIT ("zmq_poll");
+
+ /* Process an upstream subscription. */
+ if (items [0].revents & ZMQ_POLLIN) {
+ while (1) {
+
+ rc = zmq_recvmsg (app_socket, &msg, 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_recvmsg");
+
+ moresz = sizeof (more);
+ rc = zmq_getsockopt (app_socket, ZMQ_RCVMORE, &more, &moresz);
+ if (rc < 0)
+ ERR_EXIT ("zmq_getsockopt");
+
+ rc = zmq_sendmsg (console_socket, &msg, more ? ZMQ_SNDMORE : 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_sendmsg");
+
+ if (!more)
+ break;
+ }
+ }
+
+ /* Process a downstream message. */
+ if (items [1].revents & ZMQ_POLLIN) {
+ while (1) {
+
+ rc = zmq_recvmsg (console_socket, &msg, 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_recvmsg");
+
+ moresz = sizeof (more);
+ rc = zmq_getsockopt (console_socket, ZMQ_RCVMORE, &more, &moresz);
+ if (rc < 0)
+ ERR_EXIT ("zmq_getsockopt");
+
+ rc = zmq_sendmsg (app_socket, &msg, more ? ZMQ_SNDMORE : 0);
+ if (rc < 0)
+ ERR_EXIT ("zmq_sendmsg");
+
+ if (!more) {
+ break;
+ }
+ }
+ }
+
+ }
+
+ exit (EXIT_SUCCESS);
+}
+
diff --git a/nsupdate-script b/nsupdate-script
new file mode 100755
index 0000000..023b161
--- /dev/null
+++ b/nsupdate-script
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# nsupdate-script does the actuall call to 'nsupdate'. It is called by the
+# console and returns 'OK' or 'ERROR' to standard output.
+#
+# Edit the address below to point to your DNS server IP.
+#
+DNS_SERVER="192.168.1.11"
+#
+#
+#
+
+if [ $# -ne 2 ]; then
+ echo "usage: nsupdate-script <record> <value>" 1>&2
+ exit 1
+fi
+RECORD=$1
+VALUE=$2
+
+nsupdate -k ./nsupdate.test <<EOT
+server ${DNS_SERVER}
+update delete ${RECORD} TXT
+update add ${RECORD} 10 TXT "${VALUE}"
+send
+EOT
+
+if [ $? -ne 0 ]; then
+ echo "ERROR"
+ exit 1
+fi
+echo "OK"
+exit 0
diff --git a/nsupdate.key b/nsupdate.key
new file mode 100644
index 0000000..bb2923c
--- /dev/null
+++ b/nsupdate.key
@@ -0,0 +1,7 @@
+Private-key-format: v1.3
+Algorithm: 157 (HMAC_MD5)
+Key: iAKtzkTWm+dOJjjNEHSHMTivZe6vtzzlRfFoP4yM20oCEWRrt6oW+XhzU1b3NJETErQo3xNALU6gsPHGzGYghg==
+Bits: AAA=
+Created: 20111019163300
+Publish: 20111019163300
+Activate: 20111019163300
diff --git a/nsupdate.test.key b/nsupdate.test.key
new file mode 100644
index 0000000..3a418a5
--- /dev/null
+++ b/nsupdate.test.key
@@ -0,0 +1 @@
+test. IN KEY 512 3 157 iAKtzkTWm+dOJjjNEHSHMTivZe6vtzzlRfFoP4yM20oCEWRrt6oW+Xhz U1b3NJETErQo3xNALU6gsPHGzGYghg==
diff --git a/nsupdate.test.private b/nsupdate.test.private
new file mode 100644
index 0000000..bb2923c
--- /dev/null
+++ b/nsupdate.test.private
@@ -0,0 +1,7 @@
+Private-key-format: v1.3
+Algorithm: 157 (HMAC_MD5)
+Key: iAKtzkTWm+dOJjjNEHSHMTivZe6vtzzlRfFoP4yM20oCEWRrt6oW+XhzU1b3NJETErQo3xNALU6gsPHGzGYghg==
+Bits: AAA=
+Created: 20111019163300
+Publish: 20111019163300
+Activate: 20111019163300
diff --git a/test-fakehost.rb b/test-fakehost.rb
new file mode 100755
index 0000000..2fa0a32
--- /dev/null
+++ b/test-fakehost.rb
@@ -0,0 +1,54 @@
+#!/usr/bin/env ruby
+#
+# test-fakehost.rb: Simulate a fake host to excersize console GUI.
+#
+# Copyright (C) 2011 VMware, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+require 'ffi-rzmq'
+
+trap("INT") { exit }
+trap("TERM") { exit }
+def raise_if_error(rc)
+ unless ZMQ::Util.resultcode_ok?(rc)
+ raise "ZMQ Error: #{ZMQ::Util.error_string}"
+ end
+end
+
+if ARGV.length != 2
+ print "usage: test-fakehost.rb <notifier-connect-ep> <fake-host-name>\n"
+ exit 1
+end
+
+notifier_ep = ARGV.shift
+host = ARGV.shift
+
+ctx = ZMQ::Context.new()
+notifier_sock = ctx.socket(ZMQ::PUB)
+rc = notifier_sock.connect(notifier_ep)
+raise_if_error(rc)
+
+loop do
+ status = "WATCHDOG ALIVE #{host} " + Time.now.to_f.to_s
+ rc = notifier_sock.send_string(status)
+ raise_if_error(rc)
+ sleep 1
+end
+
diff --git a/test-notifier.rb b/test-notifier.rb
new file mode 100755
index 0000000..1adeb20
--- /dev/null
+++ b/test-notifier.rb
@@ -0,0 +1,55 @@
+#!/usr/bin/env ruby
+#
+# test-notifier.rb: Dump all messages received from notifier.
+#
+# Copyright (C) 2011 VMware, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+require 'ffi-rzmq'
+
+trap("INT") { exit }
+trap("TERM") { exit }
+def raise_if_error(rc)
+ unless ZMQ::Util.resultcode_ok?(rc)
+ raise "ZMQ Error: #{ZMQ::Util.error_string}"
+ end
+end
+
+unless ARGV[0]
+ print "usage: test-notifier.rb <notifier-host>\n"
+ exit 1
+end
+
+notifier_ep = "tcp://#{ARGV[0]}:7771"
+
+ctx = ZMQ::Context.new()
+notifier_sock = ctx.socket(ZMQ::SUB)
+rc = notifier_sock.setsockopt(ZMQ::SUBSCRIBE, '')
+raise_if_error(rc)
+rc = notifier_sock.connect(notifier_ep)
+raise_if_error(rc)
+
+msg = ''
+loop do
+ rc = notifier_sock.recv_string(msg, 0)
+ raise_if_error(rc)
+ print "[#{Time.now}] #{msg}\n"
+end
+
diff --git a/test-wdclient.rb b/test-wdclient.rb
new file mode 100755
index 0000000..bd162f0
--- /dev/null
+++ b/test-wdclient.rb
@@ -0,0 +1,51 @@
+#!/usr/bin/env ruby
+#
+# test-wdclient.rb: Test watchdog by sending arbitrary command messages.
+#
+# Copyright (C) 2011 VMware, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+require 'ffi-rzmq'
+
+def raise_if_error(rc)
+ unless ZMQ::Util.resultcode_ok?(rc)
+ raise "ZMQ Error: #{ZMQ::Util.error_string}"
+ end
+end
+
+if ARGV.length < 2
+ print "usage: test-wdclient <command-ep> <command> <args...>\n"
+ exit 1
+end
+
+command_ep = ARGV.shift
+command = ARGV.shift
+args = ARGV.join(' ')
+
+ctx = ZMQ::Context.new()
+command_sock = ctx.socket(ZMQ::PUSH)
+rc = command_sock.connect(command_ep)
+raise_if_error(rc)
+
+rc = command_sock.send_string(command + ' ' + args)
+raise_if_error(rc)
+rc = command_sock.close
+raise_if_error(rc)
+
diff --git a/watchdog.rb b/watchdog.rb
new file mode 100755
index 0000000..e6b82e8
--- /dev/null
+++ b/watchdog.rb
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+#
+# watchdog.rb: Watchdog component.
+#
+# Copyright (C) 2011 VMware, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+require 'thread'
+require 'ffi-rzmq'
+require 'socket'
+
+trap("INT") { exit }
+trap("TERM") { exit }
+def raise_if_error(rc)
+ unless ZMQ::Util.resultcode_ok?(rc)
+ raise "ZMQ Error: #{ZMQ::Util.error_string}"
+ end
+end
+
+if ARGV.length != 1
+ print "usage: watchdog <notifier-host>\n"
+ exit 1
+end
+
+notifier_ep = "tcp://#{ARGV[0]}:7770"
+control_ep = "tcp://*:7772"
+
+ctx = ZMQ::Context.new()
+notifier_sock = ctx.socket(ZMQ::PUB)
+rc = notifier_sock.connect(notifier_ep)
+raise_if_error(rc)
+
+control_sock = ctx.socket(ZMQ::PULL)
+rc = control_sock.bind(control_ep)
+raise_if_error(rc)
+
+poller = ZMQ::Poller.new
+poller.register_readable(control_sock)
+
+children = {}
+
+# Delete process if it went away
+trap("CHLD") do
+ pid = Process.wait
+ children.each do |k, v|
+ if v[:pid] == pid
+ children.delete(k)
+ end
+ end
+end
+
+# Kill any children on exit
+trap("TERM") do
+ children.each do |k, v|
+ Process.kill("KILL", v[:pid])
+ end
+end
+
+#
+# Main loop
+#
+loop do
+ ready = poller.poll(1)
+
+ # Process commands, if any.
+ if (ready == 1 && poller.readables.include?(control_sock))
+ msg = ''
+ rc = control_sock.recv_string(msg)
+ raise_if_error(rc)
+ if (msg =~ /^START (.+)$/)
+ command = $1
+ key = command.split()[0]
+ unless children.has_key?(key)
+ exec_command = "./" + key
+ if File.exists?(exec_command) && File.executable?(exec_command)
+ children[key] = {}
+ children[key][:pid] = Process.spawn("./" + command)
+ children[key][:command] = command
+ end
+ end
+ elsif (msg =~ /^STOP (.+)$/)
+ command = $1
+ key = command.split()[0]
+ if children.has_key?(key)
+ Process.kill("KILL", children[key][:pid])
+ while children.has_key?(key)
+ # nothing
+ end
+ end
+ end
+ # Otherwise, send out status messages to notifier.
+ else
+ status = "WATCHDOG ALIVE " + Socket.gethostname + " " + Time.now.to_f.to_s
+ rc = notifier_sock.send_string(status)
+ raise_if_error(rc)
+ children.each do |k, v|
+ status = "WATCHDOG RUNNING " + Socket.gethostname + \
+ " " + Time.now.to_f.to_s + " " + v[:command]
+ rc = notifier_sock.send_string(status)
+ raise_if_error(rc)
+ end
+ end
+end
+