diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | README.markdown | 15 | ||||
-rwxr-xr-x | console.rb | 474 | ||||
-rw-r--r-- | db.test | 13 | ||||
-rw-r--r-- | monitor.c | 175 | ||||
-rw-r--r-- | myclient.c | 78 | ||||
-rw-r--r-- | myservice.c | 70 | ||||
-rw-r--r-- | named.conf.add | 12 | ||||
-rw-r--r-- | notifier.c | 145 | ||||
-rwxr-xr-x | nsupdate-script | 32 | ||||
-rw-r--r-- | nsupdate.key | 7 | ||||
-rw-r--r-- | nsupdate.test.key | 1 | ||||
-rw-r--r-- | nsupdate.test.private | 7 | ||||
-rwxr-xr-x | test-fakehost.rb | 54 | ||||
-rwxr-xr-x | test-notifier.rb | 55 | ||||
-rwxr-xr-x | test-wdclient.rb | 51 | ||||
-rwxr-xr-x | watchdog.rb | 121 |
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 @@ -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 + @@ -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 + |