From ff7caa01f47df249066169daa314664c3669842e Mon Sep 17 00:00:00 2001 From: Martin Lucina Date: Wed, 9 Nov 2011 18:51:46 +0100 Subject: Initial commit --- .gitignore | 5 + LICENSE | 19 ++ Makefile | 13 ++ README.markdown | 15 ++ console.rb | 474 ++++++++++++++++++++++++++++++++++++++++++++++++++ db.test | 13 ++ monitor.c | 175 +++++++++++++++++++ myclient.c | 78 +++++++++ myservice.c | 70 ++++++++ named.conf.add | 12 ++ notifier.c | 145 +++++++++++++++ nsupdate-script | 32 ++++ nsupdate.key | 7 + nsupdate.test.key | 1 + nsupdate.test.private | 7 + test-fakehost.rb | 54 ++++++ test-notifier.rb | 55 ++++++ test-wdclient.rb | 51 ++++++ watchdog.rb | 121 +++++++++++++ 19 files changed, 1347 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.markdown create mode 100755 console.rb create mode 100644 db.test create mode 100644 monitor.c create mode 100644 myclient.c create mode 100644 myservice.c create mode 100644 named.conf.add create mode 100644 notifier.c create mode 100755 nsupdate-script create mode 100644 nsupdate.key create mode 100644 nsupdate.test.key create mode 100644 nsupdate.test.private create mode 100755 test-fakehost.rb create mode 100755 test-notifier.rb create mode 100755 test-wdclient.rb create mode 100755 watchdog.rb 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 , 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 \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 +#include +#include +#include +#include +#include +#include +#include + +#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 "\ + " \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 +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 \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 +#include +#include +#include +#include +#include +#include + +#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 " 1>&2 + exit 1 +fi +RECORD=$1 +VALUE=$2 + +nsupdate -k ./nsupdate.test < \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 \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 \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 \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 + -- cgit v1.2.3