Darryl L. Pierce
2008-Jun-10 21:59 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
First crack at the awaken script. I haven't finished the keytab retrieving since I'm not sure what the URL will be based on the hostname, et. al. But, the process is all in bash and no dependency on Python. Feedback is requested. Please be gentle... :) Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- identify-node/awaken.sh | 83 ++++++++ wui/src/host-browser/host-browser.rb | 101 ++++++----- wui/src/host-browser/test-host-browser-awaken.rb | 93 +++++++++ wui/src/host-browser/test-host-browser-identify.rb | 204 ++++++++++++++++++++ wui/src/host-browser/test-host-browser.rb | 204 -------------------- 5 files changed, 437 insertions(+), 248 deletions(-) create mode 100755 identify-node/awaken.sh create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/identify-node/awaken.sh b/identify-node/awaken.sh new file mode 100755 index 0000000..7148f1c --- /dev/null +++ b/identify-node/awaken.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# +# 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. + +SERVER=$1 +PORT=$2 + + +if [ -z $SERVER ]; then + dnsreply=$(dig +short -t srv _ovirt-host._tcp.$(dnsdomainname)) + if [ $? -eq 0 ]; then + set _ $dnsreply; shift + SERVER=$4; PORT=$3 + else + SERVER=; PORT+ fi +fi + +if [ -z $SERVER ]; then + echo "No server found..." + exit +fi + +echo "Connecting to $SERVER:$PORT"... + +exec 3<> /dev/tcp/$SERVER/$PORT + +read 0<&3 + +echo "Received: $REPLY" + +if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + echo "HELLO!" 1>&3 + + read 0<&3 + + echo "Received: $REPLY" + + if [ $REPLY == "MODE?" ]; then + echo "Sending wakeup notice." + echo "AWAKEN" 1>&3 + + read 0<&3 + + echo "Received: $REPLY" + + KEYTAB=`echo $REPLY | awk '{ $2 }'` + + if [ -n $KEYTAB ]; then + echo "Retrieving keytab: $KYTAB" + + + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi +else + echo "Did not get a proper startup marker." +fi + +echo "Disconnecting." + +<&3- diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..d3e9d85 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]}] " @@ -51,17 +51,28 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") @@ -75,9 +86,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +105,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +126,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +136,43 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + @session.write("KTAB #{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +194,39 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + end + #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}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5a1bcf --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,93 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + @session.should_receive(:write).with("KTAB 127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..6f4c660 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,204 @@ +#!/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, 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("KTAB 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 } + + assert_nothing_raised(Exception) { @browser.end_conversation(12345) } + end + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1
Darryl L. Pierce
2008-Jun-11 16:31 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- identify-node/ovirt-awake | 80 ++++++++ identify-node/ovirt-functions | 66 +++++++ wui/src/host-browser/host-browser.rb | 103 ++++++----- wui/src/host-browser/test-host-browser-awaken.rb | 93 +++++++++ wui/src/host-browser/test-host-browser-identify.rb | 204 ++++++++++++++++++++ wui/src/host-browser/test-host-browser.rb | 204 -------------------- 6 files changed, 501 insertions(+), 249 deletions(-) create mode 100755 identify-node/ovirt-awake create mode 100644 identify-node/ovirt-functions create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/identify-node/ovirt-awake b/identify-node/ovirt-awake new file mode 100755 index 0000000..7f654e6 --- /dev/null +++ b/identify-node/ovirt-awake @@ -0,0 +1,80 @@ +#!/bin/bash +# +# ovirt-awake Notifies the oVirt server that a managed node is +# starting up. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "AWAKEN" + + receive-text + + KEYTAB=`echo $REPLY | awk '{ $2 }'` + + if [ -n $KEYTAB ]; then + wget http://$SERVER/$KEYTAB + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server +} + +case "$1" in + start) + SERVER=$2 + PORT=$3 + PORT=$3 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/identify-node/ovirt-functions b/identify-node/ovirt-functions new file mode 100644 index 0000000..b6c9a0a --- /dev/null +++ b/identify-node/ovirt-functions @@ -0,0 +1,66 @@ +# +# 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. + +# Determines the hostname and port for the oVirt server. +# +find-server() { + if [ -z $SERVER ]; then + dnsreply=$(dig +short -t srv _ovirt-host._tcp.$(dnsdomainname)) + if [ $? -eq 0 ]; then + set _ $dnsreply; shift + SERVER=$4; PORT=$3 + else + SERVER=; PORT+ fi + fi + + if [ -z $SERVER ]; then + echo "No server found..." + exit + fi +} + +# Establishes a TCP connection to the server. +# +connect-to-server () { + echo "Connecting to $SERVER:$PORT"... + + exec 3<> /dev/tcp/$SERVER/$PORT +} + +# Disconnects form the server. +# +disconnect-from-server () { + <&3- +} + +# Sends text to the remote server. +# +send-text () { + echo "Sending: \"$1\"" + echo "$1" 1>&3 +} + +# Receives text from the remote server. +# +receive-text () { + read 0<&3 + + echo "Received: \"$REPLY\"" +} diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..8337d7b 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]}] " @@ -51,17 +51,28 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") @@ -75,9 +86,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +105,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +126,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +136,43 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + @session.write("KTAB #{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +194,39 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + end + #host_info = browser.get_remote_info + #browser.write_host_info(host_info) + #keytab = browser.create_keytab(host_info) + + browser.end_conversation rescue Exception => error session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5a1bcf --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,93 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + @session.should_receive(:write).with("KTAB 127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..6f4c660 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,204 @@ +#!/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, 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("KTAB 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 } + + assert_nothing_raised(Exception) { @browser.end_conversation(12345) } + end + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1
Darryl L. Pierce
2008-Jun-12 15:33 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- ovirt-host-creator/common-post.ks | 347 ++++++++++++++------ wui/src/host-browser/host-browser.rb | 106 ++++--- wui/src/host-browser/test-host-browser-awaken.rb | 94 ++++++ wui/src/host-browser/test-host-browser-identify.rb | 162 +++++++++ wui/src/host-browser/test-host-browser.rb | 204 ------------ 5 files changed, 556 insertions(+), 357 deletions(-) create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index 1cb1677..aaeb746 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -12,12 +12,15 @@ cat > /etc/sysconfig/iptables << \EOF COMMIT EOF -echo "Writing ovirt-identify-node script" -cat > /sbin/ovirt-identify-node << \EOF +echo "Writing ovirt-awake script" +cat > /sbin/ovirt-awake << \EOF #!/bin/bash # +# ovirt-awake Notifies the oVirt server that a managed node is +# starting up. +# # Copyright (C) 2008 Red Hat, Inc. -# Written by Chris Lalancette <clalance at redhat.com> +# 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 @@ -34,109 +37,239 @@ cat > /sbin/ovirt-identify-node << \EOF # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -ME=$(basename "$0") -warn() { printf "$ME: $@\n" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat <<EOF2 -Usage: $ME [-s server] [-p port] - -h: display this help and exit - -p: Port number the host-browser is listening on - -s: Hostname of the server to connect to -EOF2 +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "AWAKEN" + + receive-text + + KEYTAB=`echo $REPLY | awk '{ print $2 }'` + + if [ -n $KEYTAB ]; then + echo "Retrieving keytab: '$KEYTAB'" + + wget $KEYTAB --output-file=$KEYTAB_FILE + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server } -send_key_value() { - echo "$1=$2" 1>&3 +case "$1" in + start) + KEYTAB_FILE=$2 + SERVER=$3 + PORT=$4 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL +EOF +chmod +x /sbin/ovirt-awake + +echo "Writing ovirt-identify script" +cat > /sbin/ovirt-identify << \EOF +#!/bin/bash +# +# ovirt-identify Submits managed node details to the central server. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "IDENTIFY" + + receive-text + + UUID=`hostname -f` + ARCH=`uname -i` + NUMCPUS=`getconf _NPROCESSORS_ONLN` + CPUSPEED=`grep "cpu MHz" /proc/cpuinfo | uniq | cut -d':' -f2 | sed -e 's/^[[:space:]]*\(.*\)$/\1/' -e 's/^\(.*\)[[:space:]]*$/\1/'` + MEMSIZE=$((`getconf _PHYS_PAGES` * `getconf PAGESIZE` / 1024 / 1024)) - read 0<&3 - test "$REPLY" != "ACK $1" && die "Failed acknowledge of key $1" + send-text "UUID=$UUID" + receive-text + send-text "ARCH=$ARCH" + receive-text + send-text "NUMCPUS=$NUMCPUS" + receive-text + send-text "CPUSPEED=$CPUSPEED" + receive-text + send-text "MEMSIZE=$MEMSIZE" + receive-text + send-text "ENDINFO" + + receive-text + + if [ $REPLY == "BYE" ]; then + echo "Success!" + else + echo "Error submitting node information..." + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server } -############# MAIN ################## - -# parse our options -while getopts ":hs:p:" flag ; do - case "$flag" in - h) - usage ; exit 0 - ;; - s) - server=$OPTARG - ;; - p) - port=$OPTARG - ;; - ?) - usage "Unknown flag $flag" - ;; - esac -done +case "$1" in + start) + SERVER=$2 + PORT=$3 + PORT=$3 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac -test $(( $# - $OPTIND )) -ge 0 && usage "Too many options" -test -z "$server" && usage "Must specify -s" -test -z "$port" && usage "Must specify -p" - -# gather our information -all_ok=0 -uuid=$(hostname -f) && - arch=$(uname -i) && - memsize=$(( $(getconf _PHYS_PAGES) * $(getconf PAGESIZE) / 1024 / 1024 )) && - numcpus=$(getconf _NPROCESSORS_ONLN) && - speed=$(sed -n "/cpu MHz/{s/.*://p;q;}" /proc/cpuinfo | tr -dc 0-9.) && - hostname=$(hostname -f) && - hypervisor="QEMU" && all_ok=1 - -test $all_ok = 1 || die "Information gathering failed...see above" - -# open our connection to the remote host -eval 'exec 3<> /dev/tcp/$server/$port' 2>err -test $? -ne 0 && die "Connection to $server:$port failed: $(cat err)" - -# say hello -read 0<&3 -test "$REPLY" != "HELLO?" && die "Expected response HELLO?, received response $REPLY" -echo "HELLO!" 1>&3 - -# OK, start sending our information -read 0<&3 -test "$REPLY" != "INFO?" && die "Expected response INFO?, received response $REPLY" - -send_key_value "UUID" "$uuid" -send_key_value "ARCH" "$arch" -send_key_value "MEMSIZE" "$memsize" -send_key_value "NUMCPUS" "$numcpus" -send_key_value "CPUSPEED" "$speed" -send_key_value "HOSTNAME" "$hostname" -send_key_value "HYPERVISOR_TYPE" "$hypervisor" - -echo "ENDINFO" 1>&3 - -read 0<&3 - -test "${REPLY:0:4}" != "KTAB" && die "Expected response KTAB <filename>, received response $REPLY" -echo "${REPLY:5}" +exit $RETVAL EOF -chmod +x /sbin/ovirt-identify-node +chmod +x /sbin/ovirt-identify echo "Writing ovirt-functions script" # common functions cat > /etc/init.d/ovirt-functions << \EOF -# -*-Shell-script-*- +# 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. -find_srv() { - local dnsreply +# Determines the hostname and port for the oVirt server. +# +find-server() { + if [ -z $SERVER ]; then dnsreply=$(dig +short -t srv _$1._$2.$(dnsdomainname)) if [ $? -eq 0 ]; then set _ $dnsreply; shift - SRV_HOST=$4; SRV_PORT=$3 + SERVER=$4; PORT=$3 else - SRV_HOST=; SRV_PORT+ SERVER=; PORT fi + fi + + if [ -z $SERVER ]; then + echo "No server found..." + exit + fi } + +# Establishes a TCP connection to the server. +# +connect-to-server () { + echo "Connecting to $SERVER:$PORT"... + + exec 3<> /dev/tcp/$SERVER/$PORT +} + +# Disconnects form the server. +# +disconnect-from-server () { + <&3- +} + +# Sends text to the remote server. +# +send-text () { + echo "Sending: \"$1\"" + echo "$1" 1>&3 +} + +# Receives text from the remote server. +# +receive-text () { + read 0<&3 + + echo "Received: \"$REPLY\"" +} + EOF echo "Writing ovirt-early init script" @@ -168,7 +301,7 @@ configure_from_network() { if [ "$status" = "0" ]; then hostname $HOSTNAME # retrieve remote config - find_srv ovirt tcp + find-server ovirt tcp printf . if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then curl -s "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" \ @@ -219,19 +352,27 @@ start() { # now LVM partitions LVMDEVS="$DEVICES `lvscan | awk '{print $2}' | tr -d \"'\"`" - SWAPDEVS="$LVMDEVS" + SWAPDEVS="$LVMDEVS" for dev in $BLOCKDEVS; do SWAPDEVS="$SWAPDEVS `fdisk -l $dev 2>/dev/null | tr '*' ' ' \ - | awk '$5 ~ /82/ {print $1}'`" + | awk '$5 ~ /82/ {print $1}'`" done - # now check if any of these partitions are swap, and activate if so + # now check if any of these partitions are swap, and activate if so for device in $SWAPDEVS; do sig=`dd if=$device bs=1 count=10 skip=$(( $PAGESIZE - 10 )) \ - 2>/dev/null` + 2>/dev/null` if [ "$sig" = "SWAPSPACE2" ]; then swapon $device fi + + # Notify the server we're awake and retrieve a keytab file + krb5_tab=/etc/libvirt/krb5.tab + if [ ! -s $krb5_tab ]; then + ovirt-awake start start $krb5_tab + || die "Failed to notify server the node is awake" + fi + done } @@ -283,7 +424,7 @@ die() start() { echo -n $"Starting ovirt: " - find_srv ipa tcp + find-server ipa tcp krb5_conf=/etc/krb5.conf if [ ! -s $krb5_conf ]; then rm -f $krb5_conf @@ -294,17 +435,9 @@ start() { IPA_HOST=$SRV_HOST IPA_PORT=$SRV_PORT - find_srv identify tcp - krb5_tab=/etc/libvirt/krb5.tab - if [ ! -s $krb5_tab ]; then - keytab=$(ovirt-identify-node -s $SRV_HOST -p $SRV_PORT) \ - || die "Failed to identify node" - # FIXME this is IPA specific, host-browser should return full URL - wget -q "http://$IPA_HOST:$IPA_PORT/config/$keytab" -O $krb5_tab \ - || die "Failed to get $krb5_tab" - fi + ovirt-identify start - find_srv collectd tcp + find-server collectd tcp collectd_conf=/etc/collectd.conf if [ -f $collectd_conf.in -a $SRV_HOST -a $SRV_PORT ]; then sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ @@ -415,8 +548,8 @@ LoadPlugin disk </Plugin> <Plugin interface> - Interface "eth0" - IgnoreSelected false + Interface "eth0" + IgnoreSelected false </Plugin> EOF diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..3e242cf 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]}] " @@ -51,19 +51,31 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} - result['IPADDR'] = @session.peeraddr[3] + result['HOSTNAME'] = @session.peeraddr[2] + result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") loop do @@ -75,9 +87,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +106,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +127,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +137,45 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + hostname = `hostname -f`.chomp + + @session.write("KTAB http://#{hostname}/config/#{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +197,37 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + when "IDENTIFY": browser.write_host_info(browser.get_remote_info) + end + + browser.end_conversation rescue Exception => error session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5ca2e7 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,94 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + servername = `hostname -f`.chomp + @session.should_receive(:write).with("KTAB http://#{servername}/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..a70884d --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,162 @@ +#!/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"] } + + @browser = HostBrowser.new(@session) + @browser.logfile = './unit-test.log' + + # 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "IDENTIFY\n" } + + result = @browser.get_mode() + + assert_equal "IDENTIFY", result, "method did not return the right value" + 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 4,info.keys.size, "Should contain two keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + assert info.include?("key1") + assert info.include?("key2") + 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 + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1
Darryl L. Pierce
2008-Jun-16 14:35 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- identify-node/Makefile | 35 ++ identify-node/ovirt-identify.c | 326 ++++++++++++++++++ ovirt-host-creator/common-post.ks | 346 ++++++++++++++------ wui/src/host-browser/host-browser.rb | 106 ++++--- wui/src/host-browser/test-host-browser-awaken.rb | 94 ++++++ wui/src/host-browser/test-host-browser-identify.rb | 162 +++++++++ wui/src/host-browser/test-host-browser.rb | 204 ------------ 7 files changed, 916 insertions(+), 357 deletions(-) create mode 100644 identify-node/Makefile create mode 100644 identify-node/ovirt-identify.c create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/identify-node/Makefile b/identify-node/Makefile new file mode 100644 index 0000000..af950b2 --- /dev/null +++ b/identify-node/Makefile @@ -0,0 +1,35 @@ +# 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. + +CC=gcc +CFLAGS=-Wall -c -g +LFLAGS=-lvirt +OBJECTS=ovirt-identify.o +TARGET=ovirt-identify + +ALL: $(TARGET) + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -rf $(OBJECTS) $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $(OBJECTS) $(LFLAGS) + diff --git a/identify-node/ovirt-identify.c b/identify-node/ovirt-identify.c new file mode 100644 index 0000000..915b50a --- /dev/null +++ b/identify-node/ovirt-identify.c @@ -0,0 +1,326 @@ +/* identify-node -- Main entry point for the identify-node utility. + * + * 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. + */ + +#include <getopt.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libvirt/libvirt.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/socket.h> + +int config(int argc,char** argv); +void usage(void); + +int start_conversation(void); +int send_details(void); +int end_conversation(void); + +int send_text(char* text); +int get_text(char* response); + +int debug = 0; +int verbose = 0; +int testing = 0; + +char arch[128]; +char uuid[128]; +char memsize[128]; +char numcpus[128]; +char cpuspeed[128]; +char hostname[256]; +int hostport = -1; +int socketfd; + +int main(int argc,char** argv) +{ + int result = 1; + + if(!config(argc,argv)) + { + int success = 1; + + if(!testing) + { + success = 0; + + fprintf(stdout,"Connecting to libvirt.\n"); + virConnectPtr connection = virConnectOpenReadOnly(NULL); + + if(debug) printf("connection=%ld\n",(long int)connection); + + if(connection) + { + virNodeInfo info; + + if(debug) printf("Retrieving node information.\n"); + if(!virNodeGetInfo(connection,&info)) + { + success = 1; + + gethostname(uuid,128); + sprintf(arch, "%s", info.model); + sprintf(memsize, "%ld", info.memory); + sprintf(numcpus, "%d", info.cpus); + sprintf(cpuspeed,"%d", info.mhz); + + if(debug) + { + printf("Node Info:\n"); + printf(" UUID: %s\n", uuid); + printf(" Arch: %s\n", arch); + printf(" Memory: %s\n", memsize); + printf(" # CPUs: %s\n", numcpus); + printf("CPU Speed: %s\n", cpuspeed); + } + } + else + { + if(debug) printf("Failed to get node info.\n"); + } + } + else + { + if(debug) printf("Could not connect to libvirt.\n"); + } + } + else + { + strcpy(uuid,"UUID-FOR-TESTING-PURPOSES"); + strcpy(arch,"x86_64"); + strcpy(memsize,"2048"); + strcpy(numcpus,"2"); + strcpy(cpuspeed,"2048"); + } + + if(success && !start_conversation() && !send_details() && !end_conversation()) result = 0; + } + else + { + usage(); + } + + return result; +} + +int config(int argc,char** argv) +{ + int result = 0; + int option; + + while((option = getopt(argc,argv,"s:p:dvth")) != -1) + { + if(debug) printf("Processing argument: %c (optarg:%s)\n",option,optarg); + + switch(option) + { + case 's': strcpy(hostname,optarg); break; + case 'p': hostport = atoi(optarg); break; + case 't': testing = 1; break; + case 'd': debug = 1; break; + case 'v': verbose = 1; break; + case 'h': + // fall thru + default : result = 1; break; + } + } + + // verify that required options are provided + if(hostname == NULL || strlen(hostname) == 0) + { + printf("ERROR: The server name is required. (-s [hostname])\n"); + result = 1; + } + + if(hostport <= 0) + { + printf("ERROR: The server port is required. (-p [port])\n"); + result = 1; + } + + return result; +} + +void usage() +{ + printf("\n"); + printf("Usage: ovirt-identify [OPTION]\n"); + printf("\n"); + printf("\t-s [server]\t\tThe remote server's hostname.\n"); + printf("\t-p [port]\t\tThe remote server's port.\n"); + printf("\t-d\t\tDisplays debug information during execution.\n"); + printf("\t-v\t\tDisplays verbose information during execution.\n"); + printf("\t-h\t\tDisplays this help information and then exits.\n"); + printf("\n"); +} + +int start_conversation(void) +{ + int result = 1; + + if(verbose || debug) printf("Starting conversation with %s:%d.\n",hostname,hostport); + + if(debug) printf("Creating the socket.\n"); + socketfd = socket(AF_INET, SOCK_STREAM, 0); + + if(socketfd >= 0) + { + fprintf(stdout,"Created socket: socketfd=%d.\n", socketfd); + + struct hostent* server = gethostbyname(hostname); + + if(server) + { + if(debug) printf("Server reference created: server=%x\n", (unsigned int )server); + struct sockaddr_in address; + + bzero((char* ) &address, sizeof(address)); + address.sin_family = AF_INET; + bcopy((char* )server->h_addr, (char* )&address.sin_addr.s_addr, server->h_length); + address.sin_port = hostport; + + int connected = connect(socketfd, &address, sizeof(address)); + + if(debug) printf("connected=%d\n", connected); + + if(connected >= 0) + { + if(debug || verbose) printf("Connected.\n"); + + char buffer[128]; + + get_text(buffer); + + if(strcmp(buffer,"HELLO?")) + { + if(send_text("HELLO!\n")) result = 0; + } + } + else + { + perror("ERROR"); + } + } + else + { + fprintf(stdout,"Invalid hostname: %s\n", hostname); + } + } + else + { + fprintf(stdout,"Failed to connect to server: %s:%d\n", hostname, hostport); + } + + if(debug) printf("start_conversation: result=%d\n", result); + + return result; +} + +int send_value(char* label,char* value) +{ + char buffer[128]; + + bzero(buffer,128); + + sprintf(buffer,"%s=%s\n", label, value); + + int result = 0; + + if(send_text(buffer)) + { + char expected[128]; + + bzero(expected,128); + bzero(buffer,128); + + get_text(buffer); + + sprintf(expected, "ACK %s", label); + + if(strcmp(expected,buffer)) + { + result = 1; + } + } + + return result; +} + +int send_details(void) +{ + int result = 0; + + fprintf(stdout,"Sending node details.\n"); + + if((send_value("ARCH", arch)) && + (send_value("UUID", uuid)) && + (send_value("NUMCPUS", numcpus)) && + (send_value("CPUSPEED", cpuspeed)) && + (send_value("MEMSIZE", memsize))) + { + result = 1; + } + + return result; +} + +int end_conversation(void) +{ + int result = 0; + + fprintf(stdout,"Ending conversation.\n"); + + send_text("ENDINFO"); + + close(socketfd); + + return result; +} + +int send_text(char* text) +{ + int result = 1; + + if(debug || verbose) printf("\"%s\" -> %s:%d\n", text, hostname, hostport); + + int sent = write(socketfd,text,strlen(text)); + + if(sent >= 0) + { + if(debug) printf("Sent %d bytes total.\n", sent); + + result = 0; + } + + return result; +} + +int get_text(char* response) +{ + int received = read(socketfd,response,sizeof(response)); + + if(debug) printf("Received \"%s\": size=%d\n", response, received); + + return received; +} diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index d8acab8..a9e4475 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -12,12 +12,15 @@ cat > /etc/sysconfig/iptables << \EOF COMMIT EOF -echo "Writing ovirt-identify-node script" -cat > /sbin/ovirt-identify-node << \EOF +echo "Writing ovirt-awake script" +cat > /sbin/ovirt-awake << \EOF #!/bin/bash # +# ovirt-awake Notifies the oVirt server that a managed node is +# starting up. +# # Copyright (C) 2008 Red Hat, Inc. -# Written by Chris Lalancette <clalance at redhat.com> +# 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 @@ -34,109 +37,239 @@ cat > /sbin/ovirt-identify-node << \EOF # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -ME=$(basename "$0") -warn() { printf "$ME: $@\n" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat <<EOF2 -Usage: $ME [-s server] [-p port] - -h: display this help and exit - -p: Port number the host-browser is listening on - -s: Hostname of the server to connect to -EOF2 +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "AWAKEN" + + receive-text + + KEYTAB=`echo $REPLY | awk '{ print $2 }'` + + if [ -n $KEYTAB ]; then + echo "Retrieving keytab: '$KEYTAB'" + + wget $KEYTAB --output-document=$KEYTAB_FILE + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server } -send_key_value() { - echo "$1=$2" 1>&3 +case "$1" in + start) + KEYTAB_FILE=$2 + SERVER=$3 + PORT=$4 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL +EOF +chmod +x /sbin/ovirt-awake + +echo "Writing ovirt-identify script" +cat > /sbin/ovirt-identify << \EOF +#!/bin/bash +# +# ovirt-identify Submits managed node details to the central server. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "IDENTIFY" + + receive-text + + UUID=`hostname -f` + ARCH=`uname -i` + NUMCPUS=`getconf _NPROCESSORS_ONLN` + CPUSPEED=`grep "cpu MHz" /proc/cpuinfo | uniq | cut -d':' -f2 | sed -e 's/^[[:space:]]*\(.*\)$/\1/' -e 's/^\(.*\)[[:space:]]*$/\1/'` + MEMSIZE=$((`getconf _PHYS_PAGES` * `getconf PAGESIZE` / 1024 / 1024)) - read 0<&3 - test "$REPLY" != "ACK $1" && die "Failed acknowledge of key $1" + send-text "UUID=$UUID" + receive-text + send-text "ARCH=$ARCH" + receive-text + send-text "NUMCPUS=$NUMCPUS" + receive-text + send-text "CPUSPEED=$CPUSPEED" + receive-text + send-text "MEMSIZE=$MEMSIZE" + receive-text + send-text "ENDINFO" + + receive-text + + if [ $REPLY == "BYE" ]; then + echo "Success!" + else + echo "Error submitting node information..." + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server } -############# MAIN ################## - -# parse our options -while getopts ":hs:p:" flag ; do - case "$flag" in - h) - usage ; exit 0 - ;; - s) - server=$OPTARG - ;; - p) - port=$OPTARG - ;; - ?) - usage "Unknown flag $flag" - ;; - esac -done +case "$1" in + start) + SERVER=$2 + PORT=$3 + PORT=$3 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac -test $(( $# - $OPTIND )) -ge 0 && usage "Too many options" -test -z "$server" && usage "Must specify -s" -test -z "$port" && usage "Must specify -p" - -# gather our information -all_ok=0 -uuid=$(hostname -f) && - arch=$(uname -i) && - memsize=$(( $(getconf _PHYS_PAGES) * $(getconf PAGESIZE) / 1024 / 1024 )) && - numcpus=$(getconf _NPROCESSORS_ONLN) && - speed=$(sed -n "/cpu MHz/{s/.*://p;q;}" /proc/cpuinfo | tr -dc 0-9.) && - hostname=$(hostname -f) && - hypervisor="QEMU" && all_ok=1 - -test $all_ok = 1 || die "Information gathering failed...see above" - -# open our connection to the remote host -eval 'exec 3<> /dev/tcp/$server/$port' 2>err -test $? -ne 0 && die "Connection to $server:$port failed: $(cat err)" - -# say hello -read 0<&3 -test "$REPLY" != "HELLO?" && die "Expected response HELLO?, received response $REPLY" -echo "HELLO!" 1>&3 - -# OK, start sending our information -read 0<&3 -test "$REPLY" != "INFO?" && die "Expected response INFO?, received response $REPLY" - -send_key_value "UUID" "$uuid" -send_key_value "ARCH" "$arch" -send_key_value "MEMSIZE" "$memsize" -send_key_value "NUMCPUS" "$numcpus" -send_key_value "CPUSPEED" "$speed" -send_key_value "HOSTNAME" "$hostname" -send_key_value "HYPERVISOR_TYPE" "$hypervisor" - -echo "ENDINFO" 1>&3 - -read 0<&3 - -test "${REPLY:0:4}" != "KTAB" && die "Expected response KTAB <filename>, received response $REPLY" -echo "${REPLY:5}" +exit $RETVAL EOF -chmod +x /sbin/ovirt-identify-node +chmod +x /sbin/ovirt-identify echo "Writing ovirt-functions script" # common functions cat > /etc/init.d/ovirt-functions << \EOF -# -*-Shell-script-*- +# 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. -find_srv() { - local dnsreply +# Determines the hostname and port for the oVirt server. +# +find-server() { + if [ -z $SERVER ]; then dnsreply=$(dig +short -t srv _$1._$2.$(dnsdomainname)) if [ $? -eq 0 ]; then set _ $dnsreply; shift - SRV_HOST=$4; SRV_PORT=$3 + SERVER=$4; PORT=$3 else - SRV_HOST=; SRV_PORT+ SERVER=; PORT fi + fi + + if [ -z $SERVER ]; then + echo "No server found..." + exit + fi } + +# Establishes a TCP connection to the server. +# +connect-to-server () { + echo "Connecting to $SERVER:$PORT"... + + exec 3<> /dev/tcp/$SERVER/$PORT +} + +# Disconnects form the server. +# +disconnect-from-server () { + <&3- +} + +# Sends text to the remote server. +# +send-text () { + echo "Sending: \"$1\"" + echo "$1" 1>&3 +} + +# Receives text from the remote server. +# +receive-text () { + read 0<&3 + + echo "Received: \"$REPLY\"" +} + EOF echo "Writing ovirt-early init script" @@ -168,7 +301,7 @@ configure_from_network() { if [ "$status" = "0" ]; then hostname $HOSTNAME # retrieve remote config - find_srv ovirt tcp + find-server ovirt tcp printf . if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then wget --quiet -O - "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" \ @@ -219,19 +352,26 @@ start() { # now LVM partitions LVMDEVS="$DEVICES `lvscan | awk '{print $2}' | tr -d \"'\"`" - SWAPDEVS="$LVMDEVS" + SWAPDEVS="$LVMDEVS" for dev in $BLOCKDEVS; do SWAPDEVS="$SWAPDEVS `fdisk -l $dev 2>/dev/null | tr '*' ' ' \ - | awk '$5 ~ /82/ {print $1}'`" + | awk '$5 ~ /82/ {print $1}'`" done - # now check if any of these partitions are swap, and activate if so + # now check if any of these partitions are swap, and activate if so for device in $SWAPDEVS; do sig=`dd if=$device bs=1 count=10 skip=$(( $PAGESIZE - 10 )) \ - 2>/dev/null` + 2>/dev/null` if [ "$sig" = "SWAPSPACE2" ]; then swapon $device fi + + # Notify the server we're awake and retrieve a keytab file + krb5_tab=/etc/libvirt/krb5.tab + if [ ! -s $krb5_tab ]; then + ovirt-awake start $krb5_tab + fi + done } @@ -283,7 +423,7 @@ die() start() { echo -n $"Starting ovirt: " - find_srv ipa tcp + find-server ipa tcp krb5_conf=/etc/krb5.conf if [ ! -s $krb5_conf ]; then rm -f $krb5_conf @@ -294,17 +434,9 @@ start() { IPA_HOST=$SRV_HOST IPA_PORT=$SRV_PORT - find_srv identify tcp - krb5_tab=/etc/libvirt/krb5.tab - if [ ! -s $krb5_tab ]; then - keytab=$(ovirt-identify-node -s $SRV_HOST -p $SRV_PORT) \ - || die "Failed to identify node" - # FIXME this is IPA specific, host-browser should return full URL - wget -q "http://$IPA_HOST:$IPA_PORT/config/$keytab" -O $krb5_tab \ - || die "Failed to get $krb5_tab" - fi + ovirt-identify start - find_srv collectd tcp + find-server collectd tcp collectd_conf=/etc/collectd.conf if [ -f $collectd_conf.in -a $SRV_HOST -a $SRV_PORT ]; then sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ @@ -415,8 +547,8 @@ LoadPlugin disk </Plugin> <Plugin interface> - Interface "eth0" - IgnoreSelected false + Interface "eth0" + IgnoreSelected false </Plugin> EOF diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..3e242cf 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]}] " @@ -51,19 +51,31 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} - result['IPADDR'] = @session.peeraddr[3] + result['HOSTNAME'] = @session.peeraddr[2] + result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") loop do @@ -75,9 +87,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +106,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +127,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +137,45 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + hostname = `hostname -f`.chomp + + @session.write("KTAB http://#{hostname}/config/#{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +197,37 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + when "IDENTIFY": browser.write_host_info(browser.get_remote_info) + end + + browser.end_conversation rescue Exception => error session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5ca2e7 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,94 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + servername = `hostname -f`.chomp + @session.should_receive(:write).with("KTAB http://#{servername}/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..a70884d --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,162 @@ +#!/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"] } + + @browser = HostBrowser.new(@session) + @browser.logfile = './unit-test.log' + + # 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "IDENTIFY\n" } + + result = @browser.get_mode() + + assert_equal "IDENTIFY", result, "method did not return the right value" + 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 4,info.keys.size, "Should contain two keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + assert info.include?("key1") + assert info.include?("key2") + 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 + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1
Darryl L. Pierce
2008-Jun-16 20:37 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
--- ovirt-daemons/Makefile | 38 ++ ovirt-daemons/identify-node.spec | 14 + ovirt-daemons/ovirt-awake | 82 ++++ ovirt-daemons/ovirt-daemons.spec | 38 ++ ovirt-daemons/ovirt-identify | 46 +++ ovirt-daemons/ovirt-identify.c | 407 ++++++++++++++++++++ ovirt-host-creator/Makefile | 5 +- ovirt-host-creator/common-post.ks | 179 +++------ wui/src/host-browser/host-browser.rb | 106 +++--- wui/src/host-browser/test-host-browser-awaken.rb | 94 +++++ wui/src/host-browser/test-host-browser-identify.rb | 162 ++++++++ wui/src/host-browser/test-host-browser.rb | 204 ---------- 12 files changed, 1006 insertions(+), 369 deletions(-) create mode 100644 ovirt-daemons/Makefile create mode 100644 ovirt-daemons/identify-node.spec create mode 100644 ovirt-daemons/ovirt-awake create mode 100644 ovirt-daemons/ovirt-daemons.spec create mode 100644 ovirt-daemons/ovirt-identify create mode 100644 ovirt-daemons/ovirt-identify.c create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/ovirt-daemons/Makefile b/ovirt-daemons/Makefile new file mode 100644 index 0000000..1657a67 --- /dev/null +++ b/ovirt-daemons/Makefile @@ -0,0 +1,38 @@ +# 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. + +CC=gcc +CFLAGS=-Wall -c -g +LFLAGS=-lvirt +OBJECTS=ovirt-identify.o +TARGET=ovirt-identify-node +SPECFILE=ovirt-daemons.spec + +ALL: $(TARGET) + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -rf $(OBJECTS) $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $(OBJECTS) $(LFLAGS) + +rpms: $(TARGET) + rpmbuild -ba --rebuild $(SPECFILE) diff --git a/ovirt-daemons/identify-node.spec b/ovirt-daemons/identify-node.spec new file mode 100644 index 0000000..7f31db9 --- /dev/null +++ b/ovirt-daemons/identify-node.spec @@ -0,0 +1,14 @@ +Summary: oVirt managed node identification utility +Name: ovirt-daemons + + + +Source1: version +Version: %(echo `awk '{ print $1 }' %{SOURCE1}`) +Release: %(echo `awk '{ print $2 }' %{SOURCE1}`)%{?dist} +Source0: %{name}-%{version}.tar +License: Fedora +Group: Applications/System +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +URL: http://ovirt.org/ + diff --git a/ovirt-daemons/ovirt-awake b/ovirt-daemons/ovirt-awake new file mode 100644 index 0000000..30bf576 --- /dev/null +++ b/ovirt-daemons/ovirt-awake @@ -0,0 +1,82 @@ +#!/bin/bash +# +# ovirt-awake Notifies the oVirt server that a managed node is +# starting up. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "AWAKEN" + + receive-text + + KEYTAB=`echo $REPLY | awk '{ print $2 }'` + + if [ -n $KEYTAB ]; then + echo "Retrieving keytab: '$KEYTAB'" + + wget $KEYTAB --output-document=$KEYTAB_FILE + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server +} + +case "$1" in + start) + KEYTAB_FILE=$2 + SERVER=$3 + PORT=$4 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/ovirt-daemons/ovirt-daemons.spec b/ovirt-daemons/ovirt-daemons.spec new file mode 100644 index 0000000..eae83cc --- /dev/null +++ b/ovirt-daemons/ovirt-daemons.spec @@ -0,0 +1,38 @@ +Summary: oVirt managed node daemons. +Name: oVirt-daemons +Version: 1.0.0 +Release: 1%{?dist} +Group: Applications/System +License: Fedora +BuildRequires: libvirt-devel + +%description +Provides the daemons required to interact with the oVirt management +system. + + +%prep + + +%build + + +%install + +rm -rf $RPM_BUILD_ROOT +mkdir $RPM_BUILD_ROOT + +install -m755 ovirt-identify-node $RPM_BUILD_ROOT/sbin +install -m755 ovirt-awake $RPM_BUILD_ROOT/sbin +install -m755 ovirt-identify $RPM_BUILD_ROOT/sbin + + +%clean + +rm -rf %{buildroot} + +%files +%defattr(-,root,root) +/sbin/ovirt-identify-node +/sbin/ovirt-awake +/sbin/ovirt-identify diff --git a/ovirt-daemons/ovirt-identify b/ovirt-daemons/ovirt-identify new file mode 100644 index 0000000..58651b7 --- /dev/null +++ b/ovirt-daemons/ovirt-identify @@ -0,0 +1,46 @@ +#!/bin/bash +# +# ovirt-identify Submits managed node details to the central server. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + /sbin/ovirt-identify-node -s $SERVER -p $PORT +} + +case "$1" in + start) + SERVER=$2 + PORT=$3 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/ovirt-daemons/ovirt-identify.c b/ovirt-daemons/ovirt-identify.c new file mode 100644 index 0000000..7a7ec05 --- /dev/null +++ b/ovirt-daemons/ovirt-identify.c @@ -0,0 +1,407 @@ +/* identify-node -- Main entry point for the identify-node utility. + * + * 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. + */ + +#include <getopt.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libvirt/libvirt.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/socket.h> + +int config(int argc,char** argv); +void usage(void); + +int start_conversation(void); +int send_details(void); +int end_conversation(void); + +int send_text(char* text); +int get_text(char* response,int maxlength); +int create_connection(void); + +int debug = 0; +int verbose = 0; +int testing = 0; + +char arch[128]; +char uuid[128]; +char memsize[128]; +char numcpus[128]; +char cpuspeed[128]; +char hostname[256]; +int hostport = -1; +int socketfd; + +int main(int argc,char** argv) +{ + int result = 1; + + if(!config(argc,argv)) + { + int success = 1; + + if(!testing) + { + success = 0; + + fprintf(stdout,"Connecting to libvirt.\n"); + virConnectPtr connection = virConnectOpenReadOnly(NULL); + + if(debug) printf("connection=%ld\n",(long int)connection); + + if(connection) + { + virNodeInfo info; + + if(debug) printf("Retrieving node information.\n"); + if(!virNodeGetInfo(connection,&info)) + { + success = 1; + + gethostname(uuid,128); + sprintf(arch, "%s", info.model); + sprintf(memsize, "%ld", info.memory); + sprintf(numcpus, "%d", info.cpus); + sprintf(cpuspeed,"%d", info.mhz); + + if(debug) + { + printf("Node Info:\n"); + printf(" UUID: %s\n", uuid); + printf(" Arch: %s\n", arch); + printf(" Memory: %s\n", memsize); + printf(" # CPUs: %s\n", numcpus); + printf("CPU Speed: %s\n", cpuspeed); + } + } + else + { + if(debug) printf("Failed to get node info.\n"); + } + } + else + { + if(debug) printf("Could not connect to libvirt.\n"); + } + } + else + { + strcpy(uuid,"UUID-FOR-TESTING-PURPOSES"); + strcpy(arch,"x86_64"); + strcpy(memsize,"2048"); + strcpy(numcpus,"2"); + strcpy(cpuspeed,"2048"); + } + + if(success && !start_conversation() && !send_details() && !end_conversation()) result = 0; + } + else + { + usage(); + } + + return result; +} + +int config(int argc,char** argv) +{ + int result = 0; + int option; + + while((option = getopt(argc,argv,"s:p:dvth")) != -1) + { + if(debug) printf("Processing argument: %c (optarg:%s)\n",option,optarg); + + switch(option) + { + case 's': strcpy(hostname,optarg); break; + case 'p': hostport = atoi(optarg); break; + case 't': testing = 1; break; + case 'd': debug = 1; break; + case 'v': verbose = 1; break; + case 'h': + // fall thru + default : result = 1; break; + } + } + + // verify that required options are provided + if(hostname == NULL || strlen(hostname) == 0) + { + printf("ERROR: The server name is required. (-s [hostname])\n"); + result = 1; + } + + if(hostport <= 0) + { + printf("ERROR: The server port is required. (-p [port])\n"); + result = 1; + } + + return result; +} + +void usage() +{ + printf("\n"); + printf("Usage: ovirt-identify [OPTION]\n"); + printf("\n"); + printf("\t-s [server]\t\tThe remote server's hostname.\n"); + printf("\t-p [port]\t\tThe remote server's port.\n"); + printf("\t-d\t\tDisplays debug information during execution.\n"); + printf("\t-v\t\tDisplays verbose information during execution.\n"); + printf("\t-h\t\tDisplays this help information and then exits.\n"); + printf("\n"); +} + +int start_conversation(void) +{ + int result = 1; + + if(verbose || debug) printf("Starting conversation with %s:%d.\n",hostname,hostport); + + if(!create_connection()) + { + if(debug || verbose) printf("Connected.\n"); + + char buffer[128]; + + get_text(buffer,128); + + if(!strcmp(buffer,"HELLO?")) + { + if(debug) printf("Checking for handshake.\n"); + + if(!send_text("HELLO!")) + { + if(debug) printf("Handshake received. Starting conversation.\n"); + + get_text(buffer,128); + + if(!strcmp(buffer,"MODE?")) + { + if(debug) printf("Shifting to IDENTIFY mode.\n"); + + if(!send_text("IDENTIFY")) result = 0; + } + else + { + if(debug) printf("Was not asked for a mode.\n"); + } + } + } + else + { + if(debug) printf("Did not receive a proper handshake.\n"); + } + } + + else + { + if(debug) printf("Did not get a connection.\n"); + } + + if(debug) printf("start_conversation: result=%d\n", result); + + return result; +} + +int send_value(char* label,char* value) +{ + char buffer[128]; + + bzero(buffer,128); + + sprintf(buffer,"%s=%s", label, value); + + int result = 1; + + if(!send_text(buffer)) + { + char expected[128]; + + bzero(expected,128); + bzero(buffer,128); + + get_text(buffer,128); + + sprintf(expected, "ACK %s", label); + + if(debug) printf("Expecting \"%s\" : Received \"%s\"\n", expected, buffer); + + if(!strcmp(expected,buffer)) + { + result = 0; + } + } + + return result; +} + +int send_details(void) +{ + int result = 1; + + fprintf(stdout,"Sending node details.\n"); + + char buffer[128]; + + get_text(buffer,128); + + if(!strcmp(buffer,"INFO?")) + { + if((!send_value("ARCH", arch)) && + (!send_value("UUID", uuid)) && + (!send_value("NUMCPUS", numcpus)) && + (!send_value("CPUSPEED", cpuspeed)) && + (!send_value("MEMSIZE", memsize))) + { + if(!send_text("ENDINFO")) result = 0; + } + } + else + { + if(debug) printf("Was not interrogated for hardware info.\n"); + } + + return result; +} + +int end_conversation(void) +{ + int result = 0; + + fprintf(stdout,"Ending conversation.\n"); + + send_text("ENDINFO"); + + close(socketfd); + + return result; +} + +int send_text(char* text) +{ + int result = 1; + + if(debug || verbose) printf("\"%s\" -> %s:%d\n", text, hostname, hostport); + + char buffer[strlen(text) + 1]; + + sprintf(buffer,"%s\n",text); + + int sent = write(socketfd,buffer,strlen(buffer)); + + if(sent >= 0) + { + if(debug) printf("Sent %d bytes total.\n", sent); + + result = 0; + } + + return result; +} + +int get_text(char* response,int maxlength) +{ + if(debug) printf("Reading up to %d bytes from socket.\n", maxlength); + int received = read(socketfd,response,maxlength); + + response[received - 1] = 0; + + if(debug) printf("Received \"%s\": size=%d (trimmed ending carriage return)\n", response, received); + + return received; +} + +int create_connection(void) +{ + int result = 1; + + if(debug) printf("Creating the socket connection.\n"); + + struct addrinfo hints; + struct addrinfo* results; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + if(debug) printf("Searching for host candidates.\n"); + + char port[6]; + + sprintf(port,"%d", hostport); + + if(!getaddrinfo(hostname, port, &hints, &result)) + { + if(debug) printf("Got address information. Searching for a proper entry.\n"); + struct addrinfo* rptr; + + for(rptr = result; rptr != NULL; rptr = rptr->ai_next) + { + if(debug) + { + printf("Attempting connection: family=%d, socket type=%d, protocol=%d\n", + rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + } + + socketfd = socket(rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + + if(socketfd == -1) + { + continue; + } + + if(connect(socketfd, rptr->ai_addr, rptr->ai_addrlen) != -1) + { + break; + } + + // invalid connection, so close it + close(socketfd); + } + + if(rptr == NULL) + { + if(debug) printf("Unable to connect to server %s:%d\n", hostname, hostport); + } + else + { + // success + result = 0; + } + + freeaddrinfo(result); + } + else + { + if(debug) printf("No hosts found. Exiting...\n"); + } + + return result; +} diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index b7d98ec..90937c5 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -39,7 +39,10 @@ clean: repos.ks: repos.ks.in cp repos.ks.in repos.ks -build: ovirt-$(ARCH).ks common-install.ks common-pkgs.ks common-post.ks repos.ks +ovirt-identify: + (cd ../identify-node && make) + +build: ovirt-$(ARCH).ks common-install.ks common-pkgs.ks common-post.ks repos.ks ovirt-identify -rm -rf tftpboot/ ./ovirt-pxe.sh > ovirt-pxe.log 2>&1 diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index d8acab8..914b35b 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -12,12 +12,11 @@ cat > /etc/sysconfig/iptables << \EOF COMMIT EOF -echo "Writing ovirt-identify-node script" -cat > /sbin/ovirt-identify-node << \EOF -#!/bin/bash -# +echo "Writing ovirt-functions script" +# common functions +cat > /etc/init.d/ovirt-functions << \EOF # Copyright (C) 2008 Red Hat, Inc. -# Written by Chris Lalancette <clalance at redhat.com> +# 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 @@ -34,109 +33,54 @@ cat > /sbin/ovirt-identify-node << \EOF # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -ME=$(basename "$0") -warn() { printf "$ME: $@\n" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat <<EOF2 -Usage: $ME [-s server] [-p port] - -h: display this help and exit - -p: Port number the host-browser is listening on - -s: Hostname of the server to connect to -EOF2 +# Determines the hostname and port for the oVirt server. +# +find-server() { + if [ -z $SERVER ]; then + dnsreply=$(dig +short -t srv _$1._$2.$(dnsdomainname)) + if [ $? -eq 0 ]; then + set _ $dnsreply; shift + SERVER=$4; PORT=$3 + else + SERVER=; PORT+ fi + fi + + if [ -z $SERVER ]; then + echo "No server found..." + exit + fi } -send_key_value() { - echo "$1=$2" 1>&3 +# Establishes a TCP connection to the server. +# +connect-to-server () { + echo "Connecting to $SERVER:$PORT"... - read 0<&3 - test "$REPLY" != "ACK $1" && die "Failed acknowledge of key $1" + exec 3<> /dev/tcp/$SERVER/$PORT } -############# MAIN ################## - -# parse our options -while getopts ":hs:p:" flag ; do - case "$flag" in - h) - usage ; exit 0 - ;; - s) - server=$OPTARG - ;; - p) - port=$OPTARG - ;; - ?) - usage "Unknown flag $flag" - ;; - esac -done +# Disconnects form the server. +# +disconnect-from-server () { + <&3- +} -test $(( $# - $OPTIND )) -ge 0 && usage "Too many options" -test -z "$server" && usage "Must specify -s" -test -z "$port" && usage "Must specify -p" - -# gather our information -all_ok=0 -uuid=$(hostname -f) && - arch=$(uname -i) && - memsize=$(( $(getconf _PHYS_PAGES) * $(getconf PAGESIZE) / 1024 / 1024 )) && - numcpus=$(getconf _NPROCESSORS_ONLN) && - speed=$(sed -n "/cpu MHz/{s/.*://p;q;}" /proc/cpuinfo | tr -dc 0-9.) && - hostname=$(hostname -f) && - hypervisor="QEMU" && all_ok=1 - -test $all_ok = 1 || die "Information gathering failed...see above" - -# open our connection to the remote host -eval 'exec 3<> /dev/tcp/$server/$port' 2>err -test $? -ne 0 && die "Connection to $server:$port failed: $(cat err)" - -# say hello -read 0<&3 -test "$REPLY" != "HELLO?" && die "Expected response HELLO?, received response $REPLY" -echo "HELLO!" 1>&3 - -# OK, start sending our information -read 0<&3 -test "$REPLY" != "INFO?" && die "Expected response INFO?, received response $REPLY" - -send_key_value "UUID" "$uuid" -send_key_value "ARCH" "$arch" -send_key_value "MEMSIZE" "$memsize" -send_key_value "NUMCPUS" "$numcpus" -send_key_value "CPUSPEED" "$speed" -send_key_value "HOSTNAME" "$hostname" -send_key_value "HYPERVISOR_TYPE" "$hypervisor" - -echo "ENDINFO" 1>&3 - -read 0<&3 - -test "${REPLY:0:4}" != "KTAB" && die "Expected response KTAB <filename>, received response $REPLY" -echo "${REPLY:5}" -EOF -chmod +x /sbin/ovirt-identify-node +# Sends text to the remote server. +# +send-text () { + echo "Sending: \"$1\"" + echo "$1" 1>&3 +} -echo "Writing ovirt-functions script" -# common functions -cat > /etc/init.d/ovirt-functions << \EOF -# -*-Shell-script-*- +# Receives text from the remote server. +# +receive-text () { + read 0<&3 -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 + echo "Received: \"$REPLY\"" } + EOF echo "Writing ovirt-early init script" @@ -168,7 +112,7 @@ configure_from_network() { if [ "$status" = "0" ]; then hostname $HOSTNAME # retrieve remote config - find_srv ovirt tcp + find-server ovirt tcp printf . if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then wget --quiet -O - "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" \ @@ -219,19 +163,26 @@ start() { # now LVM partitions LVMDEVS="$DEVICES `lvscan | awk '{print $2}' | tr -d \"'\"`" - SWAPDEVS="$LVMDEVS" + SWAPDEVS="$LVMDEVS" for dev in $BLOCKDEVS; do SWAPDEVS="$SWAPDEVS `fdisk -l $dev 2>/dev/null | tr '*' ' ' \ - | awk '$5 ~ /82/ {print $1}'`" + | awk '$5 ~ /82/ {print $1}'`" done - # now check if any of these partitions are swap, and activate if so + # now check if any of these partitions are swap, and activate if so for device in $SWAPDEVS; do sig=`dd if=$device bs=1 count=10 skip=$(( $PAGESIZE - 10 )) \ - 2>/dev/null` + 2>/dev/null` if [ "$sig" = "SWAPSPACE2" ]; then swapon $device fi + + # Notify the server we're awake and retrieve a keytab file + krb5_tab=/etc/libvirt/krb5.tab + if [ ! -s $krb5_tab ]; then + ovirt-awake start $krb5_tab + fi + done } @@ -283,7 +234,7 @@ die() start() { echo -n $"Starting ovirt: " - find_srv ipa tcp + find-server ipa tcp krb5_conf=/etc/krb5.conf if [ ! -s $krb5_conf ]; then rm -f $krb5_conf @@ -294,17 +245,9 @@ start() { IPA_HOST=$SRV_HOST IPA_PORT=$SRV_PORT - find_srv identify tcp - krb5_tab=/etc/libvirt/krb5.tab - if [ ! -s $krb5_tab ]; then - keytab=$(ovirt-identify-node -s $SRV_HOST -p $SRV_PORT) \ - || die "Failed to identify node" - # FIXME this is IPA specific, host-browser should return full URL - wget -q "http://$IPA_HOST:$IPA_PORT/config/$keytab" -O $krb5_tab \ - || die "Failed to get $krb5_tab" - fi + /sbin/ovirt-identify-node start - find_srv collectd tcp + find-server collectd tcp collectd_conf=/etc/collectd.conf if [ -f $collectd_conf.in -a $SRV_HOST -a $SRV_PORT ]; then sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ @@ -415,8 +358,8 @@ LoadPlugin disk </Plugin> <Plugin interface> - Interface "eth0" - IgnoreSelected false + Interface "eth0" + IgnoreSelected false </Plugin> EOF diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..3e242cf 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]}] " @@ -51,19 +51,31 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} - result['IPADDR'] = @session.peeraddr[3] + result['HOSTNAME'] = @session.peeraddr[2] + result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") loop do @@ -75,9 +87,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +106,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +127,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +137,45 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + hostname = `hostname -f`.chomp + + @session.write("KTAB http://#{hostname}/config/#{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +197,37 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + when "IDENTIFY": browser.write_host_info(browser.get_remote_info) + end + + browser.end_conversation rescue Exception => error session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5ca2e7 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,94 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + servername = `hostname -f`.chomp + @session.should_receive(:write).with("KTAB http://#{servername}/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..a70884d --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,162 @@ +#!/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"] } + + @browser = HostBrowser.new(@session) + @browser.logfile = './unit-test.log' + + # 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "IDENTIFY\n" } + + result = @browser.get_mode() + + assert_equal "IDENTIFY", result, "method did not return the right value" + 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 4,info.keys.size, "Should contain two keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + assert info.include?("key1") + assert info.include?("key2") + 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 + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1
Darryl L. Pierce
2008-Jun-17 15:24 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
Incorporates feedback from others and some bugfixes. Primarily looking for more feedback on the ovirt-identify-node util and how we can pull details from libvirt to submit to the wui. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- ovirt-daemons/Makefile | 38 ++ ovirt-daemons/identify-node.spec | 14 + ovirt-daemons/ovirt-awake | 82 ++++ ovirt-daemons/ovirt-daemons.spec | 38 ++ ovirt-daemons/ovirt-identify | 46 ++ ovirt-daemons/ovirt-identify-node.c | 457 ++++++++++++++++++++ ovirt-host-creator/Makefile | 5 +- ovirt-host-creator/common-post.ks | 179 +++----- wui/src/host-browser/host-browser.rb | 106 +++-- wui/src/host-browser/test-host-browser-awaken.rb | 94 ++++ wui/src/host-browser/test-host-browser-identify.rb | 162 +++++++ wui/src/host-browser/test-host-browser.rb | 204 --------- 12 files changed, 1056 insertions(+), 369 deletions(-) create mode 100644 ovirt-daemons/Makefile create mode 100644 ovirt-daemons/identify-node.spec create mode 100644 ovirt-daemons/ovirt-awake create mode 100644 ovirt-daemons/ovirt-daemons.spec create mode 100644 ovirt-daemons/ovirt-identify create mode 100644 ovirt-daemons/ovirt-identify-node.c create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/ovirt-daemons/Makefile b/ovirt-daemons/Makefile new file mode 100644 index 0000000..a3eb399 --- /dev/null +++ b/ovirt-daemons/Makefile @@ -0,0 +1,38 @@ +# 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. + +CC=gcc +CFLAGS=-Wall -c -g +LFLAGS=-lvirt +OBJECTS=ovirt-identify-node.o +TARGET=ovirt-identify-node +SPECFILE=ovirt-daemons.spec + +ALL: $(TARGET) + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -rf $(OBJECTS) $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $(OBJECTS) $(LFLAGS) + +rpms: $(TARGET) + rpmbuild -ba --rebuild $(SPECFILE) diff --git a/ovirt-daemons/identify-node.spec b/ovirt-daemons/identify-node.spec new file mode 100644 index 0000000..7f31db9 --- /dev/null +++ b/ovirt-daemons/identify-node.spec @@ -0,0 +1,14 @@ +Summary: oVirt managed node identification utility +Name: ovirt-daemons + + + +Source1: version +Version: %(echo `awk '{ print $1 }' %{SOURCE1}`) +Release: %(echo `awk '{ print $2 }' %{SOURCE1}`)%{?dist} +Source0: %{name}-%{version}.tar +License: Fedora +Group: Applications/System +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +URL: http://ovirt.org/ + diff --git a/ovirt-daemons/ovirt-awake b/ovirt-daemons/ovirt-awake new file mode 100644 index 0000000..30bf576 --- /dev/null +++ b/ovirt-daemons/ovirt-awake @@ -0,0 +1,82 @@ +#!/bin/bash +# +# ovirt-awake Notifies the oVirt server that a managed node is +# starting up. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "AWAKEN" + + receive-text + + KEYTAB=`echo $REPLY | awk '{ print $2 }'` + + if [ -n $KEYTAB ]; then + echo "Retrieving keytab: '$KEYTAB'" + + wget $KEYTAB --output-document=$KEYTAB_FILE + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server +} + +case "$1" in + start) + KEYTAB_FILE=$2 + SERVER=$3 + PORT=$4 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/ovirt-daemons/ovirt-daemons.spec b/ovirt-daemons/ovirt-daemons.spec new file mode 100644 index 0000000..eae83cc --- /dev/null +++ b/ovirt-daemons/ovirt-daemons.spec @@ -0,0 +1,38 @@ +Summary: oVirt managed node daemons. +Name: oVirt-daemons +Version: 1.0.0 +Release: 1%{?dist} +Group: Applications/System +License: Fedora +BuildRequires: libvirt-devel + +%description +Provides the daemons required to interact with the oVirt management +system. + + +%prep + + +%build + + +%install + +rm -rf $RPM_BUILD_ROOT +mkdir $RPM_BUILD_ROOT + +install -m755 ovirt-identify-node $RPM_BUILD_ROOT/sbin +install -m755 ovirt-awake $RPM_BUILD_ROOT/sbin +install -m755 ovirt-identify $RPM_BUILD_ROOT/sbin + + +%clean + +rm -rf %{buildroot} + +%files +%defattr(-,root,root) +/sbin/ovirt-identify-node +/sbin/ovirt-awake +/sbin/ovirt-identify diff --git a/ovirt-daemons/ovirt-identify b/ovirt-daemons/ovirt-identify new file mode 100644 index 0000000..58651b7 --- /dev/null +++ b/ovirt-daemons/ovirt-identify @@ -0,0 +1,46 @@ +#!/bin/bash +# +# ovirt-identify Submits managed node details to the central server. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + /sbin/ovirt-identify-node -s $SERVER -p $PORT +} + +case "$1" in + start) + SERVER=$2 + PORT=$3 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/ovirt-daemons/ovirt-identify-node.c b/ovirt-daemons/ovirt-identify-node.c new file mode 100644 index 0000000..3689eca --- /dev/null +++ b/ovirt-daemons/ovirt-identify-node.c @@ -0,0 +1,457 @@ +/* identify-node -- Main entry point for the identify-node utility. + * + * 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. + */ + +#include <errno.h> +#include <getopt.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libvirt/libvirt.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/socket.h> + +int config(int argc,char** argv); +void usage(void); + +int start_conversation(void); +int send_details(void); +int end_conversation(void); + +int send_text(char* text); +int get_text(char* response,int maxlength); +int create_connection(void); + +int debug = 0; +int verbose = 0; +int testing = 0; + +#define BUFFER_LENGTH 128 + +char arch[BUFFER_LENGTH]; +char uuid[VIR_UUID_BUFLEN]; +char memsize[BUFFER_LENGTH]; +char numcpus[BUFFER_LENGTH]; +char cpuspeed[BUFFER_LENGTH]; +char hostname[256]; +int hostport = -1; +int socketfd; + +int main(int argc,char** argv) +{ + int result = 1; + + if(!config(argc,argv)) + { + fprintf(stdout,"Connecting to libvirt.\n"); + + virConnectPtr connection = virConnectOpenReadOnly(testing ? "test:///default" : NULL); + + if(debug) fprintf(stderr,"connection=%x\n",(unsigned int )connection); + + if(connection) + { + if(debug) fprintf(stdout,"Retrieving domain information.\n"); + virDomainPtr domain = virDomainLookupByID(connection,0); + + if(domain) + { + if(!virDomainGetUUIDString(domain,uuid) && debug) + { + fprintf(stdout,"Got UUID=%s\n", uuid); + } + else + { + fprintf(stderr, "Did not get UUID for node.\n"); + } + + } + else + { + fprintf(stderr,"Failed to connect to default domain.\n"); + } + + if(!strlen(uuid)) gethostname(uuid,sizeof uuid); + + virNodeInfo info; + + if(debug) fprintf(stdout,"Retrieving node information.\n"); + if(!virNodeGetInfo(connection,&info)) + { + sprintf(arch, "%s", info.model); + sprintf(memsize, "%ld", info.memory); + sprintf(numcpus, "%d", info.cpus); + sprintf(cpuspeed,"%d", info.mhz); + + if(debug) + { + fprintf(stdout,"Node Info:\n"); + fprintf(stdout," UUID: %s\n", uuid); + fprintf(stdout," Arch: %s\n", arch); + fprintf(stdout," Memory: %s\n", memsize); + fprintf(stdout," # CPUs: %s\n", numcpus); + fprintf(stdout,"CPU Speed: %s\n", cpuspeed); + } + + if(debug) fprintf(stdout, "Retrieved node information.\n"); + + if(!start_conversation() && !send_details() && !end_conversation()) + { + result = 0; + } + } + else + { + if(debug) fprintf(stderr,"Failed to get node info.\n"); + } + } + else + { + if(debug) fprintf(stderr,"Could not connect to libvirt.\n"); + } + } + else + { + usage(); + } + + return result; +} + +int config(int argc,char** argv) +{ + int result = 0; + int option; + + while((option = getopt(argc,argv,"s:p:dvth")) != -1) + { + if(debug) fprintf(stdout,"Processing argument: %c (optarg:%s)\n",option,optarg); + + switch(option) + { + case 's': strcpy(hostname,optarg); break; + case 'p': hostport = atoi(optarg); break; + case 't': testing = 1; break; + case 'd': debug = 1; break; + case 'v': verbose = 1; break; + case 'h': + // fall thru + default : result = 1; break; + } + } + + // verify that required options are provided + if(hostname == NULL || strlen(hostname) == 0) + { + fprintf(stderr,"ERROR: The server name is required. (-s [hostname])\n"); + result = 1; + } + + if(hostport <= 0) + { + fprintf(stderr,"ERROR: The server port is required. (-p [port])\n"); + result = 1; + } + + return result; +} + +void usage() +{ + fprintf(stdout,"\n"); + fprintf(stdout,"Usage: ovirt-identify [OPTION]\n"); + fprintf(stdout,"\n"); + fprintf(stdout,"\t-s [server]\t\tThe remote server's hostname.\n"); + fprintf(stdout,"\t-p [port]\t\tThe remote server's port.\n"); + fprintf(stdout,"\t-d\t\tDisplays debug information during execution.\n"); + fprintf(stdout,"\t-v\t\tDisplays verbose information during execution.\n"); + fprintf(stdout,"\t-h\t\tDisplays this help information and then exits.\n"); + fprintf(stdout,"\n"); +} + +int start_conversation(void) +{ + int result = 1; + + if(verbose || debug) fprintf(stdout,"Starting conversation with %s:%d.\n",hostname,hostport); + + if(!create_connection()) + { + if(debug || verbose) fprintf(stdout,"Connected.\n"); + + char buffer[BUFFER_LENGTH]; + + get_text(buffer,sizeof buffer); + + if(!strcmp(buffer,"HELLO?")) + { + if(debug) fprintf(stdout,"Checking for handshake.\n"); + + if(!send_text("HELLO!")) + { + if(debug) fprintf(stdout,"Handshake received. Starting conversation.\n"); + + get_text(buffer,sizeof buffer); + + if(!strcmp(buffer,"MODE?")) + { + if(debug) fprintf(stdout,"Shifting to IDENTIFY mode.\n"); + + if(!send_text("IDENTIFY")) result = 0; + } + else + { + if(debug) fprintf(stderr,"Was not asked for a mode.\n"); + } + } + } + else + { + if(debug) fprintf(stderr,"Did not receive a proper handshake.\n"); + } + } + + else + { + if(debug) fprintf(stderr,"Did not get a connection.\n"); + } + + if(debug) fprintf(stdout,"start_conversation: result=%d\n", result); + + return result; +} + +int send_value(char* label,char* value) +{ + char buffer[BUFFER_LENGTH]; + + bzero(buffer,sizeof buffer); + + sprintf(buffer,"%s=%s", label, value); + + int result = 1; + + if(!send_text(buffer)) + { + char expected[BUFFER_LENGTH]; + + bzero(expected,sizeof buffer); + bzero(buffer,sizeof buffer); + + get_text(buffer,sizeof buffer); + + sprintf(expected, "ACK %s", label); + + if(debug) fprintf(stdout,"Expecting \"%s\" : Received \"%s\"\n", expected, buffer); + + if(!strcmp(expected,buffer)) + { + result = 0; + } + } + + return result; +} + +int send_details(void) +{ + int result = 1; + + fprintf(stdout,"Sending node details.\n"); + + char buffer[BUFFER_LENGTH]; + + get_text(buffer,sizeof buffer); + + if(!strcmp(buffer,"INFO?")) + { + if((!send_value("ARCH", arch)) && + (!send_value("UUID", uuid)) && + (!send_value("NUMCPUS", numcpus)) && + (!send_value("CPUSPEED", cpuspeed)) && + (!send_value("MEMSIZE", memsize))) + { + if(!send_text("ENDINFO")) result = 0; + } + } + else + { + if(debug) fprintf(stdout,"Was not interrogated for hardware info.\n"); + } + + return result; +} + +int end_conversation(void) +{ + int result = 0; + + fprintf(stdout,"Ending conversation.\n"); + + send_text("ENDINFO"); + + close(socketfd); + + return result; +} + +ssize_t safewrite(int fd, const void *buf, size_t count) +{ + size_t nwritten = 0; + while (count > 0) { + ssize_t r = write(fd, buf, count); + + if (r < 0 && errno == EINTR) + continue; + if (r < 0) + return r; + if (r == 0) + return nwritten; + buf = (const char *)buf + r; + count -= r; + nwritten += r; + } + return nwritten; +} + +int send_text(char* text) +{ + int result = 1; + + if(debug || verbose) fprintf(stdout,"\"%s\" -> %s:%d\n", text, hostname, hostport); + + char buffer[strlen(text) + 2]; + + sprintf(buffer,"%s\n",text); + + // int sent = write(socketfd,buffer,strlen(buffer)); + + int sent = safewrite(socketfd, buffer, strlen(buffer)); + + if(sent >= 0) + { + if(debug) fprintf(stdout,"Sent %d bytes total.\n", sent); + + result = 0; + } + + return result; +} + +int saferead(int fd, void *buf, size_t count) +{ + if(debug) fprintf(stdout,"Begin saferead(%d, %x, %d)\n", fd, (unsigned int)buf, count); + + while (1) + { + ssize_t r = read (fd, buf, count); + if (r < 0 && errno == EINTR) continue; + return r; + } +} + +int get_text(char* response,int maxlength) +{ + if(debug) fprintf(stdout,"Reading up to %d bytes from socket.\n", maxlength); + // int received = read(socketfd,response,maxlength); + int received = saferead(socketfd,response,maxlength); + + response[received - 1] = 0; + + if(debug) fprintf(stdout,"Received \"%s\": size=%d (trimmed ending carriage return)\n", response, received); + + return received; +} + +int create_connection(void) +{ + int result = 1; + + if(debug) fprintf(stdout,"Creating the socket connection.\n"); + + struct addrinfo hints; + struct addrinfo* results; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + if(debug) fprintf(stdout,"Searching for host candidates.\n"); + + char port[6]; + + sprintf(port,"%d", hostport); + + if(!getaddrinfo(hostname, port, &hints, &results)) + { + if(debug) fprintf(stdout,"Got address information. Searching for a proper entry.\n"); + struct addrinfo* rptr; + + for(rptr = results; rptr != NULL; rptr = rptr->ai_next) + { + if(debug) + { + fprintf(stdout,"Attempting connection: family=%d, socket type=%d, protocol=%d\n", + rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + } + + socketfd = socket(rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + + if(socketfd == -1) + { + continue; + } + + if(connect(socketfd, rptr->ai_addr, rptr->ai_addrlen) != -1) + { + break; + } + + // invalid connection, so close it + if(debug) fprintf(stdout, "Invalid connection.\n"); + close(socketfd); + } + + if(rptr == NULL) + { + if(debug) fprintf(stdout,"Unable to connect to server %s:%d\n", hostname, hostport); + } + else + { + // success + result = 0; + } + + freeaddrinfo(results); + } + else + { + if(debug) fprintf(stderr,"No hosts found. Exiting...\n"); + } + + if(debug) fprintf(stdout, "create_connection: result=%d\n", result); + + return result; +} diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index b7d98ec..90937c5 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -39,7 +39,10 @@ clean: repos.ks: repos.ks.in cp repos.ks.in repos.ks -build: ovirt-$(ARCH).ks common-install.ks common-pkgs.ks common-post.ks repos.ks +ovirt-identify: + (cd ../identify-node && make) + +build: ovirt-$(ARCH).ks common-install.ks common-pkgs.ks common-post.ks repos.ks ovirt-identify -rm -rf tftpboot/ ./ovirt-pxe.sh > ovirt-pxe.log 2>&1 diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index d360736..0a1cb07 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -12,12 +12,11 @@ cat > /etc/sysconfig/iptables << \EOF COMMIT EOF -echo "Writing ovirt-identify-node script" -cat > /sbin/ovirt-identify-node << \EOF -#!/bin/bash -# +echo "Writing ovirt-functions script" +# common functions +cat > /etc/init.d/ovirt-functions << \EOF # Copyright (C) 2008 Red Hat, Inc. -# Written by Chris Lalancette <clalance at redhat.com> +# 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 @@ -34,109 +33,54 @@ cat > /sbin/ovirt-identify-node << \EOF # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -ME=$(basename "$0") -warn() { printf "$ME: $@\n" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat <<EOF2 -Usage: $ME [-s server] [-p port] - -h: display this help and exit - -p: Port number the host-browser is listening on - -s: Hostname of the server to connect to -EOF2 +# Determines the hostname and port for the oVirt server. +# +find-server() { + if [ -z $SERVER ]; then + dnsreply=$(dig +short -t srv _$1._$2.$(dnsdomainname)) + if [ $? -eq 0 ]; then + set _ $dnsreply; shift + SERVER=$4; PORT=$3 + else + SERVER=; PORT+ fi + fi + + if [ -z $SERVER ]; then + echo "No server found..." + exit + fi } -send_key_value() { - echo "$1=$2" 1>&3 +# Establishes a TCP connection to the server. +# +connect-to-server () { + echo "Connecting to $SERVER:$PORT"... - read 0<&3 - test "$REPLY" != "ACK $1" && die "Failed acknowledge of key $1" + exec 3<> /dev/tcp/$SERVER/$PORT } -############# MAIN ################## - -# parse our options -while getopts ":hs:p:" flag ; do - case "$flag" in - h) - usage ; exit 0 - ;; - s) - server=$OPTARG - ;; - p) - port=$OPTARG - ;; - ?) - usage "Unknown flag $flag" - ;; - esac -done +# Disconnects form the server. +# +disconnect-from-server () { + <&3- +} -test $(( $# - $OPTIND )) -ge 0 && usage "Too many options" -test -z "$server" && usage "Must specify -s" -test -z "$port" && usage "Must specify -p" - -# gather our information -all_ok=0 -uuid=$(hostname -f) && - arch=$(uname -i) && - memsize=$(( $(getconf _PHYS_PAGES) * $(getconf PAGESIZE) / 1024 / 1024 )) && - numcpus=$(getconf _NPROCESSORS_ONLN) && - speed=$(sed -n "/cpu MHz/{s/.*://p;q;}" /proc/cpuinfo | tr -dc 0-9.) && - hostname=$(hostname -f) && - hypervisor="QEMU" && all_ok=1 - -test $all_ok = 1 || die "Information gathering failed...see above" - -# open our connection to the remote host -eval 'exec 3<> /dev/tcp/$server/$port' 2>err -test $? -ne 0 && die "Connection to $server:$port failed: $(cat err)" - -# say hello -read 0<&3 -test "$REPLY" != "HELLO?" && die "Expected response HELLO?, received response $REPLY" -echo "HELLO!" 1>&3 - -# OK, start sending our information -read 0<&3 -test "$REPLY" != "INFO?" && die "Expected response INFO?, received response $REPLY" - -send_key_value "UUID" "$uuid" -send_key_value "ARCH" "$arch" -send_key_value "MEMSIZE" "$memsize" -send_key_value "NUMCPUS" "$numcpus" -send_key_value "CPUSPEED" "$speed" -send_key_value "HOSTNAME" "$hostname" -send_key_value "HYPERVISOR_TYPE" "$hypervisor" - -echo "ENDINFO" 1>&3 - -read 0<&3 - -test "${REPLY:0:4}" != "KTAB" && die "Expected response KTAB <filename>, received response $REPLY" -echo "${REPLY:5}" -EOF -chmod +x /sbin/ovirt-identify-node +# Sends text to the remote server. +# +send-text () { + echo "Sending: \"$1\"" + echo "$1" 1>&3 +} -echo "Writing ovirt-functions script" -# common functions -cat > /etc/init.d/ovirt-functions << \EOF -# -*-Shell-script-*- +# Receives text from the remote server. +# +receive-text () { + read 0<&3 -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 + echo "Received: \"$REPLY\"" } + EOF echo "Writing ovirt-early init script" @@ -168,7 +112,7 @@ configure_from_network() { if [ "$status" = "0" ]; then hostname $HOSTNAME # retrieve remote config - find_srv ovirt tcp + find-server ovirt tcp printf . if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then wget --quiet -O - "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" \ @@ -219,19 +163,26 @@ start() { # now LVM partitions LVMDEVS="$DEVICES `lvscan | awk '{print $2}' | tr -d \"'\"`" - SWAPDEVS="$LVMDEVS" + SWAPDEVS="$LVMDEVS" for dev in $BLOCKDEVS; do SWAPDEVS="$SWAPDEVS `fdisk -l $dev 2>/dev/null | tr '*' ' ' \ - | awk '$5 ~ /82/ {print $1}'`" + | awk '$5 ~ /82/ {print $1}'`" done - # now check if any of these partitions are swap, and activate if so + # now check if any of these partitions are swap, and activate if so for device in $SWAPDEVS; do sig=`dd if=$device bs=1 count=10 skip=$(( $PAGESIZE - 10 )) \ - 2>/dev/null` + 2>/dev/null` if [ "$sig" = "SWAPSPACE2" ]; then swapon $device fi + + # Notify the server we're awake and retrieve a keytab file + krb5_tab=/etc/libvirt/krb5.tab + if [ ! -s $krb5_tab ]; then + ovirt-awake start $krb5_tab + fi + done } @@ -283,7 +234,7 @@ die() start() { echo -n $"Starting ovirt: " - find_srv ipa tcp + find-server ipa tcp krb5_conf=/etc/krb5.conf if [ ! -s $krb5_conf ]; then rm -f $krb5_conf @@ -294,17 +245,9 @@ start() { IPA_HOST=$SRV_HOST IPA_PORT=$SRV_PORT - find_srv identify tcp - krb5_tab=/etc/libvirt/krb5.tab - if [ ! -s $krb5_tab ]; then - keytab=$(ovirt-identify-node -s $SRV_HOST -p $SRV_PORT) \ - || die "Failed to identify node" - # FIXME this is IPA specific, host-browser should return full URL - wget -q "http://$IPA_HOST:$IPA_PORT/config/$keytab" -O $krb5_tab \ - || die "Failed to get $krb5_tab" - fi + /sbin/ovirt-identify-node start - find_srv collectd tcp + find-server collectd tcp collectd_conf=/etc/collectd.conf if [ -f $collectd_conf.in -a $SRV_HOST -a $SRV_PORT ]; then sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ @@ -415,8 +358,8 @@ LoadPlugin disk </Plugin> <Plugin interface> - Interface "eth0" - IgnoreSelected false + Interface "eth0" + IgnoreSelected false </Plugin> EOF diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..3e242cf 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]}] " @@ -51,19 +51,31 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} - result['IPADDR'] = @session.peeraddr[3] + result['HOSTNAME'] = @session.peeraddr[2] + result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") loop do @@ -75,9 +87,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +106,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +127,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +137,45 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + hostname = `hostname -f`.chomp + + @session.write("KTAB http://#{hostname}/config/#{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +197,37 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + when "IDENTIFY": browser.write_host_info(browser.get_remote_info) + end + + browser.end_conversation rescue Exception => error session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5ca2e7 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,94 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + servername = `hostname -f`.chomp + @session.should_receive(:write).with("KTAB http://#{servername}/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..a70884d --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,162 @@ +#!/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"] } + + @browser = HostBrowser.new(@session) + @browser.logfile = './unit-test.log' + + # 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "IDENTIFY\n" } + + result = @browser.get_mode() + + assert_equal "IDENTIFY", result, "method did not return the right value" + 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 4,info.keys.size, "Should contain two keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + assert info.include?("key1") + assert info.include?("key2") + 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 + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1
Darryl L. Pierce
2008-Jun-18 19:00 UTC
[Ovirt-devel] [PATCH] Rewriting the identify-node piece into two managed node units
Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- build-all.sh | 1 + ovirt-host-creator/Makefile | 2 +- ovirt-host-creator/common-post.ks | 171 +++----- ovirt-managed-node/AUTHOR | 1 + ovirt-managed-node/ChangeLog | 2 + ovirt-managed-node/Makefile | 76 ++++ ovirt-managed-node/NEWS | 2 + ovirt-managed-node/README | 2 + ovirt-managed-node/ovirt-awake | 82 ++++ ovirt-managed-node/ovirt-identify | 46 ++ ovirt-managed-node/ovirt-identify-node.c | 457 ++++++++++++++++++++ ovirt-managed-node/ovirt-managed-node.spec | 45 ++ wui/src/host-browser/host-browser.rb | 106 +++-- wui/src/host-browser/test-host-browser-awaken.rb | 94 ++++ wui/src/host-browser/test-host-browser-identify.rb | 162 +++++++ wui/src/host-browser/test-host-browser.rb | 204 --------- 16 files changed, 1087 insertions(+), 366 deletions(-) create mode 100644 ovirt-managed-node/AUTHOR create mode 100644 ovirt-managed-node/ChangeLog create mode 100644 ovirt-managed-node/Makefile create mode 100644 ovirt-managed-node/NEWS create mode 100644 ovirt-managed-node/README create mode 100644 ovirt-managed-node/ovirt-awake create mode 100644 ovirt-managed-node/ovirt-identify create mode 100644 ovirt-managed-node/ovirt-identify-node.c create mode 100644 ovirt-managed-node/ovirt-managed-node.spec create mode 100755 wui/src/host-browser/test-host-browser-awaken.rb create mode 100755 wui/src/host-browser/test-host-browser-identify.rb delete mode 100755 wui/src/host-browser/test-host-browser.rb diff --git a/build-all.sh b/build-all.sh index fcf1563..d775d66 100755 --- a/build-all.sh +++ b/build-all.sh @@ -123,6 +123,7 @@ fi set -e # build ovirt-wui RPM +# also build the ovirt-managed-node RPM if [ $update_wui = 1 ]; then cd $BASE/wui rm -rf rpm-build diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index b7d98ec..12a8b77 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -39,7 +39,7 @@ clean: repos.ks: repos.ks.in cp repos.ks.in repos.ks -build: ovirt-$(ARCH).ks common-install.ks common-pkgs.ks common-post.ks repos.ks +build: ovirt-$(ARCH).ks common-install.ks common-pkgs.ks common-post.ks repos.ks -rm -rf tftpboot/ ./ovirt-pxe.sh > ovirt-pxe.log 2>&1 diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index d360736..7e27da0 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -12,12 +12,11 @@ cat > /etc/sysconfig/iptables << \EOF COMMIT EOF -echo "Writing ovirt-identify-node script" -cat > /sbin/ovirt-identify-node << \EOF -#!/bin/bash -# +echo "Writing ovirt-functions script" +# common functions +cat > /etc/init.d/ovirt-functions << \EOF # Copyright (C) 2008 Red Hat, Inc. -# Written by Chris Lalancette <clalance at redhat.com> +# 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 @@ -34,109 +33,54 @@ cat > /sbin/ovirt-identify-node << \EOF # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -ME=$(basename "$0") -warn() { printf "$ME: $@\n" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat <<EOF2 -Usage: $ME [-s server] [-p port] - -h: display this help and exit - -p: Port number the host-browser is listening on - -s: Hostname of the server to connect to -EOF2 +# Determines the hostname and port for the oVirt server. +# +find-server() { + if [ -z $SRV_HOST ]; then + 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 + fi + + if [ -z $SRV_HOST ]; then + echo "No server found..." + exit + fi } -send_key_value() { - echo "$1=$2" 1>&3 +# Establishes a TCP connection to the server. +# +connect-to-server () { + echo "Connecting to $SRV_HOST:$SRV_PORT"... - read 0<&3 - test "$REPLY" != "ACK $1" && die "Failed acknowledge of key $1" + exec 3<> /dev/tcp/$SRV_HOST/$SRV_PORT } -############# MAIN ################## - -# parse our options -while getopts ":hs:p:" flag ; do - case "$flag" in - h) - usage ; exit 0 - ;; - s) - server=$OPTARG - ;; - p) - port=$OPTARG - ;; - ?) - usage "Unknown flag $flag" - ;; - esac -done +# Disconnects form the server. +# +disconnect-from-server () { + <&3- +} -test $(( $# - $OPTIND )) -ge 0 && usage "Too many options" -test -z "$server" && usage "Must specify -s" -test -z "$port" && usage "Must specify -p" - -# gather our information -all_ok=0 -uuid=$(hostname -f) && - arch=$(uname -i) && - memsize=$(( $(getconf _PHYS_PAGES) * $(getconf PAGESIZE) / 1024 / 1024 )) && - numcpus=$(getconf _NPROCESSORS_ONLN) && - speed=$(sed -n "/cpu MHz/{s/.*://p;q;}" /proc/cpuinfo | tr -dc 0-9.) && - hostname=$(hostname -f) && - hypervisor="QEMU" && all_ok=1 - -test $all_ok = 1 || die "Information gathering failed...see above" - -# open our connection to the remote host -eval 'exec 3<> /dev/tcp/$server/$port' 2>err -test $? -ne 0 && die "Connection to $server:$port failed: $(cat err)" - -# say hello -read 0<&3 -test "$REPLY" != "HELLO?" && die "Expected response HELLO?, received response $REPLY" -echo "HELLO!" 1>&3 - -# OK, start sending our information -read 0<&3 -test "$REPLY" != "INFO?" && die "Expected response INFO?, received response $REPLY" - -send_key_value "UUID" "$uuid" -send_key_value "ARCH" "$arch" -send_key_value "MEMSIZE" "$memsize" -send_key_value "NUMCPUS" "$numcpus" -send_key_value "CPUSPEED" "$speed" -send_key_value "HOSTNAME" "$hostname" -send_key_value "HYPERVISOR_TYPE" "$hypervisor" - -echo "ENDINFO" 1>&3 - -read 0<&3 - -test "${REPLY:0:4}" != "KTAB" && die "Expected response KTAB <filename>, received response $REPLY" -echo "${REPLY:5}" -EOF -chmod +x /sbin/ovirt-identify-node +# Sends text to the remote server. +# +send-text () { + echo "Sending: \"$1\"" + echo "$1" 1>&3 +} -echo "Writing ovirt-functions script" -# common functions -cat > /etc/init.d/ovirt-functions << \EOF -# -*-Shell-script-*- +# Receives text from the remote server. +# +receive-text () { + read 0<&3 -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 + echo "Received: \"$REPLY\"" } + EOF echo "Writing ovirt-early init script" @@ -168,7 +112,7 @@ configure_from_network() { if [ "$status" = "0" ]; then hostname $HOSTNAME # retrieve remote config - find_srv ovirt tcp + find-server ovirt tcp printf . if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then wget --quiet -O - "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" \ @@ -219,16 +163,16 @@ start() { # now LVM partitions LVMDEVS="$DEVICES `lvscan | awk '{print $2}' | tr -d \"'\"`" - SWAPDEVS="$LVMDEVS" + SWAPDEVS="$LVMDEVS" for dev in $BLOCKDEVS; do SWAPDEVS="$SWAPDEVS `fdisk -l $dev 2>/dev/null | tr '*' ' ' \ - | awk '$5 ~ /82/ {print $1}'`" + | awk '$5 ~ /82/ {print $1}'`" done - # now check if any of these partitions are swap, and activate if so + # now check if any of these partitions are swap, and activate if so for device in $SWAPDEVS; do sig=`dd if=$device bs=1 count=10 skip=$(( $PAGESIZE - 10 )) \ - 2>/dev/null` + 2>/dev/null` if [ "$sig" = "SWAPSPACE2" ]; then swapon $device fi @@ -283,7 +227,7 @@ die() start() { echo -n $"Starting ovirt: " - find_srv ipa tcp + find-server ipa tcp krb5_conf=/etc/krb5.conf if [ ! -s $krb5_conf ]; then rm -f $krb5_conf @@ -294,17 +238,14 @@ start() { IPA_HOST=$SRV_HOST IPA_PORT=$SRV_PORT - find_srv identify tcp + # Notify the server we're awake and retrieve a keytab file krb5_tab=/etc/libvirt/krb5.tab if [ ! -s $krb5_tab ]; then - keytab=$(ovirt-identify-node -s $SRV_HOST -p $SRV_PORT) \ - || die "Failed to identify node" - # FIXME this is IPA specific, host-browser should return full URL - wget -q "http://$IPA_HOST:$IPA_PORT/config/$keytab" -O $krb5_tab \ - || die "Failed to get $krb5_tab" + ovirt-awake start $krb5_tab fi + /sbin/ovirt-identify start - find_srv collectd tcp + find-server collectd tcp collectd_conf=/etc/collectd.conf if [ -f $collectd_conf.in -a $SRV_HOST -a $SRV_PORT ]; then sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ @@ -415,8 +356,8 @@ LoadPlugin disk </Plugin> <Plugin interface> - Interface "eth0" - IgnoreSelected false + Interface "eth0" + IgnoreSelected false </Plugin> EOF diff --git a/ovirt-managed-node/AUTHOR b/ovirt-managed-node/AUTHOR new file mode 100644 index 0000000..d37a107 --- /dev/null +++ b/ovirt-managed-node/AUTHOR @@ -0,0 +1 @@ +Darryl L. Pierce <dpierce at redhat.com> diff --git a/ovirt-managed-node/ChangeLog b/ovirt-managed-node/ChangeLog new file mode 100644 index 0000000..61dc8cc --- /dev/null +++ b/ovirt-managed-node/ChangeLog @@ -0,0 +1,2 @@ +* Tue 17 Jun 2008 Darryl L. Pierce <dpierce at redhat.com> - 0.0.1-1 +- Created the initial RPM structure. diff --git a/ovirt-managed-node/Makefile b/ovirt-managed-node/Makefile new file mode 100644 index 0000000..564be1d --- /dev/null +++ b/ovirt-managed-node/Makefile @@ -0,0 +1,76 @@ +# 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. + +CC=gcc +CFLAGS=-Wall -c -g +LFLAGS=-lvirt +OBJECTS=ovirt-identify-node.o +TARGET=ovirt-identify-node +SPECFILE=ovirt-managed-node.spec +NAME=ovirt-managed-node +VERSION=0.1 +NV=$(NAME)-$(VERSION) +RPM_FLAGS=\ + --define "_topdir %(pwd)/rpm-build" \ + --define "_builddir %{_topdir}" \ + --define "_rpmdir %{_topdir}" \ + --define "_srcrpmdir %{_topdir}" \ + --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \ + --define "_specdir %{_topdir}" \ + --define "_sourcedir %{_topdir}" +SOURCES=\ + AUTHOR \ + ChangeLog \ + Makefile \ + NEWS \ + ovirt-awake \ + ovirt-identify \ + ovirt-identify-node.c \ + ovirt-managed-node.spec \ + README + + +ALL: $(TARGET) + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -rf $(OBJECTS) $(TARGET) $(NV) rpm-build + +bumpgit: + -echo "$(VERSION) $(GITRELEASE)" > version + +bumprelease: + -echo "$(VERSION) $(NEWRELEASE)" > version + +setversion: + -echo "$(VERSION) 0" > version + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $(OBJECTS) $(LFLAGS) + +tar: clean $(TARGET) + mkdir -p $(NV) + cp -a $(SOURCES) $(NV) + mkdir -p rpm-build + tar zcvf rpm-build/$(NV).tar $(NV) + rm -rf $(NV) + +rpms: tar + rpmbuild $(RPM_FLAGS) -ba $(SPECFILE) diff --git a/ovirt-managed-node/NEWS b/ovirt-managed-node/NEWS new file mode 100644 index 0000000..139597f --- /dev/null +++ b/ovirt-managed-node/NEWS @@ -0,0 +1,2 @@ + + diff --git a/ovirt-managed-node/README b/ovirt-managed-node/README new file mode 100644 index 0000000..139597f --- /dev/null +++ b/ovirt-managed-node/README @@ -0,0 +1,2 @@ + + diff --git a/ovirt-managed-node/ovirt-awake b/ovirt-managed-node/ovirt-awake new file mode 100644 index 0000000..f7f643b --- /dev/null +++ b/ovirt-managed-node/ovirt-awake @@ -0,0 +1,82 @@ +#!/bin/bash +# +# ovirt-awake Notifies the oVirt server that a managed node is +# starting up. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + connect-to-server + + receive-text + + if [ $REPLY == "HELLO?" ]; then + echo "Starting wakeup conversation." + + send-text "HELLO!" + + read 0<&3 + + if [ $REPLY == "MODE?" ]; then + send-text "AWAKEN" + + receive-text + + KEYTAB=`echo $REPLY | awk '{ print $2 }'` + + if [ -n $KEYTAB ]; then + echo "Retrieving keytab: '$KEYTAB'" + + wget $KEYTAB --output-document=$KEYTAB_FILE + else + echo "No keytab to retrieve" + fi + else + echo "Did not get a mode request." + fi + else + echo "Did not get a proper startup marker." + fi + + echo "Disconnecting." + + disconnect-from-server +} + +case "$1" in + start) + KEYTAB_FILE=$2 + SRV_HOST=$3 + SRV_PORT=$4 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/ovirt-managed-node/ovirt-identify b/ovirt-managed-node/ovirt-identify new file mode 100644 index 0000000..53f21b0 --- /dev/null +++ b/ovirt-managed-node/ovirt-identify @@ -0,0 +1,46 @@ +#!/bin/bash +# +# ovirt-identify Submits managed node details to the central server. +# +# 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. + +# Source function library +. /etc/init.d/ovirt-functions + +start () { + find-server identify tcp + + /sbin/ovirt-identify-node -s $SRV_HOST -p $SRV_PORT +} + +case "$1" in + start) + SRV_HOST=$2 + SRV_PORT=$3 + start + RETVAL=$? + ;; + + *) + echo "Usage: $0 start" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/ovirt-managed-node/ovirt-identify-node.c b/ovirt-managed-node/ovirt-identify-node.c new file mode 100644 index 0000000..3689eca --- /dev/null +++ b/ovirt-managed-node/ovirt-identify-node.c @@ -0,0 +1,457 @@ +/* identify-node -- Main entry point for the identify-node utility. + * + * 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. + */ + +#include <errno.h> +#include <getopt.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libvirt/libvirt.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/socket.h> + +int config(int argc,char** argv); +void usage(void); + +int start_conversation(void); +int send_details(void); +int end_conversation(void); + +int send_text(char* text); +int get_text(char* response,int maxlength); +int create_connection(void); + +int debug = 0; +int verbose = 0; +int testing = 0; + +#define BUFFER_LENGTH 128 + +char arch[BUFFER_LENGTH]; +char uuid[VIR_UUID_BUFLEN]; +char memsize[BUFFER_LENGTH]; +char numcpus[BUFFER_LENGTH]; +char cpuspeed[BUFFER_LENGTH]; +char hostname[256]; +int hostport = -1; +int socketfd; + +int main(int argc,char** argv) +{ + int result = 1; + + if(!config(argc,argv)) + { + fprintf(stdout,"Connecting to libvirt.\n"); + + virConnectPtr connection = virConnectOpenReadOnly(testing ? "test:///default" : NULL); + + if(debug) fprintf(stderr,"connection=%x\n",(unsigned int )connection); + + if(connection) + { + if(debug) fprintf(stdout,"Retrieving domain information.\n"); + virDomainPtr domain = virDomainLookupByID(connection,0); + + if(domain) + { + if(!virDomainGetUUIDString(domain,uuid) && debug) + { + fprintf(stdout,"Got UUID=%s\n", uuid); + } + else + { + fprintf(stderr, "Did not get UUID for node.\n"); + } + + } + else + { + fprintf(stderr,"Failed to connect to default domain.\n"); + } + + if(!strlen(uuid)) gethostname(uuid,sizeof uuid); + + virNodeInfo info; + + if(debug) fprintf(stdout,"Retrieving node information.\n"); + if(!virNodeGetInfo(connection,&info)) + { + sprintf(arch, "%s", info.model); + sprintf(memsize, "%ld", info.memory); + sprintf(numcpus, "%d", info.cpus); + sprintf(cpuspeed,"%d", info.mhz); + + if(debug) + { + fprintf(stdout,"Node Info:\n"); + fprintf(stdout," UUID: %s\n", uuid); + fprintf(stdout," Arch: %s\n", arch); + fprintf(stdout," Memory: %s\n", memsize); + fprintf(stdout," # CPUs: %s\n", numcpus); + fprintf(stdout,"CPU Speed: %s\n", cpuspeed); + } + + if(debug) fprintf(stdout, "Retrieved node information.\n"); + + if(!start_conversation() && !send_details() && !end_conversation()) + { + result = 0; + } + } + else + { + if(debug) fprintf(stderr,"Failed to get node info.\n"); + } + } + else + { + if(debug) fprintf(stderr,"Could not connect to libvirt.\n"); + } + } + else + { + usage(); + } + + return result; +} + +int config(int argc,char** argv) +{ + int result = 0; + int option; + + while((option = getopt(argc,argv,"s:p:dvth")) != -1) + { + if(debug) fprintf(stdout,"Processing argument: %c (optarg:%s)\n",option,optarg); + + switch(option) + { + case 's': strcpy(hostname,optarg); break; + case 'p': hostport = atoi(optarg); break; + case 't': testing = 1; break; + case 'd': debug = 1; break; + case 'v': verbose = 1; break; + case 'h': + // fall thru + default : result = 1; break; + } + } + + // verify that required options are provided + if(hostname == NULL || strlen(hostname) == 0) + { + fprintf(stderr,"ERROR: The server name is required. (-s [hostname])\n"); + result = 1; + } + + if(hostport <= 0) + { + fprintf(stderr,"ERROR: The server port is required. (-p [port])\n"); + result = 1; + } + + return result; +} + +void usage() +{ + fprintf(stdout,"\n"); + fprintf(stdout,"Usage: ovirt-identify [OPTION]\n"); + fprintf(stdout,"\n"); + fprintf(stdout,"\t-s [server]\t\tThe remote server's hostname.\n"); + fprintf(stdout,"\t-p [port]\t\tThe remote server's port.\n"); + fprintf(stdout,"\t-d\t\tDisplays debug information during execution.\n"); + fprintf(stdout,"\t-v\t\tDisplays verbose information during execution.\n"); + fprintf(stdout,"\t-h\t\tDisplays this help information and then exits.\n"); + fprintf(stdout,"\n"); +} + +int start_conversation(void) +{ + int result = 1; + + if(verbose || debug) fprintf(stdout,"Starting conversation with %s:%d.\n",hostname,hostport); + + if(!create_connection()) + { + if(debug || verbose) fprintf(stdout,"Connected.\n"); + + char buffer[BUFFER_LENGTH]; + + get_text(buffer,sizeof buffer); + + if(!strcmp(buffer,"HELLO?")) + { + if(debug) fprintf(stdout,"Checking for handshake.\n"); + + if(!send_text("HELLO!")) + { + if(debug) fprintf(stdout,"Handshake received. Starting conversation.\n"); + + get_text(buffer,sizeof buffer); + + if(!strcmp(buffer,"MODE?")) + { + if(debug) fprintf(stdout,"Shifting to IDENTIFY mode.\n"); + + if(!send_text("IDENTIFY")) result = 0; + } + else + { + if(debug) fprintf(stderr,"Was not asked for a mode.\n"); + } + } + } + else + { + if(debug) fprintf(stderr,"Did not receive a proper handshake.\n"); + } + } + + else + { + if(debug) fprintf(stderr,"Did not get a connection.\n"); + } + + if(debug) fprintf(stdout,"start_conversation: result=%d\n", result); + + return result; +} + +int send_value(char* label,char* value) +{ + char buffer[BUFFER_LENGTH]; + + bzero(buffer,sizeof buffer); + + sprintf(buffer,"%s=%s", label, value); + + int result = 1; + + if(!send_text(buffer)) + { + char expected[BUFFER_LENGTH]; + + bzero(expected,sizeof buffer); + bzero(buffer,sizeof buffer); + + get_text(buffer,sizeof buffer); + + sprintf(expected, "ACK %s", label); + + if(debug) fprintf(stdout,"Expecting \"%s\" : Received \"%s\"\n", expected, buffer); + + if(!strcmp(expected,buffer)) + { + result = 0; + } + } + + return result; +} + +int send_details(void) +{ + int result = 1; + + fprintf(stdout,"Sending node details.\n"); + + char buffer[BUFFER_LENGTH]; + + get_text(buffer,sizeof buffer); + + if(!strcmp(buffer,"INFO?")) + { + if((!send_value("ARCH", arch)) && + (!send_value("UUID", uuid)) && + (!send_value("NUMCPUS", numcpus)) && + (!send_value("CPUSPEED", cpuspeed)) && + (!send_value("MEMSIZE", memsize))) + { + if(!send_text("ENDINFO")) result = 0; + } + } + else + { + if(debug) fprintf(stdout,"Was not interrogated for hardware info.\n"); + } + + return result; +} + +int end_conversation(void) +{ + int result = 0; + + fprintf(stdout,"Ending conversation.\n"); + + send_text("ENDINFO"); + + close(socketfd); + + return result; +} + +ssize_t safewrite(int fd, const void *buf, size_t count) +{ + size_t nwritten = 0; + while (count > 0) { + ssize_t r = write(fd, buf, count); + + if (r < 0 && errno == EINTR) + continue; + if (r < 0) + return r; + if (r == 0) + return nwritten; + buf = (const char *)buf + r; + count -= r; + nwritten += r; + } + return nwritten; +} + +int send_text(char* text) +{ + int result = 1; + + if(debug || verbose) fprintf(stdout,"\"%s\" -> %s:%d\n", text, hostname, hostport); + + char buffer[strlen(text) + 2]; + + sprintf(buffer,"%s\n",text); + + // int sent = write(socketfd,buffer,strlen(buffer)); + + int sent = safewrite(socketfd, buffer, strlen(buffer)); + + if(sent >= 0) + { + if(debug) fprintf(stdout,"Sent %d bytes total.\n", sent); + + result = 0; + } + + return result; +} + +int saferead(int fd, void *buf, size_t count) +{ + if(debug) fprintf(stdout,"Begin saferead(%d, %x, %d)\n", fd, (unsigned int)buf, count); + + while (1) + { + ssize_t r = read (fd, buf, count); + if (r < 0 && errno == EINTR) continue; + return r; + } +} + +int get_text(char* response,int maxlength) +{ + if(debug) fprintf(stdout,"Reading up to %d bytes from socket.\n", maxlength); + // int received = read(socketfd,response,maxlength); + int received = saferead(socketfd,response,maxlength); + + response[received - 1] = 0; + + if(debug) fprintf(stdout,"Received \"%s\": size=%d (trimmed ending carriage return)\n", response, received); + + return received; +} + +int create_connection(void) +{ + int result = 1; + + if(debug) fprintf(stdout,"Creating the socket connection.\n"); + + struct addrinfo hints; + struct addrinfo* results; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + if(debug) fprintf(stdout,"Searching for host candidates.\n"); + + char port[6]; + + sprintf(port,"%d", hostport); + + if(!getaddrinfo(hostname, port, &hints, &results)) + { + if(debug) fprintf(stdout,"Got address information. Searching for a proper entry.\n"); + struct addrinfo* rptr; + + for(rptr = results; rptr != NULL; rptr = rptr->ai_next) + { + if(debug) + { + fprintf(stdout,"Attempting connection: family=%d, socket type=%d, protocol=%d\n", + rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + } + + socketfd = socket(rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + + if(socketfd == -1) + { + continue; + } + + if(connect(socketfd, rptr->ai_addr, rptr->ai_addrlen) != -1) + { + break; + } + + // invalid connection, so close it + if(debug) fprintf(stdout, "Invalid connection.\n"); + close(socketfd); + } + + if(rptr == NULL) + { + if(debug) fprintf(stdout,"Unable to connect to server %s:%d\n", hostname, hostport); + } + else + { + // success + result = 0; + } + + freeaddrinfo(results); + } + else + { + if(debug) fprintf(stderr,"No hosts found. Exiting...\n"); + } + + if(debug) fprintf(stdout, "create_connection: result=%d\n", result); + + return result; +} diff --git a/ovirt-managed-node/ovirt-managed-node.spec b/ovirt-managed-node/ovirt-managed-node.spec new file mode 100644 index 0000000..7f33344 --- /dev/null +++ b/ovirt-managed-node/ovirt-managed-node.spec @@ -0,0 +1,45 @@ +Summary: The managed node demons for oVirt. +Name: ovirt-managed-node +Version: 0.1 +Release: 1%{?dist} +License: Fedora +Group: Applications/System +Source0: http://www.ovirt.org/sources/%{name}-%{version}.tar + +BuildRoot: %{_tmppath}/%{name}-%{version}-root +URL: http://www.ovirt.org/ +BuildRequires: libvirt-devel +Requires: libvirt +ExclusiveArch: %{ix86} x86_64 ia64 + + +%description +Provides a series of daemons and support utilities to allow an +oVirt managed node to interact with the oVirt server. + + +%prep +%setup + +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT + + +%build +make + +mkdir -p $RPM_BUILD_ROOT/sbin +cp ovirt-awake $RPM_BUILD_ROOT/sbin +cp ovirt-identify $RPM_BUILD_ROOT/sbin +cp ovirt-identify-node $RPM_BUILD_ROOT/sbin + + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(755,root,root) +%doc README NEWS AUTHOR ChangeLog +/sbin/ovirt-awake +/sbin/ovirt-identify +/sbin/ovirt-identify-node diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index e127ddb..3e242cf 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]}] " @@ -51,19 +51,31 @@ class HostBrowser # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" end + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @session.write("MODE?\n") + response = @session.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) result = {} - result['IPADDR'] = @session.peeraddr[3] + result['HOSTNAME'] = @session.peeraddr[2] + result['IPADDR'] = @session.peeraddr[3] @session.write("INFO?\n") loop do @@ -75,9 +87,9 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value - + @session.write("ACK #{key}\n") end @@ -94,13 +106,13 @@ class HostBrowser ensure_present(host_info,'ARCH') ensure_present(host_info,'MEMSIZE') - puts "Searching for existing host record..." + puts "Searching for existing host record..." unless defined?(TESTING) host = Host.find(:first, :conditions => ["uuid = ?", host_info['UUID']]) if host == nil begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." - + puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) + Host.new( "uuid" => host_info['UUID'], "hostname" => host_info['HOSTNAME'], @@ -115,7 +127,7 @@ class HostBrowser # successfully connects to it via libvirt. "state" => "unavailable").save rescue Exception => error - puts "Error while creating record: #{error.message}" + puts "Error while creating record: #{error.message}" unless defined?(TESTING) end else host.uuid = host_info['UUID'] @@ -125,45 +137,45 @@ class HostBrowser host.arch = host_info['ARCH'] host.memory_in_mb = host_info['MEMSIZE'] end - + return host end - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation(ktab) - puts "#{@log_prefix} Ending conversation" - - @session.write("KTAB #{ktab}\n") - - response = @session.readline.chomp - - raise Exception.new("ERROR! Malformed response : expected ACK, got #{response}") unless response == "ACK" - - @session.write("BYE\n"); - end - # Creates a keytab if one is needed, returning the filename. # - def create_keytab(host_info, krb5_arg = nil) + def create_keytab(hostname, ipaddress, 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' + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' @keytab_filename = @keytab_dir + outfile # TODO need a way to test this portion unless defined? TESTING || File.exists?(@keytab_filename) # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) kadmin_local('addprinc -randkey ' + libvirt_princ) kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) File.chmod(0644, at keytab_filename) end - return outfile + hostname = `hostname -f`.chomp + + @session.write("KTAB http://#{hostname}/config/#{outfile}\n") + + response = @session.readline.chomp + + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end + + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + + @session.write("BYE\n"); end private @@ -185,35 +197,37 @@ def entry_point(server) while(session = server.accept) child = fork do remote = session.peeraddr[3] - - puts "Connected to #{remote}" + + puts "Connected to #{remote}" unless defined?(TESTING) # This is needed because we just forked a new process # which now needs its own connection to the database. 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) + case browser.get_mode + when "AWAKEN": browser.create_keytab(remote,session.peeraddr[3]) + when "IDENTIFY": browser.write_host_info(browser.get_remote_info) + end + + browser.end_conversation rescue Exception => error session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" + puts "ERROR #{error.message}" unless defined?(TESTING) end - - puts "Disconnected from #{remote}" + + puts "Disconnected from #{remote}" unless defined?(TESTING) end - - Process.detach(child) - end + + Process.detach(child) + end end unless defined?(TESTING) - + # The main entry point. # unless ARGV[0] == "-n" diff --git a/wui/src/host-browser/test-host-browser-awaken.rb b/wui/src/host-browser/test-host-browser-awaken.rb new file mode 100755 index 0000000..a5ca2e7 --- /dev/null +++ b/wui/src/host-browser/test-host-browser-awaken.rb @@ -0,0 +1,94 @@ +#!/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' + +# +TestHostBrowserAwaken+ +class TestHostBrowserAwaken < 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/' + 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + 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" } + servername = `hostname -f`.chomp + @session.should_receive(:write).with("KTAB http://#{servername}/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + 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("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb new file mode 100755 index 0000000..a70884d --- /dev/null +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -0,0 +1,162 @@ +#!/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"] } + + @browser = HostBrowser.new(@session) + @browser.logfile = './unit-test.log' + + # 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 is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "IDENTIFY\n" } + + result = @browser.get_mode() + + assert_equal "IDENTIFY", result, "method did not return the right value" + 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 4,info.keys.size, "Should contain two keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + assert info.include?("key1") + assert info.include?("key2") + 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 + +end diff --git a/wui/src/host-browser/test-host-browser.rb b/wui/src/host-browser/test-host-browser.rb deleted file mode 100755 index 6f4c660..0000000 --- a/wui/src/host-browser/test-host-browser.rb +++ /dev/null @@ -1,204 +0,0 @@ -#!/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, 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("KTAB 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 } - - assert_nothing_raised(Exception) { @browser.end_conversation(12345) } - end - -end -- 1.5.4.1