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

Hugh O. Brock hbrock at redhat.com
Tue Jun 3 19:04:49 UTC 2008


On Tue, Jun 03, 2008 at 11:49:43AM -0700, Ian Main wrote:
> 
> ok, let me try that again.  Last one didn't apply properly.
> 
> --
> 
> This patch is just a little bit of work on top of Darryl Pierces work
> to replace avahi.  It now builds into an rpm and doesn't fail.  We will
> continue to work on top of this today and should have it working soon.
>  
>  Signed-off-by: Ian Main <imain at redhat.com>

> 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..76c8216 100644
> --- a/ovirt-host-creator/common-post.ks
> +++ b/ovirt-host-creator/common-post.ks
> @@ -12,6 +12,111 @@ cat > /etc/sysconfig/iptables << \EOF
>  COMMIT
>  EOF
>  
> +echo "Writing ovirt-identify-node script"
> +cat > /sbin/ovirt-identify-node << \EOF
> +#!/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
> +
> +
> +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):
> +		self.hostname    = 'management.priv.ovirt.org'
> +		self.server_name = 'management.priv.ovirt.org'
> +		self.server_port = 12120
> +		self.host_info = {
> +		  "UUID"     : "1148fdf8-961d-11dc-9387-001558c41534",
> +		  "HOSTNAME" : "localhost",
> +		  "NUMCPUS"  : "4",
> +		  "CPUSPEED" : "2.4",
> +		  "ARCH"     : "kvm",
> +		  "MEMSIZE"  : "16384" }
> +
> +		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()
> +
> +		print 'response is', response
> +		print 'response[:4] is', response[:4]
> +		if response[:4] == 'KVNO':
> +			self.keytab = response[5:]
> +			print 'keytab is', self.keytab
> +		else:
> +			raise TypeError, "Did not receive a keytab response: '%s'" % response
> +
> +	def send_host_info_element(self,key,value):
> +		print("Sending: " + key + "=" + 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):
> +		print("Retrieving keytab information: %s" % self.keytab)
> +
> +	def end_conversation(self):
> +		print("Disconnecting from server")
> +
> +
> +if __name__ == '__main__':
> +	identifier = IdentifyNode()
> +
> +	identifier.start_conversation()
> +	identifier.send_host_info()
> +	identifier.get_keytab()
> +	identifier.end_conversation()
> +
> +EOF
> +
> +chmod +x /sbin/ovirt-identify-node
> +
> +
>  echo "Writing ovirt-functions script"
>  # common functions
>  cat > /etc/init.d/ovirt-functions << \EOF
> diff --git a/wui/conf/ovirt-host-browser b/wui/conf/ovirt-host-browser
> index 2037974..5e7fdae 100755
> --- a/wui/conf/ovirt-host-browser
> +++ b/wui/conf/ovirt-host-browser
> @@ -8,7 +8,7 @@
>  #    ovirt VM manager.
>  #
>  
> -DAEMON=/usr/share/ovirt-wui/host-browser/host-browser
> +DAEMON=/usr/share/ovirt-wui/host-browser/host-browser.rb
>  
>  . /etc/init.d/functions
>  
> @@ -21,7 +21,7 @@ start() {
>  
>  stop() {
>      echo -n "Shutting down ovirt-host-browser: "
> -    killproc host-browser
> +    killproc host-browser.rb
>      RETVAL=$?
>      echo
>  }
> diff --git a/wui/ovirt-wui.spec b/wui/ovirt-wui.spec
> index 269b7de..5a2e6d3 100644
> --- a/wui/ovirt-wui.spec
> +++ b/wui/ovirt-wui.spec
> @@ -49,10 +49,6 @@ The webapp for oVirt.
>  
>  %build
>  
> -# make sure we override the DBWRITER_PATH with where it will actually be in
> -# the end; yes, this is ugly
> -CFLAGS="-DDBWRITER_PATH=\\\"/usr/share/ovirt-wui/host-browser/dbwriter.rb\\\"" make -C src/host-browser
> -
>  %install
>  test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
>  mkdir %{buildroot}
> @@ -86,10 +82,6 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/host-status.log
>  %{__cp} -a %{pbuild}/src/* %{buildroot}%{app_root}
>  
>  # remove the files not needed for the installation
> -%{__rm} -f %{buildroot}%{app_root}/host-browser/Makefile
> -%{__rm} -f %{buildroot}%{app_root}/host-browser/.gitignore
> -%{__rm} -f %{buildroot}%{app_root}/host-browser/*.o
> -%{__rm} -f %{buildroot}%{app_root}/host-browser/*.c
>  %{__rm} -f %{buildroot}%{app_root}/task-omatic/.gitignore
>  
>  %{__cp} -a %{pbuild}/scripts/ovirt-add-host %{buildroot}%{_bindir}
> 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..2aa74b4
> --- /dev/null
> +++ b/wui/src/host-browser/host-browser.rb
> @@ -0,0 +1,217 @@
> +#!/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
> +require 'daemons'
> +include Daemonize
> +
> +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]}"
> +     	    
> +            database_connect
> + 
> +            begin
> +                browser = HostBrowser.new(session)
> +
> +                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"
> +        daemonize
> +        STDOUT.reopen browser.logfile, 'a'
> +        STDERR.reopen STDOUT
> +
> +        entry_point(server)
> +    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 100644
> 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    
> diff --git a/wui/version b/wui/version
> index d4480df..c7668ca 100644
> --- a/wui/version
> +++ b/wui/version
> @@ -1 +1 @@
> -0.0.5 1
> +0.0.5 0.1.200806031628git5b21580

ACK. Much better, appears to apply. Commit away.

--Hugh




More information about the ovirt-devel mailing list