[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.

Darryl L. Pierce dpierce at redhat.com
Tue Jun 3 15:57:08 UTC 2008


---
 ovirt-host-creator/common-pkgs.ks         |    1 +
 ovirt-host-creator/common-post.ks         |   16 +-
 ovirt-host-creator/identify.py            |  103 +++++++++++
 wui/src/host-browser/Makefile             |   12 --
 wui/src/host-browser/dbwriter.rb          |   66 -------
 wui/src/host-browser/host-browser.c       |  286 -----------------------------
 wui/src/host-browser/host-browser.rb      |  218 ++++++++++++++++++++++
 wui/src/host-browser/test-host-browser.rb |  220 ++++++++++++++++++++++
 8 files changed, 550 insertions(+), 372 deletions(-)
 create mode 100755 ovirt-host-creator/identify.py
 delete mode 100644 wui/src/host-browser/Makefile
 delete mode 100755 wui/src/host-browser/dbwriter.rb
 delete mode 100644 wui/src/host-browser/host-browser.c
 create mode 100755 wui/src/host-browser/host-browser.rb
 create mode 100755 wui/src/host-browser/test-host-browser.rb

diff --git a/ovirt-host-creator/common-pkgs.ks b/ovirt-host-creator/common-pkgs.ks
index 618a73a..7433d97 100644
--- a/ovirt-host-creator/common-pkgs.ks
+++ b/ovirt-host-creator/common-pkgs.ks
@@ -8,6 +8,7 @@ chkconfig
 rootfiles
 dhclient
 libvirt
+libvirt-python
 openssh-clients
 openssh-server
 iscsi-initiator-utils
diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks
index 088f920..46ad3c8 100644
--- a/ovirt-host-creator/common-post.ks
+++ b/ovirt-host-creator/common-post.ks
@@ -18,14 +18,14 @@ cat > /etc/init.d/ovirt-functions << \EOF
 # -*-Shell-script-*-
 
 find_srv() {
-        local dnsreply
-        dnsreply=$(dig +short -t srv _$1._$2.$(dnsdomainname))
-        if [ $? -eq 0 ]; then
-            set _ $dnsreply; shift
-            SRV_HOST=$4; SRV_PORT=$3
-        else
-            SRV_HOST=; SRV_PORT=
-        fi
+    local dnsreply
+    dnsreply=$(dig +short -t srv _$1._$2.$(dnsdomainname))
+    if [ $? -eq 0 ]; then
+        set _ $dnsreply; shift
+        SRV_HOST=$4; SRV_PORT=$3
+    else
+        SRV_HOST=; SRV_PORT=
+    fi
 }
 EOF
 
diff --git a/ovirt-host-creator/identify.py b/ovirt-host-creator/identify.py
new file mode 100755
index 0000000..a34ddf9
--- /dev/null
+++ b/ovirt-host-creator/identify.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python -Wall
+# 
+# Copyright (C) 2008 Red Hat, Inc.
+# Written by Darryl L. Pierce <dpierce at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+import socket
+import libvirt
+import sys
+import os
+
+class IdentifyNode:
+    """This class allows the managed node to connect to the WUI host
+    and notify it that the node is awake and ready to participate."""
+
+    def __init__(self, server_name, server_port):
+        conn = libvirt.openReadOnly(None)
+        info = conn.getInfo()
+        self.host_info = {
+            "UUID"     : "foo",
+            "ARCH"     : info[0],
+            "MEMSIZE"  : "%d" % info[1],
+            "NUMCPUS"  : "%d" % info[2],
+            "CPUSPEED" : "%d" % info[3],
+            "HOSTNAME" : conn.getHostname()
+            }
+
+        print(self.host_info)
+        
+        self.server_name = server_name
+        self.server_port = int(server_port)
+
+        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.socket.connect((self.server_name,self.server_port))
+        self.input  = self.socket.makefile('rb', 0)
+        self.output = self.socket.makefile('wb', 0)
+
+    def start_conversation(self):
+        print("Connecting to server")
+
+        response = self.input.readline().strip()
+        if response == 'HELLO?':
+            self.output.write("HELLO!\n")
+        else:
+            raise TypeError, "Received invalid conversation starter: %s" % response
+
+    def send_host_info(self):
+        print("Starting information exchange...")
+
+        response = self.input.readline().strip()
+        if response == 'INFO?':
+            for name in self.host_info.keys():
+                self.send_host_info_element(name,self.host_info[name])
+        else:
+            raise TypeError, "Received invalid info marker: %s" % response
+
+        print("Ending information exchange...")
+        self.output.write("ENDINFO\n")
+        response = self.input.readline().strip()
+
+        if response[1:3] == 'KVNO':
+            self.keytab = response[:5]
+        else:
+            raise TypeError, "Did not receive a keytab response: '%s'" % response
+
+    def send_host_info_element(self,key,value):
+        print("Sending: " + key + "=" + value)
+        print(type(value))
+        self.output.write(key + "=" + value + "\n")
+        response = self.input.readline().strip()
+
+        if response != "ACK " + key:
+            raise TypeError, "Received bad acknolwedgement for field: %s" % key
+
+    def get_keytab(self,tabfile):
+        print("Retrieving keytab information: %s" % self.keytab)
+
+    def end_conversation(self):
+        print("Disconnecting from server")
+
+
+if __name__ == '__main__': 
+    
+    identifier = IdentifyNode(sys.argv[1], sys.argv[2])
+
+    identifier.start_conversation()
+    identifier.send_host_info()
+    identifier.get_keytab()
+    identifier.end_conversation()
diff --git a/wui/src/host-browser/Makefile b/wui/src/host-browser/Makefile
deleted file mode 100644
index 3029be9..0000000
--- a/wui/src/host-browser/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-CC=gcc
-CFLAGS+=-g -Wall
-OBJS=host-browser.o
-LIBS=-lavahi-client
-
-all: host-browser
-
-host-browser: $(OBJS)
-	$(CC) $(CFLAGS) -o host-browser $(OBJS) $(LIBS)
-
-clean:
-	rm -f *.o *~ host-browser
diff --git a/wui/src/host-browser/dbwriter.rb b/wui/src/host-browser/dbwriter.rb
deleted file mode 100755
index 396ef60..0000000
--- a/wui/src/host-browser/dbwriter.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/ruby
-# 
-# Copyright (C) 2008 Red Hat, Inc.
-# Written by Chris Lalancette <clalance at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; version 2 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA  02110-1301, USA.  A copy of the GNU General Public License is
-# also available at http://www.gnu.org/copyleft/gpl.html.
-
-$: << File.join(File.dirname(__FILE__), "../dutils")
-
-require 'rubygems'
-require 'libvirt'
-require 'dutils'
-
-if ARGV.length != 1
-  exit
-end
-
-# connects to the db in here
-require 'dutils'
-
-# make sure we get our credentials up-front
-get_credentials
-
-begin
-  conn = Libvirt::open("qemu+tcp://" + ARGV[0] + "/system")
-  info = conn.node_get_info
-  conn.close
-rescue
-  # if we can't contact the host or get details for some reason, we just
-  # don't do anything and don't add anything to the database
-  puts "Failed connecting to host " + ARGV[0]
-  exit
-end
-
-# we could destroy the credentials, but another process might be using them
-# (in particular, the taskomatic).  Just leave them around, it shouldn't hurt
-
-
-# FIXME: we need a better way to get a UUID, rather than the hostname
-$host = Host.find(:first, :conditions => [ "uuid = ?", ARGV[0]])
-
-if $host == nil
-  Host.new(
-           "uuid" => ARGV[0],
-           "hostname" => ARGV[0],
-           "num_cpus" => info.cpus,
-           "cpu_speed" => info.mhz,
-           "arch" => info.model,
-           "memory" => info.memory,
-           "is_disabled" => 0,
-           "hardware_pool" => HardwarePool.get_default_pool
-           ).save
-end
diff --git a/wui/src/host-browser/host-browser.c b/wui/src/host-browser/host-browser.c
deleted file mode 100644
index 23af786..0000000
--- a/wui/src/host-browser/host-browser.c
+++ /dev/null
@@ -1,286 +0,0 @@
-/* 
- *  Copyright (C) 2008  Red Hat, Inc.
- *  Written by Chris Lalancette <clalance at redhat.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <time.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
-#include <netdb.h>
-#include <arpa/inet.h>
-#include <string.h>
-#include <errno.h>
-#include <signal.h>
-#include <unistd.h>
-
-#include <avahi-client/client.h>
-#include <avahi-client/lookup.h>
-
-#include <avahi-common/simple-watch.h>
-#include <avahi-common/malloc.h>
-#include <avahi-common/error.h>
-
-#ifndef DBWRITER_PATH
-#define DBWRITER_PATH "./dbwriter.rb"
-#endif
-
-static AvahiSimplePoll *simple_poll = NULL;
-
-static void usage(void)
-{
-  fprintf(stderr, "Usage: host-browser [OPTIONS]\n");
-  fprintf(stderr, "OPTIONS:\n\n");
-  fprintf(stderr, " -d\t\tRun in daemon mode (the default)\n");
-  fprintf(stderr, " -h\t\tPrint this help message\n");
-  fprintf(stderr, " -n\t\tRun in interactive (non-daemon) mode (useful for debugging)\n");
-  exit(1);
-}
-
-static void sig_chld(int signo)
-{
-  int status;
-
-  if (waitpid(-1, &status, WNOHANG) < 0) {
-    fprintf(stderr, "Error doing waitpid for child\n");
-    return;
-  }
-}
-
-// the function to make a daemon out of this program
-static int daemonize(void)
-{
-  pid_t pid;
-
-  if((pid=fork()) < 0){
-    return -1;
-  }
-  else if (pid != 0){
-    exit(0);
-  }
-
-  setsid();
-
-  //  umask(0);
-
-  return 0;
-}
-
-static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface,
-			     AVAHI_GCC_UNUSED AvahiProtocol protocol,
-			     AvahiResolverEvent event, const char *name,
-			     const char *type, const char *domain,
-			     const char *host_name, const AvahiAddress *address,
-			     uint16_t port, AvahiStringList *txt,
-			     AvahiLookupResultFlags flags,
-			     AVAHI_GCC_UNUSED void* userdata)
-{
-    assert(r);
-
-    /* Called whenever a service has been resolved successfully or timed out */
-
-    switch (event) {
-        case AVAHI_RESOLVER_FAILURE:
-            break;
-
-        case AVAHI_RESOLVER_FOUND: {
-	  char a[AVAHI_ADDRESS_STR_MAX];
-	  in_addr_t remote;
-	  struct hostent *host;
-	  char *argv[3];
-	  pid_t pid;
-	  int ret;
-	  char *libvirt_hostname;
-
-	  avahi_address_snprint(a, sizeof(a), address);
-
-	  remote = inet_addr(a);
-	  host = gethostbyaddr(&remote, sizeof(remote), AF_INET);
-	  if (host == NULL) {
-	    // we failed to resolve the address to a hostname; we'll just try
-	    // with the IP address
-	    libvirt_hostname = a;
-	  }
-	  else {
-	    libvirt_hostname = host->h_name;
-	  }
-
-	  argv[0] = DBWRITER_PATH;
-	  argv[1] = libvirt_hostname;
-	  argv[2] = NULL;
-
-	  pid = fork();
-
-	  if (pid < 0) {
-	    fprintf(stderr, "Failed to fork: %s\n",strerror(errno));
-	  }
-	  else if (pid == 0) {
-	    // child
-	    ret = execv(DBWRITER_PATH, argv);
-	    if (ret < 0) {
-	      fprintf(stderr, "Failed to exec %s: %s\n",DBWRITER_PATH,strerror(errno));
-	    }
-	  }
-	  else {
-	    // parent, do nothing; we'll catch the child exits with SIGCHLD
-	  }
-
-	  break;
-        }
-    }
-
-    avahi_service_resolver_free(r);
-}
-
-static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
-			    AvahiProtocol protocol, AvahiBrowserEvent event,
-			    const char *name, const char *type,
-			    const char *domain,
-			    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
-			    void* userdata)
-{    
-    AvahiClient *c = userdata;
-    assert(b);
-
-    /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
-
-    switch (event) {
-        case AVAHI_BROWSER_FAILURE:
-            
-            avahi_simple_poll_quit(simple_poll);
-            return;
-
-        case AVAHI_BROWSER_NEW:
-            /* We ignore the returned resolver object. In the callback
-               function we free it. If the server is terminated before
-               the callback function is called the server will free
-               the resolver for us. */
-
-            if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c)))
-                fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
-            
-            break;
-
-        case AVAHI_BROWSER_REMOVE:
-            break;
-
-        case AVAHI_BROWSER_ALL_FOR_NOW:
-        case AVAHI_BROWSER_CACHE_EXHAUSTED:
-            break;
-    }
-}
-
-static void client_callback(AvahiClient *c, AvahiClientState state,
-			    AVAHI_GCC_UNUSED void * userdata)
-{
-    assert(c);
-
-    /* Called whenever the client or server state changes */
-
-    if (state == AVAHI_CLIENT_FAILURE) {
-        fprintf(stderr, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
-        avahi_simple_poll_quit(simple_poll);
-    }
-}
-
-int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[])
-{
-    AvahiClient *client = NULL;
-    AvahiServiceBrowser *sb = NULL;
-    int error;
-    int ret = 1;
-    int daemon_mode = 1;
-    int c;
-    struct sigaction act;
-
-    while ((c = getopt(argc, argv,":dhn")) != -1) {
-      switch(c) {
-      case 'd':
-	daemon_mode = 1;
-	break;
-      case 'h':
-	usage();
-	break;
-      case 'n':
-	daemon_mode = 0;
-	break;
-      default:
-	usage();
-	break;
-      }
-    }
-
-    if ((argc - optind) != 0) {
-      usage();
-    }
-
-    if (daemon_mode) {
-        daemonize();
-    }
-
-    act.sa_handler = sig_chld;
-    sigemptyset(&act.sa_mask);
-    act.sa_flags = SA_NOCLDSTOP;
-    sigaction(SIGCHLD, &act, NULL);
-
-    /* Allocate main loop object */
-    if (!(simple_poll = avahi_simple_poll_new())) {
-        fprintf(stderr, "Failed to create simple poll object.\n");
-        goto fail;
-    }
-
-    /* Allocate a new client */
-    client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error);
-
-    /* Check wether creating the client object succeeded */
-    if (!client) {
-        fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error));
-        goto fail;
-    }
-    
-    /* Create the service browser */
-    if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_libvirt._tcp", NULL, 0, browse_callback, client))) {
-        fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
-        goto fail;
-    }
-
-    /* Run the main loop */
-    avahi_simple_poll_loop(simple_poll);
-    
-    ret = 0;
-    
-fail:
-    
-    /* Cleanup things */
-    if (sb)
-        avahi_service_browser_free(sb);
-    
-    if (client)
-        avahi_client_free(client);
-
-    if (simple_poll)
-        avahi_simple_poll_free(simple_poll);
-
-    return ret;
-}
diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb
new file mode 100755
index 0000000..e2763a9
--- /dev/null
+++ b/wui/src/host-browser/host-browser.rb
@@ -0,0 +1,218 @@
+#!/usr/bin/ruby -Wall
+# 
+# Copyright (C) 2008 Red Hat, Inc.
+# Written by Darryl L. Pierce <dpierce at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+$: << File.join(File.dirname(__FILE__), "../dutils")
+
+require 'rubygems'
+require 'libvirt'
+require 'dutils'
+
+require 'socket'
+require 'krb5_auth'
+include Krb5Auth
+
+include Socket::Constants
+
+require 'dutils'
+
+# +HostBrowser+ communicates with the a managed node. It retrieves specific information
+# about the node and then updates the list of active nodes for the WUI.
+#
+class HostBrowser
+    attr_accessor :logfile
+    attr_accessor :keytab_dir
+    attr_accessor :keytab_filename
+  
+    def initialize(session)
+        @session = session
+        @log_prefix = "[#{session.peeraddr[3]}] "
+        @logfile = '/var/log/ovirt-wui/host-browser.log'
+        @keytab_dir = '/usr/share/ipa/html/'
+    end
+
+    # Ensures the conversation starts properly.
+    #
+    def begin_conversation
+        puts "#{@log_prefix} Begin conversation"
+        @session.write("HELLO?\n")
+
+        response = @session.readline.chomp
+        raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!"
+    end
+
+    # Requests node information from the remote system.
+    #
+    def get_remote_info
+        puts "#{@log_prefix} Begin remote info collection"
+        result = {}
+        result['IPADDR'] = @session.peeraddr[3]
+        @session.write("INFO?\n")
+
+        loop do
+            info = @session.readline.chomp
+
+            break if info == "ENDINFO"
+
+            raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/
+
+            key, value = info.split("=")
+
+            puts "#{@log_prefix} ::Received - #{key}:#{value}"
+            result[key] = value
+        
+            @session.write("ACK #{key}\n")
+        end
+
+        return result
+    end
+
+    # Writes the supplied host information to the database.
+    #
+    def write_host_info(host_info)
+        ensure_present(host_info,'UUID')
+        ensure_present(host_info,'HOSTNAME')
+        ensure_present(host_info,'NUMCPUS')
+        ensure_present(host_info,'CPUSPEED')
+        ensure_present(host_info,'ARCH')
+        ensure_present(host_info,'MEMSIZE')
+
+        puts "Searching for existing host record..."
+        host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']])
+
+        if host == nil
+            begin
+                puts "Creating a new record for #{host_info['HOSTNAME']}..."
+            
+                Host.new(
+                    "uuid"          => host_info['UUID'],
+                    "hostname"      => host_info['HOSTNAME'],
+                    "num_cpus"      => host_info['NUMCPUS'],
+                    "cpu_speed"     => host_info['CPUSPEED'],
+                    "arch"          => host_info['ARCH'],
+                    "memory"        => host_info['MEMSIZE'],
+                    "is_disabled"   => 0,
+                    "hardware_pool" => HardwarePool.get_default_pool).save
+            rescue Exception => error
+                puts "Error while creating record: #{error.message}"
+            end
+        end
+    
+        return host
+    end
+
+    # Ends the conversation, notifying the user of the key version number.
+    #
+    def end_conversation(kvno)
+        puts "#{@log_prefix} Ending conversation"
+
+        @session.write("KVNO #{kvno}\n")
+
+        response = @session.readline.chomp
+
+        raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK"
+
+        @session.write("BYE\n");
+        @session.shutdown(2)
+    end
+  
+    # Creates a keytab if one is needed, returning the filename.
+    #
+    def create_keytab(host_info, krb5_arg = nil)
+        krb5 = krb5_arg || Krb5.new
+  
+        default_realm = krb5.get_default_realm
+        libvirt_princ = 'libvirt/' + host_info['HOSTNAME'] + '@' + default_realm
+        outfile = host_info['IPADDR'] + '-libvirt.tab'
+        @keytab_filename = @keytab_dir + outfile
+
+        # TODO need a way to test this portion
+        unless defined? TESTING
+            puts "Writing keytab file: #{@keytab_filename}"
+            kadmin_local('addprinc -randkey ' + libvirt_princ)
+            kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ)
+
+            File.chmod(0644, at keytab_filename)
+        end
+
+        return @keytab_filename
+    end
+
+    private
+
+    # Private method to ensure that a required field is present.
+    #
+    def ensure_present(host_info,key)
+        raise Exception.new("ERROR! Missing '#{key}'...") if host_info[key] == nil
+    end
+
+    # Executes an external program to support the keytab function.
+    #
+    def kadmin_local(command)
+        system("/usr/kerberos/sbin/kadmin -q '" + command + "'")
+    end
+end
+
+def entry_point(server)
+    while(session = server.accept)
+        child = fork do
+            puts "Connected to #{session.peeraddr[3]}"
+      
+            begin
+                browser = HostBrowser.new(session)
+
+                # redirect output to the logsg
+                STDOUT.reopen browser.logfile, 'a'
+                STDERR.reopen STDOUT
+
+                browser.begin_conversation
+                host_info = browser.get_remote_info
+                browser.write_host_info(host_info)
+                keytab = browser.create_keytab(host_info)
+                browser.end_conversation(keytab)
+            rescue Exception => error
+                session.write("ERROR #{error.message}\n")
+                puts "ERROR #{error.message}"
+            end
+      
+            session.shutdown(2) unless session.closed?
+
+            puts "Disconnected from #{session.peeraddr[3]}"
+        end
+    
+        Process.detach(child)        
+    end      
+end
+
+unless defined?(TESTING)
+    server = TCPServer.new("",12120)
+  
+    # The main entry point.
+    #
+    unless ARGV[0] == "-n"
+        pid = fork do
+            # TODO need to pull the port from the SRV record        
+            entry_point(server)
+        end
+
+        Process.detach(pid)
+    else
+        entry_point(server)
+    end
+end
diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb
new file mode 100755
index 0000000..2a05181
--- /dev/null
+++ b/wui/src/host-browser/test-host-browser.rb
@@ -0,0 +1,220 @@
+#!/usr/bin/ruby -Wall
+# 
+# Copyright (C) 2008 Red Hat, Inc.
+# Written by Darryl L. Pierce <dpierce at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+require File.dirname(__FILE__) + '/../test/test_helper'
+require 'test/unit'
+require 'flexmock/test_unit'
+
+TESTING=true
+
+require 'host-browser'
+
+class TestHostBrowser < Test::Unit::TestCase    
+
+  def setup
+    @session = flexmock('session')
+    @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] }
+
+    @krb5 = flexmock('krb5')
+
+    @browser = HostBrowser.new(@session)
+    @browser.logfile = './unit-test.log'
+    @browser.keytab_dir = '/var/temp/'
+
+    # default host info
+    @host_info = {}
+    @host_info['UUID']     = 'node1'
+    @host_info['IPADDR']   = '192.168.2.2'
+    @host_info['HOSTNAME'] = 'node1.ovirt.redhat.com'
+    @host_info['NUMCPUS']  = '3'
+    @host_info['CPUSPEED'] = '3'
+    @host_info['ARCH']     = 'x86_64'
+    @host_info['MEMSIZE']  = '16384'
+    @host_info['DISABLED'] = '0'
+  end
+
+  # Ensures that the server raises an exception when it receives an 
+  # improper handshake response.
+  #
+  def test_begin_conversation_with_improper_response_to_greeting
+    @session.should_receive(:write).with("HELLO?\n").once().returns { |greeting| greeting.length }
+    @session.should_receive(:readline).once().returns { "SUP?" }
+
+    assert_raise(Exception) { @browser.begin_conversation }
+  end
+
+  # Ensures the server accepts a proper response from the remote system.
+  #
+  def test_begin_conversation
+    @session.should_receive(:write).with("HELLO?\n").once().returns { |greeting| greeting.length }
+    @session.should_receive(:readline).once().returns { "HELLO!\n" }
+
+    assert_nothing_raised(Exception) { @browser.begin_conversation }
+  end
+
+  # Ensures that the server raises an exception when it receives
+  # poorly formed data while exchanging system information.
+  #
+  def test_get_info_with_bad_handshake
+    @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "key1=value1\n" }
+    @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "farkledina\n" }
+
+    assert_raise(Exception) { @browser.get_remote_info }
+  end
+
+  # Ensures that, if an info field is missing a key, the server raises
+  # an exception.
+  #
+  def test_get_info_with_missing_key
+    @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "=value1\n" }
+    
+    assert_raise(Exception) { @browser.get_remote_info }
+  end
+
+  # Ensures that, if an info field is missing a value, the server raises
+  # an exception.
+  #
+  def test_get_info_with_missing_value
+    @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "key1=\n" }
+
+    assert_raise(Exception) { @browser.get_remote_info }
+  end
+
+  # Ensures that, if the server gets a poorly formed ending statement, it
+  # raises an exception.
+  #
+  def test_get_info_with_invalid_end
+    @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "key1=value1\n" }
+    @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "ENDIFNO\n" }
+
+    assert_raise(Exception) { @browser.get_remote_info }
+  end
+
+  # Ensures that a well-formed transaction works as expected.
+  #
+  def test_get_info
+    @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "key1=value1\n" }
+    @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "key2=value2\n" }
+    @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "ENDINFO\n" }
+
+    info = @browser.get_remote_info
+
+    assert_equal 3,info.keys.size, "Should contain two keys"
+    assert info.include?("IPADDR")
+    assert info.include?("key1")
+    assert info.include?("key2")
+  end 
+
+  # Ensures the host browser generates a keytab as expected.
+  #
+  def test_create_keytab
+    @krb5.should_receive(:get_default_realm).once().returns { "ovirt-test-realm" }
+
+    result = @browser.create_keytab(@host_info, at krb5)
+
+    assert_equal @browser.keytab_filename, result, "Should have returned the keytab filename"
+  end
+
+  # Ensures that, if no UUID is present, the server raises an exception.
+  #
+  def test_write_host_info_with_missing_uuid
+    @host_info['UUID'] = nil
+
+    assert_raise(Exception) { @browser.write_host_info(@host_info) }
+  end
+
+  # Ensures that, if the hostname is missing, the server
+  # raises an exception.
+  #
+  def test_write_host_info_with_missing_hostname
+    @host_info['HOSTNAME'] = nil
+  
+    assert_raise(Exception) { @browser.write_host_info(@host_info) }
+  end
+
+  # Ensures that, if the number of CPUs is missing, the server raises an exception.
+  #
+  def test_write_host_info_with_missing_numcpus
+    @host_info['NUMCPUS'] = nil
+
+    assert_raise(Exception) { @browser.write_host_info(@host_info) }
+  end
+
+  # Ensures that, if the CPU speed is missing, the server raises an exception.
+  #
+  def test_write_host_info_with_missing_cpuspeed
+    @host_info['CPUSPEED'] = nil
+
+    assert_raise(Exception) { @browser.write_host_info(@host_info) }
+  end
+
+  # Ensures that, if the architecture is missing, the server raises an exception.
+  #
+  def test_write_host_info_with_missing_arch
+    @host_info['ARCH'] = nil
+
+    assert_raise(Exception) { @browser.write_host_info(@host_info) }
+  end
+
+  # Ensures that, if the memory size is missing, the server raises an exception.
+  #
+  def test_write_host_info_info_with_missing_memsize
+    @host_info['MEMSIZE'] = nil
+
+    assert_raise(Exception) { @browser.write_host_info(@host_info) }
+  end
+
+  # Ensures that the host information is properly moved to a persisted object
+  # and saved.
+  #
+  def test_write_host_info
+    result = @browser.write_host_info(@host_info)
+
+    assert result, "No persisted object returned"
+    assert_match @host_info['UUID'],     result.uuid,           "UUID was not persisted"
+    assert_match @host_info['HOSTNAME'], result.hostname,       "Hostname was not persisted"
+    assert_match @host_info['NUMCPUS'],  "#{result.num_cpus}",  "Number of CPUs was not persisted"
+    assert_match @host_info['CPUSPEED'], "#{result.cpu_speed}", "CPU speed was not persisted"
+    assert_match @host_info['ARCH'],     "#{result.arch}",      "Architecture was not persisted"
+    assert_match @host_info['MEMSIZE'],  "#{result.memory}",    "Memory size was not persisted"      
+  end
+
+  # Ensures that, if a keytab is present and a key version number available, 
+  # the server ends the conversation by returning the key version number.
+  #
+  def test_end_conversation
+    @session.should_receive(:write).with("KVNO 12345\n").once().returns { |request| request.length }
+    @session.should_receive(:readline).once().returns { "ACK\n" }
+    @session.should_receive(:write).with("BYE\n").once().returns { |request| request.length }
+    @session.should_receive(:shutdown).with(2).once()
+
+    assert_nothing_raised(Exception) { @browser.end_conversation(12345) }
+  end
+
+end    
-- 
1.5.5.1




More information about the ovirt-devel mailing list