Darryl L. Pierce
2008-Jun-02 23:37 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
I'm at a point now where I'm pretty close, but starting to stumble a little with the keytab stuff. Also, the persistence seems to not work but it could be my environment. I'm on around 5:45a EST if anybody wants to ping me to lend a hand. --- ovirt-host-creator/common-post.ks | 8 + ovirt-host-creator/identify-node.py | 94 ++++++++ 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/ruby-host-browser.rb | 220 ++++++++++++++++++ wui/src/host-browser/test-ruby-host-browser.rb | 219 ++++++++++++++++++ 7 files changed, 541 insertions(+), 364 deletions(-) create mode 100755 ovirt-host-creator/identify-node.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/ruby-host-browser.rb create mode 100755 wui/src/host-browser/test-ruby-host-browser.rb diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index 088f920..621776f 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -12,6 +12,14 @@ cat > /etc/sysconfig/iptables << \EOF COMMIT EOF +# TODO copy script into this place +echo "Writing ovirt-identify-node script" +cat > /sbin/ovirt-identify-node + +EOF + +chmod +x /sbin/ovirt-identify-node + echo "Writing ovirt-functions script" # common functions cat > /etc/init.d/ovirt-functions << \EOF diff --git a/ovirt-host-creator/identify-node.py b/ovirt-host-creator/identify-node.py new file mode 100755 index 0000000..1c23d3f --- /dev/null +++ b/ovirt-host-creator/identify-node.py @@ -0,0 +1,94 @@ +#!/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 = 'localhost' + self.server_name = 'localhost' + 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() + + 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) + 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() + + 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/ruby-host-browser.rb b/wui/src/host-browser/ruby-host-browser.rb new file mode 100755 index 0000000..205bc69 --- /dev/null +++ b/wui/src/host-browser/ruby-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. + +$: << 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 + puts "Creating a new record for #{host_info['HOSTNAME']}..." + begin + 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 + +# Fake a main method + +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-ruby-host-browser.rb b/wui/src/host-browser/test-ruby-host-browser.rb new file mode 100755 index 0000000..3a54b3e --- /dev/null +++ b/wui/src/host-browser/test-ruby-host-browser.rb @@ -0,0 +1,219 @@ +#!/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 'ruby-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 2,info.keys.size, "Should contain two keys" + 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
Perry N. Myers
2008-Jun-03 03:19 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
Darryl L. Pierce wrote:> I'm at a point now where I'm pretty close, but starting to stumble a little with the keytab stuff. > Also, the persistence seems to not work but it could be my environment. I'm on around 5:45a EST if > anybody wants to ping me to lend a hand.I'll let more accomplished coders make the important comments, but I have a few comments...> --- > ovirt-host-creator/common-post.ks | 8 + > ovirt-host-creator/identify-node.py | 94 ++++++++ > 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/ruby-host-browser.rb | 220 ++++++++++++++++++ > wui/src/host-browser/test-ruby-host-browser.rb | 219 ++++++++++++++++++ > 7 files changed, 541 insertions(+), 364 deletions(-) > create mode 100755 ovirt-host-creator/identify-node.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/ruby-host-browser.rb > create mode 100755 wui/src/host-browser/test-ruby-host-browser.rb > > diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks > index 088f920..621776f 100644 > --- a/ovirt-host-creator/common-post.ks > +++ b/ovirt-host-creator/common-post.ks > @@ -12,6 +12,14 @@ cat > /etc/sysconfig/iptables << \EOF > COMMIT > EOF > > +# TODO copy script into this place > +echo "Writing ovirt-identify-node script" > +cat > /sbin/ovirt-identify-node > + > +EOF > + > +chmod +x /sbin/ovirt-identify-node > +Hmm. I think we should create an ovirt-node rpm file where we can stick things like this, and some of the other things that have been crammed into the kickstart post files. So if you want to create this rpm and throw some stuff in there (like this and the below python script) that would be a good thing.> echo "Writing ovirt-functions script" > # common functions > cat > /etc/init.d/ovirt-functions << \EOF > diff --git a/ovirt-host-creator/identify-node.py b/ovirt-host-creator/identify-node.py > new file mode 100755 > index 0000000..1c23d3f > --- /dev/null > +++ b/ovirt-host-creator/identify-node.py > @@ -0,0 +1,94 @@ > +#!/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 = 'localhost' > + self.server_name = 'localhost' > + self.server_port = 12120server name and server port should be obtained from DNS SRV records. This should be easy to get in python... python has an API for everything, right? :)> + self.host_info = { > + "UUID" : "1148fdf8-961d-11dc-9387-001558c41534", > + "HOSTNAME" : "localhost", > + "NUMCPUS" : "4", > + "CPUSPEED" : "2.4", > + "ARCH" : "kvm", > + "MEMSIZE" : "16384" }Obviously a place holder for use later.> + 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) > + self.output.write(key + "=" + value + "\n") > + response = self.input.readline().strip() > + > + if response != "ACK " + key: > + raise TypeError, "Received bad acknolwedgement for field: %s" % keyHeh... Spell check :) acknowledgement...> + 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() > + > + identifier.start_conversation() > + identifier.send_host_info() > + identifier.get_keytab() > + identifier.end_conversation()The rest is Ruby and I need to get more familiar with it before I am able to make coherent comments on syntax stuff... I'll let some of the other folks contribute here. Perry
Ian Main
2008-Jun-03 05:42 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
On Mon, 2 Jun 2008 19:37:12 -0400 "Darryl L. Pierce" <dpierce at redhat.com> wrote:> I'm at a point now where I'm pretty close, but starting to stumble a little with the keytab stuff. > Also, the persistence seems to not work but it could be my environment. I'm on around 5:45a EST if > anybody wants to ping me to lend a hand.OK, I didn't get as far as I would have liked tonight.. apparently I've been going to bed early every night and so now 10pm hits and I'm tired :). I basically just got it building the rpm properly and setup the host image side (patch attached). I get a database error now when I run it in the developer setup: Searching for existing host record... ERROR PGError: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. : SELECT * FROM hosts WHERE (uuid = E'1148fdf8-961d-11dc-9387-001558c41534') LIMIT 1 which I didn't find an answer to. The query is fine.. dunno why the error occurs. Anyway, all I really did was clean up the rpm stuff, rename the script to host-browswer.rb so it matches all the other names (no ruby- in front), changed the fix host stuff in the python and put it in the post for the node. I see there's still a bit to go .. at least with these changes it can be installed on a developer wui appliance properly and we can start debugging. I'll ping you in the morning (PDT) and see if we can split up some of the tasks or such. I noticed that you are inserting tabs into the python and ruby code. I'm guessing you're using 4 space tabs as the ruby indentation is pretty off.. I didn't change it though. Ian -------------- next part -------------- A non-text attachment was scrubbed... Name: some-host-browser-changes.patch Type: text/x-patch Size: 31044 bytes Desc: not available URL: <http://listman.redhat.com/archives/ovirt-devel/attachments/20080602/2337fd90/attachment.bin>
Chris Lalancette
2008-Jun-03 08:56 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
Darryl L. Pierce wrote:> ovirt-host-creator/common-post.ks | 8 + > ovirt-host-creator/identify-node.py | 94 ++++++++Well, I hate to be a party-pooper, but apevec points out that this may not fly. We are generally removing python from the host image (it's in there by accident right now), so we won't have the interpreter around to run this script. Luckily the script doesn't look too complicated, so re-writing it in bash (or C) is probably not too bad. Chris Lalancette
Darryl L. Pierce
2008-Jun-03 15:57 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
--- 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
Darryl L. Pierce
2008-Jun-03 19:33 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- ovirt-host-creator/common-post.ks | 16 +++--- ovirt-host-creator/identify.py | 102 ++++++++++++++++++++++++++++++++++ wui/src/host-browser/host-browser.rb | 17 +++-- 3 files changed, 120 insertions(+), 15 deletions(-) create mode 100755 ovirt-host-creator/identify.py diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index 76c8216..df1acb4 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -123,14 +123,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..664ed2d --- /dev/null +++ b/ovirt-host-creator/identify.py @@ -0,0 +1,102 @@ +#!/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[0:4] == '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) + 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(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/host-browser.rb b/wui/src/host-browser/host-browser.rb index 2aa74b4..9519e7b 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -131,7 +131,6 @@ class HostBrowser 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. @@ -174,13 +173,17 @@ end def entry_point(server) while(session = server.accept) child = fork do - puts "Connected to #{session.peeraddr[3]}" - - database_connect - + remote = session.peeraddr[3] + + puts "Connected to #{remote}" + 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) @@ -191,9 +194,9 @@ def entry_point(server) puts "ERROR #{error.message}" end - session.shutdown(2) unless session.closed? + # session.shutdown(2) unless session.closed? - puts "Disconnected from #{session.peeraddr[3]}" + puts "Disconnected from #{remote}" end Process.detach(child) -- 1.5.5.1
Darryl L. Pierce
2008-Jun-03 19:48 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- ovirt-host-creator/common-post.ks | 16 +++--- ovirt-host-creator/identify.py | 102 ++++++++++++++++++++++++++++++++++ wui/src/host-browser/host-browser.rb | 17 +++-- 3 files changed, 120 insertions(+), 15 deletions(-) create mode 100755 ovirt-host-creator/identify.py diff --git a/ovirt-host-creator/identify.py b/ovirt-host-creator/identify.py new file mode 100755 index 0000000..664ed2d --- /dev/null +++ b/ovirt-host-creator/identify.py @@ -0,0 +1,102 @@ +#!/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[0:4] == '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) + 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(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/host-browser.rb b/wui/src/host-browser/host-browser.rb index 2aa74b4..9519e7b 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -131,7 +131,6 @@ class HostBrowser 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. @@ -174,13 +173,17 @@ end def entry_point(server) while(session = server.accept) child = fork do - puts "Connected to #{session.peeraddr[3]}" - - database_connect - + remote = session.peeraddr[3] + + puts "Connected to #{remote}" + 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) @@ -191,9 +194,9 @@ def entry_point(server) puts "ERROR #{error.message}" end - session.shutdown(2) unless session.closed? + # session.shutdown(2) unless session.closed? - puts "Disconnected from #{session.peeraddr[3]}" + puts "Disconnected from #{remote}" end Process.detach(child) -- 1.5.5.1
Darryl L. Pierce
2008-Jun-03 21:37 UTC
[Ovirt-devel] [PATCH] The server was rewritten in Ruby. The managed node side was rewritten in Python.
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- ovirt-host-creator/identify.py | 21 ++++----- wui/src/host-browser/host-browser.rb | 66 ++++++++++++++--------------- wui/src/host-browser/test-host-browser.rb | 32 ++++---------- 3 files changed, 50 insertions(+), 69 deletions(-) diff --git a/ovirt-host-creator/identify.py b/ovirt-host-creator/identify.py index 664ed2d..d20b607 100755 --- a/ovirt-host-creator/identify.py +++ b/ovirt-host-creator/identify.py @@ -1,5 +1,5 @@ #!/usr/bin/python -Wall -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Darryl L. Pierce <dpierce at redhat.com> # @@ -31,16 +31,15 @@ class IdentifyNode: 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() + "UUID" : "bar", + "ARCH" : info[0], + "MEMSIZE" : "%d" % info[1], + "NUMCPUS" : "%d" % info[2], + "CPUSPEED" : "%d" % info[3], + "HOSTNAME" : conn.getHostname(), + "HYPERVISOR_TYPE" : conn.getType() } - print(self.host_info) - self.server_name = server_name self.server_port = int(server_port) @@ -92,8 +91,8 @@ class IdentifyNode: print("Disconnecting from server") -if __name__ == '__main__': - +if __name__ == '__main__': + identifier = IdentifyNode(sys.argv[1], sys.argv[2]) identifier.start_conversation() diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 9519e7b..6eef6f4 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -Wall -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Darryl L. Pierce <dpierce at redhat.com> # @@ -41,7 +41,7 @@ class HostBrowser attr_accessor :logfile attr_accessor :keytab_dir attr_accessor :keytab_filename - + def initialize(session) @session = session @log_prefix = "[#{session.peeraddr[3]}] " @@ -78,7 +78,7 @@ class HostBrowser puts "#{@log_prefix} ::Received - #{key}:#{value}" result[key] = value - + @session.write("ACK #{key}\n") end @@ -101,21 +101,29 @@ class HostBrowser 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 + "uuid" => host_info['UUID'], + "hostname" => host_info['HOSTNAME'], + "hypervisor_type" => host_info['HYPERVISOR_TYPE'], + "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 + else + host.uuid = host_info['UUID'] + host.hostname = host_info['HOSTNAME'] + host.num_cpus = host_info['NUMCPUS'] + host.cpu_speed = host_info['CPUSPEED'] + host.arch = host_info['ARCH'] + host.memory = host_info['MEMSIZE'] end - + return host end @@ -132,12 +140,12 @@ class HostBrowser @session.write("BYE\n"); 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' @@ -174,9 +182,11 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - + puts "Connected to #{remote}" - + + database_connect + begin browser = HostBrowser.new(session) @@ -193,28 +203,16 @@ def entry_point(server) session.write("ERROR #{error.message}\n") puts "ERROR #{error.message}" end - - # session.shutdown(2) unless session.closed? puts "Disconnected from #{remote}" end - - Process.detach(child) - 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 + + entry_point(server) end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb index 2a05181..b5a616d 100755 --- a/wui/src/host-browser/test-host-browser.rb +++ b/wui/src/host-browser/test-host-browser.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -Wall -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Darryl L. Pierce <dpierce at redhat.com> # @@ -26,7 +26,7 @@ TESTING=true require 'host-browser' -class TestHostBrowser < Test::Unit::TestCase +class TestHostBrowser < Test::Unit::TestCase def setup @session = flexmock('session') @@ -50,7 +50,7 @@ class TestHostBrowser < Test::Unit::TestCase @host_info['DISABLED'] = '0' end - # Ensures that the server raises an exception when it receives an + # Ensures that the server raises an exception when it receives an # improper handshake response. # def test_begin_conversation_with_improper_response_to_greeting @@ -87,7 +87,7 @@ class TestHostBrowser < Test::Unit::TestCase 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 @@ -129,7 +129,7 @@ class TestHostBrowser < Test::Unit::TestCase assert info.include?("IPADDR") assert info.include?("key1") assert info.include?("key2") - end + end # Ensures the host browser generates a keytab as expected. # @@ -154,7 +154,7 @@ class TestHostBrowser < Test::Unit::TestCase # def test_write_host_info_with_missing_hostname @host_info['HOSTNAME'] = nil - + assert_raise(Exception) { @browser.write_host_info(@host_info) } end @@ -190,31 +190,15 @@ class TestHostBrowser < Test::Unit::TestCase 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, + # 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 +end -- 1.5.5.1