[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