From apevec at redhat.com Wed Jul 1 00:19:16 2009 From: apevec at redhat.com (Alan Pevec) Date: Wed, 1 Jul 2009 02:19:16 +0200 Subject: [Ovirt-devel] [PATCH node-image] add virt-preview YUM repository Message-ID: <1246407556-23405-1-git-send-email-apevec@redhat.com> It exists only for the last Fedora release and contains recompiled rawhide versions of libvirt and qemu --- Makefile.am | 10 ++++++++-- 1 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 7cf3213..a44ae49 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,6 +29,9 @@ CUR_RAWHIDE = 12 FEDORA = $(shell rpm --eval '%{fedora}') ARCH = $(shell rpm --eval '%{_arch}') +CUR_PREVIEW = 11 +PREVIEW_URL ?= http://markmc.fedorapeople.org/virt-preview/f$(CUR_PREVIEW)/$(ARCH) + NVR = $(PACKAGE)-$(VERSION)-$(ARCH) EXTRA_DIST = \ @@ -76,11 +79,14 @@ repos.ks: FEDORA_REPO=f$(FEDORA) ;\ FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/${ARCH}/os,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-$(FEDORA)&arch=$(ARCH))" ;\ OVIRT_DISTRO=$(FEDORA) ;\ - UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH},--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))" ;\ + UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH},--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))\n" ;\ + if [ 0$(FEDORA) == 0$(CUR_PREVIEW) ]; then \ + UPDATE_REPO_LINE="$${UPDATE_REPO_LINE}repo --name=preview --baseurl=$(PREVIEW_URL)\n" ;\ + fi ;\ fi ;\ echo "repo --name=$${FEDORA_REPO} $${FEDORA_REPO_LOC}" > $@ ;\ echo "repo --name=ovirt-org --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/$(ARCH)" >> $@ ;\ - echo "$${UPDATE_REPO_LINE}" >> $@ ;\ + printf "$${UPDATE_REPO_LINE}" >> $@ ;\ echo "repo --name=ovirt-local --baseurl=$(OVIRT_LOCAL_REPO)" >> $@ \ ) -- 1.6.0.6 From apevec at redhat.com Wed Jul 1 00:32:48 2009 From: apevec at redhat.com (Alan Pevec) Date: Wed, 1 Jul 2009 02:32:48 +0200 Subject: [Ovirt-devel] [PATCH node] assume eth0 when BOOTIF is not specified Message-ID: <1246408368-23506-1-git-send-email-apevec@redhat.com> otherwise invalid "ifcfg-" file is generated --- scripts/ovirt-early | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diff --git a/scripts/ovirt-early b/scripts/ovirt-early index ac54e4b..b4de30e 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -67,6 +67,10 @@ configure_from_network() { fi fi fi + else + # for non-PXE boot when BOOTIF parameter is not specified + # otherwise default network config is invalid + DEVICE=eth0 fi # default oVirt network configuration: # bridge each ethernet device in the system -- 1.6.0.6 From jguiditt at redhat.com Wed Jul 1 01:03:15 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Tue, 30 Jun 2009 21:03:15 -0400 Subject: [Ovirt-devel] Follow-up to fix sorting In-Reply-To: <1246283369-13477-1-git-send-email-sseago@redhat.com> References: <1246283369-13477-1-git-send-email-sseago@redhat.com> Message-ID: <1246410196-27085-1-git-send-email-jguiditt@redhat.com> This should fix sorting, if applied on top of scott's last patch, so it may be fixable in that patch, don't have time to test that part though. Hope this is of some help. From jguiditt at redhat.com Wed Jul 1 01:03:16 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Tue, 30 Jun 2009 21:03:16 -0400 Subject: [Ovirt-devel] [PATCH server] Fix Vm.paged_with_perms In-Reply-To: <1246283369-13477-1-git-send-email-sseago@redhat.com> References: <1246283369-13477-1-git-send-email-sseago@redhat.com> Message-ID: <1246410196-27085-2-git-send-email-jguiditt@redhat.com> This fixes the sorting for cloud view by uptime, so sort link should be ok to be enabled now. Signed-off-by: Jason Guiditta --- src/app/models/vm.rb | 16 ++++++++++------ src/test/unit/vm_test.rb | 6 ++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index 393f8fc..c22a02e 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -420,19 +420,23 @@ class Vm < ActiveRecord::Base return i end + def self.calc_uptime + "case when state='running' then + (cast(total_uptime || ' sec' as interval) + + (now() - total_uptime_timestamp)) + else cast(total_uptime || ' sec' as interval) + end as calc_uptime" + end + # Make method for calling paginated vms easier for clients. # TODO: Might want to have an optional param for per_page var def self.paged_with_perms(user, priv, page, order) - Vm.paginate(:include => [{:vm_resource_pool => + Vm.paginate(:joins => [{:vm_resource_pool => {:permissions => {:role => :privileges}}}], :conditions => ["privileges.name=:priv and permissions.uid=:user", { :user => user, :priv => priv }], - :select => "*, case when state='running' then - (cast(total_uptime || ' sec' as interval) + - (now() - total_uptime_timestamp)) - else cast(total_uptime || ' sec' as interval) - end as calc_uptime", + :select => calc_uptime, :per_page => 5, :page => page, :order => order) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index a5d6b3d..d405640 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -195,4 +195,10 @@ class VmTest < ActiveSupport::TestCase def test_paginated_results assert_equal 5, Vm.paged_with_perms('ovirtadmin', Privilege::VIEW, 1, 'vms.id').size end + + def test_paginated_results_sorting + vms = Vm.paged_with_perms('ovirtadmin', Privilege::VIEW, 1, 'calc_uptime') + assert_equal(5, vms.size) + assert_equal('00:00:00',vms[0].calc_uptime) + end end -- 1.6.0.6 From pronix.service at gmail.com Wed Jul 1 10:51:42 2009 From: pronix.service at gmail.com (dima vasiletc) Date: Wed, 01 Jul 2009 14:51:42 +0400 Subject: [Ovirt-devel] error with gssapi In-Reply-To: <20090625105035.23411f41@tp.mains.net> References: <4A4349C8.2000101@gmail.com> <20090625105035.23411f41@tp.mains.net> Message-ID: <4A4B3FBE.4000309@gmail.com> Hello i set for root rsa key and login in second node. this is error appear after run "libvirt-qpid --broker main.forex-24h.com" on second node 2009-jul-01 10:41:38 info QMF Agent Initialized: broker=main.forex-24h.com:5672 interval=3 storeFile= 2009-jul-01 10:41:39 debug QMF Agent attempting to connect to the broker... 2009-jul-01 10:41:39 debug ConnectionImpl created for \x00- 2009-jul-01 10:41:39 info Connecting to tcp:main.forex-24h.com:5672 2009-jul-01 10:41:39 debug TCPConnector created for \x00- 2009-jul-01 10:41:39 debug RECV [37926 main.forex-24h.com:5672] INIT(0-10) 2009-jul-01 10:41:39 trace RECV [37926 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionStartBody: server-properties={qpid.federation_tag:V2:36:str16(9b4534b8-ec7a-489c-97aa-8c39a0af5b4f)}; mechanisms=str16{V2:0:str16()}; locales=str16{V2:5:str16(en_US)}; }] 2009-jul-01 10:41:39 debug CyrusSasl::start() 2009-jul-01 10:41:39 debug min_ssf: 0, max_ssf: 256 2009-jul-01 10:41:39 debug Exception constructed: Sasl error: SASL(-4): no mechanism available: No worthy mechs found (qpid/client/SaslFactory.cpp:226) 2009-jul-01 10:41:39 warning Closing connection due to internal-error: Sasl error: SASL(-4): no mechanism available: No worthy mechs found (qpid/client/SaslFactory.cpp:226) 2009-jul-01 10:41:39 trace SENT [37926 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionCloseBody: reply-code=501; reply-text=internal-error: Sasl error: SASL(-4): no mechanism available: No worthy mechs found (qpid/client/SaslFactory.cpp:226); }] 2009-jul-01 10:41:39 trace RECV [37926 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionCloseOkBody: }] 2009-jul-01 10:41:39 debug Exception constructed: internal-error: Sasl error: SASL(-4): no mechanism available: No worthy mechs found (qpid/client/SaslFactory.cpp:226) 2009-jul-01 10:41:39 debug Exception constructed: internal-error: Sasl error: SASL(-4): no mechanism available: No worthy mechs found (qpid/client/SaslFactory.cpp:226) 2009-jul-01 10:41:39 debug Connection failed: exception=internal-error: Sasl error: SASL(-4): no mechanism available: No worthy mechs found (qpid/client/SaslFactory.cpp:226) also i check this list * Networking issues prevent node from communicating properly with server. (Ok, i connect using ssh) * DNS configuration issues prevent getting DNS SRV records for various services. (Ok, all querys resolved correct) * DNS configuration issues cause kerberos authentication to fail.(not know how to check) * Time skew causes kerberos authentication to fail.(kinit admin on second node work correct) * Time skew causes timestamps from node sent to WUI to seem to be out of date and so get marked as unavailable (no recent keepalive). (time on both servers are similar) What i must check else ? On 06/25/2009 09:50 PM, Ian Main wrote: > On Thu, 25 Jun 2009 13:56:24 +0400 > dima vasiletc wrote: > > >> Hello >> I found on ovirt.org instruction for set unavailable(enable) to >> available(enable) >> i run "ruby /usr/share/ovirt-server/qmf-libvirt-example.rb" >> and get >> >> Error caching credentials; attempting to continue... >> Connecting to amqp://main.forex-24h.com:5672.. >> /usr/lib/ruby/site_ruby/1.8/qpid/delegates.rb:209:in >> `connection_start'/usr/lib/ruby/site_ruby/1.8/qpid/delegates.rb:209: >> [BUG] Segmentation fault >> ruby 1.8.6 (2008-08-11) [x86_64-linux] >> > > qmf-libvirt-example has to access the ovirt qpid kerberos ticket in order > to connect to qpidd. In order to that it has to be run as root. > > If everything else is working (looks like it is) then that should fix it. > > Ian > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > > -- ? ?????????, ??????? -------------- next part -------------- An HTML attachment was scrubbed... URL: From pronix.service at gmail.com Wed Jul 1 12:45:34 2009 From: pronix.service at gmail.com (dima vasiletc) Date: Wed, 01 Jul 2009 16:45:34 +0400 Subject: [Ovirt-devel] error with gssapi In-Reply-To: <20090625105035.23411f41@tp.mains.net> References: <4A4349C8.2000101@gmail.com> <20090625105035.23411f41@tp.mains.net> Message-ID: <4A4B5A6E.4070804@gmail.com> I fix it. i found that i haven't ovirt.keytab I run "/usr/bin/ovirt-add-host HOSTNAME /usr/share/ovirt-server/ovirt.keytab" Thanks On 06/25/2009 09:50 PM, Ian Main wrote: > On Thu, 25 Jun 2009 13:56:24 +0400 > dima vasiletc wrote: > > >> Hello >> I found on ovirt.org instruction for set unavailable(enable) to >> available(enable) >> i run "ruby /usr/share/ovirt-server/qmf-libvirt-example.rb" >> and get >> >> Error caching credentials; attempting to continue... >> Connecting to amqp://main.forex-24h.com:5672.. >> /usr/lib/ruby/site_ruby/1.8/qpid/delegates.rb:209:in >> `connection_start'/usr/lib/ruby/site_ruby/1.8/qpid/delegates.rb:209: >> [BUG] Segmentation fault >> ruby 1.8.6 (2008-08-11) [x86_64-linux] >> > > qmf-libvirt-example has to access the ovirt qpid kerberos ticket in order > to connect to qpidd. In order to that it has to be run as root. > > If everything else is working (looks like it is) then that should fix it. > > Ian > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > > -- ? ?????????, ??????? -------------- next part -------------- An HTML attachment was scrubbed... URL: From hbrock at redhat.com Wed Jul 1 13:01:25 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Wed, 1 Jul 2009 09:01:25 -0400 Subject: [Ovirt-devel] error with gssapi In-Reply-To: <4A4B3FBE.4000309@gmail.com> References: <4A4349C8.2000101@gmail.com> <20090625105035.23411f41@tp.mains.net> <4A4B3FBE.4000309@gmail.com> Message-ID: <20090701130125.GI27234@redhat.com> On Wed, Jul 01, 2009 at 02:51:42PM +0400, dima vasiletc wrote: > Hello > i set for root rsa key and login in second node. > this is error appear after run "libvirt-qpid --broker > main.forex-24h.com" on second node [snip] > > also i check this list > > * Networking issues prevent node from communicating properly with > server. (Ok, i connect using ssh) > * DNS configuration issues prevent getting DNS SRV records for > various services. (Ok, all querys resolved correct) > * DNS configuration issues cause kerberos authentication to > fail.(not know how to check) > * Time skew causes kerberos authentication to fail.(kinit admin on > second node work correct) > * Time skew causes timestamps from node sent to WUI to seem to be > out of date and so get marked as unavailable (no recent > keepalive). (time on both servers are similar) > > What i must check else ? > Hmm, you've tried a lot. Final check on kerberos: You should be able to "kinit ovirtadmin" on both the server and the node. If you can't, check to make sure that "hostname" matches the host's dns name. Take care, --Hugh From hbrock at redhat.com Wed Jul 1 13:03:40 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Wed, 1 Jul 2009 09:03:40 -0400 Subject: [Ovirt-devel] error with gssapi In-Reply-To: <4A4B5A6E.4070804@gmail.com> References: <4A4349C8.2000101@gmail.com> <20090625105035.23411f41@tp.mains.net> <4A4B5A6E.4070804@gmail.com> Message-ID: <20090701130339.GJ27234@redhat.com> On Wed, Jul 01, 2009 at 04:45:34PM +0400, dima vasiletc wrote: > I fix it. > i found that i haven't ovirt.keytab > I run "/usr/bin/ovirt-add-host HOSTNAME > /usr/share/ovirt-server/ovirt.keytab" > Thanks Oh, excellent... so what that means is the node failed to ask for and receive a keytab when it booted (this should happen automatically). It can fail for a variety of reasons -- next time it happens, make sure the node's keytab has been generated and placed in (I think) the IPA webroot. Take care, --Hugh From dpierce at redhat.com Wed Jul 1 13:56:04 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 1 Jul 2009 09:56:04 -0400 Subject: [Ovirt-devel] [PATCH node] assume eth0 when BOOTIF is not specified In-Reply-To: <1246408368-23506-1-git-send-email-apevec@redhat.com> References: <1246408368-23506-1-git-send-email-apevec@redhat.com> Message-ID: <20090701135604.GD3539@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 01, 2009 at 02:32:48AM +0200, Alan Pevec wrote: > otherwise invalid "ifcfg-" file is generated > --- > scripts/ovirt-early | 4 ++++ > 1 files changed, 4 insertions(+), 0 deletions(-) > > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > index ac54e4b..b4de30e 100755 > --- a/scripts/ovirt-early > +++ b/scripts/ovirt-early > @@ -67,6 +67,10 @@ configure_from_network() { > fi > fi > fi > + else > + # for non-PXE boot when BOOTIF parameter is not specified > + # otherwise default network config is invalid > + DEVICE=eth0 > fi > # default oVirt network configuration: > # bridge each ethernet device in the system > -- > 1.6.0.6 Visual inspection: ACK. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Wed Jul 1 16:31:25 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 1 Jul 2009 12:31:25 -0400 Subject: [Ovirt-devel] Replacement patch for bz#507455 Message-ID: <1246465886-9329-1-git-send-email-dpierce@redhat.com> This patch includes a fix to no longer list devices with 0 bytes of storage, such as removeable media devices with no media present. From dpierce at redhat.com Wed Jul 1 16:31:26 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 1 Jul 2009 12:31:26 -0400 Subject: [Ovirt-devel] [PATCH node] Fixes when a removable media is removed. bz#507455 In-Reply-To: <1246465886-9329-1-git-send-email-dpierce@redhat.com> References: <1246465886-9329-1-git-send-email-dpierce@redhat.com> Message-ID: <1246465886-9329-2-git-send-email-dpierce@redhat.com> If a device is a removeable media device, then o-c-storage will now check if there's media present before attempting to get the storage size for the device. Any device found must also have a storage amount greater than 0 megs. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-storage | 173 +++++++++++++++++++++++++++++++----------- 1 files changed, 129 insertions(+), 44 deletions(-) diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage index 0fcc0bc..65663dc 100755 --- a/scripts/ovirt-config-storage +++ b/scripts/ovirt-config-storage @@ -11,6 +11,7 @@ ME=$(basename "$0") warn() { printf '%s: %s\n' "$ME" "$*" >&2; } die() { warn "$*"; exit 1; } +debug() { if $debugging; then printf "[DEBUG] %s\n" "$*"; fi } trap '__st=$?; stop_log; exit $__st' 0 trap 'exit $?' 1 2 13 15 @@ -36,8 +37,12 @@ logging_min_size=5 data_min_size=5 swap_min_size=5 +# Gets the drive's size and sets the supplied variable. +# $1 - the drive +# $2 - the variable get_drive_size() { + debug "get_drive_size: start" local drive=$1 local space_var=$2 @@ -62,16 +67,44 @@ get_drive_size() size=$(hal-get-property --udi "$udi" --key storage.size) if [[ "${size}" == "0" ]]; then # disk is probably hot-swappable, use different HAL key - size=$(hal-get-property --udi "$udi" --key storage.removable.media_size) + # but first check that it is removeable media and that media is present + if [[ "true" == "$(hal-get-property --udi "$udi" --key storage.removable.media_available)" ]]; then + size=$(hal-get-property --udi "$udi" --key storage.removable.media_size) + fi fi fi size=$(echo "scale=0; $size / (1024 * 1024)" | bc -l) - echo "$drive ($size MB)" + eval $space_var=$size + + debug "::size=$size" + + debug "get_drive_size: exit" +} + +print_drive_size () +{ + debug "print_drive_size: start" + local drive=$1 + local udi=${2-} + + if [ -z "$udi" ]; then + for this_udi in $(hal-find-by-capability --capability storage); do + if [[ "$(hal-get-property --udi $this_udi --key block.device)" = "$drive" ]]; then + udi=$this_udi + fi + done + fi + get_drive_size $drive SIZE + debug "::drive=$drive" + debug "::SIZE=$SIZE" + echo "$drive ($SIZE MB)" echo "Disk Identifier: $(basename "$udi")" if [ -n "$space_var" ]; then eval $space_var=$size fi + + debug "print_drive_size: end" } check_partition_sizes() @@ -127,73 +160,112 @@ check_partition_sizes() return $rc } -# Find a usable/selected storage device. -# If there are none, give a diagnostic and return nonzero. -# If there is just one, e.g., /dev/sda, treat it as selected (see below). -# and return 0. If there are two or more, make the user select one -# or decline. Upon decline, return nonzero. Otherwise, print the -# selected name, then return 0. -# Sample output: /dev/sda -get_dev_name() +# Ensures the device is acceptable +# $1 - the device +check_if_device_is_good () +{ + debug "check_if_device_is_good: start" + local device=$1 + local result=1 + + # Must start with a '/'. + case $device in + *' '*) + # we use space as separator + warn "block device name '$device' contains space; skipping"; + continue;; + /*) + local SIZE + get_drive_size $device SIZE + debug "::SIZE=$SIZE" + if [ $SIZE -gt 0 ]; then result=0; fi + ;; + *) warn "block device name $device doesn't start with '/';" \ + " skipping"; continue;; + esac + + debug "check_if_device_is_good: end (result=$result)" + return $result +} + +get_drive_list () { + debug "get_drive_list: start" + local list_var=$1 + local udi_list=$(hal-find-by-capability --capability storage) + debug "list_var=$list_var" + if test -z "$udi_list"; then warn "ERROR: no usable storage devices detected" return 1 fi local d devices sizes + for d in $udi_list; do + debug "Examining $d" local drive_type=$(hal-get-property --udi "$d" --key storage.drive_type) + debug "::drive_type=$drive_type" test "X$drive_type" = Xdisk || continue local block_dev=$(hal-get-property --udi "$d" --key block.device) - # Must start with a '/'. - case $block_dev in - *' '*) - # we use space as separator - warn "block device name '$block_dev' contains space; skipping"; - continue;; - /*) ;; - *) warn "block device name $block_dev doesn't start with '/';" \ - " skipping"; continue;; - esac - test -z "$devices" \ - && devices="$block_dev" \ - || devices="$devices $block_dev" + debug "::block_dev=$block_dev" + check_if_device_is_good $block_dev + rc=$? + if [ $rc = 0 ]; then + debug "::Acceptable device: $block_dev" + test -z "$devices" \ + && devices="$block_dev" \ + || devices="$devices $block_dev" + fi done # FIXME: workaround for detecting virtio block devices devices="$devices $(ls /dev/vd? 2> /dev/null | xargs)" devices=$(echo $devices | tr ' ' '\n' | sort -u | xargs) - local num_devices=$(echo "$devices" | wc -w) - # If there's only one device, use it. - case $num_devices in - 0) warn "ERROR: found no usable block device"; return 1;; - 1) echo "$devices"; return 0;; - *) ;; # found more than one + if [ -n "$devices" ]; then + eval $list_var='"$devices"' + fi + + debug "get_drive_list: end" +} + +select_a_drive () +{ + local DRIVE_VAR=$1 + local choices + + get_drive_list choices + case $(echo "$choices" | wc -w) in + 0) die "ERROR: there are no usable block devices" ;; + 1) echo "$choices"; return 0;; + *) ;; # continue to selection esac - # There are two or more; make the user choose. - # display description for each disk - for d in $devices; do - get_drive_size $d >&2 + for choice in $choices; do + print_drive_size $choice done - local choices="$devices Abort" - select device in $choices - do - test "$device" = Abort && return 1 - test -z "$device" && continue - echo "$device" - return 0 + + while true; do + select device in $choices Abort + do + case $device in + "Abort") return 1;; + *) eval $DRIVE_VAR=$device; return 0;; + esac + done done } do_configure() { local name_and_size - DRIVE=$(get_dev_name) || return 0 - get_drive_size $DRIVE SPACE + select_a_drive DRIVE + debug "DRIVE=$DRIVE" + test -z "$DRIVE" && return 0 + + print_drive_size $DRIVE SPACE printf "\n\nPlease configure storage partitions.\n\n" printf "* Enter partition sizes in MB.\n" @@ -285,7 +357,7 @@ do_review() The local disk will be repartitioned as follows: ================================================ - Physical Hard Disk: $(get_drive_size $DRIVE) + Physical Hard Disk: $(print_drive_size $DRIVE) Boot partition size: $BOOT_SIZE MB Swap partition size: $SWAP_SIZE MB Installation partition size: $ROOT_SIZE * 2 MB @@ -522,7 +594,7 @@ DATA_SIZE=${OVIRT_VOL_DATA_SIZE:-$default_data_size} if [ -n "$OVIRT_INIT" ]; then # if present, use the drive selected with 'ovirt_init' boot parameter DRIVE=$OVIRT_INIT - get_drive_size $DRIVE SPACE + print_drive_size $DRIVE SPACE fi # if the node is Fedora then use GPT, otherwise use MBR @@ -551,6 +623,19 @@ if [ "$1" == "AUTO" ]; then log "Missing device parameter: unable to partition any disk" fi else + # check commandline options + debugging=false + + while getopts dv c; do + case $c in + d) debugging=true;; + v) set -v;; + '?') die "invalid option \`-$OPTARG'";; + :) die "missing argument to \`-$OPTARG' option";; + *) die "internal error";; + esac + done + OPTIONS="\"Configure\" \"Review\" \"Commit Changes And Quit\" \"Return To Menu\"" eval set $OPTIONS PS3="Choose an option: " -- 1.6.2.5 From sseago at redhat.com Wed Jul 1 17:08:32 2009 From: sseago at redhat.com (Scott Seago) Date: Wed, 1 Jul 2009 17:08:32 +0000 Subject: [Ovirt-devel] [PATCH server] fixed expected status messages that were failing unit/functional tests. Message-ID: <1246468112-15097-1-git-send-email-sseago@redhat.com> --- .../functional/cloud/instance_controller_test.rb | 2 +- src/test/unit/vm_service_test.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/functional/cloud/instance_controller_test.rb b/src/test/functional/cloud/instance_controller_test.rb index 71d132b..0d713d4 100644 --- a/src/test/functional/cloud/instance_controller_test.rb +++ b/src/test/functional/cloud/instance_controller_test.rb @@ -50,7 +50,7 @@ class Cloud::InstanceControllerTest < ActionController::TestCase def test_add_valid_task post(:index,{:submit_for_list => 'Shutdown', :ids => [vms(:production_mysqld_vm).id]}) - assert_equal('shutdown_vm successful.', flash[:notice]) + assert_equal('shutdown_vm submitted.', flash[:notice]) assert_redirected_to :action => :index, :ids => vms(:production_mysqld_vm).id end diff --git a/src/test/unit/vm_service_test.rb b/src/test/unit/vm_service_test.rb index 7bd00ad..45b882b 100644 --- a/src/test/unit/vm_service_test.rb +++ b/src/test/unit/vm_service_test.rb @@ -84,7 +84,7 @@ class VmServiceTest < ActiveSupport::TestCase # (should be the same message if one or more, so we have one test for # each of those cases) def test_success_message_from_single_vm - assert_equal("shutdown_vm successful.", + assert_equal("shutdown_vm submitted.", svc_vm_actions(vms(:production_mysqld_vm).id, 'shutdown_vm', nil)) end @@ -92,7 +92,7 @@ class VmServiceTest < ActiveSupport::TestCase # (should be the same message if one or more, so we have one test for # each of those cases) def test_success_message_for_multiple_vms - assert_equal("shutdown_vm successful.", + assert_equal("shutdown_vm submitted.", svc_vm_actions([vms(:production_postgresql_vm).id, vms(:production_mysqld_vm).id, vms(:foobar_prod1_vm).id], 'shutdown_vm', nil)) @@ -111,7 +111,7 @@ class VmServiceTest < ActiveSupport::TestCase # each of those cases) def test_success_message_from_single_vm_with_less_privileged_user set_login_user('testuser') - assert_equal("shutdown_vm successful.", + assert_equal("shutdown_vm submitted.", svc_vm_actions(vms(:corp_com_qa_postgres_vm).id, 'shutdown_vm', nil)) end -- 1.6.0.6 From mmorsi at redhat.com Wed Jul 1 17:45:28 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 01 Jul 2009 13:45:28 -0400 Subject: [Ovirt-devel] [PATCH server] fixed expected status messages that were failing unit/functional tests. In-Reply-To: <1246468112-15097-1-git-send-email-sseago@redhat.com> References: <1246468112-15097-1-git-send-email-sseago@redhat.com> Message-ID: <4A4BA0B8.7060402@redhat.com> ACK. Tests are fixed. -Mo From jguiditt at redhat.com Wed Jul 1 23:30:10 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 01 Jul 2009 19:30:10 -0400 Subject: [Ovirt-devel] [PATCH server] fixed expected status messages that were failing unit/functional tests. In-Reply-To: <4A4BA0B8.7060402@redhat.com> References: <1246468112-15097-1-git-send-email-sseago@redhat.com> <4A4BA0B8.7060402@redhat.com> Message-ID: <1246491010.6298.0.camel@localhost.localdomain> On Wed, 2009-07-01 at 13:45 -0400, Mohammed Morsi wrote: > ACK. Tests are fixed. > > -Mo > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel My bad, thanks for fixing that Scott. Guess I was rushing to push after those minor tweaks we discussed in irc and forgot to run tests again. -j From sseago at redhat.com Thu Jul 2 05:24:02 2009 From: sseago at redhat.com (Scott Seago) Date: Thu, 2 Jul 2009 05:24:02 +0000 Subject: [Ovirt-devel] [PATCH server] UI for accumulated uptime for VMs. (revised) Message-ID: <1246512242-5077-1-git-send-email-sseago@redhat.com> This revised version of the patch incorporates jguidditta's sorting fix as well as some other changes required to make this work fully on the cloud UI side. --- src/app/controllers/pool_controller.rb | 2 +- src/app/controllers/resources_controller.rb | 4 +++- src/app/controllers/smart_pools_controller.rb | 5 ++--- src/app/helpers/application_helper.rb | 14 ++++++++++++++ src/app/models/vm.rb | 11 ++++++++++- src/app/views/cloud/instance/_list.rhtml | 4 ++++ src/app/views/vm/_grid.rhtml | 1 + src/app/views/vm/show.rhtml | 4 +++- src/test/unit/vm_test.rb | 6 ++++++ 9 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/app/controllers/pool_controller.rb b/src/app/controllers/pool_controller.rb index 74a958c..44cb780 100644 --- a/src/app/controllers/pool_controller.rb +++ b/src/app/controllers/pool_controller.rb @@ -96,7 +96,7 @@ class PoolController < ApplicationController def vms_json(args) attr_list = [:id, :description, :uuid, :num_vcpus_allocated, :memory_allocated_in_mb, - :vnic_mac_addr, :state, :id] + :vnic_mac_addr, :state, :calc_uptime, :id] if (@pool.is_a? VmResourcePool) and @pool.get_hardware_pool.can_view(@user) attr_list.insert(3, [:host, :hostname]) end diff --git a/src/app/controllers/resources_controller.rb b/src/app/controllers/resources_controller.rb index 9d1074a..87f07f5 100644 --- a/src/app/controllers/resources_controller.rb +++ b/src/app/controllers/resources_controller.rb @@ -63,7 +63,9 @@ class ResourcesController < PoolController def vms_json svc_show(params[:id]) - super(:full_items => @pool.vms, :find_opts => {}, :include_pool => :true) + super(:full_items => @pool.vms, + :find_opts => {:select => Vm.calc_uptime}, + :include_pool => :true) end def delete diff --git a/src/app/controllers/smart_pools_controller.rb b/src/app/controllers/smart_pools_controller.rb index 8762ac0..b355f4b 100644 --- a/src/app/controllers/smart_pools_controller.rb +++ b/src/app/controllers/smart_pools_controller.rb @@ -76,11 +76,10 @@ class SmartPoolsController < PoolController end - def items_json_internal(item_class, item_assoc) + def items_json_internal(item_class, item_assoc, find_opts = {}) if params[:id] svc_show(params[:id]) full_items = @pool.send(item_assoc) - find_opts = {} include_pool = false else # FIXME: no permissions or usage checks here yet @@ -93,7 +92,7 @@ class SmartPoolsController < PoolController else conditions = ["#{item_class.table_name}.id not in (?)", pool_items] end - find_opts = {:conditions => conditions} + find_opts[:conditions] = conditions include_pool = true end { :full_items => full_items, :find_opts => find_opts, :include_pool => include_pool} diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index 0178ad0..0c6562e 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -171,4 +171,18 @@ module ApplicationHelper def flash_path(source) compute_public_path(source, 'swfs', 'swf') end + + def number_to_duration(input_num) + input_int = input_num.to_i + hours_to_seconds = [input_int/3600 % 24, + input_int/60 % 60, + input_int % 60].map{|t| t.to_s.rjust(2,'0')}.join(':') + days = input_int / 86400 + day_str = "" + if days > 0 + day_label = (days > 1) ? "days" : "day" + day_str = "#{days} #{day_label} " + end + day_str + hours_to_seconds + end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index d4696cf..f0eaf94 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -420,14 +420,23 @@ class Vm < ActiveRecord::Base return i end + def self.calc_uptime + "*, case when state='running' then + (cast(total_uptime || ' sec' as interval) + + (now() - total_uptime_timestamp)) + else cast(total_uptime || ' sec' as interval) + end as calc_uptime" + end + # Make method for calling paginated vms easier for clients. # TODO: Might want to have an optional param for per_page var def self.paged_with_perms(user, priv, page, order) - Vm.paginate(:include => [{:vm_resource_pool => + Vm.paginate(:joins => [{:vm_resource_pool => {:permissions => {:role => :privileges}}}], :conditions => ["privileges.name=:priv and permissions.uid=:user", { :user => user, :priv => priv }], + :select => calc_uptime, :per_page => 5, :page => page, :order => order) diff --git a/src/app/views/cloud/instance/_list.rhtml b/src/app/views/cloud/instance/_list.rhtml index 1fa46de..95e5518 100644 --- a/src/app/views/cloud/instance/_list.rhtml +++ b/src/app/views/cloud/instance/_list.rhtml @@ -14,6 +14,9 @@ ">
<%= sort_link_helper "State", "state" %>
+ "> +
<%= sort_link_helper "Total Run Time", "calc_uptime" %>
+
IP Address
Load
@@ -25,6 +28,7 @@
<%= vm.description %>
<%= vm.provisioning %>
<%= vm.state.capitalize %>
+
<%= number_to_duration(vm.get_calculated_uptime) %>
N/A
N/A
diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml index b137de6..a110011 100644 --- a/src/app/views/vm/_grid.rhtml +++ b/src/app/views/vm/_grid.rhtml @@ -36,6 +36,7 @@ {display: 'Memory (MB)', name : 'memory_allocated', width : 60, sortable : true, align: 'right'}, {display: 'vNIC Mac Addr', name : 'vnic_mac_addr', width : 60, sortable : true, align: 'right'}, {display: 'State', name : 'state', width : 50, sortable : true, align: 'right'}, + {display: 'Total Run Time', name : 'calc_uptime', width : 50, align: 'right'}, {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } ], sortname: "description", diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index 0f70da8..ffe5055 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -111,6 +111,7 @@ Provisioning source:
State:
Pending State:
+ Total Run Time:
<%=h @vm.uuid %>
@@ -128,7 +129,8 @@ <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) <% end -%>
- <%=h @vm.get_pending_state %> + <%=h @vm.get_pending_state %>
+ <%=h number_to_duration(@vm.get_calculated_uptime) %>
qpidd between main node and second node ? Thanks. -- ? ?????????, ??????? From pronix.service at gmail.com Thu Jul 2 11:42:37 2009 From: pronix.service at gmail.com (dima vasiletc) Date: Thu, 02 Jul 2009 15:42:37 +0400 Subject: [Ovirt-devel] Re: qpid connection In-Reply-To: <4A4C8A9E.5040901@gmail.com> References: <4A4C8A9E.5040901@gmail.com> Message-ID: <4A4C9D2D.5000207@gmail.com> I think i found bug. when i start libvirt-qpid from second node it crashed with message 2009-07-02 11:40:41 trace RCVD SchemaRequest: package=com.redhat.libvirt class=domain libvirt-qpid: symbol lookup error: libvirt-qpid: undefined symbol: _ZN4qpid7framing6Buffer9putBin128EPh also i attache all output On 07/02/2009 02:23 PM, dima vasiletc wrote: > Hello > I try found why my second host marked as unavailable and appear question. > should there be a establlished connection libvirt-qpid <-->qpidd > between main node and second node ? > Thanks. > -- ? ?????????, ??????? -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: debug.log URL: From hbrock at redhat.com Thu Jul 2 12:04:09 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Thu, 2 Jul 2009 08:04:09 -0400 Subject: [Ovirt-devel] Re: qpid connection In-Reply-To: <4A4C9D2D.5000207@gmail.com> References: <4A4C8A9E.5040901@gmail.com> <4A4C9D2D.5000207@gmail.com> Message-ID: <20090702120409.GZ27234@redhat.com> On Thu, Jul 02, 2009 at 03:42:37PM +0400, dima vasiletc wrote: > I think i found bug. > when i start libvirt-qpid from second node it crashed with message > > 2009-07-02 11:40:41 trace RCVD SchemaRequest: package=com.redhat.libvirt > class=domain > libvirt-qpid: symbol lookup error: libvirt-qpid: undefined symbol: > _ZN4qpid7framing6Buffer9putBin128EPh > > > also i attache all output We're seeing the same thing Dima, thanks for confirming. Unfortunately the libvirt-qpid maintainer is out on vacation until next week. We have a lovely surprise in store for him when he returns ... Take care, --Hugh > > > On 07/02/2009 02:23 PM, dima vasiletc wrote: > >Hello > >I try found why my second host marked as unavailable and appear question. > >should there be a establlished connection libvirt-qpid <-->qpidd > >between main node and second node ? > >Thanks. > > > > > -- > ? ?????????, ??????? > > 2009-07-02 11:40:40 info QMF Agent Initialized: broker=main.forex-24h.com:5672 interval=3 storeFile= > 2009-07-02 11:40:41 debug QMF Agent attempting to connect to the broker... > 2009-07-02 11:40:41 debug ConnectionImpl created for \x00- > > 2009-07-02 11:40:41 info Connecting to tcp:main.forex-24h.com:5672 > 2009-07-02 11:40:41 debug TCPConnector created for \x00- > > 2009-07-02 11:40:41 debug RECV [52606 main.forex-24h.com:5672] INIT(0-10) > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionStartBody: server-properties={qpid.federation_tag:V2:36:str16(9b4534b8-ec7a-489c-97aa-8c39a0af5b4f)}; mechanisms=str16{V2:6:str16(GSSAPI)}; locales=str16{V2:5:str16(en_US)}; }] > 2009-07-02 11:40:41 debug CyrusSasl::start(GSSAPI) > 2009-07-02 11:40:41 debug min_ssf: 0, max_ssf: 256 > 2009-07-02 11:40:41 debug CyrusSasl::start(GSSAPI): selected GSSAPI response: '`\x82\x02L\x06 *\x86H\x86\xF7\x12\x01\x02\x02\x01\x00n\x82\x02;0\x82\x027\xA0\x03\x02\x01\x05\xA1\x03\x02\x01\x0E\xA2\x07\x03\x05\x00 \x00\x00\x00\xA3\x82\x016a\x82\x0120\FOREX-24H.COM\xA2&0$\xA0\x03\x02\x01\x03\xA1\x1D0\x1B\x1B\x05qpidd\x1B\x12main.forex-24h.com\xA3\x81\xED0\x81\xEA\xA0\x03\x02\x01\x12\xA1\x03\x02\x01\x04\xA2\x81\xDD\x04\x81\xDA\x92\xD12\xEE+\x92X\x94 > \x04\xDCd4\xBB\xEF\xB5\x04N=\x8D\x83P\x19\x18\xED\x1C\xBF|\xB5\xCA\xE7\x15w,p\xCC'\xE4\xFA\xE6\xDB\x0F\x00\xAA\xA0}-\xB5MFBV7\x1A\x07\xC4\xD7Z\xA3%7\x93 > !\xCA\x9A)U\xFC\x06C\x07\x7F\xA5\x19\xF9#_\x9A\xAA\x19e\xDA\xD0\xC2_W\xDAKf%\xC1.\x15\xAEQLu\x9F\xF0\x11\xEAn\xAEj\xEA\x16\xB6\xF3a\x82\x9C\x0E\x9DR\x8D\xD6C\x1B=\x1B\xDDP\x14\xEA\xB5\xD6$\x85R\xC8\xAF\xEB\xB3\xD8\xF3\xC5V$\x0Efa\xC0\x9A\xCCxS\x14\xF6Z<\xE9\xF5\xB0\xE0\xB3k\x11Z)\xDF\xB6\x05a\x9F9\xEEo\xFE\xA0\xB8da)(\xCD\xD6\xE0K\xF0V\xFD\xBF\x97%\xAF\xD5T\x19\xC3\xB0b\xA8\xF58\x95Q0\x87 > \xFB+\xF1}d\xEFHC\x844\x81\xA9a\x08\x8C1\xCD\x8D\xA4\x81\xE70\x81\xE4\xA0\x03\x02\x01\x12\xA2\x81\xDC\x04\x81\xD9\xA1k\x029j\xF0_\x8FTX\xBDY\x12\x9B\xB4\xF6y\x8Cz_\xC0\xA0\x95\xD9~\xFC ZSQ^dbZ > cD-*\x93\xC8\x1Dt\xC3\xDBy\xAE\xC2\xC5O\xCA\xDC\xF1\x9D6a)WP\x9B)\x88\x94k\xAB|S > \x8F\x19M\xEA\x80\xD3e7\x85z\xE1\x1B\xB4{T\xEA\xD2|\xBB\xFA\x1FS\xE6\xBEe\x1C\xA1\xBE\xF0\xF3\xF4a!\xC4P\x7F\xE4\x93\xA57\x06\xAF\x1E\x90"-\xF1\xD7\xF4\x05\xEB\xED\xCE\x85v\xBEH>.\x12C\xCDF)p-!\xF7*\xD5\xA7"\xDB@\xF0\xF2\x97f\xEBR\x8C\x04\x89\xB5\xEE\xCB\xD2\xCA\xFE\xC0`\xD7\x13\x9B6K\xDC/\xA3\x1AEKEZ\xDES\xF8p\xAAf \xD3\x1E\xF4"\xDD>\x16\xA6C\x86\xB0<\xC6\xA8] > \xDDf\xB7\xA8\x00,]@\xDEoR' > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionStartOkBody: client-properties={qpid.client_pid:F4:int32(3857),qpid.client_ppid:F4:int32(3755),qpid.client_process:V2:12:str16(libvirt-qpid),qpid.session_flow:F4:int32(1)}; mechanism=GSSAPI; response=xxxxxx; locale=en_US; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionSecureBody: challenge=`\x81\x99\x06 *\x86H\x86\xF7\x12\x01\x02\x02\x02\x00o\x81\x890\x81\x86\xA0\x03\x02\x01\x05\xA1\x03\x02\x01\x0F\xA2z0x\xA0\x03\x02\x01\x12\xA2q\x04o*\x18\xB8n\x05*\x1CF=r\x8A\xEA\xF7+.]t\xB0\xA8\xBB{\xBC)\xF5\x98 > \x90\x97\x1A\xF7\xF9e\xA4\xD6\xE5-q\xB1\x843 > \x84Q8\x1E\xAC>(\xBB/\xD3\x8B:\xBB'_\xD9\xCE\xC3\xAE\xFF'\xF78\xF6D\xCA\xE5 at N\xDD\xCE\xF7\xBB\xD7/ \xE4D\xB3\x1C2\x9A\xA4\x81VfQY\xE0\xCD\x00M?;\x11\xC3h/\xC7T6U\xFD; }] > 2009-07-02 11:40:41 debug CyrusSasl::step(`\x81\x99\x06 *\x86H\x86\xF7\x12\x01\x02\x02\x02\x00o\x81\x890\x81\x86\xA0\x03\x02\x01\x05\xA1\x03\x02\x01\x0F\xA2z0x\xA0\x03\x02\x01\x12\xA2q\x04o*\x18\xB8n\x05*\x1CF=r\x8A\xEA\xF7+.]t\xB0\xA8\xBB{\xBC)\xF5\x98 > \x90\x97\x1A\xF7\xF9e\xA4\xD6\xE5-q\xB1\x843 > \x84Q8\x1E\xAC>(\xBB/\xD3\x8B:\xBB'_\xD9\xCE\xC3\xAE\xFF'\xF78\xF6D\xCA\xE5 at N\xDD\xCE\xF7\xBB\xD7/ \xE4D\xB3\x1C2\x9A\xA4\x81VfQY\xE0\xCD\x00M?;\x11\xC3h/\xC7T6U\xFD): > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionSecureOkBody: response=; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionSecureBody: challenge=\x05\x04\x05\xFF\x00 > \x00\x00\x00\x00\x00\x00:\xF4Q\x00\x07\x00\xFF\xFF\xF3\x99>\xA1f?\xECR > Oh\x18; }] > 2009-07-02 11:40:41 debug CyrusSasl::step(\x07\x00\xFF\xFF\x05\x04\x05\xFF\x00\x00\x00\x00\x00\x00\x00\x00:\xF4Q\x00\xF3\x99>\xA1f?\xECR > Oh\x18): \x05\x04\x04\xFF\x00 > \x00\x00\x00\x00\x00\x00\x10.p\xA8\x04\x00\xFF\xFF\xCBa(\xCB\x1B\x81\xE2x > \x9B-\x01 > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionSecureOkBody: response=\x05\x04\x04\xFF\x00 > \x00\x00\x00\x00\x00\x00\x10.p\xA8\x04\x00\xFF\xFF\xCBa(\xCB\x1B\x81\xE2x > \x9B-\x01; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionTuneBody: channel-max=32767; max-frame-size=65535; heartbeat-min=0; heartbeat-max=120; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionTuneOkBody: channel-max=32767; max-frame-size=65535; heartbeat=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionOpenBody: virtual-host=; capabilities=void{}; insist=1; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=0; {ConnectionOpenOkBody: known-hosts=str16{V2:49:str16(amqp:tcp:10.12.105.132:5672,tcp:174.36.86.83:5672)}; }] > 2009-07-02 11:40:41 info Installing security layer, SSF: 56 > 2009-07-02 11:40:41 debug Known-brokers for connection: amqp:tcp:10.12.105.132:5672,tcp:174.36.86.83:5672 > 2009-07-02 11:40:41 debug Activating security layer > 2009-07-02 11:40:41 debug SessionState::SessionState .: 0x7ffb400143c0 > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionAttachBody: name=amq.failover2d7c3832-3844-49e1-81fe-a8190e9bbfb9; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionAttachedBody: name=amq.failover2d7c3832-3844-49e1-81fe-a8190e9bbfb9; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionCommandPointBody: command-id=0; command-offset=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionRequestTimeoutBody: timeout=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionCommandPointBody: command-id=0; command-offset=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {ExchangeQueryBody: name=amq.failover; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionTimeoutBody: timeout=0; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {ExecutionResultBody: command-id=0; val\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionCompletedBody: commands={ [0,0] }; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionDetachBody: name=amq.failover2d7c3832-3844-49e1-81fe-a8190e9bbfb9; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=1; {SessionDetachedBody: name=amq.failover2d7c3832-3844-49e1-81fe-a8190e9bbfb9; code=0; }] > 2009-07-02 11:40:41 debug SessionState::SessionState .: 0x7ffb400143c0 > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionAttachBody: name=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionAttachedBody: name=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCommandPointBody: command-id=0; command-offset=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionRequestTimeoutBody: timeout=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCommandPointBody: command-id=0; command-offset=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {QueueDeclareBody: queue=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; alternate-exchange=; exclusive=1; auto-delete=1; arguments={}; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionTimeoutBody: timeout=0; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,0] }; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {ExchangeBindBody: queue=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; exchange=amq.direct; binding-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; arguments={}; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,1] }; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {MessageSubscribeBody: queue=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; destination=qmfagent; accept-mode=0; acquire-mode=0; resume-id=; resume-ttl=0; arguments={}; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {MessageSetFlowModeBody: destination=qmfagent; flow-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {MessageFlowBody: destination=qmfagent; unit=0; value=4294967295; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {MessageFlowBody: destination=qmfagent; unit=1; value=4294967295; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {ExecutionSyncBody: }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,6] }; }] > 2009-07-02 11:40:41 info Connection established with broker > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qpid.management; accept-mode=1; acquire-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (92 bytes); properties={{MessageProperties: content-length=50; reply-to={ReplyTo: exchange=amq.direct; routing-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }; }{DeliveryProperties: routing-key=broker; }}] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (50 bytes) AM3A\x00\x00\x00\x00\x11RemoteA...] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,7] }; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionFlushBody: completed=1; }] > 2009-07-02 11:40:41 trace SENT AttachRequest: reqBroker=0 reqAgent=0 > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qmfagent; accept-mode=0; acquire-mode=0; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (16 bytes); properties={{MessageProperties: content-length=16; }}] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (16 bytes) AM3a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x14...] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ }; timely-reply=1; }] > 2009-07-02 11:40:41 trace RCVD AttachResponse: broker=1 agent=20 > 2009-07-02 11:40:41 notice Initial object-id bank assigned: 1.20 > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {ExchangeBindBody: queue=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; exchange=qpid.management; binding-key=agent.1.20; arguments={}; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionKnownCompletedBody: commands={ }; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,8] }; }] > 2009-07-02 11:40:41 trace SENT PackageInd: package=com.redhat.libvirt > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qpid.management; accept-mode=1; acquire-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (92 bytes); properties={{MessageProperties: content-length=27; reply-to={ReplyTo: exchange=amq.direct; routing-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }; }{DeliveryProperties: routing-key=broker; }}] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (27 bytes) AM3p\x00\x00\x00\x00\x12com.red...] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,9] }; }] > 2009-07-02 11:40:41 trace SENT ClassInd: package=com.redhat.libvirt class=domain > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qpid.management; accept-mode=1; acquire-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (92 bytes); properties={{MessageProperties: content-length=51; reply-to={ReplyTo: exchange=amq.direct; routing-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }; }{DeliveryProperties: routing-key=broker; }}] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (51 bytes) AM3q\x00\x00\x00\x00\x01\x12com.re...] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,10] }; }] > 2009-07-02 11:40:41 trace SENT ClassInd: package=com.redhat.libvirt class=node > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qmfagent; accept-mode=0; acquire-mode=0; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (16 bytes); properties={{MessageProperties: content-length=50; }}] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (50 bytes) AM3S\x00\x00\x00%\x12com.red...] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qpid.management; accept-mode=1; acquire-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (92 bytes); properties={{MessageProperties: content-length=49; reply-to={ReplyTo: exchange=amq.direct; routing-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }; }{DeliveryProperties: routing-key=broker; }}] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (49 bytes) AM3q\x00\x00\x00\x00\x01\x12com.re...] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,11] }; }] > 2009-07-02 11:40:41 trace SENT ClassInd: package=com.redhat.libvirt class=pool > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qmfagent; accept-mode=0; acquire-mode=0; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (16 bytes); properties={{MessageProperties: content-length=48; }}] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (48 bytes) AM3S\x00\x00\x00&\x12com.red...] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qpid.management; accept-mode=1; acquire-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (92 bytes); properties={{MessageProperties: content-length=49; reply-to={ReplyTo: exchange=amq.direct; routing-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }; }{DeliveryProperties: routing-key=broker; }}] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (49 bytes) AM3q\x00\x00\x00\x00\x01\x12com.re...] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,12] }; }] > 2009-07-02 11:40:41 trace SENT ClassInd: package=com.redhat.libvirt class=volume > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qmfagent; accept-mode=0; acquire-mode=0; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (16 bytes); properties={{MessageProperties: content-length=48; }}] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (48 bytes) AM3S\x00\x00\x00'\x12com.red...] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qpid.management; accept-mode=1; acquire-mode=0; }] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[be; channel=2; header (92 bytes); properties={{MessageProperties: content-length=51; reply-to={ReplyTo: exchange=amq.direct; routing-key=qmfagent-dcb7c8c5-521d-4cf1-80f7-347ccb188982; }; }{DeliveryProperties: routing-key=broker; }}] > 2009-07-02 11:40:41 trace SENT [52606 main.forex-24h.com:5672]: Frame[Ebe; channel=2; content (51 bytes) AM3q\x00\x00\x00\x00\x01\x12com.re...] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[BEbe; channel=2; {SessionCompletedBody: commands={ [0,13] }; }] > 2009-07-02 11:40:41 trace RECV [52606 main.forex-24h.com:5672]: Frame[Bbe; channel=2; {MessageTransferBody: destination=qmfagent; accept-mode=0; acquire-mode=0; }] > 2009-07-02 11:40:41 trace RCVD SchemaRequest: package=com.redhat.libvirt class=domain > libvirt-qpid: symbol lookup error: libvirt-qpid: undefined symbol: _ZN4qpid7framing6Buffer9putBin128EPh > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From kozlov at spbcas.ru Thu Jul 2 13:09:49 2009 From: kozlov at spbcas.ru (Konstantin Kozlov) Date: Thu, 02 Jul 2009 17:09:49 +0400 Subject: [Ovirt-devel] How to install ovirt in working environment? Message-ID: <4A4CB19D.3070106@spbcas.ru> Hello, I have the following question. How to install ovirt in working environment? Details: I want to install ovirt server on a physical host with Fedora 10 (Intel, no VT). I installed rpms from ovirt repo as stated on website. I allready have ipa, cobbler, dns, dhcp, nfs, two networks and such set up and working. I want to preserve this setup. The host I am installing ovirt on is ipa client. Ipa server is on another host. I gave consistent answers on corresponding questions of ovirt-installer. The script was produced as follows (ips and hostnames changed): # Configurations script generated by ovirt-installer # at Thu Jul 02 16:32:09 +0400 2009# import 'ovirt' import 'firewall' firewall::setup{'setup': status => 'enabled' } firewall_rule{"ssh": destination_port => "22"} #DNS Configuration $guest_httpd_ipaddr = '195.168.1.7' $guest_ipaddr = '10.1.1.7' $admin_ipaddr = '10.1.1.7' $ovirt_host = 'station.example.ru' $ipa_host = 'station.example.ru' dns::remote{setup: guest_ipaddr=> $guest_ipaddr, admin_ipaddr=> $admin_ipaddr, guest_dev => 'eth1', admin_dev => 'eth1' } # DHCP Configuration # Cobbler configuration $cobbler_hostname = 'station.example.ru' $cobbler_user_name = 'cobbler' $cobbler_user_password = 'cobbler' # Postgres Configuration $db_username = 'ovirt' $db_password = 'ovirtadmin' # FreeIPA configuration $realm_name = 'example.ru' $freeipa_password = 'secret' $short_ldap_dn = 'dc=example,dc=ru' $ldap_dn = 'cn=ipaConfig,cn=etc,dc=example,dc=ru' include cobbler::remote include postgres::bundled include freeipa::bundled include ovirt::setup I ran it with ace -d -v install ovirt | tee ovirt-inst-log And stopped it when it was going to run Puppet::Type::Package::ProviderYum: Executing '/usr/bin/yum -d 0 -e 0 -y install ipa-server' that I don't want. As far as I can tell only firewall has been changed which is not that bad. So the question is how to change the output of installer to instruct the ace to install only ovirt and use ipa, cobbler etc from the current set up. Sorry for the long story, Best regards, Konstantin From pronix.service at gmail.com Thu Jul 2 13:19:26 2009 From: pronix.service at gmail.com (dima vasiletc) Date: Thu, 02 Jul 2009 17:19:26 +0400 Subject: [Ovirt-devel] How to install ovirt in working environment? In-Reply-To: <4A4CB19D.3070106@spbcas.ru> References: <4A4CB19D.3070106@spbcas.ru> Message-ID: <4A4CB3DE.5000105@gmail.com> Check "rpm -ql ovirt-server-installer" and run only required for you function. On 07/02/2009 05:09 PM, Konstantin Kozlov wrote: > Hello, > > I have the following question. > > How to install ovirt in working environment? > > Details: > > I want to install ovirt server on a physical host with Fedora 10 > (Intel, no VT). > > I installed rpms from ovirt repo as stated on website. > > I allready have ipa, cobbler, dns, dhcp, nfs, two networks and such > set up and working. I want to preserve this setup. The host I am > installing ovirt on is ipa client. Ipa server is on another host. > > I gave consistent answers on corresponding questions of ovirt-installer. > > The script was produced as follows (ips and hostnames changed): > > # Configurations script generated by ovirt-installer > # at Thu Jul 02 16:32:09 +0400 2009# > > import 'ovirt' > import 'firewall' > firewall::setup{'setup': > status => 'enabled' > } > > firewall_rule{"ssh": destination_port => "22"} > > #DNS Configuration > $guest_httpd_ipaddr = '195.168.1.7' > $guest_ipaddr = '10.1.1.7' > $admin_ipaddr = '10.1.1.7' > $ovirt_host = 'station.example.ru' > $ipa_host = 'station.example.ru' > > dns::remote{setup: > guest_ipaddr=> $guest_ipaddr, > admin_ipaddr=> $admin_ipaddr, > guest_dev => 'eth1', > admin_dev => 'eth1' > } > > # DHCP Configuration > > > # Cobbler configuration > $cobbler_hostname = 'station.example.ru' > $cobbler_user_name = 'cobbler' > $cobbler_user_password = 'cobbler' > > # Postgres Configuration > $db_username = 'ovirt' > $db_password = 'ovirtadmin' > > # FreeIPA configuration > $realm_name = 'example.ru' > $freeipa_password = 'secret' > $short_ldap_dn = 'dc=example,dc=ru' > $ldap_dn = 'cn=ipaConfig,cn=etc,dc=example,dc=ru' > > include cobbler::remote > include postgres::bundled > include freeipa::bundled > include ovirt::setup > > I ran it with > > ace -d -v install ovirt | tee ovirt-inst-log > > And stopped it when it was going to run > > Puppet::Type::Package::ProviderYum: Executing '/usr/bin/yum -d 0 -e 0 > -y install ipa-server' > that I don't want. As far as I can tell only firewall has been changed > which is not that bad. > > So the question is how to change the output of installer to instruct > the ace to install only ovirt and use ipa, cobbler etc from the > current set up. > > Sorry for the long story, > > Best regards, > > Konstantin > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > -- ? ?????????, ??????? From kozlov at spbcas.ru Thu Jul 2 13:24:54 2009 From: kozlov at spbcas.ru (Konstantin Kozlov) Date: Thu, 02 Jul 2009 17:24:54 +0400 Subject: [Ovirt-devel] How to install ovirt in working environment? In-Reply-To: <4A4CB3DE.5000105@gmail.com> References: <4A4CB19D.3070106@spbcas.ru> <4A4CB3DE.5000105@gmail.com> Message-ID: <4A4CB526.7040807@spbcas.ru> Hello, Can you give some more details? The output of that command is: /usr/sbin/ovirt-installer /usr/share/ace /usr/share/ace/appliances /usr/share/ace/appliances/ovirt-appliance /usr/share/ace/appliances/ovirt-appliance/ovirt-appliance.pp /usr/share/ace/modules /usr/share/ace/modules/ovirt /usr/share/ace/modules/ovirt/files /usr/share/ace/modules/ovirt/files/cobbler-import /usr/share/ace/modules/ovirt/files/collectd.conf /usr/share/ace/modules/ovirt/files/dns_entries.sh /usr/share/ace/modules/ovirt/files/modules.conf /usr/share/ace/modules/ovirt/files/ovirt-appliance-setup /usr/share/ace/modules/ovirt/files/ovirt-storage /usr/share/ace/modules/ovirt/files/qpidd.conf /usr/share/ace/modules/ovirt/files/sasl2_qpidd.conf /usr/share/ace/modules/ovirt/manifests /usr/share/ace/modules/ovirt/manifests/appliance.pp /usr/share/ace/modules/ovirt/manifests/cobbler.pp /usr/share/ace/modules/ovirt/manifests/dhcp.pp /usr/share/ace/modules/ovirt/manifests/dns.pp /usr/share/ace/modules/ovirt/manifests/freeipa.pp /usr/share/ace/modules/ovirt/manifests/init.pp /usr/share/ace/modules/ovirt/manifests/ovirt.pp /usr/share/ace/modules/ovirt/manifests/postgres.pp /usr/share/ace/modules/ovirt/manifests/tftp.pp /usr/share/ace/modules/ovirt/templates /usr/share/ace/modules/ovirt/templates/digest_line.erb /usr/share/ace/modules/ovirt/templates/ovirt-dhcp.conf.erb /usr/share/ace/modules/ovirt/templates/ovirt-dns.conf.erb /usr/share/ace/modules/ovirt/templates/ovirt-tftp.conf.erb How can I select/deselect these parts (and what)? dima vasiletc wrote: > Check "rpm -ql ovirt-server-installer" and run only required for you > function. > > > > On 07/02/2009 05:09 PM, Konstantin Kozlov wrote: >> Hello, >> >> I have the following question. >> >> How to install ovirt in working environment? >> >> Details: >> >> I want to install ovirt server on a physical host with Fedora 10 >> (Intel, no VT). >> >> I installed rpms from ovirt repo as stated on website. >> >> I allready have ipa, cobbler, dns, dhcp, nfs, two networks and such >> set up and working. I want to preserve this setup. The host I am >> installing ovirt on is ipa client. Ipa server is on another host. >> >> I gave consistent answers on corresponding questions of ovirt-installer. >> >> The script was produced as follows (ips and hostnames changed): >> >> # Configurations script generated by ovirt-installer >> # at Thu Jul 02 16:32:09 +0400 2009# >> >> import 'ovirt' >> import 'firewall' >> firewall::setup{'setup': >> status => 'enabled' >> } >> >> firewall_rule{"ssh": destination_port => "22"} >> >> #DNS Configuration >> $guest_httpd_ipaddr = '195.168.1.7' >> $guest_ipaddr = '10.1.1.7' >> $admin_ipaddr = '10.1.1.7' >> $ovirt_host = 'station.example.ru' >> $ipa_host = 'station.example.ru' >> >> dns::remote{setup: >> guest_ipaddr=> $guest_ipaddr, >> admin_ipaddr=> $admin_ipaddr, >> guest_dev => 'eth1', >> admin_dev => 'eth1' >> } >> >> # DHCP Configuration >> >> >> # Cobbler configuration >> $cobbler_hostname = 'station.example.ru' >> $cobbler_user_name = 'cobbler' >> $cobbler_user_password = 'cobbler' >> >> # Postgres Configuration >> $db_username = 'ovirt' >> $db_password = 'ovirtadmin' >> >> # FreeIPA configuration >> $realm_name = 'example.ru' >> $freeipa_password = 'secret' >> $short_ldap_dn = 'dc=example,dc=ru' >> $ldap_dn = 'cn=ipaConfig,cn=etc,dc=example,dc=ru' >> >> include cobbler::remote >> include postgres::bundled >> include freeipa::bundled >> include ovirt::setup >> >> I ran it with >> >> ace -d -v install ovirt | tee ovirt-inst-log >> >> And stopped it when it was going to run >> >> Puppet::Type::Package::ProviderYum: Executing '/usr/bin/yum -d 0 -e 0 >> -y install ipa-server' >> that I don't want. As far as I can tell only firewall has been changed >> which is not that bad. >> >> So the question is how to change the output of installer to instruct >> the ace to install only ovirt and use ipa, cobbler etc from the >> current set up. >> >> Sorry for the long story, >> >> Best regards, >> >> Konstantin >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> > > From pronix.service at gmail.com Thu Jul 2 13:32:24 2009 From: pronix.service at gmail.com (dima vasiletc) Date: Thu, 02 Jul 2009 17:32:24 +0400 Subject: [Ovirt-devel] How to install ovirt in working environment? In-Reply-To: <4A4CB526.7040807@spbcas.ru> References: <4A4CB19D.3070106@spbcas.ru> <4A4CB3DE.5000105@gmail.com> <4A4CB526.7040807@spbcas.ru> Message-ID: <4A4CB6E8.2000802@gmail.com> This files describe all steps for automatic installation. You need non standart installation. you run each step manualy. for example /usr/share/ace/modules/ovirt/manifests/freeipa.pp descripe how to prepare freeipa - you select only required steps : set_kdc_defaults replace_line_returns get_krb5_tkt ipa_modify_username_length ...and many other and skip for example dns_masq restart and freeipa installation On 07/02/2009 05:24 PM, Konstantin Kozlov wrote: > Hello, > > Can you give some more details? > > The output of that command is: > > /usr/sbin/ovirt-installer > /usr/share/ace > /usr/share/ace/appliances > /usr/share/ace/appliances/ovirt-appliance > /usr/share/ace/appliances/ovirt-appliance/ovirt-appliance.pp > /usr/share/ace/modules > /usr/share/ace/modules/ovirt > /usr/share/ace/modules/ovirt/files > /usr/share/ace/modules/ovirt/files/cobbler-import > /usr/share/ace/modules/ovirt/files/collectd.conf > /usr/share/ace/modules/ovirt/files/dns_entries.sh > /usr/share/ace/modules/ovirt/files/modules.conf > /usr/share/ace/modules/ovirt/files/ovirt-appliance-setup > /usr/share/ace/modules/ovirt/files/ovirt-storage > /usr/share/ace/modules/ovirt/files/qpidd.conf > /usr/share/ace/modules/ovirt/files/sasl2_qpidd.conf > /usr/share/ace/modules/ovirt/manifests > /usr/share/ace/modules/ovirt/manifests/appliance.pp > /usr/share/ace/modules/ovirt/manifests/cobbler.pp > /usr/share/ace/modules/ovirt/manifests/dhcp.pp > /usr/share/ace/modules/ovirt/manifests/dns.pp > /usr/share/ace/modules/ovirt/manifests/freeipa.pp > /usr/share/ace/modules/ovirt/manifests/init.pp > /usr/share/ace/modules/ovirt/manifests/ovirt.pp > /usr/share/ace/modules/ovirt/manifests/postgres.pp > /usr/share/ace/modules/ovirt/manifests/tftp.pp > /usr/share/ace/modules/ovirt/templates > /usr/share/ace/modules/ovirt/templates/digest_line.erb > /usr/share/ace/modules/ovirt/templates/ovirt-dhcp.conf.erb > /usr/share/ace/modules/ovirt/templates/ovirt-dns.conf.erb > /usr/share/ace/modules/ovirt/templates/ovirt-tftp.conf.erb > > How can I select/deselect these parts (and what)? > > dima vasiletc wrote: >> Check "rpm -ql ovirt-server-installer" and run only required for you >> function. >> >> >> >> On 07/02/2009 05:09 PM, Konstantin Kozlov wrote: >>> Hello, >>> >>> I have the following question. >>> >>> How to install ovirt in working environment? >>> >>> Details: >>> >>> I want to install ovirt server on a physical host with Fedora 10 >>> (Intel, no VT). >>> >>> I installed rpms from ovirt repo as stated on website. >>> >>> I allready have ipa, cobbler, dns, dhcp, nfs, two networks and such >>> set up and working. I want to preserve this setup. The host I am >>> installing ovirt on is ipa client. Ipa server is on another host. >>> >>> I gave consistent answers on corresponding questions of >>> ovirt-installer. >>> >>> The script was produced as follows (ips and hostnames changed): >>> >>> # Configurations script generated by ovirt-installer >>> # at Thu Jul 02 16:32:09 +0400 2009# >>> >>> import 'ovirt' >>> import 'firewall' >>> firewall::setup{'setup': >>> status => 'enabled' >>> } >>> >>> firewall_rule{"ssh": destination_port => "22"} >>> >>> #DNS Configuration >>> $guest_httpd_ipaddr = '195.168.1.7' >>> $guest_ipaddr = '10.1.1.7' >>> $admin_ipaddr = '10.1.1.7' >>> $ovirt_host = 'station.example.ru' >>> $ipa_host = 'station.example.ru' >>> >>> dns::remote{setup: >>> guest_ipaddr=> $guest_ipaddr, >>> admin_ipaddr=> $admin_ipaddr, >>> guest_dev => 'eth1', >>> admin_dev => 'eth1' >>> } >>> >>> # DHCP Configuration >>> >>> >>> # Cobbler configuration >>> $cobbler_hostname = 'station.example.ru' >>> $cobbler_user_name = 'cobbler' >>> $cobbler_user_password = 'cobbler' >>> >>> # Postgres Configuration >>> $db_username = 'ovirt' >>> $db_password = 'ovirtadmin' >>> >>> # FreeIPA configuration >>> $realm_name = 'example.ru' >>> $freeipa_password = 'secret' >>> $short_ldap_dn = 'dc=example,dc=ru' >>> $ldap_dn = 'cn=ipaConfig,cn=etc,dc=example,dc=ru' >>> >>> include cobbler::remote >>> include postgres::bundled >>> include freeipa::bundled >>> include ovirt::setup >>> >>> I ran it with >>> >>> ace -d -v install ovirt | tee ovirt-inst-log >>> >>> And stopped it when it was going to run >>> >>> Puppet::Type::Package::ProviderYum: Executing '/usr/bin/yum -d 0 -e >>> 0 -y install ipa-server' >>> that I don't want. As far as I can tell only firewall has been >>> changed which is not that bad. >>> >>> So the question is how to change the output of installer to instruct >>> the ace to install only ovirt and use ipa, cobbler etc from the >>> current set up. >>> >>> Sorry for the long story, >>> >>> Best regards, >>> >>> Konstantin >>> >>> _______________________________________________ >>> Ovirt-devel mailing list >>> Ovirt-devel at redhat.com >>> https://www.redhat.com/mailman/listinfo/ovirt-devel >>> >> >> > > -- ? ?????????, ??????? From jboggs at redhat.com Thu Jul 2 15:37:07 2009 From: jboggs at redhat.com (Joey Boggs) Date: Thu, 02 Jul 2009 11:37:07 -0400 Subject: [Ovirt-devel] How to install ovirt in working environment? In-Reply-To: <4A4CB19D.3070106@spbcas.ru> References: <4A4CB19D.3070106@spbcas.ru> Message-ID: <4A4CD423.5010904@redhat.com> If you are familiar enough with using git and adding in a few patches from the mailing list you can achieve using a remote ipa server, until it gets acks from the list or folks that have tested it, it won't be commited. If you want to use cobbler that's already setup, (on the question that asks if you have a cobbler setup already, answer yes and add your hostname/user/password) Here's the patch list that will allow you to use another ipa server but it does try to do so right now using ipa-client-install. I'm not entirely sure how ipa-client install will behave if it's ran twice so you may want to skip that step by running: touch /var/ace/single_execs/ipa_setup prior to running ace install ovirt [PATCH server] update ovirt-add-host to use ipa commands instead of kadmin.local [PATCH server] separate ipa common tasks freeipa::common and rename ipa_server_install to ipa_install [PATCH server] add server-side groundwork for remote freeipa server [PATCH server] update host-browser to use ipa commands rather than kadmin [PATCH server] last patch to implement remote freeipa On 07/02/2009 09:09 AM, Konstantin Kozlov wrote: > Hello, > > I have the following question. > > How to install ovirt in working environment? > > Details: > > I want to install ovirt server on a physical host with Fedora 10 > (Intel, no VT). > > I installed rpms from ovirt repo as stated on website. > > I allready have ipa, cobbler, dns, dhcp, nfs, two networks and such > set up and working. I want to preserve this setup. The host I am > installing ovirt on is ipa client. Ipa server is on another host. > > I gave consistent answers on corresponding questions of ovirt-installer. > > The script was produced as follows (ips and hostnames changed): > > # Configurations script generated by ovirt-installer > # at Thu Jul 02 16:32:09 +0400 2009# > > import 'ovirt' > import 'firewall' > firewall::setup{'setup': > status => 'enabled' > } > > firewall_rule{"ssh": destination_port => "22"} > > #DNS Configuration > $guest_httpd_ipaddr = '195.168.1.7' > $guest_ipaddr = '10.1.1.7' > $admin_ipaddr = '10.1.1.7' > $ovirt_host = 'station.example.ru' > $ipa_host = 'station.example.ru' > > dns::remote{setup: > guest_ipaddr=> $guest_ipaddr, > admin_ipaddr=> $admin_ipaddr, > guest_dev => 'eth1', > admin_dev => 'eth1' > } > > # DHCP Configuration > > > # Cobbler configuration > $cobbler_hostname = 'station.example.ru' > $cobbler_user_name = 'cobbler' > $cobbler_user_password = 'cobbler' > > # Postgres Configuration > $db_username = 'ovirt' > $db_password = 'ovirtadmin' > > # FreeIPA configuration > $realm_name = 'example.ru' > $freeipa_password = 'secret' > $short_ldap_dn = 'dc=example,dc=ru' > $ldap_dn = 'cn=ipaConfig,cn=etc,dc=example,dc=ru' > > include cobbler::remote > include postgres::bundled > include freeipa::bundled > include ovirt::setup > > I ran it with > > ace -d -v install ovirt | tee ovirt-inst-log > > And stopped it when it was going to run > > Puppet::Type::Package::ProviderYum: Executing '/usr/bin/yum -d 0 -e 0 > -y install ipa-server' > that I don't want. As far as I can tell only firewall has been changed > which is not that bad. > > So the question is how to change the output of installer to instruct > the ace to install only ovirt and use ipa, cobbler etc from the > current set up. > > Sorry for the long story, > > Best regards, > > Konstantin > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From pmyers at redhat.com Sun Jul 5 17:46:36 2009 From: pmyers at redhat.com (Perry Myers) Date: Sun, 5 Jul 2009 13:46:36 -0400 Subject: [Ovirt-devel] [PATCH node-image] Fix create-ovirt-iso-nodes to allow manual specification of network bridge to use Message-ID: <1246815996-5762-1-git-send-email-pmyers@redhat.com> Since create-ovirt-network script has been deprecated (was part of ovirt-appliance repo) we can't default to bridge on the host of ovirtbr0 for doing fake node testing. So default to network:default and allow manual override to something like bridge:breth0 Signed-off-by: Perry Myers --- create-ovirt-iso-nodes | 11 +++++++---- 1 files changed, 7 insertions(+), 4 deletions(-) diff --git a/create-ovirt-iso-nodes b/create-ovirt-iso-nodes index d74dc4d..fe2e7ab 100755 --- a/create-ovirt-iso-nodes +++ b/create-ovirt-iso-nodes @@ -24,7 +24,7 @@ warn() { printf '%s: %s\n' "$ME" "$*" >&2; } try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } die() { warn "$@"; try_h; exit 1; } -BRIDGENAME=ovirtbr0 +NET_DEFAULT=network:default IMGDIR_DEFAULT=/var/lib/libvirt/images imgdir=$IMGDIR_DEFAULT NODEIMG_DEFAULT=/usr/share/ovirt-node-image/ovirt-node-image.iso @@ -66,7 +66,7 @@ gen_fake_managed_node() { # that just defines the VM w/o starting it. virt-install --name=node$num --ram=$ram --vcpus=$vcpus $disks \ --cdrom=$dest_nodeimg --livecd \ - --network=bridge:$BRIDGENAME --mac=00:16:3e:12:34:$last_mac \ + --network=$net --mac=00:16:3e:12:34:$last_mac \ --vnc --accelerate --hvm --noautoconsole \ --os-type=linux --os-variant=$os_variant \ --force --noreboot @@ -78,8 +78,9 @@ usage() { case $# in 1) warn "$1"; try_h; exit 1;; esac cat < References: <1246512242-5077-1-git-send-email-sseago@redhat.com> Message-ID: <1246887556.4312.1.camel@localhost.localdomain> I'll review this today, 1 minor comment below On Thu, 2009-07-02 at 05:24 +0000, Scott Seago wrote: > This revised version of the patch incorporates jguidditta's sorting fix as well as some other changes required to make this work fully on the cloud UI side. > --- Only one 'd' in my last name :) ^^ > src/app/controllers/pool_controller.rb | 2 +- > src/app/controllers/resources_controller.rb | 4 +++- > src/app/controllers/smart_pools_controller.rb | 5 ++--- > src/app/helpers/application_helper.rb | 14 ++++++++++++++ > src/app/models/vm.rb | 11 ++++++++++- > src/app/views/cloud/instance/_list.rhtml | 4 ++++ > src/app/views/vm/_grid.rhtml | 1 + > src/app/views/vm/show.rhtml | 4 +++- > src/test/unit/vm_test.rb | 6 ++++++ > 9 files changed, 44 insertions(+), 7 deletions(-) From apevec at redhat.com Mon Jul 6 16:17:35 2009 From: apevec at redhat.com (Alan Pevec) Date: Mon, 6 Jul 2009 18:17:35 +0200 Subject: [Ovirt-devel] [PATCH] gettext rubygem was split in Fedora 11 Message-ID: <1246897055-16763-1-git-send-email-apevec@redhat.com> Signed-off-by: Alan Pevec --- ovirt-server.spec.in | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 1569a9a..0ceb5b4 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -20,7 +20,11 @@ Requires: rubygem(rails) >= 2.1.1 Requires: rubygem(mongrel) >= 1.0.1 Requires: rubygem(krb5-auth) >= 0.6 Requires: rubygem(cobbler) >= 0.1.2 +%if 0%{?fedora} >= 11 +Requires: rubygem(gettext_rails) +%else Requires: rubygem(gettext) +%endif Requires: ruby-flexmock Requires: postgresql-server Requires: ruby-postgres -- 1.6.0.6 From sseago at redhat.com Mon Jul 6 17:16:42 2009 From: sseago at redhat.com (Scott Seago) Date: Mon, 6 Jul 2009 17:16:42 +0000 Subject: [Ovirt-devel] [PATCH server] UI for accumulated uptime for VMs. (revised2) Message-ID: <1246900602-16005-1-git-send-email-sseago@redhat.com> This revised version of the patch incorporates jguiditta's sorting fix as well as some other changes required to make this work fully on the cloud UI side. --- src/app/controllers/pool_controller.rb | 2 +- src/app/controllers/resources_controller.rb | 4 +++- src/app/controllers/smart_pools_controller.rb | 7 +++---- src/app/helpers/application_helper.rb | 14 ++++++++++++++ src/app/models/vm.rb | 11 ++++++++++- src/app/views/cloud/instance/_list.rhtml | 4 ++++ src/app/views/vm/_grid.rhtml | 1 + src/app/views/vm/show.rhtml | 4 +++- src/test/unit/vm_test.rb | 6 ++++++ 9 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/app/controllers/pool_controller.rb b/src/app/controllers/pool_controller.rb index 74a958c..44cb780 100644 --- a/src/app/controllers/pool_controller.rb +++ b/src/app/controllers/pool_controller.rb @@ -96,7 +96,7 @@ class PoolController < ApplicationController def vms_json(args) attr_list = [:id, :description, :uuid, :num_vcpus_allocated, :memory_allocated_in_mb, - :vnic_mac_addr, :state, :id] + :vnic_mac_addr, :state, :calc_uptime, :id] if (@pool.is_a? VmResourcePool) and @pool.get_hardware_pool.can_view(@user) attr_list.insert(3, [:host, :hostname]) end diff --git a/src/app/controllers/resources_controller.rb b/src/app/controllers/resources_controller.rb index 9d1074a..87f07f5 100644 --- a/src/app/controllers/resources_controller.rb +++ b/src/app/controllers/resources_controller.rb @@ -63,7 +63,9 @@ class ResourcesController < PoolController def vms_json svc_show(params[:id]) - super(:full_items => @pool.vms, :find_opts => {}, :include_pool => :true) + super(:full_items => @pool.vms, + :find_opts => {:select => Vm.calc_uptime}, + :include_pool => :true) end def delete diff --git a/src/app/controllers/smart_pools_controller.rb b/src/app/controllers/smart_pools_controller.rb index 8762ac0..1310808 100644 --- a/src/app/controllers/smart_pools_controller.rb +++ b/src/app/controllers/smart_pools_controller.rb @@ -66,7 +66,7 @@ class SmartPoolsController < PoolController end def vms_json - super(items_json_internal(Vm, :tagged_vms)) + super(items_json_internal(Vm, :tagged_vms, {:select => Vm.calc_uptime})) end def pools_json @@ -76,11 +76,10 @@ class SmartPoolsController < PoolController end - def items_json_internal(item_class, item_assoc) + def items_json_internal(item_class, item_assoc, find_opts = {}) if params[:id] svc_show(params[:id]) full_items = @pool.send(item_assoc) - find_opts = {} include_pool = false else # FIXME: no permissions or usage checks here yet @@ -93,7 +92,7 @@ class SmartPoolsController < PoolController else conditions = ["#{item_class.table_name}.id not in (?)", pool_items] end - find_opts = {:conditions => conditions} + find_opts[:conditions] = conditions include_pool = true end { :full_items => full_items, :find_opts => find_opts, :include_pool => include_pool} diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index 0178ad0..0c6562e 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -171,4 +171,18 @@ module ApplicationHelper def flash_path(source) compute_public_path(source, 'swfs', 'swf') end + + def number_to_duration(input_num) + input_int = input_num.to_i + hours_to_seconds = [input_int/3600 % 24, + input_int/60 % 60, + input_int % 60].map{|t| t.to_s.rjust(2,'0')}.join(':') + days = input_int / 86400 + day_str = "" + if days > 0 + day_label = (days > 1) ? "days" : "day" + day_str = "#{days} #{day_label} " + end + day_str + hours_to_seconds + end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index d4696cf..f0eaf94 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -420,14 +420,23 @@ class Vm < ActiveRecord::Base return i end + def self.calc_uptime + "*, case when state='running' then + (cast(total_uptime || ' sec' as interval) + + (now() - total_uptime_timestamp)) + else cast(total_uptime || ' sec' as interval) + end as calc_uptime" + end + # Make method for calling paginated vms easier for clients. # TODO: Might want to have an optional param for per_page var def self.paged_with_perms(user, priv, page, order) - Vm.paginate(:include => [{:vm_resource_pool => + Vm.paginate(:joins => [{:vm_resource_pool => {:permissions => {:role => :privileges}}}], :conditions => ["privileges.name=:priv and permissions.uid=:user", { :user => user, :priv => priv }], + :select => calc_uptime, :per_page => 5, :page => page, :order => order) diff --git a/src/app/views/cloud/instance/_list.rhtml b/src/app/views/cloud/instance/_list.rhtml index 1fa46de..95e5518 100644 --- a/src/app/views/cloud/instance/_list.rhtml +++ b/src/app/views/cloud/instance/_list.rhtml @@ -14,6 +14,9 @@ ">
<%= sort_link_helper "State", "state" %>
+ "> +
<%= sort_link_helper "Total Run Time", "calc_uptime" %>
+
IP Address
Load
@@ -25,6 +28,7 @@
<%= vm.description %>
<%= vm.provisioning %>
<%= vm.state.capitalize %>
+
<%= number_to_duration(vm.get_calculated_uptime) %>
N/A
N/A
diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml index b137de6..a110011 100644 --- a/src/app/views/vm/_grid.rhtml +++ b/src/app/views/vm/_grid.rhtml @@ -36,6 +36,7 @@ {display: 'Memory (MB)', name : 'memory_allocated', width : 60, sortable : true, align: 'right'}, {display: 'vNIC Mac Addr', name : 'vnic_mac_addr', width : 60, sortable : true, align: 'right'}, {display: 'State', name : 'state', width : 50, sortable : true, align: 'right'}, + {display: 'Total Run Time', name : 'calc_uptime', width : 50, align: 'right'}, {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } ], sortname: "description", diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index 0f70da8..ffe5055 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -111,6 +111,7 @@ Provisioning source:
State:
Pending State:
+ Total Run Time:
<%=h @vm.uuid %>
@@ -128,7 +129,8 @@ <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) <% end -%>
- <%=h @vm.get_pending_state %> + <%=h @vm.get_pending_state %>
+ <%=h number_to_duration(@vm.get_calculated_uptime) %>
<%= vm.state.capitalize %>
+
<%= number_to_duration(vm.get_calculated_uptime) %>
N/A
N/A
diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml index b137de6..a110011 100644 --- a/src/app/views/vm/_grid.rhtml +++ b/src/app/views/vm/_grid.rhtml @@ -36,6 +36,7 @@ {display: 'Memory (MB)', name : 'memory_allocated', width : 60, sortable : true, align: 'right'}, {display: 'vNIC Mac Addr', name : 'vnic_mac_addr', width : 60, sortable : true, align: 'right'}, {display: 'State', name : 'state', width : 50, sortable : true, align: 'right'}, + {display: 'Total Run Time', name : 'calc_uptime', width : 50, align: 'right'}, {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } ], sortname: "description", diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index 0f70da8..ffe5055 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -111,6 +111,7 @@ Provisioning source:
State:
Pending State:
+ Total Run Time:
<%=h @vm.uuid %>
@@ -128,7 +129,8 @@ <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) <% end -%>
- <%=h @vm.get_pending_state %> + <%=h @vm.get_pending_state %>
+ <%=h number_to_duration(@vm.get_calculated_uptime) %>
diff --git a/src/app/views/storage/_list.rhtml b/src/app/views/storage/_list.rhtml index ccb0dbe..32e905a 100644 --- a/src/app/views/storage/_list.rhtml +++ b/src/app/views/storage/_list.rhtml @@ -11,6 +11,8 @@ target <%elsif type == StoragePool::NFS -%> export path +<%elsif type == StoragePool::GLUSTERFS -%> + export path <% end -%> @@ -28,6 +30,8 @@ <%= storage_pool[:target] %> <%elsif type == StoragePool::NFS -%> <%= storage_pool[:export_path] %> +<%elsif type == StoragePool::GLUSTERFS -%> + <%= storage_pool[:export_path] %> <% end -%> <%- if defined?(remove_from_pool) && remove_from_pool -%> <%= link_to( 'detach', { :controller => "storage", :action => 'remove_from_pool', :id => storage_pool, :hardware_pool_id => hardware_pool }, :confirm => 'Are you sure?', :method => :post, :class => "remove") %> diff --git a/src/app/views/storage/_list_volumes.rhtml b/src/app/views/storage/_list_volumes.rhtml index ab4f623..b30e83b 100644 --- a/src/app/views/storage/_list_volumes.rhtml +++ b/src/app/views/storage/_list_volumes.rhtml @@ -12,6 +12,8 @@ LUN <%elsif type == StoragePool::NFS -%> export path +<%elsif type == StoragePool::GLUSTERFS -%> + export path <% end -%> size (gigs) @@ -30,6 +32,8 @@ <%= storage_volume.lun %> <%elsif type == StoragePool::NFS -%> <%= "#{storage_volume.storage_pool.export_path}/#{storage_volume.filename}" if storage_volume[:type] == "NfsStorageVolume" %> +<%elsif type == StoragePool::GLUSTERFS -%> + <%= "#{storage_volume.storage_pool.export_path}/#{storage_volume.filename}" if storage_volume[:type] == "GlusterfsStorageVolume" %> <% end -%> <%= storage_volume.size_in_gb %> diff --git a/src/app/views/storage/show.rhtml b/src/app/views/storage/show.rhtml index b0b1d4e..1366b13 100644 --- a/src/app/views/storage/show.rhtml +++ b/src/app/views/storage/show.rhtml @@ -29,6 +29,8 @@ Target:
<% elsif @storage_pool[:type] == "NfsStoragePool" %> Export path:
+ <% elsif @storage_pool[:type] == "GlusterfsStoragePool" %> + Export volume:
<% end %> Type:
State:
@@ -40,6 +42,8 @@ <%=h @storage_pool.target %>
<% elsif @storage_pool[:type] == "NfsStoragePool" %> <%=h @storage_pool.export_path %>
+ <% elsif @storage_pool[:type] == "GlusterfsStoragePool" %> + <%=h @storage_pool.export_path %>
<% end %> <%=h @storage_pool.get_type_label %>
<%=h @storage_pool.state %>
diff --git a/src/app/views/storage_volume/_new_volume_form.rhtml b/src/app/views/storage_volume/_new_volume_form.rhtml index ae65e18..d066ab2 100644 --- a/src/app/views/storage_volume/_new_volume_form.rhtml +++ b/src/app/views/storage_volume/_new_volume_form.rhtml @@ -19,6 +19,7 @@ <%= text_field_with_label "Filename:", 'storage_volume', 'filename' if @storage_volume.get_type_label==StoragePool::NFS %> +<%= text_field_with_label "Filename:", 'storage_volume', 'filename' if @storage_volume.get_type_label==StoragePool::GLUSTERFS %> diff --git a/src/app/views/storage_volume/show.rhtml b/src/app/views/storage_volume/show.rhtml index cefc51a..e1de593 100644 --- a/src/app/views/storage_volume/show.rhtml +++ b/src/app/views/storage_volume/show.rhtml @@ -27,6 +27,8 @@ Target:
<% elsif @storage_volume.storage_pool[:type] == "NfsStoragePool" %> Export path:
+ <% elsif @storage_volume.storage_pool[:type] == "GlusterfsStoragePool" %> + Export volume:
<% end %> Type:
State:
@@ -35,6 +37,8 @@ LUN:
<% elsif @storage_volume[:type] == "NfsStorageVolume" %> Filename:
+ <% elsif @storage_volume[:type] == "GlusterfsStorageVolume" %> + Filename:
<% elsif @storage_volume[:type] == "LvmStorageVolume" %> Volume Group:
Logical Volume:
@@ -51,6 +55,8 @@ <%=h @storage_volume.storage_pool[:target] %>
<% elsif @storage_volume.storage_pool[:type] == "NfsStoragePool" %> <%=h @storage_volume.storage_pool.export_path %>
+ <% elsif @storage_volume.storage_pool[:type] == "GlusterfsStoragePool" %> + <%=h @storage_volume.storage_pool.export_path %>
<% end %> <%=h @storage_volume.storage_pool.get_type_label %>
<%=h @storage_volume.state %>
@@ -59,6 +65,8 @@ <%=h @storage_volume.lun %>
<% elsif @storage_volume[:type] == "NfsStorageVolume" %> <%=h @storage_volume.filename %>
+ <% elsif @storage_volume[:type] == "GlusterfsStorageVolume" %> + <%=h @storage_volume.filename %>
<% elsif @storage_volume[:type] == "LvmStorageVolume" %> <%=h @storage_volume.storage_pool.vg_name %>
<%=h @storage_volume.lv_name %>
-- 1.6.0.6 From harsha at gluster.com Thu Jul 9 11:42:26 2009 From: harsha at gluster.com (Harshavardhana) Date: Thu, 9 Jul 2009 04:42:26 -0700 Subject: [Ovirt-devel] [PATCH 5/5] added new glusterfs MODELS into update and reindex search scripts Message-ID: <20090709114226.GA25771@dev.gluster.com> --- scripts/ovirt-reindex-search | 2 +- scripts/ovirt-update-search | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ovirt-reindex-search b/scripts/ovirt-reindex-search index cf4e38e..a896581 100755 --- a/scripts/ovirt-reindex-search +++ b/scripts/ovirt-reindex-search @@ -2,5 +2,5 @@ [ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails RAILS_ENV="${RAILS_ENV:-production}" RAKEFILE=/usr/share/ovirt-server/Rakefile -MODELS="Host Vm IscsiStoragePool NfsStoragePool HardwarePool VmResourcePool" +MODELS="Host Vm IscsiStoragePool NfsStoragePool GlusterfsStoragePool HardwarePool VmResourcePool" RAILS_ENV=$RAILS_ENV rake -f $RAKEFILE xapian:rebuild_index models="$MODELS" diff --git a/scripts/ovirt-update-search b/scripts/ovirt-update-search index 2b2c107..7aaecac 100755 --- a/scripts/ovirt-update-search +++ b/scripts/ovirt-update-search @@ -2,5 +2,5 @@ [ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails RAILS_ENV="${RAILS_ENV:-production}" RAKEFILE=/usr/share/ovirt-server/Rakefile -MODELS="Host Vm IscsiStoragePool NfsStoragePool HardwarePool VmResourcePool" +MODELS="Host Vm IscsiStoragePool NfsStoragePool GlusterfsStoragePool HardwarePool VmResourcePool" RAILS_ENV=$RAILS_ENV rake -f $RAKEFILE xapian:update_index models="$MODELS" -- 1.6.0.6 From harsha at gluster.com Thu Jul 9 11:42:58 2009 From: harsha at gluster.com (Harshavardhana) Date: Thu, 9 Jul 2009 04:42:58 -0700 Subject: [Ovirt-devel] [PATCH 4/5 ovirt-server] Added glusterfs for new search controller parameter Message-ID: <20090709114258.GA25781@dev.gluster.com> --- src/app/controllers/search_controller.rb | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/src/app/controllers/search_controller.rb b/src/app/controllers/search_controller.rb index 0fb6456..fb78418 100644 --- a/src/app/controllers/search_controller.rb +++ b/src/app/controllers/search_controller.rb @@ -37,17 +37,23 @@ class SearchController < ApplicationController "NfsStoragePool" => {:controller => "storage", :show_action => "show", :searched => true}, + "GlusterfsStoragePool" => {:controller => "storage" , + :show_action => "show", + :searched => true}, "IscsiStorageVolume" => {:controller => "storage_volume", :show_action => "show", :searched => false}, "NfsStorageVolume" => {:controller => "storage_volume", :show_action => "show", :searched => false}, + "GlusterfsStorageVolume" => {:controller => "storage_volume", + :show_action => "show", + :searched => false}, "LvmStorageVolume" => {:controller => "storage_volume", :show_action => "show", :searched => false}} - MULTI_TYPE_MODELS = {"StoragePool" => ["IscsiStoragePool", "NfsStoragePool"]} + MULTI_TYPE_MODELS = {"StoragePool" => ["IscsiStoragePool", "NfsStoragePool", "GlusterfsStoragePool"]} def single_result -- 1.6.0.6 From harsha at gluster.com Thu Jul 9 11:43:55 2009 From: harsha at gluster.com (Harshavardhana) Date: Thu, 9 Jul 2009 04:43:55 -0700 Subject: [Ovirt-devel] [PATCH ovirt-node] add glusterfs-client dependency for ovirt-node Message-ID: <20090709114355.GA25792@dev.gluster.com> --- ovirt-node.spec.in | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 746cf3d..2fdf4f5 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -21,7 +21,7 @@ Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig BuildRequires: libvirt-devel >= 0.5.1 BuildRequires: dbus-devel hal-devel -Requires: libvirt >= 0.5.1 +Requires: libvirt >= 0.6.3 Requires: augeas >= 0.3.5 Requires: libvirt-qpid >= 0.2.14-3 Requires: hal @@ -31,6 +31,7 @@ Requires: cyrus-sasl-gssapi cyrus-sasl cyrus-sasl-lib Requires: iscsi-initiator-utils Requires: ntp Requires: nfs-utils +Requires: glusterfs-client >= 2.0.2 Requires: krb5-workstation Requires: bash Requires: chkconfig -- 1.6.0.6 From harsha at gluster.com Thu Jul 9 11:44:28 2009 From: harsha at gluster.com (Harshavardhana) Date: Thu, 9 Jul 2009 04:44:28 -0700 Subject: [Ovirt-devel] [PATCH ovirt-node-image] removed fs/fuse from unused kernel modules list for glusterfs Message-ID: <20090709114428.GA25802@dev.gluster.com> --- common-blacklist.ks | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/common-blacklist.ks b/common-blacklist.ks index 8a132d4..ac13876 100644 --- a/common-blacklist.ks +++ b/common-blacklist.ks @@ -44,7 +44,7 @@ done # fs subdir, your mods entry would be "fs/nls" fs_mods="fs/nls fs/9p fs/affs fs/autofs fs/autofs4 fs/befs fs/bfs fs/cifs \ fs/coda fs/cramfs fs/dlm fs/ecryptfs fs/efs fs/exportfs fs/ext4 \ - fs/freevxfs fs/fuse fs/gfs2 fs/hfs fs/hfsplus fs/jbd2 fs/jffs \ + fs/freevxfs fs/gfs2 fs/hfs fs/hfsplus fs/jbd2 fs/jffs \ fs/jffs2 fs/jfs fs/minix fs/ncpfs fs/ocfs2 fs/qnx4 fs/reiserfs \ fs/romfs fs/sysv fs/udf fs/ufs fs/xfs" -- 1.6.0.6 From pmyers at redhat.com Thu Jul 9 12:32:39 2009 From: pmyers at redhat.com (Perry Myers) Date: Thu, 09 Jul 2009 08:32:39 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Users can view log files on the node. bz#506289 In-Reply-To: <4A55D260.6060301@redhat.com> References: <1245333361-24745-1-git-send-email-dpierce@redhat.com> <4A55D260.6060301@redhat.com> Message-ID: <4A55E367.8030106@redhat.com> On 07/09/2009 07:20 AM, Alan Pevec wrote: > Darryl L. Pierce wrote: >> Adds a new menu item which launches a separate script. That separate >> script presents a list of log files to the user. > > ack, just > >> +OPTIONS[${#OPTIONS[*]}]="/var/log/ovirt.log" > > add more log files to the menu, /var/log/messages at least... Might as well add /var/log/secure as well and if you want to be really tricksy dmesg output would also be useful (i.e. dmesg > /tmp/dmesg.log and then view dmesg.log). >> +OPTIONS[${#OPTIONS[*]}]="$RETURN_TO_MENU" > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel -- |=- Red Hat, Engineering, Boston, pmyers at redhat.com -=| |=- Office: +1 412 357 5233 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From dpierce at redhat.com Thu Jul 9 14:18:54 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 10:18:54 -0400 Subject: [Ovirt-devel] [PATCH node] Change DNS setup to match NTP. rhbz#508677 In-Reply-To: <1247149134-20542-1-git-send-email-dpierce@redhat.com> References: <1247149134-20542-1-git-send-email-dpierce@redhat.com> Message-ID: <1247149134-20542-2-git-send-email-dpierce@redhat.com> Rather than expecting the DNS entries to be on one line, they are now entered as two separate entries. Also fixes a bug that caused the first DNS entry to be lost when a second entry was provided. In both cases, the input is collected in a colon-delimited string that is then processed. This is to provide consistency between the interactive setup and the automated setup. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 53 +++++++++++++++++++++++++------------- scripts/ovirt-functions | 18 +++++++++++++ 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index aa04a7c..730381e 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -212,17 +212,30 @@ function configure_dns if [ -z "$AUTO" ]; then while true; do - printf "\n" - echo "Enter up to two DNS servers separated by commas:" - if [ -n "$OVIRT_DNS" ]; then - echo "Press Enter for defaults: ($OVIRT_DNS)" - fi - read -ep ": " - DNS=$REPLY - - if [ -z "$DNS" ]; then - DNS=$OVIRT_DNS - fi + for dns in first second; do + while true; do + printf "\n" + read -ep "Please enter the ${dns} DNS server (or ENTER to exit): " + if [[ -z "${REPLY}" ]]; then + if [[ -z "${DNS}" ]]; then + printf "\nAborted...\n" + return + else + break + fi + fi + if is_valid_ipv4 $REPLY; then + if [[ -z "${DNS}" ]]; then + DNS="${REPLY}" + elif [[ -n "${REPLY}" ]]; then + DNS="${DNS}:${REPLY}" + fi + break + else + printf "${REPLY} is an invalid address.\n" + fi + done + done printf "\n" ask_yes_or_no "Is this correct (y/n/a)?" true true @@ -235,12 +248,11 @@ function configure_dns fi if [ -n "$DNS" ]; then - DNS1=$(echo "$DNS" | awk -F, '{print $1}') - DNS2=$(echo "$DNS" | awk -F, '{print $2}') + DNS1=$(echo "$DNS" | awk -F\: '{print $1}') + DNS2=$(echo "$DNS" | awk -F\: '{print $2}') - test -n "$DNS1" && IF_CONFIG="set $IF_ROOT/DNS1 $DNS1" - test -n "$DNS2" && IF_CONFIG="set $IF_ROOT/DNS2 $DNS2" - printf "$IF_CONFIG\n" >> $IF_FILENAME + test -n "$DNS1" && printf "set $IF_ROOT/DNS1 $DNS1\n" >> $IF_FILENAME + test -n "$DNS2" && printf "set $IF_ROOT/DNS2 $DNS2\n" >> $IF_FILENAME fi } @@ -259,7 +271,11 @@ function configure_ntp if [ -z "$REPLY" ]; then break; fi - NTPSERVERS="$NTPSERVERS $REPLY" + if is_valid_ipv4 $REPLY; then + NTPSERVERS="${NTPSERVERS}:${REPLY}" + else + printf "${REPLY} is an invalid address.\n" + fi done fi } @@ -278,7 +294,8 @@ save\n" > $ntpconf if [ -n "$NTPSERVERS" ]; then offset=1 - for server in $NTPSERVERS; do + SERVERS=$(echo $NTPSERVERS | awk 'BEGIN{FS=":"}{for (i=1; i<=NF; i++) print $i}') + for server in $SERVERS; do printf "set /files/etc/ntp.conf/server[${offset}] ${server}\n" >> $ntpconf offset=$(echo "$offset+1" | bc) done diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index ecde762..81db36b 100755 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -654,6 +654,24 @@ ask_yes_or_no () { done } +# Verifies the address entered is a valid IPv4 address. +is_valid_ipv4 () { + local address=${1} + local result=1 + + if [[ "$address" =~ "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" ]]; then + OIFS=$IFS + IFS='.' + ip=($address) + IFS=$OIFS + [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ + && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] + result=$? + fi + + return $result +} + # execute a function if called as a script, e.g. # ovirt-functions ovirt_store_config /etc/hosts -- 1.6.2.5 From dpierce at redhat.com Thu Jul 9 14:18:53 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 10:18:53 -0400 Subject: [Ovirt-devel] Added new features to NTP/DNS fixes Message-ID: <1247149134-20542-1-git-send-email-dpierce@redhat.com> The code will now build strings that are comma-delimited to keep consistency between the interactive and automated setups. From dpierce at redhat.com Thu Jul 9 14:33:47 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 10:33:47 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Follow on patch for bz#507393. In-Reply-To: <4A55C81B.4060808@redhat.com> References: <1247060191-31532-1-git-send-email-dpierce@redhat.com> <1247060191-31532-2-git-send-email-dpierce@redhat.com> <4A55C81B.4060808@redhat.com> Message-ID: <20090709143347.GB3677@mcpierce-laptop.rdu.redhat.com> On Thu, Jul 09, 2009 at 12:36:11PM +0200, Alan Pevec wrote: > Darryl L. Pierce wrote: >> This patch adds a warning to the NTP changes. > > this looks good, just few things need to be amended: > >> function configure_dns > >> + echo "Enter up to two DNS servers separated by commas:" > > Isn't that changed to input one per line as for NTP? It is. > >> + read -ep "Is this correct (Y/N/A)? " > > should use ask_yes_or_no Fixed. > >> @@ -251,13 +263,23 @@ function configure_ntp > >> + if has_configured_interface true; then > >> + while true; do >> + read -ep "By continuing, you will remove any existing NTP settings. Continue (y/n)? " > drop this additional warning, top-level "Configuring the network will destroy any existing networking" is enough Okay. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Thu Jul 9 14:38:20 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 10:38:20 -0400 Subject: [Ovirt-devel] Fixed follow-up patch Message-ID: <1247150301-613-1-git-send-email-dpierce@redhat.com> Fixed with feedback from apevec. The only thing not included is the change to have NTP servers entered in separate lines, which is in a separate patch. From dpierce at redhat.com Thu Jul 9 14:38:21 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 10:38:21 -0400 Subject: [Ovirt-devel] [PATCH node] Follow on patch for bz#507393. In-Reply-To: <1247150301-613-1-git-send-email-dpierce@redhat.com> References: <1247150301-613-1-git-send-email-dpierce@redhat.com> Message-ID: <1247150301-613-2-git-send-email-dpierce@redhat.com> This patch fixes the warnings displayed to the user. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 84 ++++++++++++++++++++++---------------- 1 files changed, 49 insertions(+), 35 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 81f017c..2d0e2b6 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -28,6 +28,21 @@ if ! is_local_storage_configured; then exit 99 fi +# Checks that a network interface was already configured. +function has_configured_interface +{ + local show_message=${1-false} + + if [[ -n "${CONFIGURED_NIC}" ]]; then + return 0 + else + if $show_message; then + printf "\nYou must configure a network interface first.\n\n" + fi + return 1 + fi +} + function configure_interface { local NIC=$1 @@ -40,7 +55,7 @@ function configure_interface PREFIX=$OVIRT_IP_PREFIX fi - if [[ -n "${CONFIGURED_NIC}" ]]; then + if has_configured_interface; then printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" read -ep "Continue? (y/N) " case $REPLY in @@ -131,9 +146,9 @@ function configure_interface esac printf "\n" - read -ep "Is this correct (Y/N/A)? " - case $REPLY in - Y|y) + ask_yes_or_no "Is this correct (y/n/a)?" + case $? + 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" @@ -141,8 +156,8 @@ function configure_interface printf "$BR_CONFIG\n" > $BR_FILENAME break ;; - N|n) BR_CONFIG=$BR_CONFIG_BASE ;; - A|a) CONFIGURED_NIC=""; return;; + 1) BR_CONFIG=$BR_CONFIG_BASE ;; + 2) CONFIGURED_NIC=""; return;; esac done else @@ -191,11 +206,6 @@ function configure_interface function configure_dns { - if [[ -z "${CONFIGURED_NIC}" ]]; then - printf "\nYou must configure a network interface first.\n\n" - return - fi - local DNS=$1 local AUTO=$2 if [[ "$AUTO" == "AUTO" && @@ -208,27 +218,29 @@ function configure_dns local IF_CONFIG= if [ -z "$AUTO" ]; then - while true; do - printf "\n" - echo "Enter up to two DNS servers separated by commas:" - if [ -n "$OVIRT_DNS" ]; then - echo "Press Enter for defaults: ($OVIRT_DNS)" - fi - read -ep ": " - DNS=$REPLY + if has_configured_interface true; then + while true; do + printf "\n" + echo "Enter up to two DNS servers separated by commas:" + if [ -n "$OVIRT_DNS" ]; then + echo "Press Enter for defaults: ($OVIRT_DNS)" + fi + read -ep ": " + DNS=$REPLY - if [ -z "$DNS" ]; then - DNS=$OVIRT_DNS - fi + if [ -z "$DNS" ]; then + DNS=$OVIRT_DNS + fi - printf "\n" - read -ep "Is this correct (Y/N/A)? " - case $REPLY in - Y|y) break ;; - N|n) ;; - A|a) return ;; - esac - done + printf "\n" + read -ep "Is this correct (Y/N/A)? " + case $REPLY in + Y|y) break ;; + N|n) ;; + A|a) return ;; + esac + done + fi fi if [ -n "$DNS" ]; then @@ -251,13 +263,15 @@ function configure_ntp fi if [ -z "$AUTO" ]; then - while true; do - read -ep "Enter an NTP server (hit return when finished): " + if has_configured_interface true; then + while true; do + read -ep "Enter an NTP server (hit return when finished): " - if [ -z "$REPLY" ]; then break; fi + if [ -z "$REPLY" ]; then break; fi - NTPSERVERS="$NTPSERVERS $REPLY" - done + NTPSERVERS="$NTPSERVERS $REPLY" + done + fi fi } -- 1.6.2.5 From arroy at redhat.com Thu Jul 9 14:41:59 2009 From: arroy at redhat.com (Arjun Roy) Date: Thu, 09 Jul 2009 10:41:59 -0400 Subject: [Ovirt-devel] [PATCH: ovirt-identify-node replacement 0/1] Node Image Patch In-Reply-To: <2be7262f0907090044t6c8fc28cycc2f3208d38c7d21@mail.gmail.com> References: <1247078416-25107-1-git-send-email-arroy@redhat.com> <2be7262f0907090044t6c8fc28cycc2f3208d38c7d21@mail.gmail.com> Message-ID: <4A5601B7.10207@redhat.com> On 07/09/2009 03:44 AM, Alan Pevec wrote: > On Wed, Jul 8, 2009 at 8:40 PM, Arjun Roy > wrote: > > Dependencies added include: > -libboost_regex library > -libicu > > hmm, that's an unfortunate dependency: complete libicu adds 17MB and > blacklisting unused parts doesn't help much: > libboost_regex.so links with libicuuc.so (1.3M), libicui18n.so (1.6M) > and libicudata.so (14M) which makes 99% of libicu :( > > Could we avoid this by using more lightweight regexp library? > Ian Main mentioned pcre, (http://koji.fedoraproject.org/koji/buildinfo?buildID=89460) which looks like it doesn't ask for as much. So I could convert my package to use it. In the future, the data we are pulling by reading /proc/cpuinfo (and anything else needing regexes) would hopefully be exposed by libvirt anyways, which means no regex dependency at all. I'm looking forward to that. -Arjun -------------- next part -------------- An HTML attachment was scrubbed... URL: From dpierce at redhat.com Thu Jul 9 14:54:15 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 10:54:15 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Follow on patch for bz#507393. In-Reply-To: <20090709143347.GB3677@mcpierce-laptop.rdu.redhat.com> References: <1247060191-31532-1-git-send-email-dpierce@redhat.com> <1247060191-31532-2-git-send-email-dpierce@redhat.com> <4A55C81B.4060808@redhat.com> <20090709143347.GB3677@mcpierce-laptop.rdu.redhat.com> Message-ID: <20090709145415.GC3677@mcpierce-laptop.rdu.redhat.com> On Thu, Jul 09, 2009 at 10:33:47AM -0400, Darryl L. Pierce wrote: > >> + echo "Enter up to two DNS servers separated by commas:" > > > > Isn't that changed to input one per line as for NTP? > > It is. I wasn't clear. This is fixed in a separate patch. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Thu Jul 9 15:26:43 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 11:26:43 -0400 Subject: [Ovirt-devel] [PATCH node] Add VLAN support to network configuration. rhbz#510116 Message-ID: <1247153203-14461-1-git-send-email-dpierce@redhat.com> While configuring the management interface, the user can indicate whether the device will participate in a VLAN. If so, an additional property, VLAN yes, is added to the initscript for the interface. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 99 ++++++++++++++++++++++++++++++++------ 1 files changed, 83 insertions(+), 16 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index aa04a7c..d33f07f 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -44,6 +44,9 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + rm -rf /etc/sysconfig/network-scripts/ifcfg-* + cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo else printf "\nAborting...\n" return @@ -64,15 +67,18 @@ function configure_interface local BR_ROOT="$IFCONFIG_FILE_ROOT-$BRIDGE" local BR_CONFIG="rm $BR_ROOT\nset $BR_ROOT/DEVICE $BRIDGE" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/TYPE Bridge" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/PEERNTP yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DELAY 0" - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/BRIDGE $BRIDGE" - local BR_CONFIG_BASE=$BR_CONFIG if [ -z "$AUTO" ]; then while true; do + local VLAN_ID="" + local VL_ROOT="" + local VL_CONFIG="" + printf "\n" LINK=`ethtool $NIC| grep "Link detected"`:u [ -z "$LINK" ] && return @@ -87,47 +93,102 @@ function configure_interface ethtool --identify $NIC 10 fi + ask_yes_or_no "Include VLAN support (y/n/a)? " true true + case $? in + 0) + while true; do + read -ep "What is the VLAN ID for this device (a=abort) " + case $REPLY in + A|a) CONFIGURED_NIC=""; return;; + *) + if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then + VLAN_ID=$REPLY + VL_ROOT="${BR_ROOT}.${VLAN_ID}" + VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${BRIDGE}.${VLAN_ID}" + VL_FILENAME="${BR_FILENAME}.${VLAN_ID}" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/VLAN yes" + break + fi + ;; + esac + done + ;; + 2) CONFIGURED_NIC=""; return;; + esac + + # set the basic bridge configuration + if [[ -n "${VLAN_ID}" ]]; then + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}.${VLAN_ID}" + else + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" + fi + + if [[ -n "${VLAN_ID}" ]]; then + conf=$VL_CONFIG + root=$VL_ROOT + else + conf=$BR_CONFIG + root=$BR_ROOT + fi read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " case $REPLY in D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" + conf="$conf\nset $root/BOOTPROTO dhcp" ;; S|s) printf "\n" read -ep "IP Address: "; IPADDR=$REPLY read -ep " Netmask: "; NETMASK=$REPLY read -ep " Gateway: "; GATEWAY=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" + + conf="$conf\nset $root/BOOTPROTO none" + conf="$conf\nset $root/IPADDR $IPADDR" + conf="$conf\nset $root/NETMASK $NETMASK" if [ -n "${GATEWAY}" ]; then - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" + conf="$conf\nset $root/GATEWAY $GATEWAY" fi ;; A|a) CONFIGURED_NIC=""; return ;; esac + if [[ -n "${VLAN_ID}" ]]; then + VL_CONFIG=$conf + else + BR_CONFIG=$conf + fi + if [[ -n "$VLAN_ID" ]]; then + conf=$VL_CONFIG + root=$VL_ROOT + else + conf=$BR_CONFIG + root=$BR_ROOT + fi printf "\n" read -ep "Enable IPv6 support ([S]tatic, [D]HCPv6, A[u]to, [N]o or [A]bort)? " case $REPLY in S|s) read -ep "IPv6 Address: "; IPADDR=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" + conf="$conf\nset $root/IPV6INIT yes" + conf="$conf\nset $root/IP6ADDR $IPADDR" ;; D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" + conf="$conf\nset $root/IPV6INIT yes" + conf="$conf\nset $root/IPV6AUTCONF no" + conf="$conf\nset $root/IPV6FORWARDING no" + conf="$conf\nset $root/DHCPV6C yes" ;; U|u) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + conf="$conf\nset $root/IPV6INIT yes" + conf="$conf\nset $root/IPV6FORWARDING no" + conf="$conf\nset $root/IPV6AUTOCONF yes" ;; A|a) CONFIGURED_NIC=""; return;; esac + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG=$conf + else + BR_CONFIG=$conf + fi printf "\n" ask_yes_or_no "Is this correct (y/n/a)?" true true @@ -135,8 +196,14 @@ function configure_interface 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [[ -n "$VLAN_ID" ]]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi break ;; 1) -- 1.6.2.5 From dpierce at redhat.com Thu Jul 9 15:40:51 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Jul 2009 11:40:51 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Users can view log files on the node. bz#506289 In-Reply-To: <4A55D260.6060301@redhat.com> References: <1245333361-24745-1-git-send-email-dpierce@redhat.com> <4A55D260.6060301@redhat.com> Message-ID: <20090709154051.GD3677@mcpierce-laptop.rdu.redhat.com> On Thu, Jul 09, 2009 at 01:20:00PM +0200, Alan Pevec wrote: > Darryl L. Pierce wrote: >> Adds a new menu item which launches a separate script. That separate >> script presents a list of log files to the user. > > ack, just > >> +OPTIONS[${#OPTIONS[*]}]="/var/log/ovirt.log" > > add more log files to the menu, /var/log/messages at least... > >> +OPTIONS[${#OPTIONS[*]}]="$RETURN_TO_MENU" Pushed upstream with the additions mentioned here and by Perry. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From imain at redhat.com Thu Jul 9 16:50:22 2009 From: imain at redhat.com (Ian Main) Date: Thu, 9 Jul 2009 09:50:22 -0700 Subject: [Ovirt-devel] poweroff in vm causes the host to become unavailable In-Reply-To: <4A55637C.5070204@redfish-group.com> References: <4A553EC7.3070305@redfish-group.com> <20090708181843.1758d23d@tp.mains.net> <4A5554B8.7030905@redfish-group.com> <4A55574E.9000201@redhat.com> <4A55637C.5070204@redfish-group.com> Message-ID: <20090709095022.10a050f9@tp.mains.net> On Thu, 09 Jul 2009 13:26:52 +1000 Justin Clacherty wrote: > Scott Seago wrote: > > It's possible that this is related to the existing problem we've got > > with iscsi -- rescanning the target results in a _new_ lun listed in > > the db even though there's only one lun active. It seems that each > > rescan results in a new lun name 8.0.0.0, rescan shows 9.0.0.0, etc. > > I have seen some wierdness with the lun numbering so perhaps this is the > case. > > > I don't know if what you're seeing here is related to this problem or > > not -- I know that Ian is currently working to track down the rescan > > bug -- once there's a fix there it could help this issue too. > > Ok. If there's anything I can do to help let me know. Should I be > moving to next rather than the release? Well, for now I think just playing and reporting bugs like you are doing is fine. I wouldn't go to next right now, it has issues :). Hopefully it will be cleaned up some early next week or so. Ian From jboggs at redhat.com Thu Jul 9 20:17:59 2009 From: jboggs at redhat.com (Joey Boggs) Date: Thu, 09 Jul 2009 16:17:59 -0400 Subject: [Ovirt-devel] [PATCH node] Make all yes/no prompts consistent. rhbz#508778 In-Reply-To: <1246386766-4854-1-git-send-email-dpierce@redhat.com> References: <1246386766-4854-1-git-send-email-dpierce@redhat.com> Message-ID: <4A565077.4000505@redhat.com> On 06/30/2009 02:32 PM, Darryl L. Pierce wrote: > Added a new function, "ask_yes_or_no", to ovirt-functions. It contains a > default prompt if none is provided. > > Changed all prompts that ask for yes, no and/or abort to use this new > method. > > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-config-boot-wrapper | 8 ++--- > scripts/ovirt-config-collectd | 39 +++++++++++++++----------- > scripts/ovirt-config-logging | 45 ++++++++++++++++-------------- > scripts/ovirt-config-networking | 55 +++++++++++++++++++----------------- > scripts/ovirt-config-storage | 30 ++++++++------------ > scripts/ovirt-config-uninstall | 4 +-- > scripts/ovirt-functions | 18 ++++++++++++ > 7 files changed, 109 insertions(+), 90 deletions(-) > > diff --git a/scripts/ovirt-config-boot-wrapper b/scripts/ovirt-config-boot-wrapper > index ff6f6e5..4ce9969 100755 > --- a/scripts/ovirt-config-boot-wrapper > +++ b/scripts/ovirt-config-boot-wrapper > @@ -24,14 +24,12 @@ continuing." > else > bootparams="${OVIRT_BOOTPARAMS}" > fi > - read -p "Do you wish to continue (Y/n)? " > - r=$(echo $REPLY|tr '[[:lower:]]' '[[:upper:]]') > - if [ "$r" == "Y" ]; then > + if ask_yes_or_no; then > mount_live \ > -&& /usr/sbin/ovirt-config-boot /live "${bootparams}" > +&& /usr/sbin/ovirt-config-boot /live "${bootparams}" > rc=$? > break > - elif [ "$r" == "N" ]; then > + else > printf "\nExiting back to the menu\n" > rc=99 > break > diff --git a/scripts/ovirt-config-collectd b/scripts/ovirt-config-collectd > index 236ddaa..11811fd 100755 > --- a/scripts/ovirt-config-collectd > +++ b/scripts/ovirt-config-collectd > @@ -67,23 +67,28 @@ prompt_user() { > printf "\n" > printf "\n" > while true; do > - read -p "Is this correct (Y/N/A)? " > - r=$(echo $REPLY|tr '[[:lower:]]' '[[:upper:]]') > - if [ "$r" == "Y" ]; then > - printf "\nSaving configuration.\n" > - if [[ -n "$collectd_server_ip" ]]&& > - [[ -n "$collectd_server_port" ]]; then > - ovirt_collectd $collectd_server_ip \ > - $collectd_server_port > - fi > - return > - elif [ "$r" == "N" ]; then > - printf "\nRestarting collectd configuration.\n" > - break > - elif [ "$r" == "A" ]; then > - printf "\nAborting collectd configuration.\n" > - return > - fi > + ask_yes_or_no "Is this correct (y/n/a)?" > + rc=$? > + case $rc in > + 0) > + printf "\nSaving configuration.\n" > + if [[ -n "$collectd_server_ip" ]]&& > + [[ -n "$collectd_server_port" ]]; then > + ovirt_collectd $collectd_server_ip \ > + $collectd_server_port > + fi > + return > + ;; > + > + 1) > + printf "\nRestarting collectd configuration.\n" > + break > + ;; > + 2) > + printf "\nAborting collectd configuration.\n" > + return > + ;; > + esac > done > done > } > diff --git a/scripts/ovirt-config-logging b/scripts/ovirt-config-logging > index ba661c3..bb0f082 100755 > --- a/scripts/ovirt-config-logging > +++ b/scripts/ovirt-config-logging > @@ -146,27 +146,30 @@ function prompt_user { > printf "\n" > printf "\n" > while true; do > - read -p "Is this correct (Y/N/A)? " > - r=$(echo $REPLY|tr '[[:lower:]]' '[[:upper:]]') > - if [ "$r" == "Y" ]; then > - printf "\nSaving configuration.\n" > - if [[ -n "$syslog_server_ip" ]]&& > - [[ -n "$syslog_server_port" ]]&& > - [[ -n "$syslog_server_protocol" ]]; then > - ovirt_rsyslog $syslog_server_ip \ > - $syslog_server_port \ > - $syslog_server_protocol > - fi > - sed -c -i -e "s/^size=.*/size=${max_log_size}k/" \ > - /etc/logrotate.d/ovirt-logrotate.conf > - return > - elif [ "$r" == "N" ]; then > - printf "\nRestarting logging configuration.\n" > - break > - elif [ "$r" == "A" ]; then > - printf "\nAborting logging configuration.\n" > - return > - fi > + ask_yes_or_no "Is this correct (y/n/a)?" > + case $? in > + 0) > + printf "\nSaving configuration.\n" > + if [[ -n "$syslog_server_ip" ]]&& > + [[ -n "$syslog_server_port" ]]&& > + [[ -n "$syslog_server_protocol" ]]; then > + ovirt_rsyslog $syslog_server_ip \ > + $syslog_server_port \ > + $syslog_server_protocol > + fi > + sed -c -i -e "s/^size=.*/size=${max_log_size}k/" \ > + /etc/logrotate.d/ovirt-logrotate.conf > + return > + ;; > + 1) > + printf "\nRestarting logging configuration.\n" > + break > + ;; > + 2) > + printf "\nAborting logging configuration.\n" > + return > + ;; > + esac > done > done > } > diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking > index d29bd12..713cabc 100755 > --- a/scripts/ovirt-config-networking > +++ b/scripts/ovirt-config-networking > @@ -40,10 +40,12 @@ function configure_interface > > if [[ -n "${CONFIGURED_NIC}" ]]; then > printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" > - read -ep "Continue? (y/N) " > - case $REPLY in > - N|n) printf "\nAborting...\n"; return;; > - esac > + if ask_yes_or_no; then > + printf "\nDeleting existing network configuration...\n" > + else > + printf "\nAborting...\n" > + return > + fi > fi > > rm -rf $WORKDIR/* > @@ -79,12 +81,9 @@ function configure_interface > fi > echo "NIC is: $NICSTATUS" > > - read -ep "Help identify $NIC by blinking lights for 10 seconds ([Y]es/[N]o)?" > - case $REPLY in > - Y|y) > - ethtool --identify $NIC 10 > - ;; > - esac > + if ask_yes_or_no "Help identify ${NIC} by blinking lights for 10 seconds (y/n)?"; then > + ethtool --identify $NIC 10 > + fi > > read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " > case $REPLY in > @@ -129,18 +128,22 @@ function configure_interface > esac > > printf "\n" > - read -ep "Is this correct (Y/N/A)? " > - case $REPLY in > - Y|y) > - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" > - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" > - > - printf "$IF_CONFIG\n"> $IF_FILENAME > - printf "$BR_CONFIG\n"> $BR_FILENAME > - break > + ask_yes_or_no "Is this correct (y/n/a)?" > + case $? in > + 0) > + IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" > + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" > + printf "$IF_CONFIG\n"> $IF_FILENAME > + printf "$BR_CONFIG\n"> $BR_FILENAME > + break > + ;; > + 1) > + BR_CONFIG=$BR_CONFIG_BASE > + ;; > + 2) > + CONFIGURED_NIC="" > + return > ;; > - N|n) BR_CONFIG=$BR_CONFIG_BASE ;; > - A|a) CONFIGURED_NIC=""; return;; > esac > done > else > @@ -220,11 +223,11 @@ function configure_dns > fi > > printf "\n" > - read -ep "Is this correct (Y/N/A)? " > - case $REPLY in > - Y|y) break ;; > - N|n) ;; > - A|a) return ;; > + ask_yes_or_no "Is this correct (y/n/a)?" > + case $? in > + 0) break ;; > + 1) ;; > + 2) return ;; > esac > done > fi > diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage > index 41177a4..2e523df 100755 > --- a/scripts/ovirt-config-storage > +++ b/scripts/ovirt-config-storage > @@ -208,11 +208,9 @@ do_configure() > printf " partition should use up the remaining space on the disk.\n\n" > > do_review > - read -ep "Use these default values? (Y/n) " > - > - case $REPLY in > - Y|y) return;; > - esac > + if ask_yes_or_no "Use these default values (y/n)?"; then > + return > + fi > > local space_left=$SPACE > for part in boot swap root config logging data ; do > @@ -474,19 +472,15 @@ do_confirm() > "$wb$sp$w" \ > "$w8" \ > "$w8" > - printf "\n\tContinue? (Y/n) " > - read -e > - case $REPLY in > - Y|y) > - if check_partition_sizes; then > - perform_partitioning > - exit 0 > - fi > - break > - ;; > - N|n) return ;; > - *) ;; > - esac > + > + if ask_yes_or_no; then > + if check_partition_sizes; then > + perform_partitioning > + exit 0 > + fi > + else > + return > + fi > done > } > > diff --git a/scripts/ovirt-config-uninstall b/scripts/ovirt-config-uninstall > index 59661de..60ecf71 100755 > --- a/scripts/ovirt-config-uninstall > +++ b/scripts/ovirt-config-uninstall > @@ -27,9 +27,7 @@ cat< > EOF > > -read -ep "Do you wish to continue and uninstall this node (Y/N)? " > - > -if [ "$REPLY" == "Y" -o "$REPLY" == "y" ]; then > +if ask_yes_or_no "Do you wish to continue and uninstall this node (y/n)?" > if [ -d /dev/HostVG ]; then > log "Uninstalling node" > log "Detaching logging" > diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions > index e938256..09ddf50 100755 > --- a/scripts/ovirt-functions > +++ b/scripts/ovirt-functions > @@ -627,6 +627,24 @@ chkconfig_persist() { > ovirt_store_config $to_persist > } > > +# Asks a yes or no question. Accepts Y/N/A so users can abort. > +# RC=0 - Y/y entered > +# RC=1 - N/n entered > +# RC=2 - A/a entered > +ask_yes_or_no () { > + local prompt=${1-"Do you wish to proceed (y/n)?"} > + > + read -ep "${prompt} " > + if [[ "${REPLY}" = "Y" ]] || [[ "${REPLY}" = "y" ]]; then > + return 0 > + elif [[ "${REPLY}" = "N" ]] || [[ "${REPLY}" = "n" ]]; then > + return 1 > + elif [[ "${REPLY}" = "A" ]] || [[ "${REPLY}" = "a" ]]; then > + return 2 > + else > + return 99 > + fi > +} > > # execute a function if called as a script, e.g. > # ovirt-functions ovirt_store_config /etc/hosts > ack From apevec at redhat.com Thu Jul 9 21:08:48 2009 From: apevec at redhat.com (Alan Pevec) Date: Thu, 09 Jul 2009 23:08:48 +0200 Subject: [Ovirt-devel] Re: [PATCH node] Provides a means to toggle SSH password auth from the firstboot menu. rhbz#509842 In-Reply-To: <1246983193-31927-2-git-send-email-dpierce@redhat.com> References: <1246983193-31927-1-git-send-email-dpierce@redhat.com> <1246983193-31927-2-git-send-email-dpierce@redhat.com> Message-ID: <4A565C60.80605@redhat.com> Darryl L. Pierce wrote: > +toggle_ssh_access () > +{ > + local allowed=$1 > + local config=$WORKDIR/augeas-ssh $WORKDIR is not initialized, so this ends up in / but better to avoid temp file completely: > + > + if $allowed; then permit="yes"; else permit="no"; fi > + printf "set /files/etc/ssh/sshd_config/PasswordAuthentication ${permit}\n" > $config > + cat $config | augtool augtool < + > + service sshd restart reload should be enough > +} > + > +toggle_ssh () { > + local prompt=$1 > + > + printf "\nToggle SSH\n\n" > + > + while true; do > + read -ep "${prompt} (y/n)? " ask_yes_or_no instead? > + case $REPLY in > + Y|y) toggle_ssh_access true; return;; > + N|n) toggle_ssh_access false; return;; > + esac > + done > +} > + > +PASSWORD="Set administrator password" > +SSH="Enable SSH password authentication" should be "Toggle SSH" - otherwise need to make it dynamic and change the label > +QUIT="Quit and Return To Menu" > + > +while true; do > + state="disabled" > + prompt="Enable SSH access" > + grep "^PasswordAuthentication\ *yes" /etc/ssh/sshd_config > /dev/null leave parsing config files to augeas: + augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ yes, still need grep b/c shell programming sucks but parsing is out > + if [ $? == 0 ]; then > + state="enabled" > + prompt="Leave SSH access enabled" > + fi > + printf "\nSSH password authentication is currently ${state}.\n\n" > + > + PS3="Please select an option: " > + select option in "$PASSWORD" "$SSH" "$QUIT" > + do > + case $option in > + $PASSWORD) set_password; break;; > + $SSH) toggle_ssh "$prompt"; break;; > + $QUIT) exit;; > + esac > + done > + > + printf "\n" > +done From jboggs at redhat.com Thu Jul 9 21:14:13 2009 From: jboggs at redhat.com (Joey Boggs) Date: Thu, 09 Jul 2009 17:14:13 -0400 Subject: [Ovirt-devel] [PATCH node] Change DNS setup to match NTP. rhbz#508677 In-Reply-To: <1247149134-20542-2-git-send-email-dpierce@redhat.com> References: <1247149134-20542-1-git-send-email-dpierce@redhat.com> <1247149134-20542-2-git-send-email-dpierce@redhat.com> Message-ID: <4A565DA5.3080106@redhat.com> On 07/09/2009 10:18 AM, Darryl L. Pierce wrote: > Rather than expecting the DNS entries to be on one line, they are now > entered as two separate entries. > > Also fixes a bug that caused the first DNS entry to be lost when a > second entry was provided. > > In both cases, the input is collected in a colon-delimited string that > is then processed. This is to provide consistency between the > interactive setup and the automated setup. > > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-config-networking | 53 +++++++++++++++++++++++++------------- > scripts/ovirt-functions | 18 +++++++++++++ > 2 files changed, 53 insertions(+), 18 deletions(-) > > diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking > index aa04a7c..730381e 100755 > --- a/scripts/ovirt-config-networking > +++ b/scripts/ovirt-config-networking > @@ -212,17 +212,30 @@ function configure_dns > > if [ -z "$AUTO" ]; then > while true; do > - printf "\n" > - echo "Enter up to two DNS servers separated by commas:" > - if [ -n "$OVIRT_DNS" ]; then > - echo "Press Enter for defaults: ($OVIRT_DNS)" > - fi > - read -ep ": " > - DNS=$REPLY > - > - if [ -z "$DNS" ]; then > - DNS=$OVIRT_DNS > - fi > + for dns in first second; do > + while true; do > + printf "\n" > + read -ep "Please enter the ${dns} DNS server (or ENTER to exit): " > + if [[ -z "${REPLY}" ]]; then > + if [[ -z "${DNS}" ]]; then > + printf "\nAborted...\n" > + return > + else > + break > + fi > + fi > + if is_valid_ipv4 $REPLY; then > + if [[ -z "${DNS}" ]]; then > + DNS="${REPLY}" > + elif [[ -n "${REPLY}" ]]; then > + DNS="${DNS}:${REPLY}" > + fi > + break > + else > + printf "${REPLY} is an invalid address.\n" > + fi > + done > + done > > printf "\n" > ask_yes_or_no "Is this correct (y/n/a)?" true true > @@ -235,12 +248,11 @@ function configure_dns > fi > > if [ -n "$DNS" ]; then > - DNS1=$(echo "$DNS" | awk -F, '{print $1}') > - DNS2=$(echo "$DNS" | awk -F, '{print $2}') > + DNS1=$(echo "$DNS" | awk -F\: '{print $1}') > + DNS2=$(echo "$DNS" | awk -F\: '{print $2}') > > - test -n "$DNS1"&& IF_CONFIG="set $IF_ROOT/DNS1 $DNS1" > - test -n "$DNS2"&& IF_CONFIG="set $IF_ROOT/DNS2 $DNS2" > - printf "$IF_CONFIG\n">> $IF_FILENAME > + test -n "$DNS1"&& printf "set $IF_ROOT/DNS1 $DNS1\n">> $IF_FILENAME > + test -n "$DNS2"&& printf "set $IF_ROOT/DNS2 $DNS2\n">> $IF_FILENAME > fi > } > > @@ -259,7 +271,11 @@ function configure_ntp > > if [ -z "$REPLY" ]; then break; fi > > - NTPSERVERS="$NTPSERVERS $REPLY" > + if is_valid_ipv4 $REPLY; then > + NTPSERVERS="${NTPSERVERS}:${REPLY}" > + else > + printf "${REPLY} is an invalid address.\n" > + fi > done > fi > } > @@ -278,7 +294,8 @@ save\n"> $ntpconf > > if [ -n "$NTPSERVERS" ]; then > offset=1 > - for server in $NTPSERVERS; do > + SERVERS=$(echo $NTPSERVERS | awk 'BEGIN{FS=":"}{for (i=1; i<=NF; i++) print $i}') > + for server in $SERVERS; do > printf "set /files/etc/ntp.conf/server[${offset}] ${server}\n">> $ntpconf > offset=$(echo "$offset+1" | bc) > done > diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions > index ecde762..81db36b 100755 > --- a/scripts/ovirt-functions > +++ b/scripts/ovirt-functions > @@ -654,6 +654,24 @@ ask_yes_or_no () { > done > } > > +# Verifies the address entered is a valid IPv4 address. > +is_valid_ipv4 () { > + local address=${1} > + local result=1 > + > + if [[ "$address" =~ "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" ]]; then > + OIFS=$IFS > + IFS='.' > + ip=($address) > + IFS=$OIFS > + [[ ${ip[0]} -le 255&& ${ip[1]} -le 255 \ > +&& ${ip[2]} -le 255&& ${ip[3]} -le 255 ]] > + result=$? > + fi > + > + return $result > +} > + > # execute a function if called as a script, e.g. > # ovirt-functions ovirt_store_config /etc/hosts > > Can't get this one to apply against next, using: last change was (Allows viewing of log files. bz#506289) and did a fresh clone/checkout to make sure. [jboggs at localhost ovirt-node]$ git am ~/mbox-patch/\[Ovirt-devel\]\ \[PATCH\ node\]\ Change\ DNS\ setup\ to\ match\ NTP.\ rhbz#508677.eml Applying: Change DNS setup to match NTP. rhbz#508677 /home/jboggs/ovirt-node/.git/rebase-apply/patch:25: trailing whitespace. for dns in first second; do /home/jboggs/ovirt-node/.git/rebase-apply/patch:26: trailing whitespace. while true; do /home/jboggs/ovirt-node/.git/rebase-apply/patch:27: trailing whitespace. printf "\n" /home/jboggs/ovirt-node/.git/rebase-apply/patch:28: trailing whitespace. read -ep "Please enter the ${dns} DNS server (or ENTER to exit): " /home/jboggs/ovirt-node/.git/rebase-apply/patch:29: trailing whitespace. if [[ -z "${REPLY}" ]]; then error: patch failed: scripts/ovirt-config-networking:212 error: scripts/ovirt-config-networking: patch does not apply error: patch failed: scripts/ovirt-functions:654 error: scripts/ovirt-functions: patch does not apply Patch failed at 0001 Change DNS setup to match NTP. rhbz#508677 When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort". From mburns at redhat.com Thu Jul 9 21:22:06 2009 From: mburns at redhat.com (Mike Burns) Date: Thu, 9 Jul 2009 17:22:06 -0400 Subject: [Ovirt-devel] [PATCH node] Provides a means to toggle SSH password auth from the firstboot menu. rhbz#509842 In-Reply-To: <1246983193-31927-2-git-send-email-dpierce@redhat.com> References: <1246983193-31927-1-git-send-email-dpierce@redhat.com> <1246983193-31927-2-git-send-email-dpierce@redhat.com> Message-ID: <20090709212206.GA29667@mburns-laptop.bos.redhat.com> A couple small comments below, but otherwise ACK On Tue, Jul 07, 2009 at 12:13:13PM -0400, Darryl L. Pierce wrote: > The password option now goes to a submenu. This submenu lets the user > chose to either set the administrator password or else toggle SSH > password authentication on or off. > > The submenu also reports whether password authentication is current > enabled. > > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-config-password | 77 +++++++++++++++++++++++++++++++++++------ > 1 files changed, 66 insertions(+), 11 deletions(-) > > diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password > index 03b41e1..4c7d001 100755 > --- a/scripts/ovirt-config-password > +++ b/scripts/ovirt-config-password > @@ -37,14 +37,69 @@ function prompt_sasl_user { > done > } > > -printf "\n\n Password Configuration\n\n" > - > -# prompt user > -# Set the password for the root user first > -printf "\nSystem Administrator (root):\n" > -unmount_config /etc/shadow > -passwd root > -ovirt_store_config /etc/shadow > -printf "\nAdding users for libvirt remote access" > -# TODO list existing users in /etc/libvirt/passwd.db > -while prompt_sasl_user; do :; done > +set_password () { > + printf "\n\n Password Configuration\n\n" > + > + # prompt user > + # Set the password for the root user first > + printf "\nSystem Administrator (root):\n" > + unmount_config /etc/shadow > + passwd root > + ovirt_store_config /etc/shadow > + printf "\nAdding users for libvirt remote access" > + # TODO list existing users in /etc/libvirt/passwd.db > + while prompt_sasl_user; do :; done > +} > + > +toggle_ssh_access () > +{ > + local allowed=$1 > + local config=$WORKDIR/augeas-ssh > + > + if $allowed; then permit="yes"; else permit="no"; fi > + printf "set /files/etc/ssh/sshd_config/PasswordAuthentication ${permit}\n" > $config > + cat $config | augtool > + > + service sshd restart > +} > + > +toggle_ssh () { > + local prompt=$1 > + > + printf "\nToggle SSH\n\n" > + > + while true; do > + read -ep "${prompt} (y/n)? " > + case $REPLY in > + Y|y) toggle_ssh_access true; return;; > + N|n) toggle_ssh_access false; return;; > + esac > + done > +} Shouldn't we be using the ask_yes_or_no function here? > + > +PASSWORD="Set administrator password" > +SSH="Enable SSH password authentication" Should we be consistent and say Toggle SSH password auth since you can both enable or disable? > +QUIT="Quit and Return To Menu" > + > +while true; do > + state="disabled" > + prompt="Enable SSH access" > + grep "^PasswordAuthentication\ *yes" /etc/ssh/sshd_config > /dev/null > + if [ $? == 0 ]; then > + state="enabled" > + prompt="Leave SSH access enabled" > + fi > + printf "\nSSH password authentication is currently ${state}.\n\n" > + > + PS3="Please select an option: " > + select option in "$PASSWORD" "$SSH" "$QUIT" > + do > + case $option in > + $PASSWORD) set_password; break;; > + $SSH) toggle_ssh "$prompt"; break;; > + $QUIT) exit;; > + esac > + done > + > + printf "\n" > +done > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From mmorsi at redhat.com Thu Jul 9 21:56:23 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Jul 2009 17:56:23 -0400 Subject: [Ovirt-devel] permit many-to-many vms / networks relationship Message-ID: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> This patchset contains changes to the ovirt server frontend, backend, and tests components, permitting vms to be associated with multiple networks and vice versa. Also included are two patches which are required for the frontend bits; a patch adding collapsable sections to the vm form, which in itself depends on the second patch that provides default values for the cpu and memory vm table fields From mmorsi at redhat.com Thu Jul 9 21:56:24 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Jul 2009 17:56:24 -0400 Subject: [Ovirt-devel] [PATCH server] permit many-to-many vms / networks relationship (backend changes) In-Reply-To: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> Message-ID: <1247176588-4280-2-git-send-email-mmorsi@redhat.com> implements changes to the controllers, models, services, and taskomatic to permit vms to be associated with multiple networks. additional various fixes / cleanup to get things working --- src/app/controllers/pool_controller.rb | 2 +- src/app/controllers/vm_controller.rb | 54 ++++++++++++- src/app/models/network.rb | 2 - src/app/models/nic.rb | 54 +++++++++---- src/app/models/vlan.rb | 15 ++++- src/app/models/vm.rb | 17 +++-- src/app/services/network_service.rb | 12 ++-- src/app/services/nic_service.rb | 2 +- src/app/services/vm_service.rb | 39 +++++----- src/db/migrate/041_associate_vms_with_nics.rb | 103 +++++++++++++++++++++++++ src/task-omatic/task_vm.rb | 14 ++-- src/task-omatic/taskomatic.rb | 43 ++++++----- 12 files changed, 276 insertions(+), 81 deletions(-) create mode 100644 src/db/migrate/041_associate_vms_with_nics.rb diff --git a/src/app/controllers/pool_controller.rb b/src/app/controllers/pool_controller.rb index 74a958c..0d2e491 100644 --- a/src/app/controllers/pool_controller.rb +++ b/src/app/controllers/pool_controller.rb @@ -96,7 +96,7 @@ class PoolController < ApplicationController def vms_json(args) attr_list = [:id, :description, :uuid, :num_vcpus_allocated, :memory_allocated_in_mb, - :vnic_mac_addr, :state, :id] + :state, :id] if (@pool.is_a? VmResourcePool) and @pool.get_hardware_pool.can_view(@user) attr_list.insert(3, [:host, :hostname]) end diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index 197241d..2c0079c 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -55,29 +55,31 @@ class VmController < ApplicationController def new alert = svc_new(params[:vm_resource_pool_id]) _setup_provisioning_options + _setup_network_options @storage_tree = VmResourcePool.find(params[:vm_resource_pool_id]).get_hardware_pool.storage_tree.to_json - @networks = Network.find(:all).collect{ |net| [net.name, net.id] } render :layout => 'popup' end def create params[:vm][:forward_vnc] = params[:forward_vnc] - alert = svc_create(params[:vm], params[:start_now]) + _parse_network_params(params) + alert = svc_create(params[:vm], params[:start_now], params[:nics]) render :json => { :object => "vm", :success => true, :alert => alert } end def edit svc_modify(params[:id]) _setup_provisioning_options - @networks = Network.find(:all).collect{ |net| [net.name, net.id] } + _setup_network_options @storage_tree = @vm.vm_resource_pool.get_hardware_pool.storage_tree(:vm_to_include => @vm).to_json render :layout => 'popup' end def update params[:vm][:forward_vnc] = params[:forward_vnc] + _parse_network_params(params) alert = svc_update(params[:id], params[:vm], params[:start_now], - params[:restart_now]) + params[:restart_now], params[:nics]) render :json => { :object => "vm", :success => true, :alert => alert } end @@ -164,4 +166,48 @@ class VmController < ApplicationController #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end end + + # sets up a list of nics for the vm form + def _setup_network_options + net_conditions = "" + @nics = [] + + unless @vm.nil? + @vm.nics.each { |nic| + nnic = Nic.new(:mac => nic.mac, + :vm_id => @vm.id, + :network => nic.network) + if(nic.network.boot_type.proto == 'static') + nnic.ip_addresses << IpAddress.new(:address => nic.ip_address) + end + @nics.push nnic + + net_conditions += (net_conditions == "" ? "" : " AND ") + + "id != " + nic.network_id.to_s + } + end + + networks = Network.find(:all, :conditions => net_conditions) + + networks.each{ |net| + nnic = Nic.new(:mac => Nic::gen_mac, :network => net) + if(net.boot_type.proto == 'static') + nnic.ip_addresses << IpAddress.new(:address => '127.0.0.1') # FIXME + end + @nics.push nnic + } + + end + + # merges vm / network parameters as submitted on the vm form + def _parse_network_params(params) + params[:nics] = [] + unless params[:networks].nil? + params[:networks].each { |network, id| + params[:nics].push({ :mac => params[("nic_"+network.to_s).intern], + :network_id => network, + :bandwidth => 0}) + } + end + end end diff --git a/src/app/models/network.rb b/src/app/models/network.rb index 0e2aa8c..a4b1b8b 100644 --- a/src/app/models/network.rb +++ b/src/app/models/network.rb @@ -22,8 +22,6 @@ class Network < ActiveRecord::Base has_and_belongs_to_many :usages, :join_table => 'networks_usages' - has_many :vms - validates_presence_of :type, :message => 'A type must be specified.' validates_presence_of :name, diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb index e8b7768..f03c525 100644 --- a/src/app/models/nic.rb +++ b/src/app/models/nic.rb @@ -19,7 +19,10 @@ class Nic < ActiveRecord::Base belongs_to :host - belongs_to :physical_network + belongs_to :vm + + belongs_to :network + has_many :ip_addresses, :dependent => :destroy # FIXME bondings_nics table should just be replaced with @@ -33,24 +36,31 @@ class Nic < ActiveRecord::Base validates_format_of :mac, :with => %r{^([0-9a-fA-F]{2}([:-]|$)){6}$} - validates_presence_of :host_id, - :message => 'A host must be specified.' + # nic must be assigned to network if associated w/ a vm + validates_presence_of :network_id, + :unless => Proc.new { |nic| nic.vm.nil? } + + # nic must be associated w/ a vm if assigned to a vlan + validates_presence_of :vm_id, + :message => 'A vm must be specified.', + :if => Proc.new { |nic| !nic.network.nil? && nic.network.class == Vlan } + + # a vm / host can't have more than one nic on a network + validates_uniqueness_of :network_id, + :scope => [:host_id, :vm_id], + :unless => Proc.new { |nic| nic.network.nil? } validates_numericality_of :bandwidth, :greater_than_or_equal_to => 0 - validates_uniqueness_of :physical_network_id, - :scope => :host_id, - :unless => Proc.new { |nic| nic.physical_network_id.nil? } - # Returns whether the nic has networking defined. def networking? - (physical_network != nil) + (network != nil) end # Returns the boot protocol for the nic if networking is defined. def boot_protocol - return physical_network.boot_type.proto if networking? + return network.boot_type.proto if networking? end # Returns whether the nic is enslaved by a bonded interface. @@ -66,30 +76,44 @@ class Nic < ActiveRecord::Base # Returns the netmask for the nic if networking is defined. def netmask - return physical_network.ip_addresses.first.netmask if networking? && !physical_network.ip_addresses.empty? + return network.ip_addresses.first.netmask if networking? && !network.ip_addresses.empty? return nil end # Returns the broadcast address for the nic if networking is defined. def broadcast - return physical_network.ip_addresses.first.broadcast if networking? && !physical_network.ip_addresses.empty? + return network.ip_addresses.first.broadcast if networking? && !network.ip_addresses.empty? return nil end # Returns the gateway address fo rthe nic if networking is defined. def gateway - return physical_network.ip_addresses.first.gateway if networking? && !physical_network.ip_addresses.empty? + return network.ip_addresses.first.gateway if networking? && !network.ip_addresses.empty? return nil end + def parent + return host if !host.nil? && vm.nil? + return vm if !vm.nil? && host.nil? + return nil + end + + def self.gen_mac + [ 0x00, 0x16, 0x3e, rand(0x7f), rand(0xff), + rand(0xff) ].collect {|x| "%02x" % x}.join(":") + end + # validate 'bridge' or 'usage_type' attribute ? protected def validate - if ! physical_network.nil? and - physical_network.boot_type.proto == 'static' and + # nic must be associated with a host or vm, but not both + errors.add("one host or one vm must be specified") unless host.nil? ^ vm.nil? + + if ! network.nil? and + network.boot_type.proto == 'static' and ip_addresses.size == 0 - errors.add("physical_network_id", + errors.add("network_id", "is static. Must create at least one static ip") end end diff --git a/src/app/models/vlan.rb b/src/app/models/vlan.rb index 2f6acba..cce897d 100644 --- a/src/app/models/vlan.rb +++ b/src/app/models/vlan.rb @@ -19,11 +19,24 @@ class Vlan < Network has_many :bondings + has_many :nics + validates_presence_of :number, :message => 'A number must be specified.' def is_destroyable? - bondings.empty? + bondings.empty? && nics.empty? end + protected + def validate + # ensure that any assigned nics only belong to vms, not hosts + nics.each{ |nic| + if nic.parent.class == Host + errors.add("nics", "must only be assigned to vms") + break + end + } + end + end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index d4696cf..b4035b4 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -29,7 +29,7 @@ class Vm < ActiveRecord::Base end has_and_belongs_to_many :storage_volumes - belongs_to :network + has_many :nics, :dependent => :destroy has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags @@ -53,8 +53,8 @@ class Vm < ActiveRecord::Base validates_presence_of :uuid, :description, :num_vcpus_allocated, :boot_device, :memory_allocated_in_mb, - :memory_allocated, :vnic_mac_addr, - :vm_resource_pool_id + :memory_allocated, :vm_resource_pool_id + validates_format_of :uuid, :with => %r([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}) @@ -96,7 +96,7 @@ class Vm < ActiveRecord::Base :greater_than_or_equal_to => 0, :unless => Proc.new{ |vm| vm.memory_allocated.nil? } - acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], + acts_as_xapian :texts => [ :uuid, :description, :state ], :terms => [ [ :search_users, 'U', "search_users" ] ], :eager_load => :smart_pools @@ -119,8 +119,8 @@ class Vm < ActiveRecord::Base NEEDS_RESTART_FIELDS = [:uuid, :num_vcpus_allocated, - :memory_allocated, - :vnic_mac_addr] + :memory_allocated] + STATE_PENDING = "pending" STATE_CREATING = "creating" @@ -420,6 +420,11 @@ class Vm < ActiveRecord::Base return i end + def self.gen_uuid + ["%02x"*4, "%02x"*2, "%02x"*2, "%02x"*2, "%02x"*6].join("-") % + Array.new(16) {|x| rand(0xff) } + end + # Make method for calling paginated vms easier for clients. # TODO: Might want to have an optional param for per_page var def self.paged_with_perms(user, priv, page, order) diff --git a/src/app/services/network_service.rb b/src/app/services/network_service.rb index 3e7c997..221472d 100644 --- a/src/app/services/network_service.rb +++ b/src/app/services/network_service.rb @@ -205,13 +205,13 @@ module NetworkService def svc_modify_nic(id) authorize @nic = Nic.find(id) - @network = @nic.physical_network + @network = @nic.network network_options # filter out networks already assigned to nics on host network_conditions = [] - @nic.host.nics.each { |nic| - unless nic.physical_network.nil? || nic.id == @nic.id - network_conditions.push(" id != " + nic.physical_network.id.to_s) + @nic.parent.nics.each { |nic| + unless nic.network.nil? || nic.id == @nic.id + network_conditions.push(" id != " + nic.network.id.to_s) end } network_conditions = network_conditions.join(" AND ") @@ -228,8 +228,8 @@ module NetworkService def svc_update_nic(id, nic_hash, ip_hash) authorize network_options - unless nic_hash[:physical_network_id].to_i == 0 - @network = Network.find(nic_hash[:physical_network_id]) + unless nic_hash[:network_id].to_i == 0 + @network = Network.find(nic_hash[:network_id]) if @network.boot_type.id == @static_boot_type.id if ip_hash[:id] == "New" svc_create_ip_address(ip_hash) diff --git a/src/app/services/nic_service.rb b/src/app/services/nic_service.rb index 12314a4..b8c5ef1 100644 --- a/src/app/services/nic_service.rb +++ b/src/app/services/nic_service.rb @@ -33,7 +33,7 @@ module NicService private def lookup(id, priv) @nic = Nic.find(id) - authorized!(priv, at nic.host.hardware_pool) + authorized!(priv, (@nic.parent.class == Host ? @nic.host.hardware_pool : @nic.vm.vm_resource_pool)) end end diff --git a/src/app/services/vm_service.rb b/src/app/services/vm_service.rb index 073b819..54b7787 100644 --- a/src/app/services/vm_service.rb +++ b/src/app/services/vm_service.rb @@ -82,8 +82,8 @@ module VmService def svc_new(vm_resource_pool_id) raise ActionError.new("VM Resource Pool is required.") unless vm_resource_pool_id - new_vm_hash = {:vm_resource_pool_id => vm_resource_pool_id} - default_mac_and_uuid(new_vm_hash) + new_vm_hash = {:vm_resource_pool_id => vm_resource_pool_id, + :uuid => Vm::gen_uuid} @vm = Vm.new(new_vm_hash) authorized!(Privilege::MODIFY, @vm.vm_resource_pool) end @@ -94,8 +94,8 @@ module VmService # [@vm] the newly saved Vm # === Required permissions # [Privilege::MODIFY] for the vm's VmResourcePool - def svc_create(vm_hash, start_now) - default_mac_and_uuid(vm_hash) + def svc_create(vm_hash, start_now, nics) + vm_hash[:uuid] = Vm::gen_uuid unless vm_hash[:uuid] vm_hash[:state] = Vm::STATE_PENDING @vm = Vm.new(vm_hash) authorized!(Privilege::MODIFY, at vm.vm_resource_pool) @@ -103,6 +103,10 @@ module VmService alert = "VM was successfully created." Vm.transaction do @vm.save! + nics.each{ |nic| + nic[:vm_id] = @vm.id + Nic.create(nic) + } vm_provision @task = VmTask.new({ :user => @user, :task_target => @vm, @@ -129,7 +133,7 @@ module VmService # [@vm] stores the Vm with +id+ # === Required permissions # [Privilege::MODIFY] for the Vm's VmResourcePool - def svc_update(id, vm_hash, start_now, restart_now) + def svc_update(id, vm_hash, start_now, restart_now, nics) lookup(id,Privilege::MODIFY) #needs restart if certain fields are changed @@ -153,6 +157,9 @@ module VmService alert = "VM was successfully updated." Vm.transaction do + @vm.nics.clear + nics.each{ |nic| @vm.nics.push Nic.new(nic) } + @vm.update_attributes!(vm_hash) vm_provision if start_now @@ -285,10 +292,15 @@ module VmService unless system system = Cobbler::System.new("name" => name, @vm.cobbler_type => @vm.cobbler_name) - system.interfaces = [Cobbler::NetworkInterface.new( - {'mac_address' => @vm.vnic_mac_addr})] + system.interfaces = [] + # do we need to do anything if vm.nics is empty ? + @vm.nics.each { |nic| + system.interfaces.push Cobbler::NetworkInterface.new( + {'mac_address' => nic.mac}) + } system.save end + # TODO update system if found end end @@ -305,17 +317,4 @@ module VmService @vm = Vm.find(id) authorized!(priv, at vm.vm_resource_pool) end - - def default_mac_and_uuid(vm_hash) - unless vm_hash[:uuid] - vm_hash[:uuid] = ["%02x"*4, "%02x"*2, "%02x"*2, - "%02x"*2, "%02x"*6].join("-") % - Array.new(16) {|x| rand(0xff) } - end - unless vm_hash[:vnic_mac_addr] - vm_hash[:vnic_mac_addr] = [ 0x00, 0x16, 0x3e, rand(0x7f), rand(0xff), - rand(0xff) ].collect {|x| "%02x" % x}.join(":") - end - end - end diff --git a/src/db/migrate/041_associate_vms_with_nics.rb b/src/db/migrate/041_associate_vms_with_nics.rb new file mode 100644 index 0000000..ce8ad07 --- /dev/null +++ b/src/db/migrate/041_associate_vms_with_nics.rb @@ -0,0 +1,103 @@ +# Copyright (C) 2009 Red Hat, Inc. +# Written by Mohammed Morsi +# +# 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. + +class AssociateVmsWithNics < ActiveRecord::Migration + + def self.up + # assocate nics w/ vms + add_column :nics, :vm_id, :integer + execute "alter table nics add constraint + fk_nics_vm_id + foreign key (vm_id) references + vms(id)" + execute "alter table nics alter column host_id DROP NOT NULL" + + # change physical_network_id column in nic table to network_id + execute 'alter table nics drop constraint fk_nic_networks' + execute 'alter table nics rename column physical_network_id to network_id' + execute 'alter table nics add constraint fk_nic_networks + foreign key (network_id) references networks(id)' + + + # create a nic for each vm / network + Vm.find(:all, :conditions => "network_id IS NOT NULL").each{ |vm| + nic = Nic.new(:mac => vm.vnic_mac_addr, + :network_id => vm.network_id, + :vm_id => vm.id, + :bandwidth => 0) + nic.vm = vm + vm.nics.push nic + + vm.save! + nic.save! + } + + # removes 1toM networks/vms relationship + # remove network_id column from vms table + # remove vnic_mac_addr column from vms table + execute 'alter table vms drop constraint fk_vm_network_id' + remove_column :vms, :network_id + remove_column :vms, :vnic_mac_addr + + # add to the db some validations + # host_id is set xor vm_id is set + execute 'alter table nics add constraint host_xor_vm + check (host_id IS NOT NULL and vm_id IS NULL or + vm_id IS NOT NULL and host_id IS NULL)' + # network_id is set if vm_id is + execute 'alter table nics add constraint vm_nic_has_network + check (vm_id IS NULL or network_id IS NOT NULL)' + # vm_id is set if network is vlan (TBD) +# + end + + def self.down + # drop constraints added to nics table + execute 'alter table nics drop constraint host_xor_vm' + execute 'alter table nics drop constraint vm_nic_has_network' + + # add network_id, vnic_mac_addr column back to vm table + add_column :vms, :network_id, :integer + add_column :vms, :vnic_mac_addr, :string + execute 'alter table vms add constraint fk_vm_network_id + foreign key (network_id) references networks(id)' + + # copy network id over + # NOTE since we're going from a MtoM relationship to a 1toM + # we're just gonna associate the last network found + # w/ the vm, so this operation is lossy + Nic.find(:all, :conditions => 'vm_id IS NOT NULL').each{ |nic| + vm = Vm.find(nic.vm_id) + vm.vnic_mac_addr = nic.mac + vm.network_id = nic.network_id + vm.save! + nic.destroy + } + + # unassociate nics / vms + remove_column :nics, :vm_id + execute "alter table nics alter column host_id SET NOT NULL" + + # change nic::network_id back to nic::physical_network_id + execute 'alter table nics drop constraint fk_nic_networks' + execute 'alter table nics rename column network_id to physical_network_id' + execute 'alter table nics add constraint fk_nic_networks + foreign key (physical_network_id) references networks(id)' + + end +end diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index a448d30..dd71747 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -35,7 +35,7 @@ end def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, - macAddr, bridge, diskDevices) + net_interfaces, diskDevices) doc = Document.new doc.add_element("domain", {"type" => "kvm"}) @@ -87,11 +87,13 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, which_device += 1 end - unless macAddr.nil? || bridge.nil? || macAddr == "" || bridge == "" - doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) - doc.root.elements["devices"].elements["interface"].add_element("mac", {"address" => macAddr}) - doc.root.elements["devices"].elements["interface"].add_element("source", {"bridge" => bridge}) - end + net_interfaces.each { |nic| + interface = Element.new("interface") + interface.add_attribute("type", "bridge") + interface.add_element("mac", {"address" => nic[:mac]}) + interface.add_element("source", {"bridge" => nic[:interface]}) + doc.root.elements["devices"] << interface + } doc.root.elements["devices"].add_element("input", {"type" => "mouse", "bus" => "ps2"}) doc.root.elements["devices"].add_element("graphics", {"type" => "vnc", "port" => "-1", "listen" => "0.0.0.0"}) diff --git a/src/task-omatic/taskomatic.rb b/src/task-omatic/taskomatic.rb index b3c0592..7eb8a6c 100755 --- a/src/task-omatic/taskomatic.rb +++ b/src/task-omatic/taskomatic.rb @@ -168,6 +168,8 @@ class TaskOmatic and node.memory >= db_vm.memory_allocated \ and not curr.is_disabled.nil? and curr.is_disabled == 0 \ and ((!vm or vm.active == 'false') or vm.node != node.object_id) + # FIXME ensure host is on all networks a vm's assigned to + # db_vm.nics.each { |nic| ignore if nic.network ! in host } possible_hosts.push(curr) end end @@ -360,31 +362,34 @@ class TaskOmatic @logger.debug("Connecting volumes: #{volumes}") storagedevs = connect_storage_pools(node, volumes) - # determine if vm has been assigned to physical or - # virtual network and assign nic / bonding accordingly - # FIXME instead of trying to find a nic or bonding here, given - # a specified host and network, we should try earlier on to find a host - # that has a nic / bonding on the specified network + # loop through each nic/network assigned to vm, + # finding necessary host devices to bridge - net_device = "breth0" # FIXME remove this default value at some point, tho net_device can't be nil - unless db_vm.network.nil? - if db_vm.network.class == PhysicalNetwork - device = Nic.find(:first, - :conditions => ["host_id = ? AND physical_network_id = ?", - db_host.id, db_vm.network_id ]) - net_device = "br" + device.interface_name unless device.nil? + net_interfaces = [] + db_vm.nics.each { |nic| + device = net_device = nil - else + if nic.network.class == PhysicalNetwork + device = Nic.find(:first, + :conditions => ["host_id = ? AND network_id = ?", + db_host.id, nic.network_id ]) + else device = Bonding.find(:first, - :conditions => ["host_id = ? AND vlan_id = ?", - db_host.id, db_vm.network_id]) - net_device = "br" + device.interface_name unless device.nil? - end - end + :conditions => ["host_id = ? AND vlan_id = ?", + db_host.id, nic.network_id ]) + end + + unless device.nil? + net_device = "br" + device.interface_name + else + net_device = "breth0" # FIXME remove this default at some point + end + net_interfaces.push({ :mac => nic.mac, :interface => net_device }) + } xml = create_vm_xml(db_vm.description, db_vm.uuid, db_vm.memory_allocated, db_vm.memory_used, db_vm.num_vcpus_allocated, db_vm.boot_device, - db_vm.vnic_mac_addr, net_device, storagedevs) + net_interfaces, storagedevs, @logger) @logger.debug("XML Domain definition: #{xml}") -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 9 21:56:25 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Jul 2009 17:56:25 -0400 Subject: [Ovirt-devel] [PATCH server] add collapsable sections to vm form In-Reply-To: <1247176588-4280-2-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> <1247176588-4280-2-git-send-email-mmorsi@redhat.com> Message-ID: <1247176588-4280-3-git-send-email-mmorsi@redhat.com> the vm form is getting cluttered, this patch simply add collapsable sections to the form, making the 'storage' and 'network' sections collapsed by default --- src/app/helpers/application_helper.rb | 9 +++++ src/app/views/vm/_form.rhtml | 55 +++++++++++++++++++++++++-------- src/public/javascripts/ovirt.js | 25 +++++++++++++++ src/public/stylesheets/components.css | 15 +++++++++ 4 files changed, 91 insertions(+), 13 deletions(-) diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index 0178ad0..d3eecd7 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -77,6 +77,15 @@ module ApplicationHelper } end + # same as check_box_tag_with_label but w/ the checkbox appearing first + def rcheck_box_tag_with_label(label, name, value = "1", checked = false) + %{ +
#{check_box_tag name, value, checked} +
+ } + end + + def radio_button_tag_with_label(label, name, value = "1", checked = false) %{
diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 034c3df..373452d 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -6,15 +6,23 @@ <%= hidden_field 'vm', 'vm_resource_pool_id' %> <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id if @hardware_pool %> +
+ <%= link_to "", "#", :id => "vm_general_section_link" %> +
+
<%= text_field_with_label "Name:", "vm", "description", {:style=>"width:250px;"} %> <%= text_field_with_label "UUID:", "vm", "uuid", {:style=>"width:250px;"} %> <%= select_with_label "Operating System:", 'vm', 'provisioning_and_boot_settings', @provisioning_options, :style=>"width:250px;" %> <% if controller.action_name == "edit" %>*Warning* Editing provision could overwrite vm<% end %> +
+
+
-
Resources
-
-
+
+ <%= link_to "", "#", :id => "vm_resources_section_link" %> +
+
<%= text_field_with_label "CPUs:", "vm", "num_vcpus_allocated", {:style=>"width:100px; margin-bottom:2px;"}, {:style=>"padding-right: 50px;"} %>
max to create: <%=create_resources[:cpus]%>
@@ -27,6 +35,13 @@
+
+
+ +
+ <%= link_to "", "#", :id => "vm_storage_section_link" %> +
+
Storage:
    @@ -35,10 +50,13 @@
    Total:
    -
    -
    +
    +
    -
    Network
    +
    + <%= link_to "", "#", :id => "vm_network_section_link" %> +
    +
    @@ -47,15 +65,14 @@
    <%= select_with_label "Network:", 'vm', 'network_id', @networks.insert(0, ""), :style=>"width:250px;" %>
    -
    -
    - <%= check_box_tag_with_label "Forward vm's vnc port locally", "forward_vnc", 1, @vm.forward_vnc %> -
    + <%= rcheck_box_tag_with_label "Forward vm's vnc port locally", "forward_vnc", 1, @vm.forward_vnc %> +
    +
    - <%= check_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> - <%= check_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> + <%= rcheck_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> + <%= rcheck_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> @@ -104,6 +121,18 @@ ${htmlList(pools, id)} refresh: VmCreator.returnToVmForm }); }); - + toggle_visability_on_click('#vm_general_config', '#vm_general_section_link', 'General'); + toggle_visability_on_click('#vm_resources_config', '#vm_resources_section_link', 'Resources'); + toggle_visability_on_click('#vm_storage_config', '#vm_storage_section_link', 'Storage'); + toggle_visability_on_click('#vm_network_config', '#vm_network_section_link', 'Network'); + + // initially show general / resources, hide storage / networks section + $(document).ready(function(){ + show_section_with_header('#vm_general_config', '#vm_general_section_link', 'General'); + show_section_with_header('#vm_resources_config', '#vm_resources_section_link', 'Resources'); + hide_section_with_header('#vm_storage_config', '#vm_storage_section_link', 'Storage'); + hide_section_with_header('#vm_network_config', '#vm_network_section_link', 'Network'); + }); + diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js index 67dc455..6055e53 100644 --- a/src/public/javascripts/ovirt.js +++ b/src/public/javascripts/ovirt.js @@ -394,3 +394,28 @@ function get_server_from_url() var end = window.location.href.indexOf('/', 8) - start; return window.location.href.substr(start, end); } + +// hides the specified section, altering the specified header div with updated title / arrow +function hide_section_with_header(section, header, title){ + content = '
    ' + title + '
    '; + $(header).html(content); + $(section).hide('slow'); +}; + +// show the specified section, altering the specified header div with updated title / arrow +function show_section_with_header(section, header, title){ + content = '
    ' + title + '
    '; + $(header).html(content); + $(section).show('slow'); +}; + +// wire up the header to invoke either the show or hide function on click +function toggle_visability_on_click(section, header, title){ + $(header).bind('click', function(){ + if($(section).is(':hidden')){ + show_section_with_header(section, header, title); + }else{ + hide_section_with_header(section, header, title); + } + }); +}; diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 41ad3d0..1409692 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -339,3 +339,18 @@ height: 11px; } +#vm_general_config, #vm_resources_config, #vm_storage_config, #vm_network_config{ + padding-left: 30px; +} + +#vm_general_section_link img, #vm_resources_section_link img, #vm_storage_section_link img, #vm_network_section_link img{ + float: left; + padding-top: 2px; +} + +#vm_general_section_link div, #vm_resources_section_link div, #vm_storage_section_link div, #vm_network_section_link div{ + padding-top: 3px; + padding-left: 20px; + padding-bottom: 3px; + width: 25%; +} -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 9 21:56:26 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Jul 2009 17:56:26 -0400 Subject: [Ovirt-devel] [PATCH server] provide default vm allocated cpu and memory values In-Reply-To: <1247176588-4280-3-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> <1247176588-4280-2-git-send-email-mmorsi@redhat.com> <1247176588-4280-3-git-send-email-mmorsi@redhat.com> Message-ID: <1247176588-4280-4-git-send-email-mmorsi@redhat.com> adds a migration to provides default values for the vm table columns num_vcpus_allocated and memory_allocated. These propogate up to the add vm form so the user doesn't have to specify them --- src/db/migrate/040_vm_cpu_and_memory_defaults.rb | 29 ++++++++++++++++++++++ 1 files changed, 29 insertions(+), 0 deletions(-) create mode 100644 src/db/migrate/040_vm_cpu_and_memory_defaults.rb diff --git a/src/db/migrate/040_vm_cpu_and_memory_defaults.rb b/src/db/migrate/040_vm_cpu_and_memory_defaults.rb new file mode 100644 index 0000000..f33bc52 --- /dev/null +++ b/src/db/migrate/040_vm_cpu_and_memory_defaults.rb @@ -0,0 +1,29 @@ +# Copyright (C) 2009 Red Hat, Inc. +# Written by Mohammed Morsi +# +# 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. + +class VmCpuAndMemoryDefaults < ActiveRecord::Migration + def self.up + change_column :vms, :num_vcpus_allocated, :integer, :default => 1 + change_column :vms, :memory_allocated, :integer, :default => 262144 #256MB + end + + def self.down + change_column :vms, :num_vcpus_allocated, :integer, :default => nil + change_column :vms, :memory_allocated, :integer, :default => nil + end +end -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 9 21:56:27 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Jul 2009 17:56:27 -0400 Subject: [Ovirt-devel] [PATCH server] permit many-to-many vms / networks relationship (frontend changes) In-Reply-To: <1247176588-4280-4-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> <1247176588-4280-2-git-send-email-mmorsi@redhat.com> <1247176588-4280-3-git-send-email-mmorsi@redhat.com> <1247176588-4280-4-git-send-email-mmorsi@redhat.com> Message-ID: <1247176588-4280-5-git-send-email-mmorsi@redhat.com> implements changes to the views, helpers, and css to permit vms to be associated with multiple networks. --- src/app/helpers/application_helper.rb | 4 +- src/app/views/host/show.rhtml | 2 +- src/app/views/network/_select.rhtml | 2 +- src/app/views/network/edit_nic.rhtml | 4 +- src/app/views/nic/show.rhtml | 2 + src/app/views/task/_user_list.rhtml | 2 +- src/app/views/vm/_form.rhtml | 217 +++++++++++++++++++++++++++++++-- src/app/views/vm/_grid.rhtml | 1 - src/app/views/vm/_list.rhtml | 2 - src/app/views/vm/show.rhtml | 6 +- src/public/stylesheets/components.css | 39 ++++++ 11 files changed, 261 insertions(+), 20 deletions(-) diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index d3eecd7..79d835c 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -78,9 +78,9 @@ module ApplicationHelper end # same as check_box_tag_with_label but w/ the checkbox appearing first - def rcheck_box_tag_with_label(label, name, value = "1", checked = false) + def rcheck_box_tag_with_label(label, name, value = "1", checked = false, opts={}) %{ -
    #{check_box_tag name, value, checked} +
    #{check_box_tag name, value, checked, opts}
    } end diff --git a/src/app/views/host/show.rhtml b/src/app/views/host/show.rhtml index f706761..ddc6481 100644 --- a/src/app/views/host/show.rhtml +++ b/src/app/views/host/show.rhtml @@ -64,7 +64,7 @@ <%=h @host.status_str %>
    <%= @host.nics.collect{ |n| n.interface_name.to_s + " " + n.mac + - (n.physical_network.nil? ? "" : " " + n.physical_network.name) + (n.network.nil? ? "" : " " + n.network.name) }.join("
    ") %>
    <%= @host.bondings.collect { |n| n.name }.join("
    ") %>
    diff --git a/src/app/views/network/_select.rhtml b/src/app/views/network/_select.rhtml index 4d056df..47ab319 100644 --- a/src/app/views/network/_select.rhtml +++ b/src/app/views/network/_select.rhtml @@ -1,5 +1,5 @@ <% target = 'nic' unless target - network_id = 'physical_network_id' if target == 'nic' + network_id = 'network_id' if target == 'nic' network_id = 'vlan_id' if target == 'bonding' %> diff --git a/src/app/views/network/edit_nic.rhtml b/src/app/views/network/edit_nic.rhtml index 1b58c20..7f0bc02 100644 --- a/src/app/views/network/edit_nic.rhtml +++ b/src/app/views/network/edit_nic.rhtml @@ -6,10 +6,12 @@ <%= error_messages_for 'nic' %> + <%# TODO doesn't currently break anything due to where this form is displayed + but @nic.host assumption should be removed so this template can be included elsewhere%>
    <%= hidden_field_tag 'id', @nic.id %> <%= hidden_field_tag 'nic_host_id', @nic.host.id %> - <%= hidden_field_tag 'nic_network_id', @nic.physical_network.id if @nic.physical_network %> + <%= hidden_field_tag 'nic_network_id', @nic.network.id if @nic.network %>
    MAC:
    <%= @nic.mac %>
    diff --git a/src/app/views/nic/show.rhtml b/src/app/views/nic/show.rhtml index 0bd3910..46fb2c6 100644 --- a/src/app/views/nic/show.rhtml +++ b/src/app/views/nic/show.rhtml @@ -7,6 +7,8 @@

    <% end %> + <%# TODO doesn't currently break anything due to where this form is displayed + but @nic.host assumption should be removed so thiw template can be included elsewhere%>

    Host: <%= link_to @nic.host.hostname, { :controller => "host", :action => "show", :id => @nic.host }, { :class => "show" } %>

    diff --git a/src/app/views/task/_user_list.rhtml b/src/app/views/task/_user_list.rhtml index e289932..1506359 100644 --- a/src/app/views/task/_user_list.rhtml +++ b/src/app/views/task/_user_list.rhtml @@ -7,7 +7,7 @@ <% for task in tasks %>
  • action: <%= link_to task.action, { :controller => "task", :action => 'show', :id => task }, { :class => "action" } -%>
    - vm: <%= link_to task.vm.description, { :controller => "vm", :action => 'show', :id => task }, { :class => "description", :title => "cpus: %s mem: %s vNIC: %s" % [ task.vm.num_vcpus_used, task.vm.memory_used, task.vm.vnic_mac_addr ] } -%>
    + vm: <%= link_to task.vm.description, { :controller => "vm", :action => 'show', :id => task }, { :class => "description", :title => "cpus: %s mem: %s" % [ task.vm.num_vcpus_used, task.vm.memory_used ] } -%>
    state: <%= task.state -%>
  • <% end %> diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 373452d..ead26e1 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -58,19 +58,36 @@
    -
    -
    - <%= text_field_with_label "VNIC:", "vm", "vnic_mac_addr", {:style=>"width:250;"} %> -
    -
    - <%= select_with_label "Network:", 'vm', 'network_id', @networks.insert(0, ""), :style=>"width:250px;" %> -
    -
    + <% if @nics.size > 0 %> +
    +
    + Network: +
    +
    + MAC Address: +
    +
    + <%# this column is only populated if a static ip network is selected: %> + IP Address: +
    - <%= rcheck_box_tag_with_label "Forward vm's vnc port locally", "forward_vnc", 1, @vm.forward_vnc %> +
    +
    + <%# populated with jquery below: %> +
    +
    +
    + Add Another Network +
    + <% else %> +  No networks available + <% end %> +
    +
    +
    <%= rcheck_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> <%= rcheck_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> @@ -135,4 +152,186 @@ ${htmlList(pools, id)} hide_section_with_header('#vm_network_config', '#vm_network_section_link', 'Network'); }); + /////////////////////////////////////////////////// vm networks config + + // number of rows which we are currently displaying in net config + var vm_network_config_rows = 0; + + // last row currently being displayed + var vm_network_config_last_row = 0; + + // value of current selectbox + var current_selectbox_value = 0; + + // create list of nics + var nics = new Array(); + <% @nics.each { |rnic| %> + jnic = new Object; + jnic.network_id = "<%= rnic.network_id.to_s %>"; + jnic.name = "<%= rnic.network.name %>"; + jnic.mac = "<%= rnic.mac %>"; + jnic.ip = "<%= rnic.ip_address %>"; + jnic.static_ip = <%= rnic.network.boot_type.proto == 'static' %>; + jnic.selected = false; + nics.push(jnic); + <% } %> + + // adds unselected network back to selectboxes indicated by selector + function add_unselected_network(selector, network_id){ + for(j = 0; j < nics.length; ++j){ + if(nics[j].network_id == network_id){ + nics[j].selected = false; + $(selector).append(''); + break; + } + } + } + + // show / hide ip address column + function toggle_ip_address_column(){ + for(i = 0; i < nics.length; ++i){ + if(nics[i].selected && nics[i].static_ip){ + $('#vm_network_config_header_ip').show(); + return; + } + } + $('#vm_network_config_header_ip').hide(); + } + + // show a new network config row + function add_network_config_row(no_remove_link){ + + // if the number of rows is equal to the number of + // networks, don't show any more rows + if(vm_network_config_rows == nics.length) + return; + + vm_network_config_rows += 1; + vm_network_config_last_row = vm_network_config_rows; + + // create the content for another row to be added to the vm_network_config_networks div above. + // currently a row has a network select box, a mac text field, and an ip address field if a static network is selected + var content = '
    '; + content += '
    '; + content += ' '; + content += '
    '; + content += '
    '; + content += ' '; + content += '
    '; + content += '
    '; + content += '  '; + content += '
    '; + + if(!no_remove_link){ + content += '
    '; + content += ' Remove'; + content += '
    '; + } + content += '
    '; + content += '
    '; + + $('#vm_network_config_networks').append(content); + + $('#vm_network_config_networks').ready(function(){ + // when vm_network_config_remove link is click remove target row + $('#vm_network_config_remove_'+vm_network_config_rows).bind('click', function(e){ + remove_network_config_row(e.target.id.substr(25)); // remove vm_network_config_remove_ bit to get row num + }); + + // when select box clicked, store current value for use on change + $('#vm_network_config_network_select_'+vm_network_config_rows).bind('click', function(e){ + current_selectbox_value = e.target.value; + }); + + // when value of network select box is switched + $('#vm_network_config_network_select_'+vm_network_config_rows).bind('change', function(e){ + row = e.target.id.substr(33) + + // find nic w/ selected network id + for(i = 0; i < nics.length; ++i){ + if(nics[i].network_id == e.target.value){ + nics[i].selected = true; + + // fill in mac / ip address textfields as necessary + $('#vm_network_config_mac_'+row).html(''); + if(nics[i].static_ip != ""){ + $('#vm_network_config_ip_'+row).html(''); + }else{ + $('#vm_network_config_ip_'+row).html(' '); + } + + // for the other select boxes, removed selected network + $('.vm_network_config_network_select:not(#vm_network_config_network_select_'+row+') option[@value='+nics[i].network_id+']').remove(); + + break; + } + } + + // if we are clearing the row, do so + if(e.target.value == ""){ + $('#vm_network_config_mac_'+row).html(''); + $('#vm_network_config_ip_'+row).html(' '); + } + + // add unselected network back to other selectboxes. + add_unselected_network('.vm_network_config_network_select:not(#vm_network_config_network_select_'+row+')', current_selectbox_value); + + // show / hide ip address column + toggle_ip_address_column(); + + // only add a new blank row if last row's select box was changed + if(e.target.value != "" && row == vm_network_config_last_row){ + // add row + add_network_config_row(); + } + }); + }); + + // show / hide ip address column + toggle_ip_address_column(); + } + + + + // remove a network config row + function remove_network_config_row(row_num){ + // if trying to remove the first row or a nonexistant one, fail to do so + if(row_num < 2 || row_num > vm_network_config_last_row) + return; + + // get selected network, add it to other selectboxes + network_id = $('#vm_network_config_network_select_' + row_num).val(); + add_unselected_network('.vm_network_config_network_select:not(#vm_network_config_network_select_'+row_num+')', network_id); + + // remove the row + $('#vm_network_config_row_' + row_num).remove(); + + // when removed, set global params + $('#vm_network_config_networks').ready(function(){ + vm_network_config_rows -= 1; + rows = $('#vm_network_config_networks').children(); + vm_network_config_last_row = rows[rows.length - 1].id.substr(22); + + // show / hide ip address column + toggle_ip_address_column(); + }); + } + + // intially show only one vm network config row + $(document).ready(function(){ + add_network_config_row(true); + }); + + // when vm_network_config_add link is clicked show new row + $('#vm_network_config_add').bind('click', function(){ + // TODO check if there exists an empty row + // TODO check to see if we've already added as many rows as there are nets + add_network_config_row(); + }); diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml index b137de6..7ac3fdf 100644 --- a/src/app/views/vm/_grid.rhtml +++ b/src/app/views/vm/_grid.rhtml @@ -34,7 +34,6 @@ <% end %> {display: 'CPUs', name : 'num_vcpus_allocated', width : 40, sortable : true, align: 'left'}, {display: 'Memory (MB)', name : 'memory_allocated', width : 60, sortable : true, align: 'right'}, - {display: 'vNIC Mac Addr', name : 'vnic_mac_addr', width : 60, sortable : true, align: 'right'}, {display: 'State', name : 'state', width : 50, sortable : true, align: 'right'}, {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } ], diff --git a/src/app/views/vm/_list.rhtml b/src/app/views/vm/_list.rhtml index 42300d6..54ae741 100644 --- a/src/app/views/vm/_list.rhtml +++ b/src/app/views/vm/_list.rhtml @@ -4,7 +4,6 @@ Description & UUID CPUs Memory (mb) - vNIC MAC Addr State @@ -15,7 +14,6 @@ <%= link_to vm.description, { :controller => "vm", :action => 'show', :id => vm }, { :class => "show" } %>
    <%= vm.uuid %>
    <%= vm.num_vcpus_allocated %> <%= vm.memory_allocated_in_mb %> - <%= vm.vnic_mac_addr %> <%= vm.state %> <%unless vm.needs_restart.nil? or vm.needs_restart == 0 -%> diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index 0f70da8..a7a7388 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -106,7 +106,7 @@ Num vcpus used:
    Memory allocated:
    Memory used:
    - vNIC MAC address:
    + vNIC MAC addresses:
    Boot device:
    Provisioning source:
    State:
    @@ -121,7 +121,9 @@ <%=h @vm.num_vcpus_used %>
    <%=h @vm.memory_allocated_in_mb %> MB
    <%=h @vm.memory_used_in_mb %> MB
    - <%=h @vm.vnic_mac_addr %>
    + <% nic_macs = "" + @vm.nics.each { |nic| nic_macs += nic.mac + " " } %> + <%=h nic_macs %>
    <%=h @vm.boot_device %>
    <%=h @vm.provisioning_and_boot_settings %>
    <%=h @vm.state %> diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 1409692..09f7b06 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -343,6 +343,45 @@ padding-left: 30px; } +#vm_network_config .i { + float: left; +} + +#vm_network_config_header_network { + float: left; + width: 20%; +} +#vm_network_config_header_mac, #vm_network_config_header_ip{ + float: left; + width: 33%; +} + +.vm_network_config_net { + float: left; + width: 20%; +} +.vm_network_config_mac, .vm_network_config_ip{ + float: left; + width: 33%; + min-width: 100px; +} + +.vm_network_config_remove { + float: left; + padding-top: 5px; + color: #0033CC; +} +.vm_network_config_remove:hover { + cursor: pointer; +} + +#vm_network_config_add { + color: #0033CC; +} +#vm_network_config_add:hover { + cursor: pointer; +} + #vm_general_section_link img, #vm_resources_section_link img, #vm_storage_section_link img, #vm_network_section_link img{ float: left; padding-top: 2px; -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 9 21:56:28 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Jul 2009 17:56:28 -0400 Subject: [Ovirt-devel] [PATCH server] permit many-to-many vms / networks relationship (test changes) In-Reply-To: <1247176588-4280-5-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> <1247176588-4280-2-git-send-email-mmorsi@redhat.com> <1247176588-4280-3-git-send-email-mmorsi@redhat.com> <1247176588-4280-4-git-send-email-mmorsi@redhat.com> <1247176588-4280-5-git-send-email-mmorsi@redhat.com> Message-ID: <1247176588-4280-6-git-send-email-mmorsi@redhat.com> implements changes to the tests to test newly added / modified features --- src/test/fixtures/nics.yml | 16 +++--- src/test/fixtures/vms.yml | 8 --- src/test/unit/network_test.rb | 69 ++++++++++++++++++++++++++++-- src/test/unit/nic_test.rb | 95 ++++++++++++++++++++++++++++++++++++++++- src/test/unit/vm_test.rb | 14 +++--- 5 files changed, 173 insertions(+), 29 deletions(-) diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index 97397cd..aff7e9b 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -3,21 +3,21 @@ mailserver_nic_one: usage_type: 1 bandwidth: 100 host: mailservers_managed_node - physical_network: mail_network_one + network: mail_network_one mailserver_nic_two: mac: 22:11:33:66:44:55 usage_type: 1 bandwidth: 100 host: mailservers_managed_node - physical_network: mail_network_two + network: mail_network_two fileserver_nic_one: mac: 00:99:00:99:13:07 usage_type: 1 bandwidth: 100 host: fileserver_managed_node - physical_network: fileserver_network + network: fileserver_network ldapserver_nic_one: mac: 00:03:02:00:09:06 @@ -25,32 +25,32 @@ ldapserver_nic_one: bandwidth: 100 bridge: host: ldapserver_managed_node - physical_network: static_physical_network_one + network: static_physical_network_one buildserver_nic_one: mac: 07:17:19:65:03:38 usage_type: 1 bandwidth: 100 host: buildserver_managed_node - physical_network: dhcp_physical_network_one + network: dhcp_physical_network_one buildserver_nic_two: mac: 07:17:19:65:03:39 usage_type: 1 bandwidth: 100 host: buildserver_managed_node - physical_network: static_physical_network_one + network: static_physical_network_one mediaserver_nic_one: mac: 07:17:19:65:03:32 usage_type: 1 bandwidth: 100 host: mediaserver_managed_node - physical_network: mediaserver_network_one + network: mediaserver_network_one mediaserver_nic_two: mac: 07:17:19:65:03:31 usage_type: 1 bandwidth: 100 host: mediaserver_managed_node - physical_network: mediaserver_network_two + network: mediaserver_network_two diff --git a/src/test/fixtures/vms.yml b/src/test/fixtures/vms.yml index b2711b2..4b07e32 100644 --- a/src/test/fixtures/vms.yml +++ b/src/test/fixtures/vms.yml @@ -5,7 +5,6 @@ production_httpd_vm: num_vcpus_used: 2 memory_allocated: 262144 memory_used: 131072 - vnic_mac_addr: 23:51:90:A1:13:37 state: stopped needs_restart: 0 boot_device: hd @@ -20,7 +19,6 @@ production_mysqld_vm: num_vcpus_used: 1 memory_allocated: 2048 memory_used: 512 - vnic_mac_addr: 15:99:FE:ED:11:EE state: running needs_restart: 0 boot_device: network @@ -33,7 +31,6 @@ production_ftpd_vm: num_vcpus_used: 1 memory_allocated: 1024 memory_used: 512 - vnic_mac_addr: FF:AA:BB:00:11:55 state: stopped needs_restart: 1 boot_device: cdrom @@ -46,7 +43,6 @@ production_postgresql_vm: num_vcpus_used: 2 memory_allocated: 2048 memory_used: 1536 - vnic_mac_addr: 48:24:12:93:42:11 state: running needs_restart: 0 boot_device: hd @@ -59,7 +55,6 @@ foobar_prod1_vm: num_vcpus_used: 2 memory_allocated: 4096 memory_used: 4096 - vnic_mac_addr: FF:FF:FF:EE:EE:EE state: running needs_restart: 0 boot_device: cdrom @@ -85,7 +80,6 @@ foobar_prod2_vm: num_vcpus_used: 2 memory_allocated: 4096 memory_used: 4096 - vnic_mac_addr: EE:EE:EE:FF:FF:FF state: running needs_restart: 0 boot_device: cdrom @@ -98,7 +92,6 @@ corp_com_errata_vm: num_vcpus_used: 2 memory_allocated: 2048 memory_used: 2048 - vnic_mac_addr: 77:77:77:77:77:77 state: running needs_restart: 0 boot_device: network @@ -111,7 +104,6 @@ corp_com_bugzilla_vm: num_vcpus_used: 2 memory_allocated: 2048 memory_used: 2048 - vnic_mac_addr: 77:77:77:77:77:77 state: running needs_restart: 0 boot_device: network diff --git a/src/test/unit/network_test.rb b/src/test/unit/network_test.rb index 64c5df4..9b75e8b 100644 --- a/src/test/unit/network_test.rb +++ b/src/test/unit/network_test.rb @@ -1,8 +1,69 @@ -require 'test_helper' +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# 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_helper' class NetworkTest < ActiveSupport::TestCase - # Replace this with your real tests. - def test_truth - assert true + fixtures :networks + fixtures :vms + fixtures :hosts + fixtures :nics + fixtures :boot_types + + def setup + end + + def test_vlan_invalid_without_number + vl = Vlan.new({:name => 'testvlan', :boot_type_id => 2}) + flunk "Vlan without number should not be valid" if vl.valid? + vl.number = 1 + flunk "Vlan with number should be valid" unless vl.valid? + end + + def test_vlan_nics_only_associated_with_vm + vl = Vlan.create({:name => 'testvlan', + :boot_type => boot_types(:boot_type_dhcp), + :number => 1}) # need to create for id + nic = Nic.new({:mac => '11:22:33:44:55:66', + :bandwidth => 100, + :network => vl, + :host => hosts(:prod_corp_com)}) + vl.nics.push nic + flunk "Nic assigned to vlan must only be associated with vm" if vl.valid? + nic.host = nil + nic.vm = vms(:production_httpd_vm) + flunk "Vlan consisting of only vm nics should be valid" unless vl.valid? + end + + def test_physical_network_is_destroyable + pn = PhysicalNetwork.new + flunk "PhysicalNetwork with no nics should be destroyable" unless pn.is_destroyable? + pn.nics.push Nic.new + flunk "PhysicalNetwork with nics should not be destroyable" if pn.is_destroyable? + end + + def test_vlan_is_destroyable + vl = Vlan.new + flunk "Vlan with no nics and bondings should be destroyable" unless vl.is_destroyable? + vl.nics.push Nic.new + flunk "Vlan with nics should not be destroyable" if vl.is_destroyable? + vl.nics.clear + vl.bondings.push Bonding.new + flunk "Vlan with bondings should not be destroyable" if vl.is_destroyable? end end diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb index 07f54c6..b6bdb07 100644 --- a/src/test/unit/nic_test.rb +++ b/src/test/unit/nic_test.rb @@ -24,6 +24,7 @@ class NicTest < Test::Unit::TestCase fixtures :nics fixtures :hosts fixtures :networks + fixtures :vms def setup @nic = Nic.new( @@ -31,7 +32,7 @@ class NicTest < Test::Unit::TestCase :usage_type => 1, :bandwidth => 100 ) @nic.host = hosts(:prod_corp_com) - @nic.physical_network = networks(:static_physical_network_one) + @nic.network = networks(:static_physical_network_one) @ip_address = IpV4Address.new( :address => '1.2.3.4', @@ -74,10 +75,100 @@ class NicTest < Test::Unit::TestCase end def test_static_network_nic_must_have_ip - @nic.physical_network = networks(:static_physical_network_one) + @nic.network = networks(:static_physical_network_one) @nic.ip_addresses.delete_if { true } flunk 'Nics assigned to static networks must have at least one ip' if @nic.valid? end + def test_vm_nic_must_have_network + @nic.host = nil + @nic.vm = vms(:production_httpd_vm) + flunk 'vm nic that is assigned to network is valid' unless @nic.valid? + + @nic.network = nil + flunk 'vm nic without a network is not valid' if @nic.valid? + end + + def test_host_nic_cant_be_assigned_to_vlan + @nic.network = networks(:dhcp_vlan_one) + flunk 'host nic cant be assgined to vlan' if @nic.valid? + end + + def test_nic_networking + flunk 'nic.networking? should return true if assigned to network' unless @nic.networking? + @nic.network = nil + flunk 'nic.networking? should return false if not assigned to network' if @nic.networking? + end + + def test_nic_boot_protocol + nic = Nic.new + nic.ip_addresses << @ip_address + nic.network = networks(:static_physical_network_one) + flunk 'incorrect nic boot protocol' unless nic.boot_protocol == 'static' + end + + def test_nic_ip_addresses + nic = Nic.new + nic.ip_addresses << @ip_address + nic.network = networks(:static_physical_network_one) + flunk 'incorrect nic ip address' unless nic.ip_address == @ip_address.address + end + + def test_nic_netmask + nic = Nic.new + network = Network.new + network.ip_addresses << @ip_address + nic.network = network + + flunk 'incorrect nic netmask' unless nic.netmask == @ip_address.netmask + end + + + def test_nic_broadcast + nic = Nic.new + network = Network.new + network.ip_addresses << @ip_address + nic.network = network + + flunk 'incorrect nic broadcast' unless nic.broadcast == @ip_address.broadcast + end + + def test_nic_gateway + nic = Nic.new + network = Network.new + network.ip_addresses << @ip_address + nic.network = network + + flunk 'incorrect nic gateway' unless nic.gateway == @ip_address.gateway + end + + def test_nic_parent + flunk 'incorrect host nic parent' unless @nic.parent == hosts(:prod_corp_com) + + @nic.host = nil + flunk 'incorrect nic parent' unless @nic.parent == nil + + @nic.vm = vms(:production_httpd_vm) + flunk 'incorrect vm nic parent' unless @nic.parent == vms(:production_httpd_vm) + end + + def test_nic_gen_mac + mac = Nic::gen_mac + flunk 'invalid generated mac' unless mac =~ /^([0-9a-fA-F]{2}([:-]|$)){6}$/ + end + + def test_nic_vm_xor_nic_host + flunk 'host nic without vm is valid' unless @nic.valid? + + @nic.vm = vms(:production_httpd_vm) + flunk 'nic cannot specify both host and vm' if @nic.valid? + + @nic.host = nil + flunk 'vm nic without host is valid' unless @nic.valid? + + @nic.vm = nil + flunk 'nic must specify either host or vm' if @nic.valid? + end + end diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index a5d6b3d..80071c7 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -38,8 +38,8 @@ class VmTest < ActiveSupport::TestCase :num_vcpus_allocated => 1, :boot_device => 'hd', :memory_allocated_in_mb => 1, - :memory_allocated => 1024, - :vnic_mac_addr => '11:22:33:44:55:66') + :memory_allocated => 1024) + @vm.vm_resource_pool = pools(:corp_com_production_vmpool) end @@ -76,11 +76,6 @@ class VmTest < ActiveSupport::TestCase flunk 'Vm must specify memory_allocated_in_mb' if @vm.valid? end - def test_valid_fails_without_vnic_mac_addr - @vm.vnic_mac_addr = '' - flunk 'Vm must specify vnic_mac_addr' if @vm.valid? - end - def test_valid_fails_without_vm_resources_pool_id @vm.vm_resource_pool_id = '' flunk 'Vm must specify vm_resources_pool_id' if @vm.valid? @@ -195,4 +190,9 @@ class VmTest < ActiveSupport::TestCase def test_paginated_results assert_equal 5, Vm.paged_with_perms('ovirtadmin', Privilege::VIEW, 1, 'vms.id').size end + + def test_vm_gen_uuid + uuid = Vm::gen_uuid + flunk 'invalid generated uuid' unless uuid =~ /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/ + end end -- 1.6.0.6 From apevec at redhat.com Fri Jul 10 01:48:32 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 03:48:32 +0200 Subject: [Ovirt-devel] [PATCH node 0/2] ovirt-config-networking fixes (rebase) In-Reply-To: <4A565DA5.3080106@redhat.com> References: <4A565DA5.3080106@redhat.com> Message-ID: <1247190514-2843-1-git-send-email-apevec@redhat.com> From: Darryl L. Pierce This are the remaining patches from Darryl for o-c-networking rebased to apply on top of current 'next' branch. From apevec at redhat.com Fri Jul 10 01:48:34 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 03:48:34 +0200 Subject: [Ovirt-devel] [PATCH node 2/2] Change DNS setup to match NTP. rhbz#508677 In-Reply-To: <4A565DA5.3080106@redhat.com> References: <4A565DA5.3080106@redhat.com> Message-ID: <1247190514-2843-3-git-send-email-apevec@redhat.com> From: Darryl L. Pierce Rather than expecting the DNS entries to be on one line, they are now entered as two separate entries. Also fixes a bug that caused the first DNS entry to be lost when a second entry was provided. In both cases, the input is collected in a colon-delimited string that is then processed. This is to provide consistency between the interactive setup and the automated setup. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 55 +++++++++++++++++++++++++------------- scripts/ovirt-functions | 18 ++++++++++++ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index e751535..6765354 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -223,17 +223,30 @@ function configure_dns if [ -z "$AUTO" ]; then if has_configured_interface true; then while true; do - printf "\n" - echo "Enter up to two DNS servers separated by commas:" - if [ -n "$OVIRT_DNS" ]; then - echo "Press Enter for defaults: ($OVIRT_DNS)" - fi - read -ep ": " - DNS=$REPLY - - if [ -z "$DNS" ]; then - DNS=$OVIRT_DNS - fi + for dns in first second; do + while true; do + printf "\n" + read -ep "Please enter the ${dns} DNS server (or ENTER to exit): " + if [[ -z "${REPLY}" ]]; then + if [[ -z "${DNS}" ]]; then + printf "\nAborted...\n" + return + else + break + fi + fi + if is_valid_ipv4 $REPLY; then + if [[ -z "${DNS}" ]]; then + DNS="${REPLY}" + elif [[ -n "${REPLY}" ]]; then + DNS="${DNS}:${REPLY}" + fi + break + else + printf "${REPLY} is an invalid address.\n" + fi + done + done printf "\n" ask_yes_or_no "Is this correct (y/n/a)?" true true @@ -247,12 +260,11 @@ function configure_dns fi if [ -n "$DNS" ]; then - DNS1=$(echo "$DNS" | awk -F, '{print $1}') - DNS2=$(echo "$DNS" | awk -F, '{print $2}') + DNS1=$(echo "$DNS" | awk -F\: '{print $1}') + DNS2=$(echo "$DNS" | awk -F\: '{print $2}') - test -n "$DNS1" && IF_CONFIG="set $IF_ROOT/DNS1 $DNS1" - test -n "$DNS2" && IF_CONFIG="set $IF_ROOT/DNS2 $DNS2" - printf "$IF_CONFIG\n" >> $IF_FILENAME + test -n "$DNS1" && printf "set $IF_ROOT/DNS1 $DNS1\n" >> $IF_FILENAME + test -n "$DNS2" && printf "set $IF_ROOT/DNS2 $DNS2\n" >> $IF_FILENAME fi } @@ -272,7 +284,11 @@ function configure_ntp if [ -z "$REPLY" ]; then break; fi - NTPSERVERS="$NTPSERVERS $REPLY" + if is_valid_ipv4 $REPLY; then + NTPSERVERS="${NTPSERVERS}:${REPLY}" + else + printf "${REPLY} is an invalid address.\n" + fi done fi fi @@ -292,10 +308,11 @@ save\n" > $ntpconf if [ -n "$NTPSERVERS" ]; then offset=1 - for server in $NTPSERVERS; do + SERVERS=$(echo $NTPSERVERS | awk 'BEGIN{FS=":"}{for (i=1; i<=NF; i++) print $i}') + for server in $SERVERS; do printf "set /files/etc/ntp.conf/server[${offset}] ${server}\n" >> $ntpconf offset=$(echo "$offset+1" | bc) - done + done fi } diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index ecde762..81db36b 100755 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -654,6 +654,24 @@ ask_yes_or_no () { done } +# Verifies the address entered is a valid IPv4 address. +is_valid_ipv4 () { + local address=${1} + local result=1 + + if [[ "$address" =~ "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" ]]; then + OIFS=$IFS + IFS='.' + ip=($address) + IFS=$OIFS + [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ + && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] + result=$? + fi + + return $result +} + # execute a function if called as a script, e.g. # ovirt-functions ovirt_store_config /etc/hosts -- 1.6.2.5 From apevec at redhat.com Fri Jul 10 01:48:33 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 03:48:33 +0200 Subject: [Ovirt-devel] [PATCH node 1/2] Follow on patch for bz#507393. In-Reply-To: <4A565DA5.3080106@redhat.com> References: <4A565DA5.3080106@redhat.com> Message-ID: <1247190514-2843-2-git-send-email-apevec@redhat.com> From: Darryl L. Pierce This patch fixes the warnings displayed to the user. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 74 +++++++++++++++++++++++---------------- 1 files changed, 44 insertions(+), 30 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index aa04a7c..e751535 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -28,6 +28,21 @@ if ! is_local_storage_configured; then exit 99 fi +# Checks that a network interface was already configured. +function has_configured_interface +{ + local show_message=${1-false} + + if [[ -n "${CONFIGURED_NIC}" ]]; then + return 0 + else + if $show_message; then + printf "\nYou must configure a network interface first.\n\n" + fi + return 1 + fi +} + function configure_interface { local NIC=$1 @@ -40,7 +55,7 @@ function configure_interface PREFIX=$OVIRT_IP_PREFIX fi - if [[ -n "${CONFIGURED_NIC}" ]]; then + if has_configured_interface; then printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" @@ -194,11 +209,6 @@ function configure_interface function configure_dns { - if [[ -z "${CONFIGURED_NIC}" ]]; then - printf "\nYou must configure a network interface first.\n\n" - return - fi - local DNS=$1 local AUTO=$2 if [[ "$AUTO" == "AUTO" && @@ -211,27 +221,29 @@ function configure_dns local IF_CONFIG= if [ -z "$AUTO" ]; then - while true; do - printf "\n" - echo "Enter up to two DNS servers separated by commas:" - if [ -n "$OVIRT_DNS" ]; then - echo "Press Enter for defaults: ($OVIRT_DNS)" - fi - read -ep ": " - DNS=$REPLY + if has_configured_interface true; then + while true; do + printf "\n" + echo "Enter up to two DNS servers separated by commas:" + if [ -n "$OVIRT_DNS" ]; then + echo "Press Enter for defaults: ($OVIRT_DNS)" + fi + read -ep ": " + DNS=$REPLY - if [ -z "$DNS" ]; then - DNS=$OVIRT_DNS - fi + if [ -z "$DNS" ]; then + DNS=$OVIRT_DNS + fi - printf "\n" - ask_yes_or_no "Is this correct (y/n/a)?" true true - case $? in - 0) break ;; - 1) ;; - 2) return ;; - esac - done + printf "\n" + ask_yes_or_no "Is this correct (y/n/a)?" true true + case $? in + 0) break ;; + 1) ;; + 2) return ;; + esac + done + fi fi if [ -n "$DNS" ]; then @@ -254,13 +266,15 @@ function configure_ntp fi if [ -z "$AUTO" ]; then - while true; do - read -ep "Enter an NTP server (hit return when finished): " + if has_configured_interface true; then + while true; do + read -ep "Enter an NTP server (hit return when finished): " - if [ -z "$REPLY" ]; then break; fi + if [ -z "$REPLY" ]; then break; fi - NTPSERVERS="$NTPSERVERS $REPLY" - done + NTPSERVERS="$NTPSERVERS $REPLY" + done + fi fi } -- 1.6.2.5 From apevec at gmail.com Fri Jul 10 01:58:46 2009 From: apevec at gmail.com (Alan Pevec) Date: Fri, 10 Jul 2009 03:58:46 +0200 Subject: [Ovirt-devel] [PATCH ovirt-node] add glusterfs-client dependency for ovirt-node In-Reply-To: <20090709114355.GA25792@dev.gluster.com> References: <20090709114355.GA25792@dev.gluster.com> Message-ID: <2be7262f0907091858o7e336178gbe826f5e381d008b@mail.gmail.com> On Thu, Jul 9, 2009 at 1:43 PM, Harshavardhana wrote: > --- > ovirt-node.spec.in | 3 ++- > 1 files changed, 2 insertions(+), 1 deletions(-) > > diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in > index 746cf3d..2fdf4f5 100644 > --- a/ovirt-node.spec.in > +++ b/ovirt-node.spec.in > @@ -21,7 +21,7 @@ Requires(post): /sbin/chkconfig > Requires(preun): /sbin/chkconfig > BuildRequires: libvirt-devel >= 0.5.1 > BuildRequires: dbus-devel hal-devel > -Requires: libvirt >= 0.5.1 > +Requires: libvirt >= 0.6.3 > Requires: augeas >= 0.3.5 > Requires: libvirt-qpid >= 0.2.14-3 > Requires: hal > @@ -31,6 +31,7 @@ Requires: cyrus-sasl-gssapi cyrus-sasl > cyrus-sasl-lib > Requires: iscsi-initiator-utils > Requires: ntp > Requires: nfs-utils > +Requires: glusterfs-client >= 2.0.2 Latest in F-11 is 2.0.1 https://admin.fedoraproject.org/updates/glusterfs Is there any particular feature why min. 2.0.2 is required or we could try it with Fedora's 2.0.1? -------------- next part -------------- An HTML attachment was scrubbed... URL: From apevec at redhat.com Fri Jul 10 02:18:10 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 04:18:10 +0200 Subject: [Ovirt-devel] [PATCH node] remove augtool save in the middle In-Reply-To: <1247190514-2843-1-git-send-email-apevec@redhat.com> References: <1247190514-2843-1-git-send-email-apevec@redhat.com> Message-ID: <1247192290-2981-1-git-send-email-apevec@redhat.com> it fails on bind-mounted file, hence there's a wrapper in ovirt-functions which sets appropriate augeas flags to do overwrite instead to rename. --- scripts/ovirt-config-networking | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 6765354..2201369 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -304,7 +304,7 @@ rm ${ntproot}\n\n set ${ntproot}/driftfile /var/lib/ntp/drift\n\ set ${ntproot}/includefile /etc/ntp/crypto/pw\n\ set ${ntproot}/keys /etc/ntp/keys\n\ -save\n" > $ntpconf +" > $ntpconf if [ -n "$NTPSERVERS" ]; then offset=1 -- 1.6.2.5 From apevec at redhat.com Fri Jul 10 02:31:19 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 04:31:19 +0200 Subject: [Ovirt-devel] [PATCH node] display network interface driver name and MAC Message-ID: <1247193079-7526-1-git-send-email-apevec@redhat.com> to help identifying --- scripts/ovirt-config-networking | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index aa04a7c..4007ec6 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -320,6 +320,11 @@ else printf "Configuring the network will destroy any existing networking\n" printf "configuration on this system.\n" printf "***** WARNING *****\n" + for nic in $NICS; do + driver=$(basename $(readlink /sys/class/net/$nic/device/driver)) + mac=$(cat /sys/class/net/$nic/address) + printf "%s\t%s\t%s\n" $nic $driver $mac + done DNS="DNS" NTP="NTP" -- 1.6.0.6 From apevec at redhat.com Fri Jul 10 02:33:47 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 04:33:47 +0200 Subject: [Ovirt-devel] Re: [PATCH node 0/2] ovirt-config-networking fixes (rebase) In-Reply-To: <1247190514-2843-1-git-send-email-apevec@redhat.com> References: <4A565DA5.3080106@redhat.com> <1247190514-2843-1-git-send-email-apevec@redhat.com> Message-ID: <4A56A88B.90504@redhat.com> ACK - I've pushed this series incl. fixup (augtool save...) From apevec at redhat.com Fri Jul 10 02:37:34 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 04:37:34 +0200 Subject: [Ovirt-devel] [PATCH node] display network interface driver name and MAC In-Reply-To: <1247193079-7526-1-git-send-email-apevec@redhat.com> References: <1247193079-7526-1-git-send-email-apevec@redhat.com> Message-ID: <1247193454-7993-1-git-send-email-apevec@redhat.com> to help identifying --- scripts/ovirt-config-networking | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 2201369..14e845a 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -351,6 +351,11 @@ else printf "Configuring the network will destroy any existing networking\n" printf "configuration on this system.\n" printf "***** WARNING *****\n" + for nic in $NICS; do + driver=$(basename $(readlink /sys/class/net/$nic/device/driver)) + mac=$(cat /sys/class/net/$nic/address) + printf "%s\t%s\t%s\n" $nic $driver $mac + done DNS="DNS" NTP="NTP" -- 1.6.0.6 From apevec at redhat.com Fri Jul 10 02:37:54 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 04:37:54 +0200 Subject: [Ovirt-devel] [PATCH node] fix mount_live Message-ID: <1247193474-8021-1-git-send-email-apevec@redhat.com> use /dev/loop0 only when it really contains LiveCD ISO. mount_live should fail silently when LiveCD ISO is not available, which is the case on boot from HostVG/Root. Resolves: rhbz#508425 --- scripts/ovirt-functions | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) mode change 100755 => 100644 scripts/ovirt-functions diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions old mode 100755 new mode 100644 index 81db36b..a536236 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -249,9 +249,11 @@ mount_live() { return 0 fi local live_dev=/dev/live - if [ ! -e $live_dev ]; then + if [ ! -e $live_dev ] && losetup /dev/loop0|grep -q '\.iso'; then # PXE boot live_dev=/dev/loop0 + else + return 1 fi mkdir -p /live mount -r $live_dev /live || mount $live_dev /live -- 1.6.0.6 From harsha at gluster.com Fri Jul 10 04:08:23 2009 From: harsha at gluster.com (Harshavardhana) Date: Fri, 10 Jul 2009 09:38:23 +0530 Subject: [Ovirt-devel] [PATCH ovirt-node] add glusterfs-client dependency for ovirt-node In-Reply-To: <2be7262f0907091858o7e336178gbe826f5e381d008b@mail.gmail.com> References: <20090709114355.GA25792@dev.gluster.com> <2be7262f0907091858o7e336178gbe826f5e381d008b@mail.gmail.com> Message-ID: <8a80e9760907092108y3cc3f4dj5cac26857581c0af@mail.gmail.com> Alan, Recent 2.0.3 release contains support for "fuse" module which provides big_writes in kernel 2.6.26 onwards which is much needed for improving performance while using VM's. So i had to backport the patches and apply it for 2.0.2 release and kept it at our repository. I will follow up with f-11 glusterfs maintainer and get it updated to latest release. Regards -- Harshavardhana Z Research Inc http://www.zresearch.com/ On Fri, Jul 10, 2009 at 7:28 AM, Alan Pevec wrote: > > > On Thu, Jul 9, 2009 at 1:43 PM, Harshavardhana wrote: > >> --- >> ovirt-node.spec.in | 3 ++- >> 1 files changed, 2 insertions(+), 1 deletions(-) >> >> diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in >> index 746cf3d..2fdf4f5 100644 >> --- a/ovirt-node.spec.in >> +++ b/ovirt-node.spec.in >> @@ -21,7 +21,7 @@ Requires(post): /sbin/chkconfig >> Requires(preun): /sbin/chkconfig >> BuildRequires: libvirt-devel >= 0.5.1 >> BuildRequires: dbus-devel hal-devel >> -Requires: libvirt >= 0.5.1 >> +Requires: libvirt >= 0.6.3 >> Requires: augeas >= 0.3.5 >> Requires: libvirt-qpid >= 0.2.14-3 >> Requires: hal >> @@ -31,6 +31,7 @@ Requires: cyrus-sasl-gssapi cyrus-sasl >> cyrus-sasl-lib >> Requires: iscsi-initiator-utils >> Requires: ntp >> Requires: nfs-utils >> +Requires: glusterfs-client >= 2.0.2 > > > Latest in F-11 is 2.0.1 > https://admin.fedoraproject.org/updates/glusterfs > Is there any particular feature why min. 2.0.2 is required or we could try > it with Fedora's 2.0.1? > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From intermernet at gmail.com Fri Jul 10 07:06:31 2009 From: intermernet at gmail.com (Mike Hughes) Date: Fri, 10 Jul 2009 17:06:31 +1000 Subject: [Ovirt-devel] Web page updates? Message-ID: <265910900907100006h459df233u776afcf979801ca6@mail.gmail.com> Hi Guys, I know you've all got LOTS of work to do pushing v1.0 out but would it be possible for someone to update the main site (ovirt.org)? It still has 0.96 as the latest release, I think the installation instructions are a bit out of date and it still references F10 as being the required base system. Maybe just update the download page with a link to the newest versions and do a quick install procedure for F11. Feel free to ignore this request if you've got higher priorities to deal with ;-) Regards and respect, Mike Hughes -------------- next part -------------- An HTML attachment was scrubbed... URL: From apevec at redhat.com Fri Jul 10 11:30:14 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 13:30:14 +0200 Subject: [Ovirt-devel] [PATCH node] display network interface driver name and MAC In-Reply-To: <1247193454-7993-1-git-send-email-apevec@redhat.com> References: <1247193454-7993-1-git-send-email-apevec@redhat.com> Message-ID: <1247225414-9728-1-git-send-email-apevec@redhat.com> to help identifying --- scripts/ovirt-config-networking | 13 +++++++++++++ 1 files changed, 13 insertions(+), 0 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 2201369..8380187 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -351,6 +351,19 @@ else printf "Configuring the network will destroy any existing networking\n" printf "configuration on this system.\n" printf "***** WARNING *****\n" + pixied=false + for nic in $NICS; do + driver=$(basename $(readlink /sys/class/net/$nic/device/driver)) + mac=$(cat /sys/class/net/$nic/address) + if [ "$nic" = "$OVIRT_BOOTIF" ]; then + nic="*$nic" + pixied=true + fi + printf "%s\t%s\t%s\n" $nic $driver $mac + done + if $pixied; then + printf "*=PXE boot interface\n" + fi DNS="DNS" NTP="NTP" -- 1.6.0.6 From dpierce at redhat.com Fri Jul 10 13:33:21 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 09:33:21 -0400 Subject: [Ovirt-devel] [PATCH node] This is a follow-on patch for bz#507455. Message-ID: <1247232801-7772-1-git-send-email-dpierce@redhat.com> It fixes how removable media are detected and removes any device which has no storage space available. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-storage | 168 +++++++++++++++++++++++++++++++----------- 1 files changed, 125 insertions(+), 43 deletions(-) diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage index 5560b5f..f6a30c9 100755 --- a/scripts/ovirt-config-storage +++ b/scripts/ovirt-config-storage @@ -11,6 +11,7 @@ ME=$(basename "$0") warn() { printf '%s: %s\n' "$ME" "$*" >&2; } die() { warn "$*"; exit 1; } +debug() { if $debugging; then printf "[DEBUG] %s\n" "$*"; fi } trap '__st=$?; stop_log; exit $__st' 0 trap 'exit $?' 1 2 13 15 @@ -36,8 +37,12 @@ logging_min_size=5 data_min_size=5 swap_min_size=5 +# Gets the drive's size and sets the supplied variable. +# $1 - the drive +# $2 - the variable get_drive_size() { + debug "get_drive_size: start" local drive=$1 local space_var=$2 @@ -70,11 +75,36 @@ get_drive_size() fi size=$(echo "scale=0; $size / (1024 * 1024)" | bc -l) - echo "$drive ($size MB)" + eval $space_var=$size + + debug "::size=$size" + + debug "get_drive_size: exit" +} + +print_drive_size () +{ + debug "print_drive_size: start" + local drive=$1 + local udi=${2-} + + if [ -z "$udi" ]; then + for this_udi in $(hal-find-by-capability --capability storage); do + if [[ "$(hal-get-property --udi $this_udi --key block.device)" = "$drive" ]]; then + udi=$this_udi + fi + done + fi + get_drive_size $drive SIZE + debug "::drive=$drive" + debug "::SIZE=$SIZE" + echo "$drive ($SIZE MB)" echo "Disk Identifier: $(basename "$udi")" if [ -n "$space_var" ]; then eval $space_var=$size fi + + debug "print_drive_size: end" } check_partition_sizes() @@ -130,73 +160,112 @@ check_partition_sizes() return $rc } -# Find a usable/selected storage device. -# If there are none, give a diagnostic and return nonzero. -# If there is just one, e.g., /dev/sda, treat it as selected (see below). -# and return 0. If there are two or more, make the user select one -# or decline. Upon decline, return nonzero. Otherwise, print the -# selected name, then return 0. -# Sample output: /dev/sda -get_dev_name() +# Ensures the device is acceptable +# $1 - the device +check_if_device_is_good () +{ + debug "check_if_device_is_good: start" + local device=$1 + local result=1 + + # Must start with a '/'. + case $device in + *' '*) + # we use space as separator + warn "block device name '$device' contains space; skipping"; + continue;; + /*) + local SIZE + get_drive_size $device SIZE + debug "::SIZE=$SIZE" + if [ $SIZE -gt 0 ]; then result=0; fi + ;; + *) warn "block device name $device doesn't start with '/';" \ + " skipping"; continue;; + esac + + debug "check_if_device_is_good: end (result=$result)" + return $result +} + +get_drive_list () { + debug "get_drive_list: start" + local list_var=$1 + local udi_list=$(hal-find-by-capability --capability storage) + debug "list_var=$list_var" + if test -z "$udi_list"; then warn "ERROR: no usable storage devices detected" return 1 fi local d devices sizes + for d in $udi_list; do + debug "Examining $d" local drive_type=$(hal-get-property --udi "$d" --key storage.drive_type) + debug "::drive_type=$drive_type" test "X$drive_type" = Xdisk || continue local block_dev=$(hal-get-property --udi "$d" --key block.device) - # Must start with a '/'. - case $block_dev in - *' '*) - # we use space as separator - warn "block device name '$block_dev' contains space; skipping"; - continue;; - /*) ;; - *) warn "block device name $block_dev doesn't start with '/';" \ - " skipping"; continue;; - esac - test -z "$devices" \ - && devices="$block_dev" \ - || devices="$devices $block_dev" + debug "::block_dev=$block_dev" + check_if_device_is_good $block_dev + rc=$? + if [ $rc = 0 ]; then + debug "::Acceptable device: $block_dev" + test -z "$devices" \ + && devices="$block_dev" \ + || devices="$devices $block_dev" + fi done # FIXME: workaround for detecting virtio block devices devices="$devices $(ls /dev/vd? 2> /dev/null | xargs)" devices=$(echo $devices | tr ' ' '\n' | sort -u | xargs) - local num_devices=$(echo "$devices" | wc -w) - # If there's only one device, use it. - case $num_devices in - 0) warn "ERROR: found no usable block device"; return 1;; - 1) echo "$devices"; return 0;; - *) ;; # found more than one + if [ -n "$devices" ]; then + eval $list_var='"$devices"' + fi + + debug "get_drive_list: end" +} + +select_a_drive () +{ + local DRIVE_VAR=$1 + local choices + + get_drive_list choices + case $(echo "$choices" | wc -w) in + 0) die "ERROR: there are no usable block devices" ;; + 1) echo "$choices"; return 0;; + *) ;; # continue to selection esac - # There are two or more; make the user choose. - # display description for each disk - for d in $devices; do - get_drive_size $d >&2 + for choice in $choices; do + print_drive_size $choice done - local choices="$devices Abort" - select device in $choices - do - test "$device" = Abort && return 1 - test -z "$device" && continue - echo "$device" - return 0 + + while true; do + select device in $choices Abort + do + case $device in + "Abort") return 1;; + *) eval $DRIVE_VAR=$device; return 0;; + esac + done done } do_configure() { local name_and_size - DRIVE=$(get_dev_name) || return 0 - get_drive_size $DRIVE SPACE + select_a_drive DRIVE + debug "DRIVE=$DRIVE" + test -z "$DRIVE" && return 0 + + print_drive_size $DRIVE SPACE printf "\n\nPlease configure storage partitions.\n\n" printf "* Enter partition sizes in MB.\n" @@ -286,7 +355,7 @@ do_review() The local disk will be repartitioned as follows: ================================================ - Physical Hard Disk: $(get_drive_size $DRIVE) + Physical Hard Disk: $(print_drive_size $DRIVE) Boot partition size: $BOOT_SIZE MB Swap partition size: $SWAP_SIZE MB Installation partition size: $ROOT_SIZE * 2 MB @@ -519,7 +588,7 @@ DATA_SIZE=${OVIRT_VOL_DATA_SIZE:-$default_data_size} if [ -n "$OVIRT_INIT" ]; then # if present, use the drive selected with 'ovirt_init' boot parameter DRIVE=$OVIRT_INIT - get_drive_size $DRIVE SPACE + print_drive_size $DRIVE SPACE fi # if the node is Fedora then use GPT, otherwise use MBR @@ -548,6 +617,19 @@ if [ "$1" == "AUTO" ]; then log "Missing device parameter: unable to partition any disk" fi else + # check commandline options + debugging=false + + while getopts dv c; do + case $c in + d) debugging=true;; + v) set -v;; + '?') die "invalid option \`-$OPTARG'";; + :) die "missing argument to \`-$OPTARG' option";; + *) die "internal error";; + esac + done + OPTIONS="\"Configure\" \"Review\" \"Commit Changes And Quit\" \"Return To Menu\"" eval set $OPTIONS PS3="Choose an option: " -- 1.6.2.5 From hbrock at redhat.com Fri Jul 10 14:07:55 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Fri, 10 Jul 2009 10:07:55 -0400 Subject: [Ovirt-devel] Web page updates? In-Reply-To: <265910900907100006h459df233u776afcf979801ca6@mail.gmail.com> References: <265910900907100006h459df233u776afcf979801ca6@mail.gmail.com> Message-ID: <20090710140755.GL27234@redhat.com> On Fri, Jul 10, 2009 at 05:06:31PM +1000, Mike Hughes wrote: > Hi Guys, > > I know you've all got LOTS of work to do pushing v1.0 out but would it be > possible for someone to update the main site (ovirt.org)? > > It still has 0.96 as the latest release, I think the installation > instructions are a bit out of date and it still references F10 as being the > required base system. > > Maybe just update the download page with a link to the newest versions and > do a quick install procedure for F11. > > Feel free to ignore this request if you've got higher priorities to deal > with ;-) > > Regards and respect, > > Mike Hughes Yeah, we really really have to do this... thanks for the reminder... --Hugh From mmorsi at redhat.com Fri Jul 10 14:32:36 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 10 Jul 2009 10:32:36 -0400 Subject: [Ovirt-devel] [PATCH server] UI for accumulated uptime for VMs. (revised3) In-Reply-To: <1246905118-19312-1-git-send-email-sseago@redhat.com> References: <1246905118-19312-1-git-send-email-sseago@redhat.com> Message-ID: <4A575104.4020309@redhat.com> Jay and I tested this and it looks good. ACK -Mo From dpierce at redhat.com Fri Jul 10 14:45:01 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 10:45:01 -0400 Subject: [Ovirt-devel] Includes suggestions from apevec... Message-ID: <1247237102-10303-1-git-send-email-dpierce@redhat.com> This obsoletes the last patch sent. From dpierce at redhat.com Fri Jul 10 14:45:02 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 10:45:02 -0400 Subject: [Ovirt-devel] [PATCH node] Provides a means to toggle SSH password auth from the firstboot menu. rhbz#509842 In-Reply-To: <1247237102-10303-1-git-send-email-dpierce@redhat.com> References: <1247237102-10303-1-git-send-email-dpierce@redhat.com> Message-ID: <1247237102-10303-2-git-send-email-dpierce@redhat.com> The password option now goes to a submenu. This submenu lets the user chose to either set the administrator password or else toggle SSH password authentication on or off. The submenu also reports whether password authentication is current enabled. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-password | 74 ++++++++++++++++++++++++++++++++++------ 1 files changed, 63 insertions(+), 11 deletions(-) diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password index 03b41e1..78ec5ba 100755 --- a/scripts/ovirt-config-password +++ b/scripts/ovirt-config-password @@ -37,14 +37,66 @@ function prompt_sasl_user { done } -printf "\n\n Password Configuration\n\n" - -# prompt user -# Set the password for the root user first -printf "\nSystem Administrator (root):\n" -unmount_config /etc/shadow -passwd root -ovirt_store_config /etc/shadow -printf "\nAdding users for libvirt remote access" -# TODO list existing users in /etc/libvirt/passwd.db -while prompt_sasl_user; do :; done +set_password () { + printf "\n\n Password Configuration\n\n" + + # prompt user + # Set the password for the root user first + printf "\nSystem Administrator (root):\n" + unmount_config /etc/shadow + passwd root + ovirt_store_config /etc/shadow + printf "\nAdding users for libvirt remote access" + # TODO list existing users in /etc/libvirt/passwd.db + while prompt_sasl_user; do :; done +} + +toggle_ssh_access () +{ + local allowed=$1 + local config=$WORKDIR/augeas-ssh + + if $allowed; then permit="yes"; else permit="no"; fi + augtool < This patchset removes ovirt-identify-node and ovirt-listen awake. It adds startup script support for the matahari qmf agent, which takes over the responsibility for communicating node hardware capabilities to the ovirt-server. On the server side, host-browser has had its node identification functionality replaced by a new script, host-register, which is what interfaces with the matahari qmf agent over the qpid bus. There are related patchsets to node-image and server that must be applied along with this set to test things out. Arjun Roy (3): Removed autotools, rpm and initscript targets related to ovirt-identify-node and ovirt-listen-awake. Removed ovirt-listen-awake and ovirt-identify-node. Added startup support for matahari qmf agent. Makefile.am | 2 +- configure.ac | 7 - ovirt-identify-node/.gitignore | 3 - ovirt-identify-node/AUTHOR | 1 - ovirt-identify-node/COPYING | 339 ---------------------------- ovirt-identify-node/ChangeLog | 2 - ovirt-identify-node/Makefile.am | 33 --- ovirt-identify-node/NEWS | 2 - ovirt-identify-node/README | 2 - ovirt-identify-node/comm.c | 85 ------- ovirt-identify-node/debug.c | 50 ---- ovirt-identify-node/gather.c | 319 -------------------------- ovirt-identify-node/hal_support.c | 61 ----- ovirt-identify-node/main.c | 248 -------------------- ovirt-identify-node/ovirt-identify-node.h | 139 ------------ ovirt-identify-node/protocol.c | 293 ------------------------ ovirt-listen-awake/.gitignore | 3 - ovirt-listen-awake/COPYING | 339 ---------------------------- ovirt-listen-awake/Makefile.am | 22 -- ovirt-listen-awake/ovirt-listen-awake.c | 225 ------------------ ovirt-listen-awake/ovirt-listen-awake.init | 49 ---- ovirt-node.spec.in | 13 - scripts/ovirt | 7 +- scripts/ovirt-install-node-stateful | 4 +- scripts/ovirt-post | 12 +- scripts/ovirt-uninstall-node-stateful | 1 - 26 files changed, 10 insertions(+), 2251 deletions(-) delete mode 100644 ovirt-identify-node/.gitignore delete mode 100644 ovirt-identify-node/AUTHOR delete mode 100644 ovirt-identify-node/COPYING delete mode 100644 ovirt-identify-node/ChangeLog delete mode 100644 ovirt-identify-node/Makefile.am delete mode 100644 ovirt-identify-node/NEWS delete mode 100644 ovirt-identify-node/README delete mode 100644 ovirt-identify-node/comm.c delete mode 100644 ovirt-identify-node/debug.c delete mode 100644 ovirt-identify-node/gather.c delete mode 100644 ovirt-identify-node/hal_support.c delete mode 100644 ovirt-identify-node/main.c delete mode 100644 ovirt-identify-node/ovirt-identify-node.h delete mode 100644 ovirt-identify-node/protocol.c delete mode 100644 ovirt-listen-awake/.gitignore delete mode 100644 ovirt-listen-awake/COPYING delete mode 100644 ovirt-listen-awake/Makefile.am delete mode 100644 ovirt-listen-awake/ovirt-listen-awake.c delete mode 100644 ovirt-listen-awake/ovirt-listen-awake.init From arroy at redhat.com Fri Jul 10 16:47:53 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:47:53 -0400 Subject: [Ovirt-devel] [PATCH: node 1/3] Removed autotools, rpm and initscript targets related to ovirt-identify-node and ovirt-listen-awake. In-Reply-To: <1247244475-11239-1-git-send-email-arroy@redhat.com> References: <1247244475-11239-1-git-send-email-arroy@redhat.com> Message-ID: <1247244475-11239-2-git-send-email-arroy@redhat.com> --- Makefile.am | 2 +- configure.ac | 7 ------- ovirt-node.spec.in | 13 ------------- scripts/ovirt-install-node-stateful | 4 +--- scripts/ovirt-post | 12 ++---------- scripts/ovirt-uninstall-node-stateful | 1 - 6 files changed, 4 insertions(+), 35 deletions(-) diff --git a/Makefile.am b/Makefile.am index 2f52144..0374f07 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,7 +16,7 @@ # also available at http://www.gnu.org/copyleft/gpl.html. OVIRT_CACHE_DIR ?= $(HOME)/ovirt-cache -SUBDIRS = ovirt-identify-node ovirt-listen-awake gptsync +SUBDIRS = gptsync EXTRA_DIST = \ .gitignore \ diff --git a/configure.ac b/configure.ac index 73aa0d9..d965a82 100644 --- a/configure.ac +++ b/configure.ac @@ -3,18 +3,11 @@ AM_INIT_AUTOMAKE([-Wall -Werror foreign -Wno-portability tar-pax]) AC_PROG_CC AC_CONFIG_HEADERS([config.h]) -# for ovirt-identify-node -PKG_CHECK_MODULES([DBUS], [dbus-1 >= 1.0.0]) -PKG_CHECK_MODULES([HAL], [hal >= 0.5.8]) -PKG_CHECK_MODULES([VIRT], [libvirt >= 0.4.4]) - # If using gcc and default CFLAGS, enable some warnings. test x"$ac_ct_CC:$CFLAGS" = 'xgcc:-g -O2' \ && CFLAGS="$CFLAGS -Wshadow -Wall -Werror" AC_CONFIG_FILES([Makefile - ovirt-identify-node/Makefile - ovirt-listen-awake/Makefile gptsync/Makefile ovirt-node.spec ]) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 6fa45ce..3138011 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -162,9 +162,6 @@ cd - %{__install} -D -m0755 scripts/ovirt-config-view-logs %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/persist %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/unpersist %{buildroot}%{_sbindir} -%{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} -%{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} -%{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake # gptsync %{__install} -p -m0755 gptsync/gptsync %{buildroot}%{_sbindir} @@ -259,12 +256,8 @@ fi %post stateful /sbin/chkconfig --add collectd -/sbin/chkconfig --add ovirt-listen-awake %preun stateful -if [ "$1" = 0 ] ; then - /sbin/chkconfig --del ovirt-listen-awake -fi %post selinux for selinuxvariant in %{selinux_variants}; do @@ -332,24 +325,18 @@ fi %files stateful %defattr(-,root,root,0755) -%{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node-stateful %{_sbindir}/ovirt-uninstall-node-stateful -%{_initrddir}/ovirt-listen-awake %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake -%{_sbindir}/ovirt-identify-node %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) %{_sysconfdir}/collectd.conf.in %{_sysconfdir}/chkconfig.d/collectd %config %attr(0644,root,root) %{_sysconfdir}/ovirt-release %config %attr(0644,root,root) %{_sysconfdir}/default/ovirt -%doc ovirt-identify-node/README ovirt-identify-node/NEWS -%doc ovirt-identify-node/AUTHOR ovirt-identify-node/ChangeLog -%doc ovirt-identify-node/COPYING %changelog * Thu Dec 11 2008 Perry Myers - 0.96 diff --git a/scripts/ovirt-install-node-stateful b/scripts/ovirt-install-node-stateful index 9426c81..d16b42d 100755 --- a/scripts/ovirt-install-node-stateful +++ b/scripts/ovirt-install-node-stateful @@ -69,7 +69,6 @@ backup_file /etc/hosts add_if_not_exist "192.168.50.1 $PHYS_HOST" /etc/hosts add_if_not_exist "192.168.50.2 $MGMT_HOST" /etc/hosts -chkconfig ovirt-listen-awake on chkconfig collectd on chkconfig libvirt-qpid on chkconfig iptables on @@ -89,7 +88,7 @@ backup_file /etc/sysconfig/iptables # We open up anything coming from ovirtbr0 to this node, since it # is only intended for demo purposes. For reference, here are the # ports that need to be opened: -# 7777:tcp (ovirt-listen-awake), 16509:tcp (libvirtd), 5900-6000:tcp (vnc), +# 16509:tcp (libvirtd), 5900-6000:tcp (vnc), # 49152-49216:tcp (libvirt migration) lokkit -q -t ovirtbr0 @@ -108,7 +107,6 @@ else fi service collectd restart -service ovirt-listen-awake restart service libvirt-qpid restart service ntpd stop service ntpdate start diff --git a/scripts/ovirt-post b/scripts/ovirt-post index 647b049..1fcfd6a 100755 --- a/scripts/ovirt-post +++ b/scripts/ovirt-post @@ -59,16 +59,8 @@ start() { /etc/libvirt/krb5.tab \ /etc/ssh/ssh_host*_key* - find_srv identify tcp - if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then - if [ -n "$OVIRT_BOOTIF" ]; then - ovirt-identify-node -s $SRV_HOST -p $SRV_PORT -m $OVIRT_BOOTIF - else - ovirt-identify-node -s $SRV_HOST -p $SRV_PORT - fi - else - log "skipping ovirt-identify-node, oVirt registration service not available" - fi + # Removed ovirt-identify-node since it has now + # been replaced with the matahari qmf agent. } case "$1" in diff --git a/scripts/ovirt-uninstall-node-stateful b/scripts/ovirt-uninstall-node-stateful index ea16a61..5dd03ba 100755 --- a/scripts/ovirt-uninstall-node-stateful +++ b/scripts/ovirt-uninstall-node-stateful @@ -28,7 +28,6 @@ unbackup_file() { test -f "$OVIRT_BACKUP_DIR$1" && cp -pf "$OVIRT_BACKUP_DIR$1" "$1" } -chkconfig ovirt-listen-awake off chkconfig collectd off unbackup_file /etc/sysconfig/network -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 16:47:55 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:47:55 -0400 Subject: [Ovirt-devel] [PATCH: node 3/3] Added startup support for matahari qmf agent. In-Reply-To: <1247244475-11239-3-git-send-email-arroy@redhat.com> References: <1247244475-11239-1-git-send-email-arroy@redhat.com> <1247244475-11239-2-git-send-email-arroy@redhat.com> <1247244475-11239-3-git-send-email-arroy@redhat.com> Message-ID: <1247244475-11239-4-git-send-email-arroy@redhat.com> The matahari qmf agent replaces functionality in ovirt-identify-node, by listing all relevant node hardware information to the amqp broker on the ovirt server. --- scripts/ovirt | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt b/scripts/ovirt index 7490326..4ff03f2 100755 --- a/scripts/ovirt +++ b/scripts/ovirt @@ -66,8 +66,13 @@ start() { echo "LIBVIRT_QPID_ARGS=\"--broker $SRV_HOST --port $SRV_PORT\"" >> $libvirt_qpid_conf echo "/usr/kerberos/bin/kinit -k -t /etc/libvirt/krb5.tab qpidd/`hostname`" >> $libvirt_qpid_conf fi + matahari_conf=/etc/sysconfig/matahari + if [ -f $matahari_conf ]; then + echo "MATAHARI_ARGS=\"--broker $SRV_HOST --port $SRV_PORT\"" >> $matahari_conf + echo "/usr/kerberos/bin/kinit -k -t /etc/libvirt/krb5.tab qpidd/`hostname`" >> $matahari_conf + fi else - log "skipping libvirt-qpid configuration, could not find $libvirt_qpid_conf" + log "skipping libvirt-qpid and matahari configuration, could not find $libvirt_qpid_conf" fi } -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 16:47:54 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:47:54 -0400 Subject: [Ovirt-devel] [PATCH: node 2/3] Removed ovirt-listen-awake and ovirt-identify-node. In-Reply-To: <1247244475-11239-2-git-send-email-arroy@redhat.com> References: <1247244475-11239-1-git-send-email-arroy@redhat.com> <1247244475-11239-2-git-send-email-arroy@redhat.com> Message-ID: <1247244475-11239-3-git-send-email-arroy@redhat.com> --- ovirt-identify-node/.gitignore | 3 - ovirt-identify-node/AUTHOR | 1 - ovirt-identify-node/COPYING | 339 ---------------------------- ovirt-identify-node/ChangeLog | 2 - ovirt-identify-node/Makefile.am | 33 --- ovirt-identify-node/NEWS | 2 - ovirt-identify-node/README | 2 - ovirt-identify-node/comm.c | 85 ------- ovirt-identify-node/debug.c | 50 ---- ovirt-identify-node/gather.c | 319 -------------------------- ovirt-identify-node/hal_support.c | 61 ----- ovirt-identify-node/main.c | 248 -------------------- ovirt-identify-node/ovirt-identify-node.h | 139 ------------ ovirt-identify-node/protocol.c | 293 ------------------------ ovirt-listen-awake/.gitignore | 3 - ovirt-listen-awake/COPYING | 339 ---------------------------- ovirt-listen-awake/Makefile.am | 22 -- ovirt-listen-awake/ovirt-listen-awake.c | 225 ------------------ ovirt-listen-awake/ovirt-listen-awake.init | 49 ---- 19 files changed, 0 insertions(+), 2215 deletions(-) delete mode 100644 ovirt-identify-node/.gitignore delete mode 100644 ovirt-identify-node/AUTHOR delete mode 100644 ovirt-identify-node/COPYING delete mode 100644 ovirt-identify-node/ChangeLog delete mode 100644 ovirt-identify-node/Makefile.am delete mode 100644 ovirt-identify-node/NEWS delete mode 100644 ovirt-identify-node/README delete mode 100644 ovirt-identify-node/comm.c delete mode 100644 ovirt-identify-node/debug.c delete mode 100644 ovirt-identify-node/gather.c delete mode 100644 ovirt-identify-node/hal_support.c delete mode 100644 ovirt-identify-node/main.c delete mode 100644 ovirt-identify-node/ovirt-identify-node.h delete mode 100644 ovirt-identify-node/protocol.c delete mode 100644 ovirt-listen-awake/.gitignore delete mode 100644 ovirt-listen-awake/COPYING delete mode 100644 ovirt-listen-awake/Makefile.am delete mode 100644 ovirt-listen-awake/ovirt-listen-awake.c delete mode 100644 ovirt-listen-awake/ovirt-listen-awake.init diff --git a/ovirt-identify-node/.gitignore b/ovirt-identify-node/.gitignore deleted file mode 100644 index 0b2c3bd..0000000 --- a/ovirt-identify-node/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -.deps -ovirt-identify-node diff --git a/ovirt-identify-node/AUTHOR b/ovirt-identify-node/AUTHOR deleted file mode 100644 index d37a107..0000000 --- a/ovirt-identify-node/AUTHOR +++ /dev/null @@ -1 +0,0 @@ -Darryl L. Pierce diff --git a/ovirt-identify-node/COPYING b/ovirt-identify-node/COPYING deleted file mode 100644 index d511905..0000000 --- a/ovirt-identify-node/COPYING +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/ovirt-identify-node/ChangeLog b/ovirt-identify-node/ChangeLog deleted file mode 100644 index 61dc8cc..0000000 --- a/ovirt-identify-node/ChangeLog +++ /dev/null @@ -1,2 +0,0 @@ -* Tue 17 Jun 2008 Darryl L. Pierce - 0.0.1-1 -- Created the initial RPM structure. diff --git a/ovirt-identify-node/Makefile.am b/ovirt-identify-node/Makefile.am deleted file mode 100644 index 980901c..0000000 --- a/ovirt-identify-node/Makefile.am +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2008 Red Hat, Inc. -# -# 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. - -bin_PROGRAMS = ovirt-identify-node - -EXTRA_DIST = AUTHOR - -ovirt_identify_node_SOURCES = \ - ovirt-identify-node.h \ - comm.c \ - gather.c \ - hal_support.c \ - main.c \ - protocol.c - -ovirt_identify_node_LDADD = \ - $(LDADD) $(DBUS_LIBS) $(HAL_LIBS) $(VIRT_LIBS) -ovirt_identify_node_CPPFLAGS = \ - $(AM_CPPFLAGS) $(DBUS_CFLAGS) $(HAL_CFLAGS) $(VIRT_CFLAGS) diff --git a/ovirt-identify-node/NEWS b/ovirt-identify-node/NEWS deleted file mode 100644 index 139597f..0000000 --- a/ovirt-identify-node/NEWS +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/ovirt-identify-node/README b/ovirt-identify-node/README deleted file mode 100644 index 139597f..0000000 --- a/ovirt-identify-node/README +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/ovirt-identify-node/comm.c b/ovirt-identify-node/comm.c deleted file mode 100644 index ee5e174..0000000 --- a/ovirt-identify-node/comm.c +++ /dev/null @@ -1,85 +0,0 @@ - -/* comm.c -- Contains communications routines. - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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 "ovirt-identify-node.h" - -ssize_t -saferead(int fd, char *buf, size_t count) -{ - ssize_t bytes, offset; - - int len_left; - - int done = 0; - - offset = 0; - - len_left = count; - - DEBUG("Begin saferead(%d, %p, %zd)\n", fd, buf, count); - - while (!done) { - DEBUG("Before read(%d,%p,%d)\n", fd, buf + offset, len_left); - - bytes = read(fd, buf + offset, len_left); - - DEBUG("After read: bytes=%zd\n", bytes); - - if (bytes == 0) { - done = 1; - } else if (bytes > 0) { - offset += bytes; - len_left -= bytes; - done = 1; - } else if (errno == EINTR) { - continue; - } else { - done = 1; - } - - DEBUG("End of decision loop: offset=%zd, len_left=%dl, done=%d\n", - offset, len_left, done); - } - - return offset; -} - -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; -} diff --git a/ovirt-identify-node/debug.c b/ovirt-identify-node/debug.c deleted file mode 100644 index 36a7f59..0000000 --- a/ovirt-identify-node/debug.c +++ /dev/null @@ -1,50 +0,0 @@ - -/* debug.c -- Debugging methods. - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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 "ovirt-identify-node.h" - -void -debug_cpu_info(void) -{ - fprintf(stdout, "Node Info:\n"); - fprintf(stdout, " UUID: %s\n", uuid); - fprintf(stdout, " Arch: %s\n", arch); - fprintf(stdout, " Memory: %s\n", memsize); - - cpu_info_ptr current = cpu_info; - - while (current != NULL) { - fprintf(stdout, "\n"); - fprintf(stdout, " CPU Number: %s\n", current->cpu_num); - fprintf(stdout, " Core Number: %s\n", current->core_num); - fprintf(stdout, "Number of Cores: %s\n", current->number_of_cores); - fprintf(stdout, " Vendor: %s\n", current->vendor); - fprintf(stdout, " Model: %s\n", current->model); - fprintf(stdout, " Family: %s\n", current->family); - fprintf(stdout, " CPUID Level: %s\n", current->cpuid_level); - fprintf(stdout, " CPU Speed: %s\n", current->speed); - fprintf(stdout, " Cache Size: %s\n", current->cache); - fprintf(stdout, " CPU Flags: %s\n", current->flags); - - current = current->next; - } -} diff --git a/ovirt-identify-node/gather.c b/ovirt-identify-node/gather.c deleted file mode 100644 index ae0bb73..0000000 --- a/ovirt-identify-node/gather.c +++ /dev/null @@ -1,319 +0,0 @@ - -/* gather.c -- Contains methods for collecting data about the system. - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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 "ovirt-identify-node.h" - -struct sockaddr_in sa; - -#define inaddrr(x) (*(struct in_addr *) &ifr.x[sizeof sa.sin_port]) - -int -init_gather(void) -{ - int result = 1; - - hal_ctx = get_hal_ctx(); - - if (hal_ctx != NULL) { - result = 0; - } - - return result; -} - -int -get_uuid(void) -{ - const char *udi = "/org/freedesktop/Hal/devices/computer"; - - const char *key = "system.hardware.uuid"; - - VERBOSE("Getting system UUID.\n"); - - int result = 1; - - int type; - - type = libhal_device_get_property_type(hal_ctx, udi, key, &dbus_error); - - if (type == LIBHAL_PROPERTY_TYPE_STRING) { - char *value; - - DEBUG("%s/%s=%d\n", udi, key, type); - - value = - libhal_device_get_property_string(hal_ctx, udi, key, - &dbus_error); - snprintf(uuid, BUFFER_LENGTH, "%s", value); - - DEBUG("UUID=%s\n", uuid); - - result = 0; - } else { - uuid[0] = 0; - } - - return result; -} - -/* Creates a new instance of type t_cpu_info and places it into the - * linked list of CPUs. - */ -cpu_info_ptr -create_cpu_info(void) -{ - cpu_info_ptr result = calloc(1, sizeof(t_cpu_info)); - - bzero(result, sizeof(t_cpu_info)); - - strcpy(result->core_num, "0"); - strcpy(result->number_of_cores, "1"); - - return result; -} - -int -get_cpu_info(void) -{ - int result = 1; - - FILE *inputfd; - - cpu_info_ptr current = NULL; - - /* in order to support Xen, this data will need to be gathered - * from libvirt rather than directly from cpuinfo - */ - if ((inputfd = fopen("/proc/cpuinfo", "rb")) != NULL) { - VERBOSE("Parsing CPU information\n"); - do { - char buffer[255]; - - char label[BUFFER_LENGTH]; - - char value[BUFFER_LENGTH]; - - fgets(buffer, 255, inputfd); - if (strlen(buffer) > 0) - buffer[strlen(buffer) - 1] = '\0'; - - get_label_and_value(buffer, - label, BUFFER_LENGTH, - value, BUFFER_LENGTH); - - DEBUG("label=\"%s\", value=\"%s\"\n", label, value); - - if (strlen(label)) { - if (!strcmp(label, "processor")) { - VERBOSE("Starting new CPU\n"); - - cpu_info_ptr last = current; - - current = create_cpu_info(); - if (last != NULL) { - last->next = current; - } else { - cpu_info = current; - } - - COPY_VALUE_TO_BUFFER(value, current->cpu_num, - BUFFER_LENGTH); - } else - /* core id and number of cores is not correct on - * Xen machines - */ - if (!strcmp(label, "core id")) { - COPY_VALUE_TO_BUFFER(value, current->core_num, - BUFFER_LENGTH); - } else if (!strcmp(label, "cpu cores")) { - COPY_VALUE_TO_BUFFER(value, current->number_of_cores, - BUFFER_LENGTH); - } else if (!strcmp(label, "vendor_id")) { - COPY_VALUE_TO_BUFFER(value, current->vendor, - BUFFER_LENGTH); - } else if (!strcmp(label, "model")) { - COPY_VALUE_TO_BUFFER(value, current->model, - BUFFER_LENGTH); - } else if (!strcmp(label, "cpu family")) { - COPY_VALUE_TO_BUFFER(value, current->family, - BUFFER_LENGTH); - } else if (!strcmp(label, "cpuid level")) { - COPY_VALUE_TO_BUFFER(value, current->cpuid_level, - BUFFER_LENGTH); - } else if (!strcmp(label, "cpu MHz")) { - COPY_VALUE_TO_BUFFER(value, current->speed, - BUFFER_LENGTH); - } else if (!strcmp(label, "cache size")) { - COPY_VALUE_TO_BUFFER(value, current->cache, - BUFFER_LENGTH); - } else if (!strcmp(label, "flags")) { - COPY_VALUE_TO_BUFFER(value, current->flags, - BUFFER_LENGTH); - } - } - - } while (!feof(inputfd)); - - fclose(inputfd); - - result = 0; - } else { - VERBOSE("Unable to open /proc/cpuinfo\n"); - } - - return result; -} - -/* Creates a new instance of type t_nic_info. - */ -nic_info_ptr -create_nic_info(void) -{ - nic_info_ptr result = calloc(1, sizeof(t_nic_info)); - - bzero(result, sizeof(t_nic_info)); - - return result; -} - -/* Determines the speed of the network interface. - */ -void -get_nic_data(char *nic, nic_info_ptr nic_info_p) -{ - char *interface; - - struct ifreq ifr; - - int sockfd; - - struct ethtool_cmd ecmd; - - interface = - libhal_device_get_property_string(hal_ctx, nic, "net.interface", - &dbus_error); - snprintf(nic_info_p->iface_name, BUFFER_LENGTH, "%s", interface); - bzero(&ifr, sizeof(struct ifreq)); - - sockfd = socket(AF_INET, SOCK_DGRAM, 0); - if (sockfd >= 0) { - int bandwidth; - - ifr.ifr_addr.sa_family = AF_INET; - strncpy(ifr.ifr_name, interface, IFNAMSIZ - 1); - - if(!ioctl(sockfd,SIOCGIFADDR,&ifr)) { - - DEBUG("ip_address=%s\n",inet_ntoa(inaddrr(ifr_addr.sa_data))); - - snprintf(nic_info_p->ip_address, BUFFER_LENGTH, "%s", - inet_ntoa(inaddrr(ifr_addr.sa_data))); - } - - if(!ioctl(sockfd,SIOCGIFNETMASK,&ifr) && - strcmp("255.255.255.255", inet_ntoa(inaddrr(ifr_addr.sa_data)))) { - - DEBUG("netmask=%s",inet_ntoa(inaddrr(ifr_addr.sa_data))); - - snprintf(nic_info_p->netmask, BUFFER_LENGTH, "%s", - inet_ntoa(inaddrr(ifr_addr.sa_data))); - } - - if(!ioctl(sockfd,SIOCGIFBRDADDR,&ifr)) { - - DEBUG("broadcast=%s",inet_ntoa(inaddrr(ifr_addr.sa_data))); - - snprintf(nic_info_p->broadcast, BUFFER_LENGTH, "%s", - inet_ntoa(inaddrr(ifr_addr.sa_data))); - } - - ecmd.cmd = ETHTOOL_GSET; - ifr.ifr_data = (caddr_t)&ecmd; - ioctl(sockfd, SIOCETHTOOL, &ifr); - - bandwidth = 10; - if (ecmd.supported & SUPPORTED_10000baseT_Full) - bandwidth = 10000; - else if (ecmd.supported & SUPPORTED_2500baseX_Full) - bandwidth = 2500; - else if (ecmd.supported & (SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full)) - bandwidth = 1000; - else if (ecmd.supported & (SUPPORTED_100baseT_Half | - SUPPORTED_100baseT_Full)) - bandwidth = 100; - else if (ecmd.supported & (SUPPORTED_10baseT_Half | - SUPPORTED_10baseT_Full)) - bandwidth = 10; - - snprintf(nic_info_p->bandwidth, BUFFER_LENGTH, "%d", bandwidth); - - close(sockfd); - } -} - -int -get_nic_info(void) -{ - int result = 0; - - nic_info_ptr current = NULL; - - nic_info_ptr last = NULL; - - char **nics; - - int num_results; - - int i; - - nics = libhal_find_device_by_capability(hal_ctx, "net.80203", - &num_results, &dbus_error); - - DEBUG("Found %d NICs\n", num_results); - - for (i = 0; i < num_results; i++) { - char *nic = nics[i]; - - DEBUG("Starting new NIC; %s.\n", nic); - - if (current != NULL) { - last = current; - current = create_nic_info(); - last->next = current; - } else { - nic_info = current = create_nic_info(); - } - - snprintf(current->mac_address, BUFFER_LENGTH, "%s", - libhal_device_get_property_string(hal_ctx, nic, - "net.address", - &dbus_error)); - get_nic_data(nic, current); - - DEBUG("NIC details: MAC:%s, speed:%s, IP:%s\n", - current->mac_address, current->bandwidth, - current->ip_address); - } - - return result; -} diff --git a/ovirt-identify-node/hal_support.c b/ovirt-identify-node/hal_support.c deleted file mode 100644 index 1b1d0ca..0000000 --- a/ovirt-identify-node/hal_support.c +++ /dev/null @@ -1,61 +0,0 @@ - -/* hal_support.c -- Interfaces with the HAL libraries. - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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 "ovirt-identify-node.h" - -DBusConnection *dbus_connection; - -DBusError dbus_error; - -LibHalContext * -get_hal_ctx(void) -{ - LibHalContext *result = NULL; - - LibHalContext *ctx; - - ctx = libhal_ctx_new(); - if (ctx != NULL) { - dbus_error_init(&dbus_error); - dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error); - - if (!dbus_error_is_set(&dbus_error)) { - libhal_ctx_set_dbus_connection(ctx, dbus_connection); - - if (libhal_ctx_init(ctx, &dbus_error)) { - result = ctx; - } else { - fprintf(stderr, - "Failed to initial libhal context: %s : %s\n", - dbus_error.name, dbus_error.message); - } - } else { - fprintf(stderr, "Unable to connect to system bus: %s : %s\n", - dbus_error.name, dbus_error.message); - dbus_error_free(&dbus_error); - } - } else { - fprintf(stderr, "Unable to initialize HAL context.\n"); - } - - return result; -} diff --git a/ovirt-identify-node/main.c b/ovirt-identify-node/main.c deleted file mode 100644 index 400ea54..0000000 --- a/ovirt-identify-node/main.c +++ /dev/null @@ -1,248 +0,0 @@ - -/* identify-node -- Main entry point for the identify-node utility. - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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 "ovirt-identify-node.h" - -int debug = 0; - -int verbose = 0; - -int testing = 0; - -char arch[BUFFER_LENGTH]; - -char uuid[BUFFER_LENGTH]; - -char memsize[BUFFER_LENGTH]; - -char numcpus[BUFFER_LENGTH]; - -char cpuspeed[BUFFER_LENGTH]; - -char *hostname; - -int hostport = -1; - -char *management_interface; - -int socketfd; - -cpu_info_ptr cpu_info; - -nic_info_ptr nic_info; - -LibHalContext *hal_ctx; - -int -main(int argc, char **argv) -{ - int result = 1; - - virConnectPtr connection; - - virNodeInfo info; - - fprintf(stdout, "Sending oVirt Node details to server.\n"); - - if (!config(argc, argv)) { - VERBOSE("Connecting to libvirt.\n"); - - connection = - virConnectOpenReadOnly(testing ? "test:///default" : NULL); - - DEBUG("connection=%p\n", connection); - - if (connection) { - VERBOSE("Retrieving node information.\n"); - if (!virNodeGetInfo(connection, &info)) { - snprintf(arch, BUFFER_LENGTH, "%s", info.model); - snprintf(memsize, BUFFER_LENGTH, "%ld", info.memory); - - cpu_info = NULL; - nic_info = NULL; - - if (!init_gather() && !get_uuid() && !get_cpu_info() - && !get_nic_info()) { - if (!start_conversation() && !send_details() - && !end_conversation()) { - fprintf(stdout, "Finished!\n"); - result = 0; - } - } else { - VERBOSE("Failed to get CPU info.\n"); - } - } else { - VERBOSE("Failed to get node info.\n"); - } - } else { - VERBOSE("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:m:dvth")) != -1) { - DEBUG("Processing argument: %c (optarg:%s)\n", option, optarg); - - switch (option) { - case 's': - hostname = optarg; - break; - case 'p': - hostport = atoi(optarg); - break; - case 'm': - management_interface = 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-m [iface]\t\tThe management interface.\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"); -} - -void -get_label_and_value(char *text, - char *label, size_t label_length, - char *value, size_t value_length) -{ - int offset = 0; - - int which = 0; /* 0 = label, 1 = value */ - - char *current = text; - - /* iterate through the text supplied and find where the - * label ends with a colon, then copy that into the supplied - * label buffer and trim any trailing spaces - */ - - while (current != NULL && *current != '\0') { - /* if we're on the separator, then switch modes and reset - * the offset indicator, otherwise just process the character - */ - if (which == 0 && *current == ':') { - which = 1; - offset = 0; - } else { - char *buffer = (which == 0 ? label : value); - - int length = (which == 0 ? label_length : value_length); - - /* only copy if we're past the first character and it's not - * a space - */ - if ((offset > 0 || (*current != 9 && *current != ' ')) - && offset < (length - 1)) { - buffer[offset++] = *current; - buffer[offset] = 0; - } - } - - current++; - } - - /* now trim all trailing spaces from the values */ - while (label[strlen(label) - 1] == 9) - label[strlen(label) - 1] = 0; - while (value[strlen(value) - 1] == 9) - value[strlen(value) - 1] = 0; -} - -int -get_text(const char *const expected) -{ - int result = 1; - - int received; - - char buffer[BUFFER_LENGTH]; - - bzero(buffer, BUFFER_LENGTH); - - VERBOSE("Looking to receive %s\n", expected); - - received = saferead(socketfd, buffer, BUFFER_LENGTH); - - buffer[received - 1] = 0; - - VERBOSE("Received \"%s\": size=%d (trimmed ending carriage return)\n", - buffer, received); - - result = strcmp(expected, buffer); - - return result; -} diff --git a/ovirt-identify-node/ovirt-identify-node.h b/ovirt-identify-node/ovirt-identify-node.h deleted file mode 100644 index bf439bd..0000000 --- a/ovirt-identify-node/ovirt-identify-node.h +++ /dev/null @@ -1,139 +0,0 @@ -/* Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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. - */ - -#ifndef __OVIRT_IDENTIFY_NODE_H -#define __OVIRT_IDENTIFY_NODE_H - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#define BUFFER_LENGTH 768 - -typedef struct _cpu_info { - char cpu_num[BUFFER_LENGTH]; - char core_num[BUFFER_LENGTH]; - char number_of_cores[BUFFER_LENGTH]; - char vendor[BUFFER_LENGTH]; - char model[BUFFER_LENGTH]; - char family[BUFFER_LENGTH]; - char cpuid_level[BUFFER_LENGTH]; - char speed[BUFFER_LENGTH]; - char cache[BUFFER_LENGTH]; - char flags[BUFFER_LENGTH]; - struct _cpu_info* next; -} t_cpu_info; - -typedef t_cpu_info* cpu_info_ptr; - -typedef struct _nic_info { - char mac_address[BUFFER_LENGTH]; - char bandwidth[BUFFER_LENGTH]; - char ip_address[BUFFER_LENGTH]; - char netmask[BUFFER_LENGTH]; - char iface_name[BUFFER_LENGTH]; - char broadcast[BUFFER_LENGTH]; - struct _nic_info* next; -} t_nic_info; - -typedef t_nic_info* nic_info_ptr; - -int config(int argc,char** argv); -void usage(void); - -void get_label_and_value(char* text, - char* label,size_t label_length, - char* value,size_t value_length); - -int send_text(char* text); -int get_text(const char *const expected); - -/* comm.c */ -ssize_t saferead(int fd, char *buf, size_t count); -ssize_t safewrite(int fd, const void *buf, size_t count); - -/* debug.c */ -void debug_cpu_info(void); - -/* gather.c */ -int init_gather(void); -int get_uuid(void); -int get_cpu_info(void); -int get_nic_info(void); - -/* hal_support.c */ -LibHalContext* get_hal_ctx(void); - -/* protocol.c */ -int create_connection(void); -int start_conversation(void); -int send_details(void); -int end_conversation(void); -int send_value(char* label,char* value); -int send_text(char* text); - -/* variables */ -extern int debug; -extern int verbose; -extern int testing; - -extern char arch[BUFFER_LENGTH]; -extern char uuid[BUFFER_LENGTH]; -extern char memsize[BUFFER_LENGTH]; -extern char numcpus[BUFFER_LENGTH]; -extern char cpuspeed[BUFFER_LENGTH]; -extern char *hostname; -extern int hostport; -extern char *management_interface; -extern int socketfd; -extern cpu_info_ptr cpu_info; -extern nic_info_ptr nic_info; - -extern DBusConnection* dbus_connection; -extern DBusError dbus_error; -extern LibHalContext* hal_ctx; - -/* macros */ -#define DEBUG(arg...) if(debug) fprintf(stderr, ##arg) -#define VERBOSE(arg...) if(verbose) fprintf(stdout, ##arg) -#define COPY_VALUE_TO_BUFFER(value,buffer,length) \ - snprintf(buffer,length,"%s",value) - -#endif diff --git a/ovirt-identify-node/protocol.c b/ovirt-identify-node/protocol.c deleted file mode 100644 index 1099ebb..0000000 --- a/ovirt-identify-node/protocol.c +++ /dev/null @@ -1,293 +0,0 @@ - -/* protocol.c -- Manages the communication between the oVirt Node and - * the oVirt Server. - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Darryl L. Pierce - * - * 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 "ovirt-identify-node.h" - -int -create_connection(void) -{ - int result = 1; - - struct addrinfo hints; - - struct addrinfo *results; - - char port[6]; - - struct addrinfo *rptr; - - VERBOSE("Creating the socket connection.\n"); - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = 0; - hints.ai_protocol = 0; - - VERBOSE("Searching for host candidates.\n"); - - snprintf(port, 6, "%d", hostport); - - if (!getaddrinfo(hostname, port, &hints, &results)) { - VERBOSE - ("Got address information. Searching for a proper entry.\n"); - - 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 - VERBOSE("Invalid connection.\n"); - close(socketfd); - } - - if (rptr == NULL) { - VERBOSE("Unable to connect to server %s:%d\n", hostname, - hostport); - } else { - // success - result = 0; - } - - freeaddrinfo(results); - } else { - VERBOSE("No hosts found. Exiting...\n"); - } - - DEBUG("create_connection: result=%d\n", result); - - return result; -} - -int -start_conversation(void) -{ - int result = 1; - - VERBOSE("Starting conversation with %s:%d.\n", hostname, hostport); - - if (!create_connection()) { - VERBOSE("Connected.\n"); - - if (!get_text("HELLO?")) { - VERBOSE("Checking for handshake.\n"); - - if (!send_text("HELLO!")) { - VERBOSE("Handshake received. Starting conversation.\n"); - - if (!get_text("MODE?")) { - VERBOSE("Shifting to IDENTIFY mode.\n"); - - if (!send_text("IDENTIFY")) - result = 0; - } else { - VERBOSE("Was not asked for a mode.\n"); - } - } - } else { - VERBOSE("Did not receive a proper handshake.\n"); - } - } - - else { - VERBOSE("Did not get a connection.\n"); - } - - DEBUG("start_conversation: result=%d\n", result); - - return result; -} - -/* Transmits the CPU details to the server. - */ -int -send_cpu_details(void) -{ - int result = 1; - - cpu_info_ptr current = cpu_info; - - while (current != NULL) { - send_text("CPU"); - - if (!(get_text("CPUINFO?")) && - (!send_value("CPUNUM", current->cpu_num)) && - (!send_value("CORENUM", current->core_num)) && - (!send_value("NUMCORES", current->number_of_cores)) && - (!send_value("VENDOR", current->vendor)) && - (!send_value("MODEL", current->model)) && - (!send_value("FAMILY", current->family)) && - (!send_value("CPUIDLVL", current->cpuid_level)) && - (!send_value("SPEED", current->speed)) && - (!send_value("CACHE", current->cache)) && - (!send_value("FLAGS", current->flags))) { - send_text("ENDCPU"); - result = get_text("ACK CPU"); - } - - current = current->next; - } - - - return result; -} - -/* Transmits the NIC details to the server. - */ -int -send_nic_details(void) -{ - int result = 1; - - nic_info_ptr current = nic_info; - - /* only send NIC details if we found NICs to process */ - if(current) { - int sent_count = 0; - while (current != NULL) { - if((!management_interface) || (strcmp(management_interface, current->iface_name))) { - send_text("NIC"); - - if (!(get_text("NICINFO?")) && - (!send_value("MAC", current->mac_address)) && - (!send_value("BANDWIDTH", current->bandwidth)) && - (!send_value("IFACE_NAME", current->iface_name)) && - (!send_value("IP_ADDRESS", current->ip_address)) && - (!send_value("NETMASK", current->netmask)) && - (!send_value("BROADCAST", current->broadcast))) { - send_text("ENDNIC"); - result = get_text("ACK NIC"); - sent_count++; - } - - current = current->next; - } else { - current = current->next; - } - } - - /* if no nics were sent, then set default success */ - if( sent_count == 0) - result = 0; - - } else { result = 0; } - - return result; -} - -int -send_details(void) -{ - int result = 1; - - VERBOSE("Sending node details.\n"); - - if (!get_text("INFO?")) { - if ((!send_value("ARCH", arch)) && - (!send_value("UUID", uuid)) && - (!send_value("MEMSIZE", memsize)) && - (!send_cpu_details() && !send_nic_details())) { - if (!send_text("ENDINFO")) - result = 0; - } - } else { - VERBOSE("Was not interrogated for hardware info.\n"); - } - - return result; -} - -int -end_conversation(void) -{ - int result = 0; - - VERBOSE("Ending conversation.\n"); - - send_text("ENDINFO"); - - close(socketfd); - - return result; -} - -int -send_value(char *label, char *value) -{ - char buffer[BUFFER_LENGTH]; - - int result = 1; - - char expected[BUFFER_LENGTH]; - - if (value != NULL && strlen(value) > 0) { - - snprintf(buffer, BUFFER_LENGTH, "%s=%s", label, value); - - if (!send_text(buffer)) { - snprintf(expected, BUFFER_LENGTH, "ACK %s", label); - - VERBOSE("Expecting \"%s\"\n", expected); - - result = get_text(expected); - } - } else { result = 0; } - - return result; -} - -int -send_text(char *text) -{ - int result = 1; - - int sent; - - VERBOSE("Sending: \"%s\"\n", text); - - sent = safewrite(socketfd, text, strlen(text)); - sent += safewrite(socketfd, "\n", 1); - - if (sent >= 0) { - DEBUG("Sent %d bytes total.\n", sent); - - result = 0; - } - - return result; -} diff --git a/ovirt-listen-awake/.gitignore b/ovirt-listen-awake/.gitignore deleted file mode 100644 index f409bf5..0000000 --- a/ovirt-listen-awake/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.o -.deps -ovirt-listen-awake diff --git a/ovirt-listen-awake/COPYING b/ovirt-listen-awake/COPYING deleted file mode 100644 index d511905..0000000 --- a/ovirt-listen-awake/COPYING +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/ovirt-listen-awake/Makefile.am b/ovirt-listen-awake/Makefile.am deleted file mode 100644 index 783f15c..0000000 --- a/ovirt-listen-awake/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (C) 2008 Red Hat, Inc. -# -# 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. - -bin_PROGRAMS = ovirt-listen-awake - -EXTRA_DIST = ovirt-listen-awake.init - -ovirt_listen_awake_SOURCES = ovirt-listen-awake.c diff --git a/ovirt-listen-awake/ovirt-listen-awake.c b/ovirt-listen-awake/ovirt-listen-awake.c deleted file mode 100644 index 9af5bbf..0000000 --- a/ovirt-listen-awake/ovirt-listen-awake.c +++ /dev/null @@ -1,225 +0,0 @@ -/* ovirt-listen-awake: daemon to listen for and respond to AWAKE requests - * - * Copyright (C) 2008 Red Hat, Inc. - * Written by Chris Lalancette - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// We only ever expect to receive the strings "AWAKE" or "IDENTIFY", so -// 20 bytes is more than enough -#define BUFLEN 20 -#define LOGFILE "/var/log/ovirt-host.log" - -static int streq(char *first, char *second) -{ - int first_len, second_len; - - first_len = strlen(first); - second_len = strlen(second); - - if (first_len == second_len && strncmp(first, second, first_len) == 0) - return 1; - return 0; -} - -// Given a buffer, find the first whitespace character, replace it with \0, -// and return -static void find_first_whitespace(char *buffer) -{ - int i; - - i = 0; - while (buffer[i] != '\0') { - if (isspace(buffer[i])) { - // once we see the first whitespace character, we are done - buffer[i] ='\0'; - break; - } - i++; - } -} - -static int listenSocket(int listen_port) -{ - struct sockaddr_in a; - int s,yes; - - if (listen_port < 0) { - error(0, 0, "Invalid listen_port %d", listen_port); - return -1; - } - - s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { - error(0, errno, "Failed creating socket"); - return -1; - } - - yes = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,(char *)&yes, sizeof (yes)) < 0) { - error(0, errno, "Failed setsockopt"); - close(s); - return -1; - } - - memset (&a, 0, sizeof (a)); - a.sin_port = htons (listen_port); - a.sin_family = AF_INET; - if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) { - error(0, errno, "Error binding to port %d", listen_port); - close(s); - return -1; - } - - if (listen(s, 10) < 0) { - error(0, errno, "Error listening on port %d", listen_port); - close(s); - return -1; - } - return s; -} - -static void usage(int exitcode) -{ - printf("Usage: ovirt-listen-awake [OPTIONS]\n"); - printf("OPTIONS:\n"); - printf(" -h\t\tShow this help message\n"); - printf(" -n\t\tDo not daemonize (useful for debugging)\n"); - exit(exitcode); -} - -int main(int argc, char *argv[]) -{ - int listen_socket, conn; - struct sockaddr_in client_address; - unsigned int addrlen; - FILE *conn_stream; - FILE *logfile; - char buffer[BUFLEN]; - int c; - int do_daemon; - int i; - - do_daemon = 1; - - while ((c=getopt(argc, argv, ":hn")) != -1) { - switch(c) { - case 'h': - usage(0); - break; - case 'n': - do_daemon = 0; - break; - default: - usage(1); - } - } - - if ((argc-optind) != 0) { - for (i = optind; i < argc ; i ++) { - fprintf(stderr, "Extra operand %s\n", argv[i]); - } - error(1, 0, "Try --help for more information"); - } - - listen_socket = listenSocket(7777); - if (listen_socket < 0) - return 2; - - if (do_daemon) { - logfile = fopen(LOGFILE,"a+"); - if (logfile == NULL) - error(3, errno, "Error opening logfile %s", LOGFILE); - - // NOTE: this closes stdout and stderr - if (daemon(0, 0) < 0) - error(4, errno, "Error daemonizing"); - - // so re-open them to the logfile here - dup2(fileno(logfile), STDOUT_FILENO); - dup2(fileno(logfile), STDERR_FILENO); - } - - while (1) { - addrlen = sizeof(client_address); - memset(&client_address, 0, addrlen); - memset(buffer, 0, BUFLEN); - conn = accept(listen_socket, (struct sockaddr *)&client_address, &addrlen); - if (conn < 0) { - error(0, errno, "Error accepting socket"); - continue; - } - - conn_stream = fdopen(conn, "r"); - if (conn_stream == NULL) { - error(0, errno, "Error converting fd to stream"); - close(conn); - continue; - } - - if (fgets(buffer, BUFLEN, conn_stream) == NULL) { - error(0, errno, "Error receiving data"); - fclose(conn_stream); - continue; - } - - find_first_whitespace(buffer); - - if (streq(buffer, "IDENTIFY")) { - // run ovirt-identify-node against 192.168.50.2 (the WUI node) - fprintf(stderr, "Doing identify\n"); - // FIXME: it would be best to call the "find_srv" shell script here to - // find out where we should contact. However, we would still have to - // hardcode which DNS server to use (192.168.50.2), and which domain - // name to use "priv.ovirt.org" to get this to work. I don't have - // a good idea how to solve this at the moment. - system("ovirt-identify-node -s 192.168.50.2 -p 12120"); - } - else if (streq(buffer, "AWAKE")) { - // run ovirt-awake against 192.168.50.2 - fprintf(stderr, "Doing awake\n"); - // FIXME: I hate to duplicate this stuff here, but I can't use the - // ovirt init scripts as-is; they depend too much on the environment - // (in particular, which DNS server to use to resolve, and which - // domainname). Until I come up with a good solution for that, I'll - // have to leave this as-is. - system("wget -q --no-check-certificate http://192.168.50.2:80/ipa/config/krb5.ini -O /etc/krb5.conf"); - system("ovirt-awake start 192.168.50.2 12120 /etc/libvirt/krb5.tab"); - } - else { - error(0, 0, "Unknown command %s", buffer); - } - - fclose(conn_stream); - } - - close(listen_socket); - fclose(logfile); - - return 0; -} diff --git a/ovirt-listen-awake/ovirt-listen-awake.init b/ovirt-listen-awake/ovirt-listen-awake.init deleted file mode 100644 index a6bb78a..0000000 --- a/ovirt-listen-awake/ovirt-listen-awake.init +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# -# -# ovirt-listen-awake startup script for ovirt-listen-awake -# -# chkconfig: - 97 03 -# description: ovirt-listen-awake is a daemon to listen for requests from -# the ovirt VM manager. When it receives one, it contacts the machine -# back with details so that it is added to the ovirt host pool. - -DAEMON=/usr/sbin/ovirt-listen-awake - -. /etc/init.d/functions - -start() { - echo -n "Starting ovirt-listen-awake: " - daemon $DAEMON - RETVAL=$? - echo -} - -stop() { - echo -n "Shutting down ovirt-listen-awake: " - killproc ovirt-listen-awake - RETVAL=$? - echo -} - -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - status) - status $DAEMON - RETVAL=$? - ;; - *) - echo "Usage: ovirt-listen-awake {start|stop|restart|status}" - exit 1 - ;; -esac -exit $RETVAL -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 16:48:09 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:48:09 -0400 Subject: [Ovirt-devel] [PATCH: node-image] Added matahari package to node image, replacing ovirt-identify-node. Message-ID: <1247244489-11281-1-git-send-email-arroy@redhat.com> Matahari is a QMF agent that exposes node information and will provide methods to administer some functions of the node as well. As an example, it currently provides a method to blink the lights of a network adapter to aid in physical identification. Note that there is a related patch to ovirt-node (to add startup script support for this package) and a related patch to server (replacing host-browser with the new host-register, which interfaces with matahari on the node). All three of these must be applied to test out the changes. --- common-pkgs.ks | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/common-pkgs.ks b/common-pkgs.ks index cc507cf..dd25c15 100644 --- a/common-pkgs.ks +++ b/common-pkgs.ks @@ -22,6 +22,7 @@ python-libs db4 vconfig python-virtinst +matahari #debugging hdparm sos -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 16:48:16 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:48:16 -0400 Subject: [Ovirt-devel] [PATCH: server 0/3] Add host-register.rb (replaces host-browser.rb in part) Message-ID: <1247244499-11317-1-git-send-email-arroy@redhat.com> Removes node identification functionality from host-browser.rb and adds a new script, host-register.rb, that takes over that functionality. The chief difference is that host-browser used a simple TCP server setup to get data from the node, while host-register uses the qpid bus to do so. Specifically, it communicates with the matahari qmf agent added to the node in two related patchsets to node and node-image. At present, there is a bug in the ruby console api that affects host-register.rb. As a temporary workaround, managed connections have been disabled. There are two related patchsets for node and node-image that must be applied as well to test out the changes in this patchset. Arjun Roy (3): Removed host-browser identify node functionality and unit test. Added new host-register script to ovirt server. Temporarily set managed_connections to false for host-register till qpid ruby console api bug is fixed. conf/ovirt-host-register | 54 +++ installer/modules/ovirt/manifests/ovirt.pp | 6 + ovirt-server.spec.in | 5 + src/host-browser/host-browser.rb | 255 +--------------- src/host-browser/host-register.rb | 468 +++++++++++++++++++++++++++ src/test/unit/host_browser_identify_test.rb | 310 ------------------ 6 files changed, 534 insertions(+), 564 deletions(-) create mode 100644 conf/ovirt-host-register create mode 100644 src/host-browser/host-register.rb delete mode 100644 src/test/unit/host_browser_identify_test.rb From arroy at redhat.com Fri Jul 10 16:48:17 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:48:17 -0400 Subject: [Ovirt-devel] [PATCH: server 1/3] Removed host-browser identify node functionality and unit test. In-Reply-To: <1247244499-11317-1-git-send-email-arroy@redhat.com> References: <1247244499-11317-1-git-send-email-arroy@redhat.com> Message-ID: <1247244499-11317-2-git-send-email-arroy@redhat.com> --- src/host-browser/host-browser.rb | 255 +---------------------- src/test/unit/host_browser_identify_test.rb | 310 --------------------------- 2 files changed, 1 insertions(+), 564 deletions(-) delete mode 100644 src/test/unit/host_browser_identify_test.rb diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 13b2ac4..d77b321 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -22,7 +22,6 @@ $: << File.join(File.dirname(__FILE__), "../dutils") $: << File.join(File.dirname(__FILE__), "../") require 'rubygems' -require 'libvirt' require 'dutils' require 'socket' @@ -35,9 +34,7 @@ include Socket::Constants $logfile = '/var/log/ovirt-server/host-browser.log' -# +HostBrowser+ communicates with the a managed node. It retrieves specific information -# about the node and then updates the list of active nodes for the Server. -# +# +HostBrowser+ provides kerberos related services to a managed node. class HostBrowser attr_accessor :logfile attr_accessor :keytab_dir @@ -78,245 +75,6 @@ class HostBrowser response end - # Requests node information from the remote system. - # - def get_remote_info - puts "#{prefix(@session)} Begin remote info collection" unless defined?(TESTING) - result = Hash.new - result['HOSTNAME'] = @session.peeraddr[2] - result['IPADDR'] = @session.peeraddr[3] - result['NICINFO'] = Array.new - - @session.write("INFO?\n") - - loop do - info = @session.readline.chomp - - puts "Received info='#{info}'" - - break if info == "ENDINFO" - - case info - when "CPU" - cpu = get_cpu_info - cpu_info = result['CPUINFO'] - - if(cpu_info == nil) - cpu_info = Array.new - result['CPUINFO'] = cpu_info - end - - cpu_info << cpu - when "NIC" - nic = get_nic_info - nic_info = result['NICINFO'] - - if(nic_info == nil) - nic_info = Array.new - result['NICINFO'] = nic_info - end - - nic_info << nic - else - - raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]+/ - - key, value = info.split("=") - - puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value - - @session.write("ACK #{key}\n") - end - end - - return result - end - - # Extracts CPU details from the managed node. - # - def get_cpu_info - puts "Begin receiving CPU details" - - result = Hash.new - - @session.write("CPUINFO?\n") - - loop do - info = @session.readline.chomp - - break if info == "ENDCPU" - - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - - key, value = info.split("=") - - puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value - - @session.write("ACK #{key}\n") - end - - @session.write("ACK CPU\n"); - - return result - end - - # Extracts NIC details from the managed node. - # - def get_nic_info - puts "Begin receiving NIC details" - - result = Hash.new - - @session.write("NICINFO?\n") - - loop do - info = @session.readline.chomp - - break if info == "ENDNIC" - - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]*/ - - key, value = info.split("=") - - puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value - - @session.write("ACK #{key}\n") - end - - @session.write("ACK NIC\n"); - - return result - end - - # Writes the supplied host information to the database. - # - def write_host_info(host_info) - ensure_present(host_info,'HOSTNAME') - ensure_present(host_info,'ARCH') - ensure_present(host_info,'MEMSIZE') - ensure_present(host_info,'CPUINFO') - ensure_present(host_info,'NICINFO') - - cpu_info = host_info['CPUINFO'] - nic_info = host_info['NICINFO'] - - cpu_info.each do |cpu| - ensure_present(cpu,'CPUNUM') - ensure_present(cpu,'CORENUM') - ensure_present(cpu,'NUMCORES') - ensure_present(cpu,'VENDOR') - ensure_present(cpu,'MODEL') - ensure_present(cpu,'FAMILY') - ensure_present(cpu,'CPUIDLVL') - ensure_present(cpu,'SPEED') - ensure_present(cpu,'CACHE') - ensure_present(cpu,'FLAGS') - end - - puts "Searching for existing host record..." unless defined?(TESTING) - host = Host.find(:first, :conditions => ["hostname = ?", host_info['HOSTNAME']]) - - if host == nil - begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) - - host = Host.create( - "uuid" => host_info['UUID'], - "hostname" => host_info['HOSTNAME'], - "hypervisor_type" => host_info['HYPERVISOR_TYPE'], - "arch" => host_info['ARCH'], - "memory" => host_info['MEMSIZE'], - "is_disabled" => 0, - "hardware_pool" => HardwarePool.get_default_pool, - # Let host-status mark it available when it - # successfully connects to it via libvirt. - "state" => Host::STATE_UNAVAILABLE) - rescue Exception => error - puts "Error while creating record: #{error.message}" unless defined?(TESTING) - end - else - host.uuid = host_info['UUID'] - host.hostname = host_info['HOSTNAME'] - host.arch = host_info['ARCH'] - host.memory = host_info['MEMSIZE'] - end - - # delete an existing CPUs and create new ones based on the data - puts "Deleting any existing CPUs" - Cpu.delete_all(['host_id = ?', host.id]) - - puts "Saving new CPU records" - cpu_info.each do |cpu| - detail = Cpu.new( - "cpu_number" => cpu['CPUNUM'].to_i, - "core_number" => cpu['CORENUM]'].to_i, - "number_of_cores" => cpu['NUMCORES'].to_i, - "vendor" => cpu['VENDOR'], - "model" => cpu['MODEL'], - "family" => cpu['FAMILY'], - "cpuid_level" => cpu['CPUIDLVL'].to_i, - "speed" => cpu['SPEED'], - "cache" => cpu['CACHE'], - "flags" => cpu['FLAGS']) - - host.cpus << detail - end - - # Update the NIC details for this host: - # -if the NIC exists, then update the IP address - # -if the NIC does not exist, create it - # -any nic not in this list is deleted - - puts "Updating NIC records for the node" - nics = Array.new - nics_to_delete = Array.new - - host.nics.each do |nic| - found = false - - nic_info.each do |detail| - # if we have a match, then update the database and remove - # the received data to avoid creating a dupe later - puts "Searching for existing record for: #{detail['MAC'].upcase}" - if detail['MAC'].upcase == nic.mac - puts "Updating details for: #{detail['IFACE_NAME']} [#{nic.mac}]}" - nic.bandwidth = detail['BANDWIDTH'].to_i - nic.interface_name = detail['IFACE_NAME'] - nic.save! - found = true - nic_info.delete(detail) - end - end - - # if the record wasn't found, then remove it from the database - unless found - puts "Marking NIC for removal: #{nic.interface_name} [#{nic.mac}]" - nics_to_delete << nic - end - end - - nics_to_delete.each { |nic| puts "Removing NIC: #{nic.interface_name} []#{nic.mac}]"; host.nics.delete(nic) } - - # iterate over any nics left and create new records for them. - nic_info.each do |nic| - puts "Creating a new nic: #{nic['IFACE_NAME']} [#{nic['MAC']}]" - detail = Nic.new( - 'mac' => nic['MAC'].upcase, - 'bandwidth' => nic['BANDWIDTH'].to_i, - 'interface_name' => nic['IFACE_NAME'], - 'usage_type' => 1) - - host.nics << detail - end - - host.save! - - return host - end - # Creates a keytab if one is needed, returning the filename. # def create_keytab(hostname, ipaddress, krb5_arg = nil) @@ -359,12 +117,6 @@ class HostBrowser private - # Private method to ensure that a required field is present. - # - def ensure_present(info,key) - raise Exception.new("ERROR! Missing '#{key}'...") if info[key] == nil - end - # Executes an external program to support the keytab function. # def kadmin_local(command) @@ -379,17 +131,12 @@ def entry_point(server) 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 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 diff --git a/src/test/unit/host_browser_identify_test.rb b/src/test/unit/host_browser_identify_test.rb deleted file mode 100644 index 083b364..0000000 --- a/src/test/unit/host_browser_identify_test.rb +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/ruby -Wall -# -# Copyright (C) 2008 Red Hat, Inc. -# Written by Darryl L. Pierce -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -$: << File.join(File.dirname(__FILE__), "../../dutils") -$: << File.join(File.dirname(__FILE__), "../../host-browser") - -require File.dirname(__FILE__) + '/../test_helper' - -require 'test/unit' -require 'flexmock/test_unit' -require 'dutils' - -TESTING=true - -require 'host-browser' - -# +HostBrowserIdentifyTest+ tests the host-browser server to ensure that it -# works correctly during the identify mode of operation. -# -class HostBrowserIdentifyTest < Test::Unit::TestCase - fixtures :boot_types - - def setup - @connection = flexmock('connection') - @connection.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @browser = HostBrowser.new(@connection) - @browser.logfile = './unit-test.log' - - # default host info - @host_info = {} - @host_info['UUID'] = 'node1' - @host_info['IPADDR'] = '192.168.2.2' - @host_info['HOSTNAME'] = 'prod.corp.com' - @host_info['ARCH'] = 'x86_64' - @host_info['MEMSIZE'] = '16384' - @host_info['DISABLED'] = '0' - - @host_info['NUMCPUS'] = '2' - - @host_info['CPUINFO'] = Array.new - @host_info['CPUINFO'][0] = {} - @host_info['CPUINFO'][0]['CPUNUM'] = '0' - @host_info['CPUINFO'][0]['CORENUM'] = '0' - @host_info['CPUINFO'][0]['NUMCORES'] = '2' - @host_info['CPUINFO'][0]['VENDOR'] = 'GenuineIntel' - @host_info['CPUINFO'][0]['MODEL'] = '15' - @host_info['CPUINFO'][0]['FAMILY'] = '6' - @host_info['CPUINFO'][0]['CPUIDLVL'] = '10' - @host_info['CPUINFO'][0]['SPEED'] = '3' - @host_info['CPUINFO'][0]['CACHE'] = '4096 kb' - @host_info['CPUINFO'][0]['FLAGS'] = 'fpu vme de pse tsc msr pae \ - mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx \ - fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs \ - bts pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr lahf_lm' - - @host_info['CPUINFO'][1] = {} - @host_info['CPUINFO'][1]['CPUNUM'] = '1' - @host_info['CPUINFO'][1]['CORENUM'] = '1' - @host_info['CPUINFO'][1]['NUMCORES'] = '2' - @host_info['CPUINFO'][1]['VENDOR'] = 'GenuineIntel' - @host_info['CPUINFO'][1]['MODEL'] = '15' - @host_info['CPUINFO'][1]['FAMILY'] = '6' - @host_info['CPUINFO'][1]['CPUIDLVL'] = '10' - @host_info['CPUINFO'][1]['SPEED'] = '3' - @host_info['CPUINFO'][1]['CACHE'] = '4096 kb' - @host_info['CPUINFO'][1]['FLAGS'] = 'fpu vme de pse tsc msr pae \ - mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx \ - fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs \ - bts pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr lahf_lm' - - @host_info['NICINFO'] = Array.new - @host_info['NICINFO'] << { - 'MAC' => '00:11:22:33:44:55', - 'BANDWIDTH' => '100', - 'IFACE_NAME' => 'eth0'} - - @host_info['NICINFO'] << { - 'MAC' => '00:77:11:77:19:65', - 'BANDWIDTH' => '100', - 'IFACE_NAME' => 'eth01'} - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @connection.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 5,info.keys.size, "Should contain five keys" - assert info.include?("IPADDR") - assert info.include?("HOSTNAME") - assert info.include?("NICINFO") - assert info.include?("key1") - assert info.include?("key2") - end - - # Ensures that the server is fine when no UUID is present. - # - def test_write_host_info_with_missing_uuid - @host_info['UUID'] = nil - - assert_nothing_raised { @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 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 no cpu info was available, the server raises an - # exception. - # - def test_write_host_info_with_missing_cpuinfo - @host_info['CPUINFO'] = nil - - assert_raise(Exception) { @browser.write_host_info(@host_info) } - end - - # Ensures that, if no NIC info was available, the server raises an - # exception. - # - def test_write_host_info_with_missing_nicinfo - @host_info['NICINFO'] = nil - - assert_raise(Exception) { @browser.write_host_info(@host_info) } - end - - # Ensures that, if a NIC is present that was already submitted, it - # doesn't get re-entered. - # - def test_write_host_info_with_duplicate_nic - # Values taken from the nics.yml fixture - @host_info['NICINFO'] << { - 'MAC' => '00:11:22:33:44:55', - 'BANDWIDTH' => '100', - 'IFACE_NAME' => 'eth0' - } - - assert_nothing_raised { @browser.write_host_info(@host_info) } - assert_equal 3, Host.find_by_hostname('prod.corp.com').nics.size, 'Expected three NICs.' - end - - # Ensures the browser can properly parse the CPU details. - # - def test_parse_cpu_info - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "CPU\n" } - @connection.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDCPU\n" } - @connection.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?("CPUINFO") - end - - # Ensures the browser can properly parse the CPU details of two CPUs. - # - def test_parse_cpu_info_with_two_entries - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - - # CPU 0 - @connection.should_receive(:readline).once().returns { "CPU\n" } - @connection.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDCPU\n" } - @connection.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - # CPU 1 - @connection.should_receive(:readline).once().returns { "CPU\n" } - @connection.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key3=value3\n" } - @connection.should_receive(:write).with("ACK key3\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key4=value4\n" } - @connection.should_receive(:write).with("ACK key4\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDCPU\n" } - @connection.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?('CPUINFO') - assert_equal 2, info['CPUINFO'].size, "Should contain details for two CPUs" - assert_not_nil info['CPUINFO'][0]['key1'] - assert_not_nil info['CPUINFO'][0]['key2'] - assert_not_nil info['CPUINFO'][1]['key3'] - assert_not_nil info['CPUINFO'][1]['key4'] - end - - # Ensures the browser can properly parse the details for a NIC. - # - def test_parse_nic_info - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "NIC\n" } - @connection.should_receive(:write).with("NICINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDNIC\n" } - @connection.should_receive(:write).with("ACK NIC\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain four keys" - assert info.include?("NICINFO") - end -end -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 16:48:19 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:48:19 -0400 Subject: [Ovirt-devel] [PATCH: server 3/3] Temporarily set managed_connections to false for host-register till qpid ruby console api bug is fixed. In-Reply-To: <1247244499-11317-3-git-send-email-arroy@redhat.com> References: <1247244499-11317-1-git-send-email-arroy@redhat.com> <1247244499-11317-2-git-send-email-arroy@redhat.com> <1247244499-11317-3-git-send-email-arroy@redhat.com> Message-ID: <1247244499-11317-4-git-send-email-arroy@redhat.com> --- src/host-browser/host-register.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/host-browser/host-register.rb b/src/host-browser/host-register.rb index e4ed963..814b6a8 100644 --- a/src/host-browser/host-register.rb +++ b/src/host-browser/host-register.rb @@ -92,7 +92,7 @@ class HostRegister < Qpid::Qmf::Console end @logger.info "Connecting to amqp://#{server}:#{port}" - @session = Qpid::Qmf::Session.new(:console => self, :manage_connections => true) + @session = Qpid::Qmf::Session.new(:console => self, :manage_connections => false) @broker = @session.add_broker("amqp://#{server}:#{port}", :mechanism => 'GSSAPI') rescue Exception => ex -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 16:48:18 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 12:48:18 -0400 Subject: [Ovirt-devel] [PATCH: server 2/3] Added new host-register script to ovirt server. In-Reply-To: <1247244499-11317-2-git-send-email-arroy@redhat.com> References: <1247244499-11317-1-git-send-email-arroy@redhat.com> <1247244499-11317-2-git-send-email-arroy@redhat.com> Message-ID: <1247244499-11317-3-git-send-email-arroy@redhat.com> host-register is a qmf ruby console that replaces the identify node functionality in host-browser, by interfacing with the matahari qmf agent on the node to stat node hardware data. host-browser still performs kerberos related duties as before. --- conf/ovirt-host-register | 54 ++++ installer/modules/ovirt/manifests/ovirt.pp | 6 + ovirt-server.spec.in | 5 + src/host-browser/host-register.rb | 468 ++++++++++++++++++++++++++++ 4 files changed, 533 insertions(+), 0 deletions(-) create mode 100644 conf/ovirt-host-register create mode 100644 src/host-browser/host-register.rb diff --git a/conf/ovirt-host-register b/conf/ovirt-host-register new file mode 100644 index 0000000..9f47e23 --- /dev/null +++ b/conf/ovirt-host-register @@ -0,0 +1,54 @@ +#!/bin/bash +# +# +# ovirt-host-register startup script for ovirt-host-register +# +# chkconfig: - 97 03 +# description: ovirt-host-register is an essential component of the \ +# ovirt VM manager, handling the bookkeeping for hw nodes managed \ +# by the server. +# + +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + +export RAILS_ENV="${RAILS_ENV:-production}" + +DAEMON=/usr/share/ovirt-server/host-browser/host-register.rb + +. /etc/init.d/functions + +start() { + echo -n "Starting ovirt-host-register: " + daemon $DAEMON + RETVAL=$? + echo +} + +stop() { + echo -n "Shutting down ovirt-host-register: " + killproc host-register.rb + RETVAL=$? + echo +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + status) + status $DAEMON + RETVAL=$? + ;; + *) + echo "Usage: ovirt-host-register {start|stop|restart|status}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/installer/modules/ovirt/manifests/ovirt.pp b/installer/modules/ovirt/manifests/ovirt.pp index 636cd6e..b018a00 100644 --- a/installer/modules/ovirt/manifests/ovirt.pp +++ b/installer/modules/ovirt/manifests/ovirt.pp @@ -146,6 +146,12 @@ class ovirt::setup { ensure => running } + service {"ovirt-host-register" : + enable => true, + require => [Package[ovirt-server],Single_Exec[db_migrate]], + ensure => running + } + service {"ovirt-host-collect" : enable => true, require => [Package[ovirt-server],Single_Exec[db_migrate]], diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index a315381..1bf73c7 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -102,6 +102,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__install} -p -m0644 %{pbuild}/conf/%{name}.crontab %{buildroot}%{_sysconfdir}/cron.d/%{name} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-browser %{buildroot}%{_initrddir} +%{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-register %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-db-omatic %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-agent %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-collect %{buildroot}%{_initrddir} @@ -183,6 +184,7 @@ fi # on; otherwise, we respect the choices the administrator already has made. # check this by seeing if each daemon is already installed %daemon_chkconfig_post -d ovirt-host-browser +%daemon_chkconfig_post -d ovirt-host-register %daemon_chkconfig_post -d ovirt-db-omatic %daemon_chkconfig_post -d ovirt-agent %daemon_chkconfig_post -d ovirt-host-collect @@ -193,6 +195,7 @@ fi %preun if [ "$1" = 0 ] ; then /sbin/service ovirt-host-browser stop > /dev/null 2>&1 + /sbin/service ovirt-host-register stop > /dev/null 2>&1 /sbin/service ovirt-db-omatic stop > /dev/null 2>&1 /sbin/service ovirt-agent stop > /dev/null 2>&1 /sbin/service ovirt-host-collect stop > /dev/null 2>&1 @@ -200,6 +203,7 @@ if [ "$1" = 0 ] ; then /sbin/service ovirt-taskomatic stop > /dev/null 2>&1 /sbin/service ovirt-vnc-proxy stop > /dev/null 2>&1 /sbin/chkconfig --del ovirt-host-browser + /sbin/chkconfig --del ovirt-host-register /sbin/chkconfig --del ovirt-db-omatic /sbin/chkconfig --del ovirt-agent /sbin/chkconfig --del ovirt-host-collect @@ -215,6 +219,7 @@ fi %{_bindir}/ovirt-add-host %{_bindir}/ovirt-vm2node %{_initrddir}/ovirt-host-browser +%{_initrddir}/ovirt-host-register %{_initrddir}/ovirt-db-omatic %{_initrddir}/ovirt-agent %{_initrddir}/ovirt-host-collect diff --git a/src/host-browser/host-register.rb b/src/host-browser/host-register.rb new file mode 100644 index 0000000..e4ed963 --- /dev/null +++ b/src/host-browser/host-register.rb @@ -0,0 +1,468 @@ +#!/usr/bin/ruby + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), ".") + +require 'rubygems' +require 'qpid' +require 'monitor' +require 'dutils' +require 'daemons' +require 'optparse' +require 'logger' + +include Daemonize + +# This sad and pathetic readjustment to ruby logger class is +# required to fix the formatting because rails does the same +# thing but overrides it to just the message. +# +# See eg: http://osdir.com/ml/lang.ruby.rails.core/2007-01/msg00082.html +# +class Logger + def format_message(severity, timestamp, progname, msg) + "#{severity} #{timestamp} (#{$$}) #{msg}\n" + end +end + +$logfile = '/var/log/ovirt-server/host-register.log' + +class HostRegister < Qpid::Qmf::Console + + # Use monitor mixin for mutual exclusion around checks to heartbeats + # and updates to objects/heartbeats. + + include MonitorMixin + + def initialize() + super() + @cached_hosts = {} + @heartbeats = {} + @broker = nil + @debug = false + + @do_daemon = true + + opts = OptionParser.new do |opts| + opts.on('-h', '--help', 'Print help message') do + puts opts + exit + end + opts.on('-n', '--nodaemon', 'Run interactively (useful for debugging)') do |n| + @do_daemon = false + end + opts.on('-d', '--debug', 'Print out debug messages') do + @debug = true + end + end + begin + opts.parse!(ARGV) + rescue OptionParser::InvalidOption + puts opts + exit + end + + if @do_daemon + # XXX: This gets around a problem with paths for the database stuff. + # Normally daemonize would chdir to / but the paths for the database + # stuff are relative so it breaks it.. It's either this or rearrange + # things so the db stuff is included after daemonizing. + pwd = Dir.pwd + daemonize + Dir.chdir(pwd) + @logger = Logger.new($logfile) + else + @logger = Logger.new(STDERR) + end + @logger.info 'hostregister started.' + + begin + ensure_credentials + + database_connect + + server, port = nil + sleepy = 5 + while true do + server, port = get_srv('qpidd', 'tcp') + break if server + @logger.error "Unable to determine qpid server from DNS SRV record, retrying.." if not server + sleep(sleepy) + sleepy *= 2 if sleepy < 120 + end + + @logger.info "Connecting to amqp://#{server}:#{port}" + @session = Qpid::Qmf::Session.new(:console => self, :manage_connections => true) + @broker = @session.add_broker("amqp://#{server}:#{port}", :mechanism => 'GSSAPI') + + rescue Exception => ex + @logger.error "Error in hostregister: #{ex}" + @logger.error ex.backtrace + end + end + + def debugputs(msg) + puts msg if @debug == true and @do_daemon == false + end + + def ensure_credentials() + get_credentials('qpidd') + + Thread.new do + while true do + sleep(3600) + get_credentials('qpidd') + end + end + end + + def broker_connected(broker) + @logger.info 'Connected to broker.' + end + + def broker_disconnected(broker) + @logger.error 'Broker disconnected.' + end + + def agent_disconnected(agent) + synchronize do + debugputs "Marking objects for agent #{agent.broker.broker_bank}.#{agent.agent_bank} inactive" + @cached_hosts.keys.each do |objkey| + if @cached_hosts[objkey][:broker_bank] == agent.broker.broker_bank and + @cached_hosts[objkey][:agent_bank] == agent.agent_bank + + cached_host = @cached_hosts[objkey] + cached_host[:active] = false + @logger.info "Host #{cached_host['hostname']} marked inactive" + end + end # @objects.keys.each + end # synchronize do + end + + def agent_connected(agent) + synchronize do + debugputs "Marking objects for agent #{agent.broker.broker_bank}.#{agent.agent_bank} active" + @cached_hosts.keys.each do |objkey| + if @cached_hosts[objkey][:broker_bank] == agent.broker.broker_bank and + @cached_hosts[objkey][:agent_bank] == agent.agent_bank + + cached_host = @cached_hosts[objkey] + cached_host[:active] = true + @logger.info "Host #{cached_host['hostname']} marked active" + end + end # @objects.keys.each + end # synchronize do + end + + def update_cpus(host_qmf, host_db) + # Handle CPUs. First get list of cpus for host from broker + cpu_info = @session.objects(:class => 'cpu', 'host' => host_qmf.object_id) + + @logger.info "Updating CPU info for host #{host_qmf.hostname}" + debugputs "Broker reports #{cpu_info.length} cpus for host #{host_qmf.hostname}" + + # delete an existing CPUs and create new ones based on the data + @logger.info "Deleting any existing CPUs for host #{host_qmf.hostname}" + Cpu.delete_all(['host_id = ?', host_db.id]) + + @logger.info "Saving new CPU records for host #{host_qmf.hostname}" + cpu_info.each do |cpu| + flags = (cpu.flags.length > 255) ? "#{cpu.flags[0..251]}..." : cpu.flags + detail = Cpu.new( + 'cpu_number' => cpu.cpunum, + 'core_number' => cpu.corenum, + 'number_of_cores' => cpu.numcores, + 'vendor' => cpu.vendor, + 'model' => cpu.model.to_s, + 'family' => cpu.family.to_s, + 'cpuid_level' => cpu.cpuid_lvl, + 'speed' => cpu.speed.to_s, + 'cache' => cpu.cache.to_s, + 'flags' => flags) + + host_db.cpus << detail + + debugputs "Added new CPU for #{host_qmf.hostname}: " + debugputs "CPU # : #{cpu.cpunum}" + debugputs "Core # : #{cpu.corenum}" + debugputs "Total Cores : #{cpu.numcores}" + debugputs "Vendor : #{cpu.vendor}" + debugputs "Model : #{cpu.model}" + debugputs "Family : #{cpu.family}" + debugputs "Cpuid_lvl : #{cpu.cpuid_lvl}" + debugputs "Speed : #{cpu.speed}" + debugputs "Cache : #{cpu.cache}" + debugputs "Flags : #{flags}" + end + + @logger.info "Saved #{cpu_info.length} cpus for #{host_qmf.hostname}" + end + + def update_nics(host_qmf, host_db) + # Handle NICs. Get list of all nics for host from broker + nic_info = @session.objects(:class => 'nic', 'host' => host_qmf.object_id) + + # Update the NIC details for this host: + # -if the NIC exists, then update the IP address + # -if the NIC does not exist, create it + # -any nic not in this list is deleted + + @logger.info "Updating NIC records for host #{host_qmf.hostname}" + debugputs "Broker reports #{nic_info.length} NICs for host" + + nics = Array.new + nics_to_delete = Array.new + + host_db.nics.each do |nic| + found = false + + nic_info.each do |detail| + # if we have a match, then update the database and remove + # the received data to avoid creating a dupe later + @logger.info "Searching for existing record for: #{detail.macaddr.upcase} in host #{host_qmf.hostname}" + if detail.macaddr.upcase == nic.mac + @logger.info "Updating details for: #{detail.interface} [#{nic.mac}]}" + nic.bandwidth = detail.bandwidth + nic.interface_name = detail.interface + nic.save! + found = true + nic_info.delete(detail) + end + end + + # if the record wasn't found, then remove it from the database + unless found + @logger.info "Marking NIC for removal: #{nic.interface_name} [#{nic.mac}]" + nics_to_delete << nic + end + end + + debugputs "Deleting #{nics_to_delete.length} NICs that are no longer part of host #{host_qmf.hostname}" + nics_to_delete.each do |nic| + @logger.info "Removing NIC: #{nic.interface_name} [#{nic.mac}]" + host_db.nics.delete(nic) + end + + # iterate over any nics left and create new records for them. + debugputs "Adding new records for #{nic_info.length} NICs to host #{host_qmf.hostname}" + nic_info.each do |nic| + detail = Nic.new( + 'mac' => nic.macaddr.upcase, + 'bandwidth' => nic.bandwidth, + 'interface_name' => nic.interface, + 'usage_type' => 1) + + host_db.nics << detail + + @logger.info "Added NIC #{nic.interface} with MAC #{nic.macaddr} to host #{host_qmf.hostname}" + end + end + + def object_props(broker, obj) + target = obj.klass_key[0] + type = obj.klass_key[1] + return if target != 'com.redhat.matahari' or type != 'host' + + # Fix a race where the properties of an object are published by a reconnecting + # host (thus marking it active) right before the heartbeat timer considers it dead + # (and marks it inactive) + @heartbeats.delete("#{obj.object_id.agent_bank}.#{obj.object_id.broker_bank}") + + already_cache = false + already_in_db = false + + synchronize do + cached_host = @cached_hosts[obj.object_id.to_s] + host = Host.find(:first, :conditions => ['hostname = ?', obj.hostname]) + + already_cache = true if cached_host != nil + already_in_db = true if host != nil + + @logger.info "Node #{obj.hostname} with UUID #{obj.uuid} detected, already exists in db? is #{already_in_db}" + + # Four cases apply here: + # 1. Not in db, but is cached: + # Impossible, since we don't cache unless we could add to db. + # Throw an exception here. + # + # 2. Not in db or cache: + # Seeing host for the first time. Put in db and cache. + # + # 3. In db, not in cache: + # Have not seen host since last invocation of daemon. Update + # db entry, and add to cache. + # + # 4. In db, in cache: + # Property updated; update in db and cache. + + # Case 1: + if already_cache and not already_in_db + error = "Error: Found host in cache that is not present in db!" + @logger.error error + throw error + end + + # All other cases collapse to add-or-update db and cache + + # Add to db if necessary + if not already_in_db + debugputs "Didn't find host #{obj.hostname} in db!" + begin + @logger.info "Creating a new record for #{obj.hostname}..." + + host = Host.create( + 'uuid' => obj.uuid, + 'hostname' => obj.hostname, + 'hypervisor_type' => obj.hypervisor, + 'arch' => obj.arch, + 'memory' => obj.memory, + 'is_disabled' => 0, + 'hardware_pool' => HardwarePool.get_default_pool, + # Let host-status mark it available when it + # successfully connects to it via libvirt. + 'state' => Host::STATE_UNAVAILABLE) + + debugputs 'Added new host:' + debugputs "uuid: #{obj.uuid}" + debugputs "hostname: #{obj.hostname}" + debugputs "hypervisor: #{obj.hypervisor}" + debugputs "arch: #{obj.arch}" + debugputs "memory: #{obj.memory}" + + rescue Exception => error + @logger.error "Error while creating record: #{error.message}" + # We haven't added the host to the db, and it isn't cached, so we just + # return without having done anything. To retry, the host will have to + # restart its agent. + return + end + else + @logger.info "Updating record for #{obj.hostname}..." + host.uuid = obj.uuid + host.hostname = obj.hostname + host.hypervisor_type = obj.hypervisor + host.arch = obj.arch + host.memory = obj.memory + + debugputs 'Updated host #{obj.hostname} with new details:' + debugputs "uuid: #{obj.uuid}" + debugputs "hypervisor: #{obj.hypervisor}" + debugputs "arch: #{obj.arch}" + debugputs "memory: #{obj.memory}" + end # not already_in_db + + update_cpus(obj, host) + update_nics(obj, host) + + host.save! + debugputs "Finished flushing host #{obj.hostname} to db" + + # Add to cache if necessary + if not already_cache + debugputs "Did not find host #{obj.hostname} in cache!" + # Check if there is a stale entry for host that we can refresh. + # We iterate over each host in the cache. If we find an entry + # that matches hostname, we rekey it in hash. + @cached_hosts.each do |objkey, h| + if h['hostname'] == obj.hostname + debugputs "Found stale entry for #{obj.hostname} with key #{objkey}" + debugputs "Refreshing with key #{obj.object_id.to_s}" + @cached_hosts.delete(objkey) + @cached_hosts[obj.object_id.to_s] = h + cached_host = h + break + end + end # @cached_hosts.each + + if cached_host == nil + debugputs "Creating new entry for #{obj.hostname} with key #{obj.object_id.to_s}" + cached_host = {} + @cached_hosts[obj.object_id.to_s] = cached_host + end + + # By now, we either rekeyed a stale entry or started a new one. + # Update the bookkeeping parts of the data. + cached_host[:obj_key] = obj.object_id.to_s + cached_host[:broker_bank] = obj.object_id.broker_bank + cached_host[:agent_bank] = obj.object_id.agent_bank + end # not already_cache + + # For now, only cache identity information (leave CPU/NIC/etc. to db only) + cached_host[:active] = true + cached_host['hostname'] = obj.hostname + cached_host['uuid'] = obj.uuid + cached_host['hypervisor'] = obj.hypervisor + cached_host['arch'] = obj.arch + end # synchronize do + end # def object_props + + def heartbeat(agent, timestamp) + return if agent == nil + synchronize do + bank_key = "#{agent.agent_bank}.#{agent.broker.broker_bank}" + @heartbeats[bank_key] = [agent, timestamp] + end + end + + def new_agent(agent) + key = "#{agent.agent_bank}.#{agent.broker.broker_bank}" + debugputs "Agent #{key} connected!" + agent_connected(agent) + end + + def del_agent(agent) + key = "#{agent.agent_bank}.#{agent.broker.broker_bank}" + debugputs "Agent #{key} disconnected!" + @heartbeats.delete(key) + agent_disconnected(agent) + end + + def check_heartbeats() + begin + while true + sleep(5) + synchronize do + # Get seconds from the epoch + t = Time.new.to_i + + @heartbeats.keys.each do | key | + agent, timestamp = @heartbeats[key] + + # Heartbeats from qpid are in microseconds, we just need seconds.. + s = timestamp / 1000000000 + delta = t - s + + if delta > 30 + # No heartbeat for 30 seconds.. deal with dead/disconnected agent. + debugputs "Agent #{key} timed out!" + @heartbeats.delete(key) + agent_disconnected(agent) + end + end + + # Print out current objects + debugputs '===== Current Objects =====' + @cached_hosts.keys.each do |objkey| + cached_host = @cached_hosts[objkey] + cached_host.each do |key, val| + debugputs "\t#{key} : #{val}\n" + end + end + debugputs '===== Done =====' + + end # synchronize do + end # while true + rescue Exception => ex + @logger.error "Error in hostregister: #{ex}" + @logger.error ex.backtrace + end # end begin-rescue + end # def check_heartbeats + +end # Class HostRegister + +def main() + hostreg = HostRegister.new() + hostreg.check_heartbeats() +end + +main() -- 1.6.2.5 From apevec at redhat.com Fri Jul 10 18:32:02 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 20:32:02 +0200 Subject: [Ovirt-devel] Re: [PATCH node] Provides a means to toggle SSH password auth from the firstboot menu. rhbz#509842 In-Reply-To: <1247237102-10303-2-git-send-email-dpierce@redhat.com> References: <1247237102-10303-1-git-send-email-dpierce@redhat.com> <1247237102-10303-2-git-send-email-dpierce@redhat.com> Message-ID: <4A578922.5040705@redhat.com> Darryl L. Pierce wrote: > The password option now goes to a submenu. This submenu lets the user > chose to either set the administrator password or else toggle SSH > password authentication on or off. as discussed off-line, this toggle prompt is confusing, I'd like to ACK and push with the attached amendment and small fix: > + augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ need /usr/bin/augtool otherwise override from ovirt-functions is used, which doesn't accept commands as parameter -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: o-c-p.diff URL: From dpierce at redhat.com Fri Jul 10 18:34:13 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 14:34:13 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Provides a means to toggle SSH password auth from the firstboot menu. rhbz#509842 In-Reply-To: <4A578922.5040705@redhat.com> References: <1247237102-10303-1-git-send-email-dpierce@redhat.com> <1247237102-10303-2-git-send-email-dpierce@redhat.com> <4A578922.5040705@redhat.com> Message-ID: <20090710183413.GC14459@mcpierce-laptop.rdu.redhat.com> On Fri, Jul 10, 2009 at 08:32:02PM +0200, Alan Pevec wrote: > Darryl L. Pierce wrote: >> The password option now goes to a submenu. This submenu lets the user >> chose to either set the administrator password or else toggle SSH >> password authentication on or off. > > as discussed off-line, this toggle prompt is confusing, I'd like to ACK and push with the attached amendment and small fix: > >> + augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ > need /usr/bin/augtool otherwise override from ovirt-functions is used, which doesn't accept commands as parameter > diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password > index 78ec5ba..a87931b 100755 > --- a/scripts/ovirt-config-password > +++ b/scripts/ovirt-config-password > @@ -65,11 +65,9 @@ EOF > } > > toggle_ssh () { > - local prompt=$1 > + printf "\nSSH password authentication\n\n" > > - printf "\nToggle SSH\n\n" > - > - if ask_yes_or_no "${prompt} (y/n)?"; then > + if ask_yes_or_no "Enable SSH password authentication (y/n)?"; then > toggle_ssh_access true > else > toggle_ssh_access false > @@ -82,7 +80,7 @@ QUIT="Quit and Return To Menu" > > while true; do > state="disabled" > - augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ > + /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ > if [ $? == 0 ]; then > state="enabled" > fi > @@ -93,7 +91,7 @@ while true; do > do > case $option in > $PASSWORD) set_password; break;; > - $SSH) toggle_ssh "$prompt"; break;; > + $SSH) toggle_ssh; break;; > $QUIT) exit;; > esac > done ACK. That wording is very clear. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Fri Jul 10 19:07:38 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 15:07:38 -0400 Subject: [Ovirt-devel] Final patch for VLAN Message-ID: <1247252859-2798-1-git-send-email-dpierce@redhat.com> This patch incorporates findings while playing around with VLANs to ensure the right configure files are produced. From dpierce at redhat.com Fri Jul 10 19:07:39 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 15:07:39 -0400 Subject: [Ovirt-devel] [PATCH node] Add VLAN support to network configuration. rhbz#510116 In-Reply-To: <1247252859-2798-1-git-send-email-dpierce@redhat.com> References: <1247252859-2798-1-git-send-email-dpierce@redhat.com> Message-ID: <1247252859-2798-2-git-send-email-dpierce@redhat.com> While configuring the management interface, the user can indicate whether the device will participate in a VLAN. If so, an additional property, VLAN yes, is added to the initscript for the interface. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 86 ++++++++++++++++++++++++++++++-------- 1 files changed, 68 insertions(+), 18 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 8380187..66899ea 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -21,6 +21,8 @@ NTPCONF_FILE_ROOT="/files/etc/ntp" NTP_CONFIG_FILE="/etc/ntp.conf" NTPSERVERS="" CONFIGURED_NIC="" +VLAN_ID="" +VL_ROOT="" # if local storage is not configured, then exit the script if ! is_local_storage_configured; then @@ -59,6 +61,9 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + rm -rf /etc/sysconfig/network-scripts/ifcfg-* + cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo else printf "\nAborting...\n" return @@ -79,15 +84,16 @@ function configure_interface local BR_ROOT="$IFCONFIG_FILE_ROOT-$BRIDGE" local BR_CONFIG="rm $BR_ROOT\nset $BR_ROOT/DEVICE $BRIDGE" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/TYPE Bridge" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/PEERNTP yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DELAY 0" - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/BRIDGE $BRIDGE" - local BR_CONFIG_BASE=$BR_CONFIG if [ -z "$AUTO" ]; then while true; do + local VL_CONFIG="" + printf "\n" LINK=`ethtool $NIC| grep "Link detected"`:u [ -z "$LINK" ] && return @@ -102,24 +108,57 @@ function configure_interface ethtool --identify $NIC 10 fi + ask_yes_or_no "Include VLAN support (y/n/a)? " true true + case $? in + 0) + while true; do + read -ep "What is the VLAN ID for this device (a=abort) " + case $REPLY in + A|a) CONFIGURED_NIC=""; return;; + *) + if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then + VLAN_ID=$REPLY + VL_ROOT="${IF_ROOT}.${VLAN_ID}" + VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${BRIDGE}.${VLAN_ID}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" + VL_FILENAME="${BR_FILENAME}.${VLAN_ID}" + break + fi + ;; + esac + done + ;; + 1) IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" ;; + 2) + CONFIGURED_NIC="" + VLAN_ID="" + return;; + esac + read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " case $REPLY in D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" + conf="$conf\nset $root/BOOTPROTO dhcp" ;; S|s) printf "\n" read -ep "IP Address: "; IPADDR=$REPLY read -ep " Netmask: "; NETMASK=$REPLY read -ep " Gateway: "; GATEWAY=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" + + conf="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" + conf="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" if [ -n "${GATEWAY}" ]; then - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" + conf="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" fi ;; - A|a) CONFIGURED_NIC=""; return ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; esac printf "\n" @@ -127,21 +166,25 @@ function configure_interface case $REPLY in S|s) read -ep "IPv6 Address: "; IPADDR=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" ;; D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" ;; U|u) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return ;; - A|a) CONFIGURED_NIC=""; return;; esac printf "\n" @@ -150,8 +193,14 @@ function configure_interface 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [[ -n "$VLAN_ID" ]]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi break ;; 1) @@ -159,6 +208,7 @@ function configure_interface ;; 2) CONFIGURED_NIC="" + VLAN_ID="" return ;; esac -- 1.6.2.5 From imain at redhat.com Fri Jul 10 19:22:22 2009 From: imain at redhat.com (Ian Main) Date: Fri, 10 Jul 2009 12:22:22 -0700 Subject: [Ovirt-devel] [PATCH: server 2/3] Added new host-register script to ovirt server. In-Reply-To: <1247244499-11317-3-git-send-email-arroy@redhat.com> References: <1247244499-11317-1-git-send-email-arroy@redhat.com> <1247244499-11317-2-git-send-email-arroy@redhat.com> <1247244499-11317-3-git-send-email-arroy@redhat.com> Message-ID: <20090710122222.615e4e9c@tp.mains.net> On Fri, 10 Jul 2009 12:48:18 -0400 Arjun Roy wrote: > host-register is a qmf ruby console that replaces the identify node > functionality in host-browser, by interfacing with the matahari qmf > agent on the node to stat node hardware data. > > host-browser still performs kerberos related duties as before. [SNIP] > diff --git a/src/host-browser/host-register.rb b/src/host-browser/host-register.rb > new file mode 100644 > index 0000000..e4ed963 This needs to be 755.. Ian From arroy at redhat.com Fri Jul 10 19:43:27 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 15:43:27 -0400 Subject: [Ovirt-devel] [PATCH: server 0/3] Add host-register.rb (replaces host-browser.rb in part) Message-ID: <1247255010-13742-1-git-send-email-arroy@redhat.com> Removes node identification functionality from host-browser.rb and adds a new script, host-register.rb, that takes over that functionality. The chief difference is that host-browser used a simple TCP server setup to get data from the node, while host-register uses the qpid bus to do so. Specifically, it communicates with the matahari qmf agent added to the node in two related patchsets to node and node-image. At present, there is a bug in the ruby console api that affects host-register.rb. As a temporary workaround, managed connections have been disabled. There are two related patchsets for node and node-image that must be applied as well to test out the changes in this patchset. This is the third version of this patchset, and hopefully all the kinks have been worked out by now. :) Arjun Roy (3): Removed host-browser identify node functionality and unit test. Added new host-register script to ovirt server. Temporarily setting managed connections to false till ruby qmf console bug fix is pushed. conf/ovirt-host-register | 54 +++ installer/modules/ovirt/manifests/ovirt.pp | 6 + ovirt-server.spec.in | 5 + src/host-browser/host-browser.rb | 255 +--------------- src/host-browser/host-register.rb | 468 +++++++++++++++++++++++++++ src/test/unit/host_browser_identify_test.rb | 310 ------------------ 6 files changed, 534 insertions(+), 564 deletions(-) create mode 100644 conf/ovirt-host-register create mode 100755 src/host-browser/host-register.rb delete mode 100644 src/test/unit/host_browser_identify_test.rb From arroy at redhat.com Fri Jul 10 19:43:28 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 15:43:28 -0400 Subject: [Ovirt-devel] [PATCH: server 1/3] Removed host-browser identify node functionality and unit test. In-Reply-To: <1247255010-13742-1-git-send-email-arroy@redhat.com> References: <1247255010-13742-1-git-send-email-arroy@redhat.com> Message-ID: <1247255010-13742-2-git-send-email-arroy@redhat.com> --- src/host-browser/host-browser.rb | 255 +---------------------- src/test/unit/host_browser_identify_test.rb | 310 --------------------------- 2 files changed, 1 insertions(+), 564 deletions(-) delete mode 100644 src/test/unit/host_browser_identify_test.rb diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 13b2ac4..d77b321 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -22,7 +22,6 @@ $: << File.join(File.dirname(__FILE__), "../dutils") $: << File.join(File.dirname(__FILE__), "../") require 'rubygems' -require 'libvirt' require 'dutils' require 'socket' @@ -35,9 +34,7 @@ include Socket::Constants $logfile = '/var/log/ovirt-server/host-browser.log' -# +HostBrowser+ communicates with the a managed node. It retrieves specific information -# about the node and then updates the list of active nodes for the Server. -# +# +HostBrowser+ provides kerberos related services to a managed node. class HostBrowser attr_accessor :logfile attr_accessor :keytab_dir @@ -78,245 +75,6 @@ class HostBrowser response end - # Requests node information from the remote system. - # - def get_remote_info - puts "#{prefix(@session)} Begin remote info collection" unless defined?(TESTING) - result = Hash.new - result['HOSTNAME'] = @session.peeraddr[2] - result['IPADDR'] = @session.peeraddr[3] - result['NICINFO'] = Array.new - - @session.write("INFO?\n") - - loop do - info = @session.readline.chomp - - puts "Received info='#{info}'" - - break if info == "ENDINFO" - - case info - when "CPU" - cpu = get_cpu_info - cpu_info = result['CPUINFO'] - - if(cpu_info == nil) - cpu_info = Array.new - result['CPUINFO'] = cpu_info - end - - cpu_info << cpu - when "NIC" - nic = get_nic_info - nic_info = result['NICINFO'] - - if(nic_info == nil) - nic_info = Array.new - result['NICINFO'] = nic_info - end - - nic_info << nic - else - - raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]+/ - - key, value = info.split("=") - - puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value - - @session.write("ACK #{key}\n") - end - end - - return result - end - - # Extracts CPU details from the managed node. - # - def get_cpu_info - puts "Begin receiving CPU details" - - result = Hash.new - - @session.write("CPUINFO?\n") - - loop do - info = @session.readline.chomp - - break if info == "ENDCPU" - - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - - key, value = info.split("=") - - puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value - - @session.write("ACK #{key}\n") - end - - @session.write("ACK CPU\n"); - - return result - end - - # Extracts NIC details from the managed node. - # - def get_nic_info - puts "Begin receiving NIC details" - - result = Hash.new - - @session.write("NICINFO?\n") - - loop do - info = @session.readline.chomp - - break if info == "ENDNIC" - - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]*/ - - key, value = info.split("=") - - puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value - - @session.write("ACK #{key}\n") - end - - @session.write("ACK NIC\n"); - - return result - end - - # Writes the supplied host information to the database. - # - def write_host_info(host_info) - ensure_present(host_info,'HOSTNAME') - ensure_present(host_info,'ARCH') - ensure_present(host_info,'MEMSIZE') - ensure_present(host_info,'CPUINFO') - ensure_present(host_info,'NICINFO') - - cpu_info = host_info['CPUINFO'] - nic_info = host_info['NICINFO'] - - cpu_info.each do |cpu| - ensure_present(cpu,'CPUNUM') - ensure_present(cpu,'CORENUM') - ensure_present(cpu,'NUMCORES') - ensure_present(cpu,'VENDOR') - ensure_present(cpu,'MODEL') - ensure_present(cpu,'FAMILY') - ensure_present(cpu,'CPUIDLVL') - ensure_present(cpu,'SPEED') - ensure_present(cpu,'CACHE') - ensure_present(cpu,'FLAGS') - end - - puts "Searching for existing host record..." unless defined?(TESTING) - host = Host.find(:first, :conditions => ["hostname = ?", host_info['HOSTNAME']]) - - if host == nil - begin - puts "Creating a new record for #{host_info['HOSTNAME']}..." unless defined?(TESTING) - - host = Host.create( - "uuid" => host_info['UUID'], - "hostname" => host_info['HOSTNAME'], - "hypervisor_type" => host_info['HYPERVISOR_TYPE'], - "arch" => host_info['ARCH'], - "memory" => host_info['MEMSIZE'], - "is_disabled" => 0, - "hardware_pool" => HardwarePool.get_default_pool, - # Let host-status mark it available when it - # successfully connects to it via libvirt. - "state" => Host::STATE_UNAVAILABLE) - rescue Exception => error - puts "Error while creating record: #{error.message}" unless defined?(TESTING) - end - else - host.uuid = host_info['UUID'] - host.hostname = host_info['HOSTNAME'] - host.arch = host_info['ARCH'] - host.memory = host_info['MEMSIZE'] - end - - # delete an existing CPUs and create new ones based on the data - puts "Deleting any existing CPUs" - Cpu.delete_all(['host_id = ?', host.id]) - - puts "Saving new CPU records" - cpu_info.each do |cpu| - detail = Cpu.new( - "cpu_number" => cpu['CPUNUM'].to_i, - "core_number" => cpu['CORENUM]'].to_i, - "number_of_cores" => cpu['NUMCORES'].to_i, - "vendor" => cpu['VENDOR'], - "model" => cpu['MODEL'], - "family" => cpu['FAMILY'], - "cpuid_level" => cpu['CPUIDLVL'].to_i, - "speed" => cpu['SPEED'], - "cache" => cpu['CACHE'], - "flags" => cpu['FLAGS']) - - host.cpus << detail - end - - # Update the NIC details for this host: - # -if the NIC exists, then update the IP address - # -if the NIC does not exist, create it - # -any nic not in this list is deleted - - puts "Updating NIC records for the node" - nics = Array.new - nics_to_delete = Array.new - - host.nics.each do |nic| - found = false - - nic_info.each do |detail| - # if we have a match, then update the database and remove - # the received data to avoid creating a dupe later - puts "Searching for existing record for: #{detail['MAC'].upcase}" - if detail['MAC'].upcase == nic.mac - puts "Updating details for: #{detail['IFACE_NAME']} [#{nic.mac}]}" - nic.bandwidth = detail['BANDWIDTH'].to_i - nic.interface_name = detail['IFACE_NAME'] - nic.save! - found = true - nic_info.delete(detail) - end - end - - # if the record wasn't found, then remove it from the database - unless found - puts "Marking NIC for removal: #{nic.interface_name} [#{nic.mac}]" - nics_to_delete << nic - end - end - - nics_to_delete.each { |nic| puts "Removing NIC: #{nic.interface_name} []#{nic.mac}]"; host.nics.delete(nic) } - - # iterate over any nics left and create new records for them. - nic_info.each do |nic| - puts "Creating a new nic: #{nic['IFACE_NAME']} [#{nic['MAC']}]" - detail = Nic.new( - 'mac' => nic['MAC'].upcase, - 'bandwidth' => nic['BANDWIDTH'].to_i, - 'interface_name' => nic['IFACE_NAME'], - 'usage_type' => 1) - - host.nics << detail - end - - host.save! - - return host - end - # Creates a keytab if one is needed, returning the filename. # def create_keytab(hostname, ipaddress, krb5_arg = nil) @@ -359,12 +117,6 @@ class HostBrowser private - # Private method to ensure that a required field is present. - # - def ensure_present(info,key) - raise Exception.new("ERROR! Missing '#{key}'...") if info[key] == nil - end - # Executes an external program to support the keytab function. # def kadmin_local(command) @@ -379,17 +131,12 @@ def entry_point(server) 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 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 diff --git a/src/test/unit/host_browser_identify_test.rb b/src/test/unit/host_browser_identify_test.rb deleted file mode 100644 index 083b364..0000000 --- a/src/test/unit/host_browser_identify_test.rb +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/ruby -Wall -# -# Copyright (C) 2008 Red Hat, Inc. -# Written by Darryl L. Pierce -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -$: << File.join(File.dirname(__FILE__), "../../dutils") -$: << File.join(File.dirname(__FILE__), "../../host-browser") - -require File.dirname(__FILE__) + '/../test_helper' - -require 'test/unit' -require 'flexmock/test_unit' -require 'dutils' - -TESTING=true - -require 'host-browser' - -# +HostBrowserIdentifyTest+ tests the host-browser server to ensure that it -# works correctly during the identify mode of operation. -# -class HostBrowserIdentifyTest < Test::Unit::TestCase - fixtures :boot_types - - def setup - @connection = flexmock('connection') - @connection.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @browser = HostBrowser.new(@connection) - @browser.logfile = './unit-test.log' - - # default host info - @host_info = {} - @host_info['UUID'] = 'node1' - @host_info['IPADDR'] = '192.168.2.2' - @host_info['HOSTNAME'] = 'prod.corp.com' - @host_info['ARCH'] = 'x86_64' - @host_info['MEMSIZE'] = '16384' - @host_info['DISABLED'] = '0' - - @host_info['NUMCPUS'] = '2' - - @host_info['CPUINFO'] = Array.new - @host_info['CPUINFO'][0] = {} - @host_info['CPUINFO'][0]['CPUNUM'] = '0' - @host_info['CPUINFO'][0]['CORENUM'] = '0' - @host_info['CPUINFO'][0]['NUMCORES'] = '2' - @host_info['CPUINFO'][0]['VENDOR'] = 'GenuineIntel' - @host_info['CPUINFO'][0]['MODEL'] = '15' - @host_info['CPUINFO'][0]['FAMILY'] = '6' - @host_info['CPUINFO'][0]['CPUIDLVL'] = '10' - @host_info['CPUINFO'][0]['SPEED'] = '3' - @host_info['CPUINFO'][0]['CACHE'] = '4096 kb' - @host_info['CPUINFO'][0]['FLAGS'] = 'fpu vme de pse tsc msr pae \ - mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx \ - fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs \ - bts pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr lahf_lm' - - @host_info['CPUINFO'][1] = {} - @host_info['CPUINFO'][1]['CPUNUM'] = '1' - @host_info['CPUINFO'][1]['CORENUM'] = '1' - @host_info['CPUINFO'][1]['NUMCORES'] = '2' - @host_info['CPUINFO'][1]['VENDOR'] = 'GenuineIntel' - @host_info['CPUINFO'][1]['MODEL'] = '15' - @host_info['CPUINFO'][1]['FAMILY'] = '6' - @host_info['CPUINFO'][1]['CPUIDLVL'] = '10' - @host_info['CPUINFO'][1]['SPEED'] = '3' - @host_info['CPUINFO'][1]['CACHE'] = '4096 kb' - @host_info['CPUINFO'][1]['FLAGS'] = 'fpu vme de pse tsc msr pae \ - mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx \ - fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs \ - bts pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr lahf_lm' - - @host_info['NICINFO'] = Array.new - @host_info['NICINFO'] << { - 'MAC' => '00:11:22:33:44:55', - 'BANDWIDTH' => '100', - 'IFACE_NAME' => 'eth0'} - - @host_info['NICINFO'] << { - 'MAC' => '00:77:11:77:19:65', - 'BANDWIDTH' => '100', - 'IFACE_NAME' => 'eth01'} - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @connection.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.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 - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 5,info.keys.size, "Should contain five keys" - assert info.include?("IPADDR") - assert info.include?("HOSTNAME") - assert info.include?("NICINFO") - assert info.include?("key1") - assert info.include?("key2") - end - - # Ensures that the server is fine when no UUID is present. - # - def test_write_host_info_with_missing_uuid - @host_info['UUID'] = nil - - assert_nothing_raised { @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 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 no cpu info was available, the server raises an - # exception. - # - def test_write_host_info_with_missing_cpuinfo - @host_info['CPUINFO'] = nil - - assert_raise(Exception) { @browser.write_host_info(@host_info) } - end - - # Ensures that, if no NIC info was available, the server raises an - # exception. - # - def test_write_host_info_with_missing_nicinfo - @host_info['NICINFO'] = nil - - assert_raise(Exception) { @browser.write_host_info(@host_info) } - end - - # Ensures that, if a NIC is present that was already submitted, it - # doesn't get re-entered. - # - def test_write_host_info_with_duplicate_nic - # Values taken from the nics.yml fixture - @host_info['NICINFO'] << { - 'MAC' => '00:11:22:33:44:55', - 'BANDWIDTH' => '100', - 'IFACE_NAME' => 'eth0' - } - - assert_nothing_raised { @browser.write_host_info(@host_info) } - assert_equal 3, Host.find_by_hostname('prod.corp.com').nics.size, 'Expected three NICs.' - end - - # Ensures the browser can properly parse the CPU details. - # - def test_parse_cpu_info - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "CPU\n" } - @connection.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDCPU\n" } - @connection.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?("CPUINFO") - end - - # Ensures the browser can properly parse the CPU details of two CPUs. - # - def test_parse_cpu_info_with_two_entries - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - - # CPU 0 - @connection.should_receive(:readline).once().returns { "CPU\n" } - @connection.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDCPU\n" } - @connection.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - # CPU 1 - @connection.should_receive(:readline).once().returns { "CPU\n" } - @connection.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key3=value3\n" } - @connection.should_receive(:write).with("ACK key3\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key4=value4\n" } - @connection.should_receive(:write).with("ACK key4\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDCPU\n" } - @connection.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?('CPUINFO') - assert_equal 2, info['CPUINFO'].size, "Should contain details for two CPUs" - assert_not_nil info['CPUINFO'][0]['key1'] - assert_not_nil info['CPUINFO'][0]['key2'] - assert_not_nil info['CPUINFO'][1]['key3'] - assert_not_nil info['CPUINFO'][1]['key4'] - end - - # Ensures the browser can properly parse the details for a NIC. - # - def test_parse_nic_info - @connection.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "NIC\n" } - @connection.should_receive(:write).with("NICINFO?\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key1=value1\n" } - @connection.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "key2=value2\n" } - @connection.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDNIC\n" } - @connection.should_receive(:write).with("ACK NIC\n").once().returns { |request| request.length } - @connection.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain four keys" - assert info.include?("NICINFO") - end -end -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 19:43:29 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 15:43:29 -0400 Subject: [Ovirt-devel] [PATCH: server 2/3] Added new host-register script to ovirt server. In-Reply-To: <1247255010-13742-2-git-send-email-arroy@redhat.com> References: <1247255010-13742-1-git-send-email-arroy@redhat.com> <1247255010-13742-2-git-send-email-arroy@redhat.com> Message-ID: <1247255010-13742-3-git-send-email-arroy@redhat.com> host-register is a qmf ruby console the replaces the identify node functionality in host-browser, by interfacing with the matahari qmf agent on the node to stat node hardware data. host-browser still performs kerberos related duties as before. --- conf/ovirt-host-register | 54 ++++ installer/modules/ovirt/manifests/ovirt.pp | 6 + ovirt-server.spec.in | 5 + src/host-browser/host-register.rb | 468 ++++++++++++++++++++++++++++ 4 files changed, 533 insertions(+), 0 deletions(-) create mode 100644 conf/ovirt-host-register create mode 100755 src/host-browser/host-register.rb diff --git a/conf/ovirt-host-register b/conf/ovirt-host-register new file mode 100644 index 0000000..9f47e23 --- /dev/null +++ b/conf/ovirt-host-register @@ -0,0 +1,54 @@ +#!/bin/bash +# +# +# ovirt-host-register startup script for ovirt-host-register +# +# chkconfig: - 97 03 +# description: ovirt-host-register is an essential component of the \ +# ovirt VM manager, handling the bookkeeping for hw nodes managed \ +# by the server. +# + +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + +export RAILS_ENV="${RAILS_ENV:-production}" + +DAEMON=/usr/share/ovirt-server/host-browser/host-register.rb + +. /etc/init.d/functions + +start() { + echo -n "Starting ovirt-host-register: " + daemon $DAEMON + RETVAL=$? + echo +} + +stop() { + echo -n "Shutting down ovirt-host-register: " + killproc host-register.rb + RETVAL=$? + echo +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + status) + status $DAEMON + RETVAL=$? + ;; + *) + echo "Usage: ovirt-host-register {start|stop|restart|status}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/installer/modules/ovirt/manifests/ovirt.pp b/installer/modules/ovirt/manifests/ovirt.pp index 636cd6e..b018a00 100644 --- a/installer/modules/ovirt/manifests/ovirt.pp +++ b/installer/modules/ovirt/manifests/ovirt.pp @@ -146,6 +146,12 @@ class ovirt::setup { ensure => running } + service {"ovirt-host-register" : + enable => true, + require => [Package[ovirt-server],Single_Exec[db_migrate]], + ensure => running + } + service {"ovirt-host-collect" : enable => true, require => [Package[ovirt-server],Single_Exec[db_migrate]], diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index a315381..1bf73c7 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -102,6 +102,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__install} -p -m0644 %{pbuild}/conf/%{name}.crontab %{buildroot}%{_sysconfdir}/cron.d/%{name} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-browser %{buildroot}%{_initrddir} +%{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-register %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-db-omatic %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-agent %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-collect %{buildroot}%{_initrddir} @@ -183,6 +184,7 @@ fi # on; otherwise, we respect the choices the administrator already has made. # check this by seeing if each daemon is already installed %daemon_chkconfig_post -d ovirt-host-browser +%daemon_chkconfig_post -d ovirt-host-register %daemon_chkconfig_post -d ovirt-db-omatic %daemon_chkconfig_post -d ovirt-agent %daemon_chkconfig_post -d ovirt-host-collect @@ -193,6 +195,7 @@ fi %preun if [ "$1" = 0 ] ; then /sbin/service ovirt-host-browser stop > /dev/null 2>&1 + /sbin/service ovirt-host-register stop > /dev/null 2>&1 /sbin/service ovirt-db-omatic stop > /dev/null 2>&1 /sbin/service ovirt-agent stop > /dev/null 2>&1 /sbin/service ovirt-host-collect stop > /dev/null 2>&1 @@ -200,6 +203,7 @@ if [ "$1" = 0 ] ; then /sbin/service ovirt-taskomatic stop > /dev/null 2>&1 /sbin/service ovirt-vnc-proxy stop > /dev/null 2>&1 /sbin/chkconfig --del ovirt-host-browser + /sbin/chkconfig --del ovirt-host-register /sbin/chkconfig --del ovirt-db-omatic /sbin/chkconfig --del ovirt-agent /sbin/chkconfig --del ovirt-host-collect @@ -215,6 +219,7 @@ fi %{_bindir}/ovirt-add-host %{_bindir}/ovirt-vm2node %{_initrddir}/ovirt-host-browser +%{_initrddir}/ovirt-host-register %{_initrddir}/ovirt-db-omatic %{_initrddir}/ovirt-agent %{_initrddir}/ovirt-host-collect diff --git a/src/host-browser/host-register.rb b/src/host-browser/host-register.rb new file mode 100755 index 0000000..3fdf204 --- /dev/null +++ b/src/host-browser/host-register.rb @@ -0,0 +1,468 @@ +#!/usr/bin/ruby + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), ".") + +require 'rubygems' +require 'qpid' +require 'monitor' +require 'dutils' +require 'daemons' +require 'optparse' +require 'logger' + +include Daemonize + +# This sad and pathetic readjustment to ruby logger class is +# required to fix the formatting because rails does the same +# thing but overrides it to just the message. +# +# See eg: http://osdir.com/ml/lang.ruby.rails.core/2007-01/msg00082.html +# +class Logger + def format_message(severity, timestamp, progname, msg) + "#{severity} #{timestamp} (#{$$}) #{msg}\n" + end +end + +$logfile = '/var/log/ovirt-server/host-register.log' + +class HostRegister < Qpid::Qmf::Console + + # Use monitor mixin for mutual exclusion around checks to heartbeats + # and updates to objects/heartbeats. + + include MonitorMixin + + def initialize() + super() + @cached_hosts = {} + @heartbeats = {} + @broker = nil + @debug = false + + @do_daemon = true + + opts = OptionParser.new do |opts| + opts.on('-h', '--help', 'Print help message') do + puts opts + exit + end + opts.on('-n', '--nodaemon', 'Run interactively (useful for debugging)') do |n| + @do_daemon = false + end + opts.on('-d', '--debug', 'Print out debug messages') do + @debug = true + end + end + begin + opts.parse!(ARGV) + rescue OptionParser::InvalidOption + puts opts + exit + end + + if @do_daemon + # XXX: This gets around a problem with paths for the database stuff. + # Normally daemonize would chdir to / but the paths for the database + # stuff are relative so it breaks it.. It's either this or rearrange + # things so the db stuff is included after daemonizing. + pwd = Dir.pwd + daemonize + Dir.chdir(pwd) + @logger = Logger.new($logfile) + else + @logger = Logger.new(STDERR) + end + @logger.info 'hostregister started.' + + begin + ensure_credentials + + database_connect + + server, port = nil + sleepy = 5 + while true do + server, port = get_srv('qpidd', 'tcp') + break if server + @logger.error "Unable to determine qpid server from DNS SRV record, retrying.." if not server + sleep(sleepy) + sleepy *= 2 if sleepy < 120 + end + + @logger.info "Connecting to amqp://#{server}:#{port}" + @session = Qpid::Qmf::Session.new(:console => self, :manage_connections => true) + @broker = @session.add_broker("amqp://#{server}:#{port}", :mechanism => 'GSSAPI') + + rescue Exception => ex + @logger.error "Error in hostregister: #{ex}" + @logger.error ex.backtrace + end + end + + def debugputs(msg) + puts msg if @debug == true and @do_daemon == false + end + + def ensure_credentials() + get_credentials('qpidd') + + Thread.new do + while true do + sleep(3600) + get_credentials('qpidd') + end + end + end + + def broker_connected(broker) + @logger.info 'Connected to broker.' + end + + def broker_disconnected(broker) + @logger.error 'Broker disconnected.' + end + + def agent_disconnected(agent) + synchronize do + debugputs "Marking objects for agent #{agent.broker.broker_bank}.#{agent.agent_bank} inactive" + @cached_hosts.keys.each do |objkey| + if @cached_hosts[objkey][:broker_bank] == agent.broker.broker_bank and + @cached_hosts[objkey][:agent_bank] == agent.agent_bank + + cached_host = @cached_hosts[objkey] + cached_host[:active] = false + @logger.info "Host #{cached_host['hostname']} marked inactive" + end + end # @objects.keys.each + end # synchronize do + end + + def agent_connected(agent) + synchronize do + debugputs "Marking objects for agent #{agent.broker.broker_bank}.#{agent.agent_bank} active" + @cached_hosts.keys.each do |objkey| + if @cached_hosts[objkey][:broker_bank] == agent.broker.broker_bank and + @cached_hosts[objkey][:agent_bank] == agent.agent_bank + + cached_host = @cached_hosts[objkey] + cached_host[:active] = true + @logger.info "Host #{cached_host['hostname']} marked active" + end + end # @objects.keys.each + end # synchronize do + end + + def update_cpus(host_qmf, host_db, cpu_info) + + @logger.info "Updating CPU info for host #{host_qmf.hostname}" + debugputs "Broker reports #{cpu_info.length} cpus for host #{host_qmf.hostname}" + + # delete an existing CPUs and create new ones based on the data + @logger.info "Deleting any existing CPUs for host #{host_qmf.hostname}" + Cpu.delete_all(['host_id = ?', host_db.id]) + + @logger.info "Saving new CPU records for host #{host_qmf.hostname}" + cpu_info.each do |cpu| + flags = (cpu.flags.length > 255) ? "#{cpu.flags[0..251]}..." : cpu.flags + detail = Cpu.new( + 'cpu_number' => cpu.cpunum, + 'core_number' => cpu.corenum, + 'number_of_cores' => cpu.numcores, + 'vendor' => cpu.vendor, + 'model' => cpu.model.to_s, + 'family' => cpu.family.to_s, + 'cpuid_level' => cpu.cpuid_lvl, + 'speed' => cpu.speed.to_s, + 'cache' => cpu.cache.to_s, + 'flags' => flags) + + host_db.cpus << detail + + debugputs "Added new CPU for #{host_qmf.hostname}: " + debugputs "CPU # : #{cpu.cpunum}" + debugputs "Core # : #{cpu.corenum}" + debugputs "Total Cores : #{cpu.numcores}" + debugputs "Vendor : #{cpu.vendor}" + debugputs "Model : #{cpu.model}" + debugputs "Family : #{cpu.family}" + debugputs "Cpuid_lvl : #{cpu.cpuid_lvl}" + debugputs "Speed : #{cpu.speed}" + debugputs "Cache : #{cpu.cache}" + debugputs "Flags : #{flags}" + end + + @logger.info "Saved #{cpu_info.length} cpus for #{host_qmf.hostname}" + end + + def update_nics(host_qmf, host_db, nic_info) + + # Update the NIC details for this host: + # -if the NIC exists, then update the IP address + # -if the NIC does not exist, create it + # -any nic not in this list is deleted + + @logger.info "Updating NIC records for host #{host_qmf.hostname}" + debugputs "Broker reports #{nic_info.length} NICs for host" + + nics = Array.new + nics_to_delete = Array.new + + host_db.nics.each do |nic| + found = false + + nic_info.each do |detail| + # if we have a match, then update the database and remove + # the received data to avoid creating a dupe later + @logger.info "Searching for existing record for: #{detail.macaddr.upcase} in host #{host_qmf.hostname}" + if detail.macaddr.upcase == nic.mac + @logger.info "Updating details for: #{detail.interface} [#{nic.mac}]}" + nic.bandwidth = detail.bandwidth + nic.interface_name = detail.interface + nic.save! + found = true + nic_info.delete(detail) + end + end + + # if the record wasn't found, then remove it from the database + unless found + @logger.info "Marking NIC for removal: #{nic.interface_name} [#{nic.mac}]" + nics_to_delete << nic + end + end + + debugputs "Deleting #{nics_to_delete.length} NICs that are no longer part of host #{host_qmf.hostname}" + nics_to_delete.each do |nic| + @logger.info "Removing NIC: #{nic.interface_name} [#{nic.mac}]" + host_db.nics.delete(nic) + end + + # iterate over any nics left and create new records for them. + debugputs "Adding new records for #{nic_info.length} NICs to host #{host_qmf.hostname}" + nic_info.each do |nic| + detail = Nic.new( + 'mac' => nic.macaddr.upcase, + 'bandwidth' => nic.bandwidth, + 'interface_name' => nic.interface, + 'usage_type' => 1) + + host_db.nics << detail + + @logger.info "Added NIC #{nic.interface} with MAC #{nic.macaddr} to host #{host_qmf.hostname}" + end + end + + def object_props(broker, obj) + target = obj.schema.klass_key.package + type = obj.schema.klass_key.klass_name + return if target != 'com.redhat.matahari' or type != 'host' + + # Fix a race where the properties of an object are published by a reconnecting + # host (thus marking it active) right before the heartbeat timer considers it dead + # (and marks it inactive) + @heartbeats.delete("#{obj.object_id.agent_bank}.#{obj.object_id.broker_bank}") + + already_cache = false + already_in_db = false + + # Grab the cpus and nics associated before we take any locks + cpu_info = @session.objects(:class => 'cpu', 'host' => obj.object_id) + nic_info = @session.objects(:class => 'nic', 'host' => obj.object_id) + + synchronize do + cached_host = @cached_hosts[obj.object_id.to_s] + host = Host.find(:first, :conditions => ['hostname = ?', obj.hostname]) + + already_cache = true if cached_host != nil + already_in_db = true if host != nil + + @logger.info "Node #{obj.hostname} with UUID #{obj.uuid} detected, already exists in db? is #{already_in_db}" + + # Four cases apply here: + # 1. Not in db, but is cached: + # Impossible, since we don't cache unless we could add to db. + # Throw an exception here. + # + # 2. Not in db or cache: + # Seeing host for the first time. Put in db and cache. + # + # 3. In db, not in cache: + # Have not seen host since last invocation of daemon. Update + # db entry, and add to cache. + # + # 4. In db, in cache: + # Property updated; update in db and cache. + + # Case 1: + if already_cache and not already_in_db + error = "Error: Found host in cache that is not present in db!" + @logger.error error + throw error + end + + # All other cases collapse to add-or-update db and cache + + # Add to db if necessary + if not already_in_db + debugputs "Didn't find host #{obj.hostname} in db!" + begin + @logger.info "Creating a new record for #{obj.hostname}..." + + host = Host.create( + 'uuid' => obj.uuid, + 'hostname' => obj.hostname, + 'hypervisor_type' => obj.hypervisor, + 'arch' => obj.arch, + 'memory' => obj.memory, + 'is_disabled' => 0, + 'hardware_pool' => HardwarePool.get_default_pool, + # Let host-status mark it available when it + # successfully connects to it via libvirt. + 'state' => Host::STATE_UNAVAILABLE) + + debugputs 'Added new host:' + debugputs "uuid: #{obj.uuid}" + debugputs "hostname: #{obj.hostname}" + debugputs "hypervisor: #{obj.hypervisor}" + debugputs "arch: #{obj.arch}" + debugputs "memory: #{obj.memory}" + + rescue Exception => error + @logger.error "Error while creating record: #{error.message}" + # We haven't added the host to the db, and it isn't cached, so we just + # return without having done anything. To retry, the host will have to + # restart its agent. + return + end + else + @logger.info "Updating record for #{obj.hostname}..." + host.uuid = obj.uuid + host.hostname = obj.hostname + host.hypervisor_type = obj.hypervisor + host.arch = obj.arch + host.memory = obj.memory + + debugputs 'Updated host #{obj.hostname} with new details:' + debugputs "uuid: #{obj.uuid}" + debugputs "hypervisor: #{obj.hypervisor}" + debugputs "arch: #{obj.arch}" + debugputs "memory: #{obj.memory}" + end # not already_in_db + + update_cpus(obj, host, cpu_info) + update_nics(obj, host, nic_info) + + host.save! + debugputs "Finished flushing host #{obj.hostname} to db" + + # Add to cache if necessary + if not already_cache + debugputs "Did not find host #{obj.hostname} in cache!" + # Check if there is a stale entry for host that we can refresh. + # We iterate over each host in the cache. If we find an entry + # that matches hostname, we rekey it in hash. + @cached_hosts.each do |objkey, h| + if h['hostname'] == obj.hostname + debugputs "Found stale entry for #{obj.hostname} with key #{objkey}" + debugputs "Refreshing with key #{obj.object_id.to_s}" + @cached_hosts.delete(objkey) + @cached_hosts[obj.object_id.to_s] = h + cached_host = h + break + end + end # @cached_hosts.each + + if cached_host == nil + debugputs "Creating new entry for #{obj.hostname} with key #{obj.object_id.to_s}" + cached_host = {} + @cached_hosts[obj.object_id.to_s] = cached_host + end + + # By now, we either rekeyed a stale entry or started a new one. + # Update the bookkeeping parts of the data. + cached_host[:obj_key] = obj.object_id.to_s + cached_host[:broker_bank] = obj.object_id.broker_bank + cached_host[:agent_bank] = obj.object_id.agent_bank + end # not already_cache + + # For now, only cache identity information (leave CPU/NIC/etc. to db only) + cached_host[:active] = true + cached_host['hostname'] = obj.hostname + cached_host['uuid'] = obj.uuid + cached_host['hypervisor'] = obj.hypervisor + cached_host['arch'] = obj.arch + end # synchronize do + end # def object_props + + def heartbeat(agent, timestamp) + return if agent == nil + synchronize do + bank_key = "#{agent.agent_bank}.#{agent.broker.broker_bank}" + @heartbeats[bank_key] = [agent, timestamp] + end + end + + def new_agent(agent) + key = "#{agent.agent_bank}.#{agent.broker.broker_bank}" + debugputs "Agent #{key} connected!" + agent_connected(agent) + end + + def del_agent(agent) + key = "#{agent.agent_bank}.#{agent.broker.broker_bank}" + debugputs "Agent #{key} disconnected!" + @heartbeats.delete(key) + agent_disconnected(agent) + end + + def check_heartbeats() + begin + while true + sleep(5) + synchronize do + # Get seconds from the epoch + t = Time.new.to_i + + @heartbeats.keys.each do | key | + agent, timestamp = @heartbeats[key] + + # Heartbeats from qpid are in microseconds, we just need seconds.. + s = timestamp / 1000000000 + delta = t - s + + if delta > 30 + # No heartbeat for 30 seconds.. deal with dead/disconnected agent. + debugputs "Agent #{key} timed out!" + @heartbeats.delete(key) + agent_disconnected(agent) + end + end + + # Print out current objects + debugputs '===== Current Objects =====' + @cached_hosts.keys.each do |objkey| + cached_host = @cached_hosts[objkey] + cached_host.each do |key, val| + debugputs "\t#{key} : #{val}\n" + end + end + debugputs '===== Done =====' + + end # synchronize do + end # while true + rescue Exception => ex + @logger.error "Error in hostregister: #{ex}" + @logger.error ex.backtrace + end # end begin-rescue + end # def check_heartbeats + +end # Class HostRegister + +def main() + hostreg = HostRegister.new() + hostreg.check_heartbeats() +end + +main() -- 1.6.2.5 From arroy at redhat.com Fri Jul 10 19:43:30 2009 From: arroy at redhat.com (Arjun Roy) Date: Fri, 10 Jul 2009 15:43:30 -0400 Subject: [Ovirt-devel] [PATCH: server 3/3] Temporarily setting managed connections to false till ruby qmf console bug fix is pushed. In-Reply-To: <1247255010-13742-3-git-send-email-arroy@redhat.com> References: <1247255010-13742-1-git-send-email-arroy@redhat.com> <1247255010-13742-2-git-send-email-arroy@redhat.com> <1247255010-13742-3-git-send-email-arroy@redhat.com> Message-ID: <1247255010-13742-4-git-send-email-arroy@redhat.com> --- src/host-browser/host-register.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/host-browser/host-register.rb b/src/host-browser/host-register.rb index 3fdf204..5ca1e01 100755 --- a/src/host-browser/host-register.rb +++ b/src/host-browser/host-register.rb @@ -92,7 +92,7 @@ class HostRegister < Qpid::Qmf::Console end @logger.info "Connecting to amqp://#{server}:#{port}" - @session = Qpid::Qmf::Session.new(:console => self, :manage_connections => true) + @session = Qpid::Qmf::Session.new(:console => self, :manage_connections => false) @broker = @session.add_broker("amqp://#{server}:#{port}", :mechanism => 'GSSAPI') rescue Exception => ex -- 1.6.2.5 From dpierce at redhat.com Fri Jul 10 20:00:16 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 16:00:16 -0400 Subject: [Ovirt-devel] [PATCH node] Add VLAN support to network configuration. rhbz#510116 In-Reply-To: <1247256016-4769-1-git-send-email-dpierce@redhat.com> References: <1247256016-4769-1-git-send-email-dpierce@redhat.com> Message-ID: <1247256016-4769-2-git-send-email-dpierce@redhat.com> While configuring the management interface, the user can indicate whether the device will participate in a VLAN. If so, an additional property, VLAN yes, is added to the initscript for the interface. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 86 ++++++++++++++++++++++++++++++-------- 1 files changed, 68 insertions(+), 18 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 8380187..26fc664 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -21,6 +21,8 @@ NTPCONF_FILE_ROOT="/files/etc/ntp" NTP_CONFIG_FILE="/etc/ntp.conf" NTPSERVERS="" CONFIGURED_NIC="" +VLAN_ID="" +VL_ROOT="" # if local storage is not configured, then exit the script if ! is_local_storage_configured; then @@ -59,6 +61,9 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + rm -rf /etc/sysconfig/network-scripts/ifcfg-* + cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo else printf "\nAborting...\n" return @@ -79,15 +84,16 @@ function configure_interface local BR_ROOT="$IFCONFIG_FILE_ROOT-$BRIDGE" local BR_CONFIG="rm $BR_ROOT\nset $BR_ROOT/DEVICE $BRIDGE" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/TYPE Bridge" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/PEERNTP yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DELAY 0" - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/BRIDGE $BRIDGE" - local BR_CONFIG_BASE=$BR_CONFIG if [ -z "$AUTO" ]; then while true; do + local VL_CONFIG="" + printf "\n" LINK=`ethtool $NIC| grep "Link detected"`:u [ -z "$LINK" ] && return @@ -102,24 +108,57 @@ function configure_interface ethtool --identify $NIC 10 fi + ask_yes_or_no "Include VLAN support (y/n/a)? " true true + case $? in + 0) + while true; do + read -ep "What is the VLAN ID for this device (a=abort) " + case $REPLY in + A|a) CONFIGURED_NIC=""; return;; + *) + if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then + VLAN_ID=$REPLY + VL_ROOT="${IF_ROOT}.${VLAN_ID}" + VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${BRIDGE}.${VLAN_ID}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" + VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" + break + fi + ;; + esac + done + ;; + 1) IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" ;; + 2) + CONFIGURED_NIC="" + VLAN_ID="" + return;; + esac + read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " case $REPLY in D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" + conf="$conf\nset $root/BOOTPROTO dhcp" ;; S|s) printf "\n" read -ep "IP Address: "; IPADDR=$REPLY read -ep " Netmask: "; NETMASK=$REPLY read -ep " Gateway: "; GATEWAY=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" + + conf="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" + conf="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" if [ -n "${GATEWAY}" ]; then - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" + conf="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" fi ;; - A|a) CONFIGURED_NIC=""; return ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; esac printf "\n" @@ -127,21 +166,25 @@ function configure_interface case $REPLY in S|s) read -ep "IPv6 Address: "; IPADDR=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" ;; D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" ;; U|u) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return ;; - A|a) CONFIGURED_NIC=""; return;; esac printf "\n" @@ -150,8 +193,14 @@ function configure_interface 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [[ -n "$VLAN_ID" ]]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi break ;; 1) @@ -159,6 +208,7 @@ function configure_interface ;; 2) CONFIGURED_NIC="" + VLAN_ID="" return ;; esac -- 1.6.2.5 From dpierce at redhat.com Fri Jul 10 20:00:15 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 16:00:15 -0400 Subject: [Ovirt-devel] No functional change... Message-ID: <1247256016-4769-1-git-send-email-dpierce@redhat.com> Changes the name for the vlan configuration file to make things clear. From dpierce at redhat.com Fri Jul 10 20:36:36 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 16:36:36 -0400 Subject: [Ovirt-devel] New patch with unmount_config for old initscripts... Message-ID: <1247258197-7588-1-git-send-email-dpierce@redhat.com> This patch includes a call to unmount_config to ensure old initscripts are not bindmounted when they are deleted. From dpierce at redhat.com Fri Jul 10 20:36:37 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 16:36:37 -0400 Subject: [Ovirt-devel] [PATCH node] Add VLAN support to network configuration. rhbz#510116 In-Reply-To: <1247258197-7588-1-git-send-email-dpierce@redhat.com> References: <1247258197-7588-1-git-send-email-dpierce@redhat.com> Message-ID: <1247258197-7588-2-git-send-email-dpierce@redhat.com> While configuring the management interface, the user can indicate whether the device will participate in a VLAN. If so, an additional property, VLAN yes, is added to the initscript for the interface. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 87 +++++++++++++++++++++++++++++++-------- 1 files changed, 69 insertions(+), 18 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 8380187..a3b111e 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -21,6 +21,8 @@ NTPCONF_FILE_ROOT="/files/etc/ntp" NTP_CONFIG_FILE="/etc/ntp.conf" NTPSERVERS="" CONFIGURED_NIC="" +VLAN_ID="" +VL_ROOT="" # if local storage is not configured, then exit the script if ! is_local_storage_configured; then @@ -59,6 +61,10 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + unmount_config /etc/sysconfig/network-scripts/ifcfg-* + rm -rf /etc/sysconfig/network-scripts/ifcfg-* + cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo else printf "\nAborting...\n" return @@ -79,15 +85,16 @@ function configure_interface local BR_ROOT="$IFCONFIG_FILE_ROOT-$BRIDGE" local BR_CONFIG="rm $BR_ROOT\nset $BR_ROOT/DEVICE $BRIDGE" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/TYPE Bridge" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/PEERNTP yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DELAY 0" - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/BRIDGE $BRIDGE" - local BR_CONFIG_BASE=$BR_CONFIG if [ -z "$AUTO" ]; then while true; do + local VL_CONFIG="" + printf "\n" LINK=`ethtool $NIC| grep "Link detected"`:u [ -z "$LINK" ] && return @@ -102,24 +109,57 @@ function configure_interface ethtool --identify $NIC 10 fi + ask_yes_or_no "Include VLAN support (y/n/a)? " true true + case $? in + 0) + while true; do + read -ep "What is the VLAN ID for this device (a=abort) " + case $REPLY in + A|a) CONFIGURED_NIC=""; return;; + *) + if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then + VLAN_ID=$REPLY + VL_ROOT="${IF_ROOT}.${VLAN_ID}" + VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${BRIDGE}.${VLAN_ID}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" + VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" + break + fi + ;; + esac + done + ;; + 1) IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" ;; + 2) + CONFIGURED_NIC="" + VLAN_ID="" + return;; + esac + read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " case $REPLY in D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" + conf="$conf\nset $root/BOOTPROTO dhcp" ;; S|s) printf "\n" read -ep "IP Address: "; IPADDR=$REPLY read -ep " Netmask: "; NETMASK=$REPLY read -ep " Gateway: "; GATEWAY=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" + + conf="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" + conf="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" if [ -n "${GATEWAY}" ]; then - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" + conf="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" fi ;; - A|a) CONFIGURED_NIC=""; return ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; esac printf "\n" @@ -127,21 +167,25 @@ function configure_interface case $REPLY in S|s) read -ep "IPv6 Address: "; IPADDR=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" ;; D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" ;; U|u) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return ;; - A|a) CONFIGURED_NIC=""; return;; esac printf "\n" @@ -150,8 +194,14 @@ function configure_interface 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [[ -n "$VLAN_ID" ]]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi break ;; 1) @@ -159,6 +209,7 @@ function configure_interface ;; 2) CONFIGURED_NIC="" + VLAN_ID="" return ;; esac -- 1.6.2.5 From imain at redhat.com Fri Jul 10 21:03:23 2009 From: imain at redhat.com (Ian Main) Date: Fri, 10 Jul 2009 14:03:23 -0700 Subject: [Ovirt-devel] [PATCH: server 0/3] Add host-register.rb (replaces host-browser.rb in part) In-Reply-To: <1247255010-13742-1-git-send-email-arroy@redhat.com> References: <1247255010-13742-1-git-send-email-arroy@redhat.com> Message-ID: <20090710140323.3231cbb5@tp.mains.net> On Fri, 10 Jul 2009 15:43:27 -0400 Arjun Roy wrote: > Removes node identification functionality from host-browser.rb and adds a new script, > host-register.rb, that takes over that functionality. > > The chief difference is that host-browser used a simple TCP server setup to get data > from the node, while host-register uses the qpid bus to do so. Specifically, it > communicates with the matahari qmf agent added to the node in two related patchsets to > node and node-image. > > At present, there is a bug in the ruby console api that affects host-register.rb. > As a temporary workaround, managed connections have been disabled. > > There are two related patchsets for node and node-image that must be applied as well > to test out the changes in this patchset. > > This is the third version of this patchset, and hopefully all the kinks have been worked > out by now. :) ACK From imain at redhat.com Fri Jul 10 21:03:55 2009 From: imain at redhat.com (Ian Main) Date: Fri, 10 Jul 2009 14:03:55 -0700 Subject: [Ovirt-devel] [PATCH: node-image] Added matahari package to node image, replacing ovirt-identify-node. In-Reply-To: <1247244489-11281-1-git-send-email-arroy@redhat.com> References: <1247244489-11281-1-git-send-email-arroy@redhat.com> Message-ID: <20090710140355.232d534b@tp.mains.net> On Fri, 10 Jul 2009 12:48:09 -0400 Arjun Roy wrote: > Matahari is a QMF agent that exposes node information and will provide > methods to administer some functions of the node as well. As an example, > it currently provides a method to blink the lights of a network adapter > to aid in physical identification. > > Note that there is a related patch to ovirt-node (to add startup script support > for this package) and a related patch to server (replacing host-browser with the > new host-register, which interfaces with matahari on the node). All three of these > must be applied to test out the changes. ACK From imain at redhat.com Fri Jul 10 21:04:20 2009 From: imain at redhat.com (Ian Main) Date: Fri, 10 Jul 2009 14:04:20 -0700 Subject: [Ovirt-devel] [PATCH: node 0/3] replace ovirt-identify-node with matahari In-Reply-To: <1247244475-11239-1-git-send-email-arroy@redhat.com> References: <1247244475-11239-1-git-send-email-arroy@redhat.com> Message-ID: <20090710140420.5946d116@tp.mains.net> On Fri, 10 Jul 2009 12:47:52 -0400 Arjun Roy wrote: > This patchset removes ovirt-identify-node and ovirt-listen awake. > It adds startup script support for the matahari qmf agent, which takes > over the responsibility for communicating node hardware capabilities > to the ovirt-server. > > On the server side, host-browser has had its node identification > functionality replaced by a new script, host-register, which is what > interfaces with the matahari qmf agent over the qpid bus. > > There are related patchsets to node-image and server that must be applied > along with this set to test things out. ACK I'll push all these soon. Ian From apevec at redhat.com Fri Jul 10 21:27:08 2009 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Jul 2009 23:27:08 +0200 Subject: [Ovirt-devel] [PATCH node] display network interface driver name and MAC 2. Message-ID: <1247261228-13192-1-git-send-email-apevec@redhat.com> add header with column description --- scripts/ovirt-config-networking | 13 ++++++------- 1 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 8380187..e10ed12 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -351,19 +351,18 @@ else printf "Configuring the network will destroy any existing networking\n" printf "configuration on this system.\n" printf "***** WARNING *****\n" - pixied=false + printf "\nPhysical Networking Devices (*=PXE boot interface)\n" + printf "Name\tDriver\tMAC\n" for nic in $NICS; do driver=$(basename $(readlink /sys/class/net/$nic/device/driver)) mac=$(cat /sys/class/net/$nic/address) if [ "$nic" = "$OVIRT_BOOTIF" ]; then - nic="*$nic" - pixied=true + pxe="*" + else + pxe=" " fi - printf "%s\t%s\t%s\n" $nic $driver $mac + printf "%s%s\t%s\t%s\n" $pxe $nic $driver $mac done - if $pixied; then - printf "*=PXE boot interface\n" - fi DNS="DNS" NTP="NTP" -- 1.6.0.6 From dpierce at redhat.com Fri Jul 10 21:30:56 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 17:30:56 -0400 Subject: [Ovirt-devel] [PATCH node] display network interface driver name and MAC 2. In-Reply-To: <1247261228-13192-1-git-send-email-apevec@redhat.com> References: <1247261228-13192-1-git-send-email-apevec@redhat.com> Message-ID: <20090710213056.GG14459@mcpierce-laptop.rdu.redhat.com> On Fri, Jul 10, 2009 at 11:27:08PM +0200, Alan Pevec wrote: > add header with column description > --- > scripts/ovirt-config-networking | 13 ++++++------- > 1 files changed, 6 insertions(+), 7 deletions(-) > > diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking > index 8380187..e10ed12 100755 > --- a/scripts/ovirt-config-networking > +++ b/scripts/ovirt-config-networking > @@ -351,19 +351,18 @@ else > printf "Configuring the network will destroy any existing networking\n" > printf "configuration on this system.\n" > printf "***** WARNING *****\n" > - pixied=false > + printf "\nPhysical Networking Devices (*=PXE boot interface)\n" > + printf "Name\tDriver\tMAC\n" > for nic in $NICS; do > driver=$(basename $(readlink /sys/class/net/$nic/device/driver)) > mac=$(cat /sys/class/net/$nic/address) > if [ "$nic" = "$OVIRT_BOOTIF" ]; then > - nic="*$nic" > - pixied=true > + pxe="*" > + else > + pxe=" " > fi > - printf "%s\t%s\t%s\n" $nic $driver $mac > + printf "%s%s\t%s\t%s\n" $pxe $nic $driver $mac > done > - if $pixied; then > - printf "*=PXE boot interface\n" > - fi > > DNS="DNS" > NTP="NTP" > -- > 1.6.0.6 ACK. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Fri Jul 10 22:18:40 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 18:18:40 -0400 Subject: [Ovirt-devel] Fixes missed variable names... Message-ID: <1247264321-3310-1-git-send-email-dpierce@redhat.com> After the last refactor, missed restoring variable names. Fixed in this patch. From dpierce at redhat.com Fri Jul 10 22:18:41 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 18:18:41 -0400 Subject: [Ovirt-devel] [PATCH node] Add VLAN support to network configuration. rhbz#510116 In-Reply-To: <1247264321-3310-1-git-send-email-dpierce@redhat.com> References: <1247264321-3310-1-git-send-email-dpierce@redhat.com> Message-ID: <1247264321-3310-2-git-send-email-dpierce@redhat.com> While configuring the management interface, the user can indicate whether the device will participate in a VLAN. If so, an additional property, VLAN yes, is added to the initscript for the interface. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 85 +++++++++++++++++++++++++++++++-------- 1 files changed, 68 insertions(+), 17 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 8380187..0d5765a 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -21,6 +21,8 @@ NTPCONF_FILE_ROOT="/files/etc/ntp" NTP_CONFIG_FILE="/etc/ntp.conf" NTPSERVERS="" CONFIGURED_NIC="" +VLAN_ID="" +VL_ROOT="" # if local storage is not configured, then exit the script if ! is_local_storage_configured; then @@ -59,6 +61,10 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + unmount_config /etc/sysconfig/network-scripts/ifcfg-* + rm -rf /etc/sysconfig/network-scripts/ifcfg-* + cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo else printf "\nAborting...\n" return @@ -79,15 +85,16 @@ function configure_interface local BR_ROOT="$IFCONFIG_FILE_ROOT-$BRIDGE" local BR_CONFIG="rm $BR_ROOT\nset $BR_ROOT/DEVICE $BRIDGE" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/TYPE Bridge" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/PEERNTP yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DELAY 0" - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/BRIDGE $BRIDGE" - local BR_CONFIG_BASE=$BR_CONFIG if [ -z "$AUTO" ]; then while true; do + local VL_CONFIG="" + printf "\n" LINK=`ethtool $NIC| grep "Link detected"`:u [ -z "$LINK" ] && return @@ -102,6 +109,34 @@ function configure_interface ethtool --identify $NIC 10 fi + ask_yes_or_no "Include VLAN support (y/n/a)? " true true + case $? in + 0) + while true; do + read -ep "What is the VLAN ID for this device (a=abort) " + case $REPLY in + A|a) CONFIGURED_NIC=""; return;; + *) + if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then + VLAN_ID=$REPLY + VL_ROOT="${IF_ROOT}.${VLAN_ID}" + VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" + VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" + break + fi + ;; + esac + done + ;; + 1) IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" ;; + 2) + CONFIGURED_NIC="" + VLAN_ID="" + return;; + esac + read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " case $REPLY in D|d) @@ -112,14 +147,19 @@ function configure_interface read -ep "IP Address: "; IPADDR=$REPLY read -ep " Netmask: "; NETMASK=$REPLY read -ep " Gateway: "; GATEWAY=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" + + conf="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" + conf="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" if [ -n "${GATEWAY}" ]; then - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" + conf="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" fi ;; - A|a) CONFIGURED_NIC=""; return ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; esac printf "\n" @@ -127,21 +167,25 @@ function configure_interface case $REPLY in S|s) read -ep "IPv6 Address: "; IPADDR=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" ;; D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" ;; U|u) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + conf="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return ;; - A|a) CONFIGURED_NIC=""; return;; esac printf "\n" @@ -150,8 +194,14 @@ function configure_interface 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [[ -n "$VLAN_ID" ]]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi break ;; 1) @@ -159,6 +209,7 @@ function configure_interface ;; 2) CONFIGURED_NIC="" + VLAN_ID="" return ;; esac -- 1.6.2.5 From dpierce at redhat.com Fri Jul 10 23:53:40 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 19:53:40 -0400 Subject: [Ovirt-devel] [PATCH node] Add VLAN support to network configuration. rhbz#510116 Message-ID: <1247270020-5429-1-git-send-email-dpierce@redhat.com> While configuring the management interface, the user can indicate whether the device will participate in a VLAN. If so, an additional property, VLAN yes, is added to the initscript for the interface. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 59 ++++++++++++++++++++++++++++++++++++-- 1 files changed, 55 insertions(+), 4 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 8380187..8f709c4 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -21,6 +21,8 @@ NTPCONF_FILE_ROOT="/files/etc/ntp" NTP_CONFIG_FILE="/etc/ntp.conf" NTPSERVERS="" CONFIGURED_NIC="" +VLAN_ID="" +VL_ROOT="" # if local storage is not configured, then exit the script if ! is_local_storage_configured; then @@ -59,6 +61,10 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + unmount_config /etc/sysconfig/network-scripts/ifcfg-* + rm -rf /etc/sysconfig/network-scripts/ifcfg-* + cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo else printf "\nAborting...\n" return @@ -79,15 +85,16 @@ function configure_interface local BR_ROOT="$IFCONFIG_FILE_ROOT-$BRIDGE" local BR_CONFIG="rm $BR_ROOT\nset $BR_ROOT/DEVICE $BRIDGE" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/TYPE Bridge" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/PEERNTP yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DELAY 0" - IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/BRIDGE $BRIDGE" - local BR_CONFIG_BASE=$BR_CONFIG if [ -z "$AUTO" ]; then while true; do + local VL_CONFIG="" + printf "\n" LINK=`ethtool $NIC| grep "Link detected"`:u [ -z "$LINK" ] && return @@ -102,6 +109,34 @@ function configure_interface ethtool --identify $NIC 10 fi + ask_yes_or_no "Include VLAN support (y/n/a)? " true true + case $? in + 0) + while true; do + read -ep "What is the VLAN ID for this device (a=abort) " + case $REPLY in + A|a) CONFIGURED_NIC=""; return;; + *) + if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then + VLAN_ID=$REPLY + VL_ROOT="${IF_ROOT}.${VLAN_ID}" + VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" + VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" + VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" + break + fi + ;; + esac + done + ;; + 1) IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" ;; + 2) + CONFIGURED_NIC="" + VLAN_ID="" + return;; + esac + read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " case $REPLY in D|d) @@ -112,6 +147,7 @@ function configure_interface read -ep "IP Address: "; IPADDR=$REPLY read -ep " Netmask: "; NETMASK=$REPLY read -ep " Gateway: "; GATEWAY=$REPLY + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" @@ -119,7 +155,11 @@ function configure_interface BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" fi ;; - A|a) CONFIGURED_NIC=""; return ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; esac printf "\n" @@ -141,7 +181,11 @@ function configure_interface BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" ;; - A|a) CONFIGURED_NIC=""; return;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; esac printf "\n" @@ -150,8 +194,14 @@ function configure_interface 0) IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [[ -n "$VLAN_ID" ]]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [[ -n "$VLAN_ID" ]]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi break ;; 1) @@ -159,6 +209,7 @@ function configure_interface ;; 2) CONFIGURED_NIC="" + VLAN_ID="" return ;; esac -- 1.6.2.5 From apevec at redhat.com Sat Jul 11 00:01:13 2009 From: apevec at redhat.com (Alan Pevec) Date: Sat, 11 Jul 2009 02:01:13 +0200 Subject: [Ovirt-devel] Re: [PATCH node] Add VLAN support to network configuration. rhbz#510116 In-Reply-To: <1247270020-5429-1-git-send-email-dpierce@redhat.com> References: <1247270020-5429-1-git-send-email-dpierce@redhat.com> Message-ID: <4A57D649.4040607@redhat.com> ACK From apevec at redhat.com Sat Jul 11 03:43:54 2009 From: apevec at redhat.com (Alan Pevec) Date: Sat, 11 Jul 2009 05:43:54 +0200 Subject: [Ovirt-devel] [PATCH node] fix mount_live again Message-ID: <1247283834-18925-1-git-send-email-apevec@redhat.com> fixes install from ISO --- scripts/ovirt-functions | 12 +++++++----- 1 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index e01ee0e..3f55656 100644 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -249,11 +249,13 @@ mount_live() { return 0 fi local live_dev=/dev/live - if [ ! -e $live_dev ] && losetup /dev/loop0|grep -q '\.iso'; then - # PXE boot - live_dev=/dev/loop0 - else - return 1 + if [ ! -e $live_dev ] + if losetup /dev/loop0|grep -q '\.iso'; then + # PXE boot + live_dev=/dev/loop0 + else + return 1 + fi fi mkdir -p /live mount -r $live_dev /live || mount $live_dev /live -- 1.6.0.6 From apevec at redhat.com Sat Jul 11 03:58:02 2009 From: apevec at redhat.com (Alan Pevec) Date: Sat, 11 Jul 2009 05:58:02 +0200 Subject: [Ovirt-devel] [PATCH node] fix mount_live again In-Reply-To: <1247283834-18925-1-git-send-email-apevec@redhat.com> References: <1247283834-18925-1-git-send-email-apevec@redhat.com> Message-ID: <1247284682-19096-1-git-send-email-apevec@redhat.com> fixes install from ISO --- scripts/ovirt-functions | 12 +++++++----- 1 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index e01ee0e..b87cebb 100644 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -249,11 +249,13 @@ mount_live() { return 0 fi local live_dev=/dev/live - if [ ! -e $live_dev ] && losetup /dev/loop0|grep -q '\.iso'; then - # PXE boot - live_dev=/dev/loop0 - else - return 1 + if [ ! -e $live_dev ]; then + if losetup /dev/loop0|grep -q '\.iso'; then + # PXE boot + live_dev=/dev/loop0 + else + return 1 + fi fi mkdir -p /live mount -r $live_dev /live || mount $live_dev /live -- 1.6.0.6 From dpierce at redhat.com Sat Jul 11 03:59:39 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Jul 2009 23:59:39 -0400 Subject: [Ovirt-devel] [PATCH node] fix mount_live again In-Reply-To: <1247284682-19096-1-git-send-email-apevec@redhat.com> References: <1247283834-18925-1-git-send-email-apevec@redhat.com> <1247284682-19096-1-git-send-email-apevec@redhat.com> Message-ID: <20090711035939.GA8415@mcpierce-laptop.mcpierce.org> On Sat, Jul 11, 2009 at 05:58:02AM +0200, Alan Pevec wrote: > fixes install from ISO > --- > scripts/ovirt-functions | 12 +++++++----- > 1 files changed, 7 insertions(+), 5 deletions(-) > > diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions > index e01ee0e..b87cebb 100644 > --- a/scripts/ovirt-functions > +++ b/scripts/ovirt-functions > @@ -249,11 +249,13 @@ mount_live() { > return 0 > fi > local live_dev=/dev/live > - if [ ! -e $live_dev ] && losetup /dev/loop0|grep -q '\.iso'; then > - # PXE boot > - live_dev=/dev/loop0 > - else > - return 1 > + if [ ! -e $live_dev ]; then > + if losetup /dev/loop0|grep -q '\.iso'; then > + # PXE boot > + live_dev=/dev/loop0 > + else > + return 1 > + fi > fi > mkdir -p /live > mount -r $live_dev /live || mount $live_dev /live > -- > 1.6.0.6 ACK. Tested in a node and it worked fine. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From arroy at redhat.com Mon Jul 13 17:57:03 2009 From: arroy at redhat.com (Arjun Roy) Date: Mon, 13 Jul 2009 13:57:03 -0400 Subject: [Ovirt-devel] [PATCH: server] Added qmf matahari example to ovirt-server. Message-ID: <1247507823-5236-1-git-send-email-arroy@redhat.com> Just a quick script modeled after the libvirt-qpid example that dumps all connected ovirt-node hardware info to standard out, for debug purposes. --- src/qmf-matahari.example.rb | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 50 insertions(+), 0 deletions(-) create mode 100644 src/qmf-matahari.example.rb diff --git a/src/qmf-matahari.example.rb b/src/qmf-matahari.example.rb new file mode 100644 index 0000000..0934ac9 --- /dev/null +++ b/src/qmf-matahari.example.rb @@ -0,0 +1,50 @@ +#!/usr/bin/ruby + +$: << File.join(File.dirname(__FILE__), "./dutils") + +require "rubygems" +require "qpid" +require "dutils" + +get_credentials('qpidd') + +server, port = get_srv('qpidd', 'tcp') +raise "Unable to determine qpid server from DNS SRV record" if not server + +srv = "amqp://#{server}:#{port}" +puts "Connecting to #{srv}.." +s = Qpid::Qmf::Session.new() +b = s.add_broker(srv, :mechanism => 'GSSAPI') + +while true: + hosts = s.objects(:class => "host") + hosts.each do |host| + puts "host: #{host.hostname}" + for (key, val) in host.properties + puts " property: #{key}, #{val}" + end + + # List cpus for current host + cpus = s.objects(:class => "cpu", 'host' => host.object_id) + cpus.each do |cpu| + puts " cpu:" + for (key, val) in cpu.properties + puts " property: #{key}, #{val}" + end + end # cpus.each + + # List nics for current host + nics = s.objects(:class => "nic", 'host' => host.object_id) + nics.each do |nic| + puts " nic: " + for (key, val) in nic.properties + puts " property: #{key}, #{val}" + end + end + + end # hosts.each + + puts '----------------------------' + sleep(5) + +end -- 1.6.2.5 From mmorsi at redhat.com Mon Jul 13 18:25:53 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Mon, 13 Jul 2009 14:25:53 -0400 Subject: [Ovirt-devel] [PATCH server] remove vm forward vnc and vm host history Message-ID: <1247509553-14201-1-git-send-email-mmorsi@redhat.com> since the introduction of ovirt-vnc-proxy and the updating of ovirt-viewer to user it the forward vnc functionality is no longer necessary / and incompatable w/ the current system. --- src/app/controllers/vm_controller.rb | 2 - src/app/models/host.rb | 9 - src/app/models/vm.rb | 36 ---- src/app/models/vm_host_history.rb | 22 --- src/app/views/vm/_form.rhtml | 3 - src/app/views/vm/show.rhtml | 4 - src/db-omatic/db_omatic.rb | 31 ---- src/db-omatic/vnc.rb | 173 -------------------- .../040_remove_vm_history_and_forward_vnc.rb | 65 ++++++++ src/test/fixtures/vms.yml | 2 - src/test/unit/vm_test.rb | 16 -- 11 files changed, 65 insertions(+), 298 deletions(-) delete mode 100644 src/app/models/vm_host_history.rb delete mode 100644 src/db-omatic/vnc.rb create mode 100644 src/db/migrate/040_remove_vm_history_and_forward_vnc.rb diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index 197241d..11b68ed 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -61,7 +61,6 @@ class VmController < ApplicationController end def create - params[:vm][:forward_vnc] = params[:forward_vnc] alert = svc_create(params[:vm], params[:start_now]) render :json => { :object => "vm", :success => true, :alert => alert } end @@ -75,7 +74,6 @@ class VmController < ApplicationController end def update - params[:vm][:forward_vnc] = params[:forward_vnc] alert = svc_update(params[:id], params[:vm], params[:start_now], params[:restart_now]) render :json => { :object => "vm", :success => true, :alert => alert } diff --git a/src/app/models/host.rb b/src/app/models/host.rb index 588137b..4975205 100644 --- a/src/app/models/host.rb +++ b/src/app/models/host.rb @@ -54,15 +54,6 @@ class Host < ActiveRecord::Base has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags - # reverse cronological collection of vm history - # each collection item contains vm that was running on host - # time started, and time ended (see VmHostHistory) - has_many :vm_host_histories, - :order => 'time_started DESC', - :dependent => :destroy - - alias history vm_host_histories - acts_as_xapian :texts => [ :hostname, :uuid, :hypervisor_type, :arch ], :values => [ [ :created_at, 0, "created_at", :date ], [ :updated_at, 1, "updated_at", :date ] ], diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index 6d0f864..14ad14c 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -34,14 +34,6 @@ class Vm < ActiveRecord::Base has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags - # reverse cronological collection of vm history - # each collection item contains host vm was running on, - # time started, and time ended (see VmHostHistory) - has_many :vm_host_histories, - :order => 'time_started DESC', - :dependent => :destroy - - has_many :vm_state_change_events, :order => 'created_at' do def previous_state_with_type(state_type) @@ -59,22 +51,6 @@ class Vm < ActiveRecord::Base validates_format_of :uuid, :with => %r([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}) - FORWARD_VNC_PORT_START = 5901 - - validates_numericality_of :forward_vnc_port, - :message => 'must be >= ' + FORWARD_VNC_PORT_START.to_s, - :greater_than_or_equal_to => FORWARD_VNC_PORT_START, - :if => Proc.new { |vm| vm.forward_vnc && - !vm.forward_vnc_port.nil? && - vm.forward_vnc_port != 0 } - - validates_uniqueness_of :forward_vnc_port, - :message => "is already in use", - :if => Proc.new { |vm| vm.forward_vnc && - !vm.forward_vnc_port.nil? && - vm.forward_vnc_port != 0 } - - validates_numericality_of :needs_restart, :greater_than_or_equal_to => 0, :less_than_or_equal_to => 1, @@ -408,18 +384,6 @@ class Vm < ActiveRecord::Base super end - # find the first available vnc port - def self.available_forward_vnc_port - i = FORWARD_VNC_PORT_START - Vm.find(:all, - :conditions => "forward_vnc_port is not NULL", - :order => 'forward_vnc_port ASC' ).each{ |vm| - break if vm.forward_vnc_port > i - i = i + 1 - } - return i - end - def self.calc_uptime "vms.*, case when state='running' then (cast(total_uptime || ' sec' as interval) + diff --git a/src/app/models/vm_host_history.rb b/src/app/models/vm_host_history.rb deleted file mode 100644 index bd61ddc..0000000 --- a/src/app/models/vm_host_history.rb +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (C) 2008 Red Hat, Inc. -# Written by Mohammed Morsi -# -# 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. - -class VmHostHistory < ActiveRecord::Base - belongs_to :vm - belongs_to :host -end diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 034c3df..a08f2a4 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -51,9 +51,6 @@
    - <%= check_box_tag_with_label "Forward vm's vnc port locally", "forward_vnc", 1, @vm.forward_vnc %> -
    - <%= check_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> <%= check_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index ffe5055..0bdf1e9 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -101,7 +101,6 @@
    Uuid:
    - <%= @vm.forward_vnc ? "VNC uri:
    " : "" %> Num vcpus allocated:
    Num vcpus used:
    Memory allocated:
    @@ -115,9 +114,6 @@
    <%=h @vm.uuid %>
    - <%= url = request.url - url = request.url[0..(url.index('/', 8) - 1)] + ":" + @vm.forward_vnc_port.to_s - @vm.forward_vnc ? (url + "
    ") : "" %> <%=h @vm.num_vcpus_allocated %>
    <%=h @vm.num_vcpus_used %>
    <%=h @vm.memory_allocated_in_mb %> MB
    diff --git a/src/db-omatic/db_omatic.rb b/src/db-omatic/db_omatic.rb index 155ff5e..b469695 100755 --- a/src/db-omatic/db_omatic.rb +++ b/src/db-omatic/db_omatic.rb @@ -10,7 +10,6 @@ require 'dutils' require 'daemons' require 'optparse' require 'logger' -require 'vnc' include Daemonize @@ -159,36 +158,6 @@ class DbOmatic < Qpid::Qmf::Console end end - begin - # find open vm host history for this vm, - history = VmHostHistory.find(:first, :conditions => ["vm_id = ? AND time_ended is NULL", vm.id]) - - if state == Vm::STATE_RUNNING - if history.nil? - history = VmHostHistory.new - history.vm = vm - history.host = vm.host - history.vnc_port = vm.vnc_port - history.state = state - history.time_started = Time.now - history.save! - end - - VmVnc.forward(vm) - elsif state != Vm::STATE_PENDING - VmVnc.close(vm, history) - - unless history.nil? # throw an exception if this fails? - history.time_ended = Time.now - history.state = state - history.save! - end - end - - rescue Exception => e # just log any errors here - @logger.error "Error with VM #{domain['name']} operation: " + e - end - @logger.info "Updating VM #{domain['name']} to state #{state}" if state == Vm::STATE_STOPPED diff --git a/src/db-omatic/vnc.rb b/src/db-omatic/vnc.rb deleted file mode 100644 index d826841..0000000 --- a/src/db-omatic/vnc.rb +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright (C) 2008 Red Hat, Inc. -# Written by Mohammed Morsi -# -# 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. - -# provides static 'forward' and 'close' methods to forward a specified vm's vnc connections -class VmVnc - - private - - # TODO no ruby/libiptc wrapper exists, when - # it does replace iptables command w/ calls to it - IPTABLES_CMD='/sbin/iptables ' - - VNC_DEBUG = false - - def self.debug(msg) - puts "\n" + msg + "\n" if VNC_DEBUG - end - - def self.find_host_ip(hostname) - # FIXME - addrinfo = Socket::getaddrinfo(hostname, nil) - unless addrinfo.size > 0 - raise "Could not retreive address for " + hostname - end - result = addrinfo[0][3] # return ip address of first entry - debug( "vm host hostname resolved to " + result.to_s ) - return result - end - - def self.port_open?(port) - cmd=IPTABLES_CMD + ' -t nat -nL ' - debug("vncPortOpen? iptables command: " + cmd) - - `#{cmd}`.each_line do |l| - return true if l =~ /.*#{port}.*/ - end - return false - end - - def self.allocate_forward_vnc_port(vm) - Vm.transaction do - ActiveRecord::Base.connection.execute('LOCK TABLE vms') - vm.forward_vnc_port = Vm.available_forward_vnc_port - debug("Allocating forward vnc port " + vm.forward_vnc_port.to_s) - vm.save! - end - end - - def self.deallocate_forward_vnc_port(vm) - debug("Deallocating forward vnc port " + vm.forward_vnc_port.to_s) - vm.forward_vnc_port = nil - vm.save! - end - - def self.get_forward_rules(vm, history_record = nil) - if history_record.nil? - ip = find_host_ip(vm.host.hostname) - vnc_port = vm.vnc_port.to_s - else - ip = find_host_ip(history_record.host.hostname) - vnc_port = history_record.vnc_port.to_s - end - - return " -d " + ip + " -p tcp --dport " + vnc_port + " -j ACCEPT", - " -s " + ip + " -p tcp --sport " + vnc_port + " -j ACCEPT" - end - - def self.get_nat_rules(vm, history_record = nil) - if history_record.nil? - ip = find_host_ip(vm.host.hostname) - vnc_port = vm.vnc_port.to_s - else - ip = find_host_ip(history_record.host.hostname) - vnc_port = history_record.vnc_port.to_s - end - - server,port = get_srv('ovirt', 'tcp') - local_ip = find_host_ip(server) - return " -p tcp --dport " + vm.forward_vnc_port.to_s + " -j DNAT --to " + ip + ":" + vnc_port, - " -d " + ip + " -p tcp --dport " + vnc_port + " -j SNAT --to " + local_ip - end - - def self.run_command(cmd) - debug("Running command " + cmd) - status = system(cmd) - raise 'Command terminated with error code ' + $?.to_s unless status - end - - public - - def self.forward(vm) - return unless vm.forward_vnc - - allocate_forward_vnc_port(vm) - if port_open?(vm.forward_vnc_port) - deallocate_forward_vnc_port(vm) - raise "Port already open " + vm.forward_vnc_port.to_s - end - - forward_rule1, forward_rule2 = get_forward_rules(vm) - forward_rule1 = IPTABLES_CMD + " -A FORWARD " + forward_rule1 - forward_rule2 = IPTABLES_CMD + " -A FORWARD " + forward_rule2 - - prerouting_rule, postrouting_rule = get_nat_rules(vm) - prerouting_rule = IPTABLES_CMD + " -t nat -A PREROUTING " + prerouting_rule - postrouting_rule = IPTABLES_CMD + " -t nat -A POSTROUTING " + postrouting_rule - - debug(" open\n forward rule 1: " + forward_rule1 + - "\n forward_rule 2: " + forward_rule2 + - "\n prerouting rule: " + prerouting_rule + - "\n postrouting rule: " + postrouting_rule) - - - File::open("/proc/sys/net/ipv4/ip_forward", "w") { |f| f.puts "1" } - run_command(forward_rule1) - run_command(forward_rule2) - run_command(prerouting_rule) - run_command(postrouting_rule) - end - - def self.close(vm, history_record = nil) - return unless vm.forward_vnc - - unless port_open?(vm.forward_vnc_port) - raise "Port not open " + vm.forward_vnc_port.to_s - end - - forward_rule1, forward_rule2 = get_forward_rules(vm, history_record) - forward_rule1 = IPTABLES_CMD + " -D FORWARD " + forward_rule1 - forward_rule2 = IPTABLES_CMD + " -D FORWARD " + forward_rule2 - - prerouting_rule, postrouting_rule = get_nat_rules(vm, history_record) - prerouting_rule = IPTABLES_CMD + " -t nat -D PREROUTING " + prerouting_rule - postrouting_rule = IPTABLES_CMD + " -t nat -D POSTROUTING " + postrouting_rule - - debug(" close\n forward rule 1: " + forward_rule1 + - "\n forward_rule 2: " + forward_rule2 + - "\n prerouting rule: " + prerouting_rule + - "\n postrouting rule: " + postrouting_rule) - - run_command(forward_rule1) - run_command(forward_rule2) - run_command(prerouting_rule) - run_command(postrouting_rule) - - - deallocate_forward_vnc_port(vm) - end - - # should be used in cases which - # closing iptables ports is not needed - # (ex. on server startup) - def self.deallocate_all - Vm.find(:all).each{ |vm| - deallocate_forward_vnc_port(vm) - } - end -end diff --git a/src/db/migrate/040_remove_vm_history_and_forward_vnc.rb b/src/db/migrate/040_remove_vm_history_and_forward_vnc.rb new file mode 100644 index 0000000..b815f18 --- /dev/null +++ b/src/db/migrate/040_remove_vm_history_and_forward_vnc.rb @@ -0,0 +1,65 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# 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. + +# reverses 034_add_vm_vnc migration and 036_vm_host_history +# pretty much copied the self.up from 034 / 036 into self.down +# and self.down from 034 / 036 into self.up +class RemoveVmHistoryAndForwardVnc < ActiveRecord::Migration + def self.up + # 034 self.down + remove_column :vms, :forward_vnc + remove_column :vms, :forward_vnc_port + + # 036 self.down + drop_table :vm_host_histories + end + + def self.down + # 034 self.up + add_column :vms, :forward_vnc, :bool, :default => false + add_column :vms, :forward_vnc_port, :int, :default => 0, :unique => true + + # 036 self.up + # this table gets populated in db-omatic + create_table :vm_host_histories do |t| + # vm / host association + t.integer :vm_id + t.integer :host_id + + # records operating info of vm + # (most likey we will want to add a + # slew of more info here or in a future + # migration) + t.integer :vnc_port + t.string :state + + # start / end timestamps + t.timestamp :time_started + t.timestamp :time_ended + end + + execute "alter table vm_host_histories add constraint + fk_vm_host_histories_vms foreign key (vm_id) references vms(id)" + + execute "alter table vm_host_histories add constraint + fk_vm_host_histories_hosts foreign key (host_id) references + hosts(id)" + + end +end + diff --git a/src/test/fixtures/vms.yml b/src/test/fixtures/vms.yml index b2711b2..0d2caa3 100644 --- a/src/test/fixtures/vms.yml +++ b/src/test/fixtures/vms.yml @@ -11,8 +11,6 @@ production_httpd_vm: boot_device: hd host: prod_corp_com vm_resource_pool: corp_com_production_vmpool - forward_vnc: true - forward_vnc_port: 5901 production_mysqld_vm: uuid: 89e62d32-04d9-4351-b573-b1a253397296 description: production mysqld appliance diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index a28f183..d7b9d40 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -96,22 +96,6 @@ class VmTest < ActiveSupport::TestCase flunk 'Vm must specify valid state' if @vm.valid? end - # ensure duplicate forward_vnc_ports cannot exist - def test_invalid_without_unique_forward_vnc_port - vm = vms(:production_mysqld_vm) - vm.forward_vnc = true - vm.forward_vnc_port = 1234 # duplicate - assert !vm.valid?, "forward vnc port must be unique" - end - - # ensure bad forward_vnc_ports cannot exist - def test_invalid_without_bad_forward_vnc_port - vm = vms(:production_mysqld_vm) - vm.forward_vnc = true - vm.forward_vnc_port = 1 # too small - assert !vm.valid?, "forward vnc port must be >= 5900" - end - # Ensures that, if the VM does not contain the Cobbler prefix, that it # does not claim to be a Cobbler VM. # -- 1.6.0.6 From dpierce at redhat.com Mon Jul 13 19:11:55 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 13 Jul 2009 15:11:55 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 Message-ID: <1247512315-28870-1-git-send-email-dpierce@redhat.com> If the vlan kernel argument is provided, then it is used during networking auto-configuration. Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 56 ++++++++++++++++++++++++++++++++------ scripts/ovirt-early | 7 ++++- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 2674dfe..2bd082e 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -45,6 +45,33 @@ function has_configured_interface fi } +# Configures vlan for the node. +# $1 - the nic +# $2 - the network bridge name +# $3 - the vlan id +# $4 - the VL_ROOT variable +# $5 - the VL_CONFIG variable +# $6 - the IF_ROOT value +# $7 - the vlan config filename variable +# $8 - the NIC config filename +function setup_vlan +{ + local nic=$1 + local bridge=$2 + local vlan_id=$3 + local vlroot=$4 + local vlconfig=$5 + local ifroot=$6 + local vlfilename=$7 + local iffilename=$8 + + eval $vlroot="${ifroot}.${vlan_id}" + eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE ${nic}.${vlan_id}\" + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE ${bridge}\" + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" + eval $vlfilename="${iffilename}.${vlan_id}" +} + function configure_interface { local NIC=$1 @@ -118,12 +145,8 @@ function configure_interface A|a) CONFIGURED_NIC=""; return;; *) if [[ -n "$REPLY" ]] && [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then - VLAN_ID=$REPLY - VL_ROOT="${IF_ROOT}.${VLAN_ID}" - VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" - VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" - VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" - VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" + VLAN_ID=$REPLY + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME break fi ;; @@ -236,12 +259,21 @@ function configure_interface esac fi + if [ -n "$OVIRT_VLAN" ]; then + VLAN_ID=$OVIRT_VLAN + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME + fi + if [ -z "$OVIRT_IP_ADDRESS" ]; then - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" + if [ -z "$VL_CONFIG" ]; then + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" + fi + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" else if [ "$OVIRT_IP_ADDRESS" != "off" ]; then - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" + if [ -z "$VL_CONFIG" ]; then + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" + fi BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $OVIRT_IP_ADDRESS" if [ -n "$OVIRT_IP_NETMASK" ]; then BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $OVIRT_IP_NETMASK" @@ -254,9 +286,15 @@ function configure_interface IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" + if [ -n "${VL_CONFIG}" ]; then + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" + fi printf "$IF_CONFIG\n" > $IF_FILENAME printf "$BR_CONFIG\n" > $BR_FILENAME + if [ -n "$VL_CONFIG" ]; then + printf "$VL_CONFIG\n" > $VL_FILENAME + fi fi } diff --git a/scripts/ovirt-early b/scripts/ovirt-early index b4de30e..560fa14 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -208,10 +208,12 @@ start() { # ipv6=dhcp|auto # dns=server[,server] # ntp=server[,server] + # vlan=id # static network configuration ip_address= ip_gateway= ip_netmask= + vlan= netmask= gateway= ipv6= @@ -344,6 +346,9 @@ start() { hostname=*) hostname=${i#hostname=} ;; + vlan=*) + vlan=${i#vlan=} + ;; syslog=*) i=${i#syslog=} eval $(printf $i|awk -F: '{print "syslog_server="$1; print "syslog_port="$2;}') @@ -365,7 +370,7 @@ start() { ip_gateway=$gateway fi # save boot parameters as defaults for ovirt-config-* - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" # mount /config unless firstboot is forced if [ "$firstboot" != "1" ]; then mount_config -- 1.6.2.5 From imain at redhat.com Mon Jul 13 19:48:53 2009 From: imain at redhat.com (Ian Main) Date: Mon, 13 Jul 2009 12:48:53 -0700 Subject: [Ovirt-devel] [PATCH: server] Added qmf matahari example to ovirt-server. In-Reply-To: <1247507823-5236-1-git-send-email-arroy@redhat.com> References: <1247507823-5236-1-git-send-email-arroy@redhat.com> Message-ID: <20090713124853.6878e84c@tp.mains.net> On Mon, 13 Jul 2009 13:57:03 -0400 Arjun Roy wrote: > Just a quick script modeled after the libvirt-qpid example that dumps all > connected ovirt-node hardware info to standard out, for debug purposes. I think I'd change the name to matahari-list (I'd like to change the libvirt-qmf-example name too), and maybe not make it loop.. I can just make these changes and push if you like. Ian From arroy at redhat.com Mon Jul 13 20:01:21 2009 From: arroy at redhat.com (Arjun Roy) Date: Mon, 13 Jul 2009 16:01:21 -0400 Subject: [Ovirt-devel] [PATCH: server] Added qmf matahari example to ovirt-server. In-Reply-To: <20090713124853.6878e84c@tp.mains.net> References: <1247507823-5236-1-git-send-email-arroy@redhat.com> <20090713124853.6878e84c@tp.mains.net> Message-ID: <4A5B9291.7000709@redhat.com> On 07/13/2009 03:48 PM, Ian Main wrote: > I think I'd change the name to matahari-list (I'd like to change the > libvirt-qmf-example name too), and maybe not make it loop.. > > I can just make these changes and push if you like. > > Ian > That's fine. -Arjun From imain at redhat.com Mon Jul 13 20:45:54 2009 From: imain at redhat.com (Ian Main) Date: Mon, 13 Jul 2009 13:45:54 -0700 Subject: [Ovirt-devel] [PATCH] Use volume key instead of path to identify volume. Message-ID: <1247517954-23811-1-git-send-email-imain@redhat.com> This patch teaches taskomatic to use the volume 'key' instead of the path from libvirt to key the volume off of in the database. This fixes the duplicate iscsi volume bug we were seeing. The issue was that libvirt changed the way they name storage volumes and included a local ID that changed each time it was attached. Note that the first run with this new patch will cause duplicate volumes because of the key change. Ideally you would delete all storage pools and readd them after applying this patch. Signed-off-by: Ian Main --- src/task-omatic/taskomatic.rb | 13 +++++++------ 1 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/task-omatic/taskomatic.rb b/src/task-omatic/taskomatic.rb index b3c0592..6c68397 100755 --- a/src/task-omatic/taskomatic.rb +++ b/src/task-omatic/taskomatic.rb @@ -219,7 +219,7 @@ class TaskOmatic @logger.debug "Pool mounted: #{pool.name}; state: #{pool.state}" volume = @session.object(:class => 'volume', - 'name' => volume_name, + 'key' => volume_name, 'storagePool' => pool.object_id) raise "Unable to find volume #{volume_name} attached to pool #{pool.name}." unless volume @logger.debug "Verified volume of pool #{volume.path}" @@ -591,7 +591,7 @@ class TaskOmatic storage_volume.path = volume.path storage_volume.size = volume.capacity / 1024 storage_volume.storage_pool_id = db_pool.id - storage_volume.write_attribute(storage_volume.volume_name, volume.name) + storage_volume.write_attribute(storage_volume.volume_name, volume.key) storage_volume.lv_owner_perms = owner storage_volume.lv_group_perms = group storage_volume.lv_mode_perms = mode @@ -644,13 +644,14 @@ class TaskOmatic existing_vol = StorageVolume.find(:first, :conditions => ["storage_pool_id = ? AND #{storage_volume.volume_name} = ?", - db_pool_phys.id, volume.name]) + db_pool_phys.id, volume.key]) + puts "Existing volume is #{existing_vol}, searched for storage volume name and #{volume.key}" # Only add if it's not already there. if not existing_vol add_volume_to_db(db_pool_phys, volume); else - @logger.debug "Scanned volume #{volume.name} already exists in db.." + @logger.debug "Scanned volume #{volume.key} already exists in db.." end # Now check for an LVM pool carving up this volume. @@ -692,11 +693,11 @@ class TaskOmatic lvm_storage_volume = StorageVolume.factory(lvm_db_pool.get_type_label) existing_vol = StorageVolume.find(:first, :conditions => ["storage_pool_id = ? AND #{lvm_storage_volume.volume_name} = ?", - lvm_db_pool.id, lvm_volume.name]) + lvm_db_pool.id, lvm_volume.key]) if not existing_vol add_volume_to_db(lvm_db_pool, lvm_volume, "0744", "0744", "0744"); else - @logger.info "volume #{lvm_volume.name} already exists in db.." + @logger.info "volume #{lvm_volume.key} already exists in db.." end end end -- 1.6.0.6 From mmorsi at redhat.com Mon Jul 13 20:50:55 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Mon, 13 Jul 2009 16:50:55 -0400 Subject: [Ovirt-devel] [PATCH viewer] permit hostname / username / password / vm to be passed in via the cmd line Message-ID: <1247518255-22413-1-git-send-email-mmorsi@redhat.com> passing in --hostname will bypass the hostname form passing --username --password will bypass the login form passing in --vm will attempt to connect to the vm on login --- internal.h | 9 ++++- main.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++---------- wui_thread.c | 15 +-------- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/internal.h b/internal.h index bd65922..8a857eb 100644 --- a/internal.h +++ b/internal.h @@ -70,10 +70,15 @@ extern gboolean check_cert; /* server we're connecting to */ extern const char* hostname; +extern const char* username; +extern const char* password; /* port which to connect to the server via vnc */ extern int ovirt_server_vnc_port; +/* selected vm name which to automatically connect to */ +extern const char* selected_vm_name; + /* vm currently in focus */ extern struct vm* vm_in_focus; @@ -101,8 +106,8 @@ extern void wui_thread_send_connect (const char *uri); /* Disconnect, forget URI, credentials, VMs etc. */ extern void wui_thread_send_disconnect (void); -/* Set the username and password and tell the WUI to try to log in. */ -extern void wui_thread_send_login (const char *username, const char *password); +/* tell the WUI to try to log in with the username / password variables above */ +extern void wui_thread_send_login (void); /* Tell the WUI thread to refresh the VM list. Note that the WUI * thread does this automatically anyway after a successful login, and diff --git a/main.c b/main.c index dd26c93..1822fdb 100644 --- a/main.c +++ b/main.c @@ -73,7 +73,8 @@ gboolean check_cert = FALSE; // do we want this enabled by default ? static GSList *vmlist = NULL; /* internal.h shared constructs */ -const char* hostname; +const char *hostname, *username, *password; +const char* selected_vm_name; struct vm* vm_in_focus; int ovirt_server_vnc_port = 5900; @@ -82,10 +83,14 @@ static void start_ui (void); static GtkWidget *menu_item_new (int which_menu); static void refresh_menu_vm_list (GtkWidget *, gpointer); static void connect_to_wui_on_enter (GtkWidget *, gpointer); -static void connect_to_wui (GtkWidget *, gpointer); +static void connect_to_wui_via_widget (GtkWidget *, gpointer); +static void connect_to_wui (); static void send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef); static void login_to_wui_on_enter (GtkWidget *, gpointer); -static void login_to_wui (GtkWidget *, gpointer); +static void login_to_wui_via_widget (GtkWidget *, gpointer); +static void login_to_wui (); +static void connect_to_vm_name (const char* vm_name); +static void connect_to_vm (struct vm*); static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); static void destroy (GtkWidget *widget, gpointer data); static void clear_connectmenu (void); @@ -201,12 +206,20 @@ static const char *help_msg = "Use '" PACKAGE " --help' to see a list of available command line options"; static const GOptionEntry options[] = { - { "port", 'p', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port, + { "hostname", 'H', 0, G_OPTION_ARG_STRING, &hostname, + "hostname of the server to connect to", NULL}, + { "port", 'P', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port, "set port which to connect to server via vnc", NULL }, + { "username", 'u', 0, G_OPTION_ARG_STRING, &username, + "username which to connect to the server with", NULL}, + { "password", 'p', 0, G_OPTION_ARG_STRING, &password, + "password which to connect to the server with", NULL}, { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo, "set the path of the CA certificate bundle", NULL }, { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert, "check the SSL certificate of the server", NULL }, + { "vm", 'm', 0, G_OPTION_ARG_STRING, &selected_vm_name, + "name of the vm which to connect to on login", NULL}, { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "turn on debugging messages", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, @@ -279,6 +292,29 @@ main (int argc, char *argv[]) start_wui_thread (); start_ui (); + // if parameters were passed in via + // cmd line, perform necessary operations + // (and thus hostname/login dialogs won't + // be displayed later on) + if (hostname != NULL && !STREQ (hostname, "")){ + g_print("connecting to %s, may take a moment\n", hostname); + connect_to_wui(); + while(!wui_thread_is_connected()) sleep(1); + + if (username != NULL && !STREQ (username, "")){ + g_print("logging in as %s, may take a moment\n", username); + login_to_wui(); + while(!wui_thread_is_logged_in()) sleep(1); + + if (selected_vm_name != NULL && !STREQ(selected_vm_name, "")){ + g_print("waiting for list of vms, may take a moment\n"); + while(!wui_thread_has_valid_vmlist()) sleep(1); + connect_to_vm_name(selected_vm_name); + + } + } + } + DEBUG ("entering the Gtk main loop"); gtk_main (); @@ -420,7 +456,7 @@ start_ui (void) g_signal_connect (G_OBJECT (ca_hostname), "key-release-event", G_CALLBACK (connect_to_wui_on_enter), NULL); g_signal_connect (G_OBJECT (ca_button), "clicked", - G_CALLBACK (connect_to_wui), NULL); + G_CALLBACK (connect_to_wui_via_widget), NULL); login_area = gtk_event_box_new (); la_vbox = gtk_vbox_new (FALSE, 0); @@ -448,7 +484,7 @@ start_ui (void) g_signal_connect (G_OBJECT (la_password), "key-release-event", G_CALLBACK (login_to_wui_on_enter), NULL); g_signal_connect (G_OBJECT (la_button), "clicked", - G_CALLBACK (login_to_wui), NULL); + G_CALLBACK (login_to_wui_via_widget), NULL); /* Tabbed notebook. */ notebook = gtk_notebook_new (); @@ -570,16 +606,22 @@ connect_to_wui_on_enter (GtkWidget *widget, gpointer data) // if key released was not 'enter' key if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; - connect_to_wui(widget, data); + connect_to_wui_via_widget(widget, data); } static void -connect_to_wui (GtkWidget *widget, gpointer data) +connect_to_wui_via_widget (GtkWidget *widget, gpointer data) +{ + hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); + connect_to_wui(); +} + +static void +connect_to_wui() { char *uri; int len; - hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); if (STREQ (hostname, "")) return; /* https:// + hostname + /ovirt + \0 */ @@ -597,19 +639,23 @@ login_to_wui_on_enter (GtkWidget *widget, gpointer data) // if key released was not 'enter' key if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; - login_to_wui(widget, data); + login_to_wui_via_widget(widget, data); } static void -login_to_wui (GtkWidget *widget, gpointer data) +login_to_wui_via_widget (GtkWidget *widget, gpointer data) { - const char *username, *password; + username = gtk_entry_get_text (GTK_ENTRY (la_username)); + password = gtk_entry_get_text (GTK_ENTRY (la_password)); + login_to_wui(); +} - username = gtk_entry_get_text (GTK_ENTRY (la_username)); +static void +login_to_wui() +{ if (STREQ (username, "")) return; - password = gtk_entry_get_text (GTK_ENTRY (la_password)); - wui_thread_send_login (username, password); + wui_thread_send_login(); } /* Connect to a virtual machine. This callback is called from the @@ -618,9 +664,27 @@ login_to_wui (GtkWidget *widget, gpointer data) * makes a new connection. */ static void -connect_to_vm (GtkWidget *widget, gpointer _vm) +connect_to_vm_via_widget (GtkWidget *widget, gpointer _vm) { - struct vm *vm = (struct vm *) _vm; + connect_to_vm((struct vm*)_vm); +} + +static void +connect_to_vm_name(const char* vm_name) +{ + int i; + main_vmlist_updated(NULL); // dont like running this here, but need to make sure we have latest vm list + for(i = 0; i < g_slist_length(vmlist); ++i){ + if(STREQ(((struct vm*) g_slist_nth_data(vmlist, i))->description, vm_name)){ + connect_to_vm((struct vm*) g_slist_nth_data(vmlist, i)); + break; + } + } +} + +static void +connect_to_vm(struct vm* _vm){ + struct vm *vm = _vm; int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); int i, uuidlen, len, fd; GtkWidget *child; @@ -1144,5 +1208,5 @@ add_vm_to_connectmenu (gpointer _vm, gpointer data) gtk_menu_append (GTK_MENU (connectmenu), item); g_signal_connect (G_OBJECT (item), "activate", - G_CALLBACK (connect_to_vm), vm); + G_CALLBACK (connect_to_vm_via_widget), vm); } diff --git a/wui_thread.c b/wui_thread.c index 8bfa8ca..1688b83 100644 --- a/wui_thread.c +++ b/wui_thread.c @@ -179,7 +179,7 @@ wui_thread_send_disconnect (void) /* Send the login message to the WUI thread. */ void -wui_thread_send_login (const char *username, const char *password) +wui_thread_send_login (void) { struct message *msg; @@ -226,8 +226,6 @@ static int secs_between_refresh = 60; static CURL *curl = NULL; static char curl_error_buffer[CURL_ERROR_SIZE]; static char *uri = NULL; -static char *username = NULL; -static char *password = NULL; static gboolean process_message (struct message *); @@ -396,8 +394,6 @@ process_message (struct message *msg) write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); if (uri) g_free (uri); - if (username) g_free (username); - if (password) g_free (password); set_connected (FALSE); set_logged_in (FALSE); return 1; @@ -424,20 +420,11 @@ process_message (struct message *msg) curl = NULL; if (uri) g_free (uri); uri = NULL; - if (username) g_free (username); - username = NULL; - if (password) g_free (password); - password = NULL; set_connected (FALSE); set_logged_in (FALSE); break; case LOGIN: - if (username) g_free (username); - username = msg->str1; - if (password) g_free (password); - password = msg->str2; - /* If we're not connected, this message just updates the * username and password. Otherwise if we are connected, * try to login and grab the initial list of VMs. -- 1.6.0.6 From arroy at redhat.com Tue Jul 14 17:53:56 2009 From: arroy at redhat.com (Arjun Roy) Date: Tue, 14 Jul 2009 13:53:56 -0400 Subject: [Ovirt-devel] [PATCH server] add ipv6 postgres trust In-Reply-To: <20090706210138.D2D00619050@hormel.redhat.com> References: <20090706210138.D2D00619050@hormel.redhat.com> Message-ID: <4A5CC634.2070101@redhat.com> > Date: Mon, 6 Jul 2009 17:01:34 -0400 > From: Joey Boggs > Subject: [Ovirt-devel] [PATCH server] add ipv6 postgres trust > To:ovirt-devel at redhat.com > Message-ID:<1246914094-11697-1-git-send-email-jboggs at redhat.com> > > If management server has ipv6 enabled and postgres is not configured to allow localhost access via ::1 the postgres service will fail. > > --- > installer/modules/ovirt/manifests/postgres.pp | 8 +++++++- > 1 files changed, 7 insertions(+), 1 deletions(-) > > diff --git a/installer/modules/ovirt/manifests/postgres.pp b/installer/modules/ovirt/manifests/postgres.pp > index 36bcdc0..12b7764 100644 > --- a/installer/modules/ovirt/manifests/postgres.pp > +++ b/installer/modules/ovirt/manifests/postgres.pp > @@ -41,7 +41,7 @@ class postgres::bundled{ > service {"postgresql" : > ensure => running, > enable => true, > - require => [Single_exec[initialize_db],Exec[postgres_add_localhost_trust],Exec[postgres_add_all_trust]], > + require => [Single_exec[initialize_db],Exec[postgres_add_localhost_trust],Exec[postgres_add_all_trust],Exec[postgres_add_ipv6_loopback_trust]], > hasstatus => true > } > > @@ -79,6 +79,12 @@ class postgres::bundled{ > notify => Service[postgresql] > } > > + exec {"postgres_add_ipv6_loopback_trust": > + command => "/bin/echo 'host all all ::1/128 trust'>> /var/lib/pgsql/data/pg_hba.conf", > + require => Exec[postgres_add_all_trust], > + notify => Service[postgresql] > + } > + > file { "/etc/ovirt-server/" : > ensure => directory, > require => Exec[postgres_add_localhost_trust] > -- 1.6.2.5 Worked for me. ACK -Arjun -------------- next part -------------- An HTML attachment was scrubbed... URL: From jboggs at redhat.com Tue Jul 14 18:34:37 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 14 Jul 2009 14:34:37 -0400 Subject: [Ovirt-devel] [PATCH server] add ipv6 postgres trust In-Reply-To: <4A5CC634.2070101@redhat.com> References: <20090706210138.D2D00619050@hormel.redhat.com> <4A5CC634.2070101@redhat.com> Message-ID: <4A5CCFBD.8050701@redhat.com> On 07/14/2009 01:53 PM, Arjun Roy wrote: > >> Date: Mon, 6 Jul 2009 17:01:34 -0400 >> From: Joey Boggs >> Subject: [Ovirt-devel] [PATCH server] add ipv6 postgres trust >> To:ovirt-devel at redhat.com >> Message-ID:<1246914094-11697-1-git-send-email-jboggs at redhat.com> >> >> If management server has ipv6 enabled and postgres is not configured to allow localhost access via ::1 the postgres service will fail. >> >> --- >> installer/modules/ovirt/manifests/postgres.pp | 8 +++++++- >> 1 files changed, 7 insertions(+), 1 deletions(-) >> >> diff --git a/installer/modules/ovirt/manifests/postgres.pp b/installer/modules/ovirt/manifests/postgres.pp >> index 36bcdc0..12b7764 100644 >> --- a/installer/modules/ovirt/manifests/postgres.pp >> +++ b/installer/modules/ovirt/manifests/postgres.pp >> @@ -41,7 +41,7 @@ class postgres::bundled{ >> service {"postgresql" : >> ensure => running, >> enable => true, >> - require => [Single_exec[initialize_db],Exec[postgres_add_localhost_trust],Exec[postgres_add_all_trust]], >> + require => [Single_exec[initialize_db],Exec[postgres_add_localhost_trust],Exec[postgres_add_all_trust],Exec[postgres_add_ipv6_loopback_trust]], >> hasstatus => true >> } >> >> @@ -79,6 +79,12 @@ class postgres::bundled{ >> notify => Service[postgresql] >> } >> >> + exec {"postgres_add_ipv6_loopback_trust": >> + command => "/bin/echo 'host all all ::1/128 trust'>> /var/lib/pgsql/data/pg_hba.conf", >> + require => Exec[postgres_add_all_trust], >> + notify => Service[postgresql] >> + } >> + >> file { "/etc/ovirt-server/" : >> ensure => directory, >> require => Exec[postgres_add_localhost_trust] >> -- 1.6.2.5 > Worked for me. ACK > > -Arjun pushed -------------- next part -------------- An HTML attachment was scrubbed... URL: From jboggs at redhat.com Tue Jul 14 19:09:00 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 14 Jul 2009 15:09:00 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <1247512315-28870-1-git-send-email-dpierce@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> Message-ID: <4A5CD7CC.8020303@redhat.com> On 07/13/2009 03:11 PM, Darryl L. Pierce wrote: > If the vlan kernel argument is provided, then it is used during > networking auto-configuration. > > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-config-networking | 56 ++++++++++++++++++++++++++++++++------ > scripts/ovirt-early | 7 ++++- > 2 files changed, 53 insertions(+), 10 deletions(-) > > diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking > index 2674dfe..2bd082e 100755 > --- a/scripts/ovirt-config-networking > +++ b/scripts/ovirt-config-networking > @@ -45,6 +45,33 @@ function has_configured_interface > fi > } > > +# Configures vlan for the node. > +# $1 - the nic > +# $2 - the network bridge name > +# $3 - the vlan id > +# $4 - the VL_ROOT variable > +# $5 - the VL_CONFIG variable > +# $6 - the IF_ROOT value > +# $7 - the vlan config filename variable > +# $8 - the NIC config filename > +function setup_vlan > +{ > + local nic=$1 > + local bridge=$2 > + local vlan_id=$3 > + local vlroot=$4 > + local vlconfig=$5 > + local ifroot=$6 > + local vlfilename=$7 > + local iffilename=$8 > + > + eval $vlroot="${ifroot}.${vlan_id}" > + eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE ${nic}.${vlan_id}\" > + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE ${bridge}\" > + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" > + eval $vlfilename="${iffilename}.${vlan_id}" > +} > + > function configure_interface > { > local NIC=$1 > @@ -118,12 +145,8 @@ function configure_interface > A|a) CONFIGURED_NIC=""; return;; > *) > if [[ -n "$REPLY" ]]&& [[ "$REPLY" =~ "^[0-9]{1,}$" ]]; then > - VLAN_ID=$REPLY > - VL_ROOT="${IF_ROOT}.${VLAN_ID}" > - VL_CONFIG="rm ${VL_ROOT}\nset ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" > - VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/BRIDGE ${BRIDGE}" > - VL_CONFIG="${VL_CONFIG}\nset ${VL_ROOT}/VLAN yes" > - VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" > + VLAN_ID=$REPLY > + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME > break > fi > ;; > @@ -236,12 +259,21 @@ function configure_interface > esac > fi > > + if [ -n "$OVIRT_VLAN" ]; then > + VLAN_ID=$OVIRT_VLAN > + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME > + fi > + > if [ -z "$OVIRT_IP_ADDRESS" ]; then > - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" > + if [ -z "$VL_CONFIG" ]; then > + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > + fi > + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" > else > if [ "$OVIRT_IP_ADDRESS" != "off" ]; then > - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > + if [ -z "$VL_CONFIG" ]; then > + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > + fi > BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $OVIRT_IP_ADDRESS" > if [ -n "$OVIRT_IP_NETMASK" ]; then > BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $OVIRT_IP_NETMASK" > @@ -254,9 +286,15 @@ function configure_interface > > IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" > BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" > + if [ -n "${VL_CONFIG}" ]; then > + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" > + fi > > printf "$IF_CONFIG\n"> $IF_FILENAME > printf "$BR_CONFIG\n"> $BR_FILENAME > + if [ -n "$VL_CONFIG" ]; then > + printf "$VL_CONFIG\n"> $VL_FILENAME > + fi > fi > } > > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > index b4de30e..560fa14 100755 > --- a/scripts/ovirt-early > +++ b/scripts/ovirt-early > @@ -208,10 +208,12 @@ start() { > # ipv6=dhcp|auto > # dns=server[,server] > # ntp=server[,server] > + # vlan=id > # static network configuration > ip_address= > ip_gateway= > ip_netmask= > + vlan= > netmask= > gateway= > ipv6= > @@ -344,6 +346,9 @@ start() { > hostname=*) > hostname=${i#hostname=} > ;; > + vlan=*) > + vlan=${i#vlan=} > + ;; > syslog=*) > i=${i#syslog=} > eval $(printf $i|awk -F: '{print "syslog_server="$1; print "syslog_port="$2;}') > @@ -365,7 +370,7 @@ start() { > ip_gateway=$gateway > fi > # save boot parameters as defaults for ovirt-config-* > - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > # mount /config unless firstboot is forced > if [ "$firstboot" != "1" ]; then > mount_config > Tried this on 2 different boxes getting whitespace errors, but the patch file doesn't show any extra spaces? # cat -v $patchfile +# Configures vlan for the node.^M +# $1 - the nic^M +# $2 - the network bridge name^M +# $3 - the vlan id^M +# $4 - the VL_ROOT variable^M +# $5 - the VL_CONFIG variable^M +# $6 - the IF_ROOT value^M +# $7 - the vlan config filename variable^M +# $8 - the NIC config filename^M +function setup_vlan^M -bash-3.2$ git am ../\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml Applying: Adds vlan support to auto-installations for the node. bz#511056 /home/jboggs/ovirt-node/.git/rebase-apply/patch:14: trailing whitespace. # Configures vlan for the node. /home/jboggs/ovirt-node/.git/rebase-apply/patch:15: trailing whitespace. # $1 - the nic /home/jboggs/ovirt-node/.git/rebase-apply/patch:16: trailing whitespace. # $2 - the network bridge name /home/jboggs/ovirt-node/.git/rebase-apply/patch:17: trailing whitespace. # $3 - the vlan id /home/jboggs/ovirt-node/.git/rebase-apply/patch:18: trailing whitespace. # $4 - the VL_ROOT variable error: patch failed: scripts/ovirt-config-networking:45 error: scripts/ovirt-config-networking: patch does not apply error: patch failed: scripts/ovirt-early:208 error: scripts/ovirt-early: patch does not apply Patch failed at 0001. When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort". From hbrock at redhat.com Tue Jul 14 19:54:08 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Tue, 14 Jul 2009 15:54:08 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <4A5CD7CC.8020303@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> Message-ID: <20090714195407.GE10740@redhat.com> On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: > On 07/13/2009 03:11 PM, Darryl L. Pierce wrote: > >If the vlan kernel argument is provided, then it is used during > >networking auto-configuration. > > > >Signed-off-by: Darryl L. Pierce > >--- > > scripts/ovirt-config-networking | 56 > > ++++++++++++++++++++++++++++++++------ > > scripts/ovirt-early | 7 ++++- > > 2 files changed, 53 insertions(+), 10 deletions(-) > > > >diff --git a/scripts/ovirt-config-networking > >b/scripts/ovirt-config-networking > >index 2674dfe..2bd082e 100755 > >--- a/scripts/ovirt-config-networking > >+++ b/scripts/ovirt-config-networking > >@@ -45,6 +45,33 @@ function has_configured_interface > > fi > > } > > > >+# Configures vlan for the node. > >+# $1 - the nic > >+# $2 - the network bridge name > >+# $3 - the vlan id > >+# $4 - the VL_ROOT variable > >+# $5 - the VL_CONFIG variable > >+# $6 - the IF_ROOT value > >+# $7 - the vlan config filename variable > >+# $8 - the NIC config filename > >+function setup_vlan > >+{ > >+ local nic=$1 > >+ local bridge=$2 > >+ local vlan_id=$3 > >+ local vlroot=$4 > >+ local vlconfig=$5 > >+ local ifroot=$6 > >+ local vlfilename=$7 > >+ local iffilename=$8 > >+ > >+ eval $vlroot="${ifroot}.${vlan_id}" > >+ eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE > >${nic}.${vlan_id}\" > >+ eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE ${bridge}\" > >+ eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" > >+ eval $vlfilename="${iffilename}.${vlan_id}" > >+} > >+ > > function configure_interface > > { > > local NIC=$1 > >@@ -118,12 +145,8 @@ function configure_interface > > A|a) CONFIGURED_NIC=""; return;; > > *) > > if [[ -n "$REPLY" ]]&& [[ "$REPLY" =~ > > "^[0-9]{1,}$" ]]; then > >- VLAN_ID=$REPLY > >- VL_ROOT="${IF_ROOT}.${VLAN_ID}" > >- VL_CONFIG="rm ${VL_ROOT}\nset > >${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" > >- VL_CONFIG="${VL_CONFIG}\nset > >${VL_ROOT}/BRIDGE ${BRIDGE}" > >- VL_CONFIG="${VL_CONFIG}\nset > >${VL_ROOT}/VLAN yes" > >- > >VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" > >+ VLAN_ID=$REPLY > >+ setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT > >VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME > > break > > fi > > ;; > >@@ -236,12 +259,21 @@ function configure_interface > > esac > > fi > > > >+ if [ -n "$OVIRT_VLAN" ]; then > >+ VLAN_ID=$OVIRT_VLAN > >+ setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT > >VL_FILENAME $IF_FILENAME > >+ fi > >+ > > if [ -z "$OVIRT_IP_ADDRESS" ]; then > >- IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > >- BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" > >+ if [ -z "$VL_CONFIG" ]; then > >+ IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > >+ fi > >+ BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" > > else > > if [ "$OVIRT_IP_ADDRESS" != "off" ]; then > >- IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > >+ if [ -z "$VL_CONFIG" ]; then > >+ IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE > >${BRIDGE}" > >+ fi > > BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR > > $OVIRT_IP_ADDRESS" > > if [ -n "$OVIRT_IP_NETMASK" ]; then > > BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK > > $OVIRT_IP_NETMASK" > >@@ -254,9 +286,15 @@ function configure_interface > > > > IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" > > BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" > >+ if [ -n "${VL_CONFIG}" ]; then > >+ VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" > >+ fi > > > > printf "$IF_CONFIG\n"> $IF_FILENAME > > printf "$BR_CONFIG\n"> $BR_FILENAME > >+ if [ -n "$VL_CONFIG" ]; then > >+ printf "$VL_CONFIG\n"> $VL_FILENAME > >+ fi > > fi > > } > > > >diff --git a/scripts/ovirt-early b/scripts/ovirt-early > >index b4de30e..560fa14 100755 > >--- a/scripts/ovirt-early > >+++ b/scripts/ovirt-early > >@@ -208,10 +208,12 @@ start() { > > # ipv6=dhcp|auto > > # dns=server[,server] > > # ntp=server[,server] > >+ # vlan=id > > # static network configuration > > ip_address= > > ip_gateway= > > ip_netmask= > >+ vlan= > > netmask= > > gateway= > > ipv6= > >@@ -344,6 +346,9 @@ start() { > > hostname=*) > > hostname=${i#hostname=} > > ;; > >+ vlan=*) > >+ vlan=${i#vlan=} > >+ ;; > > syslog=*) > > i=${i#syslog=} > > eval $(printf $i|awk -F: '{print "syslog_server="$1; print > > "syslog_port="$2;}') > >@@ -365,7 +370,7 @@ start() { > > ip_gateway=$gateway > > fi > > # save boot parameters as defaults for ovirt-config-* > >- params="bootif init vol_boot_size vol_swap_size vol_root_size > >vol_config_size vol_logging_size vol_data_size local_boot standalone > >overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp syslog_server > >syslog_port collectd_server collectd_port bootparams hostname firstboot" > >+ params="bootif init vol_boot_size vol_swap_size vol_root_size > >vol_config_size vol_logging_size vol_data_size local_boot standalone > >overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan > >syslog_server syslog_port collectd_server collectd_port bootparams > >hostname firstboot" > > # mount /config unless firstboot is forced > > if [ "$firstboot" != "1" ]; then > > mount_config > > > > Tried this on 2 different boxes getting whitespace errors, but the patch > file doesn't show any extra spaces? > > # cat -v $patchfile > +# Configures vlan for the node.^M > +# $1 - the nic^M > +# $2 - the network bridge name^M > +# $3 - the vlan id^M > +# $4 - the VL_ROOT variable^M > +# $5 - the VL_CONFIG variable^M > +# $6 - the IF_ROOT value^M > +# $7 - the vlan config filename variable^M > +# $8 - the NIC config filename^M > +function setup_vlan^M > > > -bash-3.2$ git am ../\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ > support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml > Applying: Adds vlan support to auto-installations for the node. bz#511056 > /home/jboggs/ovirt-node/.git/rebase-apply/patch:14: trailing whitespace. > # Configures vlan for the node. > /home/jboggs/ovirt-node/.git/rebase-apply/patch:15: trailing whitespace. > # $1 - the nic > /home/jboggs/ovirt-node/.git/rebase-apply/patch:16: trailing whitespace. > # $2 - the network bridge name > /home/jboggs/ovirt-node/.git/rebase-apply/patch:17: trailing whitespace. > # $3 - the vlan id > /home/jboggs/ovirt-node/.git/rebase-apply/patch:18: trailing whitespace. > # $4 - the VL_ROOT variable > error: patch failed: scripts/ovirt-config-networking:45 > error: scripts/ovirt-config-networking: patch does not apply > error: patch failed: scripts/ovirt-early:208 > error: scripts/ovirt-early: patch does not apply > Patch failed at 0001. > When you have resolved this problem run "git am --resolved". > If you would prefer to skip this patch, instead run "git am --skip". > To restore the original branch and stop patching run "git am --abort". > My emacs buffer shows the ^Ms that are trailing each one of the lines in that patch file... --Hugh From dpierce at redhat.com Tue Jul 14 20:05:25 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 14 Jul 2009 16:05:25 -0400 Subject: [Ovirt-devel] [PATCH node] Changes the exit prompt based on whether it's firstboot of cmdline. Message-ID: <1247601925-28656-1-git-send-email-dpierce@redhat.com> If the menu is launched during firstboot then the menu shows the "Continue with stateless boot" text. If the menu is launched from the cmdline then it will show "Exit". Resolves: rhbz#511122 Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-setup | 18 +++++++++++++++++- 1 files changed, 17 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt-config-setup b/scripts/ovirt-config-setup index ee78254..105f7bc 100755 --- a/scripts/ovirt-config-setup +++ b/scripts/ovirt-config-setup @@ -10,7 +10,23 @@ CONFIG_DIR=/etc/ovirt-config-setup.d # special options, all others execute the symlinked script in CONFIG_DIR DEBUG_SHELL="Shell" -CONTINUE="Continue Stateless Boot" + +during_firstboot=true +for tty in $(ps -ef | awk '{ printf $2"::"$6" " '}); do + if [[ "$tty" =~ "$$::" ]]; then + len=${#$} + the_tty=${tty:len+2} + if [ "$the_tty" != "?" ]; then + during_firstboot=false + fi + fi +done + +if $during_firstboot; then + CONTINUE="Continue Stateless Boot" +else + CONTINUE="Exit Setup Menu" +fi declare -a OPTIONS -- 1.6.2.5 From jboggs at redhat.com Tue Jul 14 20:07:14 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 14 Jul 2009 16:07:14 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <20090714195407.GE10740@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> <20090714195407.GE10740@redhat.com> Message-ID: <4A5CE572.3@redhat.com> On 07/14/2009 03:54 PM, Hugh O. Brock wrote: > On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: > >> On 07/13/2009 03:11 PM, Darryl L. Pierce wrote: >> >>> If the vlan kernel argument is provided, then it is used during >>> networking auto-configuration. >>> >>> Signed-off-by: Darryl L. Pierce >>> --- >>> scripts/ovirt-config-networking | 56 >>> ++++++++++++++++++++++++++++++++------ >>> scripts/ovirt-early | 7 ++++- >>> 2 files changed, 53 insertions(+), 10 deletions(-) >>> >>> diff --git a/scripts/ovirt-config-networking >>> b/scripts/ovirt-config-networking >>> index 2674dfe..2bd082e 100755 >>> --- a/scripts/ovirt-config-networking >>> +++ b/scripts/ovirt-config-networking >>> @@ -45,6 +45,33 @@ function has_configured_interface >>> fi >>> } >>> >>> +# Configures vlan for the node. >>> +# $1 - the nic >>> +# $2 - the network bridge name >>> +# $3 - the vlan id >>> +# $4 - the VL_ROOT variable >>> +# $5 - the VL_CONFIG variable >>> +# $6 - the IF_ROOT value >>> +# $7 - the vlan config filename variable >>> +# $8 - the NIC config filename >>> +function setup_vlan >>> +{ >>> + local nic=$1 >>> + local bridge=$2 >>> + local vlan_id=$3 >>> + local vlroot=$4 >>> + local vlconfig=$5 >>> + local ifroot=$6 >>> + local vlfilename=$7 >>> + local iffilename=$8 >>> + >>> + eval $vlroot="${ifroot}.${vlan_id}" >>> + eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE >>> ${nic}.${vlan_id}\" >>> + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE ${bridge}\" >>> + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" >>> + eval $vlfilename="${iffilename}.${vlan_id}" >>> +} >>> + >>> function configure_interface >>> { >>> local NIC=$1 >>> @@ -118,12 +145,8 @@ function configure_interface >>> A|a) CONFIGURED_NIC=""; return;; >>> *) >>> if [[ -n "$REPLY" ]]&& [[ "$REPLY" =~ >>> "^[0-9]{1,}$" ]]; then >>> - VLAN_ID=$REPLY >>> - VL_ROOT="${IF_ROOT}.${VLAN_ID}" >>> - VL_CONFIG="rm ${VL_ROOT}\nset >>> ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" >>> - VL_CONFIG="${VL_CONFIG}\nset >>> ${VL_ROOT}/BRIDGE ${BRIDGE}" >>> - VL_CONFIG="${VL_CONFIG}\nset >>> ${VL_ROOT}/VLAN yes" >>> - >>> VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" >>> + VLAN_ID=$REPLY >>> + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT >>> VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME >>> break >>> fi >>> ;; >>> @@ -236,12 +259,21 @@ function configure_interface >>> esac >>> fi >>> >>> + if [ -n "$OVIRT_VLAN" ]; then >>> + VLAN_ID=$OVIRT_VLAN >>> + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT >>> VL_FILENAME $IF_FILENAME >>> + fi >>> + >>> if [ -z "$OVIRT_IP_ADDRESS" ]; then >>> - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" >>> - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" >>> + if [ -z "$VL_CONFIG" ]; then >>> + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" >>> + fi >>> + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" >>> else >>> if [ "$OVIRT_IP_ADDRESS" != "off" ]; then >>> - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" >>> + if [ -z "$VL_CONFIG" ]; then >>> + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE >>> ${BRIDGE}" >>> + fi >>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR >>> $OVIRT_IP_ADDRESS" >>> if [ -n "$OVIRT_IP_NETMASK" ]; then >>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK >>> $OVIRT_IP_NETMASK" >>> @@ -254,9 +286,15 @@ function configure_interface >>> >>> IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" >>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" >>> + if [ -n "${VL_CONFIG}" ]; then >>> + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" >>> + fi >>> >>> printf "$IF_CONFIG\n"> $IF_FILENAME >>> printf "$BR_CONFIG\n"> $BR_FILENAME >>> + if [ -n "$VL_CONFIG" ]; then >>> + printf "$VL_CONFIG\n"> $VL_FILENAME >>> + fi >>> fi >>> } >>> >>> diff --git a/scripts/ovirt-early b/scripts/ovirt-early >>> index b4de30e..560fa14 100755 >>> --- a/scripts/ovirt-early >>> +++ b/scripts/ovirt-early >>> @@ -208,10 +208,12 @@ start() { >>> # ipv6=dhcp|auto >>> # dns=server[,server] >>> # ntp=server[,server] >>> + # vlan=id >>> # static network configuration >>> ip_address= >>> ip_gateway= >>> ip_netmask= >>> + vlan= >>> netmask= >>> gateway= >>> ipv6= >>> @@ -344,6 +346,9 @@ start() { >>> hostname=*) >>> hostname=${i#hostname=} >>> ;; >>> + vlan=*) >>> + vlan=${i#vlan=} >>> + ;; >>> syslog=*) >>> i=${i#syslog=} >>> eval $(printf $i|awk -F: '{print "syslog_server="$1; print >>> "syslog_port="$2;}') >>> @@ -365,7 +370,7 @@ start() { >>> ip_gateway=$gateway >>> fi >>> # save boot parameters as defaults for ovirt-config-* >>> - params="bootif init vol_boot_size vol_swap_size vol_root_size >>> vol_config_size vol_logging_size vol_data_size local_boot standalone >>> overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp syslog_server >>> syslog_port collectd_server collectd_port bootparams hostname firstboot" >>> + params="bootif init vol_boot_size vol_swap_size vol_root_size >>> vol_config_size vol_logging_size vol_data_size local_boot standalone >>> overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan >>> syslog_server syslog_port collectd_server collectd_port bootparams >>> hostname firstboot" >>> # mount /config unless firstboot is forced >>> if [ "$firstboot" != "1" ]; then >>> mount_config >>> >>> >> Tried this on 2 different boxes getting whitespace errors, but the patch >> file doesn't show any extra spaces? >> >> # cat -v $patchfile >> +# Configures vlan for the node.^M >> +# $1 - the nic^M >> +# $2 - the network bridge name^M >> +# $3 - the vlan id^M >> +# $4 - the VL_ROOT variable^M >> +# $5 - the VL_CONFIG variable^M >> +# $6 - the IF_ROOT value^M >> +# $7 - the vlan config filename variable^M >> +# $8 - the NIC config filename^M >> +function setup_vlan^M >> >> >> -bash-3.2$ git am ../\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ >> support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml >> Applying: Adds vlan support to auto-installations for the node. bz#511056 >> /home/jboggs/ovirt-node/.git/rebase-apply/patch:14: trailing whitespace. >> # Configures vlan for the node. >> /home/jboggs/ovirt-node/.git/rebase-apply/patch:15: trailing whitespace. >> # $1 - the nic >> /home/jboggs/ovirt-node/.git/rebase-apply/patch:16: trailing whitespace. >> # $2 - the network bridge name >> /home/jboggs/ovirt-node/.git/rebase-apply/patch:17: trailing whitespace. >> # $3 - the vlan id >> /home/jboggs/ovirt-node/.git/rebase-apply/patch:18: trailing whitespace. >> # $4 - the VL_ROOT variable >> error: patch failed: scripts/ovirt-config-networking:45 >> error: scripts/ovirt-config-networking: patch does not apply >> error: patch failed: scripts/ovirt-early:208 >> error: scripts/ovirt-early: patch does not apply >> Patch failed at 0001. >> When you have resolved this problem run "git am --resolved". >> If you would prefer to skip this patch, instead run "git am --skip". >> To restore the original branch and stop patching run "git am --abort". >> >> > > My emacs buffer shows the ^Ms that are trailing each one of the lines in > that patch file... > > --Hugh > The ^M's are carriage return or whatever can't remember, I ran cat -v to at least make sure the last character was the last one on the line followed by the return. Anyone have any other ideas? -------------- next part -------------- An HTML attachment was scrubbed... URL: From dpierce at redhat.com Tue Jul 14 20:08:00 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 14 Jul 2009 16:08:00 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <4A5CD7CC.8020303@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> Message-ID: <20090714200800.GI3432@mcpierce-laptop.rdu.redhat.com> On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: > Tried this on 2 different boxes getting whitespace errors, but the patch > file doesn't show any extra spaces? Are you trying to apply them upstream or to EL-5? These are upstream patches. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From jboggs at redhat.com Tue Jul 14 20:10:24 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 14 Jul 2009 16:10:24 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <20090714200800.GI3432@mcpierce-laptop.rdu.redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> <20090714200800.GI3432@mcpierce-laptop.rdu.redhat.com> Message-ID: <4A5CE630.8010603@redhat.com> On 07/14/2009 04:08 PM, Darryl L. Pierce wrote: > On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: > >> Tried this on 2 different boxes getting whitespace errors, but the patch >> file doesn't show any extra spaces? >> > > Are you trying to apply them upstream or to EL-5? These are upstream > patches. > > Yeah tried directly against upstream next branch: [jboggs at localhost ovirt-node]$ git branch master * next [jboggs at localhost ovirt-node]$ git am ~/mbox-patch/\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml Applying: Adds vlan support to auto-installations for the node. bz#511056 /home/jboggs/Desktop/upstream/ovirt-node/.git/rebase-apply/patch:14: trailing whitespace. # Configures vlan for the node. /home/jboggs/Desktop/upstream/ovirt-node/.git/rebase-apply/patch:15: trailing whitespace. # $1 - the nic /home/jboggs/Desktop/upstream/ovirt-node/.git/rebase-apply/patch:16: trailing whitespace. # $2 - the network bridge name /home/jboggs/Desktop/upstream/ovirt-node/.git/rebase-apply/patch:17: trailing whitespace. # $3 - the vlan id /home/jboggs/Desktop/upstream/ovirt-node/.git/rebase-apply/patch:18: trailing whitespace. # $4 - the VL_ROOT variable error: patch failed: scripts/ovirt-config-networking:45 error: scripts/ovirt-config-networking: patch does not apply error: patch failed: scripts/ovirt-early:208 error: scripts/ovirt-early: patch does not apply Patch failed at 0001 Adds vlan support to auto-installations for the node. bz#511056 When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort". [jboggs at localhost ovirt-node]$ -------------- next part -------------- An HTML attachment was scrubbed... URL: From imain at redhat.com Tue Jul 14 20:26:01 2009 From: imain at redhat.com (Ian Main) Date: Tue, 14 Jul 2009 13:26:01 -0700 Subject: [Ovirt-devel] [PATCH] Use volume key instead of path to identify volume. In-Reply-To: <1247517954-23811-1-git-send-email-imain@redhat.com> References: <1247517954-23811-1-git-send-email-imain@redhat.com> Message-ID: <1247603161-8846-1-git-send-email-imain@redhat.com> This patch teaches taskomatic to use the volume 'key' instead of the path from libvirt to key the volume off of in the database. This fixes the duplicate iscsi volume bug we were seeing. The issue was that libvirt changed the way they name storage volumes and included a local ID that changed each time it was attached. This patch now adds a 'key' field to the storage_volumes table and uses that to key off for various taskomatic related activities. Note that the first run with this new patch will cause duplicate volumes because of the key change. Also because volumes are now looked up by 'key' any VMs using old volumes will not work, so you will have to delete all your VMs and rescan your storage volumes to make ovirt work properly again. Signed-off-by: Ian Main --- src/db/migrate/040_add_key_to_volumes.rb | 28 ++++++++++++++++++++++++ src/task-omatic/taskomatic.rb | 34 ++++++++++++++++++----------- 2 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 src/db/migrate/040_add_key_to_volumes.rb diff --git a/src/db/migrate/040_add_key_to_volumes.rb b/src/db/migrate/040_add_key_to_volumes.rb new file mode 100644 index 0000000..085aba7 --- /dev/null +++ b/src/db/migrate/040_add_key_to_volumes.rb @@ -0,0 +1,28 @@ +# +# Copyright (C) 2009 Red Hat, Inc. +# Written by Ian Main +# +# 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. + +class AddKeyToVolumes < ActiveRecord::Migration + def self.up + add_column :storage_volumes, :key, :string, :null => true + end + + def self.down + remove_column :storage_volumes, :key + end +end diff --git a/src/task-omatic/taskomatic.rb b/src/task-omatic/taskomatic.rb index b3c0592..8088769 100755 --- a/src/task-omatic/taskomatic.rb +++ b/src/task-omatic/taskomatic.rb @@ -211,17 +211,17 @@ class TaskOmatic libvirt_pool.connect(@session, node) # OK, the pool should be all set. The last thing we need to do is get - # the path based on the volume name + # the path based on the volume key - volume_name = db_volume.read_attribute(db_volume.volume_name) + volume_key = db_volume.key pool = libvirt_pool.remote_pool @logger.debug "Pool mounted: #{pool.name}; state: #{pool.state}" volume = @session.object(:class => 'volume', - 'name' => volume_name, + 'key' => volume_key, 'storagePool' => pool.object_id) - raise "Unable to find volume #{volume_name} attached to pool #{pool.name}." unless volume + raise "Unable to find volume #{volume_key} attached to pool #{pool.name}." unless volume @logger.debug "Verified volume of pool #{volume.path}" storagedevs << volume.path @@ -579,7 +579,9 @@ class TaskOmatic @logger.info "host #{host.hostname} is disabled" next end + puts "searching for node with hostname #{host.hostname}" node = @session.object(:class => 'node', 'hostname' => host.hostname) + puts "node returned is #{node}" return node if node end @@ -592,6 +594,7 @@ class TaskOmatic storage_volume.size = volume.capacity / 1024 storage_volume.storage_pool_id = db_pool.id storage_volume.write_attribute(storage_volume.volume_name, volume.name) + storage_volume.key = volume.key storage_volume.lv_owner_perms = owner storage_volume.lv_group_perms = group storage_volume.lv_mode_perms = mode @@ -643,14 +646,15 @@ class TaskOmatic storage_volume = StorageVolume.factory(db_pool_phys.get_type_label) existing_vol = StorageVolume.find(:first, :conditions => - ["storage_pool_id = ? AND #{storage_volume.volume_name} = ?", - db_pool_phys.id, volume.name]) + ["storage_pool_id = ? AND key = ?", + db_pool_phys.id, volume.key]) + puts "Existing volume is #{existing_vol}, searched for storage volume key and #{volume.key}" # Only add if it's not already there. if not existing_vol add_volume_to_db(db_pool_phys, volume); else - @logger.debug "Scanned volume #{volume.name} already exists in db.." + @logger.debug "Scanned volume #{volume.key} already exists in db.." end # Now check for an LVM pool carving up this volume. @@ -691,12 +695,12 @@ class TaskOmatic lvm_storage_volume = StorageVolume.factory(lvm_db_pool.get_type_label) existing_vol = StorageVolume.find(:first, :conditions => - ["storage_pool_id = ? AND #{lvm_storage_volume.volume_name} = ?", - lvm_db_pool.id, lvm_volume.name]) + ["storage_pool_id = ? AND key = ?", + lvm_db_pool.id, lvm_volume.key]) if not existing_vol add_volume_to_db(lvm_db_pool, lvm_volume, "0744", "0744", "0744"); else - @logger.info "volume #{lvm_volume.name} already exists in db.." + @logger.info "volume #{lvm_volume.key} already exists in db.." end end end @@ -737,9 +741,8 @@ class TaskOmatic @logger.debug " property: #{key}, #{val}" end - # FIXME: Should have this too I think.. - #db_volume.key = volume.key db_volume.reload + db_volume.key = volume.key db_volume.path = volume.path db_volume.state = StorageVolume::STATE_AVAILABLE db_volume.save! @@ -747,6 +750,8 @@ class TaskOmatic db_pool.reload db_pool.state = StoragePool::STATE_AVAILABLE db_pool.save! + rescue => ex + @logger.error "Error saving new volume: #{ex.class}: #{ex.message}" ensure libvirt_pool.shutdown end @@ -795,7 +800,7 @@ class TaskOmatic begin volume = @session.object(:class => 'volume', 'storagePool' => libvirt_pool.remote_pool.object_id, - 'path' => db_volume.path) + 'key' => db_volume.key) @logger.error "Unable to find volume to delete" unless volume # FIXME: we actually probably want to zero out the whole volume @@ -811,6 +816,8 @@ class TaskOmatic # it up, so just carry on here.. volume.delete if volume + @logger.info "Volume deleted successfully." + # Note: we have to nil out the task_target because when we delete the # volume object, that also deletes all dependent tasks (including this # one), which leads to accessing stale tasks. Orphan the task, then @@ -854,6 +861,7 @@ class TaskOmatic @logger.info("Reconnected, resuming task checking..") if was_disconnected was_disconnected = false + @session.object(:class => 'agent') tasks = Array.new begin -- 1.6.0.6 From dpierce at redhat.com Tue Jul 14 20:35:40 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 14 Jul 2009 16:35:40 -0400 Subject: [Ovirt-devel] [PATCH node] Filters out all non-physical networking devices. Message-ID: <1247603740-29846-1-git-send-email-dpierce@redhat.com> This patch will scan through the list of devices that have the net.80203 capability. It will then also ensure that only those which originate from a physical device and are not logical devices are presented. Resolves: rhbz#511369 Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 6d23735..0de7529 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -375,7 +375,9 @@ function setup_menu udi_list=$(hal-find-by-capability --capability net.80203) if [ -n "$udi_list" ]; then for d in $udi_list; do - NICS="$NICS $(hal-get-property --udi "$d" --key net.interface)" + if [[ ! "$(hal-get-property --udi $d --key net.originating_device)" =~ computer ]]; then + NICS="$NICS $(hal-get-property --udi "$d" --key net.interface)" + fi done fi -- 1.6.2.5 From hbrock at redhat.com Wed Jul 15 03:57:12 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Tue, 14 Jul 2009 23:57:12 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <4A5CE572.3@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> <20090714195407.GE10740@redhat.com> <4A5CE572.3@redhat.com> Message-ID: <20090715035712.GJ10740@redhat.com> On Tue, Jul 14, 2009 at 04:07:14PM -0400, Joey Boggs wrote: > On 07/14/2009 03:54 PM, Hugh O. Brock wrote: > >On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: > > > >>On 07/13/2009 03:11 PM, Darryl L. Pierce wrote: > >> > >>>If the vlan kernel argument is provided, then it is used during > >>>networking auto-configuration. > >>> > >>>Signed-off-by: Darryl L. Pierce > >>>--- > >>> scripts/ovirt-config-networking | 56 > >>> ++++++++++++++++++++++++++++++++------ > >>> scripts/ovirt-early | 7 ++++- > >>> 2 files changed, 53 insertions(+), 10 deletions(-) > >>> > >>>diff --git a/scripts/ovirt-config-networking > >>>b/scripts/ovirt-config-networking > >>>index 2674dfe..2bd082e 100755 > >>>--- a/scripts/ovirt-config-networking > >>>+++ b/scripts/ovirt-config-networking > >>>@@ -45,6 +45,33 @@ function has_configured_interface > >>> fi > >>> } > >>> > >>>+# Configures vlan for the node. > >>>+# $1 - the nic > >>>+# $2 - the network bridge name > >>>+# $3 - the vlan id > >>>+# $4 - the VL_ROOT variable > >>>+# $5 - the VL_CONFIG variable > >>>+# $6 - the IF_ROOT value > >>>+# $7 - the vlan config filename variable > >>>+# $8 - the NIC config filename > >>>+function setup_vlan > >>>+{ > >>>+ local nic=$1 > >>>+ local bridge=$2 > >>>+ local vlan_id=$3 > >>>+ local vlroot=$4 > >>>+ local vlconfig=$5 > >>>+ local ifroot=$6 > >>>+ local vlfilename=$7 > >>>+ local iffilename=$8 > >>>+ > >>>+ eval $vlroot="${ifroot}.${vlan_id}" > >>>+ eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE > >>>${nic}.${vlan_id}\" > >>>+ eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE ${bridge}\" > >>>+ eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" > >>>+ eval $vlfilename="${iffilename}.${vlan_id}" > >>>+} > >>>+ > >>> function configure_interface > >>> { > >>> local NIC=$1 > >>>@@ -118,12 +145,8 @@ function configure_interface > >>> A|a) CONFIGURED_NIC=""; return;; > >>> *) > >>> if [[ -n "$REPLY" ]]&& [[ "$REPLY" =~ > >>> "^[0-9]{1,}$" ]]; then > >>>- VLAN_ID=$REPLY > >>>- VL_ROOT="${IF_ROOT}.${VLAN_ID}" > >>>- VL_CONFIG="rm ${VL_ROOT}\nset > >>>${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" > >>>- VL_CONFIG="${VL_CONFIG}\nset > >>>${VL_ROOT}/BRIDGE ${BRIDGE}" > >>>- VL_CONFIG="${VL_CONFIG}\nset > >>>${VL_ROOT}/VLAN yes" > >>>- > >>>VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" > >>>+ VLAN_ID=$REPLY > >>>+ setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT > >>>VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME > >>> break > >>> fi > >>> ;; > >>>@@ -236,12 +259,21 @@ function configure_interface > >>> esac > >>> fi > >>> > >>>+ if [ -n "$OVIRT_VLAN" ]; then > >>>+ VLAN_ID=$OVIRT_VLAN > >>>+ setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT > >>>VL_FILENAME $IF_FILENAME > >>>+ fi > >>>+ > >>> if [ -z "$OVIRT_IP_ADDRESS" ]; then > >>>- IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > >>>- BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" > >>>+ if [ -z "$VL_CONFIG" ]; then > >>>+ IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" > >>>+ fi > >>>+ BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" > >>> else > >>> if [ "$OVIRT_IP_ADDRESS" != "off" ]; then > >>>- IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE > >>>${BRIDGE}" > >>>+ if [ -z "$VL_CONFIG" ]; then > >>>+ IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE > >>>${BRIDGE}" > >>>+ fi > >>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR > >>> $OVIRT_IP_ADDRESS" > >>> if [ -n "$OVIRT_IP_NETMASK" ]; then > >>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK > >>> $OVIRT_IP_NETMASK" > >>>@@ -254,9 +286,15 @@ function configure_interface > >>> > >>> IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" > >>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" > >>>+ if [ -n "${VL_CONFIG}" ]; then > >>>+ VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" > >>>+ fi > >>> > >>> printf "$IF_CONFIG\n"> $IF_FILENAME > >>> printf "$BR_CONFIG\n"> $BR_FILENAME > >>>+ if [ -n "$VL_CONFIG" ]; then > >>>+ printf "$VL_CONFIG\n"> $VL_FILENAME > >>>+ fi > >>> fi > >>> } > >>> > >>>diff --git a/scripts/ovirt-early b/scripts/ovirt-early > >>>index b4de30e..560fa14 100755 > >>>--- a/scripts/ovirt-early > >>>+++ b/scripts/ovirt-early > >>>@@ -208,10 +208,12 @@ start() { > >>> # ipv6=dhcp|auto > >>> # dns=server[,server] > >>> # ntp=server[,server] > >>>+ # vlan=id > >>> # static network configuration > >>> ip_address= > >>> ip_gateway= > >>> ip_netmask= > >>>+ vlan= > >>> netmask= > >>> gateway= > >>> ipv6= > >>>@@ -344,6 +346,9 @@ start() { > >>> hostname=*) > >>> hostname=${i#hostname=} > >>> ;; > >>>+ vlan=*) > >>>+ vlan=${i#vlan=} > >>>+ ;; > >>> syslog=*) > >>> i=${i#syslog=} > >>> eval $(printf $i|awk -F: '{print "syslog_server="$1; print > >>> "syslog_port="$2;}') > >>>@@ -365,7 +370,7 @@ start() { > >>> ip_gateway=$gateway > >>> fi > >>> # save boot parameters as defaults for ovirt-config-* > >>>- params="bootif init vol_boot_size vol_swap_size vol_root_size > >>>vol_config_size vol_logging_size vol_data_size local_boot standalone > >>>overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp syslog_server > >>>syslog_port collectd_server collectd_port bootparams hostname firstboot" > >>>+ params="bootif init vol_boot_size vol_swap_size vol_root_size > >>>vol_config_size vol_logging_size vol_data_size local_boot standalone > >>>overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan > >>>syslog_server syslog_port collectd_server collectd_port bootparams > >>>hostname firstboot" > >>> # mount /config unless firstboot is forced > >>> if [ "$firstboot" != "1" ]; then > >>> mount_config > >>> > >>> > >>Tried this on 2 different boxes getting whitespace errors, but the patch > >>file doesn't show any extra spaces? > >> > >># cat -v $patchfile > >>+# Configures vlan for the node.^M > >>+# $1 - the nic^M > >>+# $2 - the network bridge name^M > >>+# $3 - the vlan id^M > >>+# $4 - the VL_ROOT variable^M > >>+# $5 - the VL_CONFIG variable^M > >>+# $6 - the IF_ROOT value^M > >>+# $7 - the vlan config filename variable^M > >>+# $8 - the NIC config filename^M > >>+function setup_vlan^M > >> > >> > >>-bash-3.2$ git am ../\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ > >>support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml > >>Applying: Adds vlan support to auto-installations for the node. bz#511056 > >>/home/jboggs/ovirt-node/.git/rebase-apply/patch:14: trailing whitespace. > >># Configures vlan for the node. > >>/home/jboggs/ovirt-node/.git/rebase-apply/patch:15: trailing whitespace. > >># $1 - the nic > >>/home/jboggs/ovirt-node/.git/rebase-apply/patch:16: trailing whitespace. > >># $2 - the network bridge name > >>/home/jboggs/ovirt-node/.git/rebase-apply/patch:17: trailing whitespace. > >># $3 - the vlan id > >>/home/jboggs/ovirt-node/.git/rebase-apply/patch:18: trailing whitespace. > >># $4 - the VL_ROOT variable > >>error: patch failed: scripts/ovirt-config-networking:45 > >>error: scripts/ovirt-config-networking: patch does not apply > >>error: patch failed: scripts/ovirt-early:208 > >>error: scripts/ovirt-early: patch does not apply > >>Patch failed at 0001. > >>When you have resolved this problem run "git am --resolved". > >>If you would prefer to skip this patch, instead run "git am --skip". > >>To restore the original branch and stop patching run "git am --abort". > >> > >> > > > >My emacs buffer shows the ^Ms that are trailing each one of the lines in > >that patch file... > > > >--Hugh > > > > The ^M's are carriage return or whatever can't remember, I ran cat -v to > at least make sure the last character was the last one on the line > followed by the return. Anyone have any other ideas? The ^Ms are Windows carriage returns -- git's trailing whitespace filter picks them up if I'm not mistaken (I remember lots of pain with this when we were pulling in jquery patches from Windows guys back in the day). Try swapping them for Unix newlines in an emacs buffer after adding the below to your .emacs: ;; always show cruddy windows newlines (setq-default inhibit-eol-conversion t) Hope this helps, --Hugh From jboggs at redhat.com Wed Jul 15 12:59:19 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 15 Jul 2009 08:59:19 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <20090715035712.GJ10740@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> <20090714195407.GE10740@redhat.com> <4A5CE572.3@redhat.com> <20090715035712.GJ10740@redhat.com> Message-ID: <4A5DD2A7.9040709@redhat.com> On 07/14/2009 11:57 PM, Hugh O. Brock wrote: > On Tue, Jul 14, 2009 at 04:07:14PM -0400, Joey Boggs wrote: > >> On 07/14/2009 03:54 PM, Hugh O. Brock wrote: >> >>> On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: >>> >>> >>>> On 07/13/2009 03:11 PM, Darryl L. Pierce wrote: >>>> >>>> >>>>> If the vlan kernel argument is provided, then it is used during >>>>> networking auto-configuration. >>>>> >>>>> Signed-off-by: Darryl L. Pierce >>>>> --- >>>>> scripts/ovirt-config-networking | 56 >>>>> ++++++++++++++++++++++++++++++++------ >>>>> scripts/ovirt-early | 7 ++++- >>>>> 2 files changed, 53 insertions(+), 10 deletions(-) >>>>> >>>>> diff --git a/scripts/ovirt-config-networking >>>>> b/scripts/ovirt-config-networking >>>>> index 2674dfe..2bd082e 100755 >>>>> --- a/scripts/ovirt-config-networking >>>>> +++ b/scripts/ovirt-config-networking >>>>> @@ -45,6 +45,33 @@ function has_configured_interface >>>>> fi >>>>> } >>>>> >>>>> +# Configures vlan for the node. >>>>> +# $1 - the nic >>>>> +# $2 - the network bridge name >>>>> +# $3 - the vlan id >>>>> +# $4 - the VL_ROOT variable >>>>> +# $5 - the VL_CONFIG variable >>>>> +# $6 - the IF_ROOT value >>>>> +# $7 - the vlan config filename variable >>>>> +# $8 - the NIC config filename >>>>> +function setup_vlan >>>>> +{ >>>>> + local nic=$1 >>>>> + local bridge=$2 >>>>> + local vlan_id=$3 >>>>> + local vlroot=$4 >>>>> + local vlconfig=$5 >>>>> + local ifroot=$6 >>>>> + local vlfilename=$7 >>>>> + local iffilename=$8 >>>>> + >>>>> + eval $vlroot="${ifroot}.${vlan_id}" >>>>> + eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE >>>>> ${nic}.${vlan_id}\" >>>>> + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE ${bridge}\" >>>>> + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" >>>>> + eval $vlfilename="${iffilename}.${vlan_id}" >>>>> +} >>>>> + >>>>> function configure_interface >>>>> { >>>>> local NIC=$1 >>>>> @@ -118,12 +145,8 @@ function configure_interface >>>>> A|a) CONFIGURED_NIC=""; return;; >>>>> *) >>>>> if [[ -n "$REPLY" ]]&& [[ "$REPLY" =~ >>>>> "^[0-9]{1,}$" ]]; then >>>>> - VLAN_ID=$REPLY >>>>> - VL_ROOT="${IF_ROOT}.${VLAN_ID}" >>>>> - VL_CONFIG="rm ${VL_ROOT}\nset >>>>> ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" >>>>> - VL_CONFIG="${VL_CONFIG}\nset >>>>> ${VL_ROOT}/BRIDGE ${BRIDGE}" >>>>> - VL_CONFIG="${VL_CONFIG}\nset >>>>> ${VL_ROOT}/VLAN yes" >>>>> - >>>>> VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" >>>>> + VLAN_ID=$REPLY >>>>> + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT >>>>> VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME >>>>> break >>>>> fi >>>>> ;; >>>>> @@ -236,12 +259,21 @@ function configure_interface >>>>> esac >>>>> fi >>>>> >>>>> + if [ -n "$OVIRT_VLAN" ]; then >>>>> + VLAN_ID=$OVIRT_VLAN >>>>> + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT >>>>> VL_FILENAME $IF_FILENAME >>>>> + fi >>>>> + >>>>> if [ -z "$OVIRT_IP_ADDRESS" ]; then >>>>> - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" >>>>> - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" >>>>> + if [ -z "$VL_CONFIG" ]; then >>>>> + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" >>>>> + fi >>>>> + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" >>>>> else >>>>> if [ "$OVIRT_IP_ADDRESS" != "off" ]; then >>>>> - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE >>>>> ${BRIDGE}" >>>>> + if [ -z "$VL_CONFIG" ]; then >>>>> + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE >>>>> ${BRIDGE}" >>>>> + fi >>>>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR >>>>> $OVIRT_IP_ADDRESS" >>>>> if [ -n "$OVIRT_IP_NETMASK" ]; then >>>>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK >>>>> $OVIRT_IP_NETMASK" >>>>> @@ -254,9 +286,15 @@ function configure_interface >>>>> >>>>> IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" >>>>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" >>>>> + if [ -n "${VL_CONFIG}" ]; then >>>>> + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" >>>>> + fi >>>>> >>>>> printf "$IF_CONFIG\n"> $IF_FILENAME >>>>> printf "$BR_CONFIG\n"> $BR_FILENAME >>>>> + if [ -n "$VL_CONFIG" ]; then >>>>> + printf "$VL_CONFIG\n"> $VL_FILENAME >>>>> + fi >>>>> fi >>>>> } >>>>> >>>>> diff --git a/scripts/ovirt-early b/scripts/ovirt-early >>>>> index b4de30e..560fa14 100755 >>>>> --- a/scripts/ovirt-early >>>>> +++ b/scripts/ovirt-early >>>>> @@ -208,10 +208,12 @@ start() { >>>>> # ipv6=dhcp|auto >>>>> # dns=server[,server] >>>>> # ntp=server[,server] >>>>> + # vlan=id >>>>> # static network configuration >>>>> ip_address= >>>>> ip_gateway= >>>>> ip_netmask= >>>>> + vlan= >>>>> netmask= >>>>> gateway= >>>>> ipv6= >>>>> @@ -344,6 +346,9 @@ start() { >>>>> hostname=*) >>>>> hostname=${i#hostname=} >>>>> ;; >>>>> + vlan=*) >>>>> + vlan=${i#vlan=} >>>>> + ;; >>>>> syslog=*) >>>>> i=${i#syslog=} >>>>> eval $(printf $i|awk -F: '{print "syslog_server="$1; print >>>>> "syslog_port="$2;}') >>>>> @@ -365,7 +370,7 @@ start() { >>>>> ip_gateway=$gateway >>>>> fi >>>>> # save boot parameters as defaults for ovirt-config-* >>>>> - params="bootif init vol_boot_size vol_swap_size vol_root_size >>>>> vol_config_size vol_logging_size vol_data_size local_boot standalone >>>>> overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp syslog_server >>>>> syslog_port collectd_server collectd_port bootparams hostname firstboot" >>>>> + params="bootif init vol_boot_size vol_swap_size vol_root_size >>>>> vol_config_size vol_logging_size vol_data_size local_boot standalone >>>>> overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan >>>>> syslog_server syslog_port collectd_server collectd_port bootparams >>>>> hostname firstboot" >>>>> # mount /config unless firstboot is forced >>>>> if [ "$firstboot" != "1" ]; then >>>>> mount_config >>>>> >>>>> >>>>> >>>> Tried this on 2 different boxes getting whitespace errors, but the patch >>>> file doesn't show any extra spaces? >>>> >>>> # cat -v $patchfile >>>> +# Configures vlan for the node.^M >>>> +# $1 - the nic^M >>>> +# $2 - the network bridge name^M >>>> +# $3 - the vlan id^M >>>> +# $4 - the VL_ROOT variable^M >>>> +# $5 - the VL_CONFIG variable^M >>>> +# $6 - the IF_ROOT value^M >>>> +# $7 - the vlan config filename variable^M >>>> +# $8 - the NIC config filename^M >>>> +function setup_vlan^M >>>> >>>> >>>> -bash-3.2$ git am ../\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ >>>> support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml >>>> Applying: Adds vlan support to auto-installations for the node. bz#511056 >>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:14: trailing whitespace. >>>> # Configures vlan for the node. >>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:15: trailing whitespace. >>>> # $1 - the nic >>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:16: trailing whitespace. >>>> # $2 - the network bridge name >>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:17: trailing whitespace. >>>> # $3 - the vlan id >>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:18: trailing whitespace. >>>> # $4 - the VL_ROOT variable >>>> error: patch failed: scripts/ovirt-config-networking:45 >>>> error: scripts/ovirt-config-networking: patch does not apply >>>> error: patch failed: scripts/ovirt-early:208 >>>> error: scripts/ovirt-early: patch does not apply >>>> Patch failed at 0001. >>>> When you have resolved this problem run "git am --resolved". >>>> If you would prefer to skip this patch, instead run "git am --skip". >>>> To restore the original branch and stop patching run "git am --abort". >>>> >>>> >>>> >>> My emacs buffer shows the ^Ms that are trailing each one of the lines in >>> that patch file... >>> >>> --Hugh >>> >>> >> The ^M's are carriage return or whatever can't remember, I ran cat -v to >> at least make sure the last character was the last one on the line >> followed by the return. Anyone have any other ideas? >> > > The ^Ms are Windows carriage returns -- git's trailing whitespace filter > picks them up if I'm not mistaken (I remember lots of pain with this > when we were pulling in jquery patches from Windows guys back in the > day). Try swapping them for Unix newlines in an emacs buffer after > adding the below to your .emacs: > > ;; always show cruddy windows newlines > (setq-default inhibit-eol-conversion t) > > Hope this helps, > --Hugh > Only thing iI can think of is my email client (Thunderbird) but I'm running Fedora 11, maybe a bug? Don't think I've had to apply any patches since I've upgraded so that makes sense. I just removed Thunderbird 3.0 and put 2.0 from F10 in it's place, resaved the patch it's no longer complaining about the whitespaces but another can't apply error. So I guess that was it. From dpierce at redhat.com Wed Jul 15 14:38:19 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 15 Jul 2009 10:38:19 -0400 Subject: [Ovirt-devel] [PATCH node] Filters out all non-physical networking devices. Message-ID: <1247668699-3604-1-git-send-email-dpierce@redhat.com> This patch will scan through the list of devices that have the net.80203 capability. It will then also ensure that only those which originate from a physical device and are not logical devices are presented. Resolves: rhbz#511369 Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-networking | 6 ++++-- 1 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 6d23735..a040ee2 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -375,12 +375,14 @@ function setup_menu udi_list=$(hal-find-by-capability --capability net.80203) if [ -n "$udi_list" ]; then for d in $udi_list; do - NICS="$NICS $(hal-get-property --udi "$d" --key net.interface)" + if [[ ! "$(hal-get-property --udi $d --key net.physical_device)" =~ computer ]]; then + NICS="$NICS $(hal-get-property --udi "$d" --key net.interface)" + fi done fi # Add virtio NICs that were possibly not detected by hal - NICS="$NICS $(ifconfig -a | awk '/Ethernet/ {print $1}' | grep -v -E "^br|^bond|^vnet|^virbr" | xargs)" + NICS="$NICS $(ifconfig -a | awk '/Ethernet/ {print $1}' | grep -v -E "^br|^bond|^vnet|^virbr|\." | xargs)" NICS=$(echo $NICS | tr ' ' '\n' | sort -u | xargs) PS3="Please select an interface or configuration option: " -- 1.6.2.5 From mmorsi at redhat.com Wed Jul 15 16:46:44 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 15 Jul 2009 12:46:44 -0400 Subject: [Ovirt-devel] [PATCH server] convenience init script starting/stopping all ovirt services Message-ID: <1247676404-13175-1-git-send-email-mmorsi@redhat.com> installed to /usr/sbin/ovirt_ctl invoke with "sudo ovirt_ctl {start|stop|restart|on|off}" --- ovirt-server.spec.in | 2 + scripts/ovirt_ctl | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 0 deletions(-) create mode 100755 scripts/ovirt_ctl diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 1bf73c7..5fda872 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -141,6 +141,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__cp} -a %{pbuild}/scripts/ovirt-vm2node %{buildroot}%{_bindir} %{__cp} -a %{pbuild}/scripts/ovirt-reindex-search %{buildroot}%{_sbindir} %{__cp} -a %{pbuild}/scripts/ovirt-update-search %{buildroot}%{_sbindir} +%{__cp} -a %{pbuild}/scripts/ovirt_ctl %{buildroot}%{_sbindir} %{__rm} -rf %{buildroot}%{app_root}/tmp %{__mkdir} %{buildroot}%{_localstatedir}/lib/%{name}/tmp %{__ln_s} %{_localstatedir}/lib/%{name}/tmp %{buildroot}%{app_root}/tmp @@ -218,6 +219,7 @@ fi %{_sbindir}/ovirt-update-search %{_bindir}/ovirt-add-host %{_bindir}/ovirt-vm2node +%{_bindir}/ovirt_ctl %{_initrddir}/ovirt-host-browser %{_initrddir}/ovirt-host-register %{_initrddir}/ovirt-db-omatic diff --git a/scripts/ovirt_ctl b/scripts/ovirt_ctl new file mode 100755 index 0000000..e9eddde --- /dev/null +++ b/scripts/ovirt_ctl @@ -0,0 +1,66 @@ +#!/bin/bash +# control script for oVirt services, use to start/stop/restart services, and mark as on / off + +. /etc/init.d/functions + +SERVICE_CMD=/sbin/service +CHKCONFIG_CMD=/sbin/chkconfig + +SERVICES=( ovirt-db-omatic ovirt-host-browser \ + ovirt-host-collect ovirt-mongrel-rails \ + ovirt-taskomatic ovirt-vnc-proxy ovirt-agent ) + +RUNLEVELS="2345" + +start() { + for service in ${SERVICES[@]} + do + $SERVICE_CMD $service start + done +} + +stop() { + for service in ${SERVICES[@]} + do + $SERVICE_CMD $service stop + done +} + +set_on(){ + for service in ${SERVICES[@]} + do + $CHKCONFIG_CMD --levels $RUNLEVELS $service on + done +} + +set_off(){ + for service in ${SERVICES[@]} + do + $CHKCONFIG_CMD --levels $RUNLEVELS $service off + done +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + on) + set_on + ;; + off) + set_off + ;; + *) + echo "Usage: ovirt_ctl {start|stop|restart|on|off}" + exit 1 + ;; +esac + +exit $RETVAL -- 1.6.0.6 From imain at redhat.com Wed Jul 15 17:44:59 2009 From: imain at redhat.com (Ian Main) Date: Wed, 15 Jul 2009 10:44:59 -0700 Subject: [Ovirt-devel] Re: [PATCH] Use volume key instead of path to identify volume. In-Reply-To: <1247603161-8846-1-git-send-email-imain@redhat.com> References: <1247517954-23811-1-git-send-email-imain@redhat.com> <1247603161-8846-1-git-send-email-imain@redhat.com> Message-ID: <20090715104459.35e91665@tp.mains.net> On Tue, 14 Jul 2009 13:26:01 -0700 Ian Main wrote: > This patch teaches taskomatic to use the volume 'key' instead of the > path from libvirt to key the volume off of in the database. This fixes > the duplicate iscsi volume bug we were seeing. The issue was that > libvirt changed the way they name storage volumes and included a local > ID that changed each time it was attached. > > This patch now adds a 'key' field to the storage_volumes table and > uses that to key off for various taskomatic related activities. > > Note that the first run with this new patch will cause duplicate > volumes because of the key change. Also because volumes are now > looked up by 'key' any VMs using old volumes will not work, so you > will have to delete all your VMs and rescan your storage volumes to > make ovirt work properly again. > I've pushed this now, so please remember to remove your storage pools and readd them. If you don't you'll see taskomatic errors about missing volumes etc. Also a db:migrate will be required. Ian From jboggs at redhat.com Wed Jul 15 17:46:55 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 15 Jul 2009 13:46:55 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <4A5DD2A7.9040709@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> <20090714195407.GE10740@redhat.com> <4A5CE572.3@redhat.com> <20090715035712.GJ10740@redhat.com> <4A5DD2A7.9040709@redhat.com> Message-ID: <4A5E160F.6070009@redhat.com> Joey Boggs wrote: > On 07/14/2009 11:57 PM, Hugh O. Brock wrote: >> On Tue, Jul 14, 2009 at 04:07:14PM -0400, Joey Boggs wrote: >> >>> On 07/14/2009 03:54 PM, Hugh O. Brock wrote: >>> >>>> On Tue, Jul 14, 2009 at 03:09:00PM -0400, Joey Boggs wrote: >>>> >>>>> On 07/13/2009 03:11 PM, Darryl L. Pierce wrote: >>>>> >>>>>> If the vlan kernel argument is provided, then it is used during >>>>>> networking auto-configuration. >>>>>> >>>>>> Signed-off-by: Darryl L. Pierce >>>>>> --- >>>>>> scripts/ovirt-config-networking | 56 >>>>>> ++++++++++++++++++++++++++++++++------ >>>>>> scripts/ovirt-early | 7 ++++- >>>>>> 2 files changed, 53 insertions(+), 10 deletions(-) >>>>>> >>>>>> diff --git a/scripts/ovirt-config-networking >>>>>> b/scripts/ovirt-config-networking >>>>>> index 2674dfe..2bd082e 100755 >>>>>> --- a/scripts/ovirt-config-networking >>>>>> +++ b/scripts/ovirt-config-networking >>>>>> @@ -45,6 +45,33 @@ function has_configured_interface >>>>>> fi >>>>>> } >>>>>> >>>>>> +# Configures vlan for the node. >>>>>> +# $1 - the nic >>>>>> +# $2 - the network bridge name >>>>>> +# $3 - the vlan id >>>>>> +# $4 - the VL_ROOT variable >>>>>> +# $5 - the VL_CONFIG variable >>>>>> +# $6 - the IF_ROOT value >>>>>> +# $7 - the vlan config filename variable >>>>>> +# $8 - the NIC config filename >>>>>> +function setup_vlan >>>>>> +{ >>>>>> + local nic=$1 >>>>>> + local bridge=$2 >>>>>> + local vlan_id=$3 >>>>>> + local vlroot=$4 >>>>>> + local vlconfig=$5 >>>>>> + local ifroot=$6 >>>>>> + local vlfilename=$7 >>>>>> + local iffilename=$8 >>>>>> + >>>>>> + eval $vlroot="${ifroot}.${vlan_id}" >>>>>> + eval $vlconfig=\"rm \$${vlroot}\\nset \$${vlroot}/DEVICE >>>>>> ${nic}.${vlan_id}\" >>>>>> + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/BRIDGE >>>>>> ${bridge}\" >>>>>> + eval $vlconfig=\"\$${vlconfig}\\nset \$${vlroot}/VLAN yes\" >>>>>> + eval $vlfilename="${iffilename}.${vlan_id}" >>>>>> +} >>>>>> + >>>>>> function configure_interface >>>>>> { >>>>>> local NIC=$1 >>>>>> @@ -118,12 +145,8 @@ function configure_interface >>>>>> A|a) CONFIGURED_NIC=""; return;; >>>>>> *) >>>>>> if [[ -n "$REPLY" ]]&& [[ >>>>>> "$REPLY" =~ >>>>>> "^[0-9]{1,}$" ]]; then >>>>>> - VLAN_ID=$REPLY >>>>>> - VL_ROOT="${IF_ROOT}.${VLAN_ID}" >>>>>> - VL_CONFIG="rm ${VL_ROOT}\nset >>>>>> ${VL_ROOT}/DEVICE ${NIC}.${VLAN_ID}" >>>>>> - VL_CONFIG="${VL_CONFIG}\nset >>>>>> ${VL_ROOT}/BRIDGE ${BRIDGE}" >>>>>> - VL_CONFIG="${VL_CONFIG}\nset >>>>>> ${VL_ROOT}/VLAN yes" >>>>>> - >>>>>> VL_FILENAME="${IF_FILENAME}.${VLAN_ID}" >>>>>> + VLAN_ID=$REPLY >>>>>> + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT >>>>>> VL_CONFIG $IF_ROOT VL_FILENAME $IF_FILENAME >>>>>> break >>>>>> fi >>>>>> ;; >>>>>> @@ -236,12 +259,21 @@ function configure_interface >>>>>> esac >>>>>> fi >>>>>> >>>>>> + if [ -n "$OVIRT_VLAN" ]; then >>>>>> + VLAN_ID=$OVIRT_VLAN >>>>>> + setup_vlan $NIC $BRIDGE $VLAN_ID VL_ROOT VL_CONFIG $IF_ROOT >>>>>> VL_FILENAME $IF_FILENAME >>>>>> + fi >>>>>> + >>>>>> if [ -z "$OVIRT_IP_ADDRESS" ]; then >>>>>> - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE >>>>>> ${BRIDGE}" >>>>>> - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" >>>>>> + if [ -z "$VL_CONFIG" ]; then >>>>>> + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE ${BRIDGE}" >>>>>> + fi >>>>>> + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" >>>>>> else >>>>>> if [ "$OVIRT_IP_ADDRESS" != "off" ]; then >>>>>> - IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE >>>>>> ${BRIDGE}" >>>>>> + if [ -z "$VL_CONFIG" ]; then >>>>>> + IF_CONFIG="${IF_CONFIG}\nset ${IF_ROOT}/BRIDGE >>>>>> ${BRIDGE}" >>>>>> + fi >>>>>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR >>>>>> $OVIRT_IP_ADDRESS" >>>>>> if [ -n "$OVIRT_IP_NETMASK" ]; then >>>>>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK >>>>>> $OVIRT_IP_NETMASK" >>>>>> @@ -254,9 +286,15 @@ function configure_interface >>>>>> >>>>>> IF_CONFIG="$IF_CONFIG\nset $IF_ROOT/ONBOOT yes" >>>>>> BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/ONBOOT yes" >>>>>> + if [ -n "${VL_CONFIG}" ]; then >>>>>> + VL_CONFIG="$VL_CONFIG\nset $VL_ROOT/ONBOOT yes" >>>>>> + fi >>>>>> >>>>>> printf "$IF_CONFIG\n"> $IF_FILENAME >>>>>> printf "$BR_CONFIG\n"> $BR_FILENAME >>>>>> + if [ -n "$VL_CONFIG" ]; then >>>>>> + printf "$VL_CONFIG\n"> $VL_FILENAME >>>>>> + fi >>>>>> fi >>>>>> } >>>>>> >>>>>> diff --git a/scripts/ovirt-early b/scripts/ovirt-early >>>>>> index b4de30e..560fa14 100755 >>>>>> --- a/scripts/ovirt-early >>>>>> +++ b/scripts/ovirt-early >>>>>> @@ -208,10 +208,12 @@ start() { >>>>>> # ipv6=dhcp|auto >>>>>> # dns=server[,server] >>>>>> # ntp=server[,server] >>>>>> + # vlan=id >>>>>> # static network configuration >>>>>> ip_address= >>>>>> ip_gateway= >>>>>> ip_netmask= >>>>>> + vlan= >>>>>> netmask= >>>>>> gateway= >>>>>> ipv6= >>>>>> @@ -344,6 +346,9 @@ start() { >>>>>> hostname=*) >>>>>> hostname=${i#hostname=} >>>>>> ;; >>>>>> + vlan=*) >>>>>> + vlan=${i#vlan=} >>>>>> + ;; >>>>>> syslog=*) >>>>>> i=${i#syslog=} >>>>>> eval $(printf $i|awk -F: '{print "syslog_server="$1; >>>>>> print >>>>>> "syslog_port="$2;}') >>>>>> @@ -365,7 +370,7 @@ start() { >>>>>> ip_gateway=$gateway >>>>>> fi >>>>>> # save boot parameters as defaults for ovirt-config-* >>>>>> - params="bootif init vol_boot_size vol_swap_size vol_root_size >>>>>> vol_config_size vol_logging_size vol_data_size local_boot standalone >>>>>> overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp >>>>>> syslog_server >>>>>> syslog_port collectd_server collectd_port bootparams hostname >>>>>> firstboot" >>>>>> + params="bootif init vol_boot_size vol_swap_size vol_root_size >>>>>> vol_config_size vol_logging_size vol_data_size local_boot standalone >>>>>> overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan >>>>>> syslog_server syslog_port collectd_server collectd_port bootparams >>>>>> hostname firstboot" >>>>>> # mount /config unless firstboot is forced >>>>>> if [ "$firstboot" != "1" ]; then >>>>>> mount_config >>>>>> >>>>>> >>>>> Tried this on 2 different boxes getting whitespace errors, but the >>>>> patch >>>>> file doesn't show any extra spaces? >>>>> >>>>> # cat -v $patchfile >>>>> +# Configures vlan for the node.^M >>>>> +# $1 - the nic^M >>>>> +# $2 - the network bridge name^M >>>>> +# $3 - the vlan id^M >>>>> +# $4 - the VL_ROOT variable^M >>>>> +# $5 - the VL_CONFIG variable^M >>>>> +# $6 - the IF_ROOT value^M >>>>> +# $7 - the vlan config filename variable^M >>>>> +# $8 - the NIC config filename^M >>>>> +function setup_vlan^M >>>>> >>>>> >>>>> -bash-3.2$ git am ../\[Ovirt-devel\]\ \[PATCH\ node\]\ Adds\ vlan\ >>>>> support\ to\ auto-installations\ for\ the\ node.\ bz#511056.eml >>>>> Applying: Adds vlan support to auto-installations for the node. >>>>> bz#511056 >>>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:14: trailing >>>>> whitespace. >>>>> # Configures vlan for the node. >>>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:15: trailing >>>>> whitespace. >>>>> # $1 - the nic >>>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:16: trailing >>>>> whitespace. >>>>> # $2 - the network bridge name >>>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:17: trailing >>>>> whitespace. >>>>> # $3 - the vlan id >>>>> /home/jboggs/ovirt-node/.git/rebase-apply/patch:18: trailing >>>>> whitespace. >>>>> # $4 - the VL_ROOT variable >>>>> error: patch failed: scripts/ovirt-config-networking:45 >>>>> error: scripts/ovirt-config-networking: patch does not apply >>>>> error: patch failed: scripts/ovirt-early:208 >>>>> error: scripts/ovirt-early: patch does not apply >>>>> Patch failed at 0001. >>>>> When you have resolved this problem run "git am --resolved". >>>>> If you would prefer to skip this patch, instead run "git am --skip". >>>>> To restore the original branch and stop patching run "git am >>>>> --abort". >>>>> >>>>> >>>> My emacs buffer shows the ^Ms that are trailing each one of the >>>> lines in >>>> that patch file... >>>> >>>> --Hugh >>>> >>> The ^M's are carriage return or whatever can't remember, I ran cat >>> -v to at least make sure the last character was the last one on the >>> line followed by the return. Anyone have any other ideas? >>> >> >> The ^Ms are Windows carriage returns -- git's trailing whitespace filter >> picks them up if I'm not mistaken (I remember lots of pain with this >> when we were pulling in jquery patches from Windows guys back in the >> day). Try swapping them for Unix newlines in an emacs buffer after >> adding the below to your .emacs: >> >> ;; always show cruddy windows newlines >> (setq-default inhibit-eol-conversion t) >> >> Hope this helps, >> --Hugh >> > > Only thing iI can think of is my email client (Thunderbird) but I'm > running Fedora 11, maybe a bug? Don't think I've had to apply any > patches since I've upgraded so that makes sense. I just removed > Thunderbird 3.0 and put 2.0 from F10 in it's place, resaved the patch > it's no longer complaining about the whitespaces but another can't > apply error. So I guess that was it. > > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK on this finally! From dpierce at redhat.com Wed Jul 15 17:58:29 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 15 Jul 2009 13:58:29 -0400 Subject: [Ovirt-devel] [PATCH node] Adds vlan support to auto-installations for the node. bz#511056 In-Reply-To: <4A5E160F.6070009@redhat.com> References: <1247512315-28870-1-git-send-email-dpierce@redhat.com> <4A5CD7CC.8020303@redhat.com> <20090714195407.GE10740@redhat.com> <4A5CE572.3@redhat.com> <20090715035712.GJ10740@redhat.com> <4A5DD2A7.9040709@redhat.com> <4A5E160F.6070009@redhat.com> Message-ID: <20090715175829.GC8320@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 15, 2009 at 01:46:55PM -0400, Joey Boggs wrote: > ACK on this finally! Thanks. This is pushed upstream. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From apevec at gmail.com Wed Jul 15 18:03:01 2009 From: apevec at gmail.com (Alan Pevec) Date: Wed, 15 Jul 2009 20:03:01 +0200 Subject: [Ovirt-devel] [PATCH node] Filters out all non-physical networking devices. In-Reply-To: <1247668699-3604-1-git-send-email-dpierce@redhat.com> References: <1247668699-3604-1-git-send-email-dpierce@redhat.com> Message-ID: <2be7262f0907151103h3bbe2a5qbd78cc81c50181fd@mail.gmail.com> ACK -------------- next part -------------- An HTML attachment was scrubbed... URL: From imain at redhat.com Wed Jul 15 18:50:37 2009 From: imain at redhat.com (Ian Main) Date: Wed, 15 Jul 2009 11:50:37 -0700 Subject: [Ovirt-devel] [PATCH] Rename qmf-libvirt-example to libvirt-list.rb Message-ID: <1247683837-23460-1-git-send-email-imain@redhat.com> This patch renames qmf-libvirt-example to libvirt-list and makes it not repeat. Useful for debugging. File mode is also now 755. Signed-off-by: Ian Main --- src/libvirt-list.rb | 65 +++++++++++++++++++++++++++++++++++++++ src/qmf-libvirt-example.rb | 72 -------------------------------------------- 2 files changed, 65 insertions(+), 72 deletions(-) create mode 100755 src/libvirt-list.rb delete mode 100644 src/qmf-libvirt-example.rb diff --git a/src/libvirt-list.rb b/src/libvirt-list.rb new file mode 100755 index 0000000..54e8b7e --- /dev/null +++ b/src/libvirt-list.rb @@ -0,0 +1,65 @@ +#!/usr/bin/ruby + +$: << File.join(File.dirname(__FILE__), "./dutils") + +require "rubygems" +require "qpid" +require "dutils" + +get_credentials('qpidd') + +server, port = get_srv('qpidd', 'tcp') +raise "Unable to determine qpid server from DNS SRV record" if not server + +srv = "amqp://#{server}:#{port}" +puts "Connecting to #{srv}.." +s = Qpid::Qmf::Session.new() +b = s.add_broker(srv, :mechanism => 'GSSAPI') + +nodes = s.objects(:class => "node") +nodes.each do |node| + puts "node: #{node.hostname}" + for (key, val) in node.properties + puts " property: #{key}, #{val}" + end + + # Find any domains that on the current node. + domains = s.objects(:class => "domain", 'node' => node.object_id) + domains.each do |domain| + r = domain.getXMLDesc() + puts "getXMLDesc() status: #{r.status}" + puts "getXMLDesc() status: #{r.text}" + if r.status == 0 + puts "xml length: #{r.description.length}" + end + + puts " domain: #{domain.name}, state: #{domain.state}, id: #{domain.id}" + for (key, val) in domain.properties + puts " property: #{key}, #{val}" + end + end + + pools = s.objects(:class => "pool", 'node' => node.object_id) + pools.each do |pool| + puts " pool: #{pool.name}" + for (key, val) in pool.properties + puts " property: #{key}, #{val}" + end + + r = pool.getXMLDesc() + puts "getXMLDesc() status: #{r.status}" + puts "getXMLDesc() text: #{r.text}" + if r.status == 0 + puts "xml length: #{r.description.length}" + end + + # Find volumes that are part of the pool. + volumes = s.objects(:class => "volume", 'pool' => pool.object_id) + volumes.each do |volume| + puts " volume: #{volume.name}" + for (key, val) in volume.properties + puts " property: #{key}, #{val}" + end + end + end +end diff --git a/src/qmf-libvirt-example.rb b/src/qmf-libvirt-example.rb deleted file mode 100644 index 7e01ffb..0000000 --- a/src/qmf-libvirt-example.rb +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/ruby - -$: << File.join(File.dirname(__FILE__), "./dutils") - -require "rubygems" -require "qpid" -require "dutils" - -get_credentials('qpidd') - -server, port = get_srv('qpidd', 'tcp') -raise "Unable to determine qpid server from DNS SRV record" if not server - -srv = "amqp://#{server}:#{port}" -puts "Connecting to #{srv}.." -s = Qpid::Qmf::Session.new() -b = s.add_broker(srv, :mechanism => 'GSSAPI') - -while true: - nodes = s.objects(:class => "node") - nodes.each do |node| - puts "node: #{node.hostname}" - for (key, val) in node.properties - puts " property: #{key}, #{val}" - end - - # Find any domains that on the current node. - domains = s.objects(:class => "domain", 'node' => node.object_id) - domains.each do |domain| - r = domain.getXMLDesc() - puts "getXMLDesc() status: #{r.status}" - puts "getXMLDesc() status: #{r.text}" - if r.status == 0 - puts "xml length: #{r.description.length}" - end - - puts " domain: #{domain.name}, state: #{domain.state}, id: #{domain.id}" - for (key, val) in domain.properties - puts " property: #{key}, #{val}" - end - end - - pools = s.objects(:class => "pool", 'node' => node.object_id) - pools.each do |pool| - puts " pool: #{pool.name}" - for (key, val) in pool.properties - puts " property: #{key}, #{val}" - end - - r = pool.getXMLDesc() - puts "getXMLDesc() status: #{r.status}" - puts "getXMLDesc() text: #{r.text}" - if r.status == 0 - puts "xml length: #{r.description.length}" - end - - # Find volumes that are part of the pool. - volumes = s.objects(:class => "volume", 'pool' => pool.object_id) - volumes.each do |volume| - puts " volume: #{volume.name}" - for (key, val) in volume.properties - puts " property: #{key}, #{val}" - end - end - end - - end - - puts '----------------------------' - sleep(5) - -end -- 1.6.0.6 From dpierce at redhat.com Wed Jul 15 18:58:21 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 15 Jul 2009 14:58:21 -0400 Subject: [Ovirt-devel] [PATCH node] Filters out all non-physical networking devices. In-Reply-To: <2be7262f0907151103h3bbe2a5qbd78cc81c50181fd@mail.gmail.com> References: <1247668699-3604-1-git-send-email-dpierce@redhat.com> <2be7262f0907151103h3bbe2a5qbd78cc81c50181fd@mail.gmail.com> Message-ID: <20090715185821.GE8320@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 15, 2009 at 08:03:01PM +0200, Alan Pevec wrote: > ACK Pushed upstream. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From lutter at redhat.com Wed Jul 15 20:25:28 2009 From: lutter at redhat.com (David Lutterkort) Date: Wed, 15 Jul 2009 20:25:28 +0000 Subject: [Ovirt-devel] [PATCH] Rename qmf-libvirt-example to libvirt-list.rb In-Reply-To: <1247683837-23460-1-git-send-email-imain@redhat.com> References: <1247683837-23460-1-git-send-email-imain@redhat.com> Message-ID: <1247689528.7925.362.camel@avon.watzmann.net> On Wed, 2009-07-15 at 11:50 -0700, Ian Main wrote: > This patch renames qmf-libvirt-example to libvirt-list and makes it > not repeat. Useful for debugging. File mode is also now 755. > > Signed-off-by: Ian Main Looks fine to me, though it would be cleaner to do the rename with git mv, and the other changes as an additional patch. Shouldn't this ultimately go into libvirt-qpid ? There's really nothing ovirt specific about the script. David From mmorsi at redhat.com Wed Jul 15 20:44:18 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 15 Jul 2009 16:44:18 -0400 Subject: [Ovirt-devel] [PATCH server] changed vm details pane vnc uri, removed 'Remote Desktop' virt-viewer-plugin link Message-ID: <1247690658-23675-1-git-send-email-mmorsi@redhat.com> virt-viewer-plugin is deprecated and will be replaced w/ ovirt-viewer-plugin, vm details pane uri indicates ovirt-viewer should be used with the specified hostname --- src/app/views/vm/show.rhtml | 18 ++++-------------- 1 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index ffe5055..30b17cb 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -4,16 +4,7 @@ <%- content_for :action_links do -%> <%if @can_control_vms -%> - <%if @vm.has_console -%> - <%= link_to image_tag("icon_machine_11px.png") + " Remote Desktop", - {:controller => 'vm', :action => 'console', :id => @vm}, - :id=>"vnc_console_link" %> - - <% end -%> + <%# FIXME removed the vnc-viewer-plugin link, replace with ovirt-viewer-plugin link when it exists %> <%= link_to image_tag("icon_terminal_11px.png") + " Open Terminal", {:controller => 'vm', :action => 'terminal', :id => @vm}, @@ -101,7 +92,7 @@
    Uuid:
    - <%= @vm.forward_vnc ? "VNC uri:
    " : "" %> + VNC uri:
    Num vcpus allocated:
    Num vcpus used:
    Memory allocated:
    @@ -115,9 +106,8 @@
    <%=h @vm.uuid %>
    - <%= url = request.url - url = request.url[0..(url.index('/', 8) - 1)] + ":" + @vm.forward_vnc_port.to_s - @vm.forward_vnc ? (url + "
    ") : "" %> + ovirt-viewer@
    + <%=h @vm.num_vcpus_allocated %>
    <%=h @vm.num_vcpus_used %>
    <%=h @vm.memory_allocated_in_mb %> MB
    -- 1.6.0.6 From dpierce at redhat.com Wed Jul 15 23:29:02 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 15 Jul 2009 19:29:02 -0400 Subject: [Ovirt-devel] [PATCH node] Changes the exit/continue based on context. Message-ID: <1247700542-25947-1-git-send-email-dpierce@redhat.com> If the script is run during the system startup then it says "Continue with stateless boot". If the script is run from the command line then it says "Quite and Exit". Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-setup | 22 ++++++++++++++++++++-- scripts/ovirt-firstboot | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/scripts/ovirt-config-setup b/scripts/ovirt-config-setup index ee78254..4010828 100755 --- a/scripts/ovirt-config-setup +++ b/scripts/ovirt-config-setup @@ -10,7 +10,6 @@ CONFIG_DIR=/etc/ovirt-config-setup.d # special options, all others execute the symlinked script in CONFIG_DIR DEBUG_SHELL="Shell" -CONTINUE="Continue Stateless Boot" declare -a OPTIONS @@ -32,13 +31,32 @@ for cfg in $CONFIG_DIR/*; do fi done OPTIONS[${#OPTIONS[*]}]="$DEBUG_SHELL" -OPTIONS[${#OPTIONS[*]}]="$CONTINUE" # reset tty, otherwise serial console is broken reset > /dev/null clear +# set defaults +has_continue_option=false + +while getopts x c; do + case $c in + x) has_continue_option=true;; + '?') die "invalid option \`-$OPTARG'";; + :) die "missing argument to \`-$OPTARG' option";; + *) die "internal error";; + esac +done + +if $has_continue_option; then + CONTINUE="Continue Stateless Boot" +else + CONTINUE="Quit And Exit" +fi +OPTIONS[${#OPTIONS[*]}]="$CONTINUE" + + while true; do PS3="Please select an option: " diff --git a/scripts/ovirt-firstboot b/scripts/ovirt-firstboot index 844f689..4160e63 100755 --- a/scripts/ovirt-firstboot +++ b/scripts/ovirt-firstboot @@ -55,7 +55,7 @@ start () elif is_firstboot; then plymouth --hide-splash - ovirt-config-setup < /dev/console + ovirt-config-setup -x < /dev/console plymouth --show-splash fi -- 1.6.2.5 From dpierce at redhat.com Thu Jul 16 14:35:10 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 16 Jul 2009 10:35:10 -0400 Subject: [Ovirt-devel] [PATCH node-image] Removes the explicit setting of the emulator for testing. Message-ID: <1247754910-8948-1-git-send-email-dpierce@redhat.com> Instead, the script lets libvirt determine which emulator to use. Signed-off-by: Darryl L. Pierce --- autotest.sh | 6 ++---- 1 files changed, 2 insertions(+), 4 deletions(-) diff --git a/autotest.sh b/autotest.sh index c9f8a2d..96a15b5 100755 --- a/autotest.sh +++ b/autotest.sh @@ -270,7 +270,6 @@ define_node () { # flexible options # define defaults, then allow the caller to override them as needed local arch=$(uname -i) - local emulator=$(which qemu-kvm) local serial="true" local vncport="-1" local bootdev='hd' @@ -281,7 +280,7 @@ define_node () { if [ -n "$options" ]; then eval "$options"; fi debug "define_node ()" - for var in filename nodename memory harddrive cddrive bridge options arch emulator serial vncport bootdev; do + for var in filename nodename memory harddrive cddrive bridge options arch serial vncport bootdev; do eval debug "::$var: \$$var" done @@ -305,7 +304,6 @@ eval debug "::$var: \$$var" # add devices result="${result}\n" - result="${result}\n${emulator}" # inject the hard disk if defined if [ -n "$harddrive" ]; then debug "Adding a hard drive to the node" @@ -566,7 +564,7 @@ test_stateful_pxe () { start_networking $nodename $IFACE_NAME false true $workdir configure_node "${nodename}" "network" "" "10000" "" "local noapic=true" - boot_with_pxe "${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress}" ${workdir} + boot_with_pxe "${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress} vlan=12" ${workdir} # verify the booting and installation expect -c ' -- 1.6.2.5 From imain at redhat.com Thu Jul 16 16:50:31 2009 From: imain at redhat.com (Ian Main) Date: Thu, 16 Jul 2009 09:50:31 -0700 Subject: [Ovirt-devel] [PATCH] Rename qmf-libvirt-example to libvirt-list.rb In-Reply-To: <1247689528.7925.362.camel@avon.watzmann.net> References: <1247683837-23460-1-git-send-email-imain@redhat.com> <1247689528.7925.362.camel@avon.watzmann.net> Message-ID: <20090716095031.1cb70376@tp.mains.net> On Wed, 15 Jul 2009 20:25:28 +0000 David Lutterkort wrote: > On Wed, 2009-07-15 at 11:50 -0700, Ian Main wrote: > > This patch renames qmf-libvirt-example to libvirt-list and makes it > > not repeat. Useful for debugging. File mode is also now 755. > > > > Signed-off-by: Ian Main > > Looks fine to me, though it would be cleaner to do the rename with git > mv, and the other changes as an additional patch. Ah, for some reason I thought git might do the right thing, but I guess that would require two patches wouldn't it.. :) > Shouldn't this ultimately go into libvirt-qpid ? There's really nothing > ovirt specific about the script. hmm, yeah, good point. Ian From imain at redhat.com Thu Jul 16 16:52:55 2009 From: imain at redhat.com (Ian Main) Date: Thu, 16 Jul 2009 09:52:55 -0700 Subject: [Ovirt-devel] [PATCH] Rename qmf-libvirt-example to libvirt-list.rb In-Reply-To: <1247689528.7925.362.camel@avon.watzmann.net> References: <1247683837-23460-1-git-send-email-imain@redhat.com> <1247689528.7925.362.camel@avon.watzmann.net> Message-ID: <20090716095255.22b5a49e@tp.mains.net> On Wed, 15 Jul 2009 20:25:28 +0000 David Lutterkort wrote: > On Wed, 2009-07-15 at 11:50 -0700, Ian Main wrote: > > This patch renames qmf-libvirt-example to libvirt-list and makes it > > not repeat. Useful for debugging. File mode is also now 755. > > > > Signed-off-by: Ian Main > > Looks fine to me, though it would be cleaner to do the rename with git > mv, and the other changes as an additional patch. > > Shouldn't this ultimately go into libvirt-qpid ? There's really nothing > ovirt specific about the script. Actually it is ovirt specific in the way that it does auth and server lookup. I should make one for libvirt-qpid but it should probably be in python (no ruby-qmf dependency) and allow various connection settings.. Ian From sseago at redhat.com Thu Jul 16 19:43:12 2009 From: sseago at redhat.com (Scott Seago) Date: Thu, 16 Jul 2009 19:43:12 +0000 Subject: [Ovirt-devel] [PATCH server] add network QMF apis. (still in progress) Message-ID: <1247773392-6034-1-git-send-email-sseago@redhat.com> This isn't yet functional, but I'm submitting the patch so Ian can continue work on it. Signed-off-by: Scott Seago --- .../lib/ovirt/controllers/network_controller.rb | 26 ++++++++++++++++++++ .../physical_network_impl_controller.rb | 10 +++++++ .../lib/ovirt/controllers/vlan_impl_controller.rb | 9 +++++++ src/ovirt-agent/ovirt_api.xml | 12 +++++++++ 4 files changed, 57 insertions(+), 0 deletions(-) create mode 100644 src/ovirt-agent/lib/ovirt/controllers/network_controller.rb create mode 100644 src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb create mode 100644 src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb diff --git a/src/ovirt-agent/lib/ovirt/controllers/network_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/network_controller.rb new file mode 100644 index 0000000..dd6b4e5 --- /dev/null +++ b/src/ovirt-agent/lib/ovirt/controllers/network_controller.rb @@ -0,0 +1,26 @@ +module Ovirt + + class NetworkController < AgentController + + include NetworkService + + def find(id) + svc_show(id) + render(@network) + end + + def list + puts "query for Network class!" + svc_list + @networks.collect { |network| render(network) } + end + + def render(network) + puts "calling to_qmf on #{pool}, #{pool.name}" + obj = to_qmf(network, :propmap => { :proto => nil}) + obj.proto = network.boot_type.label + return obj + end + + end +end diff --git a/src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb new file mode 100644 index 0000000..456b4e9 --- /dev/null +++ b/src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb @@ -0,0 +1,10 @@ +module Ovirt + + class PhysicalNetworkImplController < NetworkController + + + def render(network) + super(network) + end + end +end diff --git a/src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb new file mode 100644 index 0000000..4cbb7d8 --- /dev/null +++ b/src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb @@ -0,0 +1,9 @@ +module Ovirt + + class VlanImplController < NetworkController + + def render(network) + super(network) + end + end +end diff --git a/src/ovirt-agent/ovirt_api.xml b/src/ovirt-agent/ovirt_api.xml index a5aa383..8eb7ef2 100644 --- a/src/ovirt-agent/ovirt_api.xml +++ b/src/ovirt-agent/ovirt_api.xml @@ -73,12 +73,24 @@ + + + + + + + + + + + + The virtual NIC of a VM; ties a MAC address to a logical network -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 16 21:04:22 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 16 Jul 2009 17:04:22 -0400 Subject: [Ovirt-devel] [PATCH server] updated anyterm/ovirt integration Message-ID: <1247778262-30570-1-git-send-email-mmorsi@redhat.com> host static anyterm content locally, url parameterize anyterm rows/cols/general param update spec/makefile Ideally I wanted and tried hard to put all this into a seperate ovirt-server--anyterm subpackage, but we cannot 'reopen' the ovirt server virtual host defined in ovirt-server.conf to add the neccessary rewrite rules. it would be nice to figure out a way to do this --- Makefile.am | 1 + anyterm/anyterm.css | 132 ++++++++ anyterm/anyterm.html | 72 +++++ anyterm/anyterm.js | 799 ++++++++++++++++++++++++++++++++++++++++++++++++ anyterm/copy.gif | Bin 0 -> 911 bytes anyterm/copy.png | Bin 0 -> 232 bytes anyterm/paste.gif | Bin 0 -> 148 bytes anyterm/paste.png | Bin 0 -> 225 bytes conf/ovirt-server.conf | 13 +- ovirt-server.spec.in | 7 + scripts/ovirt-vm2node | 16 +- 11 files changed, 1022 insertions(+), 18 deletions(-) create mode 100644 anyterm/anyterm.css create mode 100644 anyterm/anyterm.html create mode 100644 anyterm/anyterm.js create mode 100644 anyterm/copy.gif create mode 100644 anyterm/copy.png create mode 100644 anyterm/paste.gif create mode 100644 anyterm/paste.png diff --git a/Makefile.am b/Makefile.am index f115c8f..a143663 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,7 @@ EXTRA_DIST = \ ovirt-server.spec.in \ scripts \ conf \ + anyterm \ src \ installer diff --git a/anyterm/anyterm.css b/anyterm/anyterm.css new file mode 100644 index 0000000..6e68281 --- /dev/null +++ b/anyterm/anyterm.css @@ -0,0 +1,132 @@ +/* browser/anyterm.css + This file is part of Anyterm; see http://anyterm.org/ + (C) 2005-2008 Philip Endecott + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + + +/* These are the background colours: */ +.a { background-color: #000000; } /* black */ +.b { background-color: #cd0000; } /* red */ +.c { background-color: #00cd00; } /* green */ +.d { background-color: #cdcd00; } /* yellow */ +.e { background-color: #0000cd; } /* blue */ +.f { background-color: #cd00cd; } /* magenta */ +.g { background-color: #00cdcd; } /* cyan */ +.h { background-color: #e5e5e5; } /* white */ + +/* These are the foreground colours used when bold mode is NOT enabled. + They're the same as the background colours. */ +.i { color: #000000; } /* black */ +.j { color: #cd0000; } /* red */ +.k { color: #00cd00; } /* green */ +.l { color: #cdcd00; } /* yellow */ +.m { color: #0000cd; } /* blue */ +.n { color: #cd00cd; } /* magenta */ +.o { color: #00cdcd; } /* cyan */ +.p { color: #e5e5e5; } /* white */ + +/* These are the brighter foreground colours used when bold mode IS enabled. + The business with !important and .p .z is because the .p default is set + on the enclosing term element; I can't see a better way to get the desired + behaviour. */ +.z.i { color: #4d4d4d !important; } /* black */ +.z.j { color: #ff0000 !important; } /* red */ +.z.k { color: #00ff00 !important; } /* green */ +.z.l { color: #ffff00 !important; } /* yellow */ +.z.m { color: #0000ff !important; } /* blue */ +.z.n { color: #ff00ff !important; } /* magenta */ +.z.o { color: #00ffff !important; } /* cyan */ +.z.p, .p .z { color: #ffffff; } /* white */ + +/* If you want a black-on-white colour scheme like xterm, rather than the + default white-on-black, you need to change the lines for black and white + above to something like the following: + .a { background-color: #ffffff; } + .h { background-color: #000000; } + .i { color: #e5e5e5; } + .p { color: #000000; } + .z.i { color: #ffffff !important; } + .z.p, .p .z { color: #000000; } +*/ + +/* If the following rule is enabled, bold mode will actually use a bold font. + This is not a good idea in general as the bold font will probably be wider + than the normal font, messing up the layout, at least for some subset of + the character set. So it's commented out; bold characters will be + distinguished only by their brighter colours (above) */ +/* .z { font-weight: bold; } */ + +/* The cursor. You can make it blink if you really want to (on some browsers). */ +.cursor { + border: 1px solid red; + margin: -1px; +/*text-decoration: blink;*/ +} + + +/* Properties for the page outside the terminal: */ + +body { + background-color: white; + /* Don't like the white background? How about this: + background-color: #222222; + */ +} + +noscript { + /* This is for the message that users see if they don't have Javascript + enabled. We want it to be visible whatever the page background colour + (above) is set to, so we give it its own background colour. */ + color: black; + background-color: white; +} + + +/* The remaining definitions determine the appearance of the frame around the + terminal, its title bar, and buttons: */ + +.termframe { + float: left; + background-color: rgb(63,63,161); + padding: 0.4ex; +} + +.termframe p { + margin: 0; + color: white; + font-weight: bold; + font-family: sans-serif; +} + +.termframe a { + cursor: pointer; +} + +img.button { + margin: 0 3px; + cursor: pointer; + vertical-align: top; +} + +.term { + margin: 0.4ex 0 0 0; + padding: 1px; + + overflow: auto; + overflow-x: visible; +} + diff --git a/anyterm/anyterm.html b/anyterm/anyterm.html new file mode 100644 index 0000000..7e6e702 --- /dev/null +++ b/anyterm/anyterm.html @@ -0,0 +1,72 @@ + + + + + + +Anyterm + + + + + + + + + + + + + +
    + + + diff --git a/anyterm/anyterm.js b/anyterm/anyterm.js new file mode 100644 index 0000000..775970e --- /dev/null +++ b/anyterm/anyterm.js @@ -0,0 +1,799 @@ +// browser/anyterm.js +// This file is part of Anyterm; see http://anyterm.org/ +// (C) 2005-2006 Philip Endecott + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +var undefined; + +var url_prefix = ""; + +var frame; +var term; +var open=false; +var session; + +var method="POST"; +//var method="GET"; + +// Random sequence numbers are needed to prevent Opera from caching +// replies + +var is_opera = navigator.userAgent.toLowerCase().indexOf("opera") != -1; +if (is_opera) { + method="GET"; +} + +var seqnum_val=Math.round(Math.random()*100000); +function cachebust() { + if (is_opera) { + seqnum_val++; + return "&x="+seqnum_val; + } else { + return ""; + } +} + + +// Cross-platform creation of XMLHttpRequest object: + +function new_XMLHttpRequest() { + if (window.XMLHttpRequest) { + // For most browsers: + return new XMLHttpRequest(); + } else { + // For IE, it's active-X voodoo. + // There are different versions in different browsers. + // The ones we try are the ones that Sarissa tried. The disabled ones + // apparently also exist, but it seems to work OK without trying them. + + //try{ return new ActiveXObject("MSXML3.XMLHTTP"); } catch(e){} + try{ return new ActiveXObject("Msxml2.XMLHTTP.5.0"); } catch(e){} + try{ return new ActiveXObject("Msxml2.XMLHTTP.4.0"); } catch(e){} + try{ return new ActiveXObject("MSXML2.XMLHTTP.3.0"); } catch(e){} + try{ return new ActiveXObject("MSXML2.XMLHTTP"); } catch(e){} + //try{ return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e){} + try{ return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e){} + throw new Error("Could not find an XMLHttpRequest active-X class.") + } +} + + +// Asynchronous and Synchronous XmlHttpRequest wrappers + +// AsyncLoader is a class; an instance specifies a callback function. +// Call load to get something and the callback is invoked with the +// returned document. + +function AsyncLoader(cb) { + this.callback = cb; + this.load = function (url,query) { + var xmlhttp = new_XMLHttpRequest(); + var cbk = this.callback; + //var timeoutID = window.setTimeout("alert('No response after 20 secs')",20000); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState==4) { + //window.clearTimeout(timeoutID); + if (xmlhttp.status==200) { + cbk(xmlhttp.responseText); + } else { + alert("Server returned status code "+xmlhttp.status+":\n"+xmlhttp.statusText); + cbk(null); + } + } + } + if (method=="GET") { + xmlhttp.open(method, url+"?"+query, true); + xmlhttp.send(null); + } else if (method=="POST") { + xmlhttp.open(method, url, true); + xmlhttp.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + xmlhttp.send(query); + } + + } +} + + +// Synchronous loader is a simple function + +function sync_load(url,query) { + var xmlhttp = new_XMLHttpRequest(); + if (method=="GET") { + xmlhttp.open(method, url+"?"+query, false); + xmlhttp.send(null); + } else if (method=="POST") { + xmlhttp.open(method, url, false); + xmlhttp.setRequestHeader('Foo','1234'); + xmlhttp.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + xmlhttp.send(query); + } + if (xmlhttp.status!=200) { + alert("Server returned status code "+xmlhttp.status+":\n"+xmlhttp.statusText); + return null; + } + return xmlhttp.responseText; +} + + +// Process error message from server: + +function handle_resp_error(resp) { + if (resp.charAt(0)=="E") { + var msg = resp.substr(1); + alert(msg); + return true; + } + return false; +} + + +// Receive channel: + +var rcv_loader; + +var disp=""; + + + +function process_editscript(edscr) { + + var ndisp=""; + + var i=0; + var dp=0; + while (i=65 && kc<=90) k=String.fromCharCode(kc-64); // Ctrl-A..Z + else if (kc==219) k=String.fromCharCode(27); // Ctrl-[ + else if (kc==220) k=String.fromCharCode(28); // Ctrl-\ . + else if (kc==221) k=String.fromCharCode(29); // Ctrl-] + else if (kc==190) k=String.fromCharCode(30); // Since ctrl-^ doesn't work, map + // ctrl-. to its code. + else if (kc==32) k=String.fromCharCode(0); // Ctrl-space sends 0, like ctrl- at . + else { + key_ev_supress(ev); + return; + } + } + } + +// alert("keydown keyCode="+ev.keyCode+" which="+ev.which+ +// " shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey); + + process_key(k); + + key_ev_stop(ev); + return false; +} + + +// Open, close and initialisation: + +function open_term(rows,cols,p,charset,scrollback) { + var params = "a=open&rows="+rows+"&cols="+cols; + if (p) { + params += "&p="+p; + } + if (charset) { + params += "&ch="+charset; + } + if (scrollback) { + if (scrollback>1000) { + alert("The maximum scrollback is currently limited to 1000 lines. " + +"Please choose a smaller value and try again."); + return; + } + params += "&sb="+scrollback; + } + params += cachebust(); + var resp = sync_load(url_prefix+"anyterm-module",params); + + if (handle_resp_error(resp)) { + return; + } + + open=true; + session=resp; +} + +function close_term() { + if (!open) { + alert("Connection is not open"); + return; + } + open=false; + var resp = sync_load(url_prefix+"anyterm-module","a=close&s="+session+cachebust()); + handle_resp_error(resp); // If we get an error, we still close everything. + document.onkeypress=null; + document.onkeydown=null; + window.onbeforeunload=null; + var e; + while (e=frame.firstChild) { + frame.removeChild(e); + } + frame.className=""; + if (on_close_goto_url) { + document.location = on_close_goto_url; + } +} + + +function get_anyterm_version() { + var svn_url="$URL: http://svn.anyterm.org/anyterm/tags/releases/1.1/1.1.29/browser/anyterm.js $"; + var re = /releases\/[0-9]+\.[0-9]+\/([0-9\.]+)/; + var match = re.exec(svn_url); + if (match) { + return match[1]; + } else { + return ""; + } +} + +function substitute_variables(s) { + var version = get_anyterm_version(); + if (version!="") { + version="-"+version; + } + var hostname=document.location.host; + return s.replace(/%v/g,version).replace(/%h/g,hostname); +} + + +// Copying + +function copy_ie_clipboard() { + try { + window.document.execCommand("copy",false,null); + } catch (err) { + return undefined; + } + return 1; +} + +function copy_mozilla_clipboard() { + // Thanks to Simon Wissinger for this function. + + try { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + } catch (err) { + return undefined; + } + + var sel=window.getSelection(); + var copytext=sel.toString(); + + var str=Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString); + if (!str) return undefined; + + str.data=copytext; + + var trans=Components.classes["@mozilla.org/widget/transferable;1"] + .createInstance(Components.interfaces.nsITransferable); + if (!trans) return undefined; + + trans.addDataFlavor("text/unicode"); + trans.setTransferData("text/unicode", str, copytext.length * 2); + + var clipid=Components.interfaces.nsIClipboard; + + var clip=Components.classes["@mozilla.org/widget/clipboard;1"].getService(clipid); + if (!clip) return undefined; + + clip.setData(trans, null, clipid.kGlobalClipboard); + + return 1; +} + +function copy_to_clipboard() { + var r=copy_ie_clipboard(); + if (r==undefined) { + r=copy_mozilla_clipboard(); + } + if (r==undefined) { + alert("Copy seems to be disabled; maybe you need to change your security settings?" + +"\n(Copy on the Edit menu will probably work)"); + } +} + + +// Pasting + +function get_mozilla_clipboard() { + // This function is taken from + // http://www.nomorepasting.com/paste.php?action=getpaste&pasteID=41974&PHPSESSID=e6565dcf5de07256345e562b97ac9f46 + // which does not indicate any particular copyright conditions. It + // is a public forum, so one might conclude that it is public + // domain. + + // IMHO it's disgraceful that Mozilla makes us use these 30 lines of + // undocumented gobledegook to do what IE does, and documents, with + // just 'window.clipboardData.getData("Text")'. What on earth were + // they thinking? + + try { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + } catch (err) { + return undefined; + } + + var clip = Components.classes["@mozilla.org/widget/clipboard;1"] + .createInstance(Components.interfaces.nsIClipboard); + if (!clip) { + return undefined; + } + + var trans = Components.classes["@mozilla.org/widget/transferable;1"] + .createInstance(Components.interfaces.nsITransferable); + if (!trans) { + return undefined; + } + + trans.addDataFlavor("text/unicode"); + clip.getData(trans,clip.kGlobalClipboard); + + var str=new Object(); + var strLength=new Object(); + + try { + trans.getTransferData("text/unicode",str,strLength); + } catch(err) { + // One reason for getting here seems to be that nothing is selected + return ""; + } + + if (str) { + str=str.value.QueryInterface(Components.interfaces.nsISupportsString); + } + + if (str) { + return str.data.substring(0,strLength.value / 2); + } else { + return ""; // ? is this "clipboard empty" or "cannot access"? + } +} + +function get_ie_clipboard() { + if (window.clipboardData) { + return window.clipboardData.getData("Text"); + } + return undefined; +} + +function get_default_clipboard() { + return prompt("Paste into this box and press OK:",""); +} + +function paste_from_clipboard() { + var p = get_ie_clipboard(); + if (p==undefined) { + p = get_mozilla_clipboard(); + } + if (p==undefined) { + p = get_default_clipboard(); + if (p) { + process_key(p); + } + return; + } + + if (p=="") { + alert("The clipboard seems to be empty"); + return; + } + + if (confirm('Click OK to "type" the following into the terminal:\n'+p)) { + process_key(p); + } +} + + +function create_button(label,fn) { + var button=document.createElement("A"); + var button_t=document.createTextNode("["+label+"] "); + button.appendChild(button_t); + button.onclick=fn; + return button; +} + +function create_img_button(imgfn,label,fn) { + var button=document.createElement("A"); + var button_img=document.createElement("IMG"); + var class_attr=document.createAttribute("CLASS"); + class_attr.value="button"; + button_img.setAttributeNode(class_attr); + var src_attr=document.createAttribute("SRC"); + src_attr.value=imgfn; + button_img.setAttributeNode(src_attr); + var alt_attr=document.createAttribute("ALT"); + alt_attr.value="["+label+"] "; + button_img.setAttributeNode(alt_attr); + var title_attr=document.createAttribute("TITLE"); + title_attr.value=label; + button_img.setAttributeNode(title_attr); + button.appendChild(button_img); + button.onclick=fn; + return button; +} + +function create_term(elem_id,title,rows,cols,p,charset,scrollback) { + if (open) { + alert("Terminal is already open"); + return; + } + title=substitute_variables(title); + frame=document.getElementById(elem_id); + if (!frame) { + alert("There is no element named '"+elem_id+"' in which to build a terminal"); + return; + } + frame.className="termframe"; + var title_p=document.createElement("P"); + title_p.appendChild(create_img_button("copy.gif","Copy",copy_to_clipboard)); + title_p.appendChild(create_img_button("paste.gif","Paste",paste_from_clipboard)); + title_p.appendChild(create_ctrlkey_menu()); + var title_t=document.createTextNode(" "+title+" "); + title_p.appendChild(title_t); + title_p.appendChild(create_button("close",close_term)); + frame.appendChild(title_p); + term=document.createElement("PRE"); + frame.appendChild(term); + term.className="term a p"; + var termbody=document.createTextNode(""); + term.appendChild(termbody); + visible_height_frac=Number(rows)/(Number(rows)+Number(scrollback)); + if (scrollback>0) { + term.style.overflowY="scroll"; + } + document.onhelp = function() { return false; }; + document.onkeypress=keypress; + document.onkeydown=keydown; + open_term(rows,cols,p,charset,scrollback); + if (open) { + window.onbeforeunload=warn_unload; + get(); + maybe_send(); + } +} + + +function warn_unload() { + if (open) { + return "Leaving this page will close the terminal."; + } +} + + +function create_ctrlkey_menu() { + var sel=document.createElement("SELECT"); + create_ctrlkey_menu_entry(sel,"Control keys...",-1); + create_ctrlkey_menu_entry(sel,"Ctrl-@",0); + for (var code=1; code<27; code++) { + var letter=String.fromCharCode(64+code); + create_ctrlkey_menu_entry(sel,"Ctrl-"+letter,code); + } + create_ctrlkey_menu_entry(sel,"Ctrl-[",27); + create_ctrlkey_menu_entry(sel,"Ctrl-\\",28); + create_ctrlkey_menu_entry(sel,"Ctrl-]",29); + create_ctrlkey_menu_entry(sel,"Ctrl-^",30); + create_ctrlkey_menu_entry(sel,"Ctrl-_",31); + sel.onchange=function() { + var code = sel.options[sel.selectedIndex].value; + if (code>=0) { + process_key(String.fromCharCode(code)); + } + }; + return sel; +} + +function create_ctrlkey_menu_entry(sel,name,code) { + var opt=document.createElement("OPTION"); + opt.appendChild(document.createTextNode(name)); + var value_attr=document.createAttribute("VALUE"); + value_attr.value=code; + opt.setAttributeNode(value_attr); + sel.appendChild(opt); +} + +function get_url_param( name ) +{ + var regexS = "[\\?&]"+name+"=([^&#]*)"; + var regex = new RegExp( regexS ); + var results = regex.exec( window.location.href ); + if( results == null ) + return ""; + else + return results[1]; +} diff --git a/anyterm/copy.gif b/anyterm/copy.gif new file mode 100644 index 0000000000000000000000000000000000000000..2ab719e349efd9e003b4b420449c68fecea004ed GIT binary patch literal 911 zcmV;A191FDNk%w1VG;lq0QUd at 000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C at 3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy at _IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W at l$-XlQ6 at X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm at et&;|fPjF3fq{a8f`fyD zgoK2Jg at uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8 at l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK at bK{Q@$vHV^7Hfa^z`)g_4W4l_V at Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LW006%LEC2ui01^Ne000PV0DlP_NU-2Q00D%GVtZCVwI6=}zS06UUJu*?7e literal 0 HcmV?d00001 diff --git a/anyterm/copy.png b/anyterm/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..1cdd8c1de2770d63ed9d9ae80ed2e2e44216598b GIT binary patch literal 232 zcmeAS at N?(olHy`uVBq!ia0vp^LO?9W!2%@H!&puMDW)WEcNd2L?fqx=19_YU9+AZi z4BWyX%*Zfnjs#GUy~NYkmHiqsx0s>WCWE6%K%q)c7sn8d^IL;X3La44U`dzWd?qA0 z^GRY4i`f&=I6j>>hOZ{P588M`%F97aO7hyLkIuU$dRG_j|7&_?qQwm*UWHnQqJ>gH z?!9ejR3nK#q3xf`b2U5$x9Owtlo z;=CNa at C}<)9TeDPQri}{PKujtmaKF9RnGngwby^0xx7})hfPAF-|hH>HkUTJ)mIcO pzW$gMas2A!-mb2Q<GRbtO4Q=IkW%( literal 0 HcmV?d00001 diff --git a/anyterm/paste.png b/anyterm/paste.png new file mode 100644 index 0000000000000000000000000000000000000000..034debdbf7033e46e9d7d45e239a664a7f92d9fb GIT binary patch literal 225 zcmV<703QE|P)MCL3v>^Jg7)<@CMgh0YotVxnTP-Ym^o8FP&6ST z?z0mOp`ORtxR<@a$a*m|yn3VG`WI at _T69(RGMJhM!%u}-y|7sSZ at A5pHlSsO0H8WQ zBt*2n<+?L8Md(rV^p~a4l;N!HYmSJ%Bb @@ -85,3 +81,8 @@ ProxyPassReverse /ovirt/stylesheets ! ProxyPassReverse /ovirt/errors ! + +Alias /terminal /usr/share/ovirt-anyterm + + DirectoryIndex anyterm.html + diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 1bf73c7..d762178 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -150,6 +150,11 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__cp} -pr %{pbuild}/installer/appliances %{buildroot}/%{acehome} %{__cp} -pr %{pbuild}/installer/bin/ovirt-installer %{buildroot}%{_sbindir} +# setup the anyterm config +%{__mkdir} -p %{buildroot}%{_datadir}/ovirt-anyterm/ +for f in anyterm/*.{html,css,js,png,gif}; do + %{__install} -m644 "$f" %{buildroot}%{_datadir}/ovirt-anyterm/ +done %clean rm -rf $RPM_BUILD_ROOT @@ -244,11 +249,13 @@ fi %config(noreplace) %{_sysconfdir}/%{name}/development.rb %config(noreplace) %{_sysconfdir}/%{name}/production.rb %config(noreplace) %{_sysconfdir}/%{name}/test.rb +%{_datadir}/ovirt-anyterm %files installer %{_sbindir}/ovirt-installer %{acehome} + %changelog * Thu May 29 2008 Alan Pevec - 0.0.5-0 - use rubygem-krb5-auth diff --git a/scripts/ovirt-vm2node b/scripts/ovirt-vm2node index 1d6104c..4ef3d6c 100755 --- a/scripts/ovirt-vm2node +++ b/scripts/ovirt-vm2node @@ -1,23 +1,15 @@ #!/usr/bin/ruby -$: << '/usr/share/ovirt-server' -$: << '/usr/share/ovirt-server/dutils' +#$: << '/usr/share/ovirt-server' +#$: << '/usr/share/ovirt-server/dutils' -require 'dutils' +#require 'dutils' ########################## retreive host from vm w/ specified name $stdin.each{ |vmname| # get vm name from stdin begin vmname.chomp! # remove the newline - - # specially handle 'anyterm' to just return - # first host (for css/js/etc which aren't - # vm dependent) - if vmname == 'anyterm' - puts Host.find(:first, :conditions => "state = 'available'").hostname - else - puts Vm.find(:first, :conditions => ['description = ?', vmname]).host.hostname - end + puts Vm.find(:first, :conditions => ['description = ?', vmname]).host.hostname rescue Exception => e puts end -- 1.6.0.6 From jason.guiditta at gmail.com Mon Jul 20 14:58:30 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:30 -0400 Subject: [Ovirt-devel] Upgrade server to run on Rails 2.3.2/F11 Message-ID: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Note that one of the 8 patches (#6) will be sent separately in reply to this email, as some of the replaced lines are too long, so git won't let me send the email. However, there is nothing wrong with that patch, and it should be applied in the sequence listed below. Note also that I assume this will be tested on a clean f11 install, rather than an upgrade of an existing ovirt server (since if it was on F11, it was broken anyway). [PATCH server 1/8] Update core app config for Rails 2.3.2 [PATCH server 2/8] test_helper and unit test updates for Rails 2.3.2 [PATCH server 3/8] Update functional tests to work with Rails 2.3.2 [PATCH server 4/8] Switch from old connection style to current. [PATCH server 5/8] No need to track schema.rb and logs right now. [PATCH server 6/8] Update will_paginate to latest version. [PATCH server 7/8] Install render_component plugin. [PATCH server 8/8] Wrapper for mongrel to run with rails 2.3.2 From jason.guiditta at gmail.com Mon Jul 20 14:58:32 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:32 -0400 Subject: [Ovirt-devel] [PATCH server 2/8] test_helper and unit test updates for Rails 2.3.2 In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-3-git-send-email-jason.guiditta@gmail.com> Rails 2.3.2 changes test_helper use ActiveSupport::TestCase rather than Test::Unit::TestCase, so unit test need to be updated accordingly as well. Signed-off-by: Jason Guiditta --- src/test/test_helper.rb | 2 +- src/test/unit/active_record_env_test.rb | 2 +- src/test/unit/host_browser_awaken_test.rb | 2 +- src/test/unit/host_test.rb | 2 +- src/test/unit/nic_test.rb | 2 +- src/test/unit/permission_test.rb | 2 +- src/test/unit/pool_test.rb | 2 +- src/test/unit/quota_test.rb | 2 +- src/test/unit/storage_pool_test.rb | 2 +- src/test/unit/storage_volume_test.rb | 2 +- src/test/unit/task_test.rb | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/test/test_helper.rb b/src/test/test_helper.rb index fc84648..009dfc8 100644 --- a/src/test/test_helper.rb +++ b/src/test/test_helper.rb @@ -21,7 +21,7 @@ ENV["RAILS_ENV"] = "test" require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require 'test_help' -class Test::Unit::TestCase +class ActiveSupport::TestCase # Transactional fixtures accelerate your tests by wrapping each test method # in a transaction that's rolled back on completion. This ensures that the # test database remains unchanged so your fixtures don't have to be reloaded diff --git a/src/test/unit/active_record_env_test.rb b/src/test/unit/active_record_env_test.rb index 26fa139..a9a9c5a 100644 --- a/src/test/unit/active_record_env_test.rb +++ b/src/test/unit/active_record_env_test.rb @@ -20,7 +20,7 @@ require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../../dutils/active_record_env' -class ActiveRecordEnvTest < Test::Unit::TestCase +class ActiveRecordEnvTest < ActiveSupport::TestCase fixtures :pools, :hosts, :vms, :boot_types, :networks, :nics, :ip_addresses, :privileges, :roles, :permissions, :quotas, :storage_pools, :storage_volumes, :tasks diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb index d251e90..a7cf31c 100644 --- a/src/test/unit/host_browser_awaken_test.rb +++ b/src/test/unit/host_browser_awaken_test.rb @@ -31,7 +31,7 @@ require 'host-browser' # +HostBrowserAwakenTest+ ensures that the host-browser daemon works correctly # during the identify phase of operation. # -class HostBrowserAwakenTest < Test::Unit::TestCase +class HostBrowserAwakenTest < ActiveSupport::TestCase def setup @session = flexmock('session') diff --git a/src/test/unit/host_test.rb b/src/test/unit/host_test.rb index 338fbaf..4d40990 100644 --- a/src/test/unit/host_test.rb +++ b/src/test/unit/host_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class HostTest < Test::Unit::TestCase +class HostTest < ActiveSupport::TestCase fixtures :hosts fixtures :pools fixtures :vms diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb index 07f54c6..46fab10 100644 --- a/src/test/unit/nic_test.rb +++ b/src/test/unit/nic_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class NicTest < Test::Unit::TestCase +class NicTest < ActiveSupport::TestCase fixtures :ip_addresses fixtures :nics fixtures :hosts diff --git a/src/test/unit/permission_test.rb b/src/test/unit/permission_test.rb index 2ac78d5..344e615 100644 --- a/src/test/unit/permission_test.rb +++ b/src/test/unit/permission_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class PermissionTest < Test::Unit::TestCase +class PermissionTest < ActiveSupport::TestCase fixtures :privileges, :roles, :permissions fixtures :pools diff --git a/src/test/unit/pool_test.rb b/src/test/unit/pool_test.rb index c9a5554..bf9164d 100644 --- a/src/test/unit/pool_test.rb +++ b/src/test/unit/pool_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class PoolTest < Test::Unit::TestCase +class PoolTest < ActiveSupport::TestCase fixtures :pools def setup diff --git a/src/test/unit/quota_test.rb b/src/test/unit/quota_test.rb index 5903cc8..ec2f5cd 100644 --- a/src/test/unit/quota_test.rb +++ b/src/test/unit/quota_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class QuotaTest < Test::Unit::TestCase +class QuotaTest < ActiveSupport::TestCase fixtures :quotas fixtures :pools diff --git a/src/test/unit/storage_pool_test.rb b/src/test/unit/storage_pool_test.rb index 4e18a8c..bd969ad 100644 --- a/src/test/unit/storage_pool_test.rb +++ b/src/test/unit/storage_pool_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class StoragePoolTest < Test::Unit::TestCase +class StoragePoolTest < ActiveSupport::TestCase fixtures :storage_pools fixtures :pools fixtures :vms diff --git a/src/test/unit/storage_volume_test.rb b/src/test/unit/storage_volume_test.rb index f0e144e..4ec8760 100644 --- a/src/test/unit/storage_volume_test.rb +++ b/src/test/unit/storage_volume_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class StorageVolumeTest < Test::Unit::TestCase +class StorageVolumeTest < ActiveSupport::TestCase fixtures :storage_volumes fixtures :storage_pools fixtures :vms diff --git a/src/test/unit/task_test.rb b/src/test/unit/task_test.rb index 761e739..0f94052 100644 --- a/src/test/unit/task_test.rb +++ b/src/test/unit/task_test.rb @@ -19,7 +19,7 @@ require File.dirname(__FILE__) + '/../test_helper' -class TaskTest < Test::Unit::TestCase +class TaskTest < ActiveSupport::TestCase fixtures :pools, :hosts, :vms, :privileges, :roles, :permissions, :tasks def setup -- 1.6.2.5 From jason.guiditta at gmail.com Mon Jul 20 14:58:33 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:33 -0400 Subject: [Ovirt-devel] [PATCH server 3/8] Update functional tests to work with Rails 2.3.2 In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-4-git-send-email-jason.guiditta@gmail.com> Signed-off-by: Jason Guiditta --- .../functional/managed_node_configuration_test.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index a0a66e9..8759b02 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -23,7 +23,7 @@ require 'managed_node_configuration' # Performs unit tests on the +ManagedNodeConfiguration+ class. # -class ManagedNodeConfigurationTest < Test::Unit::TestCase +class ManagedNodeConfigurationTest < ActiveSupport::TestCase fixtures :bonding_types fixtures :bondings fixtures :bondings_nics -- 1.6.2.5 From jason.guiditta at gmail.com Mon Jul 20 14:58:34 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:34 -0400 Subject: [Ovirt-devel] [PATCH server 4/8] Switch from old connection style to current. In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-5-git-send-email-jason.guiditta@gmail.com> establish_connetion has been deprecated, so switch to setup_connection. Signed-off-by: Jason Guiditta --- src/app/helpers/ldap_connection.rb | 2 +- src/script/grant_admin_privileges | 2 +- src/vendor/plugins/active_ldap/init.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/helpers/ldap_connection.rb b/src/app/helpers/ldap_connection.rb index af8b41b..b8cb0e3 100644 --- a/src/app/helpers/ldap_connection.rb +++ b/src/app/helpers/ldap_connection.rb @@ -31,7 +31,7 @@ class LDAPConnection host = @@config[ENV['RAILS_ENV']]["host"] if host == nil port = @@config[ENV['RAILS_ENV']]["port"] if port == nil - ActiveLdap::Base.establish_connection(:host => host, + ActiveLdap::Base.setup_connection(:host => host, :port => port, :base => base) if LDAPConnection.connected? == false end diff --git a/src/script/grant_admin_privileges b/src/script/grant_admin_privileges index cdf36e7..a0e340a 100755 --- a/src/script/grant_admin_privileges +++ b/src/script/grant_admin_privileges @@ -9,7 +9,7 @@ ldap_config = YAML::load(File.open(File.dirname(__FILE__) +"/../config/ldap.yml" uid = ARGV[0] base, host = ldap_config["production"]["base"], ldap_config["production"]["host"] -ActiveLdap::Base.establish_connection :base => base, :host => host, :try_sasl => false +ActiveLdap::Base.setup_connection :base => base, :host => host, :try_sasl => false if Account.exists?("uid=#{uid}") puts "Creating an admin account for #{uid}..." diff --git a/src/vendor/plugins/active_ldap/init.rb b/src/vendor/plugins/active_ldap/init.rb index 3b4291e..e8395cf 100644 --- a/src/vendor/plugins/active_ldap/init.rb +++ b/src/vendor/plugins/active_ldap/init.rb @@ -13,7 +13,7 @@ ldap_configuration_file = File.join(RAILS_ROOT, 'config', 'ldap.yml') if File.exist?(ldap_configuration_file) configurations = YAML.load(ERB.new(IO.read(ldap_configuration_file)).result) ActiveLdap::Base.configurations = configurations - ActiveLdap::Base.establish_connection + ActiveLdap::Base.setup_connection else ActiveLdap::Base.class_eval do format = _("You should run 'script/generator scaffold_active_ldap' to make %s.") -- 1.6.2.5 From jason.guiditta at gmail.com Mon Jul 20 14:58:35 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:35 -0400 Subject: [Ovirt-devel] [PATCH server 5/8] No need to track schema.rb and logs right now. In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-6-git-send-email-jason.guiditta@gmail.com> Signed-off-by: Jason Guiditta --- .gitignore | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/.gitignore b/.gitignore index 0fd1d9b..9cfdd9f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ missing stamp-h1 ovirt-server*.gz ovirt-server.spec +schema.rb +log/ -- 1.6.2.5 From jason.guiditta at gmail.com Mon Jul 20 14:58:37 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:37 -0400 Subject: [Ovirt-devel] [PATCH server 8/8] Wrapper for mongrel to run with rails 2.3.2 In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-8-git-send-email-jason.guiditta@gmail.com> This file is needed because we use --prefix when running mongrel_rails (which I think we need for now). It wraps the AbstractRequest call that is no longer in rails (but triggered by --prefix) so mongrel does not constantly die. It is possible there is a way now in rails itself to specify something in place of this flag, but I have not been able to find where to put that setting yet. Signed-off-by: Jason Guiditta --- src/config/initializers/abstract_request.rb | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-) create mode 100644 src/config/initializers/abstract_request.rb diff --git a/src/config/initializers/abstract_request.rb b/src/config/initializers/abstract_request.rb new file mode 100644 index 0000000..938106d --- /dev/null +++ b/src/config/initializers/abstract_request.rb @@ -0,0 +1,14 @@ +#This is a (hopefully) temporary workaround to help +#mongrel, since it hooks into the now-gone AbstracRequest +#class. + +module ActionController + class AbstractRequest < ActionController::Request + def self.relative_url_root=(path) + ActionController::Base.relative_url_root=(path) + end + def self.relative_url_root + ActionController::Base.relative_url_root + end + end +end -- 1.6.2.5 From jason.guiditta at gmail.com Mon Jul 20 14:58:31 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:31 -0400 Subject: [Ovirt-devel] [PATCH server 1/8] Update core app config for Rails 2.3.2 In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-2-git-send-email-jason.guiditta@gmail.com> This patch updates app config and makes any required filename changes to support rails 2.3.2. Notable changes are: * application.rb -> application_controller.rb * cleanup of environment.rb * new config/initializers dir (this is where much of the environment.rb stuff went) * gettext/rails -> gettext_rails Also note that applying this patch makes ovirt server no longer run on Fedora 10, as Rails 2.3.2 is not in that yum repo. If you wish to run the app on F10 after this upgrade, you can probably still do so with: gem update rails but of course this has the potential to cause issues with yum and gem conflicting. Signed-off-by: Jason Guiditta Signed-off-by: Jason Guiditta --- src/app/controllers/application.rb | 200 -------- src/app/controllers/application_controller.rb | 198 ++++++++ src/config.ru | 7 + src/config/boot.rb | 11 +- src/config/environment.rb | 38 +-- src/config/initializers/backtrace_silencers.rb | 7 + src/config/initializers/inflections.rb | 10 + src/config/initializers/mime_types.rb | 5 + src/config/initializers/new_rails_defaults.rb | 19 + src/config/initializers/session_store.rb | 15 + src/public/javascripts/controls.js | 136 +++--- src/public/javascripts/dragdrop.js | 175 ++++---- src/public/javascripts/effects.js | 130 +++--- src/public/javascripts/prototype.js | 629 ++++++++++++++---------- 14 files changed, 861 insertions(+), 719 deletions(-) delete mode 100644 src/app/controllers/application.rb create mode 100644 src/app/controllers/application_controller.rb create mode 100644 src/config.ru create mode 100644 src/config/initializers/backtrace_silencers.rb create mode 100644 src/config/initializers/inflections.rb create mode 100644 src/config/initializers/mime_types.rb create mode 100644 src/config/initializers/new_rails_defaults.rb create mode 100644 src/config/initializers/session_store.rb diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb deleted file mode 100644 index e50f71e..0000000 --- a/src/app/controllers/application.rb +++ /dev/null @@ -1,200 +0,0 @@ -# -# Copyright (C) 2008 Red Hat, Inc. -# Written by Scott Seago -# -# 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. - -# Filters added to this controller apply to all controllers in the application. -# Likewise, all the methods added will be available for all controllers. - - -class ApplicationController < ActionController::Base - # FIXME: once all controller classes include this, remove here - include ApplicationService - - # Pick a unique cookie name to distinguish our session data from others' - session :session_key => '_ovirt_session_id' - init_gettext "ovirt" - layout :choose_layout - - before_filter :is_logged_in, :get_help_section - - # General error handlers, must be in order from least specific - # to most specific - rescue_from Exception, :with => :handle_general_error - rescue_from PermissionError, :with => :handle_perm_error - rescue_from ActionError, :with => :handle_action_error - rescue_from PartialSuccessError, :with => :handle_partial_success_error - - def choose_layout - if(params[:component_layout]) - return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux' - end - return 'redux' - end - - def is_logged_in - redirect_to(:controller => "/login", :action => "login") unless get_login_user - end - - def get_help_section - help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ]) - @help_section = help ? help.section : "" - if @help_section.index('#') - help_sections = @help_section.split('#') - @help_section = help_sections[0] - @anchor = help_sections[1] - else - @help_section = @help_section - @anchor = "" - end - end - - def get_login_user - (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" - end - - protected - # permissions checking - - def handle_perm_error(error) - handle_error(:error => error, :status => :forbidden, - :title => "Access denied") - end - - def handle_partial_success_error(error) - failures_arr = error.failures.collect do |resource, reason| - if resource.respond_to?(:display_name) - resource.display_name + ": " + reason - else - reason - end - end - @successes = error.successes - @failures = error.failures - handle_error(:error => error, :status => :ok, - :message => error.message + ": " + failures_arr.join(", "), - :title => "Some actions failed") - end - - def handle_action_error(error) - handle_error(:error => error, :status => :conflict, - :title => "Action Error") - end - - def handle_general_error(error) - flash[:errmsg] = error.message - handle_error(:error => error, :status => :internal_server_error, - :title => "Internal Server Error") - end - - def handle_error(hash) - log_error(hash[:error]) if hash[:error] - msg = hash[:message] || hash[:error].message - title = hash[:title] || "Internal Server Error" - status = hash[:status] || :internal_server_error - respond_to do |format| - format.html { html_error_page(title, msg) } - format.json { render :json => json_error_hash(msg, status) } - format.xml { render :xml => xml_errors(msg), :status => status } - end - end - - def html_error_page(title, msg) - @title = title - @errmsg = msg - @ajax = params[:ajax] - @nolayout = params[:nolayout] - if @layout - render :layout => @layout - elsif @ajax - render :template => 'layouts/popup-error', :layout => 'tabs-and-content' - elsif @nolayout - render :template => 'layouts/popup-error', :layout => 'help-and-content' - else - render :template => 'layouts/popup-error', :layout => 'popup' - end - end - - # don't define find_opts for array inputs - def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) - page = params[:page].to_i - paginate_opts = {:page => page, - :order => "#{params[:sortname]} #{params[:sortorder]}", - :per_page => params[:rp]} - arg_list << find_opts.merge(paginate_opts) - item_list = full_items.paginate(*arg_list) - json_hash = {} - json_hash[:page] = page - json_hash[:total] = item_list.total_entries - json_hash[:rows] = item_list.collect do |item| - item_hash = {} - item_hash[:id] = item.send(id_method) - item_hash[:cell] = attributes.collect do |attr| - if attr.is_a? Array - value = item - attr.each { |attr_item| value = (value.nil? ? nil : value.send(attr_item))} - value - else - item.send(attr) - end - end - item_hash - end - json_hash - end - - # json_list is a helper method used to format data for paginated flexigrid tables - # - # FIXME: what is the intent of this comment? don't define find_opts for array inputs - def json_list(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) - render :json => json_hash(full_items, attributes, arg_list, find_opts, id_method).to_json - end - - private - def json_error_hash(msg, status) - json = {} - json[:success] = (status == :ok) - json.merge!(instance_errors) - # There's a potential issue here: if we add :errors for an object - # that the view won't generate inline error messages for, the user - # won't get any indication what the error is. But if we set :alert - # unconditionally, the user will get validation errors twice: once - # inline in the form, and once in the flash - json[:alert] = msg unless json[:errors] - return json - end - - def xml_errors(msg) - xml = {} - xml[:message] = msg - xml.merge!(instance_errors) - return xml - end - - def instance_errors - hash = {} - instance_variables.each do |ivar| - val = instance_variable_get(ivar) - if val && val.respond_to?(:errors) && val.errors.size > 0 - hash[:object] = ivar[1, ivar.size] - hash[:errors] ||= [] - hash[:errors] += val.errors.localize_error_messages.to_a - end - end - return hash - end -end diff --git a/src/app/controllers/application_controller.rb b/src/app/controllers/application_controller.rb new file mode 100644 index 0000000..5ce625a --- /dev/null +++ b/src/app/controllers/application_controller.rb @@ -0,0 +1,198 @@ +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Scott Seago +# +# 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. + +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + + +class ApplicationController < ActionController::Base + # FIXME: once all controller classes include this, remove here + include ApplicationService + + init_gettext "ovirt" + layout :choose_layout + + before_filter :is_logged_in, :get_help_section + + # General error handlers, must be in order from least specific + # to most specific + rescue_from Exception, :with => :handle_general_error + rescue_from PermissionError, :with => :handle_perm_error + rescue_from ActionError, :with => :handle_action_error + rescue_from PartialSuccessError, :with => :handle_partial_success_error + + def choose_layout + if(params[:component_layout]) + return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux' + end + return 'redux' + end + + def is_logged_in + redirect_to(:controller => "/login", :action => "login") unless get_login_user + end + + def get_help_section + help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ]) + @help_section = help ? help.section : "" + if @help_section.index('#') + help_sections = @help_section.split('#') + @help_section = help_sections[0] + @anchor = help_sections[1] + else + @help_section = @help_section + @anchor = "" + end + end + + def get_login_user + (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" + end + + protected + # permissions checking + + def handle_perm_error(error) + handle_error(:error => error, :status => :forbidden, + :title => "Access denied") + end + + def handle_partial_success_error(error) + failures_arr = error.failures.collect do |resource, reason| + if resource.respond_to?(:display_name) + resource.display_name + ": " + reason + else + reason + end + end + @successes = error.successes + @failures = error.failures + handle_error(:error => error, :status => :ok, + :message => error.message + ": " + failures_arr.join(", "), + :title => "Some actions failed") + end + + def handle_action_error(error) + handle_error(:error => error, :status => :conflict, + :title => "Action Error") + end + + def handle_general_error(error) + flash[:errmsg] = error.message + handle_error(:error => error, :status => :internal_server_error, + :title => "Internal Server Error") + end + + def handle_error(hash) + log_error(hash[:error]) if hash[:error] + msg = hash[:message] || hash[:error].message + title = hash[:title] || "Internal Server Error" + status = hash[:status] || :internal_server_error + respond_to do |format| + format.html { html_error_page(title, msg) } + format.json { render :json => json_error_hash(msg, status) } + format.xml { render :xml => xml_errors(msg), :status => status } + end + end + + def html_error_page(title, msg) + @title = title + @errmsg = msg + @ajax = params[:ajax] + @nolayout = params[:nolayout] + if @layout + render :layout => @layout + elsif @ajax + render :template => 'layouts/popup-error', :layout => 'tabs-and-content' + elsif @nolayout + render :template => 'layouts/popup-error', :layout => 'help-and-content' + else + render :template => 'layouts/popup-error', :layout => 'popup' + end + end + + # don't define find_opts for array inputs + def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) + page = params[:page].to_i + paginate_opts = {:page => page, + :order => "#{params[:sortname]} #{params[:sortorder]}", + :per_page => params[:rp]} + arg_list << find_opts.merge(paginate_opts) + item_list = full_items.paginate(*arg_list) + json_hash = {} + json_hash[:page] = page + json_hash[:total] = item_list.total_entries + json_hash[:rows] = item_list.collect do |item| + item_hash = {} + item_hash[:id] = item.send(id_method) + item_hash[:cell] = attributes.collect do |attr| + if attr.is_a? Array + value = item + attr.each { |attr_item| value = (value.nil? ? nil : value.send(attr_item))} + value + else + item.send(attr) + end + end + item_hash + end + json_hash + end + + # json_list is a helper method used to format data for paginated flexigrid tables + # + # FIXME: what is the intent of this comment? don't define find_opts for array inputs + def json_list(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) + render :json => json_hash(full_items, attributes, arg_list, find_opts, id_method).to_json + end + + private + def json_error_hash(msg, status) + json = {} + json[:success] = (status == :ok) + json.merge!(instance_errors) + # There's a potential issue here: if we add :errors for an object + # that the view won't generate inline error messages for, the user + # won't get any indication what the error is. But if we set :alert + # unconditionally, the user will get validation errors twice: once + # inline in the form, and once in the flash + json[:alert] = msg unless json[:errors] + return json + end + + def xml_errors(msg) + xml = {} + xml[:message] = msg + xml.merge!(instance_errors) + return xml + end + + def instance_errors + hash = {} + instance_variables.each do |ivar| + val = instance_variable_get(ivar) + if val && val.respond_to?(:errors) && val.errors.size > 0 + hash[:object] = ivar[1, ivar.size] + hash[:errors] ||= [] + hash[:errors] += val.errors.localize_error_messages.to_a + end + end + return hash + end +end diff --git a/src/config.ru b/src/config.ru new file mode 100644 index 0000000..acbfe4e --- /dev/null +++ b/src/config.ru @@ -0,0 +1,7 @@ +# Rack Dispatcher + +# Require your environment file to bootstrap Rails +require File.dirname(__FILE__) + '/config/environment' + +# Dispatch the request +run ActionController::Dispatcher.new diff --git a/src/config/boot.rb b/src/config/boot.rb index cd21fb9..0ad0f78 100644 --- a/src/config/boot.rb +++ b/src/config/boot.rb @@ -44,6 +44,7 @@ module Rails def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" Rails::Initializer.run(:install_gem_spec_stubs) + Rails::GemDependency.add_frozen_gem_path end end @@ -67,7 +68,7 @@ module Rails class << self def rubygems_version - Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion + Gem::RubyGemsVersion rescue nil end def gem_version @@ -82,14 +83,14 @@ module Rails def load_rubygems require 'rubygems' - - unless rubygems_version >= '0.9.4' - $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + min_version = '1.3.1' + unless rubygems_version >= min_version + $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 end rescue LoadError - $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) + $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) exit 1 end diff --git a/src/config/environment.rb b/src/config/environment.rb index 98a2fbb..9d9a66d 100644 --- a/src/config/environment.rb +++ b/src/config/environment.rb @@ -19,9 +19,8 @@ # Be sure to restart your web server when you modify this file. -# Uncomment below to force Rails into production mode when -# you don't control web/app server and can't set it the proper way -# ENV['RAILS_ENV'] ||= 'production' +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') @@ -42,7 +41,7 @@ Rails::Initializer.run do |config| # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" # config.gem "aws-s3", :lib => "aws/s3" config.gem "cobbler" - config.gem "gettext", :lib => "gettext/rails" + config.gem "gettext", :lib => "gettext_rails" # Only load the plugins named here, in the order given. By default, all plugins # in vendor/plugins are loaded in alphabetical order. @@ -61,20 +60,6 @@ Rails::Initializer.run do |config| # Run "rake -D time" for a list of tasks for finding time zone names. Uncomment to use default local time. config.time_zone = 'UTC' - # Your secret key for verifying cookie session data integrity. - # If you change this key, all old sessions will become invalid! - # Make sure the secret is at least 30 characters and all random, - # no regular words or you'll be exposed to dictionary attacks. - config.action_controller.session = { - :session_key => "_ovirt_session_id", - :secret => "a covert ovirt phrase or some such" - } - - # Use the database for sessions instead of the cookie-based default, - # which shouldn't be used to store highly confidential information - # (create the session table with "rake db:sessions:create") - config.action_controller.session_store = :active_record_store - # Use SQL instead of Active Record's schema dumper when creating the test database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types @@ -83,17 +68,8 @@ Rails::Initializer.run do |config| # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector config.active_record.observers = :host_observer, :vm_observer -end -# Add new inflection rules using the following format -# (all these examples are active by default): -# Inflector.inflections do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' -# inflect.uncountable %w( fish sheep ) -# end - -# Add new mime types for use in respond_to blocks: -# Mime::Type.register "text/richtext", :rtf -# Mime::Type.register "application/x-mobile", :mobile + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] + # config.i18n.default_locale = :de +end diff --git a/src/config/initializers/backtrace_silencers.rb b/src/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..c2169ed --- /dev/null +++ b/src/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. +# Rails.backtrace_cleaner.remove_silencers! \ No newline at end of file diff --git a/src/config/initializers/inflections.rb b/src/config/initializers/inflections.rb new file mode 100644 index 0000000..9e8b013 --- /dev/null +++ b/src/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/src/config/initializers/mime_types.rb b/src/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/src/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/src/config/initializers/new_rails_defaults.rb b/src/config/initializers/new_rails_defaults.rb new file mode 100644 index 0000000..8ec3186 --- /dev/null +++ b/src/config/initializers/new_rails_defaults.rb @@ -0,0 +1,19 @@ +# Be sure to restart your server when you modify this file. + +# These settings change the behavior of Rails 2 apps and will be defaults +# for Rails 3. You can remove this initializer when Rails 3 is released. + +if defined?(ActiveRecord) + # Include Active Record class name as root for JSON serialized output. + ActiveRecord::Base.include_root_in_json = true + + # Store the full class name (including module namespace) in STI type column. + ActiveRecord::Base.store_full_sti_class = true +end + +# Use ISO 8601 format for JSON serialized times and dates. +ActiveSupport.use_standard_json_time_format = true + +# Don't escape HTML entities in JSON, leave that for the #json_escape helper. +# if you're including raw json in an HTML page. +ActiveSupport.escape_html_entities_in_json = false \ No newline at end of file diff --git a/src/config/initializers/session_store.rb b/src/config/initializers/session_store.rb new file mode 100644 index 0000000..64dbf51 --- /dev/null +++ b/src/config/initializers/session_store.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying cookie session data integrity. +# If you change this key, all old sessions will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +ActionController::Base.session = { + :key => '_rails23-app_session', + :secret => '41713a6b4a92b5b7af55314d2ef6fc499a177269ea91b9fdaa7d15c42e1234b70b32f52278ae26b774b38dbdfeb7d078585d10f643e81b6615d32410f192f1de' +} + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rake db:sessions:create") +ActionController::Base.session_store = :active_record_store diff --git a/src/public/javascripts/controls.js b/src/public/javascripts/controls.js index 1de3b29..ca29aef 100644 --- a/src/public/javascripts/controls.js +++ b/src/public/javascripts/controls.js @@ -1,22 +1,22 @@ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ -// Autocompleter.Base handles all the autocompletion functionality +// Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // -// Specific autocompleters need to provide, at the very least, +// Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method +// the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized @@ -30,23 +30,23 @@ // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); -var Autocompleter = { } +var Autocompleter = { }; Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { - element = $(element) + element = $(element); this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; @@ -59,28 +59,28 @@ Autocompleter.Base = Class.create({ this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ + this.options.onShow = this.options.onShow || + function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { - setHeight: false, + setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; - this.options.onHide = this.options.onHide || + this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - if(typeof(this.options.tokens) == 'string') + if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; - + this.element.setAttribute('autocomplete','off'); Element.hide(this.update); @@ -91,10 +91,10 @@ Autocompleter.Base = Class.create({ show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && + if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, + new Insertion.After(this.update, ''); @@ -102,7 +102,7 @@ Autocompleter.Base = Class.create({ } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, - + fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; @@ -150,15 +150,15 @@ Autocompleter.Base = Class.create({ Event.stop(event); return; } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); - this.observer = + this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, @@ -170,35 +170,35 @@ Autocompleter.Base = Class.create({ onHover: function(event) { var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) + if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, - + onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, - + onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; - this.active = false; - }, - + this.active = false; + }, + render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { + if(this.hasFocus) { this.show(); this.active = true; } @@ -207,27 +207,27 @@ Autocompleter.Base = Class.create({ this.hide(); } }, - + markPrevious: function() { - if(this.index > 0) this.index-- + if(this.index > 0) this.index--; else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, - + markNext: function() { - if(this.index < this.entryCount-1) this.index++ + if(this.index < this.entryCount-1) this.index++; else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, - + getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, - + getCurrentEntry: function() { return this.getEntry(this.index); }, - + selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); @@ -244,7 +244,7 @@ Autocompleter.Base = Class.create({ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - + var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); @@ -257,7 +257,7 @@ Autocompleter.Base = Class.create({ } this.oldElementValue = this.element.value; this.element.focus(); - + if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, @@ -269,20 +269,20 @@ Autocompleter.Base = Class.create({ Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = + this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } - } else { + } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; - + if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); @@ -298,7 +298,7 @@ Autocompleter.Base = Class.create({ }, onObserverEvent: function() { - this.changed = false; + this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); @@ -358,7 +358,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; - if(this.options.defaultParams) + if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; new Ajax.Request(this.url, this.options); @@ -382,7 +382,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the +// text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to @@ -399,7 +399,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // -// It's possible to pass in a custom function as the 'selector' +// It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. @@ -427,20 +427,20 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { var entry = instance.getToken(); var count = 0; - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; - } else if (entry.length >= instance.options.partialChars && + } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + @@ -450,14 +450,14 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { } } - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); return "
      " + ret.join('') + "
    "; } }, options || { }); @@ -474,7 +474,7 @@ Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); -} +}; Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { @@ -604,7 +604,7 @@ Ajax.InPlaceEditor = Class.create({ this.triggerCallback('onEnterHover'); }, getText: function() { - return this.element.innerHTML; + return this.element.innerHTML.unescapeHTML(); }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); @@ -780,7 +780,7 @@ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw 'Server returned an invalid collection representation.'; + throw('Server returned an invalid collection representation.'); this._collection = eval(js); this.checkForExternalText(); }.bind(this), @@ -937,7 +937,7 @@ Ajax.InPlaceCollectionEditor.DefaultOptions = { loadingCollectionText: 'Loading options...' }; -// Delayed observer, like Form.Element.Observer, +// Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields @@ -947,7 +947,7 @@ Form.Element.DelayedObserver = Class.create({ this.element = $(element); this.callback = callback; this.timer = null; - this.lastValue = $F(this.element); + this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { @@ -960,4 +960,4 @@ Form.Element.DelayedObserver = Class.create({ this.timer = null; this.callback(this.element, $F(this.element)); } -}); +}); \ No newline at end of file diff --git a/src/public/javascripts/dragdrop.js b/src/public/javascripts/dragdrop.js index e2e7d4a..07229f9 100644 --- a/src/public/javascripts/dragdrop.js +++ b/src/public/javascripts/dragdrop.js @@ -1,6 +1,6 @@ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi at oriontransfer.co.nz) -// +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi at oriontransfer.co.nz) +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ @@ -32,7 +32,7 @@ var Droppables = { options._containers.push($(containment)); } } - + if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE @@ -40,34 +40,34 @@ var Droppables = { this.drops.push(options); }, - + findDeepestChild: function(drops) { deepest = drops[0]; - + for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; - + return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { - containmentNode = element.treeNode; + containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, - + isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || - (Element.classNames(element).detect( + (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, @@ -87,12 +87,12 @@ var Droppables = { show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; - + this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); - + if(affected.length>0) drop = Droppables.findDeepestChild(affected); @@ -101,7 +101,7 @@ var Droppables = { Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - + if (drop != this.last_active) Droppables.activate(drop); } }, @@ -121,25 +121,25 @@ var Droppables = { if(this.last_active) this.deactivate(this.last_active); } -} +}; var Draggables = { drags: [], observers: [], - + register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); - + Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, - + unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { @@ -148,24 +148,24 @@ var Draggables = { Event.stopObserving(document, "keypress", this.eventKeypress); } }, - + activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, - + deactivate: function() { this.activeDraggable = null; }, - + updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -173,36 +173,36 @@ var Draggables = { // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; - + this.activeDraggable.updateDrag(event, pointer); }, - + endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, - + keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, - + addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, - + removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, - + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { @@ -210,7 +210,7 @@ var Draggables = { }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, - + _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( @@ -218,7 +218,7 @@ var Draggables = { ).length; }); } -} +}; /*--------------------------------------------------------------------------*/ @@ -234,12 +234,12 @@ var Draggable = Class.create({ }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false + afterFinish: function(){ + Draggable._dragging[element] = false } - }); + }); }, zindex: 1000, revert: false, @@ -250,57 +250,57 @@ var Draggable = Class.create({ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; - + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); - + var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); - + if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); - + if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; - + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } - Element.makePositioned(this.element); // fix IE + Element.makePositioned(this.element); // fix IE this.options = options; - this.dragging = false; + this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); - + Draggables.register(this); }, - + destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, - + currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, - + initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { + if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( @@ -309,34 +309,34 @@ var Draggable = Class.create({ tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; - + var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = Position.cumulativeOffset(this.element); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - + Draggables.activate(this); Event.stop(event); } }, - + startDrag: function(event) { this.dragging = true; if(!this.delta) this.delta = this.currentDelta(); - + if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } - + if(this.options.ghosting) { this._clone = this.element.cloneNode(true); - this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this.element._originallyAbsolute) + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } - + if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); @@ -347,15 +347,15 @@ var Draggable = Class.create({ this.originalScrollTop = this.options.scroll.scrollTop; } } - + Draggables.notify('onStart', this, event); - + if(this.options.starteffect) this.options.starteffect(this.element); }, - + updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); - + if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); @@ -403,9 +403,9 @@ var Draggable = Class.create({ } if(this.options.ghosting) { - if (!this.element._originallyAbsolute) + if (!this._originallyAbsolute) Position.relativize(this.element); - delete this.element._originallyAbsolute; + delete this._originallyAbsolute; Element.remove(this._clone); this._clone = null; } @@ -433,7 +433,7 @@ var Draggable = Class.create({ if(this.options.zindex) this.element.style.zIndex = this.originalZ; - if(this.options.endeffect) + if(this.options.endeffect) this.options.endeffect(this.element); Draggables.deactivate(this); @@ -468,8 +468,8 @@ var Draggable = Class.create({ pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { @@ -478,10 +478,10 @@ var Draggable = Class.create({ } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); } else { p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)) + return (v/this.options.snap).round()*this.options.snap }.bind(this)); } }} @@ -560,7 +560,7 @@ var Draggable = Class.create({ H = documentElement.clientHeight; } else { W = body.offsetWidth; - H = body.offsetHeight + H = body.offsetHeight; } } return { top: T, left: L, width: W, height: H }; @@ -591,9 +591,9 @@ var SortableObserver = Class.create({ var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - + sortables: { }, - + _findRootElement: function(element) { while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; @@ -608,7 +608,8 @@ var Sortable = { }, destroy: function(element){ - var s = Sortable.options(element); + element = $(element); + var s = Sortable.sortables[element.id]; if(s) { Draggables.removeObserver(s.element); @@ -689,14 +690,14 @@ var Sortable = { tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover - } + }; var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass - } + }; // fix for gecko engine Element.cleanWhitespace(element); @@ -832,7 +833,7 @@ var Sortable = { Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - + Sortable._marker.show(); }, @@ -851,11 +852,11 @@ var Sortable = { children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) - } + }; /* Get the element containing the children and recurse over it */ if (child.container) - this._tree(child.container, options, child) + this._tree(child.container, options, child); parent.children.push (child); } @@ -880,7 +881,7 @@ var Sortable = { children: [], container: element, position: 0 - } + }; return Sortable._tree(element, options, root); }, @@ -931,7 +932,7 @@ var Sortable = { if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + + return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { @@ -940,14 +941,14 @@ var Sortable = { }).join('&'); } } -} +}; // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); -} +}; Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; @@ -965,8 +966,8 @@ Element.findChildren = function(element, only, recursive, tagName) { }); return (elements.length>0 ? elements.flatten() : []); -} +}; Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -} +}; \ No newline at end of file diff --git a/src/public/javascripts/effects.js b/src/public/javascripts/effects.js index b0f056b..5a639d2 100644 --- a/src/public/javascripts/effects.js +++ b/src/public/javascripts/effects.js @@ -3,9 +3,9 @@ // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ +// For details, see the script.aculo.us web site: http://script.aculo.us/ // converts rgb() and #xxx to #xxxxxx format, // returns self (or first argument) if not convertable @@ -32,7 +32,7 @@ Element.collectTextNodes = function(element) { }).flatten().join(''); }; -Element.collectTextNodesIgnoreClass = function(element, className) { +Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? @@ -70,25 +70,20 @@ var Effect = { Transitions: { linear: Prototype.K, sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; + return (-Math.cos(pos*Math.PI)/2) + .5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; }, pulse: function(pos, pulses) { - pulses = pulses || 5; - return ( - ((pos % (1/pulses)) * pulses).round() == 0 ? - ((pos * pulses * 2) - (pos * pulses * 2).floor()) : - 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) - ); + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; }, spring: function(pos) { return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); @@ -223,7 +218,7 @@ Effect.Queues = { instances: $H(), get: function(queueName) { if (!Object.isString(queueName)) return queueName; - + return this.instances.get(queueName) || this.instances.set(queueName, new Effect.ScopedQueue()); } @@ -249,18 +244,30 @@ Effect.Base = Class.create({ this.totalTime = this.finishOn-this.startOn; this.totalFrames = this.options.fps*this.options.duration; - eval('this.render = function(pos){ '+ - 'if (this.state=="idle"){this.state="running";'+ - codeForEvent(this.options,'beforeSetup')+ - (this.setup ? 'this.setup();':'')+ - codeForEvent(this.options,'afterSetup')+ - '};if (this.state=="running"){'+ - 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+ - 'this.position=pos;'+ - codeForEvent(this.options,'beforeUpdate')+ - (this.update ? 'this.update(pos);':'')+ - codeForEvent(this.options,'afterUpdate')+ - '}}'); + this.render = (function() { + function dispatch(effect, eventName) { + if (effect.options[eventName + 'Internal']) + effect.options[eventName + 'Internal'](effect); + if (effect.options[eventName]) + effect.options[eventName](effect); + } + + return function(pos) { + if (this.state === "idle") { + this.state = "running"; + dispatch(this, 'beforeSetup'); + if (this.setup) this.setup(); + dispatch(this, 'afterSetup'); + } + if (this.state === "running") { + pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from; + this.position = pos; + dispatch(this, 'beforeUpdate'); + if (this.update) this.update(pos); + dispatch(this, 'afterUpdate'); + } + }; + })(); this.event('beforeStart'); if (!this.options.sync) @@ -392,7 +399,7 @@ Effect.Move = Class.create(Effect.Base, { // for backwards compatibility Effect.MoveBy = function(element, toTop, toLeft) { - return new Effect.Move(element, + return new Effect.Move(element, Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); }; @@ -507,17 +514,16 @@ Effect.Highlight = Class.create(Effect.Base, { Effect.ScrollTo = function(element) { var options = arguments[1] || { }, - scrollOffsets = document.viewport.getScrollOffsets(), - elementOffsets = $(element).cumulativeOffset(), - max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, - elementOffsets[1] > max ? max : elementOffsets[1], + elementOffsets[1], options, - function(p){ scrollTo(scrollOffsets.left, p.round()) } + function(p){ scrollTo(scrollOffsets.left, p.round()); } ); }; @@ -554,7 +560,7 @@ Effect.Appear = function(element) { Effect.Puff = function(element) { element = $(element); - var oldStyle = { + var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, @@ -563,12 +569,12 @@ Effect.Puff = function(element) { height: element.style.height }; return new Effect.Parallel( - [ new Effect.Scale(element, 200, + [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { - Position.absolutize(effect.effects[0].element) + Position.absolutize(effect.effects[0].element); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } @@ -580,12 +586,12 @@ Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); - } + } }, arguments[1] || { }) ); }; @@ -619,13 +625,13 @@ Effect.SwitchOff = function(element) { new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { + beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } - }) + }); } }, arguments[1] || { })); }; @@ -646,7 +652,7 @@ Effect.DropOut = function(element) { }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); - } + } }, arguments[1] || { })); }; @@ -674,7 +680,7 @@ Effect.Shake = function(element) { new Effect.Move(effect.element, { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); - }}) }}) }}) }}) }}) }}); + }}); }}); }}); }}); }}); }}); }; Effect.SlideDown = function(element) { @@ -682,7 +688,7 @@ Effect.SlideDown = function(element) { // SlideDown need to have the content of the element wrapped in a container element with fixed height! var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ + return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: window.opera ? 0 : 1, @@ -742,7 +748,7 @@ Effect.Squish = function(element) { effect.element.makeClipping(); }, afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); + effect.element.hide().undoClipping(); } }); }; @@ -810,13 +816,13 @@ Effect.Grow = function(element) { sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { - effect.effects[0].element.setStyle({height: '0px'}).show(); + effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) - ) + ); } }); }; @@ -838,7 +844,7 @@ Effect.Shrink = function(element) { var dims = element.getDimensions(); var moveX, moveY; - + switch (options.direction) { case 'top-left': moveX = moveY = 0; @@ -877,11 +883,13 @@ Effect.Shrink = function(element) { Effect.Pulsate = function(element) { element = $(element); - var options = arguments[1] || { }; - var oldOpacity = element.getInlineOpacity(); - var transition = options.transition || Effect.Transitions.sinoidal; - var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; - reverser.bind(transition); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } @@ -934,7 +942,7 @@ Effect.Morph = Class.create(Effect.Base, { effect.transforms.each(function(transform) { effect.element.style[transform.style] = ''; }); - } + }; } } this.start(options); @@ -945,7 +953,7 @@ Effect.Morph = Class.create(Effect.Base, { if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ - return parseInt( color.slice(i*2+1,i*2+3), 16 ) + return parseInt( color.slice(i*2+1,i*2+3), 16 ); }); } this.transforms = this.style.map(function(pair){ @@ -978,7 +986,7 @@ Effect.Morph = Class.create(Effect.Base, { transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) - ) + ); }); }, update: function(position) { @@ -1074,14 +1082,14 @@ if (document.defaultView && document.defaultView.getComputedStyle) { Element.getStyles = function(element) { element = $(element); var css = element.currentStyle, styles; - styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { - hash.set(property, css[property]); - return hash; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; }); - if (!styles.opacity) styles.set('opacity', element.getOpacity()); + if (!styles.opacity) styles.opacity = element.getOpacity(); return styles; }; -}; +} Effect.Methods = { morph: function(element, style) { @@ -1090,7 +1098,7 @@ Effect.Methods = { return element; }, visualEffect: function(element, effect, options) { - element = $(element) + element = $(element); var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); new Effect[klass](element, options); return element; @@ -1109,7 +1117,7 @@ $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ element = $(element); Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); return element; - } + }; } ); @@ -1117,4 +1125,4 @@ $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTex function(f) { Effect.Methods[f] = Element[f]; } ); -Element.addMethods(Effect.Methods); +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/src/public/javascripts/prototype.js b/src/public/javascripts/prototype.js index 9f6c857..dfe8ab4 100644 --- a/src/public/javascripts/prototype.js +++ b/src/public/javascripts/prototype.js @@ -1,5 +1,5 @@ -/* Prototype JavaScript framework, version 1.6.0.1 - * (c) 2005-2007 Sam Stephenson +/* Prototype JavaScript framework, version 1.6.0.3 + * (c) 2005-2008 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ @@ -7,23 +7,26 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.1', + Version: '1.6.0.3', Browser: { - IE: !!(window.attachEvent && !window.opera), - Opera: !!window.opera, + IE: !!(window.attachEvent && + navigator.userAgent.indexOf('Opera') === -1), + Opera: navigator.userAgent.indexOf('Opera') > -1, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1, MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: - document.createElement('div').__proto__ && - document.createElement('div').__proto__ !== - document.createElement('form').__proto__ + document.createElement('div')['__proto__'] && + document.createElement('div')['__proto__'] !== + document.createElement('form')['__proto__'] }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', @@ -83,12 +86,13 @@ Class.Methods = { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { - var method = value, value = Object.extend((function(m) { + var method = value; + value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method), { - valueOf: function() { return method }, - toString: function() { return method.toString() } - }); + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); } this.prototype[property] = value; } @@ -110,7 +114,7 @@ Object.extend(Object, { try { if (Object.isUndefined(object)) return 'undefined'; if (object === null) return 'null'; - return object.inspect ? object.inspect() : object.toString(); + return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; @@ -167,11 +171,12 @@ Object.extend(Object, { }, isElement: function(object) { - return object && object.nodeType == 1; + return !!(object && object.nodeType == 1); }, isArray: function(object) { - return object && object.constructor === Array; + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; }, isHash: function(object) { @@ -197,7 +202,8 @@ Object.extend(Object, { Object.extend(Function.prototype, { argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }, @@ -231,6 +237,11 @@ Object.extend(Function.prototype, { }, timeout); }, + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + wrap: function(wrapper) { var __method = this; return function() { @@ -247,8 +258,6 @@ Object.extend(Function.prototype, { } }); -Function.prototype.defer = Function.prototype.delay.curry(0.01); - Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + @@ -529,7 +538,7 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto return this.replace(/&/g,'&').replace(//g,'>'); }, unescapeHTML: function() { - return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } }); @@ -546,7 +555,7 @@ Object.extend(String.prototype.escapeHTML, { text: document.createTextNode('') }); -with (String.prototype.escapeHTML) div.appendChild(text); +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); var Template = Class.create({ initialize: function(template, pattern) { @@ -578,7 +587,7 @@ var Template = Class.create({ } return before + String.interpret(ctx); - }.bind(this)); + }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; @@ -588,10 +597,9 @@ var $break = { }; var Enumerable = { each: function(iterator, context) { var index = 0; - iterator = iterator.bind(context); try { this._each(function(value) { - iterator(value, index++); + iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; @@ -600,47 +608,46 @@ var Enumerable = { }, eachSlice: function(number, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); }, all: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!iterator(value, index); + result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; }, any: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!iterator(value, index)) + if (result = !!iterator.call(context, value, index)) throw $break; }); return result; }, collect: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, detect: function(iterator, context) { - iterator = iterator.bind(context); var result; this.each(function(value, index) { - if (iterator(value, index)) { + if (iterator.call(context, value, index)) { result = value; throw $break; } @@ -649,17 +656,16 @@ var Enumerable = { }, findAll: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (iterator(value, index)) + if (iterator.call(context, value, index)) results.push(value); }); return results; }, grep: function(filter, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) @@ -667,7 +673,7 @@ var Enumerable = { this.each(function(value, index) { if (filter.match(value)) - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, @@ -695,9 +701,8 @@ var Enumerable = { }, inject: function(memo, iterator, context) { - iterator = iterator.bind(context); this.each(function(value, index) { - memo = iterator(memo, value, index); + memo = iterator.call(context, memo, value, index); }); return memo; }, @@ -710,10 +715,10 @@ var Enumerable = { }, max: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); @@ -721,10 +726,10 @@ var Enumerable = { }, min: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); @@ -732,10 +737,10 @@ var Enumerable = { }, partition: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - (iterator(value, index) ? + (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; @@ -750,19 +755,20 @@ var Enumerable = { }, reject: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (!iterator(value, index)) + if (!iterator.call(context, value, index)) results.push(value); }); return results; }, sortBy: function(iterator, context) { - iterator = iterator.bind(context); return this.map(function(value, index) { - return {value: value, criteria: iterator(value, index)}; + return { + value: value, + criteria: iterator.call(context, value, index) + }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; @@ -806,20 +812,24 @@ Object.extend(Enumerable, { function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } if (Prototype.Browser.WebKit) { - function $A(iterable) { + $A = function(iterable) { if (!iterable) return []; - if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && - iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; - } + }; } Array.from = $A; @@ -962,8 +972,8 @@ Object.extend(Number.prototype, { return this + 1; }, - times: function(iterator) { - $R(0, this, true).each(iterator); + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); return this; }, @@ -1010,7 +1020,9 @@ var Hash = Class.create(Enumerable, (function() { }, get: function(key) { - return this._object[key]; + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; }, unset: function(key) { @@ -1050,14 +1062,14 @@ var Hash = Class.create(Enumerable, (function() { }, toQueryString: function() { - return this.map(function(pair) { + return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) - return values.map(toQueryPair.curry(key)).join('&'); - } - return toQueryPair(key, values); + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; }).join('&'); }, @@ -1298,7 +1310,7 @@ Ajax.Request = Class.create(Ajax.Base, { var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' - || (this.options.evalJS && contentType + || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } @@ -1316,9 +1328,18 @@ Ajax.Request = Class.create(Ajax.Base, { } }, + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + getHeader: function(name) { try { - return this.transport.getResponseHeader(name); + return this.transport.getResponseHeader(name) || null; } catch (e) { return null } }, @@ -1391,7 +1412,8 @@ Ajax.Response = Class.create({ if (!json) return null; json = decodeURIComponent(escape(json)); try { - return json.evalJSON(this.request.options.sanitizeJSON); + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1404,7 +1426,8 @@ Ajax.Response = Class.create({ this.responseText.blank()) return null; try { - return this.responseText.evalJSON(options.sanitizeJSON); + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1546,6 +1569,7 @@ if (!Node.ELEMENT_NODE) { return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; }).call(window); Element.cache = { }; @@ -1562,12 +1586,14 @@ Element.Methods = { }, hide: function(element) { - $(element).style.display = 'none'; + element = $(element); + element.style.display = 'none'; return element; }, show: function(element) { - $(element).style.display = ''; + element = $(element); + element.style.display = ''; return element; }, @@ -1608,24 +1634,28 @@ Element.Methods = { Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; - var content, t, range; + var content, insert, tagName, childNodes; - for (position in insertions) { + for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); - t = Element._insertionTranslations[position]; + insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { - t.insert(element, content); + insert(element, content); continue; } content = Object.toHTML(content); - range = element.ownerDocument.createRange(); - t.initializeRange(element, range); - t.insert(element, range.createContextualFragment(content.stripScripts())); + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } @@ -1670,7 +1700,7 @@ Element.Methods = { }, descendants: function(element) { - return $(element).getElementsBySelector("*"); + return $(element).select("*"); }, firstDescendant: function(element) { @@ -1709,32 +1739,31 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = element.ancestors(); - return expression ? Selector.findElement(ancestors, expression, index) : - ancestors[index || 0]; + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); - var descendants = element.descendants(); - return expression ? Selector.findElement(descendants, expression, index) : - descendants[index || 0]; + return Object.isNumber(expression) ? element.descendants()[expression] : + Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = element.previousSiblings(); - return expression ? Selector.findElement(previousSiblings, expression, index) : - previousSiblings[index || 0]; + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = element.nextSiblings(); - return expression ? Selector.findElement(nextSiblings, expression, index) : - nextSiblings[index || 0]; + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); }, select: function() { @@ -1848,23 +1877,16 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); - var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; - if (element.sourceIndex && !Prototype.Browser.Opera) { - var e = element.sourceIndex, a = ancestor.sourceIndex, - nextAncestor = ancestor.nextSibling; - if (!nextAncestor) { - do { ancestor = ancestor.parentNode; } - while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); - } - if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); - } + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) - if (element == originalAncestor) return true; + if (element == ancestor) return true; + return false; }, @@ -1879,7 +1901,7 @@ Element.Methods = { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; - if (!value) { + if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } @@ -1918,7 +1940,7 @@ Element.Methods = { getDimensions: function(element) { element = $(element); - var display = $(element).getStyle('display'); + var display = element.getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; @@ -1947,7 +1969,7 @@ Element.Methods = { element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined - if (window.opera) { + if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } @@ -2002,9 +2024,9 @@ Element.Methods = { valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName == 'BODY') break; + if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); - if (p == 'relative' || p == 'absolute') break; + if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); @@ -2012,7 +2034,7 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return; + if (element.getStyle('position') == 'absolute') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. var offsets = element.positionedOffset(); @@ -2036,7 +2058,7 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return; + if (element.getStyle('position') == 'relative') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. element.style.position = 'relative'; @@ -2087,7 +2109,7 @@ Element.Methods = { element = forElement; do { - if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } @@ -2153,46 +2175,6 @@ Element._attributeTranslations = { } }; - -if (!document.createRange || Prototype.Browser.Opera) { - Element.Methods.insert = function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = { bottom: insertions }; - - var t = Element._insertionTranslations, content, position, pos, tagName; - - for (position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - pos = t[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - pos.insert(element, content); - continue; - } - - content = Object.toHTML(content); - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - if (t.tags[tagName]) { - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - if (position == 'top' || position == 'after') fragments.reverse(); - fragments.each(pos.insert.curry(element)); - } - else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); - - content.evalScripts.bind(content).defer(); - } - - return element; - }; -} - if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { @@ -2237,12 +2219,36 @@ if (Prototype.Browser.Opera) { } else if (Prototype.Browser.IE) { - $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } var position = element.getStyle('position'); - if (position != 'static') return proceed(element); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); @@ -2251,6 +2257,14 @@ else if (Prototype.Browser.IE) { ); }); + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -2324,7 +2338,10 @@ else if (Prototype.Browser.IE) { }; Element._attributeTranslations.write = { - names: Object.clone(Element._attributeTranslations.read.names), + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; @@ -2339,7 +2356,7 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc').each(function(attr) { + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); @@ -2392,7 +2409,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName == 'IMG' && element.width) { + if(element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2444,7 +2461,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) { }; } -if (document.createElement('div').outerHTML) { +if ('outerHTML' in document.createElement('div')) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2482,45 +2499,25 @@ Element._returnOffset = function(l, t) { Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; return $A(div.childNodes); }; Element._insertionTranslations = { - before: { - adjacency: 'beforeBegin', - insert: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - initializeRange: function(element, range) { - range.setStartBefore(element); - } + before: function(element, node) { + element.parentNode.insertBefore(node, element); }, - top: { - adjacency: 'afterBegin', - insert: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - initializeRange: function(element, range) { - range.selectNodeContents(element); - range.collapse(true); - } + top: function(element, node) { + element.insertBefore(node, element.firstChild); }, - bottom: { - adjacency: 'beforeEnd', - insert: function(element, node) { - element.appendChild(node); - } + bottom: function(element, node) { + element.appendChild(node); }, - after: { - adjacency: 'afterEnd', - insert: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - initializeRange: function(element, range) { - range.setStartAfter(element); - } + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['', '
    ', 1], @@ -2532,7 +2529,6 @@ Element._insertionTranslations = { }; (function() { - this.bottom.initializeRange = this.top.initializeRange; Object.extend(this.tags, { THEAD: this.tags.TBODY, TFOOT: this.tags.TBODY, @@ -2544,7 +2540,7 @@ Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); - return node && node.specified; + return !!(node && node.specified); } }; @@ -2553,9 +2549,9 @@ Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { + document.createElement('div')['__proto__']) { window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div').__proto__; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } @@ -2570,7 +2566,7 @@ Element.extend = (function() { element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName, property, value; + tagName = element.tagName.toUpperCase(), property, value; // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); @@ -2666,7 +2662,7 @@ Element.addMethods = function(methods) { if (window[klass]) return window[klass]; window[klass] = { }; - window[klass].prototype = document.createElement(tagName).__proto__; + window[klass].prototype = document.createElement(tagName)['__proto__']; return window[klass]; } @@ -2692,12 +2688,18 @@ Element.addMethods = function(methods) { document.viewport = { getDimensions: function() { - var dimensions = { }; - var B = Prototype.Browser; + var dimensions = { }, B = Prototype.Browser; $w('width height').each(function(d) { var D = d.capitalize(); - dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : - (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } }); return dimensions; }, @@ -2716,14 +2718,24 @@ document.viewport = { window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum???s DomQuery, +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); - this.compileMatcher(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + }, shouldUseXPath: function() { @@ -2738,16 +2750,29 @@ var Selector = Class.create({ // XPath can't do namespaced attributes, nor can it read // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + if ((/(\[[\w-]*?:|:checked)/).test(e)) return false; return true; }, - compileMatcher: function() { - if (this.shouldUseXPath()) - return this.compileXPathMatcher(); + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + return true; + }, + + compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; @@ -2765,7 +2790,7 @@ var Selector = Class.create({ p = ps[i]; if (m = e.match(p)) { this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); + new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; } @@ -2804,8 +2829,27 @@ var Selector = Class.create({ findElements: function(root) { root = root || document; - if (this.xpath) return document._getElementsByXPath(this.xpath, root); - return this.matcher(root); + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } }, match: function(element) { @@ -2896,10 +2940,10 @@ Object.extend(Selector, { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'empty': "[count(*) = 0 and (count(text()) = 0)]", 'checked': "[@checked]", - 'disabled': "[@disabled]", - 'enabled': "[not(@disabled)]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v; @@ -2959,13 +3003,13 @@ Object.extend(Selector, { }, criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', attr: function(m) { m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); }, pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); @@ -2989,8 +3033,9 @@ Object.extend(Selector, { tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, - attrPresence: /^\[([\w]+)\]/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, @@ -3014,7 +3059,7 @@ Object.extend(Selector, { attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); - return Selector.operators[matches[2]](nodeValue, matches[3]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, @@ -3029,14 +3074,15 @@ Object.extend(Selector, { // marks an array of nodes for counting mark: function(nodes) { + var _true = Prototype.emptyFunction; for (var i = 0, node; node = nodes[i]; i++) - node._counted = true; + node._countedByPrototype = _true; return nodes; }, unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) - node._counted = undefined; + node._countedByPrototype = undefined; return nodes; }, @@ -3044,15 +3090,15 @@ Object.extend(Selector, { // "ofType" flag indicates whether we're indexing for nth-of-type // rather than nth-child index: function(parentNode, reverse, ofType) { - parentNode._counted = true; + parentNode._countedByPrototype = Prototype.emptyFunction; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } }, @@ -3061,8 +3107,8 @@ Object.extend(Selector, { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._counted) { - n._counted = true; + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); @@ -3102,7 +3148,7 @@ Object.extend(Selector, { nextElementSibling: function(node) { while (node = node.nextSibling) - if (node.nodeType == 1) return node; + if (node.nodeType == 1) return node; return null; }, @@ -3114,7 +3160,7 @@ Object.extend(Selector, { // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { - tagName = tagName.toUpperCase(); + var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { @@ -3127,7 +3173,7 @@ Object.extend(Selector, { if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() == tagName) results.push(node); + if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, @@ -3174,16 +3220,18 @@ Object.extend(Selector, { return results; }, - attrPresence: function(nodes, root, attr) { + attrPresence: function(nodes, root, attr, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); return results; }, - attr: function(nodes, root, attr, value, operator) { + attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); var handler = Selector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); @@ -3262,7 +3310,7 @@ Object.extend(Selector, { var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._counted) { + if (!node.parentNode._countedByPrototype) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } @@ -3289,7 +3337,7 @@ Object.extend(Selector, { 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { // IE treats comments as element nodes - if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + if (node.tagName == '!' || node.firstChild) continue; results.push(node); } return results; @@ -3300,14 +3348,15 @@ Object.extend(Selector, { var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._counted) results.push(node); + if (!node._countedByPrototype) results.push(node); h.unmark(exclusions); return results; }, 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled) results.push(node); + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); return results; }, @@ -3327,18 +3376,29 @@ Object.extend(Selector, { operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv.startsWith(v); }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, '$=': function(nv, v) { return nv.endsWith(v); }, '*=': function(nv, v) { return nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; }, matchElements: function(elements, expression) { - var matches = new Selector(expression).findElements(), h = Selector.handlers; + var matches = $$(expression), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._counted) results.push(element); + if (element._countedByPrototype) results.push(element); h.unmark(matches); return results; }, @@ -3351,11 +3411,7 @@ Object.extend(Selector, { }, findChildElements: function(element, expressions) { - var exprs = expressions.join(','); - expressions = []; - exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); + expressions = Selector.split(expressions.join(',')); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); @@ -3366,13 +3422,22 @@ Object.extend(Selector, { }); if (Prototype.Browser.IE) { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - Selector.handlers.concat = function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }; + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); } function $$() { @@ -3392,7 +3457,7 @@ var Form = { var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); - if (value != null && (element.type != 'submit' || (!submitted && + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { // a key is already present; construct an array of values @@ -3553,7 +3618,6 @@ Form.Element.Methods = { disable: function(element) { element = $(element); - element.blur(); element.disabled = true; return element; }, @@ -3593,22 +3657,22 @@ Form.Element.Serializers = { else element.value = value; }, - select: function(element, index) { - if (Object.isUndefined(index)) + select: function(element, value) { + if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { - var opt, value, single = !Object.isArray(index); + var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; - value = this.optionValue(opt); + currentValue = this.optionValue(opt); if (single) { - if (value == index) { + if (currentValue == value) { opt.selected = true; return; } } - else opt.selected = index.include(value); + else opt.selected = value.include(currentValue); } } }, @@ -3779,8 +3843,23 @@ Event.Methods = (function() { isRightClick: function(event) { return isButton(event, 2) }, element: function(event) { - var node = Event.extend(event).target; - return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); }, findElement: function(event, expression) { @@ -3791,11 +3870,15 @@ Event.Methods = (function() { }, pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; return { x: event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)), + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), y: event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)) + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) }; }, @@ -3840,7 +3923,7 @@ Event.extend = (function() { }; } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; Object.extend(Event.prototype, methods); return Prototype.K; } @@ -3850,9 +3933,9 @@ Object.extend(Event, (function() { var cache = Event.cache; function getEventID(element) { - if (element._eventID) return element._eventID; + if (element._prototypeEventID) return element._prototypeEventID[0]; arguments.callee.id = arguments.callee.id || 1; - return element._eventID = ++arguments.callee.id; + return element._prototypeEventID = [++arguments.callee.id]; } function getDOMEventName(eventName) { @@ -3880,7 +3963,7 @@ Object.extend(Event, (function() { return false; Event.extend(event); - handler.call(element, event) + handler.call(element, event); }; wrapper.handler = handler; @@ -3905,10 +3988,20 @@ Object.extend(Event, (function() { cache[id][eventName] = null; } + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. if (window.attachEvent) { window.attachEvent("onunload", destroyCache); } + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + return { observe: function(element, eventName, handler) { element = $(element); @@ -3962,11 +4055,12 @@ Object.extend(Event, (function() { if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; + var event; if (document.createEvent) { - var event = document.createEvent("HTMLEvents"); + event = document.createEvent("HTMLEvents"); event.initEvent("dataavailable", true, true); } else { - var event = document.createEventObject(); + event = document.createEventObject(); event.eventType = "ondataavailable"; } @@ -3995,20 +4089,21 @@ Element.addMethods({ Object.extend(document, { fire: Element.Methods.fire.methodize(), observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize() + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false }); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards and John Resig. */ - var timer, fired = false; + var timer; function fireContentLoadedEvent() { - if (fired) return; + if (document.loaded) return; if (timer) window.clearInterval(timer); document.fire("dom:loaded"); - fired = true; + document.loaded = true; } if (document.addEventListener) { -- 1.6.2.5 From jason.guiditta at gmail.com Mon Jul 20 14:58:36 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Mon, 20 Jul 2009 10:58:36 -0400 Subject: [Ovirt-devel] [PATCH server 7/8] Install render_component plugin. In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248101917-7586-7-git-send-email-jason.guiditta@gmail.com> This is no longer in core rails (and we should look at removing it's use altogether asap). Signed-off-by: Jason Guiditta --- src/vendor/plugins/render_component/README | 37 +++++ src/vendor/plugins/render_component/Rakefile | 22 +++ src/vendor/plugins/render_component/init.rb | 2 + .../plugins/render_component/lib/components.rb | 141 ++++++++++++++++++++ .../plugins/render_component/test/abstract_unit.rb | 8 + .../render_component/test/components_test.rb | 136 +++++++++++++++++++ 6 files changed, 346 insertions(+), 0 deletions(-) create mode 100644 src/vendor/plugins/render_component/README create mode 100644 src/vendor/plugins/render_component/Rakefile create mode 100644 src/vendor/plugins/render_component/init.rb create mode 100644 src/vendor/plugins/render_component/lib/components.rb create mode 100644 src/vendor/plugins/render_component/test/abstract_unit.rb create mode 100644 src/vendor/plugins/render_component/test/components_test.rb diff --git a/src/vendor/plugins/render_component/README b/src/vendor/plugins/render_component/README new file mode 100644 index 0000000..a6a318a --- /dev/null +++ b/src/vendor/plugins/render_component/README @@ -0,0 +1,37 @@ +Components allow you to call other actions for their rendered response while executing another action. You can either delegate +the entire response rendering or you can mix a partial response in with your other content. + + class WeblogController < ActionController::Base + # Performs a method and then lets hello_world output its render + def delegate_action + do_other_stuff_before_hello_world + render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" } + end + end + + class GreeterController < ActionController::Base + def hello_world + render :text => "#{params[:person]} says, Hello World!" + end + end + +The same can be done in a view to do a partial rendering: + + Let's see a greeting: + <%= render_component :controller => "greeter", :action => "hello_world" %> + +It is also possible to specify the controller as a class constant, bypassing the inflector +code to compute the controller class at runtime: + +<%= render_component :controller => GreeterController, :action => "hello_world" %> + +== When to use components + +Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and +conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead, +reserve components to those rare cases where you truly have reusable view and controller elements that can be employed +across many applications at once. + +So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters. + +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license \ No newline at end of file diff --git a/src/vendor/plugins/render_component/Rakefile b/src/vendor/plugins/render_component/Rakefile new file mode 100644 index 0000000..d99407b --- /dev/null +++ b/src/vendor/plugins/render_component/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the components plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the components plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Components' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end \ No newline at end of file diff --git a/src/vendor/plugins/render_component/init.rb b/src/vendor/plugins/render_component/init.rb new file mode 100644 index 0000000..b428c52 --- /dev/null +++ b/src/vendor/plugins/render_component/init.rb @@ -0,0 +1,2 @@ +require 'components' +ActionController::Base.send :include, Components diff --git a/src/vendor/plugins/render_component/lib/components.rb b/src/vendor/plugins/render_component/lib/components.rb new file mode 100644 index 0000000..9263cde --- /dev/null +++ b/src/vendor/plugins/render_component/lib/components.rb @@ -0,0 +1,141 @@ +module Components + def self.included(base) #:nodoc: + base.class_eval do + include InstanceMethods + extend ClassMethods + helper HelperMethods + + # If this controller was instantiated to process a component request, + # +parent_controller+ points to the instantiator of this controller. + attr_accessor :parent_controller + + alias_method_chain :process_cleanup, :render_component + alias_method_chain :session=, :render_component + alias_method_chain :flash, :render_component + alias_method_chain :perform_action, :render_component + alias_method_chain :send_response, :render_component + + alias_method :component_request?, :parent_controller + end + end + + module ClassMethods + # Track parent controller to identify component requests + def process_with_components(request, response, parent_controller = nil) #:nodoc: + controller = new + controller.parent_controller = parent_controller + controller.process(request, response) + end + end + + module HelperMethods + def render_component(options) + @controller.__send__(:render_component_as_string, options) + end + end + + module InstanceMethods + def perform_action_with_render_component + perform_action_without_render_component + remove_instance_variable(:@_flash) if defined? @_flash + end + + # Extracts the action_name from the request parameters and performs that action. + def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc: + flash.discard if component_request? + process_without_components(request, response, method, *arguments) + end + + def send_response_with_render_component + response.prepare! unless component_request? + response + end + + protected + # Renders the component specified as the response for the current method + def render_component(options) #:doc: + component_logging(options) do + render_for_text(component_response(options, true).body, response.status) + end + end + + # Returns the component response as a string + def render_component_as_string(options) #:doc: + component_logging(options) do + response = component_response(options, false) + + if redirected = response.redirected_to + render_component_as_string(redirected) + else + response.body + end + end + end + + def flash_with_render_component(refresh = false) #:nodoc: + if !defined?(@_flash) || refresh + @_flash = + if defined?(@parent_controller) + @parent_controller.flash + else + flash_without_render_component + end + end + @_flash + end + + private + def component_response(options, reuse_response) + klass = component_class(options) + request = request_for_component(klass.controller_path, options) + new_response = reuse_response ? response : response.dup + + klass.process_with_components(request, new_response, self) + end + + # determine the controller class for the component request + def component_class(options) + if controller = options[:controller] + controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize + else + self.class + end + end + + # Create a new request object based on the current request. + # The new request inherits the session from the current request, + # bypassing any session options set for the component controller's class + def request_for_component(controller_path, options) + new_request = request.dup + new_request.session = request.session + + new_request.instance_variable_set( + :@parameters, + (options[:params] || {}).with_indifferent_access.update( + "controller" => controller_path, "action" => options[:action], "id" => options[:id] + ) + ) + + new_request + end + + def component_logging(options) + if logger + logger.info "Start rendering component (#{options.inspect}): " + result = yield + logger.info "\n\nEnd of component rendering" + result + else + yield + end + end + + def session_with_render_component=(options = {}) + session_without_render_component=(options) unless component_request? + end + + def process_cleanup_with_render_component + process_cleanup_without_render_component unless component_request? + end + end +end diff --git a/src/vendor/plugins/render_component/test/abstract_unit.rb b/src/vendor/plugins/render_component/test/abstract_unit.rb new file mode 100644 index 0000000..f022971 --- /dev/null +++ b/src/vendor/plugins/render_component/test/abstract_unit.rb @@ -0,0 +1,8 @@ +require 'rubygems' +require 'test/unit' +require 'action_controller' +require 'action_controller/test_process' +ActionController::Routing::Routes.reload rescue nil + +$: << File.dirname(__FILE__) + "/../lib" +require File.dirname(__FILE__) + "/../init" diff --git a/src/vendor/plugins/render_component/test/components_test.rb b/src/vendor/plugins/render_component/test/components_test.rb new file mode 100644 index 0000000..53fcd7f --- /dev/null +++ b/src/vendor/plugins/render_component/test/components_test.rb @@ -0,0 +1,136 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class CallerController < ActionController::Base + def calling_from_controller + render_component(:controller => "callee", :action => "being_called") + end + + def calling_from_controller_with_params + render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" }) + end + + def calling_from_controller_with_different_status_code + render_component(:controller => "callee", :action => "blowing_up") + end + + def calling_from_template + render :inline => "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>" + end + + def internal_caller + render :inline => "Are you there? <%= render_component(:action => 'internal_callee') %>" + end + + def internal_callee + render :text => "Yes, ma'am" + end + + def set_flash + render_component(:controller => "callee", :action => "set_flash") + end + + def use_flash + render_component(:controller => "callee", :action => "use_flash") + end + + def calling_redirected + render_component(:controller => "callee", :action => "redirected") + end + + def calling_redirected_as_string + render :inline => "<%= render_component(:controller => 'callee', :action => 'redirected') %>" + end + + def rescue_action(e) raise end +end + +class CalleeController < ActionController::Base + def being_called + render :text => "#{params[:name] || "Lady"} of the House, speaking" + end + + def blowing_up + render :text => "It's game over, man, just game over, man!", :status => 500 + end + + def set_flash + flash[:notice] = 'My stoney baby' + render :text => 'flash is set' + end + + def use_flash + render :text => flash[:notice] || 'no flash' + end + + def redirected + redirect_to :controller => "callee", :action => "being_called" + end + + def rescue_action(e) raise end +end + +class ComponentsTest < ActionController::TestCase + tests CallerController + + def test_calling_from_controller + get :calling_from_controller + assert_equal "Lady of the House, speaking", @response.body + end + + def test_calling_from_controller_with_params + get :calling_from_controller_with_params + assert_equal "David of the House, speaking", @response.body + end + + def test_calling_from_controller_with_different_status_code + get :calling_from_controller_with_different_status_code + assert_equal 500, @response.response_code + end + + def test_calling_from_template + get :calling_from_template + assert_equal "Ring, ring: Lady of the House, speaking", @response.body + end + + def test_etag_is_set_for_parent_template_when_calling_from_template + get :calling_from_template + expected_etag = etag_for("Ring, ring: Lady of the House, speaking") + assert_equal expected_etag, @response.headers['ETag'] + end + + def test_internal_calling + get :internal_caller + assert_equal "Are you there? Yes, ma'am", @response.body + end + + def test_flash + get :set_flash + assert_equal 'My stoney baby', flash[:notice] + get :use_flash + assert_equal 'My stoney baby', @response.body + get :use_flash + assert_equal 'no flash', @response.body + end + + def test_component_redirect_redirects + get :calling_redirected + + assert_redirected_to :controller=>"callee", :action => "being_called" + end + + def test_component_multiple_redirect_redirects + test_component_redirect_redirects + test_internal_calling + end + + def test_component_as_string_redirect_renders_redirected_action + get :calling_redirected_as_string + + assert_equal "Lady of the House, speaking", @response.body + end + + protected + def etag_for(text) + %("#{Digest::MD5.hexdigest(text)}") + end +end -- 1.6.2.5 From jguiditt at redhat.com Mon Jul 20 15:03:56 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Mon, 20 Jul 2009 11:03:56 -0400 Subject: [Ovirt-devel] [PATCH server 6/8] Update will_paginate to latest version. In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248102236.4307.1.camel@lenovo> The file git send-email doesnt like is attached -------------- next part -------------- A non-text attachment was scrubbed... Name: 0006-Update-will_paginate-to-latest-version.patch Type: text/x-patch Size: 63525 bytes Desc: not available URL: From jboggs at redhat.com Mon Jul 20 15:56:25 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 11:56:25 -0400 Subject: [Ovirt-devel] [PATCH server 1/8] Update core app config for Rails 2.3.2 In-Reply-To: <1248101917-7586-2-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-2-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A6493A9.1000708@redhat.com> Jason Guiditta wrote: > This patch updates app config and makes any > required filename changes to support rails 2.3.2. > > Notable changes are: > * application.rb -> application_controller.rb > * cleanup of environment.rb > * new config/initializers dir (this is where much of > the environment.rb stuff went) > * gettext/rails -> gettext_rails > > Also note that applying this patch makes ovirt server > no longer run on Fedora 10, as Rails 2.3.2 is not in that > yum repo. If you wish to run the app on F10 after this > upgrade, you can probably still do so with: > gem update rails > but of course this has the potential to cause issues with > yum and gem conflicting. > > Signed-off-by: Jason Guiditta > Signed-off-by: Jason Guiditta > --- > src/app/controllers/application.rb | 200 -------- > src/app/controllers/application_controller.rb | 198 ++++++++ > src/config.ru | 7 + > src/config/boot.rb | 11 +- > src/config/environment.rb | 38 +-- > src/config/initializers/backtrace_silencers.rb | 7 + > src/config/initializers/inflections.rb | 10 + > src/config/initializers/mime_types.rb | 5 + > src/config/initializers/new_rails_defaults.rb | 19 + > src/config/initializers/session_store.rb | 15 + > src/public/javascripts/controls.js | 136 +++--- > src/public/javascripts/dragdrop.js | 175 ++++---- > src/public/javascripts/effects.js | 130 +++--- > src/public/javascripts/prototype.js | 629 ++++++++++++++---------- > 14 files changed, 861 insertions(+), 719 deletions(-) > delete mode 100644 src/app/controllers/application.rb > create mode 100644 src/app/controllers/application_controller.rb > create mode 100644 src/config.ru > create mode 100644 src/config/initializers/backtrace_silencers.rb > create mode 100644 src/config/initializers/inflections.rb > create mode 100644 src/config/initializers/mime_types.rb > create mode 100644 src/config/initializers/new_rails_defaults.rb > create mode 100644 src/config/initializers/session_store.rb > > diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb > deleted file mode 100644 > index e50f71e..0000000 > --- a/src/app/controllers/application.rb > +++ /dev/null > @@ -1,200 +0,0 @@ > -# > -# Copyright (C) 2008 Red Hat, Inc. > -# Written by Scott Seago > -# > -# 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. > - > -# Filters added to this controller apply to all controllers in the application. > -# Likewise, all the methods added will be available for all controllers. > - > - > -class ApplicationController < ActionController::Base > - # FIXME: once all controller classes include this, remove here > - include ApplicationService > - > - # Pick a unique cookie name to distinguish our session data from others' > - session :session_key => '_ovirt_session_id' > - init_gettext "ovirt" > - layout :choose_layout > - > - before_filter :is_logged_in, :get_help_section > - > - # General error handlers, must be in order from least specific > - # to most specific > - rescue_from Exception, :with => :handle_general_error > - rescue_from PermissionError, :with => :handle_perm_error > - rescue_from ActionError, :with => :handle_action_error > - rescue_from PartialSuccessError, :with => :handle_partial_success_error > - > - def choose_layout > - if(params[:component_layout]) > - return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux' > - end > - return 'redux' > - end > - > - def is_logged_in > - redirect_to(:controller => "/login", :action => "login") unless get_login_user > - end > - > - def get_help_section > - help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ]) > - @help_section = help ? help.section : "" > - if @help_section.index('#') > - help_sections = @help_section.split('#') > - @help_section = help_sections[0] > - @anchor = help_sections[1] > - else > - @help_section = @help_section > - @anchor = "" > - end > - end > - > - def get_login_user > - (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" > - end > - > - protected > - # permissions checking > - > - def handle_perm_error(error) > - handle_error(:error => error, :status => :forbidden, > - :title => "Access denied") > - end > - > - def handle_partial_success_error(error) > - failures_arr = error.failures.collect do |resource, reason| > - if resource.respond_to?(:display_name) > - resource.display_name + ": " + reason > - else > - reason > - end > - end > - @successes = error.successes > - @failures = error.failures > - handle_error(:error => error, :status => :ok, > - :message => error.message + ": " + failures_arr.join(", "), > - :title => "Some actions failed") > - end > - > - def handle_action_error(error) > - handle_error(:error => error, :status => :conflict, > - :title => "Action Error") > - end > - > - def handle_general_error(error) > - flash[:errmsg] = error.message > - handle_error(:error => error, :status => :internal_server_error, > - :title => "Internal Server Error") > - end > - > - def handle_error(hash) > - log_error(hash[:error]) if hash[:error] > - msg = hash[:message] || hash[:error].message > - title = hash[:title] || "Internal Server Error" > - status = hash[:status] || :internal_server_error > - respond_to do |format| > - format.html { html_error_page(title, msg) } > - format.json { render :json => json_error_hash(msg, status) } > - format.xml { render :xml => xml_errors(msg), :status => status } > - end > - end > - > - def html_error_page(title, msg) > - @title = title > - @errmsg = msg > - @ajax = params[:ajax] > - @nolayout = params[:nolayout] > - if @layout > - render :layout => @layout > - elsif @ajax > - render :template => 'layouts/popup-error', :layout => 'tabs-and-content' > - elsif @nolayout > - render :template => 'layouts/popup-error', :layout => 'help-and-content' > - else > - render :template => 'layouts/popup-error', :layout => 'popup' > - end > - end > - > - # don't define find_opts for array inputs > - def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) > - page = params[:page].to_i > - paginate_opts = {:page => page, > - :order => "#{params[:sortname]} #{params[:sortorder]}", > - :per_page => params[:rp]} > - arg_list << find_opts.merge(paginate_opts) > - item_list = full_items.paginate(*arg_list) > - json_hash = {} > - json_hash[:page] = page > - json_hash[:total] = item_list.total_entries > - json_hash[:rows] = item_list.collect do |item| > - item_hash = {} > - item_hash[:id] = item.send(id_method) > - item_hash[:cell] = attributes.collect do |attr| > - if attr.is_a? Array > - value = item > - attr.each { |attr_item| value = (value.nil? ? nil : value.send(attr_item))} > - value > - else > - item.send(attr) > - end > - end > - item_hash > - end > - json_hash > - end > - > - # json_list is a helper method used to format data for paginated flexigrid tables > - # > - # FIXME: what is the intent of this comment? don't define find_opts for array inputs > - def json_list(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) > - render :json => json_hash(full_items, attributes, arg_list, find_opts, id_method).to_json > - end > - > - private > - def json_error_hash(msg, status) > - json = {} > - json[:success] = (status == :ok) > - json.merge!(instance_errors) > - # There's a potential issue here: if we add :errors for an object > - # that the view won't generate inline error messages for, the user > - # won't get any indication what the error is. But if we set :alert > - # unconditionally, the user will get validation errors twice: once > - # inline in the form, and once in the flash > - json[:alert] = msg unless json[:errors] > - return json > - end > - > - def xml_errors(msg) > - xml = {} > - xml[:message] = msg > - xml.merge!(instance_errors) > - return xml > - end > - > - def instance_errors > - hash = {} > - instance_variables.each do |ivar| > - val = instance_variable_get(ivar) > - if val && val.respond_to?(:errors) && val.errors.size > 0 > - hash[:object] = ivar[1, ivar.size] > - hash[:errors] ||= [] > - hash[:errors] += val.errors.localize_error_messages.to_a > - end > - end > - return hash > - end > -end > diff --git a/src/app/controllers/application_controller.rb b/src/app/controllers/application_controller.rb > new file mode 100644 > index 0000000..5ce625a > --- /dev/null > +++ b/src/app/controllers/application_controller.rb > @@ -0,0 +1,198 @@ > +# > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Scott Seago > +# > +# 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. > + > +# Filters added to this controller apply to all controllers in the application. > +# Likewise, all the methods added will be available for all controllers. > + > + > +class ApplicationController < ActionController::Base > + # FIXME: once all controller classes include this, remove here > + include ApplicationService > + > + init_gettext "ovirt" > + layout :choose_layout > + > + before_filter :is_logged_in, :get_help_section > + > + # General error handlers, must be in order from least specific > + # to most specific > + rescue_from Exception, :with => :handle_general_error > + rescue_from PermissionError, :with => :handle_perm_error > + rescue_from ActionError, :with => :handle_action_error > + rescue_from PartialSuccessError, :with => :handle_partial_success_error > + > + def choose_layout > + if(params[:component_layout]) > + return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux' > + end > + return 'redux' > + end > + > + def is_logged_in > + redirect_to(:controller => "/login", :action => "login") unless get_login_user > + end > + > + def get_help_section > + help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ]) > + @help_section = help ? help.section : "" > + if @help_section.index('#') > + help_sections = @help_section.split('#') > + @help_section = help_sections[0] > + @anchor = help_sections[1] > + else > + @help_section = @help_section > + @anchor = "" > + end > + end > + > + def get_login_user > + (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" > + end > + > + protected > + # permissions checking > + > + def handle_perm_error(error) > + handle_error(:error => error, :status => :forbidden, > + :title => "Access denied") > + end > + > + def handle_partial_success_error(error) > + failures_arr = error.failures.collect do |resource, reason| > + if resource.respond_to?(:display_name) > + resource.display_name + ": " + reason > + else > + reason > + end > + end > + @successes = error.successes > + @failures = error.failures > + handle_error(:error => error, :status => :ok, > + :message => error.message + ": " + failures_arr.join(", "), > + :title => "Some actions failed") > + end > + > + def handle_action_error(error) > + handle_error(:error => error, :status => :conflict, > + :title => "Action Error") > + end > + > + def handle_general_error(error) > + flash[:errmsg] = error.message > + handle_error(:error => error, :status => :internal_server_error, > + :title => "Internal Server Error") > + end > + > + def handle_error(hash) > + log_error(hash[:error]) if hash[:error] > + msg = hash[:message] || hash[:error].message > + title = hash[:title] || "Internal Server Error" > + status = hash[:status] || :internal_server_error > + respond_to do |format| > + format.html { html_error_page(title, msg) } > + format.json { render :json => json_error_hash(msg, status) } > + format.xml { render :xml => xml_errors(msg), :status => status } > + end > + end > + > + def html_error_page(title, msg) > + @title = title > + @errmsg = msg > + @ajax = params[:ajax] > + @nolayout = params[:nolayout] > + if @layout > + render :layout => @layout > + elsif @ajax > + render :template => 'layouts/popup-error', :layout => 'tabs-and-content' > + elsif @nolayout > + render :template => 'layouts/popup-error', :layout => 'help-and-content' > + else > + render :template => 'layouts/popup-error', :layout => 'popup' > + end > + end > + > + # don't define find_opts for array inputs > + def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) > + page = params[:page].to_i > + paginate_opts = {:page => page, > + :order => "#{params[:sortname]} #{params[:sortorder]}", > + :per_page => params[:rp]} > + arg_list << find_opts.merge(paginate_opts) > + item_list = full_items.paginate(*arg_list) > + json_hash = {} > + json_hash[:page] = page > + json_hash[:total] = item_list.total_entries > + json_hash[:rows] = item_list.collect do |item| > + item_hash = {} > + item_hash[:id] = item.send(id_method) > + item_hash[:cell] = attributes.collect do |attr| > + if attr.is_a? Array > + value = item > + attr.each { |attr_item| value = (value.nil? ? nil : value.send(attr_item))} > + value > + else > + item.send(attr) > + end > + end > + item_hash > + end > + json_hash > + end > + > + # json_list is a helper method used to format data for paginated flexigrid tables > + # > + # FIXME: what is the intent of this comment? don't define find_opts for array inputs > + def json_list(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) > + render :json => json_hash(full_items, attributes, arg_list, find_opts, id_method).to_json > + end > + > + private > + def json_error_hash(msg, status) > + json = {} > + json[:success] = (status == :ok) > + json.merge!(instance_errors) > + # There's a potential issue here: if we add :errors for an object > + # that the view won't generate inline error messages for, the user > + # won't get any indication what the error is. But if we set :alert > + # unconditionally, the user will get validation errors twice: once > + # inline in the form, and once in the flash > + json[:alert] = msg unless json[:errors] > + return json > + end > + > + def xml_errors(msg) > + xml = {} > + xml[:message] = msg > + xml.merge!(instance_errors) > + return xml > + end > + > + def instance_errors > + hash = {} > + instance_variables.each do |ivar| > + val = instance_variable_get(ivar) > + if val && val.respond_to?(:errors) && val.errors.size > 0 > + hash[:object] = ivar[1, ivar.size] > + hash[:errors] ||= [] > + hash[:errors] += val.errors.localize_error_messages.to_a > + end > + end > + return hash > + end > +end > diff --git a/src/config.ru b/src/config.ru > new file mode 100644 > index 0000000..acbfe4e > --- /dev/null > +++ b/src/config.ru > @@ -0,0 +1,7 @@ > +# Rack Dispatcher > + > +# Require your environment file to bootstrap Rails > +require File.dirname(__FILE__) + '/config/environment' > + > +# Dispatch the request > +run ActionController::Dispatcher.new > diff --git a/src/config/boot.rb b/src/config/boot.rb > index cd21fb9..0ad0f78 100644 > --- a/src/config/boot.rb > +++ b/src/config/boot.rb > @@ -44,6 +44,7 @@ module Rails > def load_initializer > require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" > Rails::Initializer.run(:install_gem_spec_stubs) > + Rails::GemDependency.add_frozen_gem_path > end > end > > @@ -67,7 +68,7 @@ module Rails > > class << self > def rubygems_version > - Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion > + Gem::RubyGemsVersion rescue nil > end > > def gem_version > @@ -82,14 +83,14 @@ module Rails > > def load_rubygems > require 'rubygems' > - > - unless rubygems_version >= '0.9.4' > - $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) > + min_version = '1.3.1' > + unless rubygems_version >= min_version > + $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) > exit 1 > end > > rescue LoadError > - $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) > + $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) > exit 1 > end > > diff --git a/src/config/environment.rb b/src/config/environment.rb > index 98a2fbb..9d9a66d 100644 > --- a/src/config/environment.rb > +++ b/src/config/environment.rb > @@ -19,9 +19,8 @@ > > # Be sure to restart your web server when you modify this file. > > -# Uncomment below to force Rails into production mode when > -# you don't control web/app server and can't set it the proper way > -# ENV['RAILS_ENV'] ||= 'production' > +# Specifies gem version of Rails to use when vendor/rails is not present > +RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION > > # Bootstrap the Rails environment, frameworks, and default configuration > require File.join(File.dirname(__FILE__), 'boot') > @@ -42,7 +41,7 @@ Rails::Initializer.run do |config| > # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" > # config.gem "aws-s3", :lib => "aws/s3" > config.gem "cobbler" > - config.gem "gettext", :lib => "gettext/rails" > + config.gem "gettext", :lib => "gettext_rails" > > # Only load the plugins named here, in the order given. By default, all plugins > # in vendor/plugins are loaded in alphabetical order. > @@ -61,20 +60,6 @@ Rails::Initializer.run do |config| > # Run "rake -D time" for a list of tasks for finding time zone names. Uncomment to use default local time. > config.time_zone = 'UTC' > > - # Your secret key for verifying cookie session data integrity. > - # If you change this key, all old sessions will become invalid! > - # Make sure the secret is at least 30 characters and all random, > - # no regular words or you'll be exposed to dictionary attacks. > - config.action_controller.session = { > - :session_key => "_ovirt_session_id", > - :secret => "a covert ovirt phrase or some such" > - } > - > - # Use the database for sessions instead of the cookie-based default, > - # which shouldn't be used to store highly confidential information > - # (create the session table with "rake db:sessions:create") > - config.action_controller.session_store = :active_record_store > - > # Use SQL instead of Active Record's schema dumper when creating the test database. > # This is necessary if your schema can't be completely dumped by the schema dumper, > # like if you have constraints or database-specific column types > @@ -83,17 +68,8 @@ Rails::Initializer.run do |config| > # Activate observers that should always be running > # config.active_record.observers = :cacher, :garbage_collector > config.active_record.observers = :host_observer, :vm_observer > -end > > -# Add new inflection rules using the following format > -# (all these examples are active by default): > -# Inflector.inflections do |inflect| > -# inflect.plural /^(ox)$/i, '\1en' > -# inflect.singular /^(ox)en/i, '\1' > -# inflect.irregular 'person', 'people' > -# inflect.uncountable %w( fish sheep ) > -# end > - > -# Add new mime types for use in respond_to blocks: > -# Mime::Type.register "text/richtext", :rtf > -# Mime::Type.register "application/x-mobile", :mobile > + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. > + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] > + # config.i18n.default_locale = :de > +end > diff --git a/src/config/initializers/backtrace_silencers.rb b/src/config/initializers/backtrace_silencers.rb > new file mode 100644 > index 0000000..c2169ed > --- /dev/null > +++ b/src/config/initializers/backtrace_silencers.rb > @@ -0,0 +1,7 @@ > +# Be sure to restart your server when you modify this file. > + > +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. > +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } > + > +# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. > +# Rails.backtrace_cleaner.remove_silencers! > \ No newline at end of file > diff --git a/src/config/initializers/inflections.rb b/src/config/initializers/inflections.rb > new file mode 100644 > index 0000000..9e8b013 > --- /dev/null > +++ b/src/config/initializers/inflections.rb > @@ -0,0 +1,10 @@ > +# Be sure to restart your server when you modify this file. > + > +# Add new inflection rules using the following format > +# (all these examples are active by default): > +# ActiveSupport::Inflector.inflections do |inflect| > +# inflect.plural /^(ox)$/i, '\1en' > +# inflect.singular /^(ox)en/i, '\1' > +# inflect.irregular 'person', 'people' > +# inflect.uncountable %w( fish sheep ) > +# end > diff --git a/src/config/initializers/mime_types.rb b/src/config/initializers/mime_types.rb > new file mode 100644 > index 0000000..72aca7e > --- /dev/null > +++ b/src/config/initializers/mime_types.rb > @@ -0,0 +1,5 @@ > +# Be sure to restart your server when you modify this file. > + > +# Add new mime types for use in respond_to blocks: > +# Mime::Type.register "text/richtext", :rtf > +# Mime::Type.register_alias "text/html", :iphone > diff --git a/src/config/initializers/new_rails_defaults.rb b/src/config/initializers/new_rails_defaults.rb > new file mode 100644 > index 0000000..8ec3186 > --- /dev/null > +++ b/src/config/initializers/new_rails_defaults.rb > @@ -0,0 +1,19 @@ > +# Be sure to restart your server when you modify this file. > + > +# These settings change the behavior of Rails 2 apps and will be defaults > +# for Rails 3. You can remove this initializer when Rails 3 is released. > + > +if defined?(ActiveRecord) > + # Include Active Record class name as root for JSON serialized output. > + ActiveRecord::Base.include_root_in_json = true > + > + # Store the full class name (including module namespace) in STI type column. > + ActiveRecord::Base.store_full_sti_class = true > +end > + > +# Use ISO 8601 format for JSON serialized times and dates. > +ActiveSupport.use_standard_json_time_format = true > + > +# Don't escape HTML entities in JSON, leave that for the #json_escape helper. > +# if you're including raw json in an HTML page. > +ActiveSupport.escape_html_entities_in_json = false > \ No newline at end of file > diff --git a/src/config/initializers/session_store.rb b/src/config/initializers/session_store.rb > new file mode 100644 > index 0000000..64dbf51 > --- /dev/null > +++ b/src/config/initializers/session_store.rb > @@ -0,0 +1,15 @@ > +# Be sure to restart your server when you modify this file. > + > +# Your secret key for verifying cookie session data integrity. > +# If you change this key, all old sessions will become invalid! > +# Make sure the secret is at least 30 characters and all random, > +# no regular words or you'll be exposed to dictionary attacks. > +ActionController::Base.session = { > + :key => '_rails23-app_session', > + :secret => '41713a6b4a92b5b7af55314d2ef6fc499a177269ea91b9fdaa7d15c42e1234b70b32f52278ae26b774b38dbdfeb7d078585d10f643e81b6615d32410f192f1de' > +} > + > +# Use the database for sessions instead of the cookie-based default, > +# which shouldn't be used to store highly confidential information > +# (create the session table with "rake db:sessions:create") > +ActionController::Base.session_store = :active_record_store > diff --git a/src/public/javascripts/controls.js b/src/public/javascripts/controls.js > index 1de3b29..ca29aef 100644 > --- a/src/public/javascripts/controls.js > +++ b/src/public/javascripts/controls.js > @@ -1,22 +1,22 @@ > // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) > -// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) > -// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) > +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) > +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) > // Contributors: > // Richard Livsey > // Rahul Bhargava > // Rob Wills > -// > +// > // script.aculo.us is freely distributable under the terms of an MIT-style license. > // For details, see the script.aculo.us web site: http://script.aculo.us/ > > -// Autocompleter.Base handles all the autocompletion functionality > +// Autocompleter.Base handles all the autocompletion functionality > // that's independent of the data source for autocompletion. This > // includes drawing the autocompletion menu, observing keyboard > // and mouse events, and similar. > // > -// Specific autocompleters need to provide, at the very least, > +// Specific autocompleters need to provide, at the very least, > // a getUpdatedChoices function that will be invoked every time > -// the text inside the monitored textbox changes. This method > +// the text inside the monitored textbox changes. This method > // should get the text for which to provide autocompletion by > // invoking this.getToken(), NOT by directly accessing > // this.element.value. This is to allow incremental tokenized > @@ -30,23 +30,23 @@ > // will incrementally autocomplete with a comma as the token. > // Additionally, ',' in the above example can be replaced with > // a token array, e.g. { tokens: [',', '\n'] } which > -// enables autocompletion on multiple tokens. This is most > -// useful when one of the tokens is \n (a newline), as it > +// enables autocompletion on multiple tokens. This is most > +// useful when one of the tokens is \n (a newline), as it > // allows smart autocompletion after linebreaks. > > if(typeof Effect == 'undefined') > throw("controls.js requires including script.aculo.us' effects.js library"); > > -var Autocompleter = { } > +var Autocompleter = { }; > Autocompleter.Base = Class.create({ > baseInitialize: function(element, update, options) { > - element = $(element) > + element = $(element); > this.element = element; > - this.update = $(update); > - this.hasFocus = false; > - this.changed = false; > - this.active = false; > - this.index = 0; > + this.update = $(update); > + this.hasFocus = false; > + this.changed = false; > + this.active = false; > + this.index = 0; > this.entryCount = 0; > this.oldElementValue = this.element.value; > > @@ -59,28 +59,28 @@ Autocompleter.Base = Class.create({ > this.options.tokens = this.options.tokens || []; > this.options.frequency = this.options.frequency || 0.4; > this.options.minChars = this.options.minChars || 1; > - this.options.onShow = this.options.onShow || > - function(element, update){ > + this.options.onShow = this.options.onShow || > + function(element, update){ > if(!update.style.position || update.style.position=='absolute') { > update.style.position = 'absolute'; > Position.clone(element, update, { > - setHeight: false, > + setHeight: false, > offsetTop: element.offsetHeight > }); > } > Effect.Appear(update,{duration:0.15}); > }; > - this.options.onHide = this.options.onHide || > + this.options.onHide = this.options.onHide || > function(element, update){ new Effect.Fade(update,{duration:0.15}) }; > > - if(typeof(this.options.tokens) == 'string') > + if(typeof(this.options.tokens) == 'string') > this.options.tokens = new Array(this.options.tokens); > // Force carriage returns as token delimiters anyway > if (!this.options.tokens.include('\n')) > this.options.tokens.push('\n'); > > this.observer = null; > - > + > this.element.setAttribute('autocomplete','off'); > > Element.hide(this.update); > @@ -91,10 +91,10 @@ Autocompleter.Base = Class.create({ > > show: function() { > if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); > - if(!this.iefix && > + if(!this.iefix && > (Prototype.Browser.IE) && > (Element.getStyle(this.update, 'position')=='absolute')) { > - new Insertion.After(this.update, > + new Insertion.After(this.update, > ''); > @@ -102,7 +102,7 @@ Autocompleter.Base = Class.create({ > } > if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); > }, > - > + > fixIEOverlapping: function() { > Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); > this.iefix.style.zIndex = 1; > @@ -150,15 +150,15 @@ Autocompleter.Base = Class.create({ > Event.stop(event); > return; > } > - else > - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || > + else > + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || > (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; > > this.changed = true; > this.hasFocus = true; > > if(this.observer) clearTimeout(this.observer); > - this.observer = > + this.observer = > setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); > }, > > @@ -170,35 +170,35 @@ Autocompleter.Base = Class.create({ > > onHover: function(event) { > var element = Event.findElement(event, 'LI'); > - if(this.index != element.autocompleteIndex) > + if(this.index != element.autocompleteIndex) > { > this.index = element.autocompleteIndex; > this.render(); > } > Event.stop(event); > }, > - > + > onClick: function(event) { > var element = Event.findElement(event, 'LI'); > this.index = element.autocompleteIndex; > this.selectEntry(); > this.hide(); > }, > - > + > onBlur: function(event) { > // needed to make click events working > setTimeout(this.hide.bind(this), 250); > this.hasFocus = false; > - this.active = false; > - }, > - > + this.active = false; > + }, > + > render: function() { > if(this.entryCount > 0) { > for (var i = 0; i < this.entryCount; i++) > - this.index==i ? > - Element.addClassName(this.getEntry(i),"selected") : > + this.index==i ? > + Element.addClassName(this.getEntry(i),"selected") : > Element.removeClassName(this.getEntry(i),"selected"); > - if(this.hasFocus) { > + if(this.hasFocus) { > this.show(); > this.active = true; > } > @@ -207,27 +207,27 @@ Autocompleter.Base = Class.create({ > this.hide(); > } > }, > - > + > markPrevious: function() { > - if(this.index > 0) this.index-- > + if(this.index > 0) this.index--; > else this.index = this.entryCount-1; > this.getEntry(this.index).scrollIntoView(true); > }, > - > + > markNext: function() { > - if(this.index < this.entryCount-1) this.index++ > + if(this.index < this.entryCount-1) this.index++; > else this.index = 0; > this.getEntry(this.index).scrollIntoView(false); > }, > - > + > getEntry: function(index) { > return this.update.firstChild.childNodes[index]; > }, > - > + > getCurrentEntry: function() { > return this.getEntry(this.index); > }, > - > + > selectEntry: function() { > this.active = false; > this.updateElement(this.getCurrentEntry()); > @@ -244,7 +244,7 @@ Autocompleter.Base = Class.create({ > if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); > } else > value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); > - > + > var bounds = this.getTokenBounds(); > if (bounds[0] != -1) { > var newValue = this.element.value.substr(0, bounds[0]); > @@ -257,7 +257,7 @@ Autocompleter.Base = Class.create({ > } > this.oldElementValue = this.element.value; > this.element.focus(); > - > + > if (this.options.afterUpdateElement) > this.options.afterUpdateElement(this.element, selectedElement); > }, > @@ -269,20 +269,20 @@ Autocompleter.Base = Class.create({ > Element.cleanWhitespace(this.update.down()); > > if(this.update.firstChild && this.update.down().childNodes) { > - this.entryCount = > + this.entryCount = > this.update.down().childNodes.length; > for (var i = 0; i < this.entryCount; i++) { > var entry = this.getEntry(i); > entry.autocompleteIndex = i; > this.addObservers(entry); > } > - } else { > + } else { > this.entryCount = 0; > } > > this.stopIndicator(); > this.index = 0; > - > + > if(this.entryCount==1 && this.options.autoSelect) { > this.selectEntry(); > this.hide(); > @@ -298,7 +298,7 @@ Autocompleter.Base = Class.create({ > }, > > onObserverEvent: function() { > - this.changed = false; > + this.changed = false; > this.tokenBounds = null; > if(this.getToken().length>=this.options.minChars) { > this.getUpdatedChoices(); > @@ -358,7 +358,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { > this.options.parameters = this.options.callback ? > this.options.callback(this.element, entry) : entry; > > - if(this.options.defaultParams) > + if(this.options.defaultParams) > this.options.parameters += '&' + this.options.defaultParams; > > new Ajax.Request(this.url, this.options); > @@ -382,7 +382,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { > // - choices - How many autocompletion choices to offer > // > // - partialSearch - If false, the autocompleter will match entered > -// text only at the beginning of strings in the > +// text only at the beginning of strings in the > // autocomplete array. Defaults to true, which will > // match text at the beginning of any *word* in the > // strings in the autocomplete array. If you want to > @@ -399,7 +399,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { > // - ignoreCase - Whether to ignore case when autocompleting. > // Defaults to true. > // > -// It's possible to pass in a custom function as the 'selector' > +// It's possible to pass in a custom function as the 'selector' > // option, if you prefer to write your own autocompletion logic. > // In that case, the other options above will not apply unless > // you support them. > @@ -427,20 +427,20 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { > var entry = instance.getToken(); > var count = 0; > > - for (var i = 0; i < instance.options.array.length && > - ret.length < instance.options.choices ; i++) { > + for (var i = 0; i < instance.options.array.length && > + ret.length < instance.options.choices ; i++) { > > var elem = instance.options.array[i]; > - var foundPos = instance.options.ignoreCase ? > - elem.toLowerCase().indexOf(entry.toLowerCase()) : > + var foundPos = instance.options.ignoreCase ? > + elem.toLowerCase().indexOf(entry.toLowerCase()) : > elem.indexOf(entry); > > while (foundPos != -1) { > - if (foundPos == 0 && elem.length != entry.length) { > - ret.push("
  • " + elem.substr(0, entry.length) + "" + > + if (foundPos == 0 && elem.length != entry.length) { > + ret.push("
  • " + elem.substr(0, entry.length) + "" + > elem.substr(entry.length) + "
  • "); > break; > - } else if (entry.length >= instance.options.partialChars && > + } else if (entry.length >= instance.options.partialChars && > instance.options.partialSearch && foundPos != -1) { > if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { > partial.push("
  • " + elem.substr(0, foundPos) + "" + > @@ -450,14 +450,14 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { > } > } > > - foundPos = instance.options.ignoreCase ? > - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : > + foundPos = instance.options.ignoreCase ? > + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : > elem.indexOf(entry, foundPos + 1); > > } > } > if (partial.length) > - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) > + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); > return "
      " + ret.join('') + "
    "; > } > }, options || { }); > @@ -474,7 +474,7 @@ Field.scrollFreeActivate = function(field) { > setTimeout(function() { > Field.activate(field); > }, 1); > -} > +}; > > Ajax.InPlaceEditor = Class.create({ > initialize: function(element, url, options) { > @@ -604,7 +604,7 @@ Ajax.InPlaceEditor = Class.create({ > this.triggerCallback('onEnterHover'); > }, > getText: function() { > - return this.element.innerHTML; > + return this.element.innerHTML.unescapeHTML(); > }, > handleAJAXFailure: function(transport) { > this.triggerCallback('onFailure', transport); > @@ -780,7 +780,7 @@ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { > onSuccess: function(transport) { > var js = transport.responseText.strip(); > if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check > - throw 'Server returned an invalid collection representation.'; > + throw('Server returned an invalid collection representation.'); > this._collection = eval(js); > this.checkForExternalText(); > }.bind(this), > @@ -937,7 +937,7 @@ Ajax.InPlaceCollectionEditor.DefaultOptions = { > loadingCollectionText: 'Loading options...' > }; > > -// Delayed observer, like Form.Element.Observer, > +// Delayed observer, like Form.Element.Observer, > // but waits for delay after last key input > // Ideal for live-search fields > > @@ -947,7 +947,7 @@ Form.Element.DelayedObserver = Class.create({ > this.element = $(element); > this.callback = callback; > this.timer = null; > - this.lastValue = $F(this.element); > + this.lastValue = $F(this.element); > Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); > }, > delayedListener: function(event) { > @@ -960,4 +960,4 @@ Form.Element.DelayedObserver = Class.create({ > this.timer = null; > this.callback(this.element, $F(this.element)); > } > -}); > +}); > \ No newline at end of file > diff --git a/src/public/javascripts/dragdrop.js b/src/public/javascripts/dragdrop.js > index e2e7d4a..07229f9 100644 > --- a/src/public/javascripts/dragdrop.js > +++ b/src/public/javascripts/dragdrop.js > @@ -1,6 +1,6 @@ > // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) > -// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi at oriontransfer.co.nz) > -// > +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi at oriontransfer.co.nz) > +// > // script.aculo.us is freely distributable under the terms of an MIT-style license. > // For details, see the script.aculo.us web site: http://script.aculo.us/ > > @@ -32,7 +32,7 @@ var Droppables = { > options._containers.push($(containment)); > } > } > - > + > if(options.accept) options.accept = [options.accept].flatten(); > > Element.makePositioned(element); // fix IE > @@ -40,34 +40,34 @@ var Droppables = { > > this.drops.push(options); > }, > - > + > findDeepestChild: function(drops) { > deepest = drops[0]; > - > + > for (i = 1; i < drops.length; ++i) > if (Element.isParent(drops[i].element, deepest.element)) > deepest = drops[i]; > - > + > return deepest; > }, > > isContained: function(element, drop) { > var containmentNode; > if(drop.tree) { > - containmentNode = element.treeNode; > + containmentNode = element.treeNode; > } else { > containmentNode = element.parentNode; > } > return drop._containers.detect(function(c) { return containmentNode == c }); > }, > - > + > isAffected: function(point, element, drop) { > return ( > (drop.element!=element) && > ((!drop._containers) || > this.isContained(element, drop)) && > ((!drop.accept) || > - (Element.classNames(element).detect( > + (Element.classNames(element).detect( > function(v) { return drop.accept.include(v) } ) )) && > Position.within(drop.element, point[0], point[1]) ); > }, > @@ -87,12 +87,12 @@ var Droppables = { > show: function(point, element) { > if(!this.drops.length) return; > var drop, affected = []; > - > + > this.drops.each( function(drop) { > if(Droppables.isAffected(point, element, drop)) > affected.push(drop); > }); > - > + > if(affected.length>0) > drop = Droppables.findDeepestChild(affected); > > @@ -101,7 +101,7 @@ var Droppables = { > Position.within(drop.element, point[0], point[1]); > if(drop.onHover) > drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); > - > + > if (drop != this.last_active) Droppables.activate(drop); > } > }, > @@ -121,25 +121,25 @@ var Droppables = { > if(this.last_active) > this.deactivate(this.last_active); > } > -} > +}; > > var Draggables = { > drags: [], > observers: [], > - > + > register: function(draggable) { > if(this.drags.length == 0) { > this.eventMouseUp = this.endDrag.bindAsEventListener(this); > this.eventMouseMove = this.updateDrag.bindAsEventListener(this); > this.eventKeypress = this.keyPress.bindAsEventListener(this); > - > + > Event.observe(document, "mouseup", this.eventMouseUp); > Event.observe(document, "mousemove", this.eventMouseMove); > Event.observe(document, "keypress", this.eventKeypress); > } > this.drags.push(draggable); > }, > - > + > unregister: function(draggable) { > this.drags = this.drags.reject(function(d) { return d==draggable }); > if(this.drags.length == 0) { > @@ -148,24 +148,24 @@ var Draggables = { > Event.stopObserving(document, "keypress", this.eventKeypress); > } > }, > - > + > activate: function(draggable) { > - if(draggable.options.delay) { > - this._timeout = setTimeout(function() { > - Draggables._timeout = null; > - window.focus(); > - Draggables.activeDraggable = draggable; > - }.bind(this), draggable.options.delay); > + if(draggable.options.delay) { > + this._timeout = setTimeout(function() { > + Draggables._timeout = null; > + window.focus(); > + Draggables.activeDraggable = draggable; > + }.bind(this), draggable.options.delay); > } else { > window.focus(); // allows keypress events if window isn't currently focused, fails for Safari > this.activeDraggable = draggable; > } > }, > - > + > deactivate: function() { > this.activeDraggable = null; > }, > - > + > updateDrag: function(event) { > if(!this.activeDraggable) return; > var pointer = [Event.pointerX(event), Event.pointerY(event)]; > @@ -173,36 +173,36 @@ var Draggables = { > // the same coordinates, prevent needless redrawing (moz bug?) > if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; > this._lastPointer = pointer; > - > + > this.activeDraggable.updateDrag(event, pointer); > }, > - > + > endDrag: function(event) { > - if(this._timeout) { > - clearTimeout(this._timeout); > - this._timeout = null; > + if(this._timeout) { > + clearTimeout(this._timeout); > + this._timeout = null; > } > if(!this.activeDraggable) return; > this._lastPointer = null; > this.activeDraggable.endDrag(event); > this.activeDraggable = null; > }, > - > + > keyPress: function(event) { > if(this.activeDraggable) > this.activeDraggable.keyPress(event); > }, > - > + > addObserver: function(observer) { > this.observers.push(observer); > this._cacheObserverCallbacks(); > }, > - > + > removeObserver: function(element) { // element instead of observer fixes mem leaks > this.observers = this.observers.reject( function(o) { return o.element==element }); > this._cacheObserverCallbacks(); > }, > - > + > notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' > if(this[eventName+'Count'] > 0) > this.observers.each( function(o) { > @@ -210,7 +210,7 @@ var Draggables = { > }); > if(draggable.options[eventName]) draggable.options[eventName](draggable, event); > }, > - > + > _cacheObserverCallbacks: function() { > ['onStart','onEnd','onDrag'].each( function(eventName) { > Draggables[eventName+'Count'] = Draggables.observers.select( > @@ -218,7 +218,7 @@ var Draggables = { > ).length; > }); > } > -} > +}; > > /*--------------------------------------------------------------------------*/ > > @@ -234,12 +234,12 @@ var Draggable = Class.create({ > }, > endeffect: function(element) { > var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; > - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, > + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, > queue: {scope:'_draggable', position:'end'}, > - afterFinish: function(){ > - Draggable._dragging[element] = false > + afterFinish: function(){ > + Draggable._dragging[element] = false > } > - }); > + }); > }, > zindex: 1000, > revert: false, > @@ -250,57 +250,57 @@ var Draggable = Class.create({ > snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } > delay: 0 > }; > - > + > if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) > Object.extend(defaults, { > starteffect: function(element) { > element._opacity = Element.getOpacity(element); > Draggable._dragging[element] = true; > - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); > + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); > } > }); > - > + > var options = Object.extend(defaults, arguments[1] || { }); > > this.element = $(element); > - > + > if(options.handle && Object.isString(options.handle)) > this.handle = this.element.down('.'+options.handle, 0); > - > + > if(!this.handle) this.handle = $(options.handle); > if(!this.handle) this.handle = this.element; > - > + > if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { > options.scroll = $(options.scroll); > this._isScrollChild = Element.childOf(this.element, options.scroll); > } > > - Element.makePositioned(this.element); // fix IE > + Element.makePositioned(this.element); // fix IE > > this.options = options; > - this.dragging = false; > + this.dragging = false; > > this.eventMouseDown = this.initDrag.bindAsEventListener(this); > Event.observe(this.handle, "mousedown", this.eventMouseDown); > - > + > Draggables.register(this); > }, > - > + > destroy: function() { > Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); > Draggables.unregister(this); > }, > - > + > currentDelta: function() { > return([ > parseInt(Element.getStyle(this.element,'left') || '0'), > parseInt(Element.getStyle(this.element,'top') || '0')]); > }, > - > + > initDrag: function(event) { > if(!Object.isUndefined(Draggable._dragging[this.element]) && > Draggable._dragging[this.element]) return; > - if(Event.isLeftClick(event)) { > + if(Event.isLeftClick(event)) { > // abort on form elements, fixes a Firefox issue > var src = Event.element(event); > if((tag_name = src.tagName.toUpperCase()) && ( > @@ -309,34 +309,34 @@ var Draggable = Class.create({ > tag_name=='OPTION' || > tag_name=='BUTTON' || > tag_name=='TEXTAREA')) return; > - > + > var pointer = [Event.pointerX(event), Event.pointerY(event)]; > var pos = Position.cumulativeOffset(this.element); > this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); > - > + > Draggables.activate(this); > Event.stop(event); > } > }, > - > + > startDrag: function(event) { > this.dragging = true; > if(!this.delta) > this.delta = this.currentDelta(); > - > + > if(this.options.zindex) { > this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); > this.element.style.zIndex = this.options.zindex; > } > - > + > if(this.options.ghosting) { > this._clone = this.element.cloneNode(true); > - this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); > - if (!this.element._originallyAbsolute) > + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); > + if (!this._originallyAbsolute) > Position.absolutize(this.element); > this.element.parentNode.insertBefore(this._clone, this.element); > } > - > + > if(this.options.scroll) { > if (this.options.scroll == window) { > var where = this._getWindowScroll(this.options.scroll); > @@ -347,15 +347,15 @@ var Draggable = Class.create({ > this.originalScrollTop = this.options.scroll.scrollTop; > } > } > - > + > Draggables.notify('onStart', this, event); > - > + > if(this.options.starteffect) this.options.starteffect(this.element); > }, > - > + > updateDrag: function(event, pointer) { > if(!this.dragging) this.startDrag(event); > - > + > if(!this.options.quiet){ > Position.prepare(); > Droppables.show(pointer, this.element); > @@ -403,9 +403,9 @@ var Draggable = Class.create({ > } > > if(this.options.ghosting) { > - if (!this.element._originallyAbsolute) > + if (!this._originallyAbsolute) > Position.relativize(this.element); > - delete this.element._originallyAbsolute; > + delete this._originallyAbsolute; > Element.remove(this._clone); > this._clone = null; > } > @@ -433,7 +433,7 @@ var Draggable = Class.create({ > if(this.options.zindex) > this.element.style.zIndex = this.originalZ; > > - if(this.options.endeffect) > + if(this.options.endeffect) > this.options.endeffect(this.element); > > Draggables.deactivate(this); > @@ -468,8 +468,8 @@ var Draggable = Class.create({ > pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; > } > > - var p = [0,1].map(function(i){ > - return (point[i]-pos[i]-this.offset[i]) > + var p = [0,1].map(function(i){ > + return (point[i]-pos[i]-this.offset[i]) > }.bind(this)); > > if(this.options.snap) { > @@ -478,10 +478,10 @@ var Draggable = Class.create({ > } else { > if(Object.isArray(this.options.snap)) { > p = p.map( function(v, i) { > - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) > + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); > } else { > p = p.map( function(v) { > - return (v/this.options.snap).round()*this.options.snap }.bind(this)) > + return (v/this.options.snap).round()*this.options.snap }.bind(this)); > } > }} > > @@ -560,7 +560,7 @@ var Draggable = Class.create({ > H = documentElement.clientHeight; > } else { > W = body.offsetWidth; > - H = body.offsetHeight > + H = body.offsetHeight; > } > } > return { top: T, left: L, width: W, height: H }; > @@ -591,9 +591,9 @@ var SortableObserver = Class.create({ > > var Sortable = { > SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, > - > + > sortables: { }, > - > + > _findRootElement: function(element) { > while (element.tagName.toUpperCase() != "BODY") { > if(element.id && Sortable.sortables[element.id]) return element; > @@ -608,7 +608,8 @@ var Sortable = { > }, > > destroy: function(element){ > - var s = Sortable.options(element); > + element = $(element); > + var s = Sortable.sortables[element.id]; > > if(s) { > Draggables.removeObserver(s.element); > @@ -689,14 +690,14 @@ var Sortable = { > tree: options.tree, > hoverclass: options.hoverclass, > onHover: Sortable.onHover > - } > + }; > > var options_for_tree = { > onHover: Sortable.onEmptyHover, > overlap: options.overlap, > containment: options.containment, > hoverclass: options.hoverclass > - } > + }; > > // fix for gecko engine > Element.cleanWhitespace(element); > @@ -832,7 +833,7 @@ var Sortable = { > Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); > else > Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); > - > + > Sortable._marker.show(); > }, > > @@ -851,11 +852,11 @@ var Sortable = { > children: [], > position: parent.children.length, > container: $(children[i]).down(options.treeTag) > - } > + }; > > /* Get the element containing the children and recurse over it */ > if (child.container) > - this._tree(child.container, options, child) > + this._tree(child.container, options, child); > > parent.children.push (child); > } > @@ -880,7 +881,7 @@ var Sortable = { > children: [], > container: element, > position: 0 > - } > + }; > > return Sortable._tree(element, options, root); > }, > @@ -931,7 +932,7 @@ var Sortable = { > > if (options.tree) { > return Sortable.tree(element, arguments[1]).children.map( function (item) { > - return [name + Sortable._constructIndex(item) + "[id]=" + > + return [name + Sortable._constructIndex(item) + "[id]=" + > encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); > }).flatten().join('&'); > } else { > @@ -940,14 +941,14 @@ var Sortable = { > }).join('&'); > } > } > -} > +}; > > // Returns true if child is contained within element > Element.isParent = function(child, element) { > if (!child.parentNode || child == element) return false; > if (child.parentNode == element) return true; > return Element.isParent(child.parentNode, element); > -} > +}; > > Element.findChildren = function(element, only, recursive, tagName) { > if(!element.hasChildNodes()) return null; > @@ -965,8 +966,8 @@ Element.findChildren = function(element, only, recursive, tagName) { > }); > > return (elements.length>0 ? elements.flatten() : []); > -} > +}; > > Element.offsetSize = function (element, type) { > return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; > -} > +}; > \ No newline at end of file > diff --git a/src/public/javascripts/effects.js b/src/public/javascripts/effects.js > index b0f056b..5a639d2 100644 > --- a/src/public/javascripts/effects.js > +++ b/src/public/javascripts/effects.js > @@ -3,9 +3,9 @@ > // Justin Palmer (http://encytemedia.com/) > // Mark Pilgrim (http://diveintomark.org/) > // Martin Bialasinki > -// > +// > // script.aculo.us is freely distributable under the terms of an MIT-style license. > -// For details, see the script.aculo.us web site: http://script.aculo.us/ > +// For details, see the script.aculo.us web site: http://script.aculo.us/ > > // converts rgb() and #xxx to #xxxxxx format, > // returns self (or first argument) if not convertable > @@ -32,7 +32,7 @@ Element.collectTextNodes = function(element) { > }).flatten().join(''); > }; > > -Element.collectTextNodesIgnoreClass = function(element, className) { > +Element.collectTextNodesIgnoreClass = function(element, className) { > return $A($(element).childNodes).collect( function(node) { > return (node.nodeType==3 ? node.nodeValue : > ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? > @@ -70,25 +70,20 @@ var Effect = { > Transitions: { > linear: Prototype.K, > sinoidal: function(pos) { > - return (-Math.cos(pos*Math.PI)/2) + 0.5; > + return (-Math.cos(pos*Math.PI)/2) + .5; > }, > reverse: function(pos) { > return 1-pos; > }, > flicker: function(pos) { > - var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; > + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; > return pos > 1 ? 1 : pos; > }, > wobble: function(pos) { > - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; > + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; > }, > pulse: function(pos, pulses) { > - pulses = pulses || 5; > - return ( > - ((pos % (1/pulses)) * pulses).round() == 0 ? > - ((pos * pulses * 2) - (pos * pulses * 2).floor()) : > - 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) > - ); > + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; > }, > spring: function(pos) { > return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); > @@ -223,7 +218,7 @@ Effect.Queues = { > instances: $H(), > get: function(queueName) { > if (!Object.isString(queueName)) return queueName; > - > + > return this.instances.get(queueName) || > this.instances.set(queueName, new Effect.ScopedQueue()); > } > @@ -249,18 +244,30 @@ Effect.Base = Class.create({ > this.totalTime = this.finishOn-this.startOn; > this.totalFrames = this.options.fps*this.options.duration; > > - eval('this.render = function(pos){ '+ > - 'if (this.state=="idle"){this.state="running";'+ > - codeForEvent(this.options,'beforeSetup')+ > - (this.setup ? 'this.setup();':'')+ > - codeForEvent(this.options,'afterSetup')+ > - '};if (this.state=="running"){'+ > - 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+ > - 'this.position=pos;'+ > - codeForEvent(this.options,'beforeUpdate')+ > - (this.update ? 'this.update(pos);':'')+ > - codeForEvent(this.options,'afterUpdate')+ > - '}}'); > + this.render = (function() { > + function dispatch(effect, eventName) { > + if (effect.options[eventName + 'Internal']) > + effect.options[eventName + 'Internal'](effect); > + if (effect.options[eventName]) > + effect.options[eventName](effect); > + } > + > + return function(pos) { > + if (this.state === "idle") { > + this.state = "running"; > + dispatch(this, 'beforeSetup'); > + if (this.setup) this.setup(); > + dispatch(this, 'afterSetup'); > + } > + if (this.state === "running") { > + pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from; > + this.position = pos; > + dispatch(this, 'beforeUpdate'); > + if (this.update) this.update(pos); > + dispatch(this, 'afterUpdate'); > + } > + }; > + })(); > > this.event('beforeStart'); > if (!this.options.sync) > @@ -392,7 +399,7 @@ Effect.Move = Class.create(Effect.Base, { > > // for backwards compatibility > Effect.MoveBy = function(element, toTop, toLeft) { > - return new Effect.Move(element, > + return new Effect.Move(element, > Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); > }; > > @@ -507,17 +514,16 @@ Effect.Highlight = Class.create(Effect.Base, { > > Effect.ScrollTo = function(element) { > var options = arguments[1] || { }, > - scrollOffsets = document.viewport.getScrollOffsets(), > - elementOffsets = $(element).cumulativeOffset(), > - max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); > + scrollOffsets = document.viewport.getScrollOffsets(), > + elementOffsets = $(element).cumulativeOffset(); > > if (options.offset) elementOffsets[1] += options.offset; > > return new Effect.Tween(null, > scrollOffsets.top, > - elementOffsets[1] > max ? max : elementOffsets[1], > + elementOffsets[1], > options, > - function(p){ scrollTo(scrollOffsets.left, p.round()) } > + function(p){ scrollTo(scrollOffsets.left, p.round()); } > ); > }; > > @@ -554,7 +560,7 @@ Effect.Appear = function(element) { > > Effect.Puff = function(element) { > element = $(element); > - var oldStyle = { > + var oldStyle = { > opacity: element.getInlineOpacity(), > position: element.getStyle('position'), > top: element.style.top, > @@ -563,12 +569,12 @@ Effect.Puff = function(element) { > height: element.style.height > }; > return new Effect.Parallel( > - [ new Effect.Scale(element, 200, > + [ new Effect.Scale(element, 200, > { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), > new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], > Object.extend({ duration: 1.0, > beforeSetupInternal: function(effect) { > - Position.absolutize(effect.effects[0].element) > + Position.absolutize(effect.effects[0].element); > }, > afterFinishInternal: function(effect) { > effect.effects[0].element.hide().setStyle(oldStyle); } > @@ -580,12 +586,12 @@ Effect.BlindUp = function(element) { > element = $(element); > element.makeClipping(); > return new Effect.Scale(element, 0, > - Object.extend({ scaleContent: false, > + Object.extend({ scaleContent: false, > scaleX: false, > restoreAfterFinish: true, > afterFinishInternal: function(effect) { > effect.element.hide().undoClipping(); > - } > + } > }, arguments[1] || { }) > ); > }; > @@ -619,13 +625,13 @@ Effect.SwitchOff = function(element) { > new Effect.Scale(effect.element, 1, { > duration: 0.3, scaleFromCenter: true, > scaleX: false, scaleContent: false, restoreAfterFinish: true, > - beforeSetup: function(effect) { > + beforeSetup: function(effect) { > effect.element.makePositioned().makeClipping(); > }, > afterFinishInternal: function(effect) { > effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); > } > - }) > + }); > } > }, arguments[1] || { })); > }; > @@ -646,7 +652,7 @@ Effect.DropOut = function(element) { > }, > afterFinishInternal: function(effect) { > effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); > - } > + } > }, arguments[1] || { })); > }; > > @@ -674,7 +680,7 @@ Effect.Shake = function(element) { > new Effect.Move(effect.element, > { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { > effect.element.undoPositioned().setStyle(oldStyle); > - }}) }}) }}) }}) }}) }}); > + }}); }}); }}); }}); }}); }}); > }; > > Effect.SlideDown = function(element) { > @@ -682,7 +688,7 @@ Effect.SlideDown = function(element) { > // SlideDown need to have the content of the element wrapped in a container element with fixed height! > var oldInnerBottom = element.down().getStyle('bottom'); > var elementDimensions = element.getDimensions(); > - return new Effect.Scale(element, 100, Object.extend({ > + return new Effect.Scale(element, 100, Object.extend({ > scaleContent: false, > scaleX: false, > scaleFrom: window.opera ? 0 : 1, > @@ -742,7 +748,7 @@ Effect.Squish = function(element) { > effect.element.makeClipping(); > }, > afterFinishInternal: function(effect) { > - effect.element.hide().undoClipping(); > + effect.element.hide().undoClipping(); > } > }); > }; > @@ -810,13 +816,13 @@ Effect.Grow = function(element) { > sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) > ], Object.extend({ > beforeSetup: function(effect) { > - effect.effects[0].element.setStyle({height: '0px'}).show(); > + effect.effects[0].element.setStyle({height: '0px'}).show(); > }, > afterFinishInternal: function(effect) { > effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); > } > }, options) > - ) > + ); > } > }); > }; > @@ -838,7 +844,7 @@ Effect.Shrink = function(element) { > > var dims = element.getDimensions(); > var moveX, moveY; > - > + > switch (options.direction) { > case 'top-left': > moveX = moveY = 0; > @@ -877,11 +883,13 @@ Effect.Shrink = function(element) { > > Effect.Pulsate = function(element) { > element = $(element); > - var options = arguments[1] || { }; > - var oldOpacity = element.getInlineOpacity(); > - var transition = options.transition || Effect.Transitions.sinoidal; > - var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; > - reverser.bind(transition); > + var options = arguments[1] || { }, > + oldOpacity = element.getInlineOpacity(), > + transition = options.transition || Effect.Transitions.linear, > + reverser = function(pos){ > + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); > + }; > + > return new Effect.Opacity(element, > Object.extend(Object.extend({ duration: 2.0, from: 0, > afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } > @@ -934,7 +942,7 @@ Effect.Morph = Class.create(Effect.Base, { > effect.transforms.each(function(transform) { > effect.element.style[transform.style] = ''; > }); > - } > + }; > } > } > this.start(options); > @@ -945,7 +953,7 @@ Effect.Morph = Class.create(Effect.Base, { > if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; > color = color.parseColor(); > return $R(0,2).map(function(i){ > - return parseInt( color.slice(i*2+1,i*2+3), 16 ) > + return parseInt( color.slice(i*2+1,i*2+3), 16 ); > }); > } > this.transforms = this.style.map(function(pair){ > @@ -978,7 +986,7 @@ Effect.Morph = Class.create(Effect.Base, { > transform.unit != 'color' && > (isNaN(transform.originalValue) || isNaN(transform.targetValue)) > ) > - ) > + ); > }); > }, > update: function(position) { > @@ -1074,14 +1082,14 @@ if (document.defaultView && document.defaultView.getComputedStyle) { > Element.getStyles = function(element) { > element = $(element); > var css = element.currentStyle, styles; > - styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { > - hash.set(property, css[property]); > - return hash; > + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { > + results[property] = css[property]; > + return results; > }); > - if (!styles.opacity) styles.set('opacity', element.getOpacity()); > + if (!styles.opacity) styles.opacity = element.getOpacity(); > return styles; > }; > -}; > +} > > Effect.Methods = { > morph: function(element, style) { > @@ -1090,7 +1098,7 @@ Effect.Methods = { > return element; > }, > visualEffect: function(element, effect, options) { > - element = $(element) > + element = $(element); > var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); > new Effect[klass](element, options); > return element; > @@ -1109,7 +1117,7 @@ $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ > element = $(element); > Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); > return element; > - } > + }; > } > ); > > @@ -1117,4 +1125,4 @@ $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTex > function(f) { Effect.Methods[f] = Element[f]; } > ); > > -Element.addMethods(Effect.Methods); > +Element.addMethods(Effect.Methods); > \ No newline at end of file > diff --git a/src/public/javascripts/prototype.js b/src/public/javascripts/prototype.js > index 9f6c857..dfe8ab4 100644 > --- a/src/public/javascripts/prototype.js > +++ b/src/public/javascripts/prototype.js > @@ -1,5 +1,5 @@ > -/* Prototype JavaScript framework, version 1.6.0.1 > - * (c) 2005-2007 Sam Stephenson > +/* Prototype JavaScript framework, version 1.6.0.3 > + * (c) 2005-2008 Sam Stephenson > * > * Prototype is freely distributable under the terms of an MIT-style license. > * For details, see the Prototype web site: http://www.prototypejs.org/ > @@ -7,23 +7,26 @@ > *--------------------------------------------------------------------------*/ > > var Prototype = { > - Version: '1.6.0.1', > + Version: '1.6.0.3', > > Browser: { > - IE: !!(window.attachEvent && !window.opera), > - Opera: !!window.opera, > + IE: !!(window.attachEvent && > + navigator.userAgent.indexOf('Opera') === -1), > + Opera: navigator.userAgent.indexOf('Opera') > -1, > WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, > - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, > + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && > + navigator.userAgent.indexOf('KHTML') === -1, > MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) > }, > > BrowserFeatures: { > XPath: !!document.evaluate, > + SelectorsAPI: !!document.querySelector, > ElementExtensions: !!window.HTMLElement, > SpecificElementExtensions: > - document.createElement('div').__proto__ && > - document.createElement('div').__proto__ !== > - document.createElement('form').__proto__ > + document.createElement('div')['__proto__'] && > + document.createElement('div')['__proto__'] !== > + document.createElement('form')['__proto__'] > }, > > ScriptFragment: ']*>([\\S\\s]*?)<\/script>', > @@ -83,12 +86,13 @@ Class.Methods = { > var property = properties[i], value = source[property]; > if (ancestor && Object.isFunction(value) && > value.argumentNames().first() == "$super") { > - var method = value, value = Object.extend((function(m) { > + var method = value; > + value = (function(m) { > return function() { return ancestor[m].apply(this, arguments) }; > - })(property).wrap(method), { > - valueOf: function() { return method }, > - toString: function() { return method.toString() } > - }); > + })(property).wrap(method); > + > + value.valueOf = method.valueOf.bind(method); > + value.toString = method.toString.bind(method); > } > this.prototype[property] = value; > } > @@ -110,7 +114,7 @@ Object.extend(Object, { > try { > if (Object.isUndefined(object)) return 'undefined'; > if (object === null) return 'null'; > - return object.inspect ? object.inspect() : object.toString(); > + return object.inspect ? object.inspect() : String(object); > } catch (e) { > if (e instanceof RangeError) return '...'; > throw e; > @@ -167,11 +171,12 @@ Object.extend(Object, { > }, > > isElement: function(object) { > - return object && object.nodeType == 1; > + return !!(object && object.nodeType == 1); > }, > > isArray: function(object) { > - return object && object.constructor === Array; > + return object != null && typeof object == "object" && > + 'splice' in object && 'join' in object; > }, > > isHash: function(object) { > @@ -197,7 +202,8 @@ Object.extend(Object, { > > Object.extend(Function.prototype, { > argumentNames: function() { > - var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); > + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] > + .replace(/\s+/g, '').split(','); > return names.length == 1 && !names[0] ? [] : names; > }, > > @@ -231,6 +237,11 @@ Object.extend(Function.prototype, { > }, timeout); > }, > > + defer: function() { > + var args = [0.01].concat($A(arguments)); > + return this.delay.apply(this, args); > + }, > + > wrap: function(wrapper) { > var __method = this; > return function() { > @@ -247,8 +258,6 @@ Object.extend(Function.prototype, { > } > }); > > -Function.prototype.defer = Function.prototype.delay.curry(0.01); > - > Date.prototype.toJSON = function() { > return '"' + this.getUTCFullYear() + '-' + > (this.getUTCMonth() + 1).toPaddedString(2) + '-' + > @@ -529,7 +538,7 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto > return this.replace(/&/g,'&').replace(//g,'>'); > }, > unescapeHTML: function() { > - return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); > + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); > } > }); > > @@ -546,7 +555,7 @@ Object.extend(String.prototype.escapeHTML, { > text: document.createTextNode('') > }); > > -with (String.prototype.escapeHTML) div.appendChild(text); > +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); > > var Template = Class.create({ > initialize: function(template, pattern) { > @@ -578,7 +587,7 @@ var Template = Class.create({ > } > > return before + String.interpret(ctx); > - }.bind(this)); > + }); > } > }); > Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; > @@ -588,10 +597,9 @@ var $break = { }; > var Enumerable = { > each: function(iterator, context) { > var index = 0; > - iterator = iterator.bind(context); > try { > this._each(function(value) { > - iterator(value, index++); > + iterator.call(context, value, index++); > }); > } catch (e) { > if (e != $break) throw e; > @@ -600,47 +608,46 @@ var Enumerable = { > }, > > eachSlice: function(number, iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > var index = -number, slices = [], array = this.toArray(); > + if (number < 1) return array; > while ((index += number) < array.length) > slices.push(array.slice(index, index+number)); > return slices.collect(iterator, context); > }, > > all: function(iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var result = true; > this.each(function(value, index) { > - result = result && !!iterator(value, index); > + result = result && !!iterator.call(context, value, index); > if (!result) throw $break; > }); > return result; > }, > > any: function(iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var result = false; > this.each(function(value, index) { > - if (result = !!iterator(value, index)) > + if (result = !!iterator.call(context, value, index)) > throw $break; > }); > return result; > }, > > collect: function(iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var results = []; > this.each(function(value, index) { > - results.push(iterator(value, index)); > + results.push(iterator.call(context, value, index)); > }); > return results; > }, > > detect: function(iterator, context) { > - iterator = iterator.bind(context); > var result; > this.each(function(value, index) { > - if (iterator(value, index)) { > + if (iterator.call(context, value, index)) { > result = value; > throw $break; > } > @@ -649,17 +656,16 @@ var Enumerable = { > }, > > findAll: function(iterator, context) { > - iterator = iterator.bind(context); > var results = []; > this.each(function(value, index) { > - if (iterator(value, index)) > + if (iterator.call(context, value, index)) > results.push(value); > }); > return results; > }, > > grep: function(filter, iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var results = []; > > if (Object.isString(filter)) > @@ -667,7 +673,7 @@ var Enumerable = { > > this.each(function(value, index) { > if (filter.match(value)) > - results.push(iterator(value, index)); > + results.push(iterator.call(context, value, index)); > }); > return results; > }, > @@ -695,9 +701,8 @@ var Enumerable = { > }, > > inject: function(memo, iterator, context) { > - iterator = iterator.bind(context); > this.each(function(value, index) { > - memo = iterator(memo, value, index); > + memo = iterator.call(context, memo, value, index); > }); > return memo; > }, > @@ -710,10 +715,10 @@ var Enumerable = { > }, > > max: function(iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var result; > this.each(function(value, index) { > - value = iterator(value, index); > + value = iterator.call(context, value, index); > if (result == null || value >= result) > result = value; > }); > @@ -721,10 +726,10 @@ var Enumerable = { > }, > > min: function(iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var result; > this.each(function(value, index) { > - value = iterator(value, index); > + value = iterator.call(context, value, index); > if (result == null || value < result) > result = value; > }); > @@ -732,10 +737,10 @@ var Enumerable = { > }, > > partition: function(iterator, context) { > - iterator = iterator ? iterator.bind(context) : Prototype.K; > + iterator = iterator || Prototype.K; > var trues = [], falses = []; > this.each(function(value, index) { > - (iterator(value, index) ? > + (iterator.call(context, value, index) ? > trues : falses).push(value); > }); > return [trues, falses]; > @@ -750,19 +755,20 @@ var Enumerable = { > }, > > reject: function(iterator, context) { > - iterator = iterator.bind(context); > var results = []; > this.each(function(value, index) { > - if (!iterator(value, index)) > + if (!iterator.call(context, value, index)) > results.push(value); > }); > return results; > }, > > sortBy: function(iterator, context) { > - iterator = iterator.bind(context); > return this.map(function(value, index) { > - return {value: value, criteria: iterator(value, index)}; > + return { > + value: value, > + criteria: iterator.call(context, value, index) > + }; > }).sort(function(left, right) { > var a = left.criteria, b = right.criteria; > return a < b ? -1 : a > b ? 1 : 0; > @@ -806,20 +812,24 @@ Object.extend(Enumerable, { > function $A(iterable) { > if (!iterable) return []; > if (iterable.toArray) return iterable.toArray(); > - var length = iterable.length, results = new Array(length); > + var length = iterable.length || 0, results = new Array(length); > while (length--) results[length] = iterable[length]; > return results; > } > > if (Prototype.Browser.WebKit) { > - function $A(iterable) { > + $A = function(iterable) { > if (!iterable) return []; > - if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && > - iterable.toArray) return iterable.toArray(); > - var length = iterable.length, results = new Array(length); > + // In Safari, only use the `toArray` method if it's not a NodeList. > + // A NodeList is a function, has an function `item` property, and a numeric > + // `length` property. Adapted from Google Doctype. > + if (!(typeof iterable === 'function' && typeof iterable.length === > + 'number' && typeof iterable.item === 'function') && iterable.toArray) > + return iterable.toArray(); > + var length = iterable.length || 0, results = new Array(length); > while (length--) results[length] = iterable[length]; > return results; > - } > + }; > } > > Array.from = $A; > @@ -962,8 +972,8 @@ Object.extend(Number.prototype, { > return this + 1; > }, > > - times: function(iterator) { > - $R(0, this, true).each(iterator); > + times: function(iterator, context) { > + $R(0, this, true).each(iterator, context); > return this; > }, > > @@ -1010,7 +1020,9 @@ var Hash = Class.create(Enumerable, (function() { > }, > > get: function(key) { > - return this._object[key]; > + // simulating poorly supported hasOwnProperty > + if (this._object[key] !== Object.prototype[key]) > + return this._object[key]; > }, > > unset: function(key) { > @@ -1050,14 +1062,14 @@ var Hash = Class.create(Enumerable, (function() { > }, > > toQueryString: function() { > - return this.map(function(pair) { > + return this.inject([], function(results, pair) { > var key = encodeURIComponent(pair.key), values = pair.value; > > if (values && typeof values == 'object') { > if (Object.isArray(values)) > - return values.map(toQueryPair.curry(key)).join('&'); > - } > - return toQueryPair(key, values); > + return results.concat(values.map(toQueryPair.curry(key))); > + } else results.push(toQueryPair(key, values)); > + return results; > }).join('&'); > }, > > @@ -1298,7 +1310,7 @@ Ajax.Request = Class.create(Ajax.Base, { > > var contentType = response.getHeader('Content-type'); > if (this.options.evalJS == 'force' > - || (this.options.evalJS && contentType > + || (this.options.evalJS && this.isSameOrigin() && contentType > && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) > this.evalResponse(); > } > @@ -1316,9 +1328,18 @@ Ajax.Request = Class.create(Ajax.Base, { > } > }, > > + isSameOrigin: function() { > + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); > + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ > + protocol: location.protocol, > + domain: document.domain, > + port: location.port ? ':' + location.port : '' > + })); > + }, > + > getHeader: function(name) { > try { > - return this.transport.getResponseHeader(name); > + return this.transport.getResponseHeader(name) || null; > } catch (e) { return null } > }, > > @@ -1391,7 +1412,8 @@ Ajax.Response = Class.create({ > if (!json) return null; > json = decodeURIComponent(escape(json)); > try { > - return json.evalJSON(this.request.options.sanitizeJSON); > + return json.evalJSON(this.request.options.sanitizeJSON || > + !this.request.isSameOrigin()); > } catch (e) { > this.request.dispatchException(e); > } > @@ -1404,7 +1426,8 @@ Ajax.Response = Class.create({ > this.responseText.blank()) > return null; > try { > - return this.responseText.evalJSON(options.sanitizeJSON); > + return this.responseText.evalJSON(options.sanitizeJSON || > + !this.request.isSameOrigin()); > } catch (e) { > this.request.dispatchException(e); > } > @@ -1546,6 +1569,7 @@ if (!Node.ELEMENT_NODE) { > return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); > }; > Object.extend(this.Element, element || { }); > + if (element) this.Element.prototype = element.prototype; > }).call(window); > > Element.cache = { }; > @@ -1562,12 +1586,14 @@ Element.Methods = { > }, > > hide: function(element) { > - $(element).style.display = 'none'; > + element = $(element); > + element.style.display = 'none'; > return element; > }, > > show: function(element) { > - $(element).style.display = ''; > + element = $(element); > + element.style.display = ''; > return element; > }, > > @@ -1608,24 +1634,28 @@ Element.Methods = { > Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) > insertions = {bottom:insertions}; > > - var content, t, range; > + var content, insert, tagName, childNodes; > > - for (position in insertions) { > + for (var position in insertions) { > content = insertions[position]; > position = position.toLowerCase(); > - t = Element._insertionTranslations[position]; > + insert = Element._insertionTranslations[position]; > > if (content && content.toElement) content = content.toElement(); > if (Object.isElement(content)) { > - t.insert(element, content); > + insert(element, content); > continue; > } > > content = Object.toHTML(content); > > - range = element.ownerDocument.createRange(); > - t.initializeRange(element, range); > - t.insert(element, range.createContextualFragment(content.stripScripts())); > + tagName = ((position == 'before' || position == 'after') > + ? element.parentNode : element).tagName.toUpperCase(); > + > + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); > + > + if (position == 'top' || position == 'after') childNodes.reverse(); > + childNodes.each(insert.curry(element)); > > content.evalScripts.bind(content).defer(); > } > @@ -1670,7 +1700,7 @@ Element.Methods = { > }, > > descendants: function(element) { > - return $(element).getElementsBySelector("*"); > + return $(element).select("*"); > }, > > firstDescendant: function(element) { > @@ -1709,32 +1739,31 @@ Element.Methods = { > element = $(element); > if (arguments.length == 1) return $(element.parentNode); > var ancestors = element.ancestors(); > - return expression ? Selector.findElement(ancestors, expression, index) : > - ancestors[index || 0]; > + return Object.isNumber(expression) ? ancestors[expression] : > + Selector.findElement(ancestors, expression, index); > }, > > down: function(element, expression, index) { > element = $(element); > if (arguments.length == 1) return element.firstDescendant(); > - var descendants = element.descendants(); > - return expression ? Selector.findElement(descendants, expression, index) : > - descendants[index || 0]; > + return Object.isNumber(expression) ? element.descendants()[expression] : > + Element.select(element, expression)[index || 0]; > }, > > previous: function(element, expression, index) { > element = $(element); > if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); > var previousSiblings = element.previousSiblings(); > - return expression ? Selector.findElement(previousSiblings, expression, index) : > - previousSiblings[index || 0]; > + return Object.isNumber(expression) ? previousSiblings[expression] : > + Selector.findElement(previousSiblings, expression, index); > }, > > next: function(element, expression, index) { > element = $(element); > if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); > var nextSiblings = element.nextSiblings(); > - return expression ? Selector.findElement(nextSiblings, expression, index) : > - nextSiblings[index || 0]; > + return Object.isNumber(expression) ? nextSiblings[expression] : > + Selector.findElement(nextSiblings, expression, index); > }, > > select: function() { > @@ -1848,23 +1877,16 @@ Element.Methods = { > > descendantOf: function(element, ancestor) { > element = $(element), ancestor = $(ancestor); > - var originalAncestor = ancestor; > > if (element.compareDocumentPosition) > return (element.compareDocumentPosition(ancestor) & 8) === 8; > > - if (element.sourceIndex && !Prototype.Browser.Opera) { > - var e = element.sourceIndex, a = ancestor.sourceIndex, > - nextAncestor = ancestor.nextSibling; > - if (!nextAncestor) { > - do { ancestor = ancestor.parentNode; } > - while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); > - } > - if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); > - } > + if (ancestor.contains) > + return ancestor.contains(element) && ancestor !== element; > > while (element = element.parentNode) > - if (element == originalAncestor) return true; > + if (element == ancestor) return true; > + > return false; > }, > > @@ -1879,7 +1901,7 @@ Element.Methods = { > element = $(element); > style = style == 'float' ? 'cssFloat' : style.camelize(); > var value = element.style[style]; > - if (!value) { > + if (!value || value == 'auto') { > var css = document.defaultView.getComputedStyle(element, null); > value = css ? css[style] : null; > } > @@ -1918,7 +1940,7 @@ Element.Methods = { > > getDimensions: function(element) { > element = $(element); > - var display = $(element).getStyle('display'); > + var display = element.getStyle('display'); > if (display != 'none' && display != null) // Safari bug > return {width: element.offsetWidth, height: element.offsetHeight}; > > @@ -1947,7 +1969,7 @@ Element.Methods = { > element.style.position = 'relative'; > // Opera returns the offset relative to the positioning context, when an > // element is position relative but top and left have not been defined > - if (window.opera) { > + if (Prototype.Browser.Opera) { > element.style.top = 0; > element.style.left = 0; > } > @@ -2002,9 +2024,9 @@ Element.Methods = { > valueL += element.offsetLeft || 0; > element = element.offsetParent; > if (element) { > - if (element.tagName == 'BODY') break; > + if (element.tagName.toUpperCase() == 'BODY') break; > var p = Element.getStyle(element, 'position'); > - if (p == 'relative' || p == 'absolute') break; > + if (p !== 'static') break; > } > } while (element); > return Element._returnOffset(valueL, valueT); > @@ -2012,7 +2034,7 @@ Element.Methods = { > > absolutize: function(element) { > element = $(element); > - if (element.getStyle('position') == 'absolute') return; > + if (element.getStyle('position') == 'absolute') return element; > // Position.prepare(); // To be done manually by Scripty when it needs it. > > var offsets = element.positionedOffset(); > @@ -2036,7 +2058,7 @@ Element.Methods = { > > relativize: function(element) { > element = $(element); > - if (element.getStyle('position') == 'relative') return; > + if (element.getStyle('position') == 'relative') return element; > // Position.prepare(); // To be done manually by Scripty when it needs it. > > element.style.position = 'relative'; > @@ -2087,7 +2109,7 @@ Element.Methods = { > > element = forElement; > do { > - if (!Prototype.Browser.Opera || element.tagName == 'BODY') { > + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { > valueT -= element.scrollTop || 0; > valueL -= element.scrollLeft || 0; > } > @@ -2153,46 +2175,6 @@ Element._attributeTranslations = { > } > }; > > - > -if (!document.createRange || Prototype.Browser.Opera) { > - Element.Methods.insert = function(element, insertions) { > - element = $(element); > - > - if (Object.isString(insertions) || Object.isNumber(insertions) || > - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) > - insertions = { bottom: insertions }; > - > - var t = Element._insertionTranslations, content, position, pos, tagName; > - > - for (position in insertions) { > - content = insertions[position]; > - position = position.toLowerCase(); > - pos = t[position]; > - > - if (content && content.toElement) content = content.toElement(); > - if (Object.isElement(content)) { > - pos.insert(element, content); > - continue; > - } > - > - content = Object.toHTML(content); > - tagName = ((position == 'before' || position == 'after') > - ? element.parentNode : element).tagName.toUpperCase(); > - > - if (t.tags[tagName]) { > - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); > - if (position == 'top' || position == 'after') fragments.reverse(); > - fragments.each(pos.insert.curry(element)); > - } > - else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); > - > - content.evalScripts.bind(content).defer(); > - } > - > - return element; > - }; > -} > - > if (Prototype.Browser.Opera) { > Element.Methods.getStyle = Element.Methods.getStyle.wrap( > function(proceed, element, style) { > @@ -2237,12 +2219,36 @@ if (Prototype.Browser.Opera) { > } > > else if (Prototype.Browser.IE) { > - $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { > + // IE doesn't report offsets correctly for static elements, so we change them > + // to "relative" to get the values, then change them back. > + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( > + function(proceed, element) { > + element = $(element); > + // IE throws an error if element is not in document > + try { element.offsetParent } > + catch(e) { return $(document.body) } > + var position = element.getStyle('position'); > + if (position !== 'static') return proceed(element); > + element.setStyle({ position: 'relative' }); > + var value = proceed(element); > + element.setStyle({ position: position }); > + return value; > + } > + ); > + > + $w('positionedOffset viewportOffset').each(function(method) { > Element.Methods[method] = Element.Methods[method].wrap( > function(proceed, element) { > element = $(element); > + try { element.offsetParent } > + catch(e) { return Element._returnOffset(0,0) } > var position = element.getStyle('position'); > - if (position != 'static') return proceed(element); > + if (position !== 'static') return proceed(element); > + // Trigger hasLayout on the offset parent so that IE6 reports > + // accurate offsetTop and offsetLeft values for position: fixed. > + var offsetParent = element.getOffsetParent(); > + if (offsetParent && offsetParent.getStyle('position') === 'fixed') > + offsetParent.setStyle({ zoom: 1 }); > element.setStyle({ position: 'relative' }); > var value = proceed(element); > element.setStyle({ position: position }); > @@ -2251,6 +2257,14 @@ else if (Prototype.Browser.IE) { > ); > }); > > + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( > + function(proceed, element) { > + try { element.offsetParent } > + catch(e) { return Element._returnOffset(0,0) } > + return proceed(element); > + } > + ); > + > Element.Methods.getStyle = function(element, style) { > element = $(element); > style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); > @@ -2324,7 +2338,10 @@ else if (Prototype.Browser.IE) { > }; > > Element._attributeTranslations.write = { > - names: Object.clone(Element._attributeTranslations.read.names), > + names: Object.extend({ > + cellpadding: 'cellPadding', > + cellspacing: 'cellSpacing' > + }, Element._attributeTranslations.read.names), > values: { > checked: function(element, value) { > element.checked = !!value; > @@ -2339,7 +2356,7 @@ else if (Prototype.Browser.IE) { > Element._attributeTranslations.has = {}; > > $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + > - 'encType maxLength readOnly longDesc').each(function(attr) { > + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { > Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; > Element._attributeTranslations.has[attr.toLowerCase()] = attr; > }); > @@ -2392,7 +2409,7 @@ else if (Prototype.Browser.WebKit) { > (value < 0.00001) ? 0 : value; > > if (value == 1) > - if(element.tagName == 'IMG' && element.width) { > + if(element.tagName.toUpperCase() == 'IMG' && element.width) { > element.width++; element.width--; > } else try { > var n = document.createTextNode(' '); > @@ -2444,7 +2461,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) { > }; > } > > -if (document.createElement('div').outerHTML) { > +if ('outerHTML' in document.createElement('div')) { > Element.Methods.replace = function(element, content) { > element = $(element); > > @@ -2482,45 +2499,25 @@ Element._returnOffset = function(l, t) { > > Element._getContentFromAnonymousElement = function(tagName, html) { > var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; > - div.innerHTML = t[0] + html + t[1]; > - t[2].times(function() { div = div.firstChild }); > + if (t) { > + div.innerHTML = t[0] + html + t[1]; > + t[2].times(function() { div = div.firstChild }); > + } else div.innerHTML = html; > return $A(div.childNodes); > }; > > Element._insertionTranslations = { > - before: { > - adjacency: 'beforeBegin', > - insert: function(element, node) { > - element.parentNode.insertBefore(node, element); > - }, > - initializeRange: function(element, range) { > - range.setStartBefore(element); > - } > + before: function(element, node) { > + element.parentNode.insertBefore(node, element); > }, > - top: { > - adjacency: 'afterBegin', > - insert: function(element, node) { > - element.insertBefore(node, element.firstChild); > - }, > - initializeRange: function(element, range) { > - range.selectNodeContents(element); > - range.collapse(true); > - } > + top: function(element, node) { > + element.insertBefore(node, element.firstChild); > }, > - bottom: { > - adjacency: 'beforeEnd', > - insert: function(element, node) { > - element.appendChild(node); > - } > + bottom: function(element, node) { > + element.appendChild(node); > }, > - after: { > - adjacency: 'afterEnd', > - insert: function(element, node) { > - element.parentNode.insertBefore(node, element.nextSibling); > - }, > - initializeRange: function(element, range) { > - range.setStartAfter(element); > - } > + after: function(element, node) { > + element.parentNode.insertBefore(node, element.nextSibling); > }, > tags: { > TABLE: ['', '
    ', 1], > @@ -2532,7 +2529,6 @@ Element._insertionTranslations = { > }; > > (function() { > - this.bottom.initializeRange = this.top.initializeRange; > Object.extend(this.tags, { > THEAD: this.tags.TBODY, > TFOOT: this.tags.TBODY, > @@ -2544,7 +2540,7 @@ Element.Methods.Simulated = { > hasAttribute: function(element, attribute) { > attribute = Element._attributeTranslations.has[attribute] || attribute; > var node = $(element).getAttributeNode(attribute); > - return node && node.specified; > + return !!(node && node.specified); > } > }; > > @@ -2553,9 +2549,9 @@ Element.Methods.ByTag = { }; > Object.extend(Element, Element.Methods); > > if (!Prototype.BrowserFeatures.ElementExtensions && > - document.createElement('div').__proto__) { > + document.createElement('div')['__proto__']) { > window.HTMLElement = { }; > - window.HTMLElement.prototype = document.createElement('div').__proto__; > + window.HTMLElement.prototype = document.createElement('div')['__proto__']; > Prototype.BrowserFeatures.ElementExtensions = true; > } > > @@ -2570,7 +2566,7 @@ Element.extend = (function() { > element.nodeType != 1 || element == window) return element; > > var methods = Object.clone(Methods), > - tagName = element.tagName, property, value; > + tagName = element.tagName.toUpperCase(), property, value; > > // extend methods for specific tags > if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); > @@ -2666,7 +2662,7 @@ Element.addMethods = function(methods) { > if (window[klass]) return window[klass]; > > window[klass] = { }; > - window[klass].prototype = document.createElement(tagName).__proto__; > + window[klass].prototype = document.createElement(tagName)['__proto__']; > return window[klass]; > } > > @@ -2692,12 +2688,18 @@ Element.addMethods = function(methods) { > > document.viewport = { > getDimensions: function() { > - var dimensions = { }; > - var B = Prototype.Browser; > + var dimensions = { }, B = Prototype.Browser; > $w('width height').each(function(d) { > var D = d.capitalize(); > - dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : > - (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; > + if (B.WebKit && !document.evaluate) { > + // Safari <3.0 needs self.innerWidth/Height > + dimensions[d] = self['inner' + D]; > + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { > + // Opera <9.5 needs document.body.clientWidth/Height > + dimensions[d] = document.body['client' + D] > + } else { > + dimensions[d] = document.documentElement['client' + D]; > + } > }); > return dimensions; > }, > @@ -2716,14 +2718,24 @@ document.viewport = { > window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); > } > }; > -/* Portions of the Selector class are derived from Jack Slocum???s DomQuery, > +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, > * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style > * license. Please see http://www.yui-ext.com/ for more information. */ > > var Selector = Class.create({ > initialize: function(expression) { > this.expression = expression.strip(); > - this.compileMatcher(); > + > + if (this.shouldUseSelectorsAPI()) { > + this.mode = 'selectorsAPI'; > + } else if (this.shouldUseXPath()) { > + this.mode = 'xpath'; > + this.compileXPathMatcher(); > + } else { > + this.mode = "normal"; > + this.compileMatcher(); > + } > + > }, > > shouldUseXPath: function() { > @@ -2738,16 +2750,29 @@ var Selector = Class.create({ > > // XPath can't do namespaced attributes, nor can it read > // the "checked" property from DOM nodes > - if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) > + if ((/(\[[\w-]*?:|:checked)/).test(e)) > return false; > > return true; > }, > > - compileMatcher: function() { > - if (this.shouldUseXPath()) > - return this.compileXPathMatcher(); > + shouldUseSelectorsAPI: function() { > + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; > + > + if (!Selector._div) Selector._div = new Element('div'); > + > + // Make sure the browser treats the selector as valid. Test on an > + // isolated element to minimize cost of this check. > + try { > + Selector._div.querySelector(this.expression); > + } catch(e) { > + return false; > + } > > + return true; > + }, > + > + compileMatcher: function() { > var e = this.expression, ps = Selector.patterns, h = Selector.handlers, > c = Selector.criteria, le, p, m; > > @@ -2765,7 +2790,7 @@ var Selector = Class.create({ > p = ps[i]; > if (m = e.match(p)) { > this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : > - new Template(c[i]).evaluate(m)); > + new Template(c[i]).evaluate(m)); > e = e.replace(m[0], ''); > break; > } > @@ -2804,8 +2829,27 @@ var Selector = Class.create({ > > findElements: function(root) { > root = root || document; > - if (this.xpath) return document._getElementsByXPath(this.xpath, root); > - return this.matcher(root); > + var e = this.expression, results; > + > + switch (this.mode) { > + case 'selectorsAPI': > + // querySelectorAll queries document-wide, then filters to descendants > + // of the context element. That's not what we want. > + // Add an explicit context to the selector if necessary. > + if (root !== document) { > + var oldId = root.id, id = $(root).identify(); > + e = "#" + id + " " + e; > + } > + > + results = $A(root.querySelectorAll(e)).map(Element.extend); > + root.id = oldId; > + > + return results; > + case 'xpath': > + return document._getElementsByXPath(this.xpath, root); > + default: > + return this.matcher(root); > + } > }, > > match: function(element) { > @@ -2896,10 +2940,10 @@ Object.extend(Selector, { > 'first-child': '[not(preceding-sibling::*)]', > 'last-child': '[not(following-sibling::*)]', > 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', > - 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", > + 'empty': "[count(*) = 0 and (count(text()) = 0)]", > 'checked': "[@checked]", > - 'disabled': "[@disabled]", > - 'enabled': "[not(@disabled)]", > + 'disabled': "[(@disabled) and (@type!='hidden')]", > + 'enabled': "[not(@disabled) and (@type!='hidden')]", > 'not': function(m) { > var e = m[6], p = Selector.patterns, > x = Selector.xpath, le, v; > @@ -2959,13 +3003,13 @@ Object.extend(Selector, { > }, > > criteria: { > - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', > - className: 'n = h.className(n, r, "#{1}", c); c = false;', > - id: 'n = h.id(n, r, "#{1}", c); c = false;', > - attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', > + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', > + className: 'n = h.className(n, r, "#{1}", c); c = false;', > + id: 'n = h.id(n, r, "#{1}", c); c = false;', > + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', > attr: function(m) { > m[3] = (m[5] || m[6]); > - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); > + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); > }, > pseudo: function(m) { > if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); > @@ -2989,8 +3033,9 @@ Object.extend(Selector, { > tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, > id: /^#([\w\-\*]+)(\b|$)/, > className: /^\.([\w\-\*]+)(\b|$)/, > - pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, > - attrPresence: /^\[([\w]+)\]/, > + pseudo: > +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, > + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, > attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ > }, > > @@ -3014,7 +3059,7 @@ Object.extend(Selector, { > > attr: function(element, matches) { > var nodeValue = Element.readAttribute(element, matches[1]); > - return Selector.operators[matches[2]](nodeValue, matches[3]); > + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); > } > }, > > @@ -3029,14 +3074,15 @@ Object.extend(Selector, { > > // marks an array of nodes for counting > mark: function(nodes) { > + var _true = Prototype.emptyFunction; > for (var i = 0, node; node = nodes[i]; i++) > - node._counted = true; > + node._countedByPrototype = _true; > return nodes; > }, > > unmark: function(nodes) { > for (var i = 0, node; node = nodes[i]; i++) > - node._counted = undefined; > + node._countedByPrototype = undefined; > return nodes; > }, > > @@ -3044,15 +3090,15 @@ Object.extend(Selector, { > // "ofType" flag indicates whether we're indexing for nth-of-type > // rather than nth-child > index: function(parentNode, reverse, ofType) { > - parentNode._counted = true; > + parentNode._countedByPrototype = Prototype.emptyFunction; > if (reverse) { > for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { > var node = nodes[i]; > - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; > + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; > } > } else { > for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) > - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; > + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; > } > }, > > @@ -3061,8 +3107,8 @@ Object.extend(Selector, { > if (nodes.length == 0) return nodes; > var results = [], n; > for (var i = 0, l = nodes.length; i < l; i++) > - if (!(n = nodes[i])._counted) { > - n._counted = true; > + if (!(n = nodes[i])._countedByPrototype) { > + n._countedByPrototype = Prototype.emptyFunction; > results.push(Element.extend(n)); > } > return Selector.handlers.unmark(results); > @@ -3102,7 +3148,7 @@ Object.extend(Selector, { > > nextElementSibling: function(node) { > while (node = node.nextSibling) > - if (node.nodeType == 1) return node; > + if (node.nodeType == 1) return node; > return null; > }, > > @@ -3114,7 +3160,7 @@ Object.extend(Selector, { > > // TOKEN FUNCTIONS > tagName: function(nodes, root, tagName, combinator) { > - tagName = tagName.toUpperCase(); > + var uTagName = tagName.toUpperCase(); > var results = [], h = Selector.handlers; > if (nodes) { > if (combinator) { > @@ -3127,7 +3173,7 @@ Object.extend(Selector, { > if (tagName == "*") return nodes; > } > for (var i = 0, node; node = nodes[i]; i++) > - if (node.tagName.toUpperCase() == tagName) results.push(node); > + if (node.tagName.toUpperCase() === uTagName) results.push(node); > return results; > } else return root.getElementsByTagName(tagName); > }, > @@ -3174,16 +3220,18 @@ Object.extend(Selector, { > return results; > }, > > - attrPresence: function(nodes, root, attr) { > + attrPresence: function(nodes, root, attr, combinator) { > if (!nodes) nodes = root.getElementsByTagName("*"); > + if (nodes && combinator) nodes = this[combinator](nodes); > var results = []; > for (var i = 0, node; node = nodes[i]; i++) > if (Element.hasAttribute(node, attr)) results.push(node); > return results; > }, > > - attr: function(nodes, root, attr, value, operator) { > + attr: function(nodes, root, attr, value, operator, combinator) { > if (!nodes) nodes = root.getElementsByTagName("*"); > + if (nodes && combinator) nodes = this[combinator](nodes); > var handler = Selector.operators[operator], results = []; > for (var i = 0, node; node = nodes[i]; i++) { > var nodeValue = Element.readAttribute(node, attr); > @@ -3262,7 +3310,7 @@ Object.extend(Selector, { > var h = Selector.handlers, results = [], indexed = [], m; > h.mark(nodes); > for (var i = 0, node; node = nodes[i]; i++) { > - if (!node.parentNode._counted) { > + if (!node.parentNode._countedByPrototype) { > h.index(node.parentNode, reverse, ofType); > indexed.push(node.parentNode); > } > @@ -3289,7 +3337,7 @@ Object.extend(Selector, { > 'empty': function(nodes, value, root) { > for (var i = 0, results = [], node; node = nodes[i]; i++) { > // IE treats comments as element nodes > - if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; > + if (node.tagName == '!' || node.firstChild) continue; > results.push(node); > } > return results; > @@ -3300,14 +3348,15 @@ Object.extend(Selector, { > var exclusions = new Selector(selector).findElements(root); > h.mark(exclusions); > for (var i = 0, results = [], node; node = nodes[i]; i++) > - if (!node._counted) results.push(node); > + if (!node._countedByPrototype) results.push(node); > h.unmark(exclusions); > return results; > }, > > 'enabled': function(nodes, value, root) { > for (var i = 0, results = [], node; node = nodes[i]; i++) > - if (!node.disabled) results.push(node); > + if (!node.disabled && (!node.type || node.type !== 'hidden')) > + results.push(node); > return results; > }, > > @@ -3327,18 +3376,29 @@ Object.extend(Selector, { > operators: { > '=': function(nv, v) { return nv == v; }, > '!=': function(nv, v) { return nv != v; }, > - '^=': function(nv, v) { return nv.startsWith(v); }, > + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, > + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, > + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, > '$=': function(nv, v) { return nv.endsWith(v); }, > '*=': function(nv, v) { return nv.include(v); }, > '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, > - '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } > + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + > + '-').include('-' + (v || "").toUpperCase() + '-'); } > + }, > + > + split: function(expression) { > + var expressions = []; > + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { > + expressions.push(m[1].strip()); > + }); > + return expressions; > }, > > matchElements: function(elements, expression) { > - var matches = new Selector(expression).findElements(), h = Selector.handlers; > + var matches = $$(expression), h = Selector.handlers; > h.mark(matches); > for (var i = 0, results = [], element; element = elements[i]; i++) > - if (element._counted) results.push(element); > + if (element._countedByPrototype) results.push(element); > h.unmark(matches); > return results; > }, > @@ -3351,11 +3411,7 @@ Object.extend(Selector, { > }, > > findChildElements: function(element, expressions) { > - var exprs = expressions.join(','); > - expressions = []; > - exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { > - expressions.push(m[1].strip()); > - }); > + expressions = Selector.split(expressions.join(',')); > var results = [], h = Selector.handlers; > for (var i = 0, l = expressions.length, selector; i < l; i++) { > selector = new Selector(expressions[i].strip()); > @@ -3366,13 +3422,22 @@ Object.extend(Selector, { > }); > > if (Prototype.Browser.IE) { > - // IE returns comment nodes on getElementsByTagName("*"). > - // Filter them out. > - Selector.handlers.concat = function(a, b) { > - for (var i = 0, node; node = b[i]; i++) > - if (node.tagName !== "!") a.push(node); > - return a; > - }; > + Object.extend(Selector.handlers, { > + // IE returns comment nodes on getElementsByTagName("*"). > + // Filter them out. > + concat: function(a, b) { > + for (var i = 0, node; node = b[i]; i++) > + if (node.tagName !== "!") a.push(node); > + return a; > + }, > + > + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. > + unmark: function(nodes) { > + for (var i = 0, node; node = nodes[i]; i++) > + node.removeAttribute('_countedByPrototype'); > + return nodes; > + } > + }); > } > > function $$() { > @@ -3392,7 +3457,7 @@ var Form = { > var data = elements.inject({ }, function(result, element) { > if (!element.disabled && element.name) { > key = element.name; value = $(element).getValue(); > - if (value != null && (element.type != 'submit' || (!submitted && > + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && > submit !== false && (!submit || key == submit) && (submitted = true)))) { > if (key in result) { > // a key is already present; construct an array of values > @@ -3553,7 +3618,6 @@ Form.Element.Methods = { > > disable: function(element) { > element = $(element); > - element.blur(); > element.disabled = true; > return element; > }, > @@ -3593,22 +3657,22 @@ Form.Element.Serializers = { > else element.value = value; > }, > > - select: function(element, index) { > - if (Object.isUndefined(index)) > + select: function(element, value) { > + if (Object.isUndefined(value)) > return this[element.type == 'select-one' ? > 'selectOne' : 'selectMany'](element); > else { > - var opt, value, single = !Object.isArray(index); > + var opt, currentValue, single = !Object.isArray(value); > for (var i = 0, length = element.length; i < length; i++) { > opt = element.options[i]; > - value = this.optionValue(opt); > + currentValue = this.optionValue(opt); > if (single) { > - if (value == index) { > + if (currentValue == value) { > opt.selected = true; > return; > } > } > - else opt.selected = index.include(value); > + else opt.selected = value.include(currentValue); > } > } > }, > @@ -3779,8 +3843,23 @@ Event.Methods = (function() { > isRightClick: function(event) { return isButton(event, 2) }, > > element: function(event) { > - var node = Event.extend(event).target; > - return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); > + event = Event.extend(event); > + > + var node = event.target, > + type = event.type, > + currentTarget = event.currentTarget; > + > + if (currentTarget && currentTarget.tagName) { > + // Firefox screws up the "click" event when moving between radio buttons > + // via arrow keys. It also screws up the "load" and "error" events on images, > + // reporting the document as the target instead of the original image. > + if (type === 'load' || type === 'error' || > + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' > + && currentTarget.type === 'radio')) > + node = currentTarget; > + } > + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; > + return Element.extend(node); > }, > > findElement: function(event, expression) { > @@ -3791,11 +3870,15 @@ Event.Methods = (function() { > }, > > pointer: function(event) { > + var docElement = document.documentElement, > + body = document.body || { scrollLeft: 0, scrollTop: 0 }; > return { > x: event.pageX || (event.clientX + > - (document.documentElement.scrollLeft || document.body.scrollLeft)), > + (docElement.scrollLeft || body.scrollLeft) - > + (docElement.clientLeft || 0)), > y: event.pageY || (event.clientY + > - (document.documentElement.scrollTop || document.body.scrollTop)) > + (docElement.scrollTop || body.scrollTop) - > + (docElement.clientTop || 0)) > }; > }, > > @@ -3840,7 +3923,7 @@ Event.extend = (function() { > }; > > } else { > - Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; > + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; > Object.extend(Event.prototype, methods); > return Prototype.K; > } > @@ -3850,9 +3933,9 @@ Object.extend(Event, (function() { > var cache = Event.cache; > > function getEventID(element) { > - if (element._eventID) return element._eventID; > + if (element._prototypeEventID) return element._prototypeEventID[0]; > arguments.callee.id = arguments.callee.id || 1; > - return element._eventID = ++arguments.callee.id; > + return element._prototypeEventID = [++arguments.callee.id]; > } > > function getDOMEventName(eventName) { > @@ -3880,7 +3963,7 @@ Object.extend(Event, (function() { > return false; > > Event.extend(event); > - handler.call(element, event) > + handler.call(element, event); > }; > > wrapper.handler = handler; > @@ -3905,10 +3988,20 @@ Object.extend(Event, (function() { > cache[id][eventName] = null; > } > > + > + // Internet Explorer needs to remove event handlers on page unload > + // in order to avoid memory leaks. > if (window.attachEvent) { > window.attachEvent("onunload", destroyCache); > } > > + // Safari has a dummy event handler on page unload so that it won't > + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" > + // object when page is returned to via the back button using its bfcache. > + if (Prototype.Browser.WebKit) { > + window.addEventListener('unload', Prototype.emptyFunction, false); > + } > + > return { > observe: function(element, eventName, handler) { > element = $(element); > @@ -3962,11 +4055,12 @@ Object.extend(Event, (function() { > if (element == document && document.createEvent && !element.dispatchEvent) > element = document.documentElement; > > + var event; > if (document.createEvent) { > - var event = document.createEvent("HTMLEvents"); > + event = document.createEvent("HTMLEvents"); > event.initEvent("dataavailable", true, true); > } else { > - var event = document.createEventObject(); > + event = document.createEventObject(); > event.eventType = "ondataavailable"; > } > > @@ -3995,20 +4089,21 @@ Element.addMethods({ > Object.extend(document, { > fire: Element.Methods.fire.methodize(), > observe: Element.Methods.observe.methodize(), > - stopObserving: Element.Methods.stopObserving.methodize() > + stopObserving: Element.Methods.stopObserving.methodize(), > + loaded: false > }); > > (function() { > /* Support for the DOMContentLoaded event is based on work by Dan Webb, > Matthias Miller, Dean Edwards and John Resig. */ > > - var timer, fired = false; > + var timer; > > function fireContentLoadedEvent() { > - if (fired) return; > + if (document.loaded) return; > if (timer) window.clearInterval(timer); > document.fire("dom:loaded"); > - fired = true; > + document.loaded = true; > } > > if (document.addEventListener) { > ACK From jboggs at redhat.com Mon Jul 20 15:57:24 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 11:57:24 -0400 Subject: [Ovirt-devel] [PATCH server 4/8] Switch from old connection style to current. In-Reply-To: <1248101917-7586-5-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-5-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A6493E4.2020702@redhat.com> Jason Guiditta wrote: > establish_connetion has been deprecated, so switch to > setup_connection. > > Signed-off-by: Jason Guiditta > --- > src/app/helpers/ldap_connection.rb | 2 +- > src/script/grant_admin_privileges | 2 +- > src/vendor/plugins/active_ldap/init.rb | 2 +- > 3 files changed, 3 insertions(+), 3 deletions(-) > > diff --git a/src/app/helpers/ldap_connection.rb b/src/app/helpers/ldap_connection.rb > index af8b41b..b8cb0e3 100644 > --- a/src/app/helpers/ldap_connection.rb > +++ b/src/app/helpers/ldap_connection.rb > @@ -31,7 +31,7 @@ class LDAPConnection > host = @@config[ENV['RAILS_ENV']]["host"] if host == nil > port = @@config[ENV['RAILS_ENV']]["port"] if port == nil > > - ActiveLdap::Base.establish_connection(:host => host, > + ActiveLdap::Base.setup_connection(:host => host, > :port => port, > :base => base) if LDAPConnection.connected? == false > end > diff --git a/src/script/grant_admin_privileges b/src/script/grant_admin_privileges > index cdf36e7..a0e340a 100755 > --- a/src/script/grant_admin_privileges > +++ b/src/script/grant_admin_privileges > @@ -9,7 +9,7 @@ ldap_config = YAML::load(File.open(File.dirname(__FILE__) +"/../config/ldap.yml" > uid = ARGV[0] > base, host = ldap_config["production"]["base"], ldap_config["production"]["host"] > > -ActiveLdap::Base.establish_connection :base => base, :host => host, :try_sasl => false > +ActiveLdap::Base.setup_connection :base => base, :host => host, :try_sasl => false > > if Account.exists?("uid=#{uid}") > puts "Creating an admin account for #{uid}..." > diff --git a/src/vendor/plugins/active_ldap/init.rb b/src/vendor/plugins/active_ldap/init.rb > index 3b4291e..e8395cf 100644 > --- a/src/vendor/plugins/active_ldap/init.rb > +++ b/src/vendor/plugins/active_ldap/init.rb > @@ -13,7 +13,7 @@ ldap_configuration_file = File.join(RAILS_ROOT, 'config', 'ldap.yml') > if File.exist?(ldap_configuration_file) > configurations = YAML.load(ERB.new(IO.read(ldap_configuration_file)).result) > ActiveLdap::Base.configurations = configurations > - ActiveLdap::Base.establish_connection > + ActiveLdap::Base.setup_connection > else > ActiveLdap::Base.class_eval do > format = _("You should run 'script/generator scaffold_active_ldap' to make %s.") > ACK From jboggs at redhat.com Mon Jul 20 15:57:44 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 11:57:44 -0400 Subject: [Ovirt-devel] [PATCH server 8/8] Wrapper for mongrel to run with rails 2.3.2 In-Reply-To: <1248101917-7586-8-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-8-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A6493F8.4090201@redhat.com> Jason Guiditta wrote: > This file is needed because we use --prefix when running > mongrel_rails (which I think we need for now). It wraps > the AbstractRequest call that is no longer in rails (but > triggered by --prefix) so mongrel does not constantly die. > It is possible there is a way now in rails itself to specify > something in place of this flag, but I have not been able > to find where to put that setting yet. > > Signed-off-by: Jason Guiditta > --- > src/config/initializers/abstract_request.rb | 14 ++++++++++++++ > 1 files changed, 14 insertions(+), 0 deletions(-) > create mode 100644 src/config/initializers/abstract_request.rb > > diff --git a/src/config/initializers/abstract_request.rb b/src/config/initializers/abstract_request.rb > new file mode 100644 > index 0000000..938106d > --- /dev/null > +++ b/src/config/initializers/abstract_request.rb > @@ -0,0 +1,14 @@ > +#This is a (hopefully) temporary workaround to help > +#mongrel, since it hooks into the now-gone AbstracRequest > +#class. > + > +module ActionController > + class AbstractRequest < ActionController::Request > + def self.relative_url_root=(path) > + ActionController::Base.relative_url_root=(path) > + end > + def self.relative_url_root > + ActionController::Base.relative_url_root > + end > + end > +end > ACK From jboggs at redhat.com Mon Jul 20 16:21:24 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 12:21:24 -0400 Subject: [Ovirt-devel] [PATCH server 2/8] test_helper and unit test updates for Rails 2.3.2 In-Reply-To: <1248101917-7586-3-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-3-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A649984.7040703@redhat.com> Jason Guiditta wrote: > Rails 2.3.2 changes test_helper use ActiveSupport::TestCase > rather than Test::Unit::TestCase, so unit test need to be > updated accordingly as well. > > Signed-off-by: Jason Guiditta > --- > src/test/test_helper.rb | 2 +- > src/test/unit/active_record_env_test.rb | 2 +- > src/test/unit/host_browser_awaken_test.rb | 2 +- > src/test/unit/host_test.rb | 2 +- > src/test/unit/nic_test.rb | 2 +- > src/test/unit/permission_test.rb | 2 +- > src/test/unit/pool_test.rb | 2 +- > src/test/unit/quota_test.rb | 2 +- > src/test/unit/storage_pool_test.rb | 2 +- > src/test/unit/storage_volume_test.rb | 2 +- > src/test/unit/task_test.rb | 2 +- > 11 files changed, 11 insertions(+), 11 deletions(-) > > diff --git a/src/test/test_helper.rb b/src/test/test_helper.rb > index fc84648..009dfc8 100644 > --- a/src/test/test_helper.rb > +++ b/src/test/test_helper.rb > @@ -21,7 +21,7 @@ ENV["RAILS_ENV"] = "test" > require File.expand_path(File.dirname(__FILE__) + "/../config/environment") > require 'test_help' > > -class Test::Unit::TestCase > +class ActiveSupport::TestCase > # Transactional fixtures accelerate your tests by wrapping each test method > # in a transaction that's rolled back on completion. This ensures that the > # test database remains unchanged so your fixtures don't have to be reloaded > diff --git a/src/test/unit/active_record_env_test.rb b/src/test/unit/active_record_env_test.rb > index 26fa139..a9a9c5a 100644 > --- a/src/test/unit/active_record_env_test.rb > +++ b/src/test/unit/active_record_env_test.rb > @@ -20,7 +20,7 @@ > require File.dirname(__FILE__) + '/../test_helper' > require File.dirname(__FILE__) + '/../../dutils/active_record_env' > > -class ActiveRecordEnvTest < Test::Unit::TestCase > +class ActiveRecordEnvTest < ActiveSupport::TestCase > fixtures :pools, :hosts, :vms, :boot_types, > :networks, :nics, :ip_addresses, :privileges, :roles, :permissions, > :quotas, :storage_pools, :storage_volumes, :tasks > diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb > index d251e90..a7cf31c 100644 > --- a/src/test/unit/host_browser_awaken_test.rb > +++ b/src/test/unit/host_browser_awaken_test.rb > @@ -31,7 +31,7 @@ require 'host-browser' > # +HostBrowserAwakenTest+ ensures that the host-browser daemon works correctly > # during the identify phase of operation. > # > -class HostBrowserAwakenTest < Test::Unit::TestCase > +class HostBrowserAwakenTest < ActiveSupport::TestCase > > def setup > @session = flexmock('session') > diff --git a/src/test/unit/host_test.rb b/src/test/unit/host_test.rb > index 338fbaf..4d40990 100644 > --- a/src/test/unit/host_test.rb > +++ b/src/test/unit/host_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class HostTest < Test::Unit::TestCase > +class HostTest < ActiveSupport::TestCase > fixtures :hosts > fixtures :pools > fixtures :vms > diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb > index 07f54c6..46fab10 100644 > --- a/src/test/unit/nic_test.rb > +++ b/src/test/unit/nic_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class NicTest < Test::Unit::TestCase > +class NicTest < ActiveSupport::TestCase > fixtures :ip_addresses > fixtures :nics > fixtures :hosts > diff --git a/src/test/unit/permission_test.rb b/src/test/unit/permission_test.rb > index 2ac78d5..344e615 100644 > --- a/src/test/unit/permission_test.rb > +++ b/src/test/unit/permission_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class PermissionTest < Test::Unit::TestCase > +class PermissionTest < ActiveSupport::TestCase > fixtures :privileges, :roles, :permissions > fixtures :pools > > diff --git a/src/test/unit/pool_test.rb b/src/test/unit/pool_test.rb > index c9a5554..bf9164d 100644 > --- a/src/test/unit/pool_test.rb > +++ b/src/test/unit/pool_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class PoolTest < Test::Unit::TestCase > +class PoolTest < ActiveSupport::TestCase > fixtures :pools > > def setup > diff --git a/src/test/unit/quota_test.rb b/src/test/unit/quota_test.rb > index 5903cc8..ec2f5cd 100644 > --- a/src/test/unit/quota_test.rb > +++ b/src/test/unit/quota_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class QuotaTest < Test::Unit::TestCase > +class QuotaTest < ActiveSupport::TestCase > fixtures :quotas > fixtures :pools > > diff --git a/src/test/unit/storage_pool_test.rb b/src/test/unit/storage_pool_test.rb > index 4e18a8c..bd969ad 100644 > --- a/src/test/unit/storage_pool_test.rb > +++ b/src/test/unit/storage_pool_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class StoragePoolTest < Test::Unit::TestCase > +class StoragePoolTest < ActiveSupport::TestCase > fixtures :storage_pools > fixtures :pools > fixtures :vms > diff --git a/src/test/unit/storage_volume_test.rb b/src/test/unit/storage_volume_test.rb > index f0e144e..4ec8760 100644 > --- a/src/test/unit/storage_volume_test.rb > +++ b/src/test/unit/storage_volume_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class StorageVolumeTest < Test::Unit::TestCase > +class StorageVolumeTest < ActiveSupport::TestCase > fixtures :storage_volumes > fixtures :storage_pools > fixtures :vms > diff --git a/src/test/unit/task_test.rb b/src/test/unit/task_test.rb > index 761e739..0f94052 100644 > --- a/src/test/unit/task_test.rb > +++ b/src/test/unit/task_test.rb > @@ -19,7 +19,7 @@ > > require File.dirname(__FILE__) + '/../test_helper' > > -class TaskTest < Test::Unit::TestCase > +class TaskTest < ActiveSupport::TestCase > fixtures :pools, :hosts, :vms, :privileges, :roles, :permissions, :tasks > > def setup > ack, passed no errors From sseago at redhat.com Mon Jul 20 16:24:10 2009 From: sseago at redhat.com (Scott Seago) Date: Mon, 20 Jul 2009 12:24:10 -0400 Subject: [Ovirt-devel] [PATCH server 5/8] No need to track schema.rb and logs right now. In-Reply-To: <1248101917-7586-6-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-6-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A649A2A.5090709@redhat.com> Jason Guiditta wrote: > Signed-off-by: Jason Guiditta > --- > .gitignore | 2 ++ > 1 files changed, 2 insertions(+), 0 deletions(-) > > diff --git a/.gitignore b/.gitignore > index 0fd1d9b..9cfdd9f 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -13,3 +13,5 @@ missing > stamp-h1 > ovirt-server*.gz > ovirt-server.spec > +schema.rb > +log/ > ACK. no testing required here :-) From jboggs at redhat.com Mon Jul 20 16:29:53 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 12:29:53 -0400 Subject: [Ovirt-devel] [PATCH server 6/8] Update will_paginate to latest version. In-Reply-To: <1248102236.4307.1.camel@lenovo> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248102236.4307.1.camel@lenovo> Message-ID: <4A649B81.7030808@redhat.com> Jason Guiditta wrote: > The file git send-email doesnt like is attached > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK grids in vm pools display properly From jboggs at redhat.com Mon Jul 20 16:36:49 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 12:36:49 -0400 Subject: [Ovirt-devel] [PATCH server 7/8] Install render_component plugin. In-Reply-To: <1248101917-7586-7-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-7-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A649D21.2010303@redhat.com> Jason Guiditta wrote: > This is no longer in core rails (and we should > look at removing it's use altogether asap). > > Signed-off-by: Jason Guiditta > --- > src/vendor/plugins/render_component/README | 37 +++++ > src/vendor/plugins/render_component/Rakefile | 22 +++ > src/vendor/plugins/render_component/init.rb | 2 + > .../plugins/render_component/lib/components.rb | 141 ++++++++++++++++++++ > .../plugins/render_component/test/abstract_unit.rb | 8 + > .../render_component/test/components_test.rb | 136 +++++++++++++++++++ > 6 files changed, 346 insertions(+), 0 deletions(-) > create mode 100644 src/vendor/plugins/render_component/README > create mode 100644 src/vendor/plugins/render_component/Rakefile > create mode 100644 src/vendor/plugins/render_component/init.rb > create mode 100644 src/vendor/plugins/render_component/lib/components.rb > create mode 100644 src/vendor/plugins/render_component/test/abstract_unit.rb > create mode 100644 src/vendor/plugins/render_component/test/components_test.rb > > diff --git a/src/vendor/plugins/render_component/README b/src/vendor/plugins/render_component/README > new file mode 100644 > index 0000000..a6a318a > --- /dev/null > +++ b/src/vendor/plugins/render_component/README > @@ -0,0 +1,37 @@ > +Components allow you to call other actions for their rendered response while executing another action. You can either delegate > +the entire response rendering or you can mix a partial response in with your other content. > + > + class WeblogController < ActionController::Base > + # Performs a method and then lets hello_world output its render > + def delegate_action > + do_other_stuff_before_hello_world > + render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" } > + end > + end > + > + class GreeterController < ActionController::Base > + def hello_world > + render :text => "#{params[:person]} says, Hello World!" > + end > + end > + > +The same can be done in a view to do a partial rendering: > + > + Let's see a greeting: > + <%= render_component :controller => "greeter", :action => "hello_world" %> > + > +It is also possible to specify the controller as a class constant, bypassing the inflector > +code to compute the controller class at runtime: > + > +<%= render_component :controller => GreeterController, :action => "hello_world" %> > + > +== When to use components > + > +Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and > +conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead, > +reserve components to those rare cases where you truly have reusable view and controller elements that can be employed > +across many applications at once. > + > +So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters. > + > +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license > \ No newline at end of file > diff --git a/src/vendor/plugins/render_component/Rakefile b/src/vendor/plugins/render_component/Rakefile > new file mode 100644 > index 0000000..d99407b > --- /dev/null > +++ b/src/vendor/plugins/render_component/Rakefile > @@ -0,0 +1,22 @@ > +require 'rake' > +require 'rake/testtask' > +require 'rake/rdoctask' > + > +desc 'Default: run unit tests.' > +task :default => :test > + > +desc 'Test the components plugin.' > +Rake::TestTask.new(:test) do |t| > + t.libs << 'lib' > + t.pattern = 'test/**/*_test.rb' > + t.verbose = true > +end > + > +desc 'Generate documentation for the components plugin.' > +Rake::RDocTask.new(:rdoc) do |rdoc| > + rdoc.rdoc_dir = 'rdoc' > + rdoc.title = 'Components' > + rdoc.options << '--line-numbers' << '--inline-source' > + rdoc.rdoc_files.include('README') > + rdoc.rdoc_files.include('lib/**/*.rb') > +end > \ No newline at end of file > diff --git a/src/vendor/plugins/render_component/init.rb b/src/vendor/plugins/render_component/init.rb > new file mode 100644 > index 0000000..b428c52 > --- /dev/null > +++ b/src/vendor/plugins/render_component/init.rb > @@ -0,0 +1,2 @@ > +require 'components' > +ActionController::Base.send :include, Components > diff --git a/src/vendor/plugins/render_component/lib/components.rb b/src/vendor/plugins/render_component/lib/components.rb > new file mode 100644 > index 0000000..9263cde > --- /dev/null > +++ b/src/vendor/plugins/render_component/lib/components.rb > @@ -0,0 +1,141 @@ > +module Components > + def self.included(base) #:nodoc: > + base.class_eval do > + include InstanceMethods > + extend ClassMethods > + helper HelperMethods > + > + # If this controller was instantiated to process a component request, > + # +parent_controller+ points to the instantiator of this controller. > + attr_accessor :parent_controller > + > + alias_method_chain :process_cleanup, :render_component > + alias_method_chain :session=, :render_component > + alias_method_chain :flash, :render_component > + alias_method_chain :perform_action, :render_component > + alias_method_chain :send_response, :render_component > + > + alias_method :component_request?, :parent_controller > + end > + end > + > + module ClassMethods > + # Track parent controller to identify component requests > + def process_with_components(request, response, parent_controller = nil) #:nodoc: > + controller = new > + controller.parent_controller = parent_controller > + controller.process(request, response) > + end > + end > + > + module HelperMethods > + def render_component(options) > + @controller.__send__(:render_component_as_string, options) > + end > + end > + > + module InstanceMethods > + def perform_action_with_render_component > + perform_action_without_render_component > + remove_instance_variable(:@_flash) if defined? @_flash > + end > + > + # Extracts the action_name from the request parameters and performs that action. > + def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc: > + flash.discard if component_request? > + process_without_components(request, response, method, *arguments) > + end > + > + def send_response_with_render_component > + response.prepare! unless component_request? > + response > + end > + > + protected > + # Renders the component specified as the response for the current method > + def render_component(options) #:doc: > + component_logging(options) do > + render_for_text(component_response(options, true).body, response.status) > + end > + end > + > + # Returns the component response as a string > + def render_component_as_string(options) #:doc: > + component_logging(options) do > + response = component_response(options, false) > + > + if redirected = response.redirected_to > + render_component_as_string(redirected) > + else > + response.body > + end > + end > + end > + > + def flash_with_render_component(refresh = false) #:nodoc: > + if !defined?(@_flash) || refresh > + @_flash = > + if defined?(@parent_controller) > + @parent_controller.flash > + else > + flash_without_render_component > + end > + end > + @_flash > + end > + > + private > + def component_response(options, reuse_response) > + klass = component_class(options) > + request = request_for_component(klass.controller_path, options) > + new_response = reuse_response ? response : response.dup > + > + klass.process_with_components(request, new_response, self) > + end > + > + # determine the controller class for the component request > + def component_class(options) > + if controller = options[:controller] > + controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize > + else > + self.class > + end > + end > + > + # Create a new request object based on the current request. > + # The new request inherits the session from the current request, > + # bypassing any session options set for the component controller's class > + def request_for_component(controller_path, options) > + new_request = request.dup > + new_request.session = request.session > + > + new_request.instance_variable_set( > + :@parameters, > + (options[:params] || {}).with_indifferent_access.update( > + "controller" => controller_path, "action" => options[:action], "id" => options[:id] > + ) > + ) > + > + new_request > + end > + > + def component_logging(options) > + if logger > + logger.info "Start rendering component (#{options.inspect}): " > + result = yield > + logger.info "\n\nEnd of component rendering" > + result > + else > + yield > + end > + end > + > + def session_with_render_component=(options = {}) > + session_without_render_component=(options) unless component_request? > + end > + > + def process_cleanup_with_render_component > + process_cleanup_without_render_component unless component_request? > + end > + end > +end > diff --git a/src/vendor/plugins/render_component/test/abstract_unit.rb b/src/vendor/plugins/render_component/test/abstract_unit.rb > new file mode 100644 > index 0000000..f022971 > --- /dev/null > +++ b/src/vendor/plugins/render_component/test/abstract_unit.rb > @@ -0,0 +1,8 @@ > +require 'rubygems' > +require 'test/unit' > +require 'action_controller' > +require 'action_controller/test_process' > +ActionController::Routing::Routes.reload rescue nil > + > +$: << File.dirname(__FILE__) + "/../lib" > +require File.dirname(__FILE__) + "/../init" > diff --git a/src/vendor/plugins/render_component/test/components_test.rb b/src/vendor/plugins/render_component/test/components_test.rb > new file mode 100644 > index 0000000..53fcd7f > --- /dev/null > +++ b/src/vendor/plugins/render_component/test/components_test.rb > @@ -0,0 +1,136 @@ > +require File.dirname(__FILE__) + '/abstract_unit' > + > +class CallerController < ActionController::Base > + def calling_from_controller > + render_component(:controller => "callee", :action => "being_called") > + end > + > + def calling_from_controller_with_params > + render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" }) > + end > + > + def calling_from_controller_with_different_status_code > + render_component(:controller => "callee", :action => "blowing_up") > + end > + > + def calling_from_template > + render :inline => "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>" > + end > + > + def internal_caller > + render :inline => "Are you there? <%= render_component(:action => 'internal_callee') %>" > + end > + > + def internal_callee > + render :text => "Yes, ma'am" > + end > + > + def set_flash > + render_component(:controller => "callee", :action => "set_flash") > + end > + > + def use_flash > + render_component(:controller => "callee", :action => "use_flash") > + end > + > + def calling_redirected > + render_component(:controller => "callee", :action => "redirected") > + end > + > + def calling_redirected_as_string > + render :inline => "<%= render_component(:controller => 'callee', :action => 'redirected') %>" > + end > + > + def rescue_action(e) raise end > +end > + > +class CalleeController < ActionController::Base > + def being_called > + render :text => "#{params[:name] || "Lady"} of the House, speaking" > + end > + > + def blowing_up > + render :text => "It's game over, man, just game over, man!", :status => 500 > + end > + > + def set_flash > + flash[:notice] = 'My stoney baby' > + render :text => 'flash is set' > + end > + > + def use_flash > + render :text => flash[:notice] || 'no flash' > + end > + > + def redirected > + redirect_to :controller => "callee", :action => "being_called" > + end > + > + def rescue_action(e) raise end > +end > + > +class ComponentsTest < ActionController::TestCase > + tests CallerController > + > + def test_calling_from_controller > + get :calling_from_controller > + assert_equal "Lady of the House, speaking", @response.body > + end > + > + def test_calling_from_controller_with_params > + get :calling_from_controller_with_params > + assert_equal "David of the House, speaking", @response.body > + end > + > + def test_calling_from_controller_with_different_status_code > + get :calling_from_controller_with_different_status_code > + assert_equal 500, @response.response_code > + end > + > + def test_calling_from_template > + get :calling_from_template > + assert_equal "Ring, ring: Lady of the House, speaking", @response.body > + end > + > + def test_etag_is_set_for_parent_template_when_calling_from_template > + get :calling_from_template > + expected_etag = etag_for("Ring, ring: Lady of the House, speaking") > + assert_equal expected_etag, @response.headers['ETag'] > + end > + > + def test_internal_calling > + get :internal_caller > + assert_equal "Are you there? Yes, ma'am", @response.body > + end > + > + def test_flash > + get :set_flash > + assert_equal 'My stoney baby', flash[:notice] > + get :use_flash > + assert_equal 'My stoney baby', @response.body > + get :use_flash > + assert_equal 'no flash', @response.body > + end > + > + def test_component_redirect_redirects > + get :calling_redirected > + > + assert_redirected_to :controller=>"callee", :action => "being_called" > + end > + > + def test_component_multiple_redirect_redirects > + test_component_redirect_redirects > + test_internal_calling > + end > + > + def test_component_as_string_redirect_renders_redirected_action > + get :calling_redirected_as_string > + > + assert_equal "Lady of the House, speaking", @response.body > + end > + > + protected > + def etag_for(text) > + %("#{Digest::MD5.hexdigest(text)}") > + end > +end > ACK From jboggs at redhat.com Mon Jul 20 16:42:35 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 12:42:35 -0400 Subject: [Ovirt-devel] [PATCH server 7/8] Install render_component plugin. In-Reply-To: <1248101917-7586-7-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-7-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A649E7B.2070508@redhat.com> Jason Guiditta wrote: > This is no longer in core rails (and we should > look at removing it's use altogether asap). > > Signed-off-by: Jason Guiditta > --- > src/vendor/plugins/render_component/README | 37 +++++ > src/vendor/plugins/render_component/Rakefile | 22 +++ > src/vendor/plugins/render_component/init.rb | 2 + > .../plugins/render_component/lib/components.rb | 141 ++++++++++++++++++++ > .../plugins/render_component/test/abstract_unit.rb | 8 + > .../render_component/test/components_test.rb | 136 +++++++++++++++++++ > 6 files changed, 346 insertions(+), 0 deletions(-) > create mode 100644 src/vendor/plugins/render_component/README > create mode 100644 src/vendor/plugins/render_component/Rakefile > create mode 100644 src/vendor/plugins/render_component/init.rb > create mode 100644 src/vendor/plugins/render_component/lib/components.rb > create mode 100644 src/vendor/plugins/render_component/test/abstract_unit.rb > create mode 100644 src/vendor/plugins/render_component/test/components_test.rb > > diff --git a/src/vendor/plugins/render_component/README b/src/vendor/plugins/render_component/README > new file mode 100644 > index 0000000..a6a318a > --- /dev/null > +++ b/src/vendor/plugins/render_component/README > @@ -0,0 +1,37 @@ > +Components allow you to call other actions for their rendered response while executing another action. You can either delegate > +the entire response rendering or you can mix a partial response in with your other content. > + > + class WeblogController < ActionController::Base > + # Performs a method and then lets hello_world output its render > + def delegate_action > + do_other_stuff_before_hello_world > + render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" } > + end > + end > + > + class GreeterController < ActionController::Base > + def hello_world > + render :text => "#{params[:person]} says, Hello World!" > + end > + end > + > +The same can be done in a view to do a partial rendering: > + > + Let's see a greeting: > + <%= render_component :controller => "greeter", :action => "hello_world" %> > + > +It is also possible to specify the controller as a class constant, bypassing the inflector > +code to compute the controller class at runtime: > + > +<%= render_component :controller => GreeterController, :action => "hello_world" %> > + > +== When to use components > + > +Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and > +conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead, > +reserve components to those rare cases where you truly have reusable view and controller elements that can be employed > +across many applications at once. > + > +So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters. > + > +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license > \ No newline at end of file > diff --git a/src/vendor/plugins/render_component/Rakefile b/src/vendor/plugins/render_component/Rakefile > new file mode 100644 > index 0000000..d99407b > --- /dev/null > +++ b/src/vendor/plugins/render_component/Rakefile > @@ -0,0 +1,22 @@ > +require 'rake' > +require 'rake/testtask' > +require 'rake/rdoctask' > + > +desc 'Default: run unit tests.' > +task :default => :test > + > +desc 'Test the components plugin.' > +Rake::TestTask.new(:test) do |t| > + t.libs << 'lib' > + t.pattern = 'test/**/*_test.rb' > + t.verbose = true > +end > + > +desc 'Generate documentation for the components plugin.' > +Rake::RDocTask.new(:rdoc) do |rdoc| > + rdoc.rdoc_dir = 'rdoc' > + rdoc.title = 'Components' > + rdoc.options << '--line-numbers' << '--inline-source' > + rdoc.rdoc_files.include('README') > + rdoc.rdoc_files.include('lib/**/*.rb') > +end > \ No newline at end of file > diff --git a/src/vendor/plugins/render_component/init.rb b/src/vendor/plugins/render_component/init.rb > new file mode 100644 > index 0000000..b428c52 > --- /dev/null > +++ b/src/vendor/plugins/render_component/init.rb > @@ -0,0 +1,2 @@ > +require 'components' > +ActionController::Base.send :include, Components > diff --git a/src/vendor/plugins/render_component/lib/components.rb b/src/vendor/plugins/render_component/lib/components.rb > new file mode 100644 > index 0000000..9263cde > --- /dev/null > +++ b/src/vendor/plugins/render_component/lib/components.rb > @@ -0,0 +1,141 @@ > +module Components > + def self.included(base) #:nodoc: > + base.class_eval do > + include InstanceMethods > + extend ClassMethods > + helper HelperMethods > + > + # If this controller was instantiated to process a component request, > + # +parent_controller+ points to the instantiator of this controller. > + attr_accessor :parent_controller > + > + alias_method_chain :process_cleanup, :render_component > + alias_method_chain :session=, :render_component > + alias_method_chain :flash, :render_component > + alias_method_chain :perform_action, :render_component > + alias_method_chain :send_response, :render_component > + > + alias_method :component_request?, :parent_controller > + end > + end > + > + module ClassMethods > + # Track parent controller to identify component requests > + def process_with_components(request, response, parent_controller = nil) #:nodoc: > + controller = new > + controller.parent_controller = parent_controller > + controller.process(request, response) > + end > + end > + > + module HelperMethods > + def render_component(options) > + @controller.__send__(:render_component_as_string, options) > + end > + end > + > + module InstanceMethods > + def perform_action_with_render_component > + perform_action_without_render_component > + remove_instance_variable(:@_flash) if defined? @_flash > + end > + > + # Extracts the action_name from the request parameters and performs that action. > + def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc: > + flash.discard if component_request? > + process_without_components(request, response, method, *arguments) > + end > + > + def send_response_with_render_component > + response.prepare! unless component_request? > + response > + end > + > + protected > + # Renders the component specified as the response for the current method > + def render_component(options) #:doc: > + component_logging(options) do > + render_for_text(component_response(options, true).body, response.status) > + end > + end > + > + # Returns the component response as a string > + def render_component_as_string(options) #:doc: > + component_logging(options) do > + response = component_response(options, false) > + > + if redirected = response.redirected_to > + render_component_as_string(redirected) > + else > + response.body > + end > + end > + end > + > + def flash_with_render_component(refresh = false) #:nodoc: > + if !defined?(@_flash) || refresh > + @_flash = > + if defined?(@parent_controller) > + @parent_controller.flash > + else > + flash_without_render_component > + end > + end > + @_flash > + end > + > + private > + def component_response(options, reuse_response) > + klass = component_class(options) > + request = request_for_component(klass.controller_path, options) > + new_response = reuse_response ? response : response.dup > + > + klass.process_with_components(request, new_response, self) > + end > + > + # determine the controller class for the component request > + def component_class(options) > + if controller = options[:controller] > + controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize > + else > + self.class > + end > + end > + > + # Create a new request object based on the current request. > + # The new request inherits the session from the current request, > + # bypassing any session options set for the component controller's class > + def request_for_component(controller_path, options) > + new_request = request.dup > + new_request.session = request.session > + > + new_request.instance_variable_set( > + :@parameters, > + (options[:params] || {}).with_indifferent_access.update( > + "controller" => controller_path, "action" => options[:action], "id" => options[:id] > + ) > + ) > + > + new_request > + end > + > + def component_logging(options) > + if logger > + logger.info "Start rendering component (#{options.inspect}): " > + result = yield > + logger.info "\n\nEnd of component rendering" > + result > + else > + yield > + end > + end > + > + def session_with_render_component=(options = {}) > + session_without_render_component=(options) unless component_request? > + end > + > + def process_cleanup_with_render_component > + process_cleanup_without_render_component unless component_request? > + end > + end > +end > diff --git a/src/vendor/plugins/render_component/test/abstract_unit.rb b/src/vendor/plugins/render_component/test/abstract_unit.rb > new file mode 100644 > index 0000000..f022971 > --- /dev/null > +++ b/src/vendor/plugins/render_component/test/abstract_unit.rb > @@ -0,0 +1,8 @@ > +require 'rubygems' > +require 'test/unit' > +require 'action_controller' > +require 'action_controller/test_process' > +ActionController::Routing::Routes.reload rescue nil > + > +$: << File.dirname(__FILE__) + "/../lib" > +require File.dirname(__FILE__) + "/../init" > diff --git a/src/vendor/plugins/render_component/test/components_test.rb b/src/vendor/plugins/render_component/test/components_test.rb > new file mode 100644 > index 0000000..53fcd7f > --- /dev/null > +++ b/src/vendor/plugins/render_component/test/components_test.rb > @@ -0,0 +1,136 @@ > +require File.dirname(__FILE__) + '/abstract_unit' > + > +class CallerController < ActionController::Base > + def calling_from_controller > + render_component(:controller => "callee", :action => "being_called") > + end > + > + def calling_from_controller_with_params > + render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" }) > + end > + > + def calling_from_controller_with_different_status_code > + render_component(:controller => "callee", :action => "blowing_up") > + end > + > + def calling_from_template > + render :inline => "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>" > + end > + > + def internal_caller > + render :inline => "Are you there? <%= render_component(:action => 'internal_callee') %>" > + end > + > + def internal_callee > + render :text => "Yes, ma'am" > + end > + > + def set_flash > + render_component(:controller => "callee", :action => "set_flash") > + end > + > + def use_flash > + render_component(:controller => "callee", :action => "use_flash") > + end > + > + def calling_redirected > + render_component(:controller => "callee", :action => "redirected") > + end > + > + def calling_redirected_as_string > + render :inline => "<%= render_component(:controller => 'callee', :action => 'redirected') %>" > + end > + > + def rescue_action(e) raise end > +end > + > +class CalleeController < ActionController::Base > + def being_called > + render :text => "#{params[:name] || "Lady"} of the House, speaking" > + end > + > + def blowing_up > + render :text => "It's game over, man, just game over, man!", :status => 500 > + end > + > + def set_flash > + flash[:notice] = 'My stoney baby' > + render :text => 'flash is set' > + end > + > + def use_flash > + render :text => flash[:notice] || 'no flash' > + end > + > + def redirected > + redirect_to :controller => "callee", :action => "being_called" > + end > + > + def rescue_action(e) raise end > +end > + > +class ComponentsTest < ActionController::TestCase > + tests CallerController > + > + def test_calling_from_controller > + get :calling_from_controller > + assert_equal "Lady of the House, speaking", @response.body > + end > + > + def test_calling_from_controller_with_params > + get :calling_from_controller_with_params > + assert_equal "David of the House, speaking", @response.body > + end > + > + def test_calling_from_controller_with_different_status_code > + get :calling_from_controller_with_different_status_code > + assert_equal 500, @response.response_code > + end > + > + def test_calling_from_template > + get :calling_from_template > + assert_equal "Ring, ring: Lady of the House, speaking", @response.body > + end > + > + def test_etag_is_set_for_parent_template_when_calling_from_template > + get :calling_from_template > + expected_etag = etag_for("Ring, ring: Lady of the House, speaking") > + assert_equal expected_etag, @response.headers['ETag'] > + end > + > + def test_internal_calling > + get :internal_caller > + assert_equal "Are you there? Yes, ma'am", @response.body > + end > + > + def test_flash > + get :set_flash > + assert_equal 'My stoney baby', flash[:notice] > + get :use_flash > + assert_equal 'My stoney baby', @response.body > + get :use_flash > + assert_equal 'no flash', @response.body > + end > + > + def test_component_redirect_redirects > + get :calling_redirected > + > + assert_redirected_to :controller=>"callee", :action => "being_called" > + end > + > + def test_component_multiple_redirect_redirects > + test_component_redirect_redirects > + test_internal_calling > + end > + > + def test_component_as_string_redirect_renders_redirected_action > + get :calling_redirected_as_string > + > + assert_equal "Lady of the House, speaking", @response.body > + end > + > + protected > + def etag_for(text) > + %("#{Digest::MD5.hexdigest(text)}") > + end > +end > ACK, Flexchart works and no log error output From jboggs at redhat.com Mon Jul 20 16:48:04 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 20 Jul 2009 12:48:04 -0400 Subject: [Ovirt-devel] [PATCH server 3/8] Update functional tests to work with Rails 2.3.2 In-Reply-To: <1248101917-7586-4-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> <1248101917-7586-4-git-send-email-jason.guiditta@gmail.com> Message-ID: <4A649FC4.4050409@redhat.com> Jason Guiditta wrote: > Signed-off-by: Jason Guiditta > --- > .../functional/managed_node_configuration_test.rb | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb > index a0a66e9..8759b02 100644 > --- a/src/test/functional/managed_node_configuration_test.rb > +++ b/src/test/functional/managed_node_configuration_test.rb > @@ -23,7 +23,7 @@ require 'managed_node_configuration' > > # Performs unit tests on the +ManagedNodeConfiguration+ class. > # > -class ManagedNodeConfigurationTest < Test::Unit::TestCase > +class ManagedNodeConfigurationTest < ActiveSupport::TestCase > fixtures :bonding_types > fixtures :bondings > fixtures :bondings_nics > ACK From jguiditt at redhat.com Mon Jul 20 17:01:17 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Mon, 20 Jul 2009 13:01:17 -0400 Subject: [Ovirt-devel] Upgrade server to run on Rails 2.3.2/F11 In-Reply-To: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> References: <1248101917-7586-1-git-send-email-jason.guiditta@gmail.com> Message-ID: <1248109278.4307.2.camel@lenovo> On Mon, 2009-07-20 at 10:58 -0400, Jason Guiditta wrote: > Note that one of the 8 patches (#6) will be sent separately in reply > to this email, as some of the replaced lines are too long, so git > won't let me send the email. However, there is nothing wrong with > that patch, and it should be applied in the sequence listed below. > > Note also that I assume this will be tested on a clean f11 install, rather > than an upgrade of an existing ovirt server (since if it was on F11, > it was broken anyway). > > [PATCH server 1/8] Update core app config for Rails 2.3.2 > [PATCH server 2/8] test_helper and unit test updates for Rails 2.3.2 > [PATCH server 3/8] Update functional tests to work with Rails 2.3.2 > [PATCH server 4/8] Switch from old connection style to current. > [PATCH server 5/8] No need to track schema.rb and logs right now. > [PATCH server 6/8] Update will_paginate to latest version. > [PATCH server 7/8] Install render_component plugin. > [PATCH server 8/8] Wrapper for mongrel to run with rails 2.3.2 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel Set is now pushed to next. Thanks to all for testing. -j From imain at redhat.com Mon Jul 20 17:46:52 2009 From: imain at redhat.com (Ian Main) Date: Mon, 20 Jul 2009 10:46:52 -0700 Subject: [Ovirt-devel] [PATCH server] updated anyterm/ovirt integration In-Reply-To: <1247778262-30570-1-git-send-email-mmorsi@redhat.com> References: <1247778262-30570-1-git-send-email-mmorsi@redhat.com> Message-ID: <20090720104652.318da939@tp.mains.net> On Thu, 16 Jul 2009 17:04:22 -0400 Mohammed Morsi wrote: > host static anyterm content locally, > url parameterize anyterm rows/cols/general param > update spec/makefile > > Ideally I wanted and tried hard to put all this into a > seperate ovirt-server--anyterm subpackage, but we cannot > 'reopen' the ovirt server virtual host defined in > ovirt-server.conf to add the neccessary rewrite rules. it would > be nice to figure out a way to do this Holy whitespace errors batman! :) "warning: 23 lines add whitespace errors." Will get to testing shortly :) Ian From mmorsi at redhat.com Mon Jul 20 20:07:50 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Mon, 20 Jul 2009 16:07:50 -0400 Subject: [Ovirt-devel] [PATCH server] updated anyterm/ovirt integration Message-ID: <1248120470-23045-1-git-send-email-mmorsi@redhat.com> host static anyterm content locally, url parameterize anyterm rows/cols/general param update spec/makefile Ideally I wanted and tried hard to put all this into a seperate ovirt-server--anyterm subpackage, but we cannot 'reopen' the ovirt server virtual host defined in ovirt-server.conf to add the neccessary rewrite rules. it would be nice to figure out a way to do this --- Makefile.am | 1 + anyterm/anyterm.css | 132 ++++++++ anyterm/anyterm.html | 72 +++++ anyterm/anyterm.js | 799 ++++++++++++++++++++++++++++++++++++++++++++++++ anyterm/copy.gif | Bin 0 -> 911 bytes anyterm/copy.png | Bin 0 -> 232 bytes anyterm/paste.gif | Bin 0 -> 148 bytes anyterm/paste.png | Bin 0 -> 225 bytes conf/ovirt-server.conf | 13 +- ovirt-server.spec.in | 7 + scripts/ovirt-vm2node | 13 +- 11 files changed, 1021 insertions(+), 16 deletions(-) create mode 100644 anyterm/anyterm.css create mode 100644 anyterm/anyterm.html create mode 100644 anyterm/anyterm.js create mode 100644 anyterm/copy.gif create mode 100644 anyterm/copy.png create mode 100644 anyterm/paste.gif create mode 100644 anyterm/paste.png diff --git a/Makefile.am b/Makefile.am index f115c8f..a143663 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,7 @@ EXTRA_DIST = \ ovirt-server.spec.in \ scripts \ conf \ + anyterm \ src \ installer diff --git a/anyterm/anyterm.css b/anyterm/anyterm.css new file mode 100644 index 0000000..6bfea1f --- /dev/null +++ b/anyterm/anyterm.css @@ -0,0 +1,132 @@ +/* browser/anyterm.css + This file is part of Anyterm; see http://anyterm.org/ + (C) 2005-2008 Philip Endecott + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + + +/* These are the background colours: */ +.a { background-color: #000000; } /* black */ +.b { background-color: #cd0000; } /* red */ +.c { background-color: #00cd00; } /* green */ +.d { background-color: #cdcd00; } /* yellow */ +.e { background-color: #0000cd; } /* blue */ +.f { background-color: #cd00cd; } /* magenta */ +.g { background-color: #00cdcd; } /* cyan */ +.h { background-color: #e5e5e5; } /* white */ + +/* These are the foreground colours used when bold mode is NOT enabled. + They're the same as the background colours. */ +.i { color: #000000; } /* black */ +.j { color: #cd0000; } /* red */ +.k { color: #00cd00; } /* green */ +.l { color: #cdcd00; } /* yellow */ +.m { color: #0000cd; } /* blue */ +.n { color: #cd00cd; } /* magenta */ +.o { color: #00cdcd; } /* cyan */ +.p { color: #e5e5e5; } /* white */ + +/* These are the brighter foreground colours used when bold mode IS enabled. + The business with !important and .p .z is because the .p default is set + on the enclosing term element; I can't see a better way to get the desired + behaviour. */ +.z.i { color: #4d4d4d !important; } /* black */ +.z.j { color: #ff0000 !important; } /* red */ +.z.k { color: #00ff00 !important; } /* green */ +.z.l { color: #ffff00 !important; } /* yellow */ +.z.m { color: #0000ff !important; } /* blue */ +.z.n { color: #ff00ff !important; } /* magenta */ +.z.o { color: #00ffff !important; } /* cyan */ +.z.p, .p .z { color: #ffffff; } /* white */ + +/* If you want a black-on-white colour scheme like xterm, rather than the + default white-on-black, you need to change the lines for black and white + above to something like the following: + .a { background-color: #ffffff; } + .h { background-color: #000000; } + .i { color: #e5e5e5; } + .p { color: #000000; } + .z.i { color: #ffffff !important; } + .z.p, .p .z { color: #000000; } +*/ + +/* If the following rule is enabled, bold mode will actually use a bold font. + This is not a good idea in general as the bold font will probably be wider + than the normal font, messing up the layout, at least for some subset of + the character set. So it's commented out; bold characters will be + distinguished only by their brighter colours (above) */ +/* .z { font-weight: bold; } */ + +/* The cursor. You can make it blink if you really want to (on some browsers). */ +.cursor { + border: 1px solid red; + margin: -1px; +/*text-decoration: blink;*/ +} + + +/* Properties for the page outside the terminal: */ + +body { + background-color: white; + /* Don't like the white background? How about this: + background-color: #222222; + */ +} + +noscript { + /* This is for the message that users see if they don't have Javascript + enabled. We want it to be visible whatever the page background colour + (above) is set to, so we give it its own background colour. */ + color: black; + background-color: white; +} + + +/* The remaining definitions determine the appearance of the frame around the + terminal, its title bar, and buttons: */ + +.termframe { + float: left; + background-color: rgb(63,63,161); + padding: 0.4ex; +} + +.termframe p { + margin: 0; + color: white; + font-weight: bold; + font-family: sans-serif; +} + +.termframe a { + cursor: pointer; +} + +img.button { + margin: 0 3px; + cursor: pointer; + vertical-align: top; +} + +.term { + margin: 0.4ex 0 0 0; + padding: 1px; + + overflow: auto; + overflow-x: visible; +} + diff --git a/anyterm/anyterm.html b/anyterm/anyterm.html new file mode 100644 index 0000000..bf063de --- /dev/null +++ b/anyterm/anyterm.html @@ -0,0 +1,72 @@ + + + + + + +Anyterm + + + + + + + + + + + + + +
    + + + diff --git a/anyterm/anyterm.js b/anyterm/anyterm.js new file mode 100644 index 0000000..fc0aa18 --- /dev/null +++ b/anyterm/anyterm.js @@ -0,0 +1,799 @@ +// browser/anyterm.js +// This file is part of Anyterm; see http://anyterm.org/ +// (C) 2005-2006 Philip Endecott + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +var undefined; + +var url_prefix = ""; + +var frame; +var term; +var open=false; +var session; + +var method="POST"; +//var method="GET"; + +// Random sequence numbers are needed to prevent Opera from caching +// replies + +var is_opera = navigator.userAgent.toLowerCase().indexOf("opera") != -1; +if (is_opera) { + method="GET"; +} + +var seqnum_val=Math.round(Math.random()*100000); +function cachebust() { + if (is_opera) { + seqnum_val++; + return "&x="+seqnum_val; + } else { + return ""; + } +} + + +// Cross-platform creation of XMLHttpRequest object: + +function new_XMLHttpRequest() { + if (window.XMLHttpRequest) { + // For most browsers: + return new XMLHttpRequest(); + } else { + // For IE, it's active-X voodoo. + // There are different versions in different browsers. + // The ones we try are the ones that Sarissa tried. The disabled ones + // apparently also exist, but it seems to work OK without trying them. + + //try{ return new ActiveXObject("MSXML3.XMLHTTP"); } catch(e){} + try{ return new ActiveXObject("Msxml2.XMLHTTP.5.0"); } catch(e){} + try{ return new ActiveXObject("Msxml2.XMLHTTP.4.0"); } catch(e){} + try{ return new ActiveXObject("MSXML2.XMLHTTP.3.0"); } catch(e){} + try{ return new ActiveXObject("MSXML2.XMLHTTP"); } catch(e){} + //try{ return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e){} + try{ return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e){} + throw new Error("Could not find an XMLHttpRequest active-X class.") + } +} + + +// Asynchronous and Synchronous XmlHttpRequest wrappers + +// AsyncLoader is a class; an instance specifies a callback function. +// Call load to get something and the callback is invoked with the +// returned document. + +function AsyncLoader(cb) { + this.callback = cb; + this.load = function (url,query) { + var xmlhttp = new_XMLHttpRequest(); + var cbk = this.callback; + //var timeoutID = window.setTimeout("alert('No response after 20 secs')",20000); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState==4) { + //window.clearTimeout(timeoutID); + if (xmlhttp.status==200) { + cbk(xmlhttp.responseText); + } else { + alert("Server returned status code "+xmlhttp.status+":\n"+xmlhttp.statusText); + cbk(null); + } + } + } + if (method=="GET") { + xmlhttp.open(method, url+"?"+query, true); + xmlhttp.send(null); + } else if (method=="POST") { + xmlhttp.open(method, url, true); + xmlhttp.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + xmlhttp.send(query); + } + + } +} + + +// Synchronous loader is a simple function + +function sync_load(url,query) { + var xmlhttp = new_XMLHttpRequest(); + if (method=="GET") { + xmlhttp.open(method, url+"?"+query, false); + xmlhttp.send(null); + } else if (method=="POST") { + xmlhttp.open(method, url, false); + xmlhttp.setRequestHeader('Foo','1234'); + xmlhttp.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + xmlhttp.send(query); + } + if (xmlhttp.status!=200) { + alert("Server returned status code "+xmlhttp.status+":\n"+xmlhttp.statusText); + return null; + } + return xmlhttp.responseText; +} + + +// Process error message from server: + +function handle_resp_error(resp) { + if (resp.charAt(0)=="E") { + var msg = resp.substr(1); + alert(msg); + return true; + } + return false; +} + + +// Receive channel: + +var rcv_loader; + +var disp=""; + + + +function process_editscript(edscr) { + + var ndisp=""; + + var i=0; + var dp=0; + while (i=65 && kc<=90) k=String.fromCharCode(kc-64); // Ctrl-A..Z + else if (kc==219) k=String.fromCharCode(27); // Ctrl-[ + else if (kc==220) k=String.fromCharCode(28); // Ctrl-\ . + else if (kc==221) k=String.fromCharCode(29); // Ctrl-] + else if (kc==190) k=String.fromCharCode(30); // Since ctrl-^ doesn't work, map + // ctrl-. to its code. + else if (kc==32) k=String.fromCharCode(0); // Ctrl-space sends 0, like ctrl- at . + else { + key_ev_supress(ev); + return; + } + } + } + +// alert("keydown keyCode="+ev.keyCode+" which="+ev.which+ +// " shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey); + + process_key(k); + + key_ev_stop(ev); + return false; +} + + +// Open, close and initialisation: + +function open_term(rows,cols,p,charset,scrollback) { + var params = "a=open&rows="+rows+"&cols="+cols; + if (p) { + params += "&p="+p; + } + if (charset) { + params += "&ch="+charset; + } + if (scrollback) { + if (scrollback>1000) { + alert("The maximum scrollback is currently limited to 1000 lines. " + +"Please choose a smaller value and try again."); + return; + } + params += "&sb="+scrollback; + } + params += cachebust(); + var resp = sync_load(url_prefix+"anyterm-module",params); + + if (handle_resp_error(resp)) { + return; + } + + open=true; + session=resp; +} + +function close_term() { + if (!open) { + alert("Connection is not open"); + return; + } + open=false; + var resp = sync_load(url_prefix+"anyterm-module","a=close&s="+session+cachebust()); + handle_resp_error(resp); // If we get an error, we still close everything. + document.onkeypress=null; + document.onkeydown=null; + window.onbeforeunload=null; + var e; + while (e=frame.firstChild) { + frame.removeChild(e); + } + frame.className=""; + if (on_close_goto_url) { + document.location = on_close_goto_url; + } +} + + +function get_anyterm_version() { + var svn_url="$URL: http://svn.anyterm.org/anyterm/tags/releases/1.1/1.1.29/browser/anyterm.js $"; + var re = /releases\/[0-9]+\.[0-9]+\/([0-9\.]+)/; + var match = re.exec(svn_url); + if (match) { + return match[1]; + } else { + return ""; + } +} + +function substitute_variables(s) { + var version = get_anyterm_version(); + if (version!="") { + version="-"+version; + } + var hostname=document.location.host; + return s.replace(/%v/g,version).replace(/%h/g,hostname); +} + + +// Copying + +function copy_ie_clipboard() { + try { + window.document.execCommand("copy",false,null); + } catch (err) { + return undefined; + } + return 1; +} + +function copy_mozilla_clipboard() { + // Thanks to Simon Wissinger for this function. + + try { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + } catch (err) { + return undefined; + } + + var sel=window.getSelection(); + var copytext=sel.toString(); + + var str=Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString); + if (!str) return undefined; + + str.data=copytext; + + var trans=Components.classes["@mozilla.org/widget/transferable;1"] + .createInstance(Components.interfaces.nsITransferable); + if (!trans) return undefined; + + trans.addDataFlavor("text/unicode"); + trans.setTransferData("text/unicode", str, copytext.length * 2); + + var clipid=Components.interfaces.nsIClipboard; + + var clip=Components.classes["@mozilla.org/widget/clipboard;1"].getService(clipid); + if (!clip) return undefined; + + clip.setData(trans, null, clipid.kGlobalClipboard); + + return 1; +} + +function copy_to_clipboard() { + var r=copy_ie_clipboard(); + if (r==undefined) { + r=copy_mozilla_clipboard(); + } + if (r==undefined) { + alert("Copy seems to be disabled; maybe you need to change your security settings?" + +"\n(Copy on the Edit menu will probably work)"); + } +} + + +// Pasting + +function get_mozilla_clipboard() { + // This function is taken from + // http://www.nomorepasting.com/paste.php?action=getpaste&pasteID=41974&PHPSESSID=e6565dcf5de07256345e562b97ac9f46 + // which does not indicate any particular copyright conditions. It + // is a public forum, so one might conclude that it is public + // domain. + + // IMHO it's disgraceful that Mozilla makes us use these 30 lines of + // undocumented gobledegook to do what IE does, and documents, with + // just 'window.clipboardData.getData("Text")'. What on earth were + // they thinking? + + try { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + } catch (err) { + return undefined; + } + + var clip = Components.classes["@mozilla.org/widget/clipboard;1"] + .createInstance(Components.interfaces.nsIClipboard); + if (!clip) { + return undefined; + } + + var trans = Components.classes["@mozilla.org/widget/transferable;1"] + .createInstance(Components.interfaces.nsITransferable); + if (!trans) { + return undefined; + } + + trans.addDataFlavor("text/unicode"); + clip.getData(trans,clip.kGlobalClipboard); + + var str=new Object(); + var strLength=new Object(); + + try { + trans.getTransferData("text/unicode",str,strLength); + } catch(err) { + // One reason for getting here seems to be that nothing is selected + return ""; + } + + if (str) { + str=str.value.QueryInterface(Components.interfaces.nsISupportsString); + } + + if (str) { + return str.data.substring(0,strLength.value / 2); + } else { + return ""; // ? is this "clipboard empty" or "cannot access"? + } +} + +function get_ie_clipboard() { + if (window.clipboardData) { + return window.clipboardData.getData("Text"); + } + return undefined; +} + +function get_default_clipboard() { + return prompt("Paste into this box and press OK:",""); +} + +function paste_from_clipboard() { + var p = get_ie_clipboard(); + if (p==undefined) { + p = get_mozilla_clipboard(); + } + if (p==undefined) { + p = get_default_clipboard(); + if (p) { + process_key(p); + } + return; + } + + if (p=="") { + alert("The clipboard seems to be empty"); + return; + } + + if (confirm('Click OK to "type" the following into the terminal:\n'+p)) { + process_key(p); + } +} + + +function create_button(label,fn) { + var button=document.createElement("A"); + var button_t=document.createTextNode("["+label+"] "); + button.appendChild(button_t); + button.onclick=fn; + return button; +} + +function create_img_button(imgfn,label,fn) { + var button=document.createElement("A"); + var button_img=document.createElement("IMG"); + var class_attr=document.createAttribute("CLASS"); + class_attr.value="button"; + button_img.setAttributeNode(class_attr); + var src_attr=document.createAttribute("SRC"); + src_attr.value=imgfn; + button_img.setAttributeNode(src_attr); + var alt_attr=document.createAttribute("ALT"); + alt_attr.value="["+label+"] "; + button_img.setAttributeNode(alt_attr); + var title_attr=document.createAttribute("TITLE"); + title_attr.value=label; + button_img.setAttributeNode(title_attr); + button.appendChild(button_img); + button.onclick=fn; + return button; +} + +function create_term(elem_id,title,rows,cols,p,charset,scrollback) { + if (open) { + alert("Terminal is already open"); + return; + } + title=substitute_variables(title); + frame=document.getElementById(elem_id); + if (!frame) { + alert("There is no element named '"+elem_id+"' in which to build a terminal"); + return; + } + frame.className="termframe"; + var title_p=document.createElement("P"); + title_p.appendChild(create_img_button("copy.gif","Copy",copy_to_clipboard)); + title_p.appendChild(create_img_button("paste.gif","Paste",paste_from_clipboard)); + title_p.appendChild(create_ctrlkey_menu()); + var title_t=document.createTextNode(" "+title+" "); + title_p.appendChild(title_t); + title_p.appendChild(create_button("close",close_term)); + frame.appendChild(title_p); + term=document.createElement("PRE"); + frame.appendChild(term); + term.className="term a p"; + var termbody=document.createTextNode(""); + term.appendChild(termbody); + visible_height_frac=Number(rows)/(Number(rows)+Number(scrollback)); + if (scrollback>0) { + term.style.overflowY="scroll"; + } + document.onhelp = function() { return false; }; + document.onkeypress=keypress; + document.onkeydown=keydown; + open_term(rows,cols,p,charset,scrollback); + if (open) { + window.onbeforeunload=warn_unload; + get(); + maybe_send(); + } +} + + +function warn_unload() { + if (open) { + return "Leaving this page will close the terminal."; + } +} + + +function create_ctrlkey_menu() { + var sel=document.createElement("SELECT"); + create_ctrlkey_menu_entry(sel,"Control keys...",-1); + create_ctrlkey_menu_entry(sel,"Ctrl-@",0); + for (var code=1; code<27; code++) { + var letter=String.fromCharCode(64+code); + create_ctrlkey_menu_entry(sel,"Ctrl-"+letter,code); + } + create_ctrlkey_menu_entry(sel,"Ctrl-[",27); + create_ctrlkey_menu_entry(sel,"Ctrl-\\",28); + create_ctrlkey_menu_entry(sel,"Ctrl-]",29); + create_ctrlkey_menu_entry(sel,"Ctrl-^",30); + create_ctrlkey_menu_entry(sel,"Ctrl-_",31); + sel.onchange=function() { + var code = sel.options[sel.selectedIndex].value; + if (code>=0) { + process_key(String.fromCharCode(code)); + } + }; + return sel; +} + +function create_ctrlkey_menu_entry(sel,name,code) { + var opt=document.createElement("OPTION"); + opt.appendChild(document.createTextNode(name)); + var value_attr=document.createAttribute("VALUE"); + value_attr.value=code; + opt.setAttributeNode(value_attr); + sel.appendChild(opt); +} + +function get_url_param( name ) +{ + var regexS = "[\\?&]"+name+"=([^&#]*)"; + var regex = new RegExp( regexS ); + var results = regex.exec( window.location.href ); + if( results == null ) + return ""; + else + return results[1]; +} diff --git a/anyterm/copy.gif b/anyterm/copy.gif new file mode 100644 index 0000000000000000000000000000000000000000..2ab719e349efd9e003b4b420449c68fecea004ed GIT binary patch literal 911 zcmV;A191FDNk%w1VG;lq0QUd at 000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C at 3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy at _IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W at l$-XlQ6 at X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm at et&;|fPjF3fq{a8f`fyD zgoK2Jg at uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8 at l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK at bK{Q@$vHV^7Hfa^z`)g_4W4l_V at Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LW006%LEC2ui01^Ne000PV0DlP_NU-2Q00D%GVtZCVwI6=}zS06UUJu*?7e literal 0 HcmV?d00001 diff --git a/anyterm/copy.png b/anyterm/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..1cdd8c1de2770d63ed9d9ae80ed2e2e44216598b GIT binary patch literal 232 zcmeAS at N?(olHy`uVBq!ia0vp^LO?9W!2%@H!&puMDW)WEcNd2L?fqx=19_YU9+AZi z4BWyX%*Zfnjs#GUy~NYkmHiqsx0s>WCWE6%K%q)c7sn8d^IL;X3La44U`dzWd?qA0 z^GRY4i`f&=I6j>>hOZ{P588M`%F97aO7hyLkIuU$dRG_j|7&_?qQwm*UWHnQqJ>gH z?!9ejR3nK#q3xf`b2U5$x9Owtlo z;=CNa at C}<)9TeDPQri}{PKujtmaKF9RnGngwby^0xx7})hfPAF-|hH>HkUTJ)mIcO pzW$gMas2A!-mb2Q<GRbtO4Q=IkW%( literal 0 HcmV?d00001 diff --git a/anyterm/paste.png b/anyterm/paste.png new file mode 100644 index 0000000000000000000000000000000000000000..034debdbf7033e46e9d7d45e239a664a7f92d9fb GIT binary patch literal 225 zcmV<703QE|P)MCL3v>^Jg7)<@CMgh0YotVxnTP-Ym^o8FP&6ST z?z0mOp`ORtxR<@a$a*m|yn3VG`WI at _T69(RGMJhM!%u}-y|7sSZ at A5pHlSsO0H8WQ zBt*2n<+?L8Md(rV^p~a4l;N!HYmSJ%Bb @@ -85,3 +81,8 @@ ProxyPassReverse /ovirt/stylesheets ! ProxyPassReverse /ovirt/errors ! + +Alias /terminal /usr/share/ovirt-anyterm + + DirectoryIndex anyterm.html + diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 1bf73c7..d762178 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -150,6 +150,11 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__cp} -pr %{pbuild}/installer/appliances %{buildroot}/%{acehome} %{__cp} -pr %{pbuild}/installer/bin/ovirt-installer %{buildroot}%{_sbindir} +# setup the anyterm config +%{__mkdir} -p %{buildroot}%{_datadir}/ovirt-anyterm/ +for f in anyterm/*.{html,css,js,png,gif}; do + %{__install} -m644 "$f" %{buildroot}%{_datadir}/ovirt-anyterm/ +done %clean rm -rf $RPM_BUILD_ROOT @@ -244,11 +249,13 @@ fi %config(noreplace) %{_sysconfdir}/%{name}/development.rb %config(noreplace) %{_sysconfdir}/%{name}/production.rb %config(noreplace) %{_sysconfdir}/%{name}/test.rb +%{_datadir}/ovirt-anyterm %files installer %{_sbindir}/ovirt-installer %{acehome} + %changelog * Thu May 29 2008 Alan Pevec - 0.0.5-0 - use rubygem-krb5-auth diff --git a/scripts/ovirt-vm2node b/scripts/ovirt-vm2node index 1d6104c..01e25bb 100755 --- a/scripts/ovirt-vm2node +++ b/scripts/ovirt-vm2node @@ -9,17 +9,10 @@ require 'dutils' $stdin.each{ |vmname| # get vm name from stdin begin vmname.chomp! # remove the newline - - # specially handle 'anyterm' to just return - # first host (for css/js/etc which aren't - # vm dependent) - if vmname == 'anyterm' - puts Host.find(:first, :conditions => "state = 'available'").hostname - else - puts Vm.find(:first, :conditions => ['description = ?', vmname]).host.hostname - end + puts Vm.find(:first, :conditions => ['description = ?', vmname]).host.hostname rescue Exception => e - puts + puts "Error looking node with VM: #{ex.class}: #{ex.message}" + puts ex.backtrace end $stdout.flush -- 1.6.0.6 From imain at redhat.com Mon Jul 20 21:08:43 2009 From: imain at redhat.com (Ian Main) Date: Mon, 20 Jul 2009 14:08:43 -0700 Subject: [Ovirt-devel] Re: [PATCH server] updated anyterm/ovirt integration In-Reply-To: <1248120470-23045-1-git-send-email-mmorsi@redhat.com> References: <1248120470-23045-1-git-send-email-mmorsi@redhat.com> Message-ID: <20090720140843.22de12c3@tp.mains.net> On Mon, 20 Jul 2009 16:07:50 -0400 Mohammed Morsi wrote: > host static anyterm content locally, > url parameterize anyterm rows/cols/general param > update spec/makefile > > Ideally I wanted and tried hard to put all this into a > seperate ovirt-server--anyterm subpackage, but we cannot > 'reopen' the ovirt server virtual host defined in > ovirt-server.conf to add the neccessary rewrite rules. it would > be nice to figure out a way to do this ACK! Ian From jboggs at redhat.com Tue Jul 21 14:33:24 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 21 Jul 2009 10:33:24 -0400 Subject: [Ovirt-devel] node boot error In-Reply-To: <2be7262f0906240619t3265bd5eh71cd661e5f8a0a03@mail.gmail.com> References: <4A40D575.5050105@gmail.com> <20090623170739.7cec6a15@tp.mains.net> <2be7262f0906240619t3265bd5eh71cd661e5f8a0a03@mail.gmail.com> Message-ID: <4A65D1B4.7070309@redhat.com> Alan Pevec wrote: > On Wed, Jun 24, 2009 at 2:07 AM, Ian Main > wrote: > > On Tue, 23 Jun 2009 17:15:33 +0400 > dima vasiletc > wrote: > > > Hello > > i have fedora 11 and build from next branch ovirt > > during boot by network (pxe) second node appear error. > > host stay anavailable and not sent to manage node identify data. > > ping return "unreachable host" > > > > Where may be error ? > > To see the real error you have to look at the console on the booting > node. We're having issues with fedora 11 nodes, Alan and I are > working > on resolving it right now. Hopefully it won't be long! I'm getting > frustrated with this one myself :). > > > Two issue were identified with livecd initrd in F11: > - PXE boot failing with "Could not find any loop device." - loop is > built-in since F10 so actually /dev/loop* was missing b/c udev didn't > create it in time. > Fixed by adding back udevadm settle (removed for F11 in > http://git.fedorahosted.org/git/?p=mkinitrd;a=commitdiff;h=0016a5fb236839db31344fef1c87ad68680e301b > ) > > - udev sometimes hangs on startup, root cause unclear: > Running plymouthd > udev: starting version 141 > > Note that I didn't get any injected echo output after nash daemonize > call in /init !? > Workaround for this hang is to remove plymouth completely from initrd > scripts, but that's clearly suboptimal. > > Attached is the patch for livecd-iso-to-pxeboot.sh which adds above > workarounds into initrd, proper fixes need to be discussed with > mkinitrd guys, > but with this we can at least get reliable PXE boot. > > Alan > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > This also affected booting from the iso image directly. Seems to be resolved with this commit as of 7/20/09, just needs to hit the fedora repos. Or just copy mkliveinitrd in place of your current one for now. http://git.fedorahosted.org/git/?p=mkinitrd;a=commitdiff;h=bd8476a27c3fa98c486c13587728b1a893243710 From arroy at redhat.com Tue Jul 21 15:15:21 2009 From: arroy at redhat.com (Arjun Roy) Date: Tue, 21 Jul 2009 11:15:21 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed db-omatic so it doesn't segfault because of newer qmf api. Message-ID: <1248189321-4186-1-git-send-email-arroy@redhat.com> --- src/db-omatic/db_omatic.rb | 12 ++++++------ 1 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/db-omatic/db_omatic.rb b/src/db-omatic/db_omatic.rb index 155ff5e..b5b7b81 100755 --- a/src/db-omatic/db_omatic.rb +++ b/src/db-omatic/db_omatic.rb @@ -270,10 +270,10 @@ class DbOmatic < Qpid::Qmf::Console end def object_props(broker, obj) - target = obj.klass_key[0] + target = obj.schema.klass_key.package return if target != "com.redhat.libvirt" - type = obj.klass_key[1] + type = obj.schema.klass_key.klass_name # I just sync this whole thing because there shouldn't be a lot of contention here.. synchronize do @@ -289,7 +289,7 @@ class DbOmatic < Qpid::Qmf::Console values[:broker_bank] = obj.object_id.broker_bank values[:agent_bank] = obj.object_id.agent_bank values[:obj_key] = obj.object_id.to_s - values[:class_type] = obj.klass_key[1] + values[:class_type] = type values[:timed_out] = false values[:synced] = false @logger.info "New object type #{type}" @@ -339,9 +339,9 @@ class DbOmatic < Qpid::Qmf::Console end def object_stats(broker, obj) - target = obj.klass_key[0] + target = obj.schema.klass_key.package return if target != "com.redhat.libvirt" - type = obj.klass_key[1] + type = obj.schema.klass_key.klass_name synchronize do values = @cached_objects[obj.object_id.to_s] @@ -351,7 +351,7 @@ class DbOmatic < Qpid::Qmf::Console values[:broker_bank] = obj.object_id.broker_bank values[:agent_bank] = obj.object_id.agent_bank - values[:class_type] = obj.klass_key[1] + values[:class_type] = type values[:timed_out] = false values[:synced] = false end -- 1.6.2.5 From dpierce at redhat.com Tue Jul 21 16:13:19 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 12:13:19 -0400 Subject: [Ovirt-devel] [PATCH node-image] Moved all temporary files into a single work directory to clean up. Message-ID: <1248192799-22846-1-git-send-email-dpierce@redhat.com> All temporary files are kept in a single directory. At the end of the autotests that one directory is deleted. Signed-off-by: Darryl L. Pierce --- autotest.sh | 20 +++++++++++--------- 1 files changed, 11 insertions(+), 9 deletions(-) diff --git a/autotest.sh b/autotest.sh index c9f8a2d..d658cf3 100755 --- a/autotest.sh +++ b/autotest.sh @@ -40,6 +40,7 @@ # an ISO file. ME=$(basename "$0") +WORKDIR=$(mktemp -d) warn() { printf '%s: %s\n' "$ME" "$*" >&2; } die() { warn "$*"; exit 1; } debug() { if $debugging; then log "[DEBUG] %s" "$*"; fi } @@ -140,7 +141,7 @@ start_dnsmasq () { --dhcp-boot=tftpboot/pxelinux.0 --enable-tftp --tftp-root=${tftproot} - --log-facility=/tmp/dnsmasq-${nodename}.log + --log-facility=$WORKDIR/dnsmasq-${nodename}.log --log-queries --log-dhcp --pid-file=${pidfile}" @@ -180,7 +181,7 @@ start_networking () { local workdir=$5 local definition="" local network=$NETWORK - local xmlfile=$(mktemp) + local xmlfile=$WORKDIR/$nodename-$ifacename.xml debug "start_networking ()" for var in nodename ifacename use_dhcp start_dnsmasq workdir network xmlfile; do @@ -363,7 +364,7 @@ configure_node () { local hdfile="" local cdfile=$5 local args=$6 - local nodefile=$(mktemp) + local nodefile=$WORKDIR/$nodename.xml if [ -z "${boot_device}" ]; then boot_device="hd"; fi if [ -z "${memory}" ]; then memory="524288"; fi @@ -375,7 +376,7 @@ configure_node () { # create the hard disk file if [ -n "${hdsize}" ]; then - hdfile=$(mktemp) + hdfile=$WORKDIR/$nodename-hd.img create_hard_disk $hdfile $hdsize fi @@ -457,7 +458,7 @@ substitute_boot_device () { local nodename=$1 local old_device=$2 local new_device=$3 - local new_node_file=$(mktemp) + local new_node_file=$WORKDIR/$nodename-new.xml if [ -n "${nodename}" ]; then local xml=$(sudo virsh dumpxml $nodename | sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/") @@ -471,7 +472,7 @@ substitute_boot_device () { add_test "test_stateless_pxe" test_stateless_pxe () { local nodename="${vm_prefix}-stateless-pxe" - local workdir=$(mktemp -d) + local workdir=$WORKDIR start_networking $nodename $IFACE_NAME false true $workdir @@ -513,7 +514,7 @@ exit 3' add_test "test_stateless_pxe_with_nohd" test_stateless_pxe_with_nohd () { local nodename="${vm_prefix}-stateless-pxe-nohd" - local workdir=$(mktemp -d) + local workdir=$WORKDIR start_networking $nodename $IFACE_NAME false true $workdir @@ -556,7 +557,7 @@ exit 3' add_test "test_stateful_pxe" test_stateful_pxe () { local nodename="${vm_prefix}-stateful-pxe" - local workdir=$(mktemp -d) + local workdir=$WORKDIR local ipaddress=${NODE_ADDRESS} for var in nodename workdir ipaddress; do @@ -683,6 +684,7 @@ cleanup_after_testing () { destroy_node $vm done stop_networking + rm -rf $WORKDIR } # check commandline options @@ -715,7 +717,7 @@ set +u if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi set -u -result_file=$(mktemp) +result_file=$WORKDIR/results.log debug "result_file=${result_file}" log "Logging results to file: ${RESULTS}" -- 1.6.2.5 From dpierce at redhat.com Tue Jul 21 16:55:08 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 12:55:08 -0400 Subject: [Ovirt-devel] [PATCH node-image] Adds a preserve option for autotest VMs. Message-ID: <1248195308-28157-1-git-send-email-dpierce@redhat.com> If the -p option is provided, then no VMs are destroyed. Instead they, and their related networks, are left intact. Signed-off-by: Darryl L. Pierce --- autotest.sh | 11 ++++++++++- 1 files changed, 10 insertions(+), 1 deletions(-) diff --git a/autotest.sh b/autotest.sh index c9f8a2d..b72ec98 100755 --- a/autotest.sh +++ b/autotest.sh @@ -219,6 +219,9 @@ stop_networking () { networkname=${NETWORK_NAME-} fi + # exit if preserve was enabled + if $preserve_vm; then return; fi + if [ -n "${networkname}" ]; then debug "Destroying network interface: ${networkname}" check=$(sudo virsh net-list --all) @@ -388,6 +391,9 @@ destroy_node () { local nodename=$1 local undefine=${2-true} + # if preserving nodes then exit + if $preserve_vm; then return; fi + if [ -n "${nodename}" ]; then check=$(sudo virsh list --all) if [[ "${check}" =~ "${nodename}" ]]; then @@ -670,6 +676,7 @@ setup_for_testing () { NODE_ADDRESS=$NETWORK.100 debug "NODE_ADDRESS=${NODE_ADDRESS}" DNSMASQ_PID=0 + debug "preserve_vm=${preserve_vm}" } # cleans up any loose ends @@ -691,12 +698,14 @@ debugging=false isofile="${PWD}/ovirt-node-image.iso" show_viewer=false vm_prefix="$$" +preserve_vm=false -while getopts di:n:vwh c; do +while getopts di:n:pvwh c; do case $c in d) debugging=true;; i) isofile=($OPTARG);; n) tests=($OPTARG);; + p) preserve_vm=true;; v) set -v;; w) show_viewer=true;; h) usage; exit 0;; -- 1.6.2.5 From mburns at redhat.com Tue Jul 21 17:39:15 2009 From: mburns at redhat.com (Mike Burns) Date: Tue, 21 Jul 2009 13:39:15 -0400 Subject: [Ovirt-devel] [PATCH node-image] Moved all temporary files into a single work directory to clean up. In-Reply-To: <1248192799-22846-1-git-send-email-dpierce@redhat.com> References: <1248192799-22846-1-git-send-email-dpierce@redhat.com> Message-ID: <20090721173915.GB10951@mburns-laptop.bos.redhat.com> On Tue, Jul 21, 2009 at 12:13:19PM -0400, Darryl L. Pierce wrote: > All temporary files are kept in a single directory. At the end of the > autotests that one directory is deleted. > > Signed-off-by: Darryl L. Pierce > --- > autotest.sh | 20 +++++++++++--------- > 1 files changed, 11 insertions(+), 9 deletions(-) > > diff --git a/autotest.sh b/autotest.sh > index c9f8a2d..d658cf3 100755 > --- a/autotest.sh > +++ b/autotest.sh > @@ -40,6 +40,7 @@ > # an ISO file. > > ME=$(basename "$0") > +WORKDIR=$(mktemp -d) > warn() { printf '%s: %s\n' "$ME" "$*" >&2; } > die() { warn "$*"; exit 1; } > debug() { if $debugging; then log "[DEBUG] %s" "$*"; fi } > @@ -140,7 +141,7 @@ start_dnsmasq () { > --dhcp-boot=tftpboot/pxelinux.0 > --enable-tftp > --tftp-root=${tftproot} > - --log-facility=/tmp/dnsmasq-${nodename}.log > + --log-facility=$WORKDIR/dnsmasq-${nodename}.log > --log-queries > --log-dhcp > --pid-file=${pidfile}" > @@ -180,7 +181,7 @@ start_networking () { > local workdir=$5 > local definition="" > local network=$NETWORK > - local xmlfile=$(mktemp) > + local xmlfile=$WORKDIR/$nodename-$ifacename.xml > > debug "start_networking ()" > for var in nodename ifacename use_dhcp start_dnsmasq workdir network xmlfile; do > @@ -363,7 +364,7 @@ configure_node () { > local hdfile="" > local cdfile=$5 > local args=$6 > - local nodefile=$(mktemp) > + local nodefile=$WORKDIR/$nodename.xml > > if [ -z "${boot_device}" ]; then boot_device="hd"; fi > if [ -z "${memory}" ]; then memory="524288"; fi > @@ -375,7 +376,7 @@ configure_node () { > > # create the hard disk file > if [ -n "${hdsize}" ]; then > - hdfile=$(mktemp) > + hdfile=$WORKDIR/$nodename-hd.img > create_hard_disk $hdfile $hdsize > fi > > @@ -457,7 +458,7 @@ substitute_boot_device () { > local nodename=$1 > local old_device=$2 > local new_device=$3 > - local new_node_file=$(mktemp) > + local new_node_file=$WORKDIR/$nodename-new.xml > > if [ -n "${nodename}" ]; then > local xml=$(sudo virsh dumpxml $nodename | sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/") > @@ -471,7 +472,7 @@ substitute_boot_device () { > add_test "test_stateless_pxe" > test_stateless_pxe () { > local nodename="${vm_prefix}-stateless-pxe" > - local workdir=$(mktemp -d) > + local workdir=$WORKDIR > > start_networking $nodename $IFACE_NAME false true $workdir > > @@ -513,7 +514,7 @@ exit 3' > add_test "test_stateless_pxe_with_nohd" > test_stateless_pxe_with_nohd () { > local nodename="${vm_prefix}-stateless-pxe-nohd" > - local workdir=$(mktemp -d) > + local workdir=$WORKDIR > > start_networking $nodename $IFACE_NAME false true $workdir > > @@ -556,7 +557,7 @@ exit 3' > add_test "test_stateful_pxe" > test_stateful_pxe () { > local nodename="${vm_prefix}-stateful-pxe" > - local workdir=$(mktemp -d) > + local workdir=$WORKDIR > local ipaddress=${NODE_ADDRESS} > > for var in nodename workdir ipaddress; do > @@ -683,6 +684,7 @@ cleanup_after_testing () { > destroy_node $vm > done > stop_networking > + rm -rf $WORKDIR > } > > # check commandline options > @@ -715,7 +717,7 @@ set +u > if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi > set -u > > -result_file=$(mktemp) > +result_file=$WORKDIR/results.log > debug "result_file=${result_file}" > > log "Logging results to file: ${RESULTS}" > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > Only comment is that you might want to report as a debug statement where the working directory is located in the setup_for_testing function. Other than that, ACK Mike From mmcgrath at redhat.com Tue Jul 21 18:13:03 2009 From: mmcgrath at redhat.com (Mike McGrath) Date: Tue, 21 Jul 2009 13:13:03 -0500 (CDT) Subject: [Ovirt-devel] [PATCH server] Fixed db-omatic so it doesn't segfault because of newer qmf api. In-Reply-To: <1248189321-4186-1-git-send-email-arroy@redhat.com> References: <1248189321-4186-1-git-send-email-arroy@redhat.com> Message-ID: On Tue, 21 Jul 2009, Arjun Roy wrote: > --- > src/db-omatic/db_omatic.rb | 12 ++++++------ > 1 files changed, 6 insertions(+), 6 deletions(-) > > diff --git a/src/db-omatic/db_omatic.rb b/src/db-omatic/db_omatic.rb > index 155ff5e..b5b7b81 100755 > --- a/src/db-omatic/db_omatic.rb > +++ b/src/db-omatic/db_omatic.rb > @@ -270,10 +270,10 @@ class DbOmatic < Qpid::Qmf::Console > end > > def object_props(broker, obj) > - target = obj.klass_key[0] > + target = obj.schema.klass_key.package > return if target != "com.redhat.libvirt" > > - type = obj.klass_key[1] > + type = obj.schema.klass_key.klass_name > > # I just sync this whole thing because there shouldn't be a lot of contention here.. > synchronize do > @@ -289,7 +289,7 @@ class DbOmatic < Qpid::Qmf::Console > values[:broker_bank] = obj.object_id.broker_bank > values[:agent_bank] = obj.object_id.agent_bank > values[:obj_key] = obj.object_id.to_s > - values[:class_type] = obj.klass_key[1] > + values[:class_type] = type > values[:timed_out] = false > values[:synced] = false > @logger.info "New object type #{type}" > @@ -339,9 +339,9 @@ class DbOmatic < Qpid::Qmf::Console > end > > def object_stats(broker, obj) > - target = obj.klass_key[0] > + target = obj.schema.klass_key.package > return if target != "com.redhat.libvirt" > - type = obj.klass_key[1] > + type = obj.schema.klass_key.klass_name > > synchronize do > values = @cached_objects[obj.object_id.to_s] > @@ -351,7 +351,7 @@ class DbOmatic < Qpid::Qmf::Console > > values[:broker_bank] = obj.object_id.broker_bank > values[:agent_bank] = obj.object_id.agent_bank > - values[:class_type] = obj.klass_key[1] > + values[:class_type] = type > values[:timed_out] = false > values[:synced] = false > end I was seeing this issue as well, after applying the patch things seem to be working. ACK -Mike From dpierce at redhat.com Tue Jul 21 18:13:47 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 14:13:47 -0400 Subject: [Ovirt-devel] [PATCH node-image] Moved all temporary files into a single work directory to clean up. In-Reply-To: <20090721173915.GB10951@mburns-laptop.bos.redhat.com> References: <1248192799-22846-1-git-send-email-dpierce@redhat.com> <20090721173915.GB10951@mburns-laptop.bos.redhat.com> Message-ID: <20090721181347.GA26110@mcpierce-laptop.rdu.redhat.com> On Tue, Jul 21, 2009 at 01:39:15PM -0400, Mike Burns wrote: > Only comment is that you might want to report as a debug statement where the working directory is located in the setup_for_testing function. > > Other than that, ACK Thanks. I've added that output and pushed this upstream. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Tue Jul 21 18:16:23 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 14:16:23 -0400 Subject: [Ovirt-devel] [PATCH node-image] Adds a preserve option for autotest VMs. In-Reply-To: <1248200183-655-1-git-send-email-dpierce@redhat.com> References: <1248200183-655-1-git-send-email-dpierce@redhat.com> Message-ID: <1248200183-655-2-git-send-email-dpierce@redhat.com> If the -p option is provided, then no VMs are destroyed. Instead they, and their related networks, are left intact. Signed-off-by: Darryl L. Pierce --- autotest.sh | 15 ++++++++++++++- 1 files changed, 14 insertions(+), 1 deletions(-) diff --git a/autotest.sh b/autotest.sh index b0f0ff9..4f03f72 100755 --- a/autotest.sh +++ b/autotest.sh @@ -220,6 +220,9 @@ stop_networking () { networkname=${NETWORK_NAME-} fi + # exit if preserve was enabled + if $preserve_vm; then return; fi + if [ -n "${networkname}" ]; then debug "Destroying network interface: ${networkname}" check=$(sudo virsh net-list --all) @@ -389,6 +392,9 @@ destroy_node () { local nodename=$1 local undefine=${2-true} + # if preserving nodes then exit + if $preserve_vm; then return; fi + if [ -n "${nodename}" ]; then check=$(sudo virsh list --all) if [[ "${check}" =~ "${nodename}" ]]; then @@ -672,6 +678,7 @@ setup_for_testing () { NODE_ADDRESS=$NETWORK.100 debug "NODE_ADDRESS=${NODE_ADDRESS}" DNSMASQ_PID=0 + debug "preserve_vm=${preserve_vm}" } # cleans up any loose ends @@ -685,6 +692,10 @@ cleanup_after_testing () { destroy_node $vm done stop_networking + + # do not delete the work directory if preserve was specified + if $preserve_vm; then return; fi + rm -rf $WORKDIR } @@ -694,12 +705,14 @@ debugging=false isofile="${PWD}/ovirt-node-image.iso" show_viewer=false vm_prefix="$$" +preserve_vm=false -while getopts di:n:vwh c; do +while getopts di:n:pvwh c; do case $c in d) debugging=true;; i) isofile=($OPTARG);; n) tests=($OPTARG);; + p) preserve_vm=true;; v) set -v;; w) show_viewer=true;; h) usage; exit 0;; -- 1.6.2.5 From dpierce at redhat.com Tue Jul 21 18:16:22 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 14:16:22 -0400 Subject: [Ovirt-devel] Rebased patch... Message-ID: <1248200183-655-1-git-send-email-dpierce@redhat.com> This version is rebased against the temporary directory patch which is now pushed upstream. The difference is that the script will now check if the -p option was specified and will not delete the work directory if it's specified. From mburns at redhat.com Tue Jul 21 19:06:49 2009 From: mburns at redhat.com (Mike Burns) Date: Tue, 21 Jul 2009 15:06:49 -0400 Subject: [Ovirt-devel] [PATCH node-image] Adds a preserve option for autotest VMs. In-Reply-To: <1248200183-655-2-git-send-email-dpierce@redhat.com> References: <1248200183-655-1-git-send-email-dpierce@redhat.com> <1248200183-655-2-git-send-email-dpierce@redhat.com> Message-ID: <20090721190649.GC10951@mburns-laptop.bos.redhat.com> On Tue, Jul 21, 2009 at 02:16:23PM -0400, Darryl L. Pierce wrote: > If the -p option is provided, then no VMs are destroyed. Instead they, > and their related networks, are left intact. > > Signed-off-by: Darryl L. Pierce > --- > autotest.sh | 15 ++++++++++++++- > 1 files changed, 14 insertions(+), 1 deletions(-) > > diff --git a/autotest.sh b/autotest.sh > index b0f0ff9..4f03f72 100755 > --- a/autotest.sh > +++ b/autotest.sh > @@ -220,6 +220,9 @@ stop_networking () { > networkname=${NETWORK_NAME-} > fi > > + # exit if preserve was enabled > + if $preserve_vm; then return; fi > + > if [ -n "${networkname}" ]; then > debug "Destroying network interface: ${networkname}" > check=$(sudo virsh net-list --all) > @@ -389,6 +392,9 @@ destroy_node () { > local nodename=$1 > local undefine=${2-true} > > + # if preserving nodes then exit > + if $preserve_vm; then return; fi > + > if [ -n "${nodename}" ]; then > check=$(sudo virsh list --all) > if [[ "${check}" =~ "${nodename}" ]]; then > @@ -672,6 +678,7 @@ setup_for_testing () { > NODE_ADDRESS=$NETWORK.100 > debug "NODE_ADDRESS=${NODE_ADDRESS}" > DNSMASQ_PID=0 > + debug "preserve_vm=${preserve_vm}" > } > > # cleans up any loose ends > @@ -685,6 +692,10 @@ cleanup_after_testing () { > destroy_node $vm > done > stop_networking > + > + # do not delete the work directory if preserve was specified > + if $preserve_vm; then return; fi > + > rm -rf $WORKDIR > } > > @@ -694,12 +705,14 @@ debugging=false > isofile="${PWD}/ovirt-node-image.iso" > show_viewer=false > vm_prefix="$$" > +preserve_vm=false > > -while getopts di:n:vwh c; do > +while getopts di:n:pvwh c; do > case $c in > d) debugging=true;; > i) isofile=($OPTARG);; > n) tests=($OPTARG);; > + p) preserve_vm=true;; > v) set -v;; > w) show_viewer=true;; > h) usage; exit 0;; > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > Looks good. ACK Mike From jboggs at redhat.com Tue Jul 21 19:12:26 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 21 Jul 2009 15:12:26 -0400 Subject: [Ovirt-devel] [PATCH node] updated unpersist prompts bz512539 Message-ID: <1248203546-6720-1-git-send-email-jboggs@redhat.com> --- scripts/ovirt-functions | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-) diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index 404c366..7657bae 100644 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -508,8 +508,16 @@ remove_config() { if [ -f /config$f ]; then # refresh the file in rootfs if it was mounted over cp -a /config$f $f + if [ $? -ne 0 ]; then + printf " Failed to unpersist %s\n" $f + rc=1 + else + printf " %s successully unpersisted\n" $f + fi fi fi + else + printf " %s is not in persistent storage" $f fi # clean up the persistent store rm -f /config$f -- 1.6.2.5 From dpierce at redhat.com Tue Jul 21 19:23:39 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 15:23:39 -0400 Subject: [Ovirt-devel] [PATCH node] Adds a new kernel cmdline argument to toggle SSH password auth. Message-ID: <1248204219-17500-1-git-send-email-dpierce@redhat.com> The new karg is "ssh" and can be set using either 0/1 or true/false to set whether SSH password auth will be enabled or not during an automated install. rhbz#513037 Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-password | 42 ++++++++++++++++++++++------------------ scripts/ovirt-early | 10 ++++++++- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password index b6b9f07..ffb30d9 100755 --- a/scripts/ovirt-config-password +++ b/scripts/ovirt-config-password @@ -76,23 +76,27 @@ PASSWORD="Set administrator password" SSH="Toggle SSH password authentication" QUIT="Quit and Return To Menu" -while true; do - state="disabled" - /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ - if [ $? == 0 ]; then - state="enabled" - fi - printf "\nSSH password authentication is currently ${state}.\n\n" - - PS3="Please select an option: " - select option in "$PASSWORD" "$SSH" "$QUIT" - do - case $option in - $PASSWORD) set_password; break;; - $SSH) toggle_ssh; break;; - $QUIT) exit;; - esac +if [[ "$1" == "AUTO" ]]; then + toggle_ssh_access $OVIRT_SSH +else + while true; do + state="disabled" + /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ + if [ $? == 0 ]; then + state="enabled" + fi + printf "\nSSH password authentication is currently ${state}.\n\n" + + PS3="Please select an option: " + select option in "$PASSWORD" "$SSH" "$QUIT" + do + case $option in + $PASSWORD) set_password; break;; + $SSH) toggle_ssh; break;; + $QUIT) exit;; + esac + done + + printf "\n" done - - printf "\n" -done +fi diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 560fa14..00d7422 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -209,6 +209,7 @@ start() { # dns=server[,server] # ntp=server[,server] # vlan=id + # ssh=[0|1] # static network configuration ip_address= ip_gateway= @@ -219,6 +220,7 @@ start() { ipv6= dns= ntp= + ssh= # hostname=fqdn # hostname @@ -349,6 +351,12 @@ start() { vlan=*) vlan=${i#vlan=} ;; + ssh=1 | ssh=true) + ssh=true + ;; + ssh=0 | ssh=false) + ssh=false + ;; syslog=*) i=${i#syslog=} eval $(printf $i|awk -F: '{print "syslog_server="$1; print "syslog_port="$2;}') @@ -370,7 +378,7 @@ start() { ip_gateway=$gateway fi # save boot parameters as defaults for ovirt-config-* - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" # mount /config unless firstboot is forced if [ "$firstboot" != "1" ]; then mount_config -- 1.6.2.5 From dpierce at redhat.com Tue Jul 21 19:29:46 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Jul 2009 15:29:46 -0400 Subject: [Ovirt-devel] [PATCH node-image] Adds a preserve option for autotest VMs. In-Reply-To: <20090721190649.GC10951@mburns-laptop.bos.redhat.com> References: <1248200183-655-1-git-send-email-dpierce@redhat.com> <1248200183-655-2-git-send-email-dpierce@redhat.com> <20090721190649.GC10951@mburns-laptop.bos.redhat.com> Message-ID: <20090721192946.GB26110@mcpierce-laptop.rdu.redhat.com> On Tue, Jul 21, 2009 at 03:06:49PM -0400, Mike Burns wrote: > Looks good. ACK Thanks. This is pushed upstream now. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From mburns at redhat.com Wed Jul 22 13:31:15 2009 From: mburns at redhat.com (Mike Burns) Date: Wed, 22 Jul 2009 09:31:15 -0400 Subject: [Ovirt-devel] [PATCH node-image] Removes the explicit setting of the emulator for testing. In-Reply-To: <1247754910-8948-1-git-send-email-dpierce@redhat.com> References: <1247754910-8948-1-git-send-email-dpierce@redhat.com> Message-ID: <20090722133115.GF10951@mburns-laptop.bos.redhat.com> On Thu, Jul 16, 2009 at 10:35:10AM -0400, Darryl L. Pierce wrote: > Instead, the script lets libvirt determine which emulator to use. > > Signed-off-by: Darryl L. Pierce > --- > autotest.sh | 6 ++---- > 1 files changed, 2 insertions(+), 4 deletions(-) > > diff --git a/autotest.sh b/autotest.sh > index c9f8a2d..96a15b5 100755 > --- a/autotest.sh > +++ b/autotest.sh > @@ -270,7 +270,6 @@ define_node () { > # flexible options > # define defaults, then allow the caller to override them as needed > local arch=$(uname -i) > - local emulator=$(which qemu-kvm) > local serial="true" > local vncport="-1" > local bootdev='hd' > @@ -281,7 +280,7 @@ define_node () { > if [ -n "$options" ]; then eval "$options"; fi > > debug "define_node ()" > - for var in filename nodename memory harddrive cddrive bridge options arch emulator serial vncport bootdev; do > + for var in filename nodename memory harddrive cddrive bridge options arch serial vncport bootdev; do > eval debug "::$var: \$$var" > done > > @@ -305,7 +304,6 @@ eval debug "::$var: \$$var" > > # add devices > result="${result}\n" > - result="${result}\n${emulator}" > # inject the hard disk if defined > if [ -n "$harddrive" ]; then > debug "Adding a hard drive to the node" > @@ -566,7 +564,7 @@ test_stateful_pxe () { > start_networking $nodename $IFACE_NAME false true $workdir > > configure_node "${nodename}" "network" "" "10000" "" "local noapic=true" > - boot_with_pxe "${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress}" ${workdir} > + boot_with_pxe "${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress} vlan=12" ${workdir} Think this was part of something separate, not this patch. > > # verify the booting and installation > expect -c ' > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > ACK if vlan=12 is removed Mike From dpierce at redhat.com Wed Jul 22 13:33:11 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 09:33:11 -0400 Subject: [Ovirt-devel] [PATCH node] updated unpersist prompts bz512539 In-Reply-To: <1248203546-6720-1-git-send-email-jboggs@redhat.com> References: <1248203546-6720-1-git-send-email-jboggs@redhat.com> Message-ID: <20090722133311.GC4651@mcpierce-laptop.rdu.redhat.com> On Tue, Jul 21, 2009 at 03:12:26PM -0400, Joey Boggs wrote: ACK with one comment. > --- > scripts/ovirt-functions | 8 ++++++++ > 1 files changed, 8 insertions(+), 0 deletions(-) > > diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions > index 404c366..7657bae 100644 > --- a/scripts/ovirt-functions > +++ b/scripts/ovirt-functions > @@ -508,8 +508,16 @@ remove_config() { > if [ -f /config$f ]; then > # refresh the file in rootfs if it was mounted over > cp -a /config$f $f > + if [ $? -ne 0 ]; then > + printf " Failed to unpersist %s\n" $f > + rc=1 I don't see where rc is being used. Can you remove it before pushing? > + else > + printf " %s successully unpersisted\n" $f > + fi > fi > fi > + else > + printf " %s is not in persistent storage" $f > fi > # clean up the persistent store > rm -f /config$f > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Wed Jul 22 13:37:43 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 09:37:43 -0400 Subject: [Ovirt-devel] Changes the ssh karg to ssh_pwauth Message-ID: <1248269864-11750-1-git-send-email-dpierce@redhat.com> This patch obsoletes the previous one with feedback from pmyers at redhat.com. From dpierce at redhat.com Wed Jul 22 13:37:44 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 09:37:44 -0400 Subject: [Ovirt-devel] [PATCH node] Adds a new kernel cmdline argument to toggle SSH password auth. In-Reply-To: <1248269864-11750-1-git-send-email-dpierce@redhat.com> References: <1248269864-11750-1-git-send-email-dpierce@redhat.com> Message-ID: <1248269864-11750-2-git-send-email-dpierce@redhat.com> The new karg is "ssh" and can be set using either 0/1 or true/false to set whether SSH password auth will be enabled or not during an automated install. rhbz#513037 Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-password | 42 ++++++++++++++++++++++------------------ scripts/ovirt-early | 10 ++++++++- scripts/ovirt-firstboot | 1 + 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password index b6b9f07..225a834 100755 --- a/scripts/ovirt-config-password +++ b/scripts/ovirt-config-password @@ -76,23 +76,27 @@ PASSWORD="Set administrator password" SSH="Toggle SSH password authentication" QUIT="Quit and Return To Menu" -while true; do - state="disabled" - /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ - if [ $? == 0 ]; then - state="enabled" - fi - printf "\nSSH password authentication is currently ${state}.\n\n" - - PS3="Please select an option: " - select option in "$PASSWORD" "$SSH" "$QUIT" - do - case $option in - $PASSWORD) set_password; break;; - $SSH) toggle_ssh; break;; - $QUIT) exit;; - esac +if [[ "$1" == "AUTO" ]]; then + toggle_ssh_access $OVIRT_SSH_PWAUTH +else + while true; do + state="disabled" + /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ + if [ $? == 0 ]; then + state="enabled" + fi + printf "\nSSH password authentication is currently ${state}.\n\n" + + PS3="Please select an option: " + select option in "$PASSWORD" "$SSH" "$QUIT" + do + case $option in + $PASSWORD) set_password; break;; + $SSH) toggle_ssh; break;; + $QUIT) exit;; + esac + done + + printf "\n" done - - printf "\n" -done +fi diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 560fa14..8cf2cd0 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -209,6 +209,7 @@ start() { # dns=server[,server] # ntp=server[,server] # vlan=id + # ssh_pwauth=[0|1] # static network configuration ip_address= ip_gateway= @@ -219,6 +220,7 @@ start() { ipv6= dns= ntp= + ssh_pwauth= # hostname=fqdn # hostname @@ -349,6 +351,12 @@ start() { vlan=*) vlan=${i#vlan=} ;; + ssh_pwauth=1 | ssh_pwauth=true) + ssh_pwauth=true + ;; + ssh_pwauth=0 | ssh_pwauth=false) + ssh_pwauth=false + ;; syslog=*) i=${i#syslog=} eval $(printf $i|awk -F: '{print "syslog_server="$1; print "syslog_port="$2;}') @@ -370,7 +378,7 @@ start() { ip_gateway=$gateway fi # save boot parameters as defaults for ovirt-config-* - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" # mount /config unless firstboot is forced if [ "$firstboot" != "1" ]; then mount_config diff --git a/scripts/ovirt-firstboot b/scripts/ovirt-firstboot index 844f689..4969261 100755 --- a/scripts/ovirt-firstboot +++ b/scripts/ovirt-firstboot @@ -46,6 +46,7 @@ start () ovirt-config-networking AUTO ovirt-config-logging AUTO ovirt-config-collectd AUTO + ovirt-config-password AUTO if [ "$OVIRT_LOCAL_BOOT" = 1 ]; then mount_live ovirt-config-boot /live "$OVIRT_BOOTPARAMS" no -- 1.6.2.5 From jboggs at redhat.com Wed Jul 22 14:01:13 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 22 Jul 2009 10:01:13 -0400 Subject: [Ovirt-devel] [PATCH node] updated unpersist prompts bz512539 In-Reply-To: <20090722133311.GC4651@mcpierce-laptop.rdu.redhat.com> References: <1248203546-6720-1-git-send-email-jboggs@redhat.com> <20090722133311.GC4651@mcpierce-laptop.rdu.redhat.com> Message-ID: <4A671BA9.20500@redhat.com> Darryl L. Pierce wrote: > On Tue, Jul 21, 2009 at 03:12:26PM -0400, Joey Boggs wrote: > > ACK with one comment. > > >> --- >> scripts/ovirt-functions | 8 ++++++++ >> 1 files changed, 8 insertions(+), 0 deletions(-) >> >> diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions >> index 404c366..7657bae 100644 >> --- a/scripts/ovirt-functions >> +++ b/scripts/ovirt-functions >> @@ -508,8 +508,16 @@ remove_config() { >> if [ -f /config$f ]; then >> # refresh the file in rootfs if it was mounted over >> cp -a /config$f $f >> + if [ $? -ne 0 ]; then >> + printf " Failed to unpersist %s\n" $f >> + rc=1 >> > > I don't see where rc is being used. Can you remove it before pushing? > > >> + else >> + printf " %s successully unpersisted\n" $f >> + fi >> fi >> fi >> + else >> + printf " %s is not in persistent storage" $f >> fi >> # clean up the persistent store >> rm -f /config$f >> -- >> 1.6.2.5 >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> > > removed the rc=1 line and pushed upstream From dpierce at redhat.com Wed Jul 22 16:06:58 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 12:06:58 -0400 Subject: [Ovirt-devel] Updated patch... Message-ID: <1248278819-1910-1-git-send-email-dpierce@redhat.com> This patch obsoletes the previous, renaming the karg to ssh_pwauth. From dpierce at redhat.com Wed Jul 22 16:06:59 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 12:06:59 -0400 Subject: [Ovirt-devel] [PATCH node] Adds a new kernel cmdline argument to toggle SSH password auth. In-Reply-To: <1248278819-1910-1-git-send-email-dpierce@redhat.com> References: <1248278819-1910-1-git-send-email-dpierce@redhat.com> Message-ID: <1248278819-1910-2-git-send-email-dpierce@redhat.com> The new karg is "ssh" and can be set using either 0/1 or true/false to set whether SSH password auth will be enabled or not during an automated install. rhbz#513037 Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-password | 44 +++++++++++++++++++++++----------------- scripts/ovirt-early | 10 ++++++++- scripts/ovirt-firstboot | 1 + 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password index b6b9f07..ab0325a 100755 --- a/scripts/ovirt-config-password +++ b/scripts/ovirt-config-password @@ -76,23 +76,29 @@ PASSWORD="Set administrator password" SSH="Toggle SSH password authentication" QUIT="Quit and Return To Menu" -while true; do - state="disabled" - /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ - if [ $? == 0 ]; then - state="enabled" - fi - printf "\nSSH password authentication is currently ${state}.\n\n" - - PS3="Please select an option: " - select option in "$PASSWORD" "$SSH" "$QUIT" - do - case $option in - $PASSWORD) set_password; break;; - $SSH) toggle_ssh; break;; - $QUIT) exit;; - esac +if [[ "$1" == "AUTO" ]]; then + if [ -n "${OVIRT_SSH_PWAUTH}" ]; then + toggle_ssh_access $OVIRT_SSH_PWAUTH + fi +else + while true; do + state="disabled" + /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ + if [ $? == 0 ]; then + state="enabled" + fi + printf "\nSSH password authentication is currently ${state}.\n\n" + + PS3="Please select an option: " + select option in "$PASSWORD" "$SSH" "$QUIT" + do + case $option in + $PASSWORD) set_password; break;; + $SSH) toggle_ssh; break;; + $QUIT) exit;; + esac + done + + printf "\n" done - - printf "\n" -done +fi diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 560fa14..8cf2cd0 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -209,6 +209,7 @@ start() { # dns=server[,server] # ntp=server[,server] # vlan=id + # ssh_pwauth=[0|1] # static network configuration ip_address= ip_gateway= @@ -219,6 +220,7 @@ start() { ipv6= dns= ntp= + ssh_pwauth= # hostname=fqdn # hostname @@ -349,6 +351,12 @@ start() { vlan=*) vlan=${i#vlan=} ;; + ssh_pwauth=1 | ssh_pwauth=true) + ssh_pwauth=true + ;; + ssh_pwauth=0 | ssh_pwauth=false) + ssh_pwauth=false + ;; syslog=*) i=${i#syslog=} eval $(printf $i|awk -F: '{print "syslog_server="$1; print "syslog_port="$2;}') @@ -370,7 +378,7 @@ start() { ip_gateway=$gateway fi # save boot parameters as defaults for ovirt-config-* - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" # mount /config unless firstboot is forced if [ "$firstboot" != "1" ]; then mount_config diff --git a/scripts/ovirt-firstboot b/scripts/ovirt-firstboot index 844f689..4969261 100755 --- a/scripts/ovirt-firstboot +++ b/scripts/ovirt-firstboot @@ -46,6 +46,7 @@ start () ovirt-config-networking AUTO ovirt-config-logging AUTO ovirt-config-collectd AUTO + ovirt-config-password AUTO if [ "$OVIRT_LOCAL_BOOT" = 1 ]; then mount_live ovirt-config-boot /live "$OVIRT_BOOTPARAMS" no -- 1.6.2.5 From jboggs at redhat.com Wed Jul 22 16:55:53 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 22 Jul 2009 12:55:53 -0400 Subject: [Ovirt-devel] [PATCH node] Adds a new kernel cmdline argument to toggle SSH password auth. In-Reply-To: <1248278819-1910-2-git-send-email-dpierce@redhat.com> References: <1248278819-1910-1-git-send-email-dpierce@redhat.com> <1248278819-1910-2-git-send-email-dpierce@redhat.com> Message-ID: <4A674499.7040900@redhat.com> Darryl L. Pierce wrote: > The new karg is "ssh" and can be set using either 0/1 or true/false to > set whether SSH password auth will be enabled or not during an automated > install. > > rhbz#513037 > > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-config-password | 44 +++++++++++++++++++++++----------------- > scripts/ovirt-early | 10 ++++++++- > scripts/ovirt-firstboot | 1 + > 3 files changed, 35 insertions(+), 20 deletions(-) > > diff --git a/scripts/ovirt-config-password b/scripts/ovirt-config-password > index b6b9f07..ab0325a 100755 > --- a/scripts/ovirt-config-password > +++ b/scripts/ovirt-config-password > @@ -76,23 +76,29 @@ PASSWORD="Set administrator password" > SSH="Toggle SSH password authentication" > QUIT="Quit and Return To Menu" > > -while true; do > - state="disabled" > - /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ > - if [ $? == 0 ]; then > - state="enabled" > - fi > - printf "\nSSH password authentication is currently ${state}.\n\n" > - > - PS3="Please select an option: " > - select option in "$PASSWORD" "$SSH" "$QUIT" > - do > - case $option in > - $PASSWORD) set_password; break;; > - $SSH) toggle_ssh; break;; > - $QUIT) exit;; > - esac > +if [[ "$1" == "AUTO" ]]; then > + if [ -n "${OVIRT_SSH_PWAUTH}" ]; then > + toggle_ssh_access $OVIRT_SSH_PWAUTH > + fi > +else > + while true; do > + state="disabled" > + /usr/bin/augtool get /files/etc/ssh/sshd_config/PasswordAuthentication|grep -q yes$ > + if [ $? == 0 ]; then > + state="enabled" > + fi > + printf "\nSSH password authentication is currently ${state}.\n\n" > + > + PS3="Please select an option: " > + select option in "$PASSWORD" "$SSH" "$QUIT" > + do > + case $option in > + $PASSWORD) set_password; break;; > + $SSH) toggle_ssh; break;; > + $QUIT) exit;; > + esac > + done > + > + printf "\n" > done > - > - printf "\n" > -done > +fi > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > index 560fa14..8cf2cd0 100755 > --- a/scripts/ovirt-early > +++ b/scripts/ovirt-early > @@ -209,6 +209,7 @@ start() { > # dns=server[,server] > # ntp=server[,server] > # vlan=id > + # ssh_pwauth=[0|1] > # static network configuration > ip_address= > ip_gateway= > @@ -219,6 +220,7 @@ start() { > ipv6= > dns= > ntp= > + ssh_pwauth= > > # hostname=fqdn > # hostname > @@ -349,6 +351,12 @@ start() { > vlan=*) > vlan=${i#vlan=} > ;; > + ssh_pwauth=1 | ssh_pwauth=true) > + ssh_pwauth=true > + ;; > + ssh_pwauth=0 | ssh_pwauth=false) > + ssh_pwauth=false > + ;; > syslog=*) > i=${i#syslog=} > eval $(printf $i|awk -F: '{print "syslog_server="$1; print "syslog_port="$2;}') > @@ -370,7 +378,7 @@ start() { > ip_gateway=$gateway > fi > # save boot parameters as defaults for ovirt-config-* > - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > # mount /config unless firstboot is forced > if [ "$firstboot" != "1" ]; then > mount_config > diff --git a/scripts/ovirt-firstboot b/scripts/ovirt-firstboot > index 844f689..4969261 100755 > --- a/scripts/ovirt-firstboot > +++ b/scripts/ovirt-firstboot > @@ -46,6 +46,7 @@ start () > ovirt-config-networking AUTO > ovirt-config-logging AUTO > ovirt-config-collectd AUTO > + ovirt-config-password AUTO > if [ "$OVIRT_LOCAL_BOOT" = 1 ]; then > mount_live > ovirt-config-boot /live "$OVIRT_BOOTPARAMS" no > ack From jboggs at redhat.com Wed Jul 22 17:13:14 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 22 Jul 2009 13:13:14 -0400 Subject: [Ovirt-devel] [PATCH node] Changes the exit/continue based on context. In-Reply-To: <1247700542-25947-1-git-send-email-dpierce@redhat.com> References: <1247700542-25947-1-git-send-email-dpierce@redhat.com> Message-ID: <4A6748AA.9080502@redhat.com> Darryl L. Pierce wrote: > If the script is run during the system startup then it says "Continue > with stateless boot". > > If the script is run from the command line then it says "Quite and > Exit". > > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-config-setup | 22 ++++++++++++++++++++-- > scripts/ovirt-firstboot | 2 +- > 2 files changed, 21 insertions(+), 3 deletions(-) > > diff --git a/scripts/ovirt-config-setup b/scripts/ovirt-config-setup > index ee78254..4010828 100755 > --- a/scripts/ovirt-config-setup > +++ b/scripts/ovirt-config-setup > @@ -10,7 +10,6 @@ CONFIG_DIR=/etc/ovirt-config-setup.d > > # special options, all others execute the symlinked script in CONFIG_DIR > DEBUG_SHELL="Shell" > -CONTINUE="Continue Stateless Boot" > > declare -a OPTIONS > > @@ -32,13 +31,32 @@ for cfg in $CONFIG_DIR/*; do > fi > done > OPTIONS[${#OPTIONS[*]}]="$DEBUG_SHELL" > -OPTIONS[${#OPTIONS[*]}]="$CONTINUE" > > > # reset tty, otherwise serial console is broken > reset > /dev/null > clear > > +# set defaults > +has_continue_option=false > + > +while getopts x c; do > + case $c in > + x) has_continue_option=true;; > + '?') die "invalid option \`-$OPTARG'";; > + :) die "missing argument to \`-$OPTARG' option";; > + *) die "internal error";; > + esac > +done > + > +if $has_continue_option; then > + CONTINUE="Continue Stateless Boot" > +else > + CONTINUE="Quit And Exit" > +fi > +OPTIONS[${#OPTIONS[*]}]="$CONTINUE" > + > + > while true; do > PS3="Please select an option: " > > diff --git a/scripts/ovirt-firstboot b/scripts/ovirt-firstboot > index 844f689..4160e63 100755 > --- a/scripts/ovirt-firstboot > +++ b/scripts/ovirt-firstboot > @@ -55,7 +55,7 @@ start () > elif is_firstboot; then > plymouth --hide-splash > > - ovirt-config-setup < /dev/console > + ovirt-config-setup -x < /dev/console > > plymouth --show-splash > fi > ack From dpierce at redhat.com Wed Jul 22 17:21:09 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 13:21:09 -0400 Subject: [Ovirt-devel] [PATCH node-image] Add libmlx4 in order to provide Mellanox support. Message-ID: <1248283269-2898-1-git-send-email-dpierce@redhat.com> Added the RPM to the list of those installed. Resolves: rhbz#513238 Signed-off-by: Darryl L. Pierce --- common-install.ks | 2 +- common-pkgs.ks | 1 + 2 files changed, 2 insertions(+), 1 deletions(-) diff --git a/common-install.ks b/common-install.ks index a20a5b4..2a7fbb7 100644 --- a/common-install.ks +++ b/common-install.ks @@ -4,7 +4,7 @@ timezone --utc UTC auth --useshadow --enablemd5 selinux --enforcing firewall --disabled -part / --size 550 --fstype ext2 +part / --size 600 --fstype ext2 services --enabled=auditd,ntpd,ntpdate,collectd,iptables,network,rsyslog,libvirt-qpid # This requires a new fixed version of livecd-creator to honor the --append settings. bootloader --timeout=30 --append="console=tty0 console=ttyS0,115200n8" diff --git a/common-pkgs.ks b/common-pkgs.ks index dd25c15..3c0f278 100644 --- a/common-pkgs.ks +++ b/common-pkgs.ks @@ -9,6 +9,7 @@ dhclient openssh-clients openssh-server kvm +libmlx4 ovirt-node-stateless ovirt-node-selinux ovirt-node-logos -- 1.6.2.5 From dpierce at redhat.com Wed Jul 22 17:30:00 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 13:30:00 -0400 Subject: [Ovirt-devel] [PATCH node] Changes the exit/continue based on context. In-Reply-To: <4A6748AA.9080502@redhat.com> References: <1247700542-25947-1-git-send-email-dpierce@redhat.com> <4A6748AA.9080502@redhat.com> Message-ID: <20090722173000.GB18818@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 22, 2009 at 01:13:14PM -0400, Joey Boggs wrote: > ack Thanks. This is pushed. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Wed Jul 22 17:34:33 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 13:34:33 -0400 Subject: [Ovirt-devel] [PATCH node] Adds a new kernel cmdline argument to toggle SSH password auth. In-Reply-To: <4A674499.7040900@redhat.com> References: <1248278819-1910-1-git-send-email-dpierce@redhat.com> <1248278819-1910-2-git-send-email-dpierce@redhat.com> <4A674499.7040900@redhat.com> Message-ID: <20090722173433.GC18818@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 22, 2009 at 12:55:53PM -0400, Joey Boggs wrote: > ack Thanks. This is pushed upstream. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From pmyers at redhat.com Wed Jul 22 17:48:33 2009 From: pmyers at redhat.com (Perry Myers) Date: Wed, 22 Jul 2009 13:48:33 -0400 Subject: [Ovirt-devel] [PATCH node-image] Add libmlx4 in order to provide Mellanox support. In-Reply-To: <1248283269-2898-1-git-send-email-dpierce@redhat.com> References: <1248283269-2898-1-git-send-email-dpierce@redhat.com> Message-ID: <4A6750F1.3000907@redhat.com> On 07/22/2009 01:21 PM, Darryl L. Pierce wrote: > Added the RPM to the list of those installed. Ack Perry From dhuff at redhat.com Wed Jul 22 21:17:11 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 22 Jul 2009 17:17:11 -0400 Subject: [Ovirt-devel] [PATCH ovirt-node-image] alias vi to vi + restorecon Message-ID: <1248297431-2070-1-git-send-email-dhuff@redhat.com> fixes a problem with editing a bindmounted file with vi. The file will not retain the same selinux context due to the way vi copies files around. Resolves rhbz#509082 --- common-post.ks | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/common-post.ks b/common-post.ks index 8a4940a..c85be15 100644 --- a/common-post.ks +++ b/common-post.ks @@ -131,6 +131,11 @@ sed -i 's/node\.session\.initial_login_retry_max.*/node.session.initial_login_re # root's bash profile cat >> /root/.bashrc < From: Michael Burns Was backing up to backup.log and restoring from backup.lo. Fixed to use lo in both places. Signed-off-by: Michael Burns --- scripts/ovirt-config-networking | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 5443523..cc7626b 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -88,7 +88,7 @@ function configure_interface printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" if ask_yes_or_no "Do you wish to continue (y/n)?"; then printf "\nDeleting existing network configuration...\n" - cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.lo remove_config /etc/sysconfig/network-scripts/ifcfg-* rm -rf /etc/sysconfig/network-scripts/ifcfg-* cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo -- 1.6.2.5 From dpierce at redhat.com Wed Jul 22 22:24:15 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 22 Jul 2009 18:24:15 -0400 Subject: [Ovirt-devel] [PATCH][node] Fixed typo in o-c-networking that was causing loss of ifcfg-lo In-Reply-To: <1248300867-24840-1-git-send-email-mburns@redhat.com> References: <1248300867-24840-1-git-send-email-mburns@redhat.com> Message-ID: <20090722222415.GB4686@mcpierce-laptop> On Wed, Jul 22, 2009 at 06:14:27PM -0400, Mike Burns wrote: > From: Michael Burns > > Was backing up to backup.log and restoring from backup.lo. > Fixed to use lo in both places. > > Signed-off-by: Michael Burns > --- > scripts/ovirt-config-networking | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking > index 5443523..cc7626b 100755 > --- a/scripts/ovirt-config-networking > +++ b/scripts/ovirt-config-networking > @@ -88,7 +88,7 @@ function configure_interface > printf "This will delete the current configuration for ${CONFIGURED_NIC}.\n" > if ask_yes_or_no "Do you wish to continue (y/n)?"; then > printf "\nDeleting existing network configuration...\n" > - cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.log > + cp -a /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/backup.lo > remove_config /etc/sysconfig/network-scripts/ifcfg-* > rm -rf /etc/sysconfig/network-scripts/ifcfg-* > cp -a /etc/sysconfig/network-scripts/backup.lo /etc/sysconfig/network-scripts/ifcfg-lo > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From mburns at redhat.com Wed Jul 22 23:56:44 2009 From: mburns at redhat.com (Mike Burns) Date: Wed, 22 Jul 2009 19:56:44 -0400 Subject: [Ovirt-devel] [PATCH][NODE] Cleanup old bridges when re-running o-c-networking Message-ID: <1248307004-26705-1-git-send-email-mburns@redhat.com> From: Michael Burns Detects and removes existing bridges named breth* Resolves rhbz#513062 Signed-off-by: Michael Burns --- scripts/ovirt-config-networking | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index cc7626b..837d493 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -515,5 +515,10 @@ stop_log if [ "$net_configured" = 1 ]; then service network stop > /dev/null 2>&1 + for i in `brctl show | grep breth | awk '{print $1}'` + do + ifconfig $i down + brctl delbr $i + done service network start fi -- 1.6.2.5 From dpierce at redhat.com Thu Jul 23 13:29:09 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 23 Jul 2009 09:29:09 -0400 Subject: [Ovirt-devel] [PATCH ovirt-node-image] alias vi to vi + restorecon In-Reply-To: <1248297431-2070-1-git-send-email-dhuff@redhat.com> References: <1248297431-2070-1-git-send-email-dhuff@redhat.com> Message-ID: <20090723132909.GB4295@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 22, 2009 at 05:17:11PM -0400, David Huff wrote: > fixes a problem with editing a bindmounted file with vi. The file will not > retain the same selinux context due to the way vi copies files around. > > Resolves rhbz#509082 > --- > common-post.ks | 5 +++++ > 1 files changed, 5 insertions(+), 0 deletions(-) > > diff --git a/common-post.ks b/common-post.ks > index 8a4940a..c85be15 100644 > --- a/common-post.ks > +++ b/common-post.ks > @@ -131,6 +131,11 @@ sed -i 's/node\.session\.initial_login_retry_max.*/node.session.initial_login_re > # root's bash profile > cat >> /root/.bashrc < # aliases used for the temporary > +function mod_vi() { > + vi $@ > + restorecon -v $@ > +} > +alias vi="mod_vi" > alias ping='ping -c 3' > EOF > > -- > 1.6.0.6 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Thu Jul 23 13:29:59 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 23 Jul 2009 09:29:59 -0400 Subject: [Ovirt-devel] [PATCH][NODE] Cleanup old bridges when re-running o-c-networking In-Reply-To: <1248307004-26705-1-git-send-email-mburns@redhat.com> References: <1248307004-26705-1-git-send-email-mburns@redhat.com> Message-ID: <20090723132959.GD4295@mcpierce-laptop.rdu.redhat.com> On Wed, Jul 22, 2009 at 07:56:44PM -0400, Mike Burns wrote: > From: Michael Burns > > Detects and removes existing bridges named breth* > > Resolves rhbz#513062 > > Signed-off-by: Michael Burns > --- > scripts/ovirt-config-networking | 5 +++++ > 1 files changed, 5 insertions(+), 0 deletions(-) > > diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking > index cc7626b..837d493 100755 > --- a/scripts/ovirt-config-networking > +++ b/scripts/ovirt-config-networking > @@ -515,5 +515,10 @@ stop_log > > if [ "$net_configured" = 1 ]; then > service network stop > /dev/null 2>&1 > + for i in `brctl show | grep breth | awk '{print $1}'` > + do > + ifconfig $i down > + brctl delbr $i > + done > service network start > fi > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From sseago at redhat.com Thu Jul 23 17:53:59 2009 From: sseago at redhat.com (Scott Seago) Date: Thu, 23 Jul 2009 17:53:59 +0000 Subject: [Ovirt-devel] [PATCH server] changes required for fedora rawhide inclusion. Message-ID: <1248371639-24255-1-git-send-email-sseago@redhat.com> Signed-off-by: Scott Seago --- AUTHORS | 17 ++++++ README | 10 +++ conf/ovirt-agent | 12 ++++ conf/ovirt-db-omatic | 12 ++++ conf/ovirt-host-browser | 12 ++++ conf/ovirt-host-collect | 12 ++++ conf/ovirt-host-register | 12 ++++ conf/ovirt-mongrel-rails | 7 ++- conf/ovirt-server.logrotate | 57 +++++++++++++++++++ conf/ovirt-taskomatic | 12 ++++ conf/ovirt-vnc-proxy | 12 ++++ ovirt-server.spec.in | 59 +++++++++++-------- src/app/util/stats/Stats.rb | 1 - src/app/util/stats/StatsData.rb | 1 - src/app/util/stats/StatsDataList.rb | 1 - src/app/util/stats/StatsRequest.rb | 1 - src/app/util/stats/StatsTypes.rb | 1 - src/app/util/stats/statsTest.rb | 1 - src/dutils/active_record_env.rb | 1 - src/flexchart/flexchart.swf | Bin 370538 -> 0 bytes src/public/.htaccess | 40 ------------- src/test/unit/host_browser_awaken_test.rb | 1 - 22 files changed, 208 insertions(+), 74 deletions(-) create mode 100644 AUTHORS create mode 100644 README create mode 100644 conf/ovirt-server.logrotate mode change 100644 => 100755 installer/modules/ovirt/files/cobbler-import mode change 100644 => 100755 installer/modules/ovirt/files/ovirt-appliance-setup mode change 100644 => 100755 installer/modules/ovirt/files/ovirt-storage mode change 100755 => 100644 src/app/views/layouts/ovirt-layout.rhtml delete mode 100644 src/app/views/quota/show.rhtml delete mode 100644 src/flexchart/flexchart.swf delete mode 100644 src/public/.htaccess mode change 100755 => 100644 src/public/javascripts/jquery.js mode change 100755 => 100644 src/public/javascripts/ui.core.js mode change 100755 => 100644 src/public/javascripts/ui.slider.js diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..badbf1b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,17 @@ + ovirt-server Authors + ==================== + +The primary maintainers and people with commit access rights: + + Scott Seago + Hugh Brock + Steve Linabery + Jay Guiditta + Mohammed Morsi + Ian Main + Chris Lalancette + Darryl Pierce + Arjun Roy + David Lutterkort + + [....send patches to get your name here....] diff --git a/README b/README new file mode 100644 index 0000000..95e9330 --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +The oVirt Server is an open cross-platform virtualization management +system. It provides a web-based management interface that enables +users to manage hosts and storage, install and remove virtual machines +and level resources across a large group of machines. The oVirt Server +manages hosts running the oVirt Node Image. oVirt Server Suite scales +from a small group of users with little need for access control and +quota management, all the way up to hundreds of hosts with robust +control over grouping, permissions, and quotas. + +For further docs see: http://ovirt.org diff --git a/conf/ovirt-agent b/conf/ovirt-agent index 1067a24..b576f86 100755 --- a/conf/ovirt-agent +++ b/conf/ovirt-agent @@ -12,6 +12,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" DAEMON=/usr/share/ovirt-server/ovirt-agent/ovirt-agent.rb +AGENT_LOCKFILE="${AGENT_LOCKFILE:-/var/lock/subsys/ovirt-agent }" . /etc/init.d/functions @@ -20,6 +21,9 @@ start() { daemon $DAEMON RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $AGENT_LOCKFILE + fi } stop() { @@ -27,6 +31,9 @@ stop() { killproc ovirt-agent.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $AGENT_LOCKFILE + fi } case "$1" in @@ -40,6 +47,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/conf/ovirt-db-omatic b/conf/ovirt-db-omatic index f8337e0..cba275d 100755 --- a/conf/ovirt-db-omatic +++ b/conf/ovirt-db-omatic @@ -13,6 +13,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" DAEMON=/usr/share/ovirt-server/db-omatic/db_omatic.rb +DBOMATIC_LOCKFILE="${DBOMATIC_LOCKFILE:-/var/lock/subsys/ovirt-db-omatic }" . /etc/init.d/functions @@ -21,6 +22,9 @@ start() { daemon $DAEMON RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $DBOMATIC_LOCKFILE + fi } stop() { @@ -28,6 +32,9 @@ stop() { killproc db_omatic.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $DBOMATIC_LOCKFILE + fi } case "$1" in @@ -41,6 +48,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/conf/ovirt-host-browser b/conf/ovirt-host-browser index 3112c3f..754410b 100755 --- a/conf/ovirt-host-browser +++ b/conf/ovirt-host-browser @@ -13,6 +13,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" DAEMON=/usr/share/ovirt-server/host-browser/host-browser.rb +HOST_BROWSER_LOCKFILE="${HOST_BROWSER_LOCKFILE:-/var/lock/subsys/ovirt-host-browser }" . /etc/init.d/functions @@ -21,6 +22,9 @@ start() { daemon $DAEMON RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $HOST_BROWSER_LOCKFILE + fi } stop() { @@ -28,6 +32,9 @@ stop() { killproc host-browser.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $HOST_BROWSER_LOCKFILE + fi } case "$1" in @@ -41,6 +48,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/conf/ovirt-host-collect b/conf/ovirt-host-collect index d2ac08c..5c05020 100755 --- a/conf/ovirt-host-collect +++ b/conf/ovirt-host-collect @@ -13,6 +13,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" DAEMON=/usr/share/ovirt-server/host-collect/host-collect.rb +HOST_COLLECT_LOCKFILE="${HOST_COLLECT_LOCKFILE:-/var/lock/subsys/ovirt-host-collect }" . /etc/init.d/functions @@ -21,6 +22,9 @@ start() { daemon $DAEMON RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $HOST_COLLECT_LOCKFILE + fi } stop() { @@ -28,6 +32,9 @@ stop() { killproc host-collect.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $HOST_COLLECT_LOCKFILE + fi } case "$1" in @@ -41,6 +48,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/conf/ovirt-host-register b/conf/ovirt-host-register index 9f47e23..2335eab 100644 --- a/conf/ovirt-host-register +++ b/conf/ovirt-host-register @@ -14,6 +14,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" DAEMON=/usr/share/ovirt-server/host-browser/host-register.rb +HOST_REGISTER_LOCKFILE="${HOST_REGISTER_LOCKFILE:-/var/lock/subsys/ovirt-host-register }" . /etc/init.d/functions @@ -22,6 +23,9 @@ start() { daemon $DAEMON RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $HOST_REGISTER_LOCKFILE + fi } stop() { @@ -29,6 +33,9 @@ stop() { killproc host-register.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $HOST_REGISTER_LOCKFILE + fi } case "$1" in @@ -42,6 +49,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/conf/ovirt-mongrel-rails b/conf/ovirt-mongrel-rails index e1a6e5c..4050989 100755 --- a/conf/ovirt-mongrel-rails +++ b/conf/ovirt-mongrel-rails @@ -16,7 +16,7 @@ RAILS_ENV="${RAILS_ENV:-production}" OVIRT_DIR="${OVIRT_DIR:-/usr/share/ovirt-server}" MONGREL_LOG="${MONGREL_LOG:-/var/log/ovirt-server/mongrel.log}" MONGREL_PID="${MONGREL_PID:-/var/run/ovirt-server/mongrel.pid}" -MONGREL_LOCKFILE="${MONGREL_LOCKFILE:-/var/lock/subsys/ovirt-server}" +MONGREL_LOCKFILE="${MONGREL_LOCKFILE:-/var/lock/subsys/ovirt-mongrel-rails }" USER="${USER:-ovirt}" GROUP="${GROUP:-ovirt}" PREFIX="${PREFIX:-/ovirt}" @@ -70,6 +70,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $MONGREL_PROG RETVAL=$? diff --git a/conf/ovirt-server.logrotate b/conf/ovirt-server.logrotate new file mode 100644 index 0000000..3239783 --- /dev/null +++ b/conf/ovirt-server.logrotate @@ -0,0 +1,57 @@ +weekly +rotate 52 +compress +missingok +notifempty + +/etc/init.d/ovirt-host-register +/etc/init.d/ovirt-host-collect + +/var/log/ovirt-server/taskomatic.log { + postrotate + /etc/init.d/ovirt-taskomatic restart + endscript +} + +/var/log/ovirt-server/db-omatic.log { + postrotate + /etc/init.d/ovirt-db-omatic restart + endscript +} + +/var/log/ovirt-server/host-browser.log { + postrotate + /etc/init.d/ovirt-host-browser restart + endscript +} + +/var/log/ovirt-server/host-register.log { + postrotate + /etc/init.d/ovirt-host-register restart + endscript +} + +/var/log/ovirt-server/host-collect.log { + postrotate + /etc/init.d/ovirt-host-collect restart + endscript +} + +/var/log/ovirt-server/vnc-proxy.log { + postrotate + /etc/init.d/ovirt-vnc-proxy restart + endscript +} + +/var/log/ovirt-server/ovirt-agent.log { + postrotate + /etc/init.d/ovirt-agent restart + endscript +} + +/var/log/ovirt-server/rails.log /var/log/ovirt-server/mongrel.log { + sharedscripts + postrotate + /etc/init.d/ovirt-mongrel-rails restart + endscript +} diff --git a/conf/ovirt-taskomatic b/conf/ovirt-taskomatic index cccd5ed..c331747 100755 --- a/conf/ovirt-taskomatic +++ b/conf/ovirt-taskomatic @@ -14,6 +14,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" export COBBLER_YML="${COBBLER_YML:-/usr/share/ovirt-server/config/cobbler.yml}" DAEMON=/usr/share/ovirt-server/task-omatic/taskomatic.rb +TASKOMATIC_LOCKFILE="${TASKOMATIC_LOCKFILE:-/var/lock/subsys/ovirt-taskomatic }" . /etc/init.d/functions @@ -22,6 +23,9 @@ start() { daemon $DAEMON RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $TASKOMATIC_LOCKFILE + fi } stop() { @@ -29,6 +33,9 @@ stop() { killproc taskomatic.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $TASKOMATIC_LOCKFILE + fi } case "$1" in @@ -42,6 +49,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/conf/ovirt-vnc-proxy b/conf/ovirt-vnc-proxy index a4387f1..c9c266f 100755 --- a/conf/ovirt-vnc-proxy +++ b/conf/ovirt-vnc-proxy @@ -15,6 +15,7 @@ export RAILS_ENV="${RAILS_ENV:-production}" export OVIRT_VNC_PROXY_PORT="${OVIRT_VNC_PROXY_PORT:-5900}" DAEMON=/usr/share/ovirt-server/vnc-proxy/vnc-proxy.rb +VNC_PROXY_LOCKFILE="${VNC_PROXY_LOCKFILE:-/var/lock/subsys/ovirt-vnc-proxy }" . /etc/init.d/functions @@ -23,6 +24,9 @@ start() { daemon $DAEMON --port $OVIRT_VNC_PROXY_PORT RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + touch $VNC_PROXY_LOCKFILE + fi } stop() { @@ -30,6 +34,9 @@ stop() { killproc vnc-proxy.rb RETVAL=$? echo + if [ $RETVAL -eq 0 ]; then + rm $VNC_PROXY_LOCKFILE + fi } case "$1" in @@ -43,6 +50,11 @@ case "$1" in stop start ;; + reload) + ;; + force-reload) + restart + ;; status) status $DAEMON RETVAL=$? diff --git a/installer/modules/ovirt/files/cobbler-import b/installer/modules/ovirt/files/cobbler-import old mode 100644 new mode 100755 diff --git a/installer/modules/ovirt/files/ovirt-appliance-setup b/installer/modules/ovirt/files/ovirt-appliance-setup old mode 100644 new mode 100755 diff --git a/installer/modules/ovirt/files/ovirt-storage b/installer/modules/ovirt/files/ovirt-storage old mode 100644 new mode 100755 diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index d762178..003785f 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -2,15 +2,18 @@ %define app_root %{_datadir}/%{name} %define acehome %{_datadir}/ace -Summary: oVirt Server Suite +Summary: The oVirt Server Suite Name: ovirt-server Version: @VERSION@ Release: 0%{?dist}%{?extra_release} +# full source URL will be added with the next oVirt release. This is a pre-release +# code drop to make sure we get the package approved by f12 feature freeze. Source0: %{name}-%{version}.tar.gz #Entire source code is GPL except for vendor/plugins/will_paginate and #vendor/plugins/betternestedset, which are MIT, and -#public/javascripts/jquery.*, which is both MIT and GPL -License: GPLv2+ and MIT +#public/javascripts/jquery.*, which is both MIT and GPL, and +#src/flexchart/com/adobe/serialization/json/* which are BSD +License: GPLv2+ and MIT and BSD Group: Applications/System Requires: ruby >= 1.8.1 Requires: ruby(abi) = 1.8 @@ -50,7 +53,6 @@ BuildRequires: ruby >= 1.8.1 BuildRequires: ruby-devel BuildRequires: rubygem(gettext) BuildRequires: rubygem(rake) >= 0.7 -Provides: ovirt-server BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot URL: http://ovirt.org/ @@ -63,6 +65,7 @@ Requires: ace Requires: ace-postgres Requires: rubygem(highline) Requires: hal +Requires: %{name} = %{version}-%{release} %description The Server Suite for oVirt. @@ -76,7 +79,7 @@ The Installer for the ovirt server suite %build %install -test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT +test "x%{buildroot}" != "x" && rm -rf %{buildroot} mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{_bindir} @@ -87,6 +90,7 @@ mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/httpd/conf.d %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/%{name} %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/%{name}/db +%{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/cron.d %{__install} -d -m0755 %{buildroot}%{_localstatedir}/lib/%{name} %{__install} -d -m0755 %{buildroot}%{_localstatedir}/log/%{name} @@ -94,12 +98,17 @@ mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{app_root} %{__install} -d -m0755 %{buildroot}/%{acehome} +# Creating these files now to make sure the logfiles will be owned +# by ovirt:ovirt. This is a temporary workaround while we've still +# got root-owned daemon processes. Once we resolve that issue +# these files will no longer be added explicitly here. touch %{buildroot}%{_localstatedir}/log/%{name}/mongrel.log touch %{buildroot}%{_localstatedir}/log/%{name}/rails.log touch %{buildroot}%{_localstatedir}/log/%{name}/taskomatic.log touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__install} -p -m0644 %{pbuild}/conf/%{name}.conf %{buildroot}%{_sysconfdir}/httpd/conf.d %{__install} -p -m0644 %{pbuild}/conf/%{name}.crontab %{buildroot}%{_sysconfdir}/cron.d/%{name} +%{__install} -p -m0644 %{pbuild}/conf/%{name}.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-browser %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-register %{buildroot}%{_initrddir} @@ -107,9 +116,9 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-agent %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-host-collect %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-mongrel-rails %{buildroot}%{_initrddir} -%{__install} -Dp -m0755 %{pbuild}/conf/ovirt-mongrel-rails.sysconf %{buildroot}%{_sysconfdir}/sysconfig/ovirt-mongrel-rails -%{__install} -Dp -m0755 %{pbuild}/conf/ovirt-rails.sysconf %{buildroot}%{_sysconfdir}/sysconfig/ovirt-rails -%{__install} -Dp -m0755 %{pbuild}/conf/ovirt-vnc-proxy.sysconf %{buildroot}%{_sysconfdir}/sysconfig/ovirt-vnc-proxy +%{__install} -Dp -m0644 %{pbuild}/conf/ovirt-mongrel-rails.sysconf %{buildroot}%{_sysconfdir}/sysconfig/ovirt-mongrel-rails +%{__install} -Dp -m0644 %{pbuild}/conf/ovirt-rails.sysconf %{buildroot}%{_sysconfdir}/sysconfig/ovirt-rails +%{__install} -Dp -m0644 %{pbuild}/conf/ovirt-vnc-proxy.sysconf %{buildroot}%{_sysconfdir}/sysconfig/ovirt-vnc-proxy %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-taskomatic %{buildroot}%{_initrddir} %{__install} -Dp -m0755 %{pbuild}/conf/ovirt-vnc-proxy %{buildroot}%{_initrddir} @@ -118,7 +127,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log # move Flash movie to the public folder %{__install} -d -m0755 %{buildroot}%{app_root}/public/swfs -%{__mv} %{buildroot}%{app_root}/flexchart/flexchart.swf %{buildroot}%{app_root}/public/swfs +# not building Flash for now until we've got flex compiler in Fedora # move configs to /etc, keeping symlinks for Rails %{__mv} %{buildroot}%{app_root}/config/database.yml %{buildroot}%{_sysconfdir}/%{name} @@ -136,6 +145,9 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log # remove the files not needed for the installation %{__rm} -f %{buildroot}%{app_root}/task-omatic/.gitignore +%{__rm} -f %{buildroot}%{app_root}/vendor/plugins/will_paginate/.gitignore +%{__rm} -f %{buildroot}%{app_root}/vendor/plugins/will_paginate/.manifest +%{__rm} -f %{buildroot}%{app_root}/vendor/plugins/acts_as_xapian/.gitignore %{__cp} -a %{pbuild}/scripts/ovirt-add-host %{buildroot}%{_bindir} %{__cp} -a %{pbuild}/scripts/ovirt-vm2node %{buildroot}%{_bindir} @@ -157,11 +169,12 @@ for f in anyterm/*.{html,css,js,png,gif}; do done %clean -rm -rf $RPM_BUILD_ROOT +rm -rf %{buildroot} %pre -/usr/sbin/groupadd -r ovirt 2>/dev/null || : -/usr/sbin/useradd -g ovirt -c "oVirt" \ +getent group ovirt >/dev/null || /usr/sbin/groupadd -g 108 -r ovirt 2>/dev/null || : +getent passwd ovirt >/dev/null || \ + /usr/sbin/useradd -u 108 -g ovirt -c "oVirt" \ -s /sbin/nologin -r -d /var/ovirt ovirt 2> /dev/null || : %post @@ -171,20 +184,10 @@ rm -rf $RPM_BUILD_ROOT LISTRET=$? \ /sbin/chkconfig --add %{-d*} \ if [ $LISTRET -ne 0 ]; then \ - /sbin/chkconfig %{-d*} on \ + /sbin/chkconfig %{-d*} on \ fi \ %{nil} -#removes obsolete services if present -if [ -e %{_initrddir}/ovirt-server ] ; then - /sbin/service ovirt-server stop > /dev/null 2>&1 - /sbin/service ovirt-host-keyadd stop > /dev/null 2>&1 - /sbin/chkconfig --del ovirt-server - /sbin/chkconfig --del ovirt-host-keyadd - %{__rm} %{_initrddir}/ovirt-server - %{__rm} %{_initrddir}/ovirt-host-keyadd -fi - # if this is the initial RPM install, then we want to turn the new services # on; otherwise, we respect the choices the administrator already has made. # check this by seeing if each daemon is already installed @@ -231,12 +234,13 @@ fi %{_initrddir}/ovirt-mongrel-rails %{_initrddir}/ovirt-taskomatic %{_initrddir}/ovirt-vnc-proxy -%{_sysconfdir}/cron.d/%{name} +%config(noreplace) %{_sysconfdir}/cron.d/%{name} +%config(noreplace) %{_sysconfdir}/logrotate.d/%{name} %config(noreplace) %{_sysconfdir}/sysconfig/ovirt-mongrel-rails %config(noreplace) %{_sysconfdir}/sysconfig/ovirt-rails %config(noreplace) %{_sysconfdir}/sysconfig/ovirt-vnc-proxy %config(noreplace) %{_sysconfdir}/httpd/conf.d/%{name}.conf -%doc +%doc README AUTHORS COPYING %attr(-, ovirt, ovirt) %{_localstatedir}/lib/%{name} %attr(-, ovirt, ovirt) %{_localstatedir}/run/%{name} %attr(-, ovirt, ovirt) %{_localstatedir}/log/%{name} @@ -252,11 +256,16 @@ fi %{_datadir}/ovirt-anyterm %files installer +%defattr(-,root,root,0755) %{_sbindir}/ovirt-installer %{acehome} +%doc README AUTHORS COPYING %changelog +* Fri Jul 17 2009 Scott Seago - 0.100-1 +- rpmlint fixes for Fedora 12 inclusion + * Thu May 29 2008 Alan Pevec - 0.0.5-0 - use rubygem-krb5-auth diff --git a/src/app/util/stats/Stats.rb b/src/app/util/stats/Stats.rb index fee3b28..442519e 100644 --- a/src/app/util/stats/Stats.rb +++ b/src/app/util/stats/Stats.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Mark Wagner diff --git a/src/app/util/stats/StatsData.rb b/src/app/util/stats/StatsData.rb index b1e2edb..91fd90d 100644 --- a/src/app/util/stats/StatsData.rb +++ b/src/app/util/stats/StatsData.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Mark Wagner diff --git a/src/app/util/stats/StatsDataList.rb b/src/app/util/stats/StatsDataList.rb index 4f7928f..ef49859 100644 --- a/src/app/util/stats/StatsDataList.rb +++ b/src/app/util/stats/StatsDataList.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Mark Wagner diff --git a/src/app/util/stats/StatsRequest.rb b/src/app/util/stats/StatsRequest.rb index bd37628..da7f15e 100644 --- a/src/app/util/stats/StatsRequest.rb +++ b/src/app/util/stats/StatsRequest.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Mark Wagner diff --git a/src/app/util/stats/StatsTypes.rb b/src/app/util/stats/StatsTypes.rb index 4896bb3..7700142 100644 --- a/src/app/util/stats/StatsTypes.rb +++ b/src/app/util/stats/StatsTypes.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Mark Wagner diff --git a/src/app/util/stats/statsTest.rb b/src/app/util/stats/statsTest.rb index d506a7e..7af2a36 100644 --- a/src/app/util/stats/statsTest.rb +++ b/src/app/util/stats/statsTest.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Mark Wagner diff --git a/src/app/views/layouts/ovirt-layout.rhtml b/src/app/views/layouts/ovirt-layout.rhtml old mode 100755 new mode 100644 diff --git a/src/app/views/quota/show.rhtml b/src/app/views/quota/show.rhtml deleted file mode 100644 index e69de29..0000000 diff --git a/src/dutils/active_record_env.rb b/src/dutils/active_record_env.rb index 7795b24..bc3bfcb 100644 --- a/src/dutils/active_record_env.rb +++ b/src/dutils/active_record_env.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby # # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago diff --git a/src/flexchart/flexchart.swf b/src/flexchart/flexchart.swf deleted file mode 100644 index 7e2e4a9d2dd664d0c0f529e2848e20c327ee818a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 370538 zcmV)SK(fC>S5pZFXA1y$+QhsEd{o8yH-6 at vIWv29H$5aF6rKQ3X$V$mfj&6FF>}$PajHhSYh7_--odBlDtZ*+*~0cT;a~eL<%i z+7)!_*s-(O4MDxJLz1*D3MTL-rF;7O+Koy#b^Ou|e{pJ0cj?_^NxGN#%*`pTG@%r84BL_Fbc-EA5rvDHrgU zMx0FjPW at WTcHj6uuNc+PklxVHkOxSA@ZBTi$f5_?y9d5dAOGUC4sPDipf-#eP4|u( z!y10k8X88^hF=7=fi^V!%o|1_9Hm|u^)tV4L3{3(G4#Stwc+Dm_}O_obtOY~y`jgS z%`v3^dGR9gclKsQIfuGGE4)_zy>+u0p1k?nEB-d)$!}h?(jVPy@^7-z`)@YuIcCux zyZN8lTW@*eA7d^0xtnVzl56+xIdSt48NPY*QA1&U%kJ@&M>Ky;Bt>dq`wD-!HsGyK z>28K*KIzyX|5MQub?-xXI-ZdBu_`Ef|dIXd$C~EBH+{eM=&KW6td?kwK!q2a*BHd%j?2cs#_4N z(_iBYS?x!BBO=aHpdND3^r)*T7y9Tf9#GsZSX~>e!NR6>7_-}~#y3o?biGIf&QgE1FC?P| z24!X$V(psQy at PdOpNys?3`{VGBlQ8F7{3TDdZ(;z{rgKocVBtH8ch!D>h#wZE^t4pUHXCQ}jwtan4%a0&)IB!uys-7E@)g61gP#eHK5 z{q{asFNPGj-xsLJEEXf{4YLyP_3)O%1k`5~M{_NT8$(0Q!MYISn_gUEMXl!3qJ*w> zH5CD=3PVEP+A4o}IK3nYny(_NEsm1 at z06(}VL~gA)dO?fq&PlR;u`{;3)QD4*2{~# z)N`6rov#<&ipXpuhhgKHVFrt at 667{E<>DC#ia at S{Ttg&EPIt?F|p* zs7`dih}b5c3gOdGFc?X(%9`>bBS<~3^arr&!bb1lFuxCrueNEtiYDmEm5sVma9ysK zZd8Zr5gq)(bkukQE=(_WNvSG1e%Ktj7kDB9_w))Q6f$bi5vh#L(Bq;J!chAoeM7wy zrlwgx{6YSf<64g(u!hn2aSc zid at v5#ICsG%cpyi3{|FCM|=fT%mDMq1(+V^`^yUCRQbIDjHM#oHv*e<1XB|Tlwp`* zxU#lwG!Qi>$K?-8$(2B_bK^B3&XT)Z$65bWPlCmCgu-%Fg;V8(4yd(c-!w_9VwPQq zEH={_6~0Pe2vY7NHhD4S+3_W9i=SyuZ0|T-ORcd>$W`GoH_N(+KM>X>#bM7_PGfy3 zjHK2COXGTy9r6tkstOZrt`lK-5{8kuL6yr|gu%`-WtE`t8R%Td8?VEzxXEhzE0QjZ zw8UT|m@(l;_yfXIiT63e<_2rq1h*${A##st#jNnIyhfC~xEfR4SVp0+iRqrC9Vfpr z!I?W-%V;H at XskONA8W9Uwb3mY2*N^`TdXstsre90H{pFiFk;vNUnD9sVf4D8FzjKP zCvoFUnAMnX(IinVZ0jOtt3*n=qKRnRUKX}99?pyP$aW4)F{F7C6(z%yyzJ6r{)Ab? zh#Kn at d$~(1(jq1`(VFcdTb<0WfB=i+s>KW!$8|EC>Pe_4yRosSMiTna`oB53UY6iV zHF_{|SH at 9O!ttfu at dt~X=z*h4JUKh+X~Z3NxUWWdAr;&o?kB8ChCRC8{u;|J!WSq9 zryQ=1Z31muB$FY%%p3Lv(7}Geu=Kbnn8J=~?+97MQSGmhk at RTQR!&A7!E#)x?N&1* z!=alHe#{xkuw9mD*V5xUD{C~W?V8g3HRXZ23hb`Ey!FAlh!ZB`4%3}VG2Ik%_pL~e zt5w!<>Bas+($0~cFma|c7B2WzH^XAcP1rBHME6VKn|G~``0nrqaH`Ot(V2iGg>;3h zg2OH6Fss5k9O5uoRAcV$ss5lm%Gu6=bWAvQ1Yrr%5XR?bBArw}45v6+`9g6AOu?39 z1>DvZf;?Ad6i3Hk^(+M?gn-Q3fE1jMVkvjLuhcBQo}@Oigdou~?-0yB6tS}9~X_>|S{i0{)9wd)(&0{O; z;!H#42$Sp%$K1P$CdF~;6J5(KqKmPy+@{gok80q>B^VX)gGFSc&8Y?7rVatYOj zg5t#CtFUhcgQ2k7W)yBrTGC}g^Z2WVE=ieAadtAVqM}FU7zyr&dC8S#=UFHBX0XW+ zcZGe2lDLF$=n62Y-xWr=HPVV4tBAO*h~sWzk*ymQi)u3`X}IANJYs&b(7WoSrI;>g zjn$IG=d23_teXfbdZ{5T>q}~_MS|dx?`Seb(I`#cLpbI6P2Tu9gsUOesG!NCo-f?e z^pcX?p&r4InV%t#YYEpr^3*DpgTIC$w;0DfQ-8$KZUD|T8Q3 at z%ZS?ybI#w99eKDd(CnH)*c3sWI18rI9uSsfmpg&S&Q$%U&cCo)-Tv0`WH5s-BMJZX) ztH3VNt3bUx0JD2ZqQSq&TBemHI(;sAWngY!P8pZ`Q5>7}+v3vFiA*g^I60a3dvcv$ zE)I2~Nn3NedPD8K;nvsv{`xL4E-JQ`)Y76mOFg{{OKvIdd?M|8}QQ;DMRw}HfoR$!*rSP1y8u72wlcneML}!C}h6zu| z>5)1odX(--Qu%m-bzVwZBs?}4vnST<*q*DY%H~jGlckHvzf!wZF`u4KNao7CB-%)!f93xDP3=HB zrNUR~tqVlVuRU^6Njgsc;>Z#VWhXEcnJ3$(VwZ39!;o8CP?UPhotUV zl(FW*6_B4I8>uEG;j3P5vJP5wmp?2mlMvFCwr2E`+icu)#_NJCoolluDWKDALtGYS zMCxmO{e1zSkfXdS%S+bDuyq#6vb8oYuQg8o|8qC|VWEQ;(KGzvI|M~08DVQC!#Rx(yi>L$C4D7MOG>wkud^7NfbjdS at 7%FV zV}04nW63RCHxI{%ykvx5^n$qo&mG~;9XJHM{>^_r+ zlW>#7vygQnj&ARb=4RWHNVrXk9f0gl_~KK9X;cxB9$}NXlXvV+$RRXb*j0x-7NS5_ z0GUZ%7PoGdm_ICTv+ at K>V<&cDTZ-lR7enS&!VNyL&}HqJqG;j~Ju`8jLKoX59g%Id z?R-s_i8g5>?jMF5xrdO z1kQl3GGekgt1yE zaWpkEQB)jOSlFuA?h|5GyUZiI=dhb}mj&U;R9l0wd*`q#=jLOQislJ|NP1Y~#=651 z-YlBkm at jRJPmt2Um|r91dkH(@^h9%==3R=IkgVtw#c0vnHS0N!(^rkrijRdA;shS( zC7#3l)@KgeEt8))Ebq+_Ga=cW5pS7AbVMwtEkk}_>k-HA68_~KsN8Q}%|x#PE!ss# z1XF35>5Am~YCHq_w>BBXUT8VgIk5*rUE&@L>2jmuI63*eVV^GdFbY?}%dK~nI{dA= z+7z>dHO{=ax)L%n5>jH#UY2qlkcD8E at GNy zD&~!ud%JlF8e(RS%i5P at i5{`I9`IL}1r3-PVR6eN;=~|sY|`vII_p~9Eie151T(#g zl$yHgC at q}fO at 8G@J$0b~^GIo4(%2y4`r$0AkNC`ch_u+)uwRa-Z}r>bmpr;hOkiep z>?xRi-x{(zSkWZ5<;6W26Q6tKEW~`Q>=aiY#vAxwIkj0EODOTaH8=MCAmO1| znl#+#q&yV)!cMVinolnHH?m89ZEaZjsm0xU6?sZ~mK62x*|%5s44mI13E$epskHyV z9?~b0yv2P=i%RY&?3Donl)xFzj-9MxpLh8kMJ4@<`}X12w(ltZ(>>xV4;3g&AbDfZ z9YTWjsVT2__32ycDfD#f+pBL$PNUCK!Z~H)AQFK*VsB&zl=O0WYQ17h6w~31MP1Sn zGH1|XPo+cNUtqUQi5_PJK`ZRuUBhBZFD)tT)4xaGlHQ(fJq!EXQj`{bDP-hJ;VV&)okbsbPz+P9BKTwyuOq!Aaliq4`tQNDj^VQG=! z8{x-h02RsTR#H at 03QFkQl!QkUDcuH?^zU2Z8PKmgnsl4*90tgb6)7ke>zODPTSdIM4|J@! zJGX9QsL(wso*w9uu1l&5brxFvb_y$F_B*?8fFi-tySUUP zZ%L6=?T at 6H-HjGbP5eA(&VRp>zPDiA^moVK$5O1Lff><(*w?Y%v0_yRAz+Y3Nc+r%0r4^VMgXsd%5mffW=MUt3b`dpPqoORiXjb+s40b8cSy>*w{b zvpjlRknjJsQ+T*l?f-bq5zYxW+CXZL;$Bdo-i7^y82dHxi05s-&@f+x$74P?#Oie8 zY!j8TEAAV+Au at _eO8S;K>@V?>*Pbp{+=q5vZoaW;QW5_WwAA{5X)ZC}fExeb>*RGC z1-aVo4-c&JW7DhkmiwIHy0UP at AF0C<6`9ho7g;}9DKH&I(HKT&zV@^nBeQ{K<&9)n z%PYmQjh;@Pj-G3pc at kL7?@87Rp6teINyE}A!8-I<)wt~HuJv at x|Mj|DajiD-G*)r8 z%~9A3CaL>f9)UB5nVE$F>0p!q<(L2Ju(Oh@=wDQtkyt~2UnH-w3PjC9^`jpIbytZEfy34Shka3>E!^D_^BF?9Wu-@?TMl!y30L(9kNq;Y?G|7*=htYvjjO*l`rb>XTe=;G~lQ0q#+ucDiMAD)u%Qd*aSCq7cEl?TFVxSCbS zwa0=|2~*|sMEM;}JS}$g>Fep=t*}>-5w=NA$x#^a52rKDs0wx^?Nr z&@gs+`%XcgX?v7b`3e%Y{DSfzHttZJ at JI`Wd$H)?T=*&qhFAG&3L at Z+FD@Cpk%CqQ z{p2B{02?}({NVz)F$IxeL0R39f@?Z;{=*HO+EZc2UGiOmEE{&q-#CTMFQZ2Ext%V) zoR;YFn~fn3gO%d>fU|es0sV_SV!L;k(E5CYE@|%) zE|)xe^{pfw3~6lS?}uQ+!mPZa1c4i8vgpU|X12 at NrCTTn-$`neb)V#_4Z_OT_kc!( zd4+$7FO1DZT&{&3;=XwZdfhU?DZc3W!ljAXjof>*jJDtC7pv%nSEFB-;%;dZuW6gb zWhCD+HvP3iok%+!>gJDRCf>zBuwgx1<*$s`C)W0HFZ41if;dcyWl=uVI^?E`1q;2a z_Q`wg470W3*o97t{Y1=sj^M{M_JKFnAC3zXJt+5OSf6oXPHBpzmevo6pcygCK!s4K z*ydAxII78~xpM2r*;ih<(6V5lg88AD{&J^?7t5%_zLn7B(b6b`Tx&4h0f)46;*Q8cODp3o5-IvZ^_F|Uz!6JAv%{EVi{Z)6qAN19evi-fHB9MN84ZgHvkp50c( z#NH-miqh_G-deBR=)})JgpKrtO0hC|cVBoYY+%#G+PjCmLmGR?+NF&sLW-U3hd6%Seg?kcJ(M zb?u7-QEw_YvXbIkdX}oaN)55K$%MlJPPn1)kyDAv`m3$CoVyceH(oaOee7M4G_3IzjUDt~}!{*G8281?W_jGLJj zz!~tD`OI$)IaR at s{~w}!puhYLPd{mDwP0msR@`%BagG-~xzrQ#msbg?7FGBo=Hs+N z_&w5mNtENkh8?m#t;Wddo^EE(x?0~TT6iK>**naJ?F@;sJykw=*0SC(njaB8-Q~-a zU at +`6dEFlK_eC<1>JhtBS8R?}i;-Z3w?0;9rf0Z(W^KCaR*i872=hSVv2@)0BIik1 z+g2I#*Gl478D_(29?`|(n%cUE)e-aIo>fV+_(qbX;Pmu{p|pl9&SFwou|(@?V??j` zSyo)H)65 at qL`&w|!omWPcT at 3lG@Y7KZ&{4!^2GWMnZ}ltReRJ)N)nqlQfGG{amw;N z;o-vK%4(xaE at _&3HZsjF4=je?lzgt$)nv_S?(tR(6W1FR-7gkJij<#SXA at 69TQG68 zWQo&2Sy0^ei|0hv2)f(CwiGF at K(?owSNt%}8!|1dB`8^U92*4V^9}7DuVPI+0e{WV zHf!YBh at Iz> zm!*~iS>Y>(6F;D&n2Nd0j{OX~eRS?qqVKE)Ju~4mocR#Fe-X}k#ie(debpoIzHv&$ z&e5t*us~j971);tkN^x>fjr4nf~}y9+NM1(t1By$XL_L<0pag(9JMN-$r;gZ6xKz8 zVu%i}6)~#oaMTe$LU0TZhAIZa?Qq7Pgk at M?R%15;hWXoapbhis!^I5~tM>&0!Qrgh8w%o7P~#m|uVERp+PV&^TrbI}*=(xN?d~u}rYplt zUqJkFK2#VXt<;Vq^sqR?kdE#;c_0!$L-tl=hZ}j@;u?-6SBmwGZ7?;Q at NXp;wc-th zFipCAVQ)ll^3rQy2jve6OU*YHDQ1?hLZ-6JlzC}jIiSR!>8q}d)F%*AFf&;F-heDd zy0lGNM7E=P(Q;^$+D4j2^D}ut8>e}pD}G?ndVn0VxEc_)AXe1;Zc}g at SDrC-WMs5ziJVeg}#dR%)F~^=4t#HfH*a(UAj2M#B>u9ix3a_t2*ef z^qwGwJBXJ1`~fvGLXFg?Ni%4E9T!H>`WH#8A8V(_zS#GW6vxn-VEIsN(V6xSK~1`_ z!1C9d^=29#a&3J-{|H|oObUoWQizkd8BXs-BWl&Z{ZswhzsV3BQL*cl`y5dCF!~(1 zBuRo&ZLaud$A7o&Qt;1f{@tEbyX}7#r=m-T4i0;hbA_=(mjy>i>n^^VlopAH{$XEp zaaCi9&@1bd^_vr-%QLJi?t3Cfb;8(RCikq`fcYl7zb_K@$Saq^nhN_9(6HVXO99R% zVPVRL`NMVIKtZ|vlkqC=FlY_dRGeEOG)G<`mD_g+1y{tP712|@^f+P}_UERy?XzII zYofoZG~ZTg?c29^V}#LPMdsU|4(*})?40)P5-ykvtcz6i6;6(Mn`(AhMBD#{82Z}?DhzA^uyqxUGPG^)9OlPPEEj<54nzygI4|>xD-2(@Km`>+pDhkJ zIqg%!5qV*6UBcx=V+As!pbhk{P5Z0p%lTc- zl{wevbk6CT(?eGreH^zt206lc^NC{t(UqJ{BZ4%*q z0&Umx?$;d;=#GcCOA!Ern zGM-Ez6Uii!HieL>WEvTnKb<7!rCvs6ki3~>6#e(6_#C`J;vXqQmCQ6ec=dC%*$;jdr zg1yMqD~C95TS-7!C7UyDC#z+B{nki!cga8$y<@G}mlAR(Nio(FGV;z1WFzrx5+(jk zM&2umlx-HN|D==DEo5ZneMmUc$;e5hYRFcUHsce?J^pQkq#rRjQ904Ls&m3P;hZ?Q z=H$f1X$n_UxthkcbgpJ_J&WtvoaJyemydNbGm%+*4k+Kn6CxlzQ^ZsD{iSBrW2t(@M*Gy3w3emt{;XZGirr95*0&%A?Y z4&<44^31z<=3jW`UwP)=jJCu`QH|fLJnL?r<>6U_d6t)FmGP`{o>jrKd_1d?XAR+5 zRXoejv+m(pLwQz!XI1m88lDy8S+$)0-3VzsJHoT;c=j-!J)CEc;Mw&&`yV{}pFI0t zJp11~`#(H;B+qUjJbM&iG++#1EMOd9JYWJ~B483=GGGc|DqtF5I$#FC!?R~1m<5;( zm;;y#sO9QBV*Fh-vMA44NORw5Y6$zOQx zDrB!l%E!qX1T{Q&ErPo_K|gZWAzIFJ)}xvYNNyxN?_PwP0b7uDA7CrM%L#f+wgLQ{ zl=9r|V7MRf0O9(B2p>YuKu-R{Nk5+RFcKJW?jy+Df$&klV`$-VggXJd0K36c#&h=| z=+DVtIqA!DpFpCP=j;XRK12ue+$X`gAJIE_?g3B_B3i|B4}tm=l20Rf7tcM6>m7vef>h6Q-b3_#kUl{8AxI at W_aj6< z27H3#r+^VW_cH{Tb at DmLUjV)Ye1%e91HJ+MTf&=t5BLG_H}3qA at D_LT7UvQC0=NKZ zPa?REj6N9}ea+ylU3X!iudy()L_L$UoRb5P|So@X0?q`-u`t%{{{Y2%z{|xXs;0wT)0Iay*e}(XC0Hk-_H{knD<$w4d at Po>){}JI& zfS)1B^Qy51X8MK;D!-vYdp9YwY z_zZ-Yu^TW;oo6CF3+dSi=OCPma2{Yj;tLQi1YqVmFVZ+cKW|u!^b){Qyg|57AQ&QjR-d(eJ{ez2)BR at qUd}d;#&dRkcPNA zZ$}7Gb-o{b4*(uS-a~+g5q|{X4#1;;#{iFmwiB=m@!co~@pj&W_!FS*MYs>}q{h1( z02~Ay0z9R0`m|>J36ckyfJ8td$aX%X8CzAxZ-foG at rcH6d=BtD;3(i2;5gs};3NR1 z_{JAd)hUht=?vgSz*)dc8o%ikz^j1QG~))&bIxh}&u?h_<~Pv{l!YLhK-mZsDd#Q3 zp+tXv8zG92*8oC+-qCpBdm8V0H}CoZ0zdEiA%c$p9|JxCd&QGn5aF at Ujvae(oF34n=!Nr1_K zDb)B%W4!w`%DYdeyl4jHj+vC-G8- at lFc-9Wl#>O3g at 8qX#gsdj0G3jt4db^ir~K9x zAfr13-6T^GM;8gYed|iZ(KUju+`0;JG)d3}g09 at U8X>w%Ff4*$-MR*842NJi1j8d3 z4ng+`hC(nTf?*I0gP at xPLm(IeLAP_(0oGG~+Xkdh3avr734rM)nBrddB7ZYr3*bJ= zdu;`5LmYW at JLP>I0Ix784gs%&W4S?MuuvY}Oiom83*sVKH zQl4^3)CqV|l%f2uFHx?(jPMntUq$$ur~~0Sz#D)!0dE1`7UCuRukRrKF5o at D`+yHX z{}ACv2tNjVg7~KhKL>nC`Q2Y3{2Jjml;8O+HCm{A at DG#^{*iL;&w%p)=(qP5gckq} zI;W#_UOpC{>o}cPU^V$B=-dZ$?}I7#O+OA}~ z;1R$Mz at vc2FzUzAo1Hqpb2o}Uq4QxFdFoyr?vl=P_Jei+ at q>sT0z3tH8i4hYgLRaH zbur=@q_Mt6Jgf5&M-Ufl;yILi9&i+C^fu=h;>Q6ebbi%Io!5h}9^pS;0PPguG~h+R zOMq7ZuK~^h-UPf2co*QJeTpDc>vhWTj#^BEC4J7falgl2p2Qn z3*+py1m%|jRsdEpJ`nl;T7&pnz&gNszy`oZz$VmpFXR8-jQAFW_aWR0*ap}RxF7HU z=no=%2=EAC2hxu*{-4JY-wD_S*bR6Buotioupe-Mae9#Pkxwz+^l79I1D*jq%XkAh zBG7X(rU5-KVn+o!#<+SMTqgi05q|;UDZpvK8P*I3x^sxVA!LL_?ZI>)`VQb-z$(r=t$@b>I{~{mGQ>G%HxhdQPXP7;o&@X%8~_~ThR-nmh9(Xn^%OU*rp=suU_BBa+kEHD-X{Slr>m=m)JmCAzUmbvH{fcA(?~s at g2octll=$5m>; zH8u9DDtAqDaL05Mo&lH%nB_2fs?E}j50Odcf-uivv`Xh}0ip{5ivWuOO8`p&%K*zA zyy;4Ws{pG3YjJ8{hj2Y$gM+IZ5pDw93)l?U0=N&b74R=k{>_^X;WG)$+Dx*|0kmBp zXxmJ3KR6z6 at R1J!9uhs!jq|E*e4}P?_9)Vi0UihJ1nhD&%QT+WRO1R^A6(}$u^b}#P>Rk^Nwtz!N^95 zY-2I$mQt9-}H;flqp0R`H84v1t z#?zD=`)E_+5N*mwF2b@}W*8 at FKGYwhT?tqPSPg*dy^gF!WF26=SlW>K3cwqH*Th_d zw$U(7(Pl=MX2y*wAK73uYmRwFr4Ja!w>00lSvCGl`AD3I$b&|+7RD%DHI_hDv-Fn6 z9K9t^$F!%z#-zi#r1#_L4;wuF7;AP37h`bkKm(5g9y6L1VB(Rt3$WWTrs)o2hJLAW z>!rpL1RDX})3I%(8%5~`JQw3OsJ)Ix_ZY@*J>S@?JMQK3A6F*|mH(c6{GST4rXkcc zJ4(!$L=;FB*}6i8cOR9QE;i^ zpe~ow>BgUI!a-cFgsx`BcdXHMtu(rbqxu^y6JJ>QZBvjT2pNsCNK=&>zuxsYW>a4r zmLez1ohX;tw)WQz?tf)#-XiP6)ENO%~ZH2~LQQLSW%rzC7tUB?h?zC(CF%yYwzOalPvdiQ8pu94`_Vi;syVI-{jSC5Uo5KNw+pCJ8V>KtgSb?!Nhij z%!CZNMc5S8p|+M-HG8U>zDZ`H?zB%tw%JeGFn$+5V;$N?aa)0vq&ZbOq6qB()In&I zDs at UHf?rH%LJgv_CE6<>p#%{UwwG3>m{qy5n61wVN=>Q5rFF7e_AMTK|`}RqYaU&5BJmwxKpA_ls>*V%M(|)m|TuOq+0>uzpc5LizvS zlZYR3e2<$(2k1;T!to|uuD6&%$E!7oO|nLWpY4=ulO}Cj$s5BT;x{w&O~v00XviT9 zsj8X(NrC6ZSHj{dk>cqb=V_a2m|zuRW;!}0zcy>w+7fpsYbQWpZbbi;)Xc5R+ at gMS z1N;AC>uM)rmfFPYDHXav8}pAY-U;GQEdR~1P)2mUOJ>ZP3{^#cd%1P5l#5$W53;1E~zx&8ouwOcbM#DDJK>m)(ex z(jAA7BBG at AAWG^jL~->b3NI!~da9ypx8Bz4%ADS+lGjI7^82dFrM)$!X+NS|al5Lt zC{dM5`V*yXsj9RbpemQ$L6kNFiE{a!METuas*?Q|P09JIs^tDnQ<@JVO6$L3eVMBKzFbxQTtSq9K2_;ZsVPN6RK-)JD*gPbQtH!`j`wKF zABSqnp8~2fxLQ>P)o4oZpsMt&CCVLtSCt-BnsR$cRjvsW<<5wz-0Ihq0d+(v8>T7U zsx{^AN>%AToG5<_YD)1vs?uess at zzkD!oRi%1!mEa`WFc<(7YF%C-L_%3Z@$rR%>m zrT at Q);{A`R{AHx7T-`v3^4Ex_+*Sr_{ts2 at Gz#|Q52JxP|4UU$#vsyn6j7DyM+4n3 z2B=TDrWB4vr2S>%h|z at JHlFmFK&r&kGNP;!kJOZB$zR!(_4u^x0%F3O-7f z03RkxLI3A6a+z|3EC=CfvI2xDWF=~uMOJ~bkE})oYlyNIiKoaqvFMcbU>d#wXd>AN z^5bL^YIu&^i}*IO8Tb*h1x+m?_YraO-f9kK8-ndbxnFkZ0p!mk52B8d523)nA4Y5% zc?8i1&?!y%*AB2fDn_U%X=p)|*$H~>E<~{-?*`{evPTZ_36z~g_KL2#-Rr?LVjpUs zPo4yxOZJ0vIXNJTxm*X)(V>Sx*+8BmnaXJLG|EmTht0;G5yEu4pGCRFQhLgXX}yU7dSJB6;CMq~G!F&lmnIh)8?bZ!)R z2?dry#;P)tybSt-K7&08QWAa8 at Pk-P)^ zBzYHj4SCP(+D`BUA4Vh7$@|g*d?4|MApiX%pb6w-WR4}DAU=+KY8LtoHQY-+2i{A* zK*o6TB at u_JuMnR>z6N~-`3Cq9`PMA(ohhyFk^i3`h){iy-W#=QH9Kuv8qszajK^LV?3fykO``s?r}Jc z)lXD~7EA(t9ht0(OVTOoWkx>fb&RO{A(-RjA>b1vP0`7%X9=B(YEF}>D0+%a13p8h zqwI at hhDwa)$`1q;=wv+Epk6f-oRigA;JH9%A>N?Q2A;0Y0cD&z7kCLM&`@Rhib0$!yq24$wY1bDu>6qGgUGQ`KK%YnzBl%_0JS0FwQb!y5|btU5G z$xPr$>MHQBRab+)Qe6W)QC$l>TV03T at t~{93Uxj34D>-$7OES7r>Yx;G}KMNt5FIw zj8dvHOT8DlbJfkjqrr(a4T`GF0Yz0tsarr_j at C8h7qSJEW$Jc9jF#lKdsX*5p?~U= z!rrLQiuI_T5IU-E0lr^-OV}j!JZ5pTdLQQCGOSlD0`(ov8bS9Zae*!@!5sM@)6tVb1=epgpNR z2E0ps9C)v~6L`0}3wWQp8%%q!!#{y=uL=TIi~1z+L3Ka+&{{d7LS2b*Il1kqnkt5O zN)G0<9Lx)HkY`jej1%es)X_!}gE=dQ{h}P|G4-INACdzWgM3L2@}v~garG(T$DC8O zryibtXm!WrCLWsw%wB*MM`0_PQCIGe!P}%z9Je zw+IL7T)r263 zvOxO*c#ifXXp2Gn3E>iiKa2ICod=$!{Q|s5yMUZ!$Z61s(Mj2)DfxNEwaONajM6Su z?$h>2q3_XBM{AmLzcxlwmB+NPz&o{ZpggFJ2YytW0Lnw!MBqoX(ZJimsVR?xM^zpG zzozU0kET2f9!=SyO+t&iwaJ>(xL$cuV^fgauT9k$4iwrnOLdx at 7O-S$)P0Yd9+A7fLRE?|#*E`x8lsKcU71E*WgcP;)z&~mmG`V$c6fGIr zCUBkA?ln8K84O=)TQsr#-ls{8*$UDJ+BV?#we4aKwEM+;YY(8 at G3`Ob-_stFBY4;> z at CZoXX*)z$2zgZ0>(m}IvmQs*d2J_p_p-JNrhU2VT$)wBbs>Idzhs9rgQVlP6hvW=&aIHw&p6VIUdQSDiwnA#EG z&$Z{|*q#UdS?wq&zi7u0e@;7&_%|4orX1H!h+19RNszwMUN9xI6V<+?odW5Cb{hC2 zbWK%GYG>qBJcIc2+KW;Q&m;bfb{0GzqHa|=qP=8}ZWk&)t-UOCTzduaSG8Bgojhh( zQ%-=QDz9j-n_ at dBd-(=PuWN6b^R!oMr5x7Yk|mZR<4vt2%+3T#-$sSw=sUn;=)1sU z>3hKA>HAm`eVh9O*Um3)H)Seu?-> z`W5gh`ZXvE={M-%68bGDE9iHiECtq-WuU9dV(@6nBKkdYm(w4BSJNMX*V3O*yRwDS zpOM;3&jW9wzX0D$F92_(?U4Nd)fy-k932IGKOGIcjb1~Ho5>iugQkq7!tjg}r#_4b zUIm>1yoXK%-(z$V;=AZ%;3w!5$vGAAy>uGzJ~|ydkJA~zkJ6c-?54BCN6AA}olQ06 z0G*ATgS0tFFVT!SNIXa9g6#yI2YiOk2h%fj4)F7I0q}9U5IoP)MaX at TE=K$)cvR&u zaP&36~JfdO3{JuX|q*GeNI;ce?r#)zf0GG_Z(e^ zn%W3=P2SE9jJ_!67eF*p?`Y`YZ^bz29AaAU3`Y7;Qv;`F7F?yb=kE7sE zbSH>E(_M5oHF_uwy0!=D(fSj>WAweiqx5~07(JD7I(?Fg-DAJR2Y|1GkQGQ>c?ulG zTd$C)ZYT0k9y$8>2D)GLw^T&gZ?h?Lj679Ir{s^ovF8kWNuS7>uDcQ z;U<5GtOxavWTPJ=zD55;3glCXKLcf({yFe{`WL_t>t6zI*S`XONdKBjkNO)bu5`bp z(s}p}Ia~GbX=kIi@~E!=7XPF^O2;u#A1x_kbTNjppgg6Ila%p at Kci2O@rj5Z z(kB5Q&?oEWiB at Jz1?jLpO(#ZwB2XTh3#OuRMUFgFs08v1$za7FVRgUV~Y?L^z z&jCK6&jmiF&l7ofkoo#4y~P4uRZi;*bWJ&wR5J zJ5gTMmr90Zh`+5b2Yypu0enth3H*gV6}<21LXF_d>8pW1)7R*Q%14N;1$?frL+Vpq z7{@~8Lxk(4EH)tamc9}A9eoq<8~VM#FY23t-_^IE%*TjLLYYr=F{?y*P2UPZ+@*Z0 zW2%Yq7v%@NU^@mhp52cDO=1rKU(g>!?GNct0J%Xtf^w7D4l{8-m?tt!w(_(7m?W{s zP41mwo5FSpn#y*Yv_0VaQGY_vG`82I?K3NS(hME|$1nPRbHW})>*w_Yz~k6Kllu at 7 zW7q+3jb%?EK8ii9%f0z2kQ&$_;L&UkGJevx15aRAyN$mo(-}Ez*582`gOpi}J_E)% z>{(st;St1Vv*%>|c}T&dEM`h4n3kjZN~Rq{?lN`^l`Ut-F{TylTA0Us89xEiT6Pko zP3#5WjqC(i*RWH->)C1G4eSi?I`$$eEmIz1!oKLrql}yd`wsS!evMI~>}2F+{ZeHY zyG}TP&ocKbD06_l3cR1aCbXNqF0`JV17#n31NaH{rqFcu7UEBWqA7dX+lU`x?*JcU z?+O)X&>*8yInJPV(AFW!Nu~+iJjFhg$|scR1@=c|`IR%QrO=GG*e59e8hahi at XJi- z*PHA!P|mW?O;!FBR^%o21u{NlUxM_Nu6!-DjeUazbm%)I-e=$I(i;3AG>|+g?C7=&iHgb7Eou#x>e0(WmYNlIT#uWWB=zRtqvVt)!Geuj; z6pFcB#WJ+Y)r=8(SQj at _FA(Kra_NgSBj+=6mS&`XK-RD<{WY?dWwO`FI+l_4K6#dA zq`XUx(2U&A$$C`N0_!8w at dkLZ_&IWvX5 at cGUZELzUyxU6M)oJE{-ke{%q)hOEz zRo%iA!j${O*-3dkmUCFu&(lo$thyClpl-KK-v&nPmFVL$>hm%7S5$GReOFaJR5hD+ zKScSF`Tz=CiF2v)i%K6ve4Z}s1&*=tY!}qgTC}e5u_z96>oomg+2$kQTOX}_ot{BA zXeg_1#Hp#p4vd$c(aEEXHKi~@k1>`)U)IUvjAheTb+VH&N?~nwG3KOi=wvrzI(?iGS-4($sJ=XpH5)pIAf`F5+f%V%cI|^WE(6ZoM(-m#0(saO5-G2J*2%LCUF`g zqbasR;}px_)9Gm`J0auA^bE^z%%Cq~q*Lfw%t6QK9J~ZMPNgri3^ol;wX%UKTdDF8 z)n5VcBXotHp*>7(-d9nw0Jc;p4BU#RDcm{*i~9nF%K}fu at j8}(+2jQM98;JumZB4H zGKIH*DaLLPZTM|eh7*Y%9mzYU;=U{7v6PV=F;VZ*>3d=pmoxId$X&^zSs#emU(Lwg z821q!JDze_^R zY1+3eQ{TqE$GDLA0kgT4{RkD!P1k;6iUHcsLQx)OV at t02WY{4(GHwv znf!isfhjI=4ZxJgnX-o|&#~xWKfpvj&l-4!evEyD1$301UIWH42<=LxxtD)b~FQ&dXy zmk3Ft3j2|e8BkTeQ6)K4aXg}u?Sv}EV=CDSvh!(`tU=oKoJ#g1?S5G$k0PD&hDx47 zI`tiuJcBR|TjrHiN&i$O&mo!dvr0}N%sj7>NgUf at j$@QYUIHa+oJL+om_1b^ovG4f ztwye*O3u9+Y0#*WyHz9kRLOfnBYVKq6vysaAUB%~ls}P at j#O!m^Y8mew!k^`Q-m#X z%KQo8B{(;>qe=n%j%%oLDUNfqC{-?dnUdyIX at w)nN+et3e9?j`mv7RwT}b}!DWEI% z0kwg1+mb46;k6PE@&WGb7hh<$CDni=h7DF-8udksEe1@{&$T=Oavf z)$Z~O($DQ!XE26S;Q#0WfLb$knZ_0m2WS}EpRkcLR9Cmj)Qs?+^JNxy+t?x zR#v8+bz8xmq{_HOyi6yQimav5HMS27f-)HK8ShhrTFuVnMD z%z%s7cJMMbKa(iREr>K+cKSr+1U1tMH?!hIA!(a#D9VY-pULsQley@`5XzisJIJw) z1Ft;Y(MK*FU9I^@K|^Xa6+uC at E`nCo%tww_6CW8gh5JZ=^nf_WRjaL)GiY{nIZYp; zD29)e(UUD at 1>n&Jr%o`dL4sEHsT{@3>ISs}0bxMxF_n`Zt%zBDK=82#GaZlu3JlgJ z^i0oClw%!Fbo`5QWx0N3$E&JI3y0o`Y8kUHTyRi_R?w69=aEHZ8lTSD3@(12fK|_0 z!SfSa$!xCBRCs=hUH}gd3tg$wRHtjU3iN*OgMsfpCN6k&JZLsMCt%i2gu+v zB-4f3WT|69)_3Dz&*Ka8?>AB-A$B1%Xr^qTtW52%rb+Q^VVeZ{LCyx88nTzdy|2%vfW#3<*m~= z(FU*JHs1U45WNqP~@bSMyYt~!awYw8jPeCC=oD#PaqB5Us$L+2 at qg3Gb$sxTvN|ODuICDBy@#MyhFTYpo{BP5 zh4`;L*6|oUNE%X39nTcsQF>x5^DT+qM}}p>@*%AB^ejM$fL;D^QNCJ53hQj}Su%Jh zSp`aspok at uGk6{8kt3Lbc98}Zsanw_irn3x9-CJCe~F z!}^X4dXyX+)MoG`-tJ*?Ya7*!t`yN*A0=AVcLc1cH)OZ+o=AmN&FRWG*6}!!tHP)6 zevcfldQYsbh|RW6u+`a}S%>^}B7YcGpKBFXUqJ&oRWR-}f-{5nl7j4s=*(U+K&jeG zY>k;AHHMsOt(4Q_qs#U5Y^=F5y^OXTcN%3*96y14?^51Lry1{Hf#?pJB-(4u;r+S5A at +aR`4b`bi9+oEwDb3f^8XU~ ze}cd=A at xB|!WunE2z1`NfeXBmA20B3;&<;s?R$tY1B3PvlYt3m7{)nS?XgN#>e8_!2e0Inr%4@{MqbRcG>&PsDwrCG-PH at CtD|7# zov!9$axciqy?{<#Aa}1MgIAIhE2~zDu8fvtxKQo$qR^OV9iNKiSYpfLQxyJGlo&^Z z>ZU_=t?B(th2+6IUh=k$Wqc_zMxQ!9_+FkZIyytG+{}6+INsw;Dr%pQT-8 at rRTGml zzP2(Zk&73>?L7w{HW$I+xJ7U at wg}co7r|iyR#Y>Y2&2&q)oddfsVKRP4BAeP-MSq{ zVH*JvJX543vcdDEw2y<`XgAM0cNcsSaR9R#eXW?EF* zTSanuY{s_A8QTh>ZzXrnA%o|L8Ji>IKSP#DlT8adbgU;DN8y>#x{iwEtXN$~WnD*6 z*HLo!!({NoqOONUU9$!t* zmSa?5EC($jgPT=u=7X1z-+`=|=`DQl5i@=4_=%Hcv`p`_i0Ei?4txlwtIBRNyFW$% zjy~s5@;)wUJ?`Vt)jviZme~&SMO)ZC(4zu$WYD>2lYP$-D=AwP$#h4oN;EiEHh39y z=`6YObmdvH_soft-m}D^In+TvkmIn)b9($h#NwYvsP1%KZnB;ctgtm at R4`-HmdraL zJBxchQBiTrSjGF|Dn3Mx_c%nvL at Xd$V*CGxwKoBbs=EHi at 7wNslLSaY07cO#AuK)w zt!uRvwT_t9j at n|WcBXG8I-Odz{fbcAU%%fDfdC?is353l!Y2E^2(s_HnHiEGi|oiE zh$0ArIjLZ`9>IwBJ_b_ zB6Oq}S3ZO$9+)CR-WDN^M94wjPp|d zIE(Wd2!S3#n&#dLNm>HhHBhc7FBROOv*!22Tp69NR~0m;b|Hp))Fe^sIq1B_ zU2`923<+`{BqmZW53036o}G^b!3}am^>2`+=thW*heVGr?ix{v$9%z~DtM%fMAQl; z1=`JK*44EIU++l at 3jZPsUtX%Buo}SAWWJzzsk|mxSkTE9qcik!WNm)Q{WEo`M=2&3 z^OW<>(SFziLej*di5gwxX@@RI7TWN0|jxkWZ_ at SXo6e)WHCI&=UQ%^v`6dzN=70vO>AyWopy9S_xp_f zT~l1^hI~UI5cEx(piwfzdU+ at lBtmK3AEj4QQ0~Zo8&QQOY7JE=l=@|e!zs-0*pYeWy-tW_>IiOB7CXIK6 z9?>Jb*~GfK%T?T4s5hsxZiTFacP0d>=xn{@PKlZngqma!+EG_$#TD~AZkOYtTaLgy zE?e7W|4h!}HnVY+4+C-nu62;J|!m~_Lx-sWP z47~T%{d06F{FR%apKd3}&PO{&vYq%iw>vdQ^W>mwKqgvWO4f17Iwn~kNYsI|g#m)z zo2ipzL at ZY_HJZXg)KE at fft-V3Um>JkXA)4&s;%jI9_jC-I{B;%l7Ftw5S)0b3ML at F zMJu$}Uss2Om68IXQ!7)-JPdEChGyfRAeZSn1P+Dewx_P4T1iNgX6Q#rk|s$=lUf;r zxkpVd6;pMPBWhlvt}ZlCO2!64)Hk0^0x#G+0rk8pJVmyvd4QfC^n}xaO4#1UidJCa z%yH9LZ^F=(6C&?Ck#~Z!wgyVbI$VpGTSSC4PZt^8lJMUm6^G~PBI|4~>%c at A+?-Hm z=Ql#w2_ at k_p9!IqFE^La^2DX>7lq;3KuWkMb!kn>&Sg)TCp#)|o>InDW*B`c(o zA$pvMZiyLk0QBhrl2n!WB?+J1_}UHa8lrT(&}Od6L`nW~7N-6ToDV!QhY)y3kb&AA zJddyQHp&Ev?48zP;uJGY4RDhV?tX!rBwU)(CT2{#_C!4CvWa+dM#x4HGDR?&MIlLN z0-lCDS{)M`f2dRVCp1EioYqqcgW at KWGmprWTl8tp% zV9k^yu%J|RM&7doJNG~-#89Mhg_$H_Pa;XoAJ%Y*Itm_%aG8upGF&4C7wEw&U}S%! zmt5ht;A}~OqWC{F(oR5&Xj@~AkfQfE`omo`R7Xe{Sr1C+d;Ng|ErP^NL`zUV4fd9U z3T944^7oeQdDB45iUd-Ptef7BS~YyW{77p&DY{q}{=nS)w4O7RCQYlbf4neW zSd&cnm*^5nQoIeKJDh4$7?cY=>#UZ>`$wWgEo_OHIfbA*O-B48Nil4PUtxTyUdo}q z>WSzwXsgyz-M<`jBDY)ePo#e=)l;Ia{7aGFQXPFhx=BkGJj|FmT&IL9$eG547PC_C zMa-Ea5s`Zh^|BSZ2zZME8d1PXotniQfq)2)ZqOonfvs9o8=E7G103I}uo?lMk{(V7)HW)7DCC6(q1hR}+DaVs2X^`cF)_pR1~( z&`Z8k!)V6(F2+5iM`JJ*aI#WzQM4jhFTqWE(yEjKn{+uzL#8&jF*U;`J!S0`p=5-N z)|(xzs|$WD1-_Oj!=++=yb>8^=;>s=R!M;>fw4^JGfO1l=7~42$<*NY2o0Xpa`GgN zmI|TphlWeR9dgMInZ<^aC^svm at MgV1+qDYOMNG_?DJhVW$Pb&BE9$Lnc$pH~qT?;L z<$Dqbl_W%Ekwoc}Vs_smX7`VEF1eE@ zFI8p7I1ytnjTq-djK4JE11Dm_r4bXIh)I`5Om-rs)QhOnqY2%dt`|IjIsw)U2W#e~ z$!F;Pahg@JnGuekJk(zGv5u=|A=M^1LfVciV9KCp;R~L5tWEeI<%oX6WnHW&Vy3mpu~a~p^9CE_T%-$ z6%x63{|*I(n21Z&|E^SoAV}EqPzb|>yIN42Fz4GPfobnrJeYULH59lYLpg|kC6gm9 zqNQMsfQ6O97xM)5q1K)W`Jtd8)sTruGAC&zDWq3^JI6+J0y6A!GUykI;;W+gYPyuG z#E9(ZEgg)}8 at 2Kl3o=&*_Sh=}DIqvkOB{@SBSdra8dx5+p+!=0Y?0)eE;O|6N*f`y zDchiFjV4Nw{@WB*_*5y_JVkTh6gf6k%FUtHbda~9U4#p5t8Tq#yU^@K6KCnB7ZIQZ z9 at 5aPu~OS2Mo3!dO(`@`4mJ<2l>93t2I44sY#(%e`mZGc zvzS|_r9ibVUy{v3$Y!N53?Uh(ap!#o4^QXc=c@=>Nxvo46S+O*Tr#}u2w at i5HXtG( z&8SP^kqmqUWom$A3mVezq1Zbg(C*1{MDeP?JQ`q)(6G7*tFIy+z+96WX8^xEj5{M>7W^au<=!O at Xy~LAL1Wl at vVA4c_MA^4!yt^B6q(qmA9K;R>jk+^j7#D+H*SZBbGWe6wRGcl z>IIFxI432!!NE%CD=C;$lEY$ONeGGX at CQ0Q_3#9GCdrKWN=m_uAdBOQ?{{k at gobZV zMwmnr&fKw1QnRwMt7PFwEQ~9)ZPR``a<~AD!nm*07dnFpEp|q-KF715mBd`Vw{f2P zj)_`HM0e?a+HX*cZXf+rPx*K2QZFb at htN5M==|7Gc1dog3YJoJmGTrc$H`8|N?A>8 zBYTS!Y?AUfkpf$wNiLOAmeT?EqaGX6N2RU;1Ql|wxF&qVnp=mX&0J7WKt0$;D=%h at t$nf z0p<&JYy}dfndsQ-T_xJBIfQL!AZ(*hjBbUId4~MeXKa7 at 8R4(C&d_qeXzN|xm^>QJ z+`lnn?%yO#)Mgkxhb3#0-jXUs9VK_o&Kq1Y{mm3%iK=Y6^*b+#6( zoh_xabxUU}ud|ia*(#R^x~r}1|6^+ at zbEb#seueyp40RI^Gqp{XSG$9wRp+PK_ci(T z_}5))<+l1Oq#bOP+`TBV?f0VV~DaJ6EJV zUJq}gz&lY7Z;rtGq8{E9fp at YV-e`gMWj(wJ0`IH(Wt%O^cB&p?r9k|;Uf5I-cDi0B z(?up{>fucmc;84GSJYf56I#Rcf22|yoUNbxG?Du`)U2v`(&t<1mr5S`ZS z at 0AUOe3cCmIG!djqoMc{>G+uq#eb2GpVd(O$#nc|!Qhqk8T`^_u)RS&e@;UbzDiS= z+fe+ebo at NoZM(0Xw)3@$&7c;WFItBt%qjeF7M?gH^F+*a%>+5f;YJs-?LRAFbn%nbbaFFX at QaY`hAu`e5Dc zYq;B|$c<4PJ)>K5#ByN7=h#dUpJPIHp%tD&@@$B8w`>v7!_GAKDzUB=@kJU(zwuVJ zvYl0}#*xO6M$L9X4asqd<~T%2z0Ap=a}Cist44BcLT6RUE=hJIH-psny1LL(Ik-%Y zWS1HbX;S)}K^Hu$I~~Y<37&I9+?U|DZpgR)R!HE8tO=WEzFZuePhDrR ztiUX}4XTXU&#-2SvL(#^>DK|?t8LNE6(6X at 8I*j9ta4x0bLjA*U&OPk6Vda~N)yq3 zyhkm{A~qG-v=&CxQdE at xWf=Oq<%%9Psr2`!_^x$43e|OW=6X44u9H*U zXc#ROOt;2Ja1_M_uA*qol4;>0xI!)rtPpEJ%Vb&eWx?DhF6va)qXsWY$|8PeZSY3x z#^`6f-4?Ge3~U z5ig4r3MR4;FaKT^Xf5&vM%3s9b+X-TD at 2yjAIS2smt_$X8RGGS*+tag1w9e{PWNBX zrSP})O1M(ik=+hSw6pL!1gFdX>2ys>O)`2>?@^nK){)PM8l=c7iuTdlwoOIUk|IsC z%6u8aIYcH;M4jOX7nKxeauteKXNOQq1m0rr&V-USy8|s`whxxWJkb5+k`x`S7s=Bv z3mt>d8Toh6nrC__ at 3Dt+N@woMQ$Kg*$?Z6a3=3nGOcuJW at vjy=tUncau}lVK=J1ch zUU^^7*f;7*L&|W5oKFkIP)F_YM9BE)pZPH$F` zH6^K%70_;utk%fZYB}1+pqEwjLi>^uvZkrpJtEqhZ9m6s>!H)UE!(lHF{LA9XsJrB z!EhhLPz|UZw0N78HLhy++jKG&6EC-9h0p-(cu*=;1u0GJKOJ8RF`G at Mnl3-wpk+J0Y+B-2gWL(tns7d>jN>*IgfKx?3b ze9}BU^H2$%1SwH7xP7UM7xGCgY6=!znuhp(kE`_THfBeI)fWpG7susLTtWomoNtz z{+~z=9?i?`O4>2Jr}D at N;vCu#IbnheLtrBB6rR)%j*G!hwsw{5qQ(tDP=)!iBt?IRFiEzB{KbYUtWJi->wAz+e^ht2=I}fyo}oEB zc!RysN?7tYNZ~o0c4$FuqN5l!;v@!P4bw`7VOpKTTS4y3;a#Oh*B8 at S47$4~Q%v?& zT>f8n_;1=Oww)L(QyS3a`@k&BYGT?O<%&By4E&zip}?6Pb`Veflm;rQzf#Y1JW7ZI6o?RQ0=iS^GdU6a zOb+drld=7BXrG*l?UO^hpdQC|$)UZ_@?v}C;3=)-l*Zzxv{D{?#VC2jVDVRsQayB7 z4&GrMl}qlBVuztYM;Gftr?3v%7KaEBv+=Qv05J<6$_N-_L=!7nC<;)Uz%e%4gTv?D` zH&1J^wLG+to4d5wLLOSe&4XHO2 at kE}=1DEKiiehQ^N1E(#zRSPD94gK^dUDt(PAI+ zP!%`(^H>!Rt>oqjEw++}HgU5~i*4eekGWaSV;@sfO`!>riydRh)I(H9Qx#RxIMrTD z3;pK}!+O&wX-U;Qjp~)!JUC3$>lOId{R#+)mPj_;>n=+bSMI)!neEEh5*laA&_ssM zmb=T*_P(rZfsk90%i_7!biienmN17K;bnTFC at XwiBc*1A!1E-`VB&72!c||^Qu4hQ zPVSJG-atFqKHoQ3iPA}`8fT`dOCf9n%6e4^zhk5l;qRc? z?9r{)6k4o**BG6oHO4_okvzJRR!72RvWOqz#;2lXvVWA}MijARbU7ucOhs!XJIMq+ zMN|lrR_|yjYp_y9j)JNb{UvKd$oXcOJ#i~H2lMb&YL?Zax=6|1O4IZj)P&)q(=uCm z!p2xD#QKjAv{bS7DzT+XXoO2vaQHgC+LcVrP8&Crksr;6Dq<=Bw0yGHhFBUCN?&E7^{J?nlQF_7%K?l zV-I5mFjf#om4`8(FcKcdd|=EcjHE(l($gfd5d^TOl6eYE3kiD~+2MSUGr(HqeH`J| zc<m44sGU1MR!v&irVgF zSxH&$@UpB#mX(y{P7h- at VeAqZSJE!BX0yc^&fUP=tpukl0St5``1Gk09ohqxYy>oX7!DRktx6=L=)!S at XRdj^Z)ZMI at TE-Y69v&DAI&oEip z<8Pk=DUh(XDb8s3zi&vfeM)Eog(oQ-V!;icK(&2XQIYp<2(fn6)>IVvK zw?l+TyIDMhlK+X42z+9jDT)tbtK1;-qFoK3))JfPtSY6G+Vo0{SG-wU6mL(RUBer6 z)!LvGHl>~56$)Mh6^azvtOT=4vRG`h5}M+?;<-XX*5XJB-lmARM92J2Hp$>dh3fwU z-M>+hf}6z01lg%GIP`bD26^wj1S$=lPFy5a)Jsp_9*H8k(x&3PAiYFWN9MaA zN81A%;Osv~9Q7c!D#AD$ZIq0LEP{`B>-8|GH*u#Ae$NS#smVVQ*=<`#aK zTlnvxMCA_EDq^;ep>yLEw3oV^(Zq|OO(MwVkG7N4itjR-!JYRSi8hwhq7P3nC}1~Ghwo1H(*8u(B90P%Q znwBV%IyURX0}~CUekwb&ih79ylMJ~btV)~eCCHNvl9@*pJ;#^zod&sAnvKCKx@%36 zcGh-m*0I^+ve75L8ZPo7FNs1;!5;3)gvc@(iDHN4lvfyCiQ|)B^c{JR4G}V`2XKZ;hmKKSfwbJRq(&86!OU_ zbV12uLRC(XqU at aDz-Q9=)a?C4Gwin{Er4DNEo_Mr%uo39rPvZBv>oqBdf%R*yL_(5 zFpr+a0IN;VqDr2V9B-L9^zuGM{=G=CFJfJbo=S{Pj_Fy8 at RWn*EXquCFj7UOjp?nl zE2feZsT<^}6RLk3s6um<;@Dgz__h*wTVW1Yp9NB8i-=~tr7T7t1Qi?rz-Xg^l_ zWc0kAjP at g8G!?9#Qe;Ia;E6U<wldD134yg;KaTU3a4f at 9=mmxr1`ogqKe0HB=fVuC&pn8%#yE zGYq=BW~RXuh0QXU+?vfcm0mO+GSglAX!u! zd{6Per?8%wWftlYm3q6_>nhQnRgzWY-i at f88jXgm_Mx}Xz$m5NlgpLSDZCnqh#p3V zi5TIj20B!y#z0C&^wE{0g%*w}O9U=gQW3)oEQM|b!2{a3f3892+>7d(l5Z>@fkiCopJ&LB1wE_DR{BPv#obOXQn1A5h(hT6}eY_N93_q$!pBu?-oF)AH~C3-D&|}RsniN{o>7sAUQjDQkhUv{ zV$P;D);)@6l+zk2K3%ii`E(6)@6$HTy-(XXcRpS7-21c*bo!jD(5)P9>1=UD9L^UU zVoIvVWN at HT6`ih^3{<4}K&2*`h<>XEUJ;hf1tnwgwcgS)Eixzy_hk*CURbZ*Yl4&L z3WDe)Whm|OMpu}mi2G#blHY|yN-(7)tx1YMMa at CNZL5OM*jB}L#n2M>TqXY|+hWA% z4J|++7EmFq6bH{R2q!6{tq&Dzt5Qj_yhceCJ>|64MMc5%@EbD6swI!LUBsH`5n^RN zX}zIL6$c-xCA=mpi=>ijNv0Gjdu at G&7|l*T0*N|6jpz1D{ePS<{eIl02KH>PLt6Nm^TIbNn+N33-8#J1t z2B|7n)K#>GR$kMV_LT>B at PxI{h<;@FcW}8!2ij%cxIkQC2#qlQC6CjJBHR1vm!pR zo|uTJ(WM5ZMLhJsveKN~zY;=DB>S3jItLNqKxkW_GL{>nbGPPjy#Pv#oeiBVMbJnQ zR9vbTBj|b&^p+b$SmMC0IYOtsX>A2oD%dGV+p1NG6g^ZyGH_6lZ_dfiE32zBa*XVo zvwV4v<2B2mTkM92+M#uJ(Y)og3CAm%>?L+OiN-0f73Uw_fJGF5!JQOmQbd!Ki=&&b zsZa~FyU{zh at q~XHm&$9c8lIwhNYMIxUh{g3q>$Q(ji}+JMnt9c(xtSJG8Foes{KK1 z>(i+6`S(yOYqQjrtc7)owoN?(~WmE;>J61ah968ro1K*+|HB!?OdX*cyvA8LHl|-nw{QIX^rr= zf?>P2Ai2x4&24QeAMJ_mw$wn!RjIQjyNc5jWu-bbqK|IBxQ2GeYgYUIU2oQT-cjVB z8=S64dsD5q95hUImxGo$%Rvwnhe=QjSKT#>@v6IKvD_%^eogEIMStMQk*Qwv3PfKU zJ5SNmyy)4UnCYVyb}x*Xv{h7XV9j){s_KgR1|9J#ucc#)&Txs}RLYZLg^U!Uc%ieE zA-K|Tb|9=Y%*B|OD$FHDMQv~=PbK|3xlG6KRC3 at YE)5rVPGELOlwTT(iKc**VmpIX zZU(E2Uil at Ngf2%&=Xjk*`1`OZr+lG7*@qZ3>n3rAq3UT!pL?bFO|ap+(!$H?XZyuyKliY&Py`9 z#z2B#Kh at t)rBt-PR;ATIIy6!rH at b$FBNw>6$SNm4^TJDz-5jE;3Hc*|?B2Y#NTpp( zz8M%3NXCN1th@!(x>djLEm1?)6$_CgH`N^Aaz>_$rE3P~vaB!F$n5ybjTAVy#FM5e;H% z7;%lu8oyJ>gM at Wkv`n{_sp52FwXRVBG|wgV0($G#QZImX6b(|3lcXNPtsxu~645uH zGQMe)4B<+A2q)c8>>W)yTStYiXz!v-I$B|wAaaXGq|d)3l99NNCL{6HUSg=5L%2kV zGLZUev^CEzbh%b36s2EazZCcsxnw;9hZfR6!HfCL~3NCCD2ssS~C zTEI5ICxGpM9e|yHT}E-YE5iqPXg4=M#rJN&9>Dv6y?}jy{RZ)~n}_!QE1dm2w1=Aq z4ElG_C}QRzqfMP;er9xRZXPx|wlI$v9j`Et8Xd1Rj~N|Xnx7jTo0-Rrj#rr{jE+~E zUl?uTl6lhT*vkCU=y;9!mC^B9^OVuC(EQryc%6Bg-p2O*a-YyC_i=pFm4q4z7FtN!K=N#Z8mgjal^UvbPc+AB)liB8 zQ)*}jJ$I<~)b_IG%Bb}uqXud;m}y~ey_$%vr-pvT!$=XvAP*x2j1*xE_Aqu3#;YF2 z4q)scj3JsT*_#WuY0)#B{+{JIm_qq=}1f3n3w|d;uv6YpvsUUYGH98VaVc=kv-!0C?+S=TVCK^?gftUQXk-M>X9Defi&XV9^!5f at f{CwcN+0s4{^VTILbrZpGJI7 zv?rZ2+5_b;X|SY?{JubLnhr+-Ymz>NuIQ-HXlp8M|7cfpU0H2{JX{lnQR7dcJ6tph zZX&0MS!X1QB>z?ey&zxeS>yUW#i?3xTj-0zaBY$Fw~$`!O1PB#b;-Gf#--QWH<_;4 zv7N5}l8oTDTJT#2Q+bl!GJQE|utf?ucqHh-SfyzEr>HZ(2{F!|>6EC*rzts-$VS*~h%Pc;bdk}bMt$T}b?R}9M*DtfMm>mb z??0&0ZrQOk$7r_(*Pzd=QltAd|4*2I4Z8XowZ^Kl0}p7(Ga0TitXiXfcOFM7iS~RU z=A>GyRyC8lS>~fz(As7^F8j~RN_ma-iD6Hl{_TcDM|!?*J|(LY6JbgmT1X`^ks at h| zra8I^0*XjdC?BM`qq}KZk+s9HeG at wk;po|6h)bb&8P=zUwc7}Ms*(TW1C4V{|5DuT zU8)I3?5*~F-iy at dbb3}x&-v)t?71w*5t}4WZHoM~5tV$M)@PdVQ6AAGJ8_q>8qmps zsR`AAF70-8ho`0BB64YJC5upxb|ziM5x(Kzml3|<;-i|0cyPJu+z3u&jS^^NXwj#| z4bzMcEjfI48L!!#KI;>u$4i!bZ36|8hXzl*E6dtr(C;DaHkf){PRLE zt2LNxWM}U(n071s#9;a&m3?Y3r7$OFn*kk*?KD_cGd4l=*x!@5x)t56N(h$tauvqf zU3AODYBlhQhUY3Zek=N8;E)FGUsOmcx>BX%4dSy5pDV;Cktjs5E)k0pHQHtNBRZ_@ zD3k44#n!69{hsS*vD&>DCfp)5c)-DE+bXu4CYMQKE^U>m)f}Yrxd(aZ5I5iDu|qT= zPu5iEgSXM)bk*Bge1 at jf1lcU1(`x$EuGOl44Bw~KL{(eo`0EsCRvI6HPIO%v2_fs4 zO8&S6o^x#Rfwx5Tpb%Pgk{-{lPLM4Zt`y!A>jZ4_>k)kdGfw!B5j>%moKRUAsezYZ z9(OR0JDA7SlH(0yPNI{VQ#Hk>@fvYyD%}k#l^Vg%F>QUW(ydW#(RyOSvfX{e#$@O9 zxOmmFcH8i8+Av3!tB6^H{tZkRifVSw4!S;7)565Ggi z_iAk&T+^Ip6bC*t+`B9f)YS=`W|!b3iUWr+;n_&`{fyH@`3}qW#ah=vcWCGMK8VAX zBq6?36M-*b9VBUnAaChuVJJ7;wn;j_+mo!+gS at Fq_fwSBh&ul%DA{#Qw{1~nC$FU2 zzmBP7tcQ+~s&GuDwoQf)3ilFq217g&&m1&FZ?iR3 z=SFW;2yB%Eb~#B}lFve1OC(3>%O|2Pm)UKdpIbo37mH4c{c8%u?{ku1_-es0p at 21r zSeI*}Q~aDNc)wckp2l$t;NqaU-VkK!H&YwRs>@8x=Jr|LBS0VbrPGr;4m9YH#l7~g zM$j<}loWw92iUTtWv;!zMr_-_LV_Dg)|2mkjAfrPI*jq)>R#e+$(dSk&6z~oaoXJ?hTAFw1XIBwPKK^ zZ5EQnL?Odwq3adJTn5uGcq()DYLD6L8anhXrmIVQgdYl4>EisvLo@^yXo{in*uQzC zBI6D$ZXK2VpUYDG+76Hjw>#%2s&zUGal3PP!u=}Kt#c}U(P?twYM|@Ki^ckFNwE|! zwimy`-|1D!Umg+%2R;&IYer|;McMAezwRASva>W3FG8%w+h)$hN4Pnf$ByvOVQyCN z*kK;}jGOQA*k at Gg#Tw7zdK-HmI at wMSX;l#(JFiwn^w7;%l&m{nftONu*5 zN!RlZ_}iD(X9kITw&?7VFB##{bB2>Xwb{fN!jBv5; zNjFf^jr;hJfEs*XOQ!tqYqB^%lB9K(h$a?VXno94k<2f7MMSSCCvCd5*!v-tCvxd_ zK>Bchwy6{h=3R^2=vvhuM1ne>vNQQ%ycV|K{WV{z!W`d!#|Ja1SRY^&aD@*SG0a{k-G z`0cgk8$RnhqXJg^MMI1VVZ`4I>;?u&0|xxXh7I_g&>=Z5vg#ZIKJF6+e4TB;_x4$3 zKC6$BDQ3(ri9NmWWewIw+wg;;;g=9Hn*;hR=7VSR~sk& zXS4==$v;|C;wG)kI^G6k6<3{RI1Cd?<3Dw9BdUw+a2kZJ$KJuEy7d& zlU$~~7c_?)(KOiHf4DqLLB-1ul}8l)_L8Ldi3hqhnj8VPz;{lO8zKiG_{Fm1IYY>H zot)YtzoVLp7V6k=5$727bIeDMV#V~BNM&B0%HFykVYt@|Uv7h;fhK!UMaV64Vch2rN>-q%tInz`a; zE}hn!3}XJt^hrBuMHl%a2GdB*zMy}kRva6t1xIQAQ5qBD&;CI>d&@y}!hFj|^N at YL zOKb7z&P4Q!iM}|mO-2X!#LoOl8hM!mlQbpt7PQ{CNNM^~7%Cxh%G^|gaV370E&8&L z#?z0T at zfeo!B>rB^i`B_h#Gj+kV!EBU6r<%{@uj_|gT~%cd+gCpTw!@7{mlZ<29Ms`}GZDbYpHZsy<=ogi=L|m!OEh8l*%_6JZq`Z1vF at Io-yXf4D(%||5cwvkh5R z=jx~a)-y+zlJa?8*a=sP&qP!D1vNZlOSRBC^xkzu^n#aQ0VVj(OR&I6fM;w$MuLl8 zf|Zn@&P%Y;Nq}c;Wk!NHcM~k41iiVNV3Csm&)A}j1Z7@=xs;%fmtd}w0MFRmj0AnX z1oJ6DxtCzRlK{`y{EP(syaX#KL4Pm73MT=cu at xB!26zdUQ-YVh1k0TSc*d4zBp66P z%B^kDY3Fp4cBQ%PO7q&4=C>x?8bivqNJ%%!s{Yq} z5?x=6cg=p+tl64aC7Z29%Vf_A;MQV0cyYa8-CAe|FVvznk{4`PtL@;`^@3^o&?|b- z37UuxPdY7I*{ozJSweDa)- zbUyi%k4okRsM8tJ at 6AylMe^)?^CJ1_eADGyoz8bgX1-0GkaWHU^~$GJpfZVp8mYmO ziug!DWu$4OprP_ozP;>xXJ+Qx%n3>7+x&aV`$HVqC2!{O(Y!L!EYdu){Q0!IFyu1Uohhtk)%L1gGoWIwX=dU7J#%zwWaSZwBW;X(@(HG)Gh8b2Lm*DQDmY-t@`A zVLq^6eW+QpG;4`gGR&vOhxwpT7KexXf;ob2Nsb!NDb>YEE$)BIC*uo>WORfN+T{pe zWzSN2840}te=6x1pdNjjg5LItqh?0*9g2I0zH&m#G;oBlG8unrB(NpNmTA-#K|iR| zA+r))j_|;{J~apxssp~_I;0I}C-$j)Xq2~E-IBaH&1e2TcJakf^7h)5QQK1 at XIArr}}v}v!ZM>{Kn_F_Rh%b~qk zLnpqJ_F~XR*UoksCQi|vvnm8_ at uRbX8WD|T6VZi4v`9i75YdGi`t+ql7lJ5ydXC5N zj7*~T at Q&nE)}x(6v{yT{a|G?x8hZMrv{!@nYMQJDaaZMd&rvyUY89JKF at t#n?y*;) zZ3~Z z{r0ugwTRWw&*u47$iXi-RQ`_*$^QkH$iAg5ByaSnr629`rIy!He9DW5{hCTP5!<6u z`9^QF*6DRZjxrP6lxTzb=l!k`)QRb%Svw2HdA%zp at C zY$PX@`=6rRuvOgpC0+hSDEX4}!&eY^e25<6_|8Ef08JO`e;LYcYPa=*iWZ~2RlJ@`{EpYV$2tAumR7MX)UV#< z?!ss#clW%G_Z4=(HFh&?^WpA>*~wn)2Z+5b_9NQ1AUapKOsnS`-myvS8y-4K&$B%A zHSgFs_B9WE%RA=AzU85d-29NoF7nU?ZqDVg3p{k5Ti@{Tc^*2&+lEi^P#qq1)Jvyw z_g2W$WW7)0&h<{A(>xJAO&HTf{*4e+2k~9Ut at DI9gVT-IFWXj60%2k^fpvmcMEo)-;z-)U{1kg{YUFio>og=}ensX*tBll at v+kINtReEWxG(j4yf1&Z| zKE_+K89pYfY^INCg>06Osdus2KBh=)j*ph?t1-kiej_bTX^H4upWxTI+U>4H$z2k@ zC at LAA>kIbh$&&u!j>4LZYONJrrjb4AmhnWaj2d{GsO%yTKC8LE)pJ_24`Q9x?B9(N zKQBuz(@!`pay1pm;-t+*a)!Ld$@){t8PHPG4 zjMhJPT3m;Ivm)p1NAFe)*{!zIn)s|cRrlp> zV|_7Q;_IRoI*G?gl3DwN^y`AAK{xPt8cw_6kd3$V5V?sAp$#wefwMw~t6(_~lyedv z`!Rxeql1!jDd|^Qel2(t)sp|D`{q^jp35qF&t(<8=dz04b9qHOK?77;nzY}{{+hK? z#58p2C{@>00yXfFk9inU1_q)~CND}(M>%!+pz^{X=`|r;K9HtKv&7p9X}z>rys-nK zQhx#I{kazST>JhevK!K=&wtTU2RZFy`zL8H_8`g}HciE-I;{oIXeDRRvrkiFCBi3E zcck^@#WvC6G*OqIzAis*K%YwkdbhnVzCTLU(3KFj6wLFBn;&j- z7d at ru8)9=7t at +-j*u zs52es>CH1I>Z>eYWX!E6IqjCQ+^#gie8c=p=KzU{Wtq*xK z*UL0>eI at 2}G7(r?2|Ay2Ix}Q99a}h)8S*Wq`X)1^ z4`p^XGvqs>bB?=<%_wo;Yq3M(TQQS-C}xt|Y7r4Q2LUsf)149Y^_|8-gV7BKbDKf& z%kNIjm$@~c+e;vWxBOE3tq;%dmPYh?uFY=#j}hew24tI}@Z%b7o^akopGq|-Gia%X z3XpW>!t+9rpx at XT%<~XSCpo zOoV%Y&|loyElPc7rfFNIn=fmzGCkBsH<$2OA3fAthrEgP)TLswd6Lk8~W((PQ;bI%$(N(LambO^y}OhL at O$@9K(T zH2S~Ytv(6_b)){>>hDp;ypT>K`!X&od5aTte?VMQGMqAgUvI!@t~3;ZAz)rcXt9g`GTw^prXU za1uAJNF@=U5=nI at sh;LvuS|)wg3*-V-b)?HB7*c+cLVJ5+Lsqr>h-43(Yos&+2BYh zf2-ICYQiykdU74BXF5PWwjChj^n6Ikd#XB{OMT`tpSj#;uJD;Feda2kIhUKOedZdU zxz=Z{^O@^?<_4d+(PwV*nVWs)huqxa>*jMDCgb(X9VQQ=ca0=2J`lZ&rq=;_VR!(I z%?b5OFwrZ)MyCW_TgBd?wCzddWxtO;`}9ktvG5Bo{{Fd_e*awf`S2f}exb+jfAzN> zFFyU;!}cfZ!Zu`icTbCE?mAGotwQOn9r>R(S8s1;HNB3tcxzMWcdUPBx%tcg*xu%wWlclzKgKqX87=+8 z$(7QyD^%L?-#ogp=hb`iH%ov2eE#$CQSE#az9<=ZtaJV>Hu&lZ2^M7upNuN#&$)_+ zj2dEWCR11!rBEr9*vOG1K`E6I0JN( z$L~No!%e3<=_WHD(!Iv6!8fI|tHkX6!07P at _WL)l=`!&t)~ea<{LM(8<)-&} z`DT0h+C8Dcd~@7%*-pAqtPu-bn7|&3JLz)%ao42ZJuvr|)1T|v&mT|t8oMbPIVs*_ zO;}epMfz3TDPc~(8~rCH&TL*?GHUL;LS&NbCTZd%S<1%aZytMLXop!N?)Xz&7R3|U zwc61A?JnD!KfnH#g51RH`!-*7i at Epbw-h91-}~@YeAHF;TeDjVsvoTwKWXp-E$7{0 zR?n=AP96N<+qcDjJ0 at D*z{B#Fz1<lX%AdIBrL0F9EfeisR~L;& zsl{)cbJz7PrY?Hr`E~g-MVIgWbUS`~9tOq9A0IyR`qYz4etz=PiMLP4Dw1D#=C!wn zElCy7sr-*&*K*cG+ZoXdw zr1FZRi?;r4(wN7JCvX0F%bB-c|ID_nf1BL&(JpATmTse6*#}wyE8cB!Xz)8j8g1)WHl#0uY at XOXLTWo!FR#J9obR{QBFk0+tM{ zcV_3`|0u8q40^S$?&m*$xQ at ae&R(3WxjEE!gUpgCJpwfFaA1r+p~L at s=)CdnBL95?;pQjlsA0b;BGAk-ukTkR`2^C zTO&rYUSgdb`DXRZ5z8KVsS@?DX6oPv*w}V+s%K6Z)6G9F{@C45Ju_kJ%*myXc4^

    zbIxCOziYJKOO-P2UhAQuT~H?&a&>iAUo9q|HTTa9&koP1oK=xklKn8)qqP4hLV-Itw9kwcUao z?PT}Dy8JQYB-RRTmi_D>BTv5&e({fgex41Ne+R8I1SkVc0$iQV*k5uOD~5f!wFzTK z0HsiqcL56X7<&-#Pry*XXuv$cuzbcA0R}Z?Y+3vV@>a5Yzd$d ztp6#1J%H!$V(fLmukS`#0quXn*kZs^zyZMApQ29hVeC~j)+c~(>3JWXfW1Ehe}2>t z;3+^az~^Lq)A1^(iqb>S_W7DpW%RQeHa_u7j4oHeGV{a0C;(su_p&I_UJ2& z-8YD_qQR)=R~h>TaCiv%z-wslp^W|fb>O at KQyTD_H&H&o`C+K{;TUvp!H@;a9f9|e zjO_y)e;aN1F6wp^ZSh8bs9A}dB6lfqX{s> z0rEujVZda-V!%z4kPqN)Og8fXO{OsR2f%Q^D!}JcXd4B>022WF0lsM{GvG-;Kfoct zgz1p&GtdWS(9ggi%`D_O8{=mVW7=GlAFz5J$~YhWegWEaA!FBkgt$et=LmJY1mhPF zS&B5v7~25&>vHs)73eoB8T;dEjGZ;;i!d8 at u7^C?$k_kG#GSnvZ3D>Ng8Bz60kruT zP=$V(z!(C&lf*bmG1hl0#%VQU9c$6XfH~V>=zoIt*pBk<0PUUVJAhy9LOlYi0lA+t z_5k1|z;b}G8#WN225 at c<+Gjswalm~CQSL)%gU?W}hf&@mjQ!*&;aZl2{es(KaxxiY0afpmAG?nSgfze{LtSK>)2icmfo6kk~H) zD*?v<*W4hnHvmmdiS+>lJ4)V{{zT+7JcYBv?E|Ipvm(R zyAE(0pex{cz>+_sPXQkL3+fWk<^_p819%6p5pW*RzK6uR0Nw%^FQOg+9RUvjf`FcY z!GOttb%28axu?WB0Dca59`G7qHee4x?uC2+j{$lDMgZ0T4g<3PDzQ5OVZac;Ccr5` z;Y;Y#fPsKnfG+ at 7{SEC1hyxY_&I5k+chm{sZ-7C7_W`p2*Znv8L2sn*C$Uk0<$x66 zG{EQ&T7XV~`vJcJyaM?B0N?@cei``>l-Ov%%2&{@21#t*VDR`V@)?3Qd=342D8>%p zkFQJYDBz!ONX+*p+5^CbAzy$t9CTRWtu;fbD_QYM5o#?CRZ4n$2fs zIWcX-+RC?t>Q;f5!=GZypkGyOQ#DY(hL@)uNa`^U`#3FWcB`H8E!sa}e+79x-+8BSY~0Jbc~19b(WYjS7*vo0Hy$P!+rOpQ`zF}FxO7(&>G|#&o at 3Q; z=y%Gkm`e$5NnKDurtJ4=>^mEJ-?0M0P7iofa$4u#=dpQh_)GiWHFj#C at i7sVexaLw z)1lYKuf9(Va(Je+f4L{f%ko}{s;KC|)m8P}GsiI{FA?-!ifhKjtas at YPROVZsZR`V zWt*s}pabtX4ZOs)4`>8;T9;h~AUcE0?1w)^m4x<-|9*+p9XREbM(BPj{`i-mO7}>v zpscp}DuX%EZ>-sb`L8nK;YSfd>K{pzaANfQ6b>d^;Ez1D|Er|XLbT at J0ZA>fahD1^)GUS2{fcZ1l5(wIEqP3mGoDKT)r}-$&>`F3Vd5^hvtOV#QNS^cD zwdu)NsZ`Eh at 1BvV4`%KJMd!dN==}sh?c?(b6TQ2O)p;mU+(|c40yJGeQ&4PP!23Zf zYp|TAY`JBBsRt;$3%ENRqqc2Tr5%kw2eI=&Q@*+adMs=?^wd59h`OiMPW+VJeA762 zoV4$D72)7SzX+^Q0QuJ3D46gmQAI1|Wtb%LY?>7YvP=Mij{>cTF)9d^0cVCb9tx5u z1G6>-_-BCmagDxD)rs^P at j#83_)XK$+D at n>EfT(zlC{vU0K`J28U519>5aBgc_cn2 z8X>yGn>F#_>y40?`Ks5|X8^>e1uzf~sLEwt&Q&Y8*=G?8IpZB`<=_F4%sip{WnTPm zM_$B=S!)d&gn5WCeji_h% zwT+vww0EIwxG!q(j2?_Ve&|gIBbLhiaC6->IC-bY$Yb8N+`DpJ(sv=_T2;8!b1M%1 z42c+yly_8S)|V4N1`p^T&KF95$6Mx7t;$;&)+JPn;#gNPfVXhB>;?v6a_tQD#9&3N z8~WmL+}YQo)?XVIAr=_Grvw?l!@&pqC&=`9@bM7B|RvAJPb_UVPiw`rfMu9&9)4zCmt7acZ7sSBQp zzLz#)Kgu*n1f&&bZl^-*GIBSMkb`+0{3n>|<6~ zMs3<A~_WPun8rGGV8n zb2oezoG{7Qa{|D>F86)3%aTqOmdHGJJOuo6$&6~^ zaJ}1!HWHs%*6s$PDo-!}0TS;$g9pS+420lfeZeLS7ad^KG}*^9UKw)UwqpLK?0Hp# zE5tBUv|Vhrm5Z3`l0>FKRGMB1ZIEdZ0NUbJPJ8kvlB|yQMB($=;wY-%+7G$f6%9*i zCr3H9Ly0=~>w7YEH8q|wf%Ogs9flgX3BM}t*4$X<_g at iLR&Ch+-8`pAyT#xMJo!hV z|EQ^pYDuWe(zsaxlWFKe>PX9i%(+le(Xqz`yiaImb?~-OjsVxH`Vwzr%3)MEhO?32*+T^{*%1b#1ioI3Z07$f}!Pu*U+baduk z9$@!**D4gz%sJ2TigrST!C-?-BC_2*^}W+`iqUY2c5dCSK~n|LeGfcrnjBsqul`(z zbNBkX^VC|HC4(Ly&yRr1swUQ=O+o&dliv62NI3J!Q5AW>!D$Z^&hg$bo{-fb&<-^k{-ma%)2prSF`P#&b3_YjyVa at AX z0(GUvy-eFSBCE~wX~g3P0PcYSEm0S9Lyb>YW>@87-G?}R>(q=M0S_-SB$G}BS$S;2 zman0>q`MMLGFvk-%=ZCsTSZ{vbR{F`v4yj9`Ne&b%2p+1x|ITQj at BSi5wPRvg4Iq; z)tH3!i$|2&X*R)^=8yece`Qc#J%4h4EtrXB7JH0l7ERQhdgt_$4*NdkRAuH^e}&VD zhK%|)8OkZDEVQ4mwcaeD3P1 at 2AZKFHgZ4}vba1n>b+D3uV=1}ucn4UA0R~B~3B!_R zf%{e_k`JmP&LUNxtw{$ZvD=o&;UhEcD^NCZVy at u?DMq(n;l&D at pX32N#RD80(W$s{ zXN|w0(yC%zGC at Dhz6sD(mw;2gPzq{MmhNN;HlWq8l_?;X5_4*ou5?&LQ|6{;L(J#x zhwHvS|Mr`n$t$@N8mX}pFh{-FA=h)Xt2$5I(PYNVdb at 5-?f}CD zmkiwUkj`ddPa7e$GVN?U875T@}Fv09wf<^1+ z>#ZxUq2H2~v$?;;^CJ+42p)fS9e?&by|a0G{!9zZt)blmM;l<;!mI+IT)~hh){jm^ z$Dbw5lzivG?%Ow$rL6-v&ew&!Wrsa z?#s4mo<^P$51KThUX64@=po^PxV{N895*hPsjd9hGx4S6g8|jHX*}4;-0?s4I41CH+L2*&5tiUz15S>`lR64%AFI#B^Aq~IgNwO0CGSD}P(M#MRIJ$5Nmo+EM(XI5w*JwVC?nfbTIrjdB<1Ydiu z0k70^)eh;Eh{9o9)x})5d8M2EMG6ho4rlTc(!=Ms%4!xCAy+E^!tdTRzv-sGF`se9 zg(!!EkLAN{NgQNo$xT3q@Cu}q?pJ8#F%vp#rdj!1g-2=I0)VCo%bQy>v-L#D-HX{V}q z5 at fsYsPOx_Tb0l@nFJIc)fMra+#qfxAT{St#77i90Zdpjq~t_yJj3TDF-IBG`!PR0 zm;7V9f&nNm3FG`$x=pn+!j~)pa?gIvR#nlh$8Oj#shQ#5mxt2Bm6! zv5EUwbCnFSn4$$}&A4;x0(647C}&hEAFXk5HNb3gvR1P{P4Qz|b_ErclO+I9P8f*% zdN^ULOhrYbuwr6y=Q;dN*Ka^5l6jNwg$pQ3KUehaZ=Q?pwn$9rE>lDkK*a*+_4RP~ zp|bY-gxo&2GXt4bj{}H60ZRdGOPYOfvv(=xJXbR6uQ<6pE}uPoizGvvvH-|39;l`r zfX?G~ILDhb8l_$vT at TV0D)ZEWS*CZ|m0cYV4<4k2#I!5qCZme#VxZ?8Yg%&E+E=n3 zw&noN6J%K8$#JAuhZ6E*gqe$KxyY at tNXEkkfOv=j0dJi zB}ZtyG+w)=-Lk$Kwl%>*GT=BxS_GTV?F@#4DNaRv&TETHn23t)j6pI^G`8veOvrA>~VqdB&lw?O~PrD_MF8-1ygOb z|7vaoH9-4RDs*%YYfySmoBJB6)`XgNRBnh_)=kFIMHn!@u9{yNI+K6;=jNHOFv|%@ zM1!H8%+a1-sG32OVM(JS%i4l$SBtHUGA4#9Z*W&WJS~}F(RX=0)k>qpR<^<@zS6z= zkJjGszstB$N#bp6p5k`5INNXTt4sPmYKl{G$5i5b&ykdQPtf^~biVU+Gtzgk;#D>h zTaf at PZl>Vzxi)^QEaUV)36{5JT5dhE4*3PU2#`b!xN^b8CObN8D=uydts|B)FAu-L zTT(VkY9pb{TlAiPX-)c9JI(=QO)x;UkdNBI88t*+vsgVwk;h`8p8%U0ub3KOwBV*K zocShgH)xWY*(?u_!+<8iiOwboa2KJ{oJ1_EEOl3}VC;O_Fx at JsvROby?e zlYRDx09ydjw*<{l>gh?wvQmzoPsD5OM*O zcaT(ged^-cq?CL<8{cib-vaV}a#}4vbJCYy z;4rmEd3AMCX;igR5jv87iem>5ox}K&U+a<-0DtHKct+AIo%Ag=8&}J>hADxFqQU_J88|Jv z^xxsh1oX$N%rm^kmJ__JF3zS2wOAT_3Bdqs6o3V*prRRq#V z1kA3QZdPV(WGC~!R(sv-p^|xj&HIZZacsXk*dI^)H26%Rk)V&)S z=!89fUfkt7tTiQ~G|E))M$`0Q!(h7MWl|R{I(Z8~-CN at qYChjoPFx@CvqcBkBzq?h zHz)sjI{u5nsZ2$1bnta$hJR)0!k|@4qm#AhN^(?mJL3Mgo}kvF6tZEarc2`=ya7vL z<5|4<8k(f>;ua*svVHl9tVJM%mjC1L;TAlt20ncb4Jj5#=^J zYlD&|mgqPMfM~>k@}D at _CO0FkmnSB5oF$H4 zkW$&aUcXNRNxy3Wl*x|%J4uY(wrBxd;=LqWWes#S5JEN|?VRaYsSALeKkk@$-R*4X zxR>@voLDoWJbnTX1Vngi19~R$z}oJ#D^5L!ezbqn_MUh1pWqvI`)>~}1HnwMeOtMi zSJ+g0z}8cW3f8#bU|kvi#_}%zcUeB~cL06_4oM<_y*l}tk(%=;dBfKfz^egJ(z=X_`>fwDk9~}Ps;NycyRpXs z=nu*P_Y5KBW+7H at H(p_uNca<4T3X!ApK$=-4m-due=IX>p0iGv%K!D7bDM{E?L_?Q z=A*Lcuk)vGFScgIpU?PE{Ne&G{|w^oP*JAEI?4)v-X{L`jc^Qvco)FjegD|xxBcY3 zt8&?eZ&&c5O>a;v;LbS0x?NgSpVkdE4Er#^YiLBa$T2O!S6_=h7S4A#MUxEi zM+JvhM@#D)6TI(>9OXP(#NJIv%A_m?5~9v(^X`+Xl_8^@-vxkO2f>ttU7=0#m2HG(YNbGBht18MsGga3&A!p9<6?i}jyCpm=O!8;(PSXg`;S;G{@b_A!KyXi^)o8d z;6W({yy@(?Z){LWhIX}X{g(aGwlm`_P}()xTahQ*ZRyu20xG)!9oJp`2N{=7$$qN3 zV4hx&*RSo?1K_L|8KNDHWL!rOg{2$*P_^r(FM4 at ta#mO at f(k4ETR*W`AsOk~bmiH5 z$IP|^3Z*+}ykqe{+&J};rlFJ?XXZ7vlV;KBxMDM<-b$K=s_^K9EZr=NuXX;zVblWRl76X9svPHYila0`Q zi%)=oy=0P4Cmq17|JRa2*8&ecCx_R{sP0C#nfb__G at MD6{8?@5Slz|wm)yQFhVE+PM;v-t8pv5J81GWn}cR&PC<1 at A`zc9!&ZB7Vmd ze%}Ld^_- at j3Or3$D&y)I`I)EW+>$HApC%ACuBy4C$qf4y(?J^fA=dN;whuU2-^C at _ z1xTSy0di^;@aumxB1R_(EJ@{``%+xN8T%krK$c6IdE3fUdl~7&g0&rKxJxm9BrELn2z10Ldp0!Yn^%d)}pgeP-Fxk;_kcKW43B$?ZNsK?HSQwNSd z*WB8Em*zh2_h(or5^1SK)#e-J{On#U%X3MASfK!iYyY^TMU9Fh-&kg0l2Dm^`M%au zK>(!+0lK1Nv(6lYcm}Uba3vA$T+&G$9q85Q#!#(VF?Q`sM%!vwyG(WSFo%LQT=rHv z6R>8XWq;(}l>hmhJ~wVhhw_FduT$mV3juJ~YbrOTH_FB^17%BHNoa9zT^l>76EaRSzrmihk?*gml<0;I;V at dZ}6?THMv4$2Z7fp+eU!U~DnLj^q at Vz?=5LhjfETXYqZ1 z`(f2a$6P;D^Xu6=&xHb!)*s}jW1mcX{}lIuQ$-;C#Vg$jfA;SZ&muN at 6XqH|3VmNT zJy^Yd?P6$Z1VJ|aQ7$ecu4(2(vW*zC7{ez{)B6=&k at a|SXQ)ahCdrLCm#0|@({B|%vGBWe=|3EKM~l8|;SXN`O+d204t_eu6nvhg zzkstTtfA6=>y>brww^OPqw(E1Py8{;*k)6_oC zeuvaH`qRR{KPwAuCy0FOSik%7$>3?xS@#$vQL6wc}4 zfB}&=Wx9pqm3|a(|8@~Z68|CXW~@e~(j%KXD{#P;0CUWM*iu+5 at X&EUzyC$!S7DbH z{w at WW>o!>?Z*u=hLj4`CYYen9GFZFG5ZE($d{|gY1lW>kj1EzqnT)6=13R*U!R at Ld z3TR~~4Z$Ny7T?+(%oG2sUJBd?(`&;{CTyU*L$ zj{!lBm3Mk*v%nVeoB!>pz!d#^r` zGvmAIPyf0gr=*VQ0e~1 at gxoUuqq=u%gPzf{6LVwvuFbX-A7DZsHAU##b5#5;GF?$y zvU%_^+MNt>{zrzq?69<>9ROOy10ZKbzmfqlyAxcw6K#%GGZ|g?K&jEG|H>HtmGSpO zjrt-q5HqP|n+q at s6D?dj6 zD#3LH3=TS0|J3F55c4WNigoqvO-(~xzF~FZv=Rk_zzwd+>lx&6+7inTO#A&`nOv3o?EKdm_;|O*N2kK9 zRs7 at B)U**3X_{C62kGsy2=pz(20Lb+u4E6#tj+b$4gGM7&`*ZJ0jRz+aZ+TLNr+TA zv40J5Ygp^num!MEqp6;GW2manXP*;cxu2*RVJKJdGN8Tol`yINyW0+p67 at HUqQ`m3 zFvV5kI9oKiX_Et~>8d}maZGMr&cu#uyWgm2%i^V13w>DXRL&YsTuB6T* z^@bOUcZveAmf7f$hQy_u$9CaznG1HN75$&$SK)`g`REuI(o6HC!f^Af at 6H_ z4drwj6rD{9dz+rc2fva<)Q45CwZGkUjV{SE(|^+XbuTyma`N~)H3GT2kIAlTi5qb6X8u!(!6roB4W5qhULfgQM2;lfdfVu9K!T36H>R8F3>x+Gj zO7`k?z;e_~`^Z-tflhnVWY_oyzV~;`bprk1^xEsO8(Lmh0aAnv5WR_nQqvc=bz@=Y>n(>`KXHBRr4 at DF9j)n z1vSs^k9QvcxNIl1?UzjL-#5iWYZa&wJtGPsh+;(#3at>$jSG9xU=}aLx=FW~y|% z_RSK_JieDE?2jh5ANZ9vsG}Ru5b4L!Lgl*ze79av2tiJhE;i3Iw~rTU4$cC^J6A{& zBP(a&OXp_vmqzOe>m+R>H8O+EhnL?CZ-+0?AfZaPRVsNa?oSoJu2;{&E9qZ`I&-ym zOY#uPFjX07Qm3{ch1~>GQb0yYgJVgI}}OOp^~CqAv8E2<&tvO at TAmifK8HJ$tp41hb(LgZ3FMyV>x(m=^GffpXl zo;(Repp(oCZx-jE30f zM_=Op2xwSLjF$|aRkK8Yi~0bL+a<8ibAJ62L4%>{zpFXAtacRI)Tqq?GWWjZuG#iN(XkQg-sP_`HdU*N1&jJD#8V>)Z}-HfhsG!24I;r}Q7 zq3M42ur4N>6(-T{<#|g5=ZRwzlax!=B-%{c0Rt|;NZDKBlBrUKh94der{uvY&~_Qf zS%8$Lz|1=76S|pvV}I=D#$h1Zw at BT>^Gd&*&^M=sH?z(jWZh_*XU$fV(PDSRGu|hM z{lf^|y1j-3;f6pYCmtniw at C5SK)`W1-5yu!?N+i%e*YwG09l&SQ5a33-pr;eYW5Q3 znmiT-DOcv82i&o|g`JZXrr?f{Sh)2E>C(6D8JoF%(Je-5P9&>N2(BFPKC!Wmh_(&V zLabQ7c%9?pg7&0<@<{;Ig?&vLQ+V*!N1J$md?JRSL&nd$suP0U0*ueE5h4!0O1BZy zBASGxUnIZmtVe at k;`uvb1 z0A&SLKQ+WZzTT_^EIY7P8Hg|WBm)KB3aTRX`xSZ)yPa3-?ZO`Hmz`#JIuuxYUO3bS zfD752u#ACREpX36d{ci>;` zu^Iur6IQ0r8t$AI81e2wAsh9Lt~Q*wA!X1~Oo3?nfnLs`Ue4S*^QV at zPv>)d8;!kw zK!M;gz;SeO*hm$-AUYt za-*U5WR7Uv^-hQd5g=4s#tkBFyq-b4^SS+SJ74|p&%J)m$)q;f)PcA6Z)cV_G1y=L zG$R(UD?BP(-au{~L`P^VerY|Xu>oRbfaq1P?qdG>D_2|d2H(^?cPpJcaEu$NsGWE0 z3|PO)DF3{*Aoo|r0Ryzm)l~$dOT&0Iz28487{4w51klpIfL>VIE9H8A)CH&3LD$Px zddyGZ>Cv%dJSBg&_G-3Z#djK06M{S8*#k(Y~I1GSD_g at GCO&Q9T;b;WWcQ! z%}t5ogFf98_mW?K{8N*o>wOsvp~Z at R2USyd6HwQ)Q*r{8m^-?RbKgmL(TI!AQO6ur z`z~Gn5;ov7dZnqWAi at IBvwcl=DttxDx^I30aNyGR9`4nW&kp&N at p@5)GwH<*P0D%8 z*G7cfsYIl8D~!0Ig)V1TUb@*$IbUy#oxPp*nE$FHLoNc}rF6O*W{-D)JqjRLsO3LdVO@$ZR zg*Hw at clUPHPv4(l>QPKA8xco+P`-^xd#PPTlC zKl4*;vsI?eH|`~=$ZuXmp|P~9{MNJA2BgB2c}~ruD9cre%khTyOaYJeW6ewe(?twL z5v?^iiaQ}A<@2)0zKa$2Fm$?w;ZJ`@cS6`SoklU>&<7`Z!rJVn4B~t|OvBg8GB;hZ zuZYU(*IS>Paq7y_-=dLQaB)#vV*-vbQt+Y?F=&j9b!Igxe1#FUsNBQE*!ZyV)b?UYtp zbh3o^)4>xmS?c`>5=BLQ3U8nZyPb2({bzpqfjgR)mGHpDL}MGLKDE~)0_pS8 at SZEg z%33V~RN9a7Qu9T?!_xdJgG at gof$OGE at QW)(hNRw}7k zUP?x at i9Va%+U at TE{pRdaB?el)-WyO*9ivizz3B0IQSLK&VyC?yfz_IOIR~H2k67ud zj>>8D#3BUyQxaX5M)3J*6XAbgN at GPL5a*Z-y9>+Z>Z4w4A$od7BY3CRSvVEK4YXLs zM`DE~nx^57kwGsLS{cd$RmOKFCOn1dZ|TV!A~wbDtY+!$Nk06b!uTWcRy0C*NpIl4 zzPwP&uotL=9?0)b7TLfs>Y1e5HuCFh~kzI=nv2vs5hL=Mc_&~ z at N1rI2{LP0 at Y<@C)4vkd9?m at H8V&cC<6TyKU59$f0iJr1p>Z(Tzma?g>uq&@^pD;48mC^@*NbMFb$ zopXyMw$feRlZexv^#*710o;=e9tpJOLf+K9j{0)FS7jQakemCPoU!_)Z(u`CYp9D1 zC$S{BWQ?VNREDL$ly|otAb0P=uHQ=PKfCkJlnl)v(XxR=;om+!ey+O=QK}-c#-E{p zXEz0MOe;}w3=3C#@!iX2NtP&N1c-MBy3`-IoSME?TjBLEuW zFwHwqK1o$;6GM)5=HEM5JL}sU9bD&BWT5F`^Okcf7rqg~i$^>h>WwDNe7_{3^PRUlPpB!1+v-C@?CB{{;m7-R at _l7 zeEFF;J-Xf%R7W+-_L_@!goD9db!Z+I;f^S4wEI>tBJW@#Qu z4*f*~+4 at B=U7t|e@!%1tF9!nRYZ%HQ{`j|?bRG3nCJL1!o<2_;WdptKbkED?$!BBO z;|y0P>@HVHou~+SCIbZIMjMMsSGOh~Cp7zHr)mT$akodpW6H-a4j=M0{^7>U7*0SP zGAZ}nFpP&O;PK;Kv$AUvxfJ)cR)(8bSo<3lRTWGDDxnj4D z(|CU`TDP^q&W#|%slH;HXE+bYla_*R%(+yu2%jt{O^{GThst23ONEv`Fs7jAAs&@WZx|ZVk`?~Mc*qvB{JltcKV*TDqY?`p*@dkAxDVDJ4z_upU{(O z=D=k-6ua4C82HJM0c~O>yA3Kw3w7Gy1sl8)zgTGwb<9xzb1ylzaP$Mx!Zz9(skknC zOd}Q~9W at 8c%9XU03P;||%U>8pW7^q}4Dt^~R^8>Z6jvV{wJ*1>HYlOVjHAy5HZ{Kv zeJC}zl)c>CZ8Cn2czsS|Vz9A>uUAqjF;b5Vu at D2C$v3U}zJFTmCJr65arly?Qt;$M zMT at 0uZp5zA_;YbSa0fwVQq&VSlHfuMdbXHN<$4hw6R0OINp=$(&JjPWF3VrKsY8Yk zwg5CWbrNfAV$u+vDb%;lS(!RvTxkqgp0LcZUcOiYu$H=K);@l_cAA(IsAATV#gH53 z=OuNewCIXwOV3*F^_w^VuX+UJ6(iQ4yx8tfx2uxHosY5_tK~Go04ppRViMCKm$1yD1-C1gxAF!ox5}TDt|zQZ0MzYHXu<~E<5_3dG=H9)|C6ZovRF$B z1c?G1GohA;CQ;j3Ew<@zf^4VWQhj`~$^pu`6T-)WwIVku(-+*;lQx_V=Tv#ubfNW~ zE7j`N`5Ycr(qXbb-)Yk{l8|MVWGz=XcE3VC?^(XoGCW)lY`d4T7r=+%wD zNsLF383>zuSf?AABr7*({&$0Cod!CgzX?Fr at 89bT;jhKcB!YK~zRFib at p+D1-4@%u zKlhxhFcAKL1FAqrMZr4Sy)|ic-ij1r!*TR6;PLA8 at ti*0(({5IZ7VXIMa3>xDAUh6;rCBM8jR!DRh3Taq1j^TQ}cEkm+N^87Jv4?9~Bo zEub1WFhA7XJr>D5a+k1VxVvPS0x=u_=dNe+3QHEcH+eX0tCL5ETy@<#DlX!-#x{R zW=%JkBu_w at fXW1!-qkc-P9nCbobn&LDbv=~Ud}$T<%FCsO^sajWJAogHwtpjSFqVG znYK#$@rrbee-HCB{&l}Q#FO5&mH$NanQi$p!w^sF%qIl-xGVpOQ?2VdswLqr18XJ2 zhI0!i&yWa@{x3gr`u}6^|1$R0tXrKzUQ9gEkeX{Q5#&LBxp}hw*5oNyz?-c}bh*3% zm}*SHt*oWf<2SjrYI)~J4QGeqZDDJQwQp#y94tAG_~5LhLSs&~!F4Q!J*dk7jJbv5 zr0f8~^hn;F5iilnc=+^US<#HVo&C|b*8`|xG?_lXXT(VB2EBVRon^S9>VV()^&Zv+ zfS413+*JFc`Z0N>`k!U^@Nci**01Y2!DAws1+QM$EuQn6 at 9ICZyThyEX@p31F-w3b zSp;{Cmu1kte3$6V?kb8$(-YM27+?5(awY>wF{bCO75F^>7Q7qt<1+d;kD-Usmb>}{1N1I46jz*-OBg1(+Ax3knR>9w1 zvQT!qdc(IOh1!73Q9>WMid{VDXe&x-yCAs~5raD!Dk?h1JQV$GGeSC&OYo%4QUv*= z6^5=fvxEB at smAVqyffZ8O{Cxd`{uAD#^IxXtcy_;-e16kK?KHp7|}V;>=9Xm^b$m9Hg0ZpmAZBM51rmrbBPguy2=-Wmi@?T}= z46%=g%`>^jmP^qEOl72EyJ)VX2px#m`!oTVIUhhz&2z|+R^%cQ-0bo*rSwW8Iz zFT$4bJ>xB8HgoOqkaw}`QL(@x;*eYD_u*R)oyUa8?-WW$Cv#T{#0wof5r4AA`MQxP{{nk8Rc{}q6e4)c`Sm~FjE7sR>13E}FA%wHj%7knL(@m< zB$gw1^<28iY;Gcu5H|^&CBblX=dwoymWjdW-MNvrEa^x=y~P;T{z1Bc1|WYVTNayS z5;Pm{t%jG{U4+qc%r^nsmH*oNUG1Xi!)~!ve+8aK6gZ}c7nL=Fa!DBRM8z3-GWFv` z67>L-3IKGruQI_~iVXGEa*tf~OXmF{15S|V`6#N!4Em9QVz2!%}u z1%@P0^~;~bj?rrsL)cMdXl^e+Z+jKx&o-FvCZ_#fLCvc7hiqgwiIx7=^wN)5E0KP7 z48XlPK-=%3+~)%awozZri;HxQ@@bJ&m``kWNtQs)+7ufU9 z?;7u^Q-$m7ydu!M9>7=&%bCnkx4dGv+!tKY7fgXElcDz-zr_dNQY!_}2nuuo6fA&N zm_`OMfFv5CwReaLB8PrC?r2$`3E;g)**t|vzqgQek-go$_X}No-?*3Hh-am5)X>+! zi27smGR{j at M=zDHuve8HeO`0<_OHn_#%)Oqg!lw77%k>I at J6~qxH8C0pH$YI#a%LU zwb;!XJJ)q1#)`kU`2j?$2q^Bw$!bOA7_6C<=-grT8dIDq2Z5ly(T&uPq3_xW at p{M@ z{NzkqF+=TI(&zX~VX*)%2B2}Gt3{^QVc0TqM(yvoitXM9;4~IPXUZ9ylgbwt^Ak(? zN67pDj}DFWCc}j&kdXJ~&fh)5QAqLX#J at XjW4jK|xZp$;Z z`@MBO_H(}U-pysp`Q-vp`bV=Bi|APUm=zP)8e+Cnuj?&6&zW at 7GlE*Otws^IJ zxJr{2{mFt&hKc9b3ppc8`nZr%0@|)=9?`Z;Z4r$WD{QTCgf4XS{;9LkjlB z7>mTyhZ0X;DXzRy+{OOf#r_#>d&YL(XQtM7*2xmI0-1AYiA6XEPmLN+HGKU4#fU1z z{=J?4rLdkR=$`air0_|suvu;TTF!{j11exS_Y=$}CrN z)c#8p5Z=6k^s&qow2!Q*_u1|_aBUfisg?b zx%>xi{V)Lbta_So1npLys`)wNB}~U;OY*(@d#knb*~d0+OXDWPvAvOAT2L_ML`oTsZ`b&k<08eiA z>}+tN-8F`|h|Xlcn9Bfgubc zX$T@&(vVeh&>?3W7;;chQ9y|fQCUX`0+OSEAekWwh$6`)h$P8TWDo>o2qGfE>;-o3 zcg}j;yY6?-*l|DvZP zF<{YNP}~nrH4d&L0aNz29o{m>C}>IugWp^%=C=itymvqcRmEfxeX60DMC3Eo12rzpYsSVEZ>T{B5gvDpzPTI6| zQB`~Ra1U}&xQ!S0$_R5oXSS2HN{ta*&@`&lcCG5Vz{kS9sX9wGCsdiuZ<*%q8p5|c z9^CJ>>TQZn8wzG?)@pvpCG;FCefw~^#koPcBTCA91=|em4>0t5tF5JVQ#@z5tZ+EH z@(A%c{-&Xm at R)-85ltBY5ikPuZvArctOh5bY5lg^W84Tt79A=d2h&*rWRCcRJ$YHA z_UWGIfKPtAWW at 0r$6?{TzHGQNt3)&(Q}HE)l_G$bN`M1rpFK$@*3~bR5a-yUM-zM~qvqI3uG!tf~IWyEZYWQsjjzv(IyF>@)t&H&y?h^iZq4CgNJA8+^lkBH_O`3P;KVX7&Z`V}2uk{k`my;xjDO^VnFL2N4IbaVa z>0ZO?x%~kG<{_2q0OSKIE`q(OVUKjO?il4`9PdIki&W3lG1Ptb^zxg#fD?5T z$HO)YV11oa=Vqs{9Vgd1PKt|bFn1CFjA;cD6mhVrOR^uPv&-8XQnwkjz2BC>ns@>Z^K>MIFSV4qYBIY9I)=gLRwt+LDjVnvco;xj;2C zd9KAr#qAsuSG#M>Pc-q}L^vt#HJGD)uLipY@@P4CIaTpRqeP*C3n=;3$ihu;PGRNL9ppna+V;vfpT|0z1$C+MQM{EuR~ zw+(b}17tivc;2{e z+qS={C3G5DGI>{BvZ at hp_HrliCT0*XMiLu#(nnwSs*9I*w)26_t?jH&E6(+X_Enyw z{GQI-hw}Wg|BBVByH)w+Gv at Q>L{5JBO!(}%dfLFRu;HJ-yvz6(CJD*|*#L^Bg^g~87rbC~)~s{PG0jS#vkU_yfyRKkvA!d~bW>=zB%8i at ue{g{+E!%;5TtVAf3l&HDGDvFh0nfAYEW zcL#$V4f6PvJ^r_!xgGph%+ud~=4CZsVOjt7Qiy|d&(cpq#2nUiQxw;u`jk$rEk~&< zWIsZ|WE_AikF&ul|3osoho%sT;Omp24tsbo!G0N2=}982v9pb_ZXok6U{!e*99?Y} z$;xsf$U at A)R5!j1zhG7w4{^;b-S=ZuqrnLQSm53tmbtPi3mN%VQ5_y`4|S)@1Zkd&g#_lM3Pa<|5M5vt7tyrLjxC)UCT at k;Xh81WVO(36 zlT2h+>&ZBN`9iS+1Y0OV?ID6sgjB3bhS5cY5{{&{?(CEB{3KMuGq>V5dzq zB8DD6tB%dPZ>oRIp7&8OAU{hr*nh$U4}=*GN**nK*yno<>~2f;DS?c>T(HOhx^=Pf zwe7o(dgipu8T$JeF at D)1>w_{8ZfzPrzA3_?re>N1_Y6PW-a1qX#99p&Mcdh1nqCFhx|w3$5jX{=TQ zhTkL4TEeXmARZ3{pSyHqTU8WW|8SL=GpcIAee;LA4cj-6;#xQJ*1|sU+dB52B2Ui~ z@?AI%sGEllGpk+j8+nQcqR|GcKg4d~4w_JhkbTU~o z9PJp*Ef^e2AA*9O%>n#kciiy9<03GNY>27z4F6SM$_9fP35J_iih{#E0K$?1j?qb` z2kHx)iW%FJQR7x3Ds}TOo~2 zJB*``8S*ms+CtP=fHo^5y+)9g>7eP*ogiMrOl?5A2L&Nx?pKGcMdzAfktUDbGtuJ? zM=~I)L?8oOr*6EED_DutL0%X~ACmzjM5?ZfT1y$AM(%>xg*vfhz4TPkd02tD zEZ!fpyVzXT*r}T_eY)@T-4*)B22jz;U7kZ3b|bz3Vokt78g$+nx*i at pm#xl5Y|v$w zr6?cTpR12yHS_@{;wVU>uePbh?6yvAXMh*mweHmg9aci;_ZS=^(Qcg-JXU5D^bV at M zxt4R|hs48G*s_Z!5+4}>VdfuNj9vRoT3rp at ANOm#GSGSI at G{>(ysq*1?&GKT^{m?Fcxny&BG96C)e?)2kP=Eqp1m>4`*DT7YB3Hri z!D8ux%+Xqv-$LeV;;I*Q+U1*R5r(o5jJcB9Fb(?h5Q6Kr6$94v{shy-zMvcLvL0&? z at 8D?GxgfVVRXerU6iY*Z(G-9NjaK4q!cMQ;=D$d_>#DC-TNMtv>Ct9;^rfoI?W03N zmUe0Dj=|nTy}*zY&_VX8p|rZrB*+z4yYg-ttRA^&x8Nf(`uF0Rph`3S6H-9mGT8%j za^bId)Zg^1VkCaWz5S+VmF at BszUDVQ!<6A)x)If%xe-xtiVXTw;Rx5}GM&&rDHUg0ogE|x9DW_v(y at y-BF&4p- z at 16Nlug2wI4YzE8esAk=wi3qa;LzDYn-HPQrUQFkeGIg+x-hgZXg)z+g1YLpPmQT6~q$bi&^-d1Q!^*ia=&W7KWJr&EBaWd3k&q6 at 3 zpLLm&ZW5Vx12lLNI8vpss>}w)bV^p>2^C3GfMj;)2`oO`zd z_FUwIQL|xu*`WI;gGkPejGyVRzE$=L=Hz?7a>RVglZqbxl at sP$o>aEtR~#_k@&vC> z{Mi=^-?H>l7Jhj;H-C0I&lHL3DDA)9+s`hh-zNs{3Kr7JJ09$l-qacMv*3ZXo4OyIN!_+{0<6HJ}!>D`s{_b$Sntpi3ze4;*Zo@ z&T6d5K;}n!wI$>uL*H z7Hcl0q*U&5a=`(iDjaAp)ymU98SY3XC13!DLyQ?N83VH*fSiQptbqbq5aW|c-N(r( zUD8m$o2*0!MauG$q5lS*l62Qh{@DpV(Hdma#F9PW64b`Yb=ox?siOZ8+

    Bfw^~eI6SalpPZyE7;Tx|bgDmnT5>kB*I#vZp z2Yycr{V$e1VKPfppA9g*!VS2}?zZk&n|*k*u{21Lm3W>?sW;XbPVF8X*K3zUY;;tS zuoQ*bP at xk00YVZ7Z5>6IZ%{l)R at tA&Jlq9auMpHtqvSi1r)Rx~1=AQ4Y$ql8i~-|H z{y}=>VlVEGK<<>Tyb=<-^fZm0n6Fmd01DsWNY3AdrxI{D($WkN7FIBPxzJNl`+5R+s9<1q=~>J7o1FQWwsgMiM^M84O=AV27a z^3>@}IZp}$>ysWfaC?ni8pp0sGfy?g=d9Ty53SxbtSf3Dp+;6*{z zpY+Cf6I=QEgBt#hkQqsK+E-rg1R67uB~%q$ed0ohAMtL(-z+KAcE*s2aNS#V=s+#4 zXj6>$wWn2bJQ;t-cl>kOHQxX6w1qP(T)Oj3m$)PN=2Tg}U&hW3nS%mi^~m;NOh`N8HM at Ni&Jn&(|C zi at a=lFSGY^nLdXpWGNWgP)ODEzrd0f{rB~=fSl)aRFB7zb+yy$E5`87k(Bf~56T)s z7HQFb_W`M6JFmR`V&+^8I{Gh;rTllU+rSY*yw-fFSj#xJe`^J?C7UYoSp25?S--qf zf9>8RUkCKP*3Wo4$dDk-I9ZgWbN*YeE69X?e*N%uQg9CAo|SY1R^6xd>z?_Z!}N(6 zzn1jZpyu?Vty0l85{i!H=8MmwH=oAA#AE at t{|z1HM^6jiV|f$U4-N&$8ryawnu-Um z>2%fYseIg+sVPXn!46MQP6+W@{MgszBGVj-%Zy) z!cMpH)k0`3^M^5`UK)gAFHj3+u&w1BTud#X)?Akhowfxc%9Qgr(038D;nJ=oSKgniG z(qaDAg?rfg50cW?Et-D^_naE3_nq0oDtiJ2H-QJ{(u=9tcbl&_z^X)S6Qxu3&DV3G zvPAnJ?9Z#s^dGgVP8?9Hm87fOM+QK?fB0uCf0Cjh!_Syo%8Tu)0af z$K51J2kPa{39p>VJVI8((bTi$SwaJ>?Q+&w9OWe}(cw&2QAsMje3dvDH5Vw~K#2HX z-k*mKcMMv;rR;w;jDAi<8#5orOs0WYIcaxIUtcb8 at NwfO{W{JyF7RKY_}z at k2EVvc z`mVJ9Zl#^^%Cp6GXl at 0@(2j9#CSZQR;a9z0u`=Lgx(mHH`po at A#dyh)$Q2`^{9e4LKEAtQs|#R>1Dl``qRo zTtbe|*Kfy!3#&MA=-^==HQ%QXnbp+&+BS&Zu8jSP?Dt=0O=lA7t%9&>XmL!*SL9Fr zlHE0_`tYc=-*hh*K7pU4!1$9CMmC5yx|81%Z4Dk@#wt`f9ad!Dcz#zvHOnjeRpt*h z<_uYv>WyJo^+|EFH at J6qHaX~EUYYwEpV;n*;il^ULJWf=TVm)6{rzGPF5MDC$!>$N z(fLxbAi?<@u`PRUwcIkk-AR^S-i!wa(fJTxc-UU}rH0^p!>G~b8%xda=qfqBKnOR6 z{gcViK8KKudD1!ksUP&Ygl-ytR`B%s7k at H-7xjw=K7T{O$^St8&F5cHU{B|?GYDC# zZ-d7(vwSi8AE>d&$r062i{-%B4D^|B$@Bnf0Szi0y32hI^h8V2JJBFB2QVFK3W_Sz zknEs~khpipLA)eJ#d>RK{{zP-+nUmMg&(9W$d^b-^*KCW74Y$=u|Jz&HA0mrSVJC$ z)y+zypWCv&&j9uEq>!r at 6wue;ph80VmhghXm0Ltbku?FaOv=viNZm|5d}B!G%2d{S zS&#Vi at LYijlLeUsA7FpUhD(QObz<(Z??^~Zlzm8*pRfy z^Ys~op2I;*UIDUAH%6IS2%xk^34pv9VMMjVx z%*x=47o(Sn_-QOk41q;yP+=d5ZiW*M(v}K9~*^^z158=Z08dRZMW!s$JHr;Oj?a z9dRJ57zqDZ5%yxKMsYbMHjNZv2@|k{(W2nY4Pbpvz;BK&HihD0&VgcwT}8A|$@OgZ z-YJfv?SyRGb4c`%5DMZgM{P1bBV(vWZf3AlsLB(@)~XSZ+=r&`K#-OP?-R5OdEfvF zNC8Nc9L_QyK!buFt9e3#(|mdqDXt!4XaqPDiT1|J!6tr&A7pYld!9P$edO;g1JJbw z^luX(HD2PD>@x+7PadgzZ`g2n>uVcahKoYuN}wTJFse!5_bRUbTu8e<=_#WV1+36S;_dU)e)LF%^4oXI(g><}9yZnK}GWx>RijjuuP41@!fI2mn+s~JWxtrW7 z>oMLfi)7d+V?7>6F%YYT?a@^2 at u;=^oz0t}&%D3dJ^QEkUSYq9*m|#JRoZU4r#Z~= z*+oArDQ~9Ui4|InK0#uW)bu|WeA|1$Zgz6wita8y>l)b8lUW=)<;1gTJLSYZF*G*J zEdSaa0hr!T&>8$!dV1DFcDOPXgMkp$cuA;#`Rd$JU at Ak z^9hgtb)4$6c3&;mFAhz#3$kryi~FVX_KeBS8!q|z(t z^D3w>%&}oGs{=)HY9^FFT`Tt7rD5Wo1vWw4dWfLXaR8N^iDf^q-usFmsP$`OKa|oj z`Nc)}=aUm^*Qv4Jm)Rt5Ila at -;vt1{-R8}5Y#l~+#Z@&oSV_#rXXwpvB(-jyr+le~ znNOR3 at A8Cjt2f#-Qrmma&B4Cj zSL3Cl-;&VI={C3Q>?4)6RW;PCmr at y0I!{Za?K?OrfrSPEk zCVzzGkxX^%2515RyQC*_nw_Uo{T%mH<#6 at Nt5A~wI^jFc2>>?ZyL=JTb9&=U)8Ml@ zQ9GX at Y32y+jO_;93~H$uN$uO&%9Uf1d+`bN@}X8ZGT6dZg)E+Wr(!fPd@}B+xygFb zeVudM|3h#5MXbQ{Sgc$SFuY5ikA=*SB8q!q!dybPQ1D_L#e=Lco!oSk4 at W%tr9soI zTy-bDcRh~B>efpEJkAzS52BGSxeyj)is9vCZt>Mj%EJx7ba}XSEAmG^*Wxz2&2D>wO; zak3e&Kd(0V+ppgR`Tszn%(tuqe`KA^-(I`WM%x7Q)k~%7KNHN;QMNNe{8$CAq~400 ztEguF$rYVZ6Zpv${f71Cs`Qt5C61n;54p2Vv0sqzlLT5BtJ at j39fC$2kUR}GpyYkH zOPeg5>JP}rZAF<`r~&|^t^_cP+%!3=K#0A2)l}!HIQMXx^%YCopO^i%%b@&FjjQ=% z!}=3WL2kEo7 at M}f8o`Bws=?4P5G^97iadD+E|-E!@E9~b!by>xY~J<= z#l1R=*qZXo{$`HEtV`#{Y=qGnfNJi!%A0pwH%^ym7pxvTLq2w%d at Sl+depmYoIo~C zbju;|0sBu{7hL7*Xf1ZSqgYLMgt}qMx!!!^xHgk^D;O5&~;&@ z)~p`)NxnPRNv#}M7K>2LrK=-)<0WeV^tL|$u^J1xN7Oa at t~niww9~Qu z?V1?|(-IvVv_-M at AaZu#;NdB!b03*%tv6C{_c%3d=R~N&4?5m$mXS(-d(Ps;^iuy# z&VwkRw}68vkQ8 at 2vY(ylNH5eYSWpe%iI+XVU<-0B0I6q>qo(y*$CQ&^DbwCKc*gic zE>MoLy=2$*-0Zea;e5fQ=a1Kc|)ArHsfwNo$nk$%pg?d*kTn at LEj#CmK8E^t?qcBYeh+!{i z8#L6qR}pZGt at lhTlJS;``q_%T{`{&{*%=UpEjNv>yNxb2fFLrfbTEzfRbR-3jSPr> zJlG={Zpfd_eCCP4bEOY&Ob4DMIBdCs!lYQu262W*YF68TDdUcx08=Nz#%IECe?39^ z(Q>^s-Y(gutyhep`bWt&67AQ4+9}ySot-&XZBzEwk*(a9495gWpFv+;)p7470;kMw#5;ZTroXDqWh!qkrDjc5` z@)9$;`#38#qDH_>CC?Kpc&QzE$L8(uhNZKseeL_a)E_(C$tJS6stS>%Y3eC&TITbw zAB~0>A_1*!$AG0?iIgQve?n#iN4^+-U9p-SKyx_&N!G408`>^3nNtEd6%Mz_X~^w+ z*e2hC$~CM(nbl3HzmU~2OOVs`wMd`WPhO6r02nF&JI%N@?0zhz-JZeH0$)+-B!UNv zMu4GQ;Xq~FEukwEo`P;yMC=@O9x#RDERC`PD}orXcth#9;CiI&nT!MKIwZX6EB!{t zsJTWojU%vFz(Ol7JpU8E@?Xrwyrw)-5(j&E$0$v~9!Ve(*6%%VOYO{13ir(m4ck#T zLZ)g!^}PRsQN+0Or5i8O!2Hos1=HN_$cvG0=|;W1O=e+A%I4_EOT~=acLvy zypqIX%`W*R5F!Y;&pNeavzDJaTXVguv*btnyvG}sp}hsA1j`1Gg|p;hKh~tDzAM-u zgXY`XazPFk0=TU5`!@yTuV&u>Xx>La$|>L!eIYH-&Z?%%_N3u}?zNy+4Q}_LlnBOC z*}@x at Iv(dZHCZrXRM zfroDM`BQ5jtxdy<%~?w1qQjBu!tPqRG62E at eK}>UUbWqALa%hE+r%^3l5Q>hcQyPs zsUbRZ_r9O&8WNqi>ly}ZeyVHGsoAM(*xB*lRq_`qxkew)Q1w%S&EnK{f{j}0=LB0( z{!W4|`@4Gno77XI@=svx{jTo6Q1 at q}p-(fwsXMucA_|U__P{+ySAK+Sq^@u}M1yoU z5cP2`>f_{un7M`OAcq(VwyPI}`^4|#;6B#beX}$6IVScw3T_Wz?5F1tOCR0!0O=+Q zRtHh4gJ9vFg7_Jt%ChhLn$aS7Ieefp?Sv3ujQ+lT{!QEGu+&%JiNCA+FVubY-Jp66 z*S`8Hq4`=_qVn!}TFK$dX?^U8E`h9slioEu*1hK*!~I at x(8}nB#$f))=^*(fpJTXp z`mbp!wZIWsp!w-s>nAr?W)YFh1sY~9Xs`p&P}HG|qP;mgXExjJRnYF00Xu+$M&5cA z=55wNEB$#H^TkDg6<0wF12IM!(l^2(Ij|%7{Z^QX11g;z^ zR;=N{4EuNoWwRgbBUZl34OSFofz9p7`fc!aNB>3JzJt{LcLNN}C}dE;%y;XHpj{LD zpJB~6_1&>ZO?OJmgP9@dHK{+v z7DS6HnFv-ygGbdo+WE1(*C#z|L~E|Vxs;B>Y0PmTLXP)}UOiTEQ-}Wle!JkHm+)fl zB?TesryVkCIzC;Vj|@wb-!GZTM09d6g>OZvSp(gPEGw1Uf$D>3v$Bo6rfASi{zf6^ zVqE1C5L5;vBcmDX8}#A~xqA5I;CXB1eBr>o89=xW2LR;M@|gWkQxRZxrjQpopLe$g zk$pF?WKVA*djXc{zdM107_fV21$*d$!L+{aS=G8^D(_cS0M80dS+IBEWY*!o(M%@7VnI$EI zI(NDCHnRz}pHr!@&c(q5-2k1^Rc(gROckPsT$tjs>FZv^Irh5T;B2HFe)?k?+u`Rs zD3ocsZ-pMQOXp8!lKzubWv#cWPAGD|>?X{*1~d9CEu9}VCbN* znG!>T%*H_{&dEE^9qdd=5B11z%wCXicvluwL$4EK92i3>l1m%g$_9|`4G^lOx9EF4 zjAqlBy!iR_;yq2-WNvwkr>-}dI!|`xP3#(@{kAreZ5YpBUjxKdmRkB1Zua3#1M80W ztVB#>>6Nlu1WG|VHETBLek at Lqnq3kU`P%6%$XTD?V1V_{==dUL`gkz61^5O6iU)ylXRizr+U5~_8P6xtg> zxdsIN0omle%)8?8xlBPwb at 9|Ns%MkHAL7TO#W at I=HN}H^l7a at 1aRbQcx_Xvl;#h#N zY9M2h?4gnzV2T at HO7<>ct6l*65P*`l->=)zuY`G^gt-T!Pmb at 80f>D7ve?nt(`wF`j|G{#9Jl7rizu3`a$4ZfH;IBaXW?{XYMe%suG`-)YSre zK<1{b^xmEq^7yfruq2x`S`Y at N5jRFY5-hxl7x!&bWn=pRqgq!dbPum*6V;S z43LI6DxH?BH@!MPe08oMFJRD73kRpef%VxrzuDn<c at vxj_!>z;i2Rqo1xQ6 zENHr1j^1Yjl}bQG*i(AtLT&JTgi1N6f}rJ6oY^>p)+mKg65ECHZy)9_i8wV+m;Lh(EGS9Urs<}S}aYXQu)EKe`$%CgH%L1S)yQqA?bo_;lK$?ZDT=Ai-tR_ocN!mQ-mX z3vA{-&zAuaN6=txNG1`R5xA26E-$B^mIYflk{rr;VZ~V?*Q#FZH1y^8uVjS2X53jt zuKZu1k-uWRPZ$aLe})r$%{X(IuV9P&?Xg#%#sBtYRPk9r(#OeBc?b!Z+GK3~$cXF$ zSq|F7q<6 at iPacWJKoAXJCpj`L$fHrOa!vD#e6m0KPJi^Xg@!hO28{&?x?0zR4v!x` z;S+#3Jax6qIxpth1BX# zWUDox$hl z{t~!3vHXGZOU31W9a4b?v`tk6X^m>((rQ*w#%Vw8jCw!Lgl`-E~i&K_`QYXb8 zNs7e?pBoT%tEXl;ruwsYEZ##UK7hD2fT;GGdE+$+Sx14()ciBg5})=RlkYpaa=@Tp zP!VE9WxVYk_ja%MmIxgz5#mqgBQekd$8Zf6@*nnkH4_?f7ZS)OcP zt-ApqBe_1!U!7m;d|}(!z0wZx;j at _LT@lB){|XcI_fD?gMkiOQ?tx#Sx&E#4U~@D$ zHsWe>(WXA-ar^KfQ&AS5!VQ)Fp_9RTJrYivbeLQR442SA&+VD1r)sjrgY~OYdxu5v zJtTwKin|9;5F>X0g at v?eG3_IBP)##hh?D0axknj0Z&<)?`v~u^dMa}gw1J;%hKY*e ztjH<%Y$^7W6*cZYFpT~0EQU9BhM_>uDoluFA076pAe zk|U1?bofJ&V=-&bqgIzUT>REOr8~vY6H at xr(}o$q-dWbAXA_!=x;?eH71a zc02xZC6p7#9VojPsZNX*kB0Y-W zWwi%I)eNk>0KSOb3W40S3RNCNy2TlkdiD*5+}G2JWTa#W+{9O=uZG#2~Bd!qc^ZZgDMukH=9+*gKZ+o2HcPa at 6U45QRxH z_Di>p#6gUr0Hlx1SuErfoe0Gxkc!m+4z$HB4mbH1EPq^cHZe4K? zHt*r4b|63o;h?v|-ky=Y*>$oav_xdk!cvOK7CaGRPE-Y at zD9&wLFmefKQ?Z`b_kS7G-dmqnd*58{x3wM!j=-KSP{^`P7dy0hz zgaoZ;XFE|HgO(&h833ZA2FQ~}(W at Kqjq@t?>DN37Wb9r*4FPEHNFDEsFux^jV{mGA z_({cp^E**BqVb4JiLIQcG)dAi!V&!sgYQTnm@)}kaAOF-*tkAk7z}u``W^?tTm(>> zxZ7HX44NcL2&c*p2+rp|RGH4Ey}Qt*5q8Tk;hiPl55tl`=vD)G+~|=v!OR6}@nCO& z4SU9jra8mzIH*MLWK8aEN=AgB;F;h#3jfR|PRrPP!8tc=x@(xvm9QA$H4s?|cXNzV zaUf><3Wf74C092^y7I!5!-fvIsHo7?7nw|4v-It6=3v@}p||CK)J( z6WBOAE--%R7rBS0&+u0z|ET-#sbKzprc~JeAh{eb$^Yn0X%O~5dQ;kViWAj`r9lvr z)aCiQ0nF?n*h^?Io>(FUe4c>R*uR|=+-7kp$LT%!JZ)Ycpa;_=>)6c z-iFsd1$b#isVa91qZ8%Oap9y345B2lq1%6O7mVKdgGNB+z+E%3*URp$eDttMt^D<^ zs9j}wD{5D#hpu{H-fjc at y|*Ls&_AGK6y1=80qf(t#RdCEFS#&w-lI&s)UJl(l4{Pk z5!@hTXU^3c?(f^RT|0Jj1&}Q6d)#Nhth>w4rt+mmcH<|5MA8-9!IlFr)GiQ2_Rkwd zGk=OLLN;4 at GbZpWY#qbZ-i-%(R(@_2FCKEuP1 zYkTzZozegCAorIal%}{cenlSzx~+BaLtD<@iK$7GE$7cBkj})5R;?Alj2r-COYOQD z(~Fs5mZ3QF6YY$lX1u0cz`z6r+25ROF|^zM^p>kP=xSb(tw>`OzM`NHVESADe&Fzs zVB4p?1;fTJ`Q~zA_ULEGXmDUf+*dhzAp?|^*8T{uS$;3gtcfhE^bk4?FUMRLX#+y# zC~!YBI*)G;6}(z#)M#eOn6{`KjRFi~8Q_SWy7Jo~M)LNhA03aJSszVFiM;QGkp)6V zIG}LPD3>tH+k;x*_a~u0KaSUa;RZx}m_Hc&ii8)fDz6<9cEGCTNAPp3I~{wlq?{iw zks`esgsrv-k=!hTKzhYtjK3n_xtJV!DRBwFhaWLc%(iwUGcnBBgP!8uasy&fplS#} z>Al(s19xl)PaLp>jVlU62?y&fRCwi7wKpW5Fv877%Rj`6T(o-#y!iL!1}h4Fd|m?+ zCxHl`XVj at G*VstucXh<~U$2Znc5E3N6vUPNM&#Pk>ldyfxzej0x=P-r7VCs9_tNYF z*kdhN+41zMD}%lCOX;(l3}&aVYXM$$E=U12zl>J at iBs$L{jR2#RR)7a*rgSkO(%)1 z(Y6k?`}fi302K>T+4|eattBpDL*h>xg6DG7{FclgSQj@!O;wQQ?4k zuW94`qigQWKc2BQ92 at 0mQP(Ut1dLTBFAT0wtgCdPKnB?Y3{r!W%#Ei;+KF67>v9EU z`u9=b>Mk(S at 7~^P*~-21+or)=b~-i`x-!o2Pg@@1n?U)&X6x9gP1`zl)NucK6R=&9 zs4aU~3P71XrhEsInA7AHumjmujAXOs_`OM!fCDkO-Gu at T&4~ph*LKNUH#DQkViSNP z+%G66ACCr8NIt<)d>8roBM#BTszKQ|jfx z%=+6j_yaNP0RXmL$h6>@pJrPmKa3tlr9NxtfPk!U}wBTd=tnv8$^%ta?ZJGS7RwTgeS>hrQ2qiBMX!*bM7 zsiWBuW>p$a5q&A6duT3{ML+c8TK-x_?613a{aQM|{{m#J_4OIfbSiIb1$hvU%|YD1 zl63uEDyE&&>F_%xTKyNnE%9(Eha%T#;^Ub^JXzV;i>TF4CjqAoMSDg$6CA%3;k{{FEO)5 zBf>!ze-5187J-|GYz5vduEt61^wnE)DVLjQ#qbMdXdEC at TfP*dz!YBy%N5ksCeru2 z`DA2pjKZjlE~yN%o^c{e89iBn-y at Q#D!UW?aH>ZESq!wP*`M* z`HB8g8ND^=2F+1F`K|w4Gt*o16WM?*q)J#}%LH;)U6S9gDOdg%XzBmmu(x^@c@)>} zF7?qJtx&3GGWV;36GJj~jIKztB#wOltx28^M}DRHK!ob(<7@{@XX zCU=*7*7arp8BNX*%b_>`!)%Qg;s$8+hJB6c6UC(l%cNn$@q&rfo0s!2XP_gN51)GK?u>w|BR|jxZ)scJY}TNG4ndY;jb# z_Y&wL9(#ys2br7(WDKjb5;w%mSkLIY=hZ at mOh#GofVpeym?+DiX6HM!zaV8=C%II- zO3&E8UIO0$X{(e!5trFrV|^~>gfYGW{GigOyDAE|*X++T_C0d$Hr%%x2bF?u_+A?m z0u&Vw6fYF9yj+#WkzD3_iH(b4F?t4|_=&Z`h-Fu|+x(kU)PA8_BX}HC3p!1j?T`ax zAf-2vtP3j>>4p093nrxe31}7T1~8++vERkxSXg#6*xxoK)VePakQ?k=tZnTQSHBz0 zT{F8VypB5J;j-NSXmGb3G`%=a5*@-Zp$SPn;YWc_~!=CAL(f1|GB|4QK9yj zE}!F+OID`mBo+BMzEIsx+{EQfXinj)Uk_0Ez~?{&2EA zrNWX at 9UWf8euM6iHq{h38e!ig)rX3}Qf-JrKZMDRV0U{yf;nO3TNI-g-E$#FSkY|j zRs%gIKRTJNZREPXiseHo;u at YgW3NaL^A88{I6F|O>1$Q0HT5=QHHO#{`Z|XPJ=Ux2 zZ2Csc{6ExdUUa=+ at gNsxZL)5e0H6}2cF{)r_CN$s*jOwXRx?OI%?F9vrf4M}_`%3u zW-6s#GP$TG{!$3rM)YRszXBkWOO=Xyg_%RpEl(*4eJb8qR{JuT>;D> z2Us=chB`=QnPlZ>IuuOos)=jtjGpW6s|caGpYSS- at 50lbGQnF*)Rsosrtvrmcr?VG zd}ukx|LAnpjd=y%55^ukCYrow8=yJY0ZA?Eg3Y5+Owaie+}`TVV!< z4M)Z$CHa)Z2r%<;LD=aI)T{8{RLt!Xh_}YC`07NFU%d;kg;J6Yz&P4fUz=?6!byzx z1C2We5Q`_c0Am6m3Dhc*q!;R-IETK>)*1a$Wkyj30CCPP(oT z`XDV(#G%tSFjK;b?M$QAmUGO}@G;Qv*_D1PJUuhXdMe2r5|gn`!E2m~j^`iTCw0F6 zR8MhgTpt98Qm21h?La@=j(Y*`sDRR}mW3p-0gmSM9rg3 at logcf`4 z$oo`or8X;imAiF#|G%TLOTVHv^Y0`dZJk*q-Zk7N6U3nEQ!xEbTO(ALNi2!fDHO!y z2_SRxkvbR0GCPw=xj?}5a#~_UHPok49SuY?d6W3mVy(k`0HP!VT$Ys* z+!uhbYS&h;yKHG at O#jFlFlBDb%p-bW4oPEGAh})>*Db29_;k=?sjGJpaaEP;0A0yG zZq_iEl2!($F&EpDW)X~ka1HHmwg-~=Fgq<|s7Yd}`UxwpqM21(LJB){H)@_h&>bZ?)tp?kT= z&Qk^~Uq_}^7H|F2$NX{OFm} zRi%`azA*mE(QQ~S`5Lb}`_I=z4*HMu{uI@!55K9N8ne$$iZ?IwUf(gLLf_;XBe>F}8@?6bNN zTiFlNBvEa)kO6}Pfhu3R(wsq!npnW(i-PdF9{jNP;V1WTthF~=Y8fW(JPZ#IgDA)k zFn^gzvxR07gJKCKi~X3PI9lMsA->H{I!RYJp+p0jN#;+&(?vAqDr7hsJWxM$Ddq?x zrUqubBq$@et76KjBg3*HIOV~myz;%pmSl?vbh~0~iHNsq?fI#YjE?kTw^||L{EDo5 ztEtEr$XgEzbS^l>Z|VqV at f5-fpVCl`;1$KK758^ed{}7Nb}ET((p{1`N}Hy68%42% zh4TRh`FG_RkcKKcX0x;U{9Fkk9Rud!rBF at dNnX#}OhXEW&VZa<3mk*iYxWBd`Z_Bj z5(gT>+R_AzL(8}zx?8Scv#(LOOpL|-%7p7W{EHDhE_ZR1M5 at lmx)SN{L6U!dl7IH6 zJ=34|+!*t{G4^3&xh2 at x=nCEbk6Q6JW8yj4xaeWjrxiX-x}E+;NboGz+~8Ly3o)22L!A-HEvEc}E%tYcvMTDA8(eZRc3Z z<g4T2LD|(u!yRCI|q}*DEv}5K3Kj)_9-l zJojO*@274?o3j|Kfar0m_3F;`iXga05QVGKbyp>T#si_QgPhrVt&7esVUMlC?#jEH z{U7$;JFLkr>l at t(gc3Rg=^ZISiVA`>LoW&>Arxt1p(-jEsx8us^dizbgpL%c8akrV zQ9wYd6s3y2eF at LZ`DW%k-<)~Nyyuy7=8s(0ecjwQ_bzL%wSIfAwSGa3GhZXFWyF1+ z64yNDJSug+uHTF%FR{G}sT$QEGwIlWU5*iOM#e!D!ph?c0-jKri%TmV(c*n#gDFoo zy9Q7Cq>#-2r4Tci46#(YWEKvyAk>Z%>PUz7NV3)p8rBR6z{i-csIip?v1aCeDX+Ei zkbc at i*QKYLMbB#A_PvyDjlQN-A8^gqYN7mV7IKgP{rpEa<`fNkpr%TR0GYN$s~)^g z{`CNe1sSU1b_$Oa^V&+aSx%n&L0vDpYd36Bg^RwXYGQM{k_<4 za~$+Twps0%Jj`%dcJREOX;y<iQ`d*i4 zu|M{*{f)KdmxHlyc at KZ>S)6oj{CJuKvc*9)-AXmx=R2EidiF^|_D6=NhU$Du0mN7W z&^)t2Po_j&lri!+QmA1tVm{{Y>NDGCbtqvq!c6T0cEjLo0{*3*fYR`&ts?%#;8{Fo z!d}qF-T?3BW#q)4aJs!{?5f*C)`;OQ(VKVNR?awOHj6%;nCnteiXn;CJ>s-L5f&Rl!UbPZ};JG=b at YSU7U(S;I`hO%8&PHUx(zSf&Cm%s|@0_IZv%6=HU~ zdn&xtjZoT5ot2(r^x(NHy(0-Pv_k{+;ASm28 zZv6t0xl-a|+QJpQ==EZ4$!ah{9&T_kwVBCB)=R_HF4k%&z2GZ-9sVFuC-AfYyc9E3 zs at mlfc3R++ZKP2|BY@`-Ah at +;V^t^fTP7v16Fpjg-mZ06U$vxaZRNL$6{ObMfkHT2sDlU%tl}xoZhRe7a#h&!(I>j@>t! z8^ii3EgN9`F|Q8sm^rl*pf&&6l at 5QSqq(5?N*Hp#rI6#;+AUkfGbw{?oaPIM(V!yh z at U)NC(PUS$)M|XBAGDf~Jkgq8=&&1;mFmB@*PA_bPf{0$l`lbc27jOt|vKj@@G<}cP^4${YF+yBsId(PfAiR ziBg!0+jaoMa4j)3R(C%C#mhF%`YpWWREiXgog{rY2Yw0#qw1!wB-@@EH#D#rN;x|t z{y?C>xW-twj7ix5=hns_L4aVX0Ho`#y_|SAad`$~#&xw*R6~a`d=6BQ+73uGMiQXk z)e{wnONk5$)4bQ1+8?HCru)~f5%%eqxV2q_Skob2iXDZr#u|tlCtZQ6+>68LyE0F; zCAEhl9>AV6-tn5en{@2F*j0cnCP4OHjb)p(ns1pL)ZB}-VKXeu1yCd<2Bb<>O>q_l zNuaG@*LrLH0P&VXq+>`=U6$I1`nZ{+PS!ZR`35=;C?*kLXekyLjEeL~7ICj*D4lLY zx;myQ at U$^Ir{SvINKD9+SK at nWZmQ>IM^2fn{%1TASrY^Ce9RN%^COY9r$Rs4Tz&0S zV{el-IVyKXrD*y6T?>+HWHs-(^1ReC1=mMeOi|xHWEDR?JqJ-D0`8I{R_;0nefY=O zi<5~*29o9TQ;O%bOTkp$)-!V6`EGYQ+zc8%x$3-=h>DZOi8YSi_EC385QlL9Q(*Fbk5hX2>`Zee=GM9w;zPZm`HLqL zK#)NIU-C(}*Q^NOZeJU_?Uh}(n_ZW6Sw=;s&8+!&#fnge;`!|uCOI!dcV|{ml)s+lu32TwRJ4~f zlhucO_g)xKpDG7*2e|G-O63)4Z0A5>D4Vu_)>^N>In6 z3J*my6!kjWuxhZkij$rxkHZ2JS7T5Y-Hg(~W&|@e{dy z at 9|zdp0sh^i4>^2DFRy06T#N!onXSlVYt)2&>6b%n^rMzG0%b)6))U at QPsvF^!qsamI_QzV|K{Vbe(sNKta>0fqd_&qVM=;zsjVt*PrgP72i55*zTxw z$b14p^Ic=_Oze%HrRLfFh3^j}y*}(_O at JMi0+{EAP54K-1&O0~*0VkZar!=D1TX?w z+^&8I9gDTTM0hhJ#q3ejl8cGn45WzL8_RWf5?S05GEi>Eu=d4H2WMAXh?`_6d(;?3 z70mS1GH$xwP~>MW_3gB!bBT8P2sFt333$qHg4Y#mi0iLTC;r$woMYpE)%rtIf7AMZkaK?VaV_`r82Xl zHcf_K&68paT|^XLcyaIom~tby5No9yKR&DmLzuQZp2t~pkGJ8y^)4RCbj@)lx;PUZ z=#VT>dTvfveVBz5C at cU}B6rq#m}vn+=|)R33UblrQ|{WZ zOH-$g}6h{LgyizKopPy3gEVZ|z7T4lvs6 zA|tPemgI+xq^aB#l@=&t)Ae~qfD*9){%z)K*_Q9_ZASMe-w!V%yULQvSb-!m2c*vT zmam5i)<#zya6i_18qG{gM5zd*0cf2Q0Ii)@iT=@~nskP8U-oXEE*?t*MT_`nFpN2& zp3{7JY+Tb`e1C)U-z5Y88%hTLZk*VE^kDy=&T$BYkHMK{zC9O))L!~9V>)R6BAVOW zbvW2dit~J2j*SBF!JH_Pw$L&ycQg at sN2x9&5AbR z7&V7*Db9&pvjx=Kj?`9z>Q;mJ75q@}n|bY4A=OsF3jm!0=p^~H>3cGcS%`%>G0BV? zjNaSix{}YnO~j`Hm`WmOp#hlnR*pe)ttnPAt3M&jE}_Es+Y(tpS4+xqI}>5#D at V?~ruUOy5`sCH^4?sv~F_9rA1MrP at U)u~%q6Enmh+o%W-dJe& zmRbcoXV`RPy5y6c4!*bKmpN+GQH#W~`Jy;s%|bc at S%y#xJJaqiXxp-;WPFPp at y!Qm zTyYsfOqXX-mzyx_N0`+V%+eGr1L~)btcF*e79_Q1=>9&--hicx)M#XS=tVT*O+6&` z?zqb5=k`1CCa-xAh5+M8b}2LUJXWj)ln&6ET)8r4`|6`owfB)y$6Nb>`tmd0fb>5THqE8HMOMqtYhQY+Y53YGo;AtWXY0$ch|#`7 ziQqF*CL43z5Q;hDwThGwh^afBtIpklvO*WUV zllQBU_#wV;56$0_rcn4ebwhQm?{=(!gO!=kaR`{ zi!Xjx6JXb2sRVw^jbtRJ-y3dd}|l+Aux+sJfONCwpOG* z7clPgF7oAVhU0-%r_2Xc^mtq65jR|CiHjz0{gG3?2^ABx-`a1J{sR%*t0qTXj*b)9 zej#>#A#@VebrN}B5AwbqZxFnKb#HmFo at Krex?U-~j$DY?QuROcW$3HG5cQ){T>h+z zQC4E`{ipM|+#41=n1w{7Alnew$v&Ao(`qeOC;+Hr7Nnf~LUzjw9=UghIJ>D>IvH!r ztwM7i>a6;YJ^{vM5fhCBtJdPKU1g_n_1QvX(uZ at wu0iUqL9Bd(tbDzlqrIKujjr at m(gvj=((9ft z15A(mp3L`3YF6d}pzsc$?jBw{WzRph9>ANHF&`w&uc82~;RI-H4b`R6E~W=}NMNbh zUeU9!rqojy922N0neeFthx+3nK24k0e2pYxlym0chAX}9Mc;i&-vQ}t5_FL<^0GqT zvxC@(5L`1qQZ9@^JD|DGc at T>&Iy+Zgm&E`tJmP^gm#B+5fhq9&$^>ABR{>3UK6vfLs}MlF2kpay2i(%yXqXWwh~MHCT^V>9 z_}}$^pzpKeS&(EcIQnz3D0@@=WsKW%zm;k`-s`aTANTlj(~wkMG{Sf03aJHYb%8V< z1))9%4;KpuyFZ(G^0FslTA(M2tHr**@LYTDBA6XNRrlTCatZinTP6y;ttd{{SQbX366z`u>G~^}l9p{=c(5{v1cY;qga`qu+;_HUw~hhc)+7E)3;b#yf7Tasj4;q*%Mu_hhxGVW$f{abdbTKl=T at QAllG2|Xyte=2b at ttLA*M$@8Xj__Y_@>Mmje>nmLU5 zxYv;nXrX$e{F%%fBa0by-NttT?UF3wvFK0{?k3X7AnB7Dx;SGMh at s`rdPL{tfT%;~ zd;4o*g~VKelB8d=QDb1?GXTT1ty%C*&(6D>Y#!kKz#dozD{3lZfHZQf#Y07Zp4SFw8 zYe>KUO+{GpI50m67(BL$H?!oiXDX*?--%-gMRRB{?udD0(7`nZ+-zL^Rjp-ZYC(Gd zvFHIbXZqdd(50_n zBOZCteXPrgb-$)z&+KAZ_~eyQIqgvNfSXv4_+6f%WhLVT&8rF%5|TB%{tePbp5+x at u)KX5a=+0s^j6- z>G at eMdi<0<7`a)d==WT6INTye26>#3!#1Xz3WnE?@aaU@~m;C;)$&&v|<` z#DC-Wuk!x({T}l7dH(eM8p`+k0HctdyTVORz#vGejWz=EyLCQv^sx zDWEyFd{DhCDe1j5i)ML&`qyuwyk+mgoeTl80R_5#?iNXIA?1VJ@;>V?k4Ij%w|+Lt zP{!y|D1MxS2ghfWtW^>`I1+y9YpW2htbK2i05c2*H1nf~hfE4NGEPWn12=asnzYC* zYfl?Ika_gW+%u;$$9*y-Gcr&)s|{ z&pC_a-aF1qY!>Npx?jjjeh3^W>rk?(qI;u>8c+Q~)%(KMO6c^J8BDK}VC(6?eXoX$ zJQtb`&aENoka)pwVI=@NK*Yb-H~9x^1M at WpsKb|$31UepLjZZ+5uA$uNjzfkd1&B4 zzK&{ptvNFEIeAR(-3gST6&XiC&e{`iarzW~_IMOy?y-Ge)6hM#OjJL0Z7)PKvsu!&fxfqZ`Z50l0f8aGAgG zSlaiDI2g*6Iw4;Z#Su?u>x6CdeIntYoqDvJ&xA+7sUwzjv(jX zmEQiNmEI^Av-{W)B>?O3-wYicbiJI|tGK3q?Ut&B@?!o=M~I0j;Nm_z>KQ6{@p#gk z+WSwqyd4bfN#HIgfU45W`(l;3JntyirE&xW=@m*T+DMT0L;E|6uy<9 at hCV^0UW!vJ z at z5&F&4G9lTA$V5LJnJ#jW5Fa0Tbc^fUB1SxXr|l&Rgkzq1d~06#xCT3(8b-fl<`pQYS`61ZYHji zrGy_{v@&~l3m5ga&U||Ye&g8gC?d;VA+sq{`qljh`@^&xh at 4fdB7i!oZW)xi7}Ud9 zL82Q&jkRD!0Vf=R`rz293f?FgcX6Ce1mc<9 at KLVAIrR^sjrr+%FIf$3B(L|M*M5+C zpP5rkc%F=Q4Z+!T4e`<15tG*nV>9oWbo2!j70>nxL+0_HDr^U-g&-FXDrHV2Mq0!H zwOd|cS2B!tjoEL+g_XOCyk&)weY7BD|~*Lc9f;Jg5VF5T^6Tyiej!Woz*~U zO!95v3TD`L-detI$59s4>Ool#jU>jhF21skv6w0b>MBR{0rq6H4tL-O;~j8(_RX0 at msl#_ioY z>#5%7+B@PxNiJCE~MAI z*Q+O+nXOqrJIA(dDX{8kwoU8sEx`FYLeF=>)jllKh{Js~h(%%<)2?69Y&p`V`buVT zeR6h}!*taWzg!z5#$Tz}a<8&f>esF{!G|(FOO??@f7bGL}6hIqxR%?Dj zI8}*Gf-$crDap>&Px6!3l1*9e3$-Udv&)}>U!mTxkl?W at ieB6$>qU7KB>0<KV(65a8 z6$3h5dr&sYXnCTP>fuO^+I2>Ns22w1p~=WTH^ICwM(gsy2dS~zXcRCVBT#!kVd~}) z^E53|G$-DTv%!cTh~LKK6-to=g|yOjR-G%?Z!$DzU2Im2zLp2r4+=w0U#%-PV=(sP zpB%mOJd;kaZs^Wi$o at KBJb;|ll!-$F{?}1d1`DL0hsK4Xls;GDZpqwx=XPDP at hnY;N|BV01 at bnl) z_ at A=PJ%1WGtHI2+kaUpV5%~Ea1O)==gV2GF{fFF)^m at VqZ~MyWl{O2UZ(HJVGS$SU zO^glODyi1+EKL{osS0-v6r`NQX=o9xw+cEQ zcJI)PgI(<{!!S;NQGP#q{3yxFG*ea4yz(y2%{($R*=N7vtH?Vu)^`?7GfErB8_|y# z2Ocq25fCyY$gg|o+si$=7KVM at +9^?Eh-NuXt9Z191L}#lQ8aMFPe+SiKa)1EopIVW|dX zSORhkcDU-syM#>2v>zl-yVS0q1k>Wx6`f#8dtzhx5$7hoS}42F~(VWkEf;as_-aiSpz5)=(E zUyv-CY)#roe-iRgPsJ9GAwf(^09T`h`SlP>Z*|0qd3#Tqhl8S94k#H1&=ato=n?tU zNe>NN%IH`aO`Fu!i)CbQXA{sqzJk5}aYwQRmvFsKP*PT8!HYmgDOuQk#=?4pjG4mD zCWZTOquDABLU?mVJdBwnp3wp9Hh_ja=0wPSr>&?!>V@~p^$5FVh4yZ!(x_?r~G zczK~f^H~ed zAl`T>80VbEodViaRuHd_4qZ(ir1!M9C2pqgL=gFax9Sg8|Ht1Yjd7xXQ%ft64?)(j=b2x`EqugB!fr8 at xZB z7XNrk;Ev^6ZRSnvrimcYY`t>0{p!rex7i=x{-|I1QBR_c*0*4G`g|ZvWXy&?G==~< zxeMUieb_3-JC!!|OrGu`yI_o~4+`kJ7$P50lz=`6JObC{5Ei3jPEa5`H{325i=`$j zfs+8d^-2 at CRHzq1lxnoLF#Q@>rbfapKRm|?VC?}7UZdm1a- at ziYt}l8q|wMGlPrLk zc7!B_DY3^t_k29IdGyDt$(s(d`w6UVjg}S0C~EGV+L3HN4dvyf;#&@<;+v(P at a2&J z{K{3(6IdEPR9cb+_hy at 0VLDc&qBsO_9xs(ph%ktQ{aECOX^d$!Tqf at gPhCcPS_*(6 zr2t=an^EJdE{P4S_T`~#8FgXD`++beZG5m0fG;DF>#?c^qBX-zS!Qse*npBnGhS0U zA)nQ2*SvdYhNXUmw@^?$%dgT!m*dZ>m}u0>p7)`?p6o!8#q+?oZuDkE-UcD8 at aGmK z^@$RyvGAU3{;KM7fCY>?>yDPB!t;K{qn;=sS2XQQYf$I_VA8LN*vv0pUR%8HoB74Y zv68ws{9gUgqdRGGVTR+hvP3MxD8I`=3d0r2$_g>L1h|Fn4^zGI)}S{n9?CqjVLmE1 z1cil_5i9iHJU8(}{|)Ca zkhyKVIzO*j$WY?^1x__Bm??p+P#B7|eU@%H>*e7BXm#m*nuWQ_-vJ02g5-8+kiDk; zbN8p*|M{I_ zkty#b#vf*+pC&;jy*FP{^hcgVX5x6Srf5!%PT+mvNL)YNO#n@<1FV9d3L#rw#LzWl z>Lm+{DLo5&8uOzaKQ_}Awo#GUHA1TOF2!Y*H_suq zs791ou(|?hT`mBjPJZR&AAq((t|Me^k^w9-Aja8z%-Ol7W6V;aPC08d^vecWjZEY~ zg7rOEBhoP%d+TDahbIdiVw?%!KG}x)(C}o8C`>+a(7?$InwMX|dWUK&07MZ9+&sR) zQWQ|7J(m3LkbDuR*3DED30>` zjq9(;IZ}?I8|{&HN5M=#_%38y-BJlH&SxbqMeAq8k5R~pNPyw%apL3rbfYw5{QZIz z`g2)VQvh^x$Nt(r*U0bgV>rk{!yDc$HVYDyC+=KEM78#EHa+2_XccNQEGA1xj9uX= zuYN1u(DBA8zB%PzR)p7|flUYsqNpR|&&gP^g!o9p7MD|DXZPx5H|GINE*!{W=8eY% z=@dt>K_()`hDk{U;ljxV);)Cn*KuAARs$6eUaLI;Vy^r#@BZtdoR-Z> z6&A!-Cym-sPBo3g4vY6qHjgJMhMDUa;zjpy?Y~Kh|B{rLQrgu9Q2m7okr%8#N>zj< zTZO_(RWS-|jFtMc&Ba9w3xQTL82ZZ$0f~;k$q<;gEc(mJ&GmQn)C+*bpja*kb48LIC%M1dh$r;-0jyv z_7M7OfS8%h;Tf{Me?*zb4OH!rQVt!U<~{JkP|%>Cj(){3Sqs97>-oug6NKf=g zB?%hk2f8#O76c+6sui^rE5o_k at 4swM-iC(wffKvyEZ-aZB8fcQCD`GBzKE5!?@mc_`8#Z$UY#*LaWo>UhvtTUh{yKc^xZm6~c=J zqJ^zb>OEE at UR(t}HVnVH4r>yn6sYVYmRAP>AV~W)ktXT};GT*DP09y89)-DNF!}zPs2W!}dET6jM2N#VwXa^&9|A{KD8vU0=WF5r$-^uJXJ2 z`c^ON*xor#_4ty}1l4F=vb6(b#H6IWcg^jnk!hl0#{41k0d^9wpOvY^8}$BXH19ue zmxe1=KTnnt)%_aDZJUKy5cGoGsan=_7!m_$;7J59JQQL7!KRO4NV6~C at Yao`OiyJit*1FBakM{~l0 z3@<3uQAzvMQT12U*#kBLU3BFAh=uc>VWn8d|4$Is9mDAiykBZnW@;nb$<+Y;qPJrE z(Y}9Zj=J|P2)&kPUIiJ)KAnTL(7Td z;;#}pE`I%6Cci)OyzI##xSP-G)e>it&z+Ud`@L|$FN at h4+@cSy_ z4`o6OOQL*JmyJ|BHoF*nbw-{)Jk7sP$I#wuOWGTt4fGT^bZ7oN=^wm90(M<{WeHbgU#8anh`hwC)ldIJBjQf}*wa at xs7C}L0V}&ko zTD^Si)w1dj6-lu+6F}ync-&g3Tv*bY4X`PXg<#1qjUK}Xcx0=z3=hbC3rNneu)BTEzA8he zGaZ7vlv~>$KV3zucIFwk1O_4n7N4=pQQpo8^Q99XX!*8#p0XSB?2>Wg2w;~KIx%*B z>jNEWSX)7Xt;rP1m$D7aJqa{yOG(`p|3D-*n6Xuj43h zNcOrKHtks;Q{kK81X$@bp!Jw0&qh7l3~Lwp`La2(SElJO0wvptbMii4`9h#OZR|qC zlK{dHX at hPoJVJ9TQP`#T$itH3k<)-RArAaf8EGw!mU;H#BJsI%^DY_Z2tRs2fTkmW zyreTz9R!vF-D0BcppTw&o^H`5PD+>@8p4BjQc>J5Q)d))5O z5%nToGp*3Xd}>a*cIqa8KoXz=kI`XQdIV~XFN~XW0hH& z+Jwrtx$ElhOY)S(sekt-Y)Pgv%O4JHsSGQ5Kadwb9Y;Bm=rhEB|8$S~O4B7PpE!Mn z+}uX<%e(7%1E1vMWQew!LID<~@X`0fa{R|W`K0NWwKj_GbUNXWu8^PjmaOKRQX5Cs zNtHA~?}rTpe}!B7tp>#2?&9cz_NoHrR0;ATx;M2NO0xUez%qmI-1;06786$T>ARLX zO74QxUp_&>J}2`EHCd(cuY7|(%t%wJ;ns(fmzIIxuo8J`*;M&@@P$w|W#Lx%kL at D_ zsN&YUuWH{;Tp3<@IK0pTKkU$v2c(Mtl}xs=SJNI~uC4DCH~?96#Ldz7ZYrJFRQi at - z_ATRP#rq!>bIiTk=B9uJ8uvahZmtkFSBREAqay79)Lo^uWbs at RV{cbSU$kqzt(Fq7 zhtpb)08UXyz#`3zQ!h{f^xg|$R(jVdX68ODUP1lb+%gUhmSd3o2`(Jx&9usQaQFHc_e;;Y(Fk61m3WP%$gnn1>#6mVJe3E+-n3ngN=7fwp7v1ZeiqQ1PDBKF1c9QZ(%OIVoL%(|)p)4H(Sd zOJ^9i&q6lg4SdUEFL%HEEfMzo$vg?^8qSe)w$sPXDJD9L0)c9Ib&Run4k$nMY%Hzz zDAw5#sLH2z+r|XD6Eg^)eJ?GacEk~sIy$_1^&^Cnb;f{B5!1nl0v0z&knV3st+?Lx zfzvC|PiDC0NA2wCDyFq9 at 2=_&j5XsAa~a3tO+Q7#k_a^1M^Ac)DK_LeLex at 0-#he$ z>vWP4Blk(|XufIH9B`7+L&~P6F4yTQkj_Ivhd)Pp2!;2g_lJ+{)-FZ*+%nv-q$=a{ zT^1!C7Tnb{+7dS*0dyaL7iVe{R|H%6x9oTYJ+)Xez=1*lBbGVi-6u1X9}YWt>|WtB zHQ9N%_!z_B$wt$zt5t3Gsg2{uR^&k4j@>$tO-4cRH1YNIy!tO5paHHL1zg1WFd1>K zm&FZEH^58PPv$!f6+alt{~USvr|9nclGfI at Xi$2C005KmRjM^qT~3ZH+sKh%{M`#Q z>pYOAIldA{5w;x3k3jRO(b z^&^}cLn$0T8iZWOez^`oMNklbh*cY~*o66Ia at -laa7ZRsyOjQ-`hjV)f`+cWL3`q< z50lkzd85P1F`t37KMHasw$$?TSCsQW63wM2Q>F!(1 at qLUsyzz&<}+f+`R&ObLNpF@ zz}ynRHo5V$^I at 4Lna9T_x1DUwXe=HXZTX2#zRciw)$wEPM(-jr6592Q`k!VC*s*blF!3l^AhgEe+|dFOg^<{y=jfY!3P3te4$}yjBX&Q=u^T+p zVY~itXj+Qvk at YN(AqaLbZX8`IiYmq$3cMTHUevy>;cHh1ck9(9j2wE zo0>)j1X`jY at M(XJ!t#%SmxuAZ?`(W5(3#1nh9EkOKr*17ezyFwXRfjd?rc6(|O z>C5utx-8qT>BqgLYwtJIb>?at-5IwMKH~eNPKD)w zYs^;U#a7k_m`#ovHz_^`kOGSB^7daHxlMrJ9&^HrzQZhDd&wYM=kFZL>2jY6Vr!WL zlo+o$<_wyOv&CP*QvPRa=P{?k!F~K?T%Mkqqg7gh&`V=VI17|5 at H>R|d{E4NHp6xzP zLKo&g>L=Yye7V7Z`KC>xhP}O#pPQ^<8{%_Dfm`a0S11lJ*CfzxSsDtpCIc9D4)AaV zdS7s$Cr}$>fmqzjLVR?9<)v*6i9xE8A6v$;vm2!S{m)nt#gbiiHm)J>Ps{V3`eJ6uKCYyp0iq<*|%Z|a4{4#Q)IQ+ at P-zuI2^%36SB7L zY6p(MQB;bohFd^XbIDVz7H_eyzfX$A6mbtXx#z`{$hB z#%^Z$vN>Y9toaJ*%`BhrFKn2Q4Z{;Io&QoNbzW; zI#FZz{C3>5;vonQ#U=N%G!@5b%9Wc*8C* zgk*0`D1eRQ03uk;w+g*6+jPU-L}sC&MZn`^&5MBvfV&HTzYf>Q{=*k}JaE?Cbw^5+ z(ET|>K&rU#o7{^%^kqRfq*I%4wYvKz<6(zs#w2Abu~dLp!cl;b8nO7~Vy$k=OsuVr~R{m~ielIfzf=)NQX3D7w7Q=nchUwc3{qdCe& zF8hPKr(%<=}yxYu9TSXQb at sG23z`bbvWhn*NirQcXN zP)|1ceyPFV4MS2Y^q-bF{9xjGtaB6~%uujR`QhtP-=r-xk}V$lVP0ne=wTJeGgaCJi7!hzvfUFOC>8rhXh2X1?YTfbXIC}SMpSX7gPvPLxyRRk5mq5R{ORa^Yi=I zwMZrYg~0`>+Rx$Elmma+_=Ttoexr<2ik-^!+O8YuI0$zRunMlrx+5zo%YJ!d!vyL*5T!i`t?$56)0#bY^zhv1`AkMFe-cQavBna<^}y0Pq`GQ zYp$Ijz>a2B8L$R0JrqQU^L+*E#(I^lV)Dj=K-GFFu`(oKpFBX( zY9vU{|MbBm8R at ge-9y+KM=DPgsJa$0!@uC0E1cwNcyP#W7|*3hiq8AMfbz>s#7kb7 zd=`klCLGK at tVLZ1m?`MzNiG0;nBF=QtVZ*JvYA)<^Qg9NA8#@u&+qDzT+lsK?|#hQ z=F_Tc+gF}ckQV^tJ#R%#pP4i!zALTx))Di48?Z%?n9p9U^t at Oh+L;rtFf{)$Smowb z0$dP4nCDh?8jlERWU^?mbxE>y0d|zb8>$*T02_Y&I?4CboBRw+$;yG-Y&I3z&)5i*A1Yp`@71=UER<&yhh>_PXbg;OHw4(5PskZUHa z{y0F-mVFRnc_Rs?3SdWEf$hL`lh&1HGe?Zk1n-5H`uCMSC)~3 at e_Gcb$x?SMpY0y%jQz>>YhbU_Pla>N_329vlu%UH{4< z`2Fjot7Zc>@Ka7s8;QOn4&tupQ(SD|9)JhZWq?YKbKPs+;d7)+h5ZdZ5#Cvr@%% z6?ehhpGNODVwO8!XIM2K8rUjgUJMv14JVk~RMZ+j)mBP)!Ys(#G|1e*I}*fLrxTjS zI$!7tq^(KdrBi^3j}>4Gh=VBAI#?AeEeD7LDFT81dsE(RInVs`ytdZBWRKoJzf7s| zNm|oi+NYA~IX^GH#b?}V2m>%A8N?k9Gp6Epx==GzUhb6t!?5lI38J>|HUC?R{*Y4C z|0EMJL7RL%Jw&;w(dFa3>Fvw>g`?xklBR&pbNKWafsRgfp-`+6$wNdxKk~IdBfF){>ten?WFKh1XiI?PmIRZO~o?O`D{UDUq{-zyRr)yXxbEljs^uN4jJydEtXi;ml{t7AME&ARxWhLLJXe88l`(#0%d3E{ssI(}-CdTo}r3iWC( z43|nb`s$<&BXjk{;5e*;b7*3SYmqar at w>ay8oS$f`sE*#);4$N{1uiAg}f-fgtny4SsRtynWw&(OQ!Hm_E_d2 at +tlRFJ?xW|F-zU at AI*DrL4V(xJjVoqu<$d9`}WV*WYvuD zDQ>*bYqq>MP=0=s(A9M9?O*y}%Z2RvBLfC)$iFzw0 at fBdWI63NZ3{iPZ0>bN`CGC)8|Q>xgm@ zKKUN}RtvU3Eib+@{)a82Kiq!AA(*uqe3V+>b>58|{tx#}`)$wcwNE%BQCk;t^u(=i#%t-=cQzr}+^l>e*H&wrz at nv1Ji{XJo;4gTPyw38$j(6iuCCuqD zx6$-XY`c#dzmC|^TRIl3e+A?X10ODW=hx30HWV+*_M5TkR^{>?;X at _~(th6L{ron!ZCLk?X4N~75v?zXR=I_ at wR=h9{>h{N)fKQ&) z9ziQi2l-e3_w&YI|7ZXA^W#!U{lGq_ZoK~4w z%j>U}^jxiak+{gMeV+plH|MW=Z9upBx{=A(--*lUKj-0m1c*d0L;LjKIgXc26>F=W zF5dHUSK41~_8;3YI^yT+AFO$BD7X80IdX%}bRq&?j(M=M{~FJSbF-J59v4(B7#;NT z=hPnQS5l@;`uf?QNXDL2?x~M+xCEZ`bMHTY?0vmDyF6vMXnDRxvlmXv$Ho#dQ3FaQ zZ2fgc`o+hhCu at f_D~J9krmJ%3thCp3=Q{*USdA~AyVhK=Of=~}U35P<@c5=}{l_hH zxVdM5Wyh>5^ZG=dU-pS|>6T~nd#{W185cd47`hd>hvr^1hzI4Lp0DY>Yua~2&Y{LE zb>~!#=HS)O6(3pt1n4Hf-03SL9?n>r|3b5PV8>}>Q{SRuU;;$J*6 at Zk%4bdA`R?hxl#-7?b_ooQP|8 zGF>)&Kx}&S?awnO4p`&>qrRHv?`!dORGAv{#~Vd2fpSQaI8C3M8yGO+$RNKD%`tli z-2DNF;*-Fi9^2;#e^^@^6&E5sb-euC&ZXOT5JL_Fx6;oGhPG1<^8Feebo?ICzW?0a zm&RUMdo+7^$)luWmnWY?cI8{}!JV<d2WJ;s&@4O+EJxT>QUs z2(!8^+SByQ(o2gYSC;rbn4I%r2ms-OXFA&ZH{a2cDuJK5?nU;i_xmn+oO$hb=$Ef#TXIQDE4>;)>XcY91;Ka*c{#0f~g0R*2DlW*H8Yls7oa9%K2AOm%*1x z`<4t at IBn6wclxgD`(oO+Afgb6zwNqaqAcsv@$0M+7vfJ3+_Lto?;knPYZA82o*7lv zaX at b6R|R7}ZTMh^0l3_`Gzt3emo4OgVJ`Vs|tkwFP6|ZSd}eygwBm3u~uyB;>!FNix|7#x`$ zHDuB4tsBwlf!y(Cs!Fw^W0qXfbZFkon7avgle3#55@*;uq?G*8G3SlxU;L7r{D(5Q z5iK%0y)GP5aWkT0q<#BhKjKT{f{P==_pa)8zG7DM*xdADWi|jYucyXHq2*ln+Ja at n zPe%6sef^;cTQ1y=Jd)V413)i at IOGwr{n+Uo+uDo4Pp3?owR-iwIWfy#Z}_z`J at WXb zycLGUKpF^$d7hPek)KGvW^$mvRXXk4DWj%Gev|HcKIYAbLkE5~&f}+mKcx`^kG}aH zSXTl>UwyhM^Y=gzd3N!~Ya8RYEtoP(zk2uQzefM``c`Sg7Qqa>A9SLTRJ}SkrRiYg z$OCnos;-WIx~%VGT_4*=gF7BQ4%5#{W*wLu+5OzE>n~Lw&Hdb9-#%EsxZmx_FG8gA zVS1VnHFERUt_1_)COlj|X`OXI^nAhjmnC=B&mG`nh9%Z5P>t%4?%kftMz$zDd(Y3` z{q{}OQ}5ByZcks_yy8)*bJ=hI at XDd^QQ?zE)PVMJ+fey&M{xPIGe1hbkaXkRQR}={ zbuSjZo%sh`b`ChS7mRs4- at _yx7Xj=|`;cYZOj)+sX~1Ll{V{oh4}DH04GF2;x$=?u zncq6u(p8GvwO3|5 at cQ9O|J(hib)NY)3Uhe-d)wGcB284XKV%1kGRyo3a9Gyku_S_w}^DD3TuRNDu z&t87LD^Nxz2_!k0`iQ0n at eTKv=yR6Gqyr-5E|B{4N}6*uiJ0_$Zzy&qkN~J z=8=Z;L%!>Iy|HzzdsBz5U3|pQsWvmN1^&<88-J;4LwZzwQ|2 at LKUK8suWG}a{jWzA zJ_?HX>&#oRS2kbEo>|n~u=G~!Ib3eAoJS$q8}WgrWkoNoParwwuXR9z4J&dYSgdO1 zzF0VCF7b+)S at iE!XsW(HcKg#rj}!K7!>1k_y|J7fWmd`gAvboGLtpr5+Aiw=*t@}> z>HTWmhlL-lRBrpB$E@>lhz at sG!A54RPbk{b^vk zkZiFjrfr7fySe1mkW01MKmCx6!q{4mvSRt=QyXCYgXPaAz-`bkNu2Xv})TKRHSs@)E}X}f&Fem@ zgpxDvEY0$kV at 2FyWV%uF{kO-~jQhAa4N~_0r2R<4ykn=nMA9=63zFca*NETx2TXr7 z;zL=+k^CYkSlbd-k4^S9>CWvPvuR6z?;9}7?ZyDZrN5&BC6e0~4|99V!7_Zx$QF7u zB26v{!-K`3k1Gy2SoAre_O8c5T{!cG&tW at m<)cEvCYl{e^&rbQ2uVEsox+)o97c0T zA1vB(RQF-QB_zjW{!!iP(ZAodT!-fS-ZjYoj_EBmc+V_GgC8H}^w8kRk;u8PBch^@ zzj6tKI&rkA8`VwrM*joehYn!vgyqhk-oYs`RQ8Vw5m$RvFqXjY4{MJ$5n|{B5h at LY zbh39HzWLfP5u69MG{xocI1D8LbGUPD^M-=9%t?b`O?q3U5^R7+UB>k0a zL45ml>jmYT<}1=pH#4NAvx>Eecbi%*ei*j*=i2UGkd}V)VR>lyM^q=#;(Meh3hE)% z#nl27(jB$?yTthyk5n9}@4LNO7mPu(Low)M43cmlC}l^2hePo~?|+Pmo%+jQ&5Hro zZ{B`zS~8C*<<<1sR}*Xd9(aBfMIYTF))x(sgq~_n=yq(0pWyjh`B4d;sf)k2Yu}EJ z7C#DJUu0MHiMsgklkLUDvg3+^ArF%k!zZ7AJ$NVZUy-+Q at wq9%9}3t09$yN&m3ZYw ztr5kKaZUfl`WYBA|J04)DMIUE#jIcNO$Ue9EIzz_s^`7h_Wrj{dlI!tx1$;#exfLL$QJe3@&Uis)58aI zy&lmIjxcdmZ8J+!c~y(!`I)7{oIWWM>*4LQp51$^@jzdy|IS%RwOqpD-O^v01vf*f^IT}5KexAiqX>0^JDV-y5Y`FEv+J{Ld*zy~rV9R?} zE#PS4ByI{QZf7Gix1s at Wf&<>M0}|2+trD*u`~d^OTE4g??M>T0)dh9j3 at KSB zI5lIm#2ty-4^Z4*ND93Va^m(dV6&P>=Zncu$~kIGb=k+4 at _=|JYUCg(XQ^@cwv&|C z4~}w{a=+)iZW{rVawr%=C>T)6RU at F3vlv^&^Ys-4p<)hZ{hWfqg0KaNZSEzU_zV#>GNxII;jNs+mF?`KC8)A`uex=*H&11|W!w#4h-fq%(&*UOQMc#w=4J|mpN$Zy94erA)yEf6P%Ei90(JDgcqQ5(}#&)2ylQ;+kdf0E`2 zU%-F%35J5$4w32#B-NA7$d12_?6?Bj at vk$TSi5JH6Ki#`DTjF79xdgQsN00RWjz38$a5jG_;W8q at wXvFJ41_q)O*l*uHl&O-}IhKk8*XyXVy`$U1cAuekRf4;bT^=&T=w|Wn{$%)Is1GSmizlD2PdXx9 zvYpqi!AomV(go)Rwa(PEcS~vjM+9=rRsdoIK3ZFuw^zcZ`@Wtn*H6CahW{=nH zgJXwQKSq2?KWWWrcQm?t_E1&VvVA!7Das5NPtS;3v3)n>UFnAOM}o`!Toz&`L>Hj3 ztWV-+KF|;i(6 at l(^F_p8=Lo!xnjMOowZz!xoqbwdb-sSohf!-}kVY;Ot;bBWzJ(@H zk|8U8j58;#fSGQDW$N_dJOa+HNjbzL;QAC_IQgJXU2&(L+j$&c=U7^T`pAd*Py-V* zJ$A2s39UwoKe8J6VPpjy802_3;&|DfQ)eLtkmDL;HGHAf at Lz{Geh6~>MacP~3AAWJ zj^B+8TVfycE}Y{bbBbl3MW#n1a&Ch3hjaWohm)Xsju);aF+B%+(1xY`b--bKZ4j`?~a z#q_g~=~H31?_O7MG_EfOeBL!5Oftleu7KTso(;QwGQ){ImmkTW-h1mh6kF&B%!A#A zj^>ZHfzKZ}T0W7Fm=^JgJV_bZGT;*4!a@<-N}3~Anl{u`s49uYo? zvwl7@{7HRIr{>j}vQL-ghcxG3dbnZBQ`VEb{uzd%}`vC~!qwTvF9AA22 zK;+7vOM2ThWfdyoavo at 3zOsDLqsxZ!eBa(=_e|@N%rBzO=YV`)AUbog;LMll6PvXTj1B;xgu?htE*B?a9hm zp4LdTF93?HF{gYBCq)!g0dejL;QM6ErQv%g0Z~;R=z4R>p_enpf1L55I(+8ufxmyL zdH>9bj`o7L+s&Sygyyi!bKuve+b>59_YuyH0o?EnXL` zek-ZSpcj*0;J4oC53X7=bW_vk8`njTn6kTXWK>TF7~CaEWWD&pv~pPQC+Qp4UmP)G z-=;^)>sQ4CY1urmLR`MIv8XDgc>P87oa~c^i;n<4K*7Hkfxb7}=(_Vv*tOU at yl~Zm z^pdY$_iA``%eUvgP5J%mCIabj3&;A66;|0XzDJs|qs{B^T(hZOG>ms|3RDgc;U;E>$gbpLw7 zs;X~htoKp(7U<66)3+ at 5q+g!_H=ChX6nwYjYET0F1WDiY6O^o-?8;$c7JrQ zad5ATl~1aJ59AJxYMO6LYZ%p-B#!M1a=Yl#(+5{RUp$~gB#>Rt0FGHH at 8uncxbr*k zRmB3o?$&})(`EyST(;=`iu!p(ZmgeR12MMCbP&1GAtX9{Z}IvANT0)zzVke|p*dvT zY7jXBfM(seVa<1eFMe&m|L9KcW5KZUME6HylIHwJcM}YUU%gCKc;`;21MFCrM2rlTYw-~_ENj~^v$tp1At)9002G?{C(>uBV2&^E>Rz~ z_3Z*6b&0d(&I!H at h;#=i`{h!=A90{q3H*p- at 0_#T`5q7{4iNl>yKDEOAOalOa6EN0 z5DUZOH=b0C2ViX@=ss%P?BPKmS_)*~hc6x`{L~D-{CYJQ0Bg{(ncHmv59PHiBsR z_zoWgeeoCwW&!ZU*`V$(rh=RZpcGEMzxr1EA|RLsK>W_l3S%YMK};%}4T<9CqIu{1 zg0i16QA#(s2W*0)R{|+<*_Kni;uI!IyZ0`++=sLR2cBj2-PB(ZKX&E8BDZVR4#9>5 zAiOy1Y)yV{;Tq}QA&o>~C~%ot*r#6(3VV5YdGpo&GxPU)6hwgM2_ZRi-=1!{kP;lX zMlF_TlIFKtIodnE at BVE6;&V1>ws&^B>6hp4pOf at F;c;r8F2=`)FC~5&ZB5G>7M^ys z at y}|TYw;afuj^$C;v;s=9lvMw+9wg+*BqRkG-BVIr at G#JzEyrGr}^Z_erETqnJ=%5 zpOas+|FY`h=+ZSu`<}{uH9UBZ)b^I(&hVmDy{i8;ZRX>{SGq2GoZ2htr?D={Ge(!) zj_bS2bKmgN38!)|TLuL$Sfo4Y`QWoBhhw{GLZ>7wTLHT6|8 at PfceWE};Y1gE{2o!;~)`thiW zp~o$57eH}Hen#^z4)X69n>-f{<6cLa!bR&2^zk^=^xN)j#GwIpwOE*Tz+Uml;+xjAn7XMKApuDhgu zG^LMn{+pv}&Q*1;a at i98B=6JCV?R``Q=c3L#B~{<=5l1e{E^CrdB8XO8PF-y3xua% z>+15Q=C$#&55Kxd-np?QY42w_qYj69-VVvV)Y%gRJ&@&G8GEX5Sjyu3<@3MnXbXO8 z=*SHH-cP=nk^jyrv9X{1^09rf4srKqpy<@qteHEL1o3$w;#TxW1;I^s+ at qRwyACFN zUa%!fKV$poh-LTQJv}Ekpxm=|Bntcb9y&LP?|#vDrmt_;j$1;{?i&*PgTCLEoG-_o z68oe~PFp>XO!ImZ5ECI|CuxTLd=hlIRCRlF*{#mGksZC0yQa+>I=E5X`vlN^JUXq< zyO|f&C70eyBFyMDq<)vH^FG=jTnm()8ilIFb0<7LeC{@ALXo-8y>?&z`|Ha57n^T% zQU1}zQ-J=TK(Dtd*2$+yo7y?g?Cy^uB?_TR;$+2ZHWp*Du;L`)0&I<- at bq zk#`bC-rfvy{)AF&OC2?8oBUGx9xVX#eSsjmf_T4hg-fy>5Lt_W at 9uH=BYUrxHx`Wh z#xK9({GzL`?*1zygiQXjZ&UDv>G$IowVUg`>f at XZAI>eU%Iz-i+0L!PD|rl>F25(v zT(}acoXqEM2$FulKd4o!bssfHKh`%o~w& zammc*n}!@7?m!M;(EW0g^2M9eRxMZ=|0wdCY1gy7YyIcHmS5+8yfN+K|7r6*uZ`{FR&)xOQ}SiRr4?H`rw`d>*LaO>n(ya!Ii=vi2chHqt4HRHzB+a0 z#fuNpC%t#Q&w)7)#A^oq=go1wn}g;YSx~HeICRdm%}@G{*#6CoPlwqb1Z=*1VdggD z<*5bl57n>kEKVmNA?pho9{pY$aj)}~xq(vTr{6ukX;S}LcNV{_)^3>D at Mlz5+UZB( znTJ8IE=fZ9ZE5`~)rOsGvv1Da6khoCppqeJ{F{A9bG=B*H+i{ zKMnJq2UZ at 8|Neu)^&9U!-xeG5!wDcY#e!?sPR*)+4mu7=5_cNvw|3E!ae;7J-4B9~ z{&x9iguvs%16$)9q9+$Tz6>gRkrH(=_w=H9y7S=y7EdDUIB54yUG9V%Nut6GutxIz zoxJ5;7j^;4{y>;9qV_s*e`B)!(3q1MAZulCbM6O63cdebG}mAK6o~r-fT|}yrXQK) z at M=~GriTA82RdEuDv)yvD7^l4$Jceq`FMGAa(~%-KhL~y>cf@(_G=TDtr0(aG#H(% z-g#?VRU-4^lA)xa6Vbo#6x9d!d8ZlV+dK&VimI^WGnHal at 60 zX$g;OwT|Die(~6o0fzMjga5RB at u$V?A055sdk=q=;H^q9sQ(bJcRWA5_h9(FlQo-8 z)~puya0uq^elfG=jlS!FVA5*A4Y2A4I4F)^SULH`^2lTWn#7xfk`2*4D~HOPCih#t zI=j(T@(%+r13)MU106v)P=ijOGjv)bK^G7OqCr;>1G<4&&>i#uJwY7k1$u)%pf89A z37{Y74+ellFc2hxWRL<r0W at F;7z#2$7RUy}Kn}SGiJ%tL zfqF0ryaxUSUI(ke8n70802;tLupVpx8^I>98EgSt!8WiRd<0(HL2w8h21mfh;1kdYJ_Sd?G4L7q92^Hk=!p(Z*Jorwq{lITK25z$0f zB8KQj#1h?!9z;(fj_5`7Ci)P4iFhJ`=tuM?1`vtFKq84qCQ^u0B8^BV1`&ga3_?Q; zA%+r}L>7 at v3?p)gTq2LiCklu{qKFty6cZ)H2x25LiWp6dA+*F;LPwMmWrUs}iE^TX zs3Z);IAT0uB&rA#VJ0lZ1j0(#2s=?t)DRPiTB44qCngcE5&t4yC*B|y5Wf%2(Agf6=l8NLZg~&tXDe at 9| zi+n^%k*~;4sEa5{6fNp1 ziV<}a#frL%dWd?8;zYefy+wUQeMRx21W`Xxf6)L at qG+HfNt7%~5v7XKMCqbIqQRmJ zkw!E`G*px+$`WOZhKX`Td7^w#fv8YaBpNO%7L|xbh(?M=iN=VuqIshEB1g#NkSQT= zhD;5a7VGo_p?;xZp`AiwLPv&<3LPCfCUjot{LlrV3qu!$E)Lxo zDhP84^9b__^A7V33k(Yi3kmBG78w>5mJv2KOc$mPBf|t8Jv*v8hIRyMfm*KiR4dg1 z>R@%aI$9m4?yt^K=c at D6`RW37p}I&tTwSa#QIAlMRF6`RR*z9@)nnBp=Y^*r at _^#b)m^=kDR^=9=J^;Y#R^+EMv^=Il+>eK44 z)L*Nws_&~4I`!++zf)4Dlulzim31OJm3JD~X?!Q*nc$iD8S$IoH)F<2&p%`dehGf% z!LLWZ9vwD#RIfoE4M7b-VIB6zB=|K1p*)n+@`K>}W6 z6B3WdC-{MgQN3P|Zb*UY#8JHv4CSG}Q--3_u$&+I%v8ftL8wI#LA8ti9``!#1tN}n zHKZ{8H3WeOln0^l(4 at o9{-6`e{XrC#OsNw}RPuSkiuSKZ?~g&LnCL$`Jd8pkY6uF5 z`K1dg-4N6l<)@&}jaY;Y^8q;sL+V4}m*9uSm5|7MV1J1SLX8%UgHM&P!PF=i3bm%* z!ziu>DofFc`J%Boe`C5IkLU9F--JZxFDi$=OusbtUg+du=P_M_msBfM> zq(cyxg&6FN<-+Q5u2rkH_DLZR*j~e^jrSetEs}G+=1nz@|ZtI7fU_4x(36 zS4Wqa_S>O;P3^Tkd`I+-XwY$c4CoUBvCNA?lG6|lu}p^FuG=GHl3)tDB%eSUQ6JMojPgJoejUN&NnpQSJ$T52GaU zdD)Km?|mOeJ&f8OgZ}2l?vKff&5PaM7xdWP_y4a?UIOZ4RPZP`o|s|OXL at G@qu|?f zV+tTI`h)(CE|3 at 95C!2=1R62=D*^v}{`vg#`RDV`=fC;@@P_wyfdI4zNdmz96Ljr^ zevYOH1#l4#t|c_W)Yjezc6*>u;Fieb#=umU#G`DkbIP4f-ArIo4Qxt+Tlq0jDEPTW zT{DFJ&_;W2_5dJoAWDf6Tjz4Aqik+L at 4qY8R_Yl}g}~Im*;LDNKMdr<4E>nYk~Z3V z52hR#=YpOLY$1gGfnh7ya!c7%Nl$*=k8rsMZ0-hjY;)V_Z97aAq%d{QizouG78~o_kM!B#0IKw_+QymkS)N=?68OYFH$#P+P4<^-@ z%{{@wzF^DsU~^+2Y-b`$d9%5`Y^n>J@?%qRFja$V^oOa9IQ2E`LpTtnRBXAPPJOg& z#nPLPJ=Ka|8|D5!#nGyXrJsL-GyV2JP2sfm30tFGBGbxw78VU_e2#Usv0v+!&%)GN zoVzauYFWP{Ftsz~?|8m{AfvyW)^6(OTuz+IuW=RUCJg+qq^UDYHNijz_C3yBmdG!c zobbP^-CQa<@&CLom->Rm?+sRK8+x}+Eo4*6*wnw-)GapkqJ zJ*x{`svVmOWK%(Hz><0(nMA42Sxx at BjnrN?b(l>NeVCM(P5sH at Cud>1*&3-ZcQfwo zA%YEbP9Y2koZ1Cb5}Z22!YG|V(w+ocamBDunA(--oa)8)(G{jPP$?Dz|2FiT;|xc? z|1RuJR_d;>eT-ybJ6V{6g?;+B<+iZ07R&a*$zL1`+ri>;y9d7<1-rn)KHxNkg-vH+ zmsl959~`IqvG~QX{Z3>pQuj7s at hps+Lpj(cG#lV?s9|a=<_wyD8!?QVD}H2g at n&VH znT3g1n2@!sK`iVpH;1zPm9XX9*mC($nr>oi8v)Br>4)bxR?8CDx|{{+&%)ZVsg+I^ zlY;GIVLjWBuwHGzI7#HV6~w~kvQ_|LunALWG3ynivXn&BJwaIunA(D2d-^anR&0SR#aVAIOwbU>N6pMY7m)9#0Yr+sS!B ze at l)33CxG(WgX{vu&_5+80TqmKEqU&;-0K6K{|61!#cv$It(+hTyTY{-56$M>FmkU zc>+6YgtD+IwmlcNZd>CRme?BBmaq4}PoegIYYF{Xt>tD|ZmtMqXD}_S at n>Sl|8N%n zC-qP2pVU98e^URX{z?6l`X}{I>YvmFngN8qx|rQA%ng0MZJSTXj`r4~Sw6 z(EJJic>n<+R(ZZZ$K|DP{@OJCyzIw{J)WZd{vj?>iA_B z at 4WA_scFB) z>EF$P)bBI;H!cZ)Mu_fr$uJN8{+LV^D at +>o&e7(GQ9sh<0+N2Jyfc08FLx7r;(vDr z8qCk8oh-OS|K1z8Y$^J8l`j8q;Fo96zqk}UHkX#fxmVJ;$*8; zS0}158}u&2N|Cg<guTaZm@wrAcPfRDGer zWS5s4b+$^n88NOV8B}d5L#2E*=~bnqUQg-=nN9XQvch1qTkG7Z8hQ#7KPQY*VL6?_ zL|R?4t6^2E3}R5FGwCWwt4*Pyv_Fe_cGnCtmsQ*7Cpjz#jflD0T1MJDHFuh$iihh(*7*vqvDz`k?tIkwmBq{o2nf291a*(bJQmhWq$7ZiHk~W{z{QP{BN+ZjR zI?8~#S~H774jqW^>8X!QWbOju}$hO9LCULhKARB-N5Ey}TU$V8T=Q!d7kKeDotl{KeTe zR}EE8UZ_Fs4>DUJBSqD)NrZKURcEP$eR<}YtD&9NvqX{cM9G70LSuq`n94{uG(t*F zhLUxqW}Q`!pQM;C6hg=r;*<<>1kMULE$2&xI2DfECK*g)AfpITMd}SYMLxo&0NPwz zImB0v5YIR1EVfEB9JaoKp->=??lDl@%|;_`l(f0!Mxv%sm!2H;G0|<}RP-a)3`@6K%~Zc$c#x>aJnT(EK4Yg$ zXBDAer+%uCQ%CjVqd~c-6Hebn$c=JCFl<(RC~D}+;N)R5lp2f%d!2tW|B04)#Brou z6vt5tlg)(^HyLU*wbE>c&a6a3D_>x$&Ir-fS!ucEG|ZD%oT8hPSc;$-=+0IfXMe`gKX-ErSL>ffUe3E7}8k`$45V&a{E}tBqLt at Lr!UjQ0M8g7F zwL at nFx7h)m$@Wg`a)#LmadJaYYG9fiv!#$RKSUP^#2<^EKgE(#vY^gFrs~QnNjUYG zt$G=hx@@!EP;MZtN?tnOfMz8~YK4Y{B40eX%v=?t)0<1l7)VS5v_bXMB4eD*Y;qft zKPb2yyFpq%(|1*j^NF_3HU`hcQ> zKhL#^(IY3Hp0iLHC~mcNa at uNB!iFh`XznEEb}LEJ6 at xM$fppd~IF0Iz&KUY=3Sc>B z6%axKO3)mH=%=t#UD3AHn?y=Sd6zWE-3D7Fi=CcI=qF#Y78yK)J%dEcIT|XdWfJsE zp%=lKd!ne)peJ3d*d6d^DrOl>!wq_SC0&q%>uULM_ey>Teg;#Sv06`ROnlYS%O~<` z1n_!r!jA5u68Oo{SR`_wu(oQ(qYTZ?kWQ_;ZL_=-&8}41-N@&g7_T7|7ANn* zQ*W?Ra!i-eWFtfU)p0(w at p;G3LUffrgJ#-t}VVN=;M zn8lQo;c1OlhH~t!F>4?Oiiw?L)RmD{$p4|`*>0|Y_KcMUov|94(5fnfU0enkFkV at P zjJwH34zD!WS%O#EtBh#sVbHW7%RoPP;eN35$B1P%L;|;IXSJDfM#`vh*tw+YW-22fHUn>X9?)O6BV}Nop?)LZ+|J_=n$T_Zb8sDWgR*rsh6-#L)14H_ zL$9ktt_M at tm&S_JXF&`Yrh1_!^6RQf&5R6tSD|bt5M+iGIT&@r%7?>=kKr`3Lj`8o z4X+1`K_2#K8T(?xQyt2{a8`KWILH?K^}>Dgu~xEJmq0|BCiPId_2!8t+Bsl^x*V at 1 zoD8m#;-qsoJ`AF8NK8lW<%rwNlP9tseEtVjQ9csiZ(`U at 860T$TXds>>?&$u5S;iO zhIHlcMZyOW8T-x#i;YE}dgkNjgAsP_-;XKh43(x(jes2DQ+DR^1FLCdg`L9xzoy z*Vs_Th=QKP+npRMWqNZNBYVM4D at yDILHClL!o4)yvWJ?}V*ajpLaX9lWh{VNV%ZcN z1g@}b?%2U+*sFWF_^~iWW|(hylE%btKpKe^F9Vh8)8Tvf2u*tf8z!nHBAvHZ$P}S{C+K2Ri&+n{mMp8bll1c2)RFcw?FP{6TeH>X zttn#CnRsbRT2)R35?U9K at f<7f8g|buzq%BwgnvzoZMY1bNpD0;dXCZ5YBi9hUdtSB z8_8Qd7ojP|yP_4yx5btKRX3F9lP0uT!j*OuJ2-6(CbZfb^rM)>fi-E6tab%g2ieL% zWFs^tt`y_K)+r&%92jo^B-_2Y+G&t!aPA;|IZ%PFwA!e%Viy^01sZEwb?;{-@$^Q4 zks+lPv|J+;aiBuC=p0G}+TKBqAf|(or9TfS=R1tvS){$vtjDY5bR(Yi)jXk&(b>Yg z$HgYJF6B~%K_Z>C!X`z#5Y#@S57Q&B5gBjK(p!^ns6wWY+k_pVVL*R&B*&^2T;S z&)wR6;gE>uR>TP-~(fMtsWf=@YdVFxYCY zw%A;%(7s}^#AY{J(7Mj89AT29Gm+kQw7Q38fZJIJ;G8Vy!NNf>)`;q|L@}PMOM`9% z%|stc+cYLLD0(*rttMa(igIecq)Yl%>TJA1%#^3)ORmCsIfveM;aX#`8KAmIv<4eq zh)T6qdIQ_0(Au0f*L8aRFp6AqzOU}pY9;1{i9_ucQx2d95*sJ};N5o(yCl;GwJBJ* z1J`hBbts3lDYVezm4{o&Dl?k4p%N>!Rs0Gb1{*aez)@T6aGI{-bkW6vCjp9oCGJp8 zlFRJXI=qna!z6`vPD`y~>8^q({Xr!liLLpFQS=sRdp(?CtWYRPZsT~6)m()|-sYmk zGVh~pEfPvj966w;!30&tfZTbi0S+O`p<|>%+j at o(gx3nf>- at Bof8xC7Z1b?`YRDAm z^`pH~DBqs!i3;N at wOp_JP!@*DW=Kve3vR-feqg89RCBe-?(ZyxDR9{NuxovLtMj>y zwv6AfyOW$L(8f4DZa4_{D*nlhCo7&BI8ZnM36x4K1DzrFSQyIwtr~aNTcPZSp`k&Q#bB?}S at 5`BC<&D?>r*7}%s~+H&wZ`rglcGy z;ZlgNk9edkwcHIfzu?fwT6ZUr_BLWi1F{wh8RgF at D2qP`j?4zlpA2%$Nb6@{o5pUC zQCH!?>jRNbiWm9GNH0AqjOJ3EvA|64^|?WUVp%U{QzhQoR(l}^xUg&wrou|>sc2|1*VrUIXt3Rv`*Sp!(Mx)b-pEuV#(ofuPHGjfV zTC8TY-3wP%tTfnMwUmLUe at ncywiaiAzI3&8CmMT~;q^A3haN5Vs?b6X&wadeF(kB3 z;xA$LHGH%>+ySZQXy>D4siRm=0K zMQFN{*l6uT6K?_QUdVX*xqP4JULajmXqQz{L zaSn{lo#fkWsm@|C)?pI6YAKuTNl~Apl)KYzk9ZG at 1f>D<-K at +j@Ie?@9T1+$_t?Kh9HiAU#}~O7tlPvI?$R%0#>C>~`1`Z78As zwOV<2Q3Av}>vPxg{T&aj^IAt?G?($rraO;;WUZ&Rg)btdObx0iqgaK9=>^?lXHOd` zA||jKYY*gF-YV0JH{a1>WU#TGvQ*2u|E{#8$oX at akF#%=W`M at nol$-4!v}C0&&*`_ zh|1vQZ4IFfgd<244)wlFO?TX_Yw6k*kzidCVfbR;@B2MfK at N>-pv5v1POL?GSu zps=iFwmu|`o96|Ulx~LB5s8*wm3V1edc+8Or}vr!J3DC`GuFTjkG*q|-ULS1ZR(PEC!}gBxqLJhd%m zHjft0O#q{ADJ|@TPZ^RrW3thh%4o99Tia^&=<7UIX7OR;CL6fkbDH`6kSXM?E*XCs zO~yFz33-wO2G#y5+=UO9nP+ZP}GUF zJD%g2TGSo_ at -HZhfNZ1O&I`G6uW(Xc_(ZL|Y=vlC+2_MAL0cBEjN<)QNNlP(4kmaNbGFoF|VnmE&tI>Ng{9)PStI5+%Wcp{J%C^p0 zWsqQHBxS5R(KeK0M6yXw6)x3TZJP3Il7up>vT}`U+hT{T%xJLCttjZgIJ&$S)rFqj zN`&hP9=B-e4b=YC8L}sqbKZi&r4-AxQir!mQt7#Z8k>SPG_-r{r{&Qajx2}rYkgX> zIYnoc at rDQ;zSa`z3F~5aklA7P&^mXfq__Jib)fhB<<#gYRg^QQ&{lI5=|PbRi3Dw< z>5*W`@p9%IL@=ft(jwh|5Z4A|U at 1{K?0yKNjZV^Xq zFKtWmOQHDFbd+m(Lr*}k^6ju8X@@+h(cXD^G|lBa;bl3m*SPk{@a3M(>LnkqPWWDk_36o0h% z^qp~Vv`(gp+O-^BNsmB zkaGDlX7tD^P^h+^Qe=8KZ&Xkfl-f3|loyZ7DI^qAQh{iP7)Ci+?#v at LpHAfG7Ii_D zIvY-AAz_3)a%2i>i(!--BP-)H$K6RoyeO(9bMu?Gw$%g&JyH}5!pVjwXX`24IR>;a z7}~$`K>W3B8^QK%bCL;{)VAdT{AG*{U8~*S#9YYvleZGCNvkZ*1#BKI1`k&>X!!xN z(DFdp7N1WY&7fiup4$l$5lPF&fyVWaP3i} zv+-J!@iutaBUt{X50ZB(2EQ;b(~a(kw7OZK$tXafB{B*htPlQL9aoh%Yq>k&EJHh) zP~eeSm7|3iy=1b<^<-&vMFE_fU at 0Y~GIsV z!eyn^sL(o%K!IabsPIy#?{aiA&z=D-l-XJrP)Yq#=%AU1cAkAvj1lUNinO7J;cY>i zfKIIPhO$*)b^XaZQV<&WeZbYQK%LQY$XnY z*BfBFD20}w)IAvV$4{$++^5|o4(zGr7=;1twRXC05j($d#o_crA|XXV^7!Tt0(xnA zG4mKA*HY(E_(7p%qtvM$?I_5PYtvgh5v~Bt6Dda0E%G>lA?{i_ at C85l@OXxTEf6Pt zv`#nC=yuE&-GpiqO->Xt9$NN(0F4~pmSq}l$M3iWWt7M{C-H%84rDu3L8`?!SJ`~s zV5>pT8;OS&2ZFMNf*1la@#K8kQrmI`x>61(({i{&I&+cgbnC1cZ$U~FEJqh{W at C^R zuHxbRj+ zeKZ&9KBdTBXCWm}JMjjQOlx6RP+nY;-U^m-koIka3UO__Q$ZDUHCK8gv;%t=G@;GA zpsrS&wz*b8TMeZ`jH6D)Wk%S#Co3A%;h`;{)lLYV;n}k75a)=wme~>;9k<|ybtl!V zCtVFTylE!KkSq!tKi)`FxueREHVDDXmT5Fu>G{8zw3`&CltrBNcCA%Q*_awKi8^qNe9Co7}b$`vX?iynIYNiQAg(nTVq zB2pWBz0eDL!#ZmUTC(bpxt5l)OIUw=P0&hNY7Giu)ZtA(9~w()Vx~^UWTo5|q>s)x zQCDZnuY{V+M9I3 at U>g>Qx6KX52%&Ob^ct8uS#x)aP4{XQIW9p<54>bVCsa(^O6Y&& z(enrL-to0j9~+aMLRJ{4C7F~9zILlZQNC!4SW2SpTeNp;^J43yn<$T#w=icm0?}n0 z6CSfW3M@@F8gw>v(@264M0a(3AlVR$a0AJ9ylW<5;_Kb)m2kd8bnxaE8&-~J3>VB$ zovUarN|<qcv$i{z?RX{5>$Y4hppvVvYRa{JIYR8H0YglDUZMp`i0<57rGMQwaIw&0OwEU_l<(;Bn_vf$YWVgcdnIoEc&d&x4$zQk|w=Pj2Cd z!8Rp*G(tU=P(-9laW9YvtTyG*QNab~LK`nfif$*-*Djr!(xmxY+pdC`%xeH0-BC$5 zDn=2eP=! z8ydL|P8ldXHLg#e1TNpToSHBs%(rft!`Rs*F8M+c!LF&=E#6?#|9g#o~6$^>3 ztn1m!-`+e=lAdUT+E}X6S at N_xldj58MqQxhbK9 zMC4|7&X7vV%$7Q|^QLePUw1Mh&RL$k2ol!d&_rpOB{7lM*0I_&qf!=+tBT7)iyMza+~Kl?50dG?_z2~%lBU>=!H7{ z=vi%Y2KbRCZDD>me)SE{&s8uh(UfYmry#AMD?!spg}yU8?*Saaoiltj{CH=^3uIm7 zHgjd=yyIcqN{-cFwi at hp?2VaLP`Y#8>p1AhOV)_6Z|9(-$`u93=!coyP>2%r3`j0+ zEWOr1$E2_j6lsq^B6Ra8oa^wfUyk4oEgaecr8d{Rc|LGUBhflb%EcBaIlMWbD_WGG zJM`r^j2Z33I8Drfe06imVI6JW8$5*2hKh`2x!r~Gs>JxUP)gn at MH+JrG=!&{ZDe=+ z%Xfh!aPI=qvX at HXn%QY*Utx6G_?6KYLHki2^E{iEANiGVNb=WF+udDTJ|0UY?4`W zFy$1^=WTfCImT*R8VPN;(W_N?FFP6%o;;~J*%k)HqJq at 6LM@AnFLFn(bg0%DDJ(pB z^uX_v5aYkD)fP+%53Mu#eOVgud-c#dBQ9Zvt8nI}lzVJVJrv2q+<<{ZqQR1~1(8J>oLWU_pZ&Y%l_OUTXV4Qd0-;!ut z5aVCSkAhbN?He$|LT{VFStadS6NHVpLb0w4ZG&2sV{cZtX_+YY;J*_9%oI+naJaVrPhg%AB5Y_2# zYq7m=P3bb_| z?C|N>K8PthV+A=imB7!Er}B=?BLBWiUP(`Ulb-jcCvho~PmeOQG2f!&LdDU36bUhe zK)BdxQ7OIF;!B6?(PxUuCOvhQhwAf#T|@9?fn-xzrJ33pwxw00jaB*@Ss=wlEiWvR zuIR@*B_u1*ajM2tXd^wW7$+7{58m-6+GUh$Y0*L(-QFycEXWfNjKUg^4F%P;di_GCt6ckAJeBiIMKiCpli%$Ms2Q*UBaV;-cHWlT~A-8Lre zj$Jn9Nl;U!pjbQtTsh-4VE>OCV zK+>C@=zNwc;LqI+ak}^?LNTB2&dYU9$z>mY_1RzWLeUdN_?AB1XM&%fMWsfcA*K^5n%)mKikK@?tzH zcKa)U7q;p|VqZ96pd%8B_zb$fQzRoaD-iP&^6VyJO0^LNkE!rT9$@)p+|7pj@~ig z0_R;g)4NiEn^r|}a;*W{*HY4mCQ=v5-n!ui5WvfBsG?rllZ-EAqw8z{q;`vKVw at qTNs15qJj>~EG3RJ<27s?Nv^ZZzRoR1NH#Q}-==AhOy9_3# zLWCk`BK%HBh}3-*<{T}6r9^`B3oQNFxDvz}L_kV`+}KgTmdrM8P(iI&`oBnf z5AevY@?iM8qbYZKku;dbpJiV+UrlIgAHj?)5oyUZ#ZQ93AVD46cGOU32<<&7(5^NSi|~I@?ng zR3)qhTpBf-#3t0|o{8|I#K#jyD0kb9&^4*rG5PXT?L;X8r#r?`_{zCX?TTFRA}z9K z`Z*tD0hvU*CuN?K{dvZ_9i)yn{1!cu%F6rsTB|dr2{oy+=t^Iflf$y4n(~b}tx3Fk zBVmh;lXyf1ZW)IswTj4`8k43V$KgFc=+9?qDQMbECH&f+75K6xPeZEG=HKTp}z45f{5&jymRWDQ2rJMdzk?xU9UQ%{6x8r%eZgp#~Hl+ZHP7t10-bs at c{uhp!h zEZB;e{<1`Ch+)^c`PSf=a6%Lp5$SNIyedw2S`IP;FL9nQ=Si;%Ti!FBF2cwZfrhIp2WMfG04=@rGD6gXw~Fk0rf&N_e!8`V@!9wW;xPrEH(J!Kl}l5-R(m ztap|S149Y5*st&p%0PO|yG at Pj=a;JcDt`q-^CSglkbB*?HhpOa{5G6V`fWIe$dqH) zrk?~JEKNZ`JJ?sQ&$e^QZ#^=@O4+Cg%-8*_FaYSqkWoB-Z3w+4ZcC)=YykFkK8Q$d zq5rd<;o6AcshW5#Ld-je(y|E*Eq9%ZO}M6o`OakGy=xv2==eH`{*%B_w6 at VhqMeK8 zreFr!yXLwWg(%(AFjLTVoC{$XNjP at K`kO))mH=NQ5nSfxy28;zO}px~6KNGYMo z1tI2>HBL!_3|jt?5c#+&$w<0tpFlAXYwuQ;e at +i8c$6hnwaClB?td9aGH^R%~x ztUk|&o2{v7BJQcv%?WudY$IU(STcU8iNtd{_Q#a9cjo+4=R-DHb)ms0*=XCpMvm=m zr;FtVSn%(9l6t;=3%g_DMU7M{;Ibk4(1MC_sKya at z3=#+Y3(*=cFYY=t*X1_s8boRgG8#dmYa{<@Ee{i{2tW|IEb>39=+-2 zn{K%Y>Uj7AFD(c?_Dwh5eB<#OPuz0juRox&KA%(e(PPJNId;p9H=nrq0Touxsq7n$ z9J%r48*aMsh6nLg+2W%&9y at yDi6bYD-}Jx=`}|$m$8Wgd=9`Y3xZ#H1d=Q1b4&QXc zky~!Q<><|~L|LZ*CUnBT3|0JShZqq;{XyX2_7*0e(y_!e83jUx1|bJa%2gifcAyVg z^QYc6pwRFDm!I_LW#j5Oar}mxkKFV>sta9!aa~7lIeOEL89_M4B)SV6?7KwuB?l2J zU-+LifE|t5M{l|DrsFpsyZOfdS$EJg8FlBD6GxBUd{cVW69vG@?VFUo)U+llvv+Qp zDsroPHZxc<6|{!G+*HrB at q+$4Jjbg(z at Qs;Ar%vBFel%_rhQAQ*qg2u60*)_OatP zABW9+^9?bt#(`5sCd8yfHzNgoV%~rhF!s7o+ltxhHKREE;*yoB2>E7V)Ob+vS&3sy zs8eP`4`#!0dx#rH+- at CzGncwG&yqMGhKWmzYQW6|-4Q@`)~`vlI}K;md2IQy+ko5v z0(iQ{bgqmt56`4^H{NvlYfviPbm^o-bDBi7>s{*HffyCx`&+0?s5n7VgLSsAzR64E zH>4q3xw=hOMmDxvF5nAP0}`F_t9OGwmuRT3&4sY z)I?zS4 at v=h1mRIwF_NP!?YECHmWb>Z(Fed6EiyIA8CIQ5;wOU?z1p zWD|nLHs`y9mUqU7#tXu+u*$^&k6fNbd#AVyWs6*8Jq`uN-iBqwPi|0e!_J%2G>s`u=z(Svdp&SKNvLas` zSY_Jw4-OALC*gN1o8%R+=d^YsTxbjJO%j2MhdgEbW+3g2JGDOPvnFgwZQO-EdC<7+ zl^dhGc4dPrl>SNJmBpl)6daebZBW^SzgE at d7*cPvW@(*4dWO$fZC(wD-P7P$UJbr4 zI%azdu%|aO>2YSO4fz$R(7D81A7XnV=8%w z;Ykr1TuAv(z0QAu)ql;yb3X-O8QFBB4MDH6>4N&;(@xS zEMafbHBqy*km3ButmNz&yH9;>j7>w&b-KDyzPjIrO%inxl!|L#ht8-Dv3Eb8AdfEc zyRNS6I4qfdf$N--gjE8BQx8CFUuB-CFyX)78{qYOI2t^;M}D&4DNaFJ^7LelXynI at E0tPwS0i31e-?3apIQ7vNP}i{N&_AQFC+xi^az!TMSZCl(N{ed$f0kS3%jQQ>ue$BiKn06X;z%!H)eKovY~v1 zqOtc!rTnv_6_71T`J-ans;V%*#=Ns!y z0HCx{l7q`#G83-U@}LFsovynG98?=+U`&qUtk#1}e5q)s5 at z`F4>U_gl3F*r z0H&G5R`4puRCoW-y(y)Y`$3R at 2}yh#o3-Ym;R`%J_YXnt6gZMa at Y`OIthdUgZ$#GX z8Bk=2+OmI^8vi~RFg7e#@Y at VWFRTAAX2gqT~@jlbL`_gnwLqtrj7Ya6CUWMH92*R2+{NEVOdZ1 zWj!$9yOOf5oA?~fqBO>zb&wfsqGMwyd zpy#+bDJmj!2QC^>P<+P)`7RxUNV!MOJG4$h+9J&kngh+ZZtD_zi6SqCf7uP88VfTFYbNX11m>9LC_-LZ9y4Bu|4Ax;iiklqPDc7k+OZJLJs&lC4K73`v{pF zc6wQF6r}f9YPCLI7$MyxtRXdr4QMpo)h(wEH=h|e+hU%i=z&2|&y!wilmIlf7a#M@ zb%&`=VLs@|nghR`lW%cOqVk#&zw&z=hEa0i at N4Q=XSc(WAn`gWIxvS_GkYPuXa})W zM0juP6L>`N&$ve8)qMi%wBH0?3(%twMrF1n#aTM$MaDVC#fjcqB2zF0$h9D80)S1p zo`5=THXqg!q8igA{kha8+BiVWG#>9nh!>3zwqfW~Bn+-4l4A$BI-`UiGcqTHiGOyG zR8z}5d#>ME7deUZs&d&(H}?{6ZWRuKD->Yv(hB{2?3ldQ~5 zom4@%Uimv6A{oVOt-m=j(Kmo~F>jDVR63{U<}U3Um`G2ocvLrzATRkoFQHLo0W5BN zj$jjVHxo|l#b?+_x;IEkTmM3ql6626CCbQ3r_)PQPS2*4DHAd(i}as~wcPGGwpB)T zeGR7zO>Kj4`;@rZ*bTOZ>}nF=4!Z=(NAR47KiZ_`X0st)c3DsqB{7*a zNNyzNSh)o2b=f^HDgz^Wr^3TR2(AcR7UGs66poYWNX zj^IOm<2IOMy#E9t3w=>rT5u}(k0(l%8Z|ZD6 at q3r#+Eji4(^dd0$k}P*+kqP^-(fz zpKsa;@AE!#C3_B?scj?NvxgYW$_PgU-P$F_ at zQ>Eo<`JGU$=!}GwowOv(Y(wz?_$5 z at 6olKJz$CJP#@YtYs3#pX3+K?8fx at Shl-u(_qMH6>Cy9YtEH^=p#Xsb5D4?73u$RN zfI(*z&(dmVtuh<)YChDLX;6_qp;w((%fY5oUtZIcb`xBm*^w=|7(?UFu;`MuUdMi~ zj*9~@QQcLcnPWbvC;eG;T0$O|J^rZ5a$cw4Dt(;7XA{LuA$ zmMcZk39s?Kr6~zklw>wV14faj+4r4&UJahjbaf*5y6jsQo<)H zyy+@(w<$eur~ABmr~KZx76_;_!k at qlr>yBKWF|V?;_>4?SDeeZ}HMDQ! z{Z7l}C!@-1dD`~3Ic|I2$CEzqR=qElavXI3FdvKNph z-U{B-_JhrIpVRZS?QfN7i)Ll)zMM&)5A}qt4N}VQvRwgzS@{Wkh9iRL;X(r*@#yAM?)fEBj9RAz6Gzhn4*3OK$e=Wp*a z`$`RAWnnPwhCS=MOra6jv16JYMWr-0s_BwRd5)<)zc3^-5EkXS0p&kb^K1WkXKtaYXNm(kN2w1D% z7oceiow$Qu{kISCO_0@#3eOG>+9fAN6z(grLq|ol(3NJL4A!KBHbrfvRCh@`|1{Bduo?7yXflU4$Uc~H| z)Oj^R8hL))KX!uX9%nW+QEd+m&2du8G3wXTpty(ZQED(Jl6t#abu{JVZXrRm8=|=g zvt{ihv!+GT{(dDXJuzuhK}EvqbuKu79dJGJrqnlXD4%g)&r!wH7 at q@ZuXnUhn*-xA z*aUH=P=9`Hc+dN@#j#!7Za3z=qW4eLAzFl#`-#-sy=#!CQ~{rJt7hlk0Kul44JRl6N2lxf^Fy0*$_gBGxR)*5og%HQLROHLw47# z#|WAx9WZ_JGTDLKQVgqQx7*(9*(6g(mspI`7do3_EckTIMQ&JQ)JFzk%FDP<4o2cI zhxU||kvXp3OYj1(^7DdWs#7+nrTYSrF(YmG%gj at k$$YXuGf|%ipp5Y~bKb`^FAhp~ z_%kFC!YWoq)DwTvA+X2l&}_hu_F16CxP6!5)TFoD(uOu!B_Eeau}}U>+7M@<$s}t{ zo?%9e at +V2hM#}bC@{V)w2#uQ<>@~$xXN4P}jm|^wm*uIl2GI`&`nO>kM?|>R4C0A7 zf=t&(tS&7cf-^#BW{p5ypvRPAAJ4UG!Ms65`nr zwwYpf7gCVS*UIu5(kc!&UA$5XqlrrY#&stBHVw5D^!5gGw6sCS)r8H*rjo4B*#l8g z6_X-)IvH|RRj`WP=w{8mKvop;%K9a=QZuT-*tKU1G6{!%wYnfKuKVrFIL0d2d- at N| z!0>F&b-JFjNyHY&y^qrv>1=F>?|OzCE$+{tgF|5^bC|=lQOz{$LbD-=q6d1s?1_?~`dX^IKj63e1_cAeDh zYHp8Nwbs{hxpiB+g$-Ct`b at f~b_JPmlr;6BBU)-sTV;~!rk$N=VGz|tq7A88m&TT1 z4M>oK9WcJ`Yo=r at XhdopwCu$i9CN()gcCtQY~04}sxem&x%zmDd0Ej>_OTk-s_OHL zEc(W6&R}!P8uuZK!v){=#wjhXcxc^_dT4jWTNX|Z=!#n0G>dd^Rr_h&ziV+VR6ASD z7e>I2hn!fl0_UirWGzkL1g$sG>B{B3rthx&aM&WY z0L{HvzBz%w=m3*fg`6uo|9J}AVp(*3RbWIC&v{{#PfFr zrGqv)qY|8IqKnqxj^SlEp(tNIi#TUTX2gmM;Zg#ugKCuwJ-l**qc8^Zh&WkA z2bBq%VI|FI%EL1j0Nc#^3!w4WmQW6GGwVof(5M)xx^DnY3a+6Rh@;w^V$fYeQ#f$& zX9q-QlT=EYEr~OtMF>Sw=MTGNLkAXrjKiP zgLQ~TmEwf<36H~d;x+pYwWO_MLkS(qUx_#j4sMC;Jt0&k;U^85Bb-3j>#zq8Lvf_Z zKCC)DyzaU=zv)42fq<_*Th{`*V+VgIQ4xzH-=M?ym-~Ut$#E=ux+Y`gpMPJOL)O9!Vk~>_beQ;KKD8$qV## zcw4syl7-7S5>>r3CDVU26E^FISA-SYeQ1og*I66E)^YT~m5}5!Lja5MzLmvQ=K6sW z9tn(^Xiz0DO=>oMGJum3{K*T(Dy#4UP@$FMA&6uvZ#bvexMvr^YTx-StbN&ko+&DewG)cvo#w-(84|f|qp>1QI z|BcI_P?E|ZVe)?aGMP!Tke86;Qa0imxSvEd_GYcMyxl$L$H6V_lHohXiyq0+KORe6 z_8SZEsJ+oVhaSM>T^VlrIJw#xWHI5wHw`ph6TXAh+dBtX{M+eXM8fA>-<4s^htrE8 z!rh601MAL+3`ml-vn6d#{U9G50I|+a{Gzu{w9Be4 at uyCp(IJOx3b!uaLipyBlpvR` z9l5Q+t-jZe&cX`t$5`0kVNl@;!eQvX(R`esjW3dsFlOOd1A9iCCRgscnMyrLzYxNq z0NK(^zfh2AV!iKhQeuL*?_r!JmL>>qJc$WgyA$Mz at H96tQ-t?>ooOU1t9GSYUa(iI zXXncq{c?r%Oll+J-y#Qii!I6Z>OW{khul76O at +SZ&JSFEGm5goN{Q})e!SW at Gg%D{ z=QT5ae%Nql)SB`n(A}PLTNt$^(x2q3ra&&KHM)V~WJABI>_@=g-rgcO+YiK2J`u`( z8ig332sQ&yv?~Tdk*}Y^{J#gxP&|JcvqYuzR4bKki{dP#&o{_c;`BT ztof`f_aIvvXCJp5k#KxRfRW5>s2MZ9 at 7qOTlGBq?AdHb`t(%OXuW%k5op zIU$E%CF?$Io4(8d)Vdt`v15 zX=b2T2}JfJhcV+_AGyX4(b+NX=0p=Ib)>v!|L-| z1r(9zYzqEL>1w>xm!O@<q|g|AW0i$LK4#8kR%3-gGb(~N zCi$y+E~)LnDf3ESNW>TPc!4`f3e{$9N9w&Jl@%GKv{U1fvIUbWzo_A_lJw__?j(KP z$#MZrYpbArhfNQvFO6wQDl$RCE(U924{6|t_An=&RG61tu5TRUP_l2YE4PRIfi1x);)^vo z{-_~_N8V6u__`z8In8!*s4EE4T?G zAUN`~KGm{_&;gD%U9lU?rpxOI?d^&<%!P{?B@%9xn$Kt+P73<59~QzAeP8bZj-tL^ zg at FI-V?*MOO+APTjup`eSkr;Cvwoi3MyXqAsZqlm385cGK9!>E2p%2%NUM^*>aN at C z(?fHQ2 at hgQO$YH)p&#!l!D#|`MNV5qmuC&>)mP7u<_nTinLFTDr|V0Na}v#dwCg-p zm;@6tio$CJ*;%Q>L|$;(NiHD#$;7w#mmHtXl7NNl73`p2=+nw&keFz>hD zTXp>dVaK5V6I060n02uTHJ{a*;|11L z^?V&OavCRZj*J&h^y}G(lF>Rk0Ys_O6oMjDIAAFv6E{7IU!`VhEo_c(*qAu48D~wc z-d0((?|^&ZX?wXen;=CCaXyYqY at Ssnq;Av3C01tPuZ$ts!jJ0oF0^5H)~CB5bZ5!C zgH}38N(dK(f6}~cv0rr|%Qpe6P_!)NZ&}vUgb^7M+gsj1I;#C{it7mnd0y+qML^%TeA@~&e_{`<91zHn2wVIH5rFScT4ytnd3bX z?cH#KUFX~xe4LFF3MmX4D9d^PutQWT%r#|Y&ZyJ+O5POA}gx&(XFj%F~^YCU?#ubz2VB zfB{KUrlk9(B*AtKIWa>PzLE8;Ig;rc7B(g@E2h7rH{276XK!G;A2d zJk(ilU?jCvMGUi$sqx{3eO3QJr(J(DtRcD@!gLCt6ma^YW=gMl(LtACzKBSB8*YQm zx at n7-xZO~?9Vrnf^eK`qAq^rl&h{{ByZEuC_7WKaaN|<>407zV_tp&w*=p#yz}+Yz$9b z_WssK&&c=$x?!2?5zHjfN?o;A)momc-CqMa6JCHL3C-2vchI~X`DsS;O?H0^;_?hb zQ%($#+2DxG`LO#F%hmW6;SLe`uDkyt8ea{?0xtSv+GJBn7q%FpUL=H_?)PIpHe{Z) zyHYH($1mu^{?Ks6p z;m~Oi_19D{DEx;@i?S}FpDN0l+0?#)u-3u>@_&Y at EZ6i+`tVMuYmHYcdVk~9K?LD7 zK0?T1Eh)(}zcAcCs)$it2NpgcD%OyoQ z{^UOYCw+_HT&_A_&ba1-WI`u$YVmO_CFCQox?Ig74}ojgQ)e^%mVd9k zzji5txwM?>`7I|&!KF)ZtfiHmw-qw;HaWxs9H(R}lIkuie#m8yIX&mZ=5Ru{ya0Gq z?(xlGr1wmJh1Xv07}PVy*3}oyo~SZJ#~57gg^MDx?TQ8Ejq~cu)h;vn3^P=8I<`16 z*(x1d*W-8I-1Ke&(4m*adY8o9=DP5~LwYryq3~OnOio{AO#Y7Zj??m!{)6kS?XI;~ zskVp?r<+;7!23&ID0EcU>J76DlE at k@LvVSkSJtmT7*n(F&kJVK?7ZZ}gK^!G* zB_70l(4tx9YxUTleAB9of>B)E6Fq}3o%d02nz10Q at gbE_lr-{-K_t^ zG30(SFCw}n3ud(8Vc!5c<=O*x(|b{&hi6YTT_kWbhc;0`E~PLN%AU}31UI4TIAs%u9^L*r=<+f~-=S*b982ylv(j$5YQ==Q z`&86T1~~?4xJ&5*FvlK9_x;DjfwtrJ^s~dKa(alIkCLE})RV6u+tJIOb9Ue^-tajP z6gmfVuI2`7NHoOVjx1b>&T{UUH_}nTN2_glh#xty^eUv03$sQ1xFlsm0kbE#9cd+e z#nq<->6{oGgI-wusj=;5L-^qd*=UqL;yPqqD at t;P=vgi(qI(!9_-v8+=w->Ki!`!I zn at E#6cC|-_Z7OXd_+_P`c258Yy2*&|(gtOT)ty#LI4=`+qH4QzIdD^r+U_Z2`?E9d zQKDhc2bHg&3B{V9^%;LcT^VW=GW5oELtdWrp3g`T=Cc at _Qkd*)%40pUD?Ful$w;+y zM^B!vW`$KAN&i5LL+T?@>>M9M0Ga9jp#9;B at RMmXRL?yunpD~R4#Vfb=khb zYEax}Q7DgUqn{+{EM*d3VK=iZOFX|_nq?yd(R#vWiN=|<>7w=|5JD6&$ioI at +VwxoKB%fNAiLyQ%Z2GL+ zAWA?nNuc#MhZj)_Wyd^m{KyVNzc5|Z&+yOLgb*!ctITtN`RG2?Fbp5uI!|)Q5qhh3 zf~NqZ)U#nYf at tQMY9~s+n4R9vQbBsFcE)w=knjgQ&<^c|KdK!0ey;DV)2lUG#l6|W zoFRoPWm&D>hu!b0^72M)X^SU_W3BfV!=WAQS7;ZXBRi*dM|SbA+8NoYb`o=h;@+Q4 z6+Kr%8l1ubj-t&IdQ0HI-hWMcE at Vt%nAseO1ri6nzY6qRyoOL5eGB!Bd`^8&cRCkh z8?_4#t*W#6+45XzZN6$RluN5?E9F^cre;-i=?us08_2kzn4^dIcqr)xSaCzq5l0g+ zO47#m+G1s~QmvHcD~~GAdaXD!Mm`=J$P~dVDY_Z!?&kD1 at P{S_8o}`eG%d9040E72 zZV#(Fa(j7YwX(DrIX-;^|0 at jb9d&$k0pcsm(E8<-rRDNUwNhS<{kn|&%`voICmaqn zk*epkdUC$#cHIocsuOh*$_Y3 z`8&ZI5);mB;L~Z$NZv}Yv{qdTEG-5)?UALqIiaX?s*AS_xEQ*};@xxWLN$Ch)1P_` z?hx2sy$&EZuzKzQ5hSrK|X{D-KfrofZ zV34Q`(s0(d&mqwSZJ9JBpU+KDyvURwtCDb)zXLGhG2uYeyCO69S-5LtLzFP4=M-;P z&_ASAV8>^{a8!VE0Hf$Gl+uKqO_y}PJ^Beu2wS+^Pq_CI>iy#jN0!`E&E95hD~--g z>5Yqx*4m`SvzMSv)suFO(SN{@yyfsp zc@>WRv^Yi2ifBR5W9eUJv at 9&Gt(NUs*r at i>T6Gn6SEir9Im&tNI9)E!%S_n;`e;O) z>XB2G#aU=Sc9x|iOO3|3GJ318 z&SXos4V-S1&3e?yUyf%+WEln@)3>0LYg70Q=VK8s|wo9JI`z19`(CDZCl zf|7A$XMMe1Z!|LJJ8sYQH?Dre#>SB&V?rjxXMh;pAMsmK7L=uK*=QU+awNX#z?sBk zuQ3!$+Jdu+iDE2`JzAo at 4Y@9&qGV22!)z)(WaBXvoG-0b zLvxjt)oMgQKvUB4bEnF4ODkoYQhD++r8$7*HWuQDNYf=z!7z!Qxxrd}-JNK9K56_nI2nn!{JS0`LeZ_PIQ6&AVfxuu!4 zRctoZE%o7Iltv9E{~grSq*kD^7=_bhjSxgrI(FhH#3m^UQAzY?*lX6&5G`vR2x6 at z zT`01d5n85f3fP~(x&@RXEz)1zSEZB{^huac+IxLq!X92eKRkMJ&-N7e`hsj|!QQiM znuwOnTg?aWWJDN9b+NZjv0l zOWWD0(&AYGuNRhP%Okb+Zra=R6c2rV!lx9wsQ(Yp`FF3s#+AoL*g%@Gzhu_cFT$w&9KY7}V z%dGGA7*LA>X4lzZL}88<|Q&c{n0!le5Fh zY^F57e7Zy{2=MRO@@nuj9OvN$fCg&|BlAmli~vp$B*cOb{}3JbqXbwzg#d)C`-BZQ zopU0!hca|yJ4X^==oQBeBJvVMg at zzPsvzQn0YG^gBz75gNCGsmP+GZFQlBh)51X)- zy@%126k-3>(>U)-^RqF*Fo#-#aH=haX6Bbx%OP^)DufnD at 6Buqy|qM*B#YR%C5kez z*8rJJ4l#8%ut}|w5?U9OE=vHib$fD~r at Z+24lFF`K?4IB>GqY at 9BiMI^5{Z!kYg}n zr=|O9PuhAtNnfckJxMkrqibBTgA{+mvg%vPla4=ws>$wa3nE8CSy0t6?rV`OI&KeB z9?IK8b!^7%VM;c6d#Lt at W$z&>pAtx=ShR{ij`!R#Nhl!b<=F%C0!mSh`j=n#bJ3*S zQBShz_AB)R=gh+V9C}If1PI~+MBz0nuF18TB7WACLTeF*aI+CXvIs%j%d;Vp=nKv) zEuRenB7R#CpwNZTQuTCsCDiGiN7r4Y#hEfdOn?Q5=C)8pa4QcAtTSNot;POwpLx_Rc;3x0=!8~}oec52q?CK&SD<=b)GG-l$yMLZUk78WYiD$J~{=QHu%TLP>q!dKhb2%p!D at FHAR zV8G@|^Q+|~!8ZF8UR}!7;Nsf+e6YH*R*rk|<;dFNZHoZALinXp0wJR;5MHgWz=vZh zt_+#xmlgx=W_Y=TNN~KgybMTE2~V)l^3w9!a)`ukVv7h)7vREpI;z%wa(ShEdj)Ry zHY!Xdq&^ZkDN;#PCMnaXT$C!qGN#-x36_jU*7VM9d~?T^XHA4Tc4x%z*{G7(KPctX zPgcr9*al-hJLtEzI_tHTo9}hD$p4Hl(LHp&*V{UI{q at YOf@ARd((3W+j~zL3)Aj4y zuq&Hwp9hWAtuEk at V;gYVo}U(elumNw)&5AI8o#2mUg6I~TF4B^Ipe;|RI;Db1Rsu> zZ;BvL at G)n7eGHKc>zHRCH@)J=3r&RwD-7Dat$dd5 at L_yeTR!0XcBSv*S)!a&c!?94 zEaq{Ll>PfkMYaGHR1vifBFcS0rLWX3$vcZ0Fg5xW#iP9PGES={IU8-3hsf=#Qi7{! zH0h*ofY4y(>K${aieL2A?Q{0T4who9lDPu&0jSJkZE*ztLN44XoH#BJ;bnb{5UQTw zvWdvJU}xFK?IVFZ9uuyiT1*esZ63J|14E*_h{oYr6gYV at LWX&kHp1 at qIxK7Bw*!y< z?I0=w8jp^#8%M-jV*pJz9eSPIb6|oCwcdHaMFD4Bi4$?so?9s`lu=cgT-f89AwL7u z$^CjX;i6h1ZhdcdC%oAs2lR5B5>!Hp;x@^)kv{*Fd;qV~UE#KBAoXgQs(H*B$Q0b# zcF%`hyn8z%wRLxV=r$MR%Uip(1~%?gXD6^+F)kVB6EX^q*zihe7J8Lk$7`ibK;L3v zq$QPBqLhFUWG)cb0G8i6->kdnA0*I&|GR`v4sQp=9k6&3Io^Q2 zGiXYlqaU=n53wO)%|z*mN+KzGtHL)fAgd)tL7MJ at r;y!3X%G!mnB5L at bVuqPcf@VB zV at RZCLa&&AdJkgx6y6q{HCSvLXa*Sfqq^dfn;9KeBum|M+lUF!-ERa=2xEGb6rNx* zW}SMg#+pAdUO%{}Q0#`1s~wL=lwg3uDsowkcrx4Bgya}5ZjeZ{sMi1CEaFEjP}N~nD_rm3%^|YgCi|fUeY>u1 zqyGxIXUQ2n4kFvh5sjMG;EopOX>BTTH5yXG8Qt6+7>^L#Jz8#fa~B6ROjw_5xH0u< zH;Nf39_ at 3t0Vp@s9*J(tx%CXn(F%gBWQd+}v72AI%vab!Fy+D8V~q3T8jqny*IaG0 z+1fRxvJkyVS2wB4K+Z(TE?lUFPs)A=5t at SQomL|$1>30ZAg0Ec2!M8I!cF>#Vl=fW z-0VR$^~eQ42v9|!<{9RZ_~SY;HP at rtsNUV+#hgSiZKW^+(d0$l{9hcpSQ6-LY)e^;O>XHX=0Ze$OX#%2n8!X6uXu|P zuXyW at idVoz_-$=DuCbF?%xm>L`_J*-B?58frm$m95v#~8r?VC9ElQge>q-!1R$rB4 za*ZxAABY5m(q3i?XJF_byRFHx#MarRE`aR#y60VY{}5*$iX&baZ~>x{F4O+i?RDDs zqdstRimtGee$Q~^8jh(NM%}nLrgfs%17LxBHnh>)fot1FtJCSGaeZma3O3&EOsC

    +oC at DY+z0~nm`~yXKP||gN%%A+rvz~a%ccE&K&?_F^Cd<54i71) z9Ng;>dsIh{;c*`a5FX08iE_pbDrW+ at pqlp~|Xfm`DX}jKAf^INlOHcty!}caCohCdiR_g-8pV zU9>KC|BL9_+~KBkHfQeQ!`#ZCWVbCYY{MRJWfieAKz)kucgO96`;2h+mNe`2qFJ-n zNkF;q<}9z>==XR;wVi+oUJiiS&oC}kcHmgAcSPR^*5`0lz1LOsK6lN1?wb3l_>kZQ z^*I$v76NvW)V~nKB{!d1BzdBVdJgsGxsLM)Yo?06v|{EuF*I?hca@(oj`XzPcy8h* zjGg}rJm`Xe7{5mT1}Y?w&@!3wb=e7NsZ06009+iZkf@*q)yxeM%cc415Lyf&uY=HC zmIfFl6-(sAFeVu0^D7D at 3tqthpA^Q$&wsPeEv`WvIu-nlKH$3f*@Dy^VchzOt>5IZ z*Hcw}m!J%>w*ZQcu;JSW`5K^zq%GWIsHQ^ z1 at 52Gj(G1fKL6{e?2m6Yf1iG%4;EYU#>}fI_p~q$h|+HJ2SsvN{S9cTdt_b`PFCIGvas9$=|{G>pYB%4HFjEwT;fXNqm;q9VPd+5FI+M{}q+_Dp`v<$j!J zI6shq>GTBC5Pl$o7N(16*SQYF+klwoK)g*5Zv)~<2jWvebTI_10(?pkpDKw{w9cP$ zZ3gFu0|cMv-9^4eGEXVm%Xr~LF at Gx5D}GAu*iYrAwL?7m{Mn)xKsFF*Y z0sTwX)qrbsdU3=&Em&Rh{u7&mg=?yuUKlYyc4Fp*(yNA=@3{S|UH+cr^9?0pSC))F zK|-&jp7lylxJAQRS9AI>mgC3{r#d!K9dXXgy>b)kwgrL`O6ze&67=4 at tk~qfT?O+X zNehXnR#wfI at dQ`5OPy~d%v66zDB~S#5XyL#F`9$angoLj%NU|#Z#mOd&|F$&~x`r^%7xwuotNw-tagmqmzs}pLkOyh=3Es)7{XuG+ zgG!d_KY!dlOzPX#)p+6U8n+K;=V34L9SOIZU?o3sxX;+v(2Lg(%?E0X;|-9HG}bii zGfq%wg9!algs!E~k0`WCgr02Z+DG3~#k?gvE=}s0zZ7awuu`2JW#PW2;#ISg(e>BR zy29NtKt85i!MQKxjQ*FSA4bu}p6Y#UiR(W#GndlVGMF-4AFxz4N?PYrqsuox8;5VRqt>9t zc at 8_}wZfvF+=8z8Njv!r;%gPrZ+w^O%M&jN8)`s15s4s#Fdw1`#E{sLLOL0|4kbI= zgdnIRSBaUOeyss{3IpckM>GOp1=u2}DIefYG>L2-(wcso_bE(#-yX?V8;Siy%=fe0 z{sK#wnibbSX at JM9oPvH)rV)V>fOeY}+xP#p&1&q{m;oEq+zaf5{boxcfwuzpyNh$m zU7W-xL at Pg0%*wWC;*t{obq`;#2U~va%UmMvJfdS>!IkW)fECbi zz+0uw^SI5A$oPEkE^o!%(TsY>z{c(!?9yiD^M5h65hn*jpnod-VW>DOHC0ldprK&TT^HHnzSwL?RM^ z%{s-if#^?_h`_UsN%33{iwbV6k+bP1o2<>KEzo-=(#@wu=ano7W485JgQR>2!Q87r z&ti>s2Xr@!v==Dt1v~93q!sZ?M%dD|L;(4%Kw^m?jb57K-z7;<$mG{$cBX&8iT(i} z^$&P+Xup8$&~82swu-^o<2IS8^=dY(@Jlzf{*;RSAnLOs1jUqiL1`^0lJYAN1P<)4 zDD;=`#C|DLS}adxx%3;?ksBZNOrZcKqk26*_VS&0HAq>toP#WGCqJxe;T$12HUMfa zKt|mUmin at bSG}jXdmCxx`9R6WK2T(~_{D21q;`e8{H+8&i@~3{7Wm2PQt;;)e8aWC zKkSG7Sq6Xh+ZN`227ivhf9?X0 at -Scy*cu-E0;@AHpKah)A`H7~Wb-Qp7U;ev0Unv+ zN%%vS2RBX*wx4+5GIis|f{N#44=v#1wyLnJ#FF$pZx~eGB)GZ(7JfN{?_rkrD)C>U z%YG at x&Rr+51T8HEjbbUlGGZ_J5ETeRfcFoDye;Ht7c at a!mzL_-%4W8?eR!m8beo?I z!NI6flJjP6zOZ0;iYxTK!N9+zxM#sV-d4wZ3J;LH=y>DP4Da@^Eh at X}8%1T0 zxDHJE!2Q28(5CTa-ZaAO*#l~P)%7i_u9q?os6uSv71uU#2g8An`e*Y2HTKWY)ZfYH zALX1jOR4Z=7K7DOc_*pd_rD|uHG6ut%C9HA#!2w>)-*~oO- zNZzZ86ZS7%7nge+M6-bC4bFHtd$T%TNuzIOmK)J5H8}xwebY%f9VvS_WrV$1K0~H& zX1bb~Ni~?M7VDPfvhHK!gZs>xDr+{(RGSEh(2FOIDVynP@H{<3f at u(V=GLIsHTHSn9O|NODe5RO7!*c7xstn79KvMvk(uBm+_86Sxg4cM}-+I9-Qy(@_ANJgKh6jI+tM~W3s=*Qr zL3ImzL#5kDm>x3caP7^>DjwBK%ppW`)c7;NOkp^6Q7PiEgcFBY at FdzLwE4h2|*JWUrDV3P%9Rjats# z?a94L&YY+wd+0;4c?Gd<0Cazn`qJ9cP*k))NGR_w; zM!(}eF?$IoW(Qm+*9XI18ptom;^=yR!*@qy_BSLR#}s~C#Zj4&UNO|1r`jiHd;(qc zR;1?*FNytpSc!K=JfWITsQnJZpHSIs_!BB0mM7*qz;lV&?hiay z0M8Y`5Ix~B|EGKq{y4B_qjGF*vy3;6=8^~6?Up|qm8zL1 at Ni=02`+e=EJTNS)NMv< z;H;ZAT$JtUp$npg1<7q5D-8S6=Z3c~ZII{#fMY{jw-1-e6O}D*_XvQP6TPz_$>4Zs z2iLmF_CtQj%ONvC6(1hEyEH&DKU**G43B z9YWuu#`XH}i0JZ2Hn}{KSQp7gbm*&f!04{II%1q{FqRFD9UAWGNUlR(NBbk{2BP;J zL{AZ-ry_~&fe_eoZpc9md&;MOY(4|^UlIa33V=h)j zZVOc>xA|)8e3k9MhHY8HF3|6f!bQ2jXAs2X`kHfNfYodtg zldvtpx6YE_dr!VG^Cal~DZXH-b@%7f5o6Wh%+sjK>2NUY^Uo{e$3v^4Qd>3s)sS1E zKcWO}TGl$e%t9%g4JJhkz0pEnw9w|GKR^8i=r3r<5ZP|`LV2LAoSy}4t58;Z at g`__ z>~f>r(H7cXo(dVcp{vDhFS(Bo>1GyoidQ1vrD5Gp%mJ7Cd_&RwIWg$0(t(_mR;EOzB&DL^H|Z2N)BSm2Ov#4?Ks+!LZ7~AG!RGaCqn^7GY79Pp1>_GTANuU at I;3W6a- at gjTwN^6_%qqHFLgcrE7FKewxhx{+ zau$NOH%Zbj#>jDBmS%WtJ*2YtWz~Lwct0d--o3tmcvR{m8tOBbszO~0EvIDg>cmnt z6<}J`Yd#&x*|%O|*5okeJ7HFHyEo;tBEziVmZK@>gSm|yVPLJ3S>j+ExO6M`HCFE1 zVX(E&F-6C^3H_>T?g-F$GZmJD`8uhf1|ZbzE8{KSV&9_bdy}*Qwt2MXVmzrvX)h4Qsx;o z at w9q}lzCcBY`}K|FY?c*JxP8^tGyQdwXf%9JidHSt$l*>G;*I%>-AMu{RG-icu^!v zq52=;#L#|XM8|4ze_1x4e!e+&f0^8qe4vaU8981kP_9|Z^eR*G6P-LZAdgA?^Vl#@ z9t*8J7WT_y1Ld*d>O4AX9VK^2y*{J1K30bC$+ftk)L9_mwmp6i#bk4>kC(B4N-d6y ze&-vuD#V8Vc=QLZ)1r-FL8sOhH!1g09#z%5VB~d^8egKeEK#M*CN;5B1;72_(9BLX zv6bB)49#p+6ICns;HL+cd`_pcSJmS47x at PBd6$=B zca_Gz=6HEuw6Szww6SzwB)mPw;h?#b4-?Eys&Vh|BvY9CYO(S9cN&IE{7yme1Pyyd zK^(6rh&%Q*$7>hF^4bM)ytD-?dhK!vTLIH*v2TJfiMd>yU#-o4QbJau!@;=L7MX zT7O8KANAnz;)&jIv~hl}+8i{ceNZ2Q^0611`8iczdZLFAz at Co}H*#`Z(5|Jt+U&+5wc9=G6|Q#PnpXEs#6~ z3hZ6Aj$X(VP3VP8(XyE-TH=V>m^tD)S)E at C)s0^X;Rg3bXp0bQFHvjS>$Bxd_WJm$ zvsPHi-0QP0I at 3>uCJ5VX6TbE-oOTT=PV`@>I4$7f{>fD;PW!2_`|ZMt+=Eu`KM|ru zKSAC>7idOoJR+7)u#Fv!um#$V?>Lfgb zgv)9j1*$_Th;yhmdN zs(ZAe0 at WE+C{R(Xqd;{=EhFE!Q!g|A7k^N;gfjwV4u=sjibX zt5*{}#9H^LiO(sE&(&|K^0nT{=?Y5xbgix6b%n58!JL1l)>aDpTop=T8)_|WCYp{B zn0Kcy*di#L at Dpy&I!Nz9 at t<>$-b3ZR2Pe}T9f$+S_W5E%9k3ee0Ofx`O`N5woh at zf z39bfzf$^Xy?HWJcL^JqAz_!Lnm^V2Y<&n|mVn%r at qdaAlR}-fwqf at 0B-Q&t=i-YF@ zRB5X{QOPS_%1V$U^MIB(NqA1WChCiv at cNPxP6&==d8_m4SX>X*+K(Kar=px!6Q>Eq zX%~v^1E82}p_r-x#Y+x~sZ@%o+EplCc8cGFW_X30;a{#AP(&si^|&6 at QjhDQQqi#L zAjl4lhFkf{*53(!r*Sv+a!zs at DD9%pDn}he&TDP`b{5`exJ+(wz?lms{8XuVR zeFG`XwQEqAg8}hJ{qaHMg>9}!{qop-mIfVR%L19T`dC!bA{rKzx5l0|GAs&gH-l(k zYaW|NRF(&A*JyD|Zfz#4O@!6_$_YNCLiO$|3MN{7+zFnbCBF(#u1%DyxnH$a>n^C{ zwZ9h-FL>=c!^;$i&gXXnJ8)MG=Zno9$@2k9%dec#z&PAMY54k)Q$wR~KjEk9h>=Mo ztcggNc~XVq2w`P$4FNkb$WL;XWcz?N9_oK5Db-2UJnIzwfSPOo2pw=gLVps7IdOtp*{T6QVThgx$g+IKCu(jAI^BEJp z6uBS^dzD!he+sdIVW>SIAx;p=l74Zd2- z$<}`gxD|hbd1|aY+G<=IeV?Mg^(?{$bGq#sVxjy5*Ha;=Ke-$2cg66d`FOS|O_+rluMO~pbwj15`igZIWGyVvx6Y< zdw30~#nRTUS%Nq{*}pt4vo6#d96N*(e4#Q^_ET7d`>C95y?2BRnV|=nFu%z>-fjJ* z#mDywIe^@FJ8;3!ZWbq^zPZtU4sNf{*2diHi)8ltEXqx2dupM63ac1YHuVHemv*hc z`YE at Mxve>UcxbW^aHtnKnxKk`&m?>rlZ%9JTCik$5acFjHC0N>H$|VSEOt-cap~>N z=03+;k$!imS+Kp(H~qx%OpcoOX}0HRgL$vxdEfSwD7fMHzRzqrCNOkbK`E$&!?_uwx z{E zwwh~umHzGCS{S$(TI6`9YPo^9mVLEezjz3x2?mXXrf!dlc?3<}zJ%>2{)9MXxS^@r z=1bTfVpZ}uOYX;$46MGJ@(rZFo8vql4Q6s`;(d0{4QAd4XWZ+sE$!e5`)d8ZO0||Ol4U3yMg55|nJ4jEq;M*pk}%gH^%@088Yz3u8<9`l%LS#^L^?0Brt za5N^Lz>rQ)4BBfHFL>Q7zuZo<5Rr?6SO9yF)oowNGI8^f?oJoy at M}l|H#a6}5Brfy zuJp^z8FHLcJB8S!2r^%3PUnUZ>r}?zgeCcMbK)GWs^?VY4!OC3{qBZX7( z at Anz^MU81ug1%B?~Z24>yvgiOY%tR z)n>_~N^kMVOga{J`xl>uncXE6aA7Gz`lv|%jfgHojAL at p)Yvl4+di&0mrXBLl8Y5vcqJEGG(ytYaP5K-m?VdI z!qw1Zy2PXNqX2C%A%?-lspR4`a&aKkFi_wTSD;|EAfZren|^)m>$Sd56t%v`!yZWO zZ_DKsi)8mT`7;rtYbbt)gZRBxf#1BL_&prKZwCK12k~1I!EeD({1)b}uj>X3I1jebWCIy;q+hl|)L?``j at sABLl&8MHH-p(Pg4j$x5jq;VfG5wYwC z9pj84VR02hTHTM^8O5v(r8(|Up*f&JCu##e>rJ$Fxi4$Vd?uEdqs8B=B;S;l_bTPg z98F2%?kvH2Y=}%`V+jtw4dJGf=pW){HfG8#|G<&IjmWeUx%+zZO=;PEJxAg;?hctU z6`AF&oDv%}Dp~Aeg_PN#B~~~dcA2U4_J}!Cb0j)a9MY1K+f%(cb86_sysZ~YwCN|AQ2 zcn>SFi4f5ru^KoSn@?-_d at ZqUP858qT#BOlIcnDuA~5& zg`LOPedY!&-BC$dWykiB+x&U@^3Z(ilu8dGE>|G)_-rlZi_O+#Dg=e*Xo07At2k7S zlur~S<)@M)9;PfG*1pCt&Zka==cB at BYi$D)tF>(FY7HQt)#5#{6*3Rut15OK*WNr+ zfox|{cC+m4Zf)wb22*<+=3LwSY=ARM+nUQ25EY<8Qz^OBSy5=JTo!dJ7QL|#bbY&@ z>U^a_QnwZgc}S2XHfqsAW}{Zx at Wlal&Z*X1RO@`L{h0Y&%4)7=8RKycgZ}OExr5*t z%=`=7E`QDgSQA!ZMww4&xb}`%%zX9{8bGs_(NIugnHC at 4GG59o!_1`yO&(J%3P672foSq$ zl^n>*nbPFQp#;k;-h92CAu|W&la6;}`phu%8JG9dVdf();L9V;h3rKwmWf&{BPMNR z>mv-(nEV3t<4f-EA5$N7YOI6VNz;LLSRgeaCN;uavJRk`YuwG8ZCy-lZ7a}T;z9V5 zm)K=-Duxn0s-dTVNAp$Q{CwCSDXNth%)yWOTz=uTOo7Ktr2!!e~J`lU77F{J; zl*R#n-c_?&tEl(yrF$C8=QK8*&)&VI-Y-U$c)xUezmq;Y%)BI{vy~s0qO*pBjn453 zbdL8+x7S7|u9Yv>`pYURm-8(3(z0+1E2j#Jl6+=W)Uq45kE$AbTgEfUb2R&zA&Y>n z(SAc+)1?W2tV at G|L-TnJ2=nRJn~7Mi3w6rkl8pHJ^W=u;f(Iu>dRSa0e2vt1+G zLU>I#6bd&scEKUTZZ0=G;woUo$l zXQL?Adp&*{ubFYup65VY$M2tv^ zoZx}!etvJKoL|~0>tb{?cXC{!KjL`^jI`L>84>!DynI$oQ~q2rSAGh02_Lu8ifL}t zuw*VFI-**Yg$=J7ny+dvnM= z*OYt%x||d1ur>T`XA|mzXe0=%%mW=8zz9yAx4fcqrw10rMp!&tdrC;25i=LenyK|# zqbxP`##7lCnA?$U{`6NtFt{&8!Z0k?>+652!R at 4?7!4FX*X2`pss+xCKq(&w_Yx#^Q2>P#OWjdh6~&!F*WbmjgFh zbfKn-xh0EN1j{6N$;Mub*V)$6dcJ<&{9$q5{20-(xd-ZeH5HY+LDD+5fNG$2{HAM(ncs4w$O36hewOA4pTsE zby!ZX38!<-ltTGOTz2Z_^Mh<9^ydcw=J`Q#`d*dNJZ-0Shg)fzIR-*C#B;~*Ht#l< z`%T{fS3s!0lvuu5Zn;|qM45oNdkT5XD;jo2iRpSSGhI(yp~d&gfD$#2I8{7~Dt6Z4 zIiZ6{`%+V7^wJbl*oNbq(re_`Y&WXnMHhbFT%u$UXC at G&tA0<{LHee)b0Zomvg)y!8 at NTOS}sW z_bd%_ajx{B>s)Dpli&mrEaYDN6H2g`671CyC(w)7xtC{S<+KHGb>+iqY#(){H*-Qu z9HL?!ausWlgLW#av6#``MriM+YTU0Urs^f?0ff+{9x%0EH`u3kT=*HT!Y^@h=|nC| zxwbgH>!e&d`(xqcGhNA-Imss>`SLp3fY>BTK1qiqB8CWaZ`pnG74^**Xsd39hb+$* zwk!`hGGAzkOO)>=S8J>s0L@`S^C8fza?ns03p5`Rnh&+a7DBVdg=V!=-aaH;C1H3<0WyLKBrxHo^$X_0G^G` zoSZ=8WP+Y}j7Z00dJ+D42mU_5zu>^%NAUOQiOB>%*#*DJfqw|_o1JFfMa{BHOFVSd zWWlYO)yxm+iHU at 7q6^;^rZIIrH)Bb&&hf};pW|)i74t73CiS=`#oy3c-_Wp+GtLj8 zZ5)nR$bG?f&l5_KgrzV`QuSRQRmMc0seWVq~qp+-u*4np-uX3jsL{Ntq>V%d& zU_niFptgvu?G}RC!cZS-$-|m?Nd#Y_;7c6*g_itEGq;O2+fKpTIrv(0H~F at e({v}N zJsMuRp_$P{GXrUlXt>fr@*_yz+YDmvXK%YVxx+#*%|UQl(}od((}dtOBk0hR)Ag(N zIObzISNt(*@W;5pC+Nw27PfmGZ13_Gm?l^gX6Pe2VQYOvm(9r{^<+vtnN#1VCttKu zPjpgG6i^chY9d2Dq$l6Bpe8#|2Spbgq&yCCi9gnoc?)Wa4b^ko8Vz$~TU{VfS at nv{ z+ppLI9?5kW7Qg$Zy*1Iw=uqa)`)F~A=s2hB)bp`UUGBiae|$qEZ!T418zP<#Vxq+f zSG?0lyAvV86%BO}r;8IWKTf>-A%A9H>0y<@2VBAe<3 zZ2A{g%8Rtu^@-zStY{%#(fHiCzp+T+oH49h`y&Q|)E;U$gAdX=dULS#W3C!*Y#Wm6 zioHu?x~`9{p%XikPd at IYPh;zGUJG8XgRavcQN?*gnO+u`YcJz8Y95Qsykb*YDjOwo zmrdZ(kcsCYem8TG0wtjP*{tMv90`lpux&tdQ5X{OFt*#MWhZ!6_ zeo-?QD;TI3H4m1!&oPlRpKI|4^<3+NI@$L1;MJcm<>Z$#%=ytkgW0 at sZ9zz`+&>%|NqCL`XQdtq5M67; zRwflnZzB{Kok-EO3UtSXLPX(7u*5xjRL}U1ETf- zwfU`4uhERJL2aJ$c3F76z)Ry0Zi(E&>R-y=*!p<6N9f^V%EghVR~FbrKCOMt9+pk~ z077h@#BAapX6x}Q*Jb(4lWkuLiC(r;*F+t~mQ~3CEVg`cMC$d-|i|D8;|wc5RBz+y{C89incpYqnRI z&pqSzueSYE%OA!VJ?FivLj;AZo*=?6!f%r$TK z=Am_3d$dr1lGYns`4}GD(XG>SysO0a{VkG3Wb+YXhObRNh-zd(H6ZcH=uUcJDs4d1 zg&N~+%o1XcuqL=kzJ~2WywXXf0y$4i*Uith3~?G%CRf^aSt{B#41Y8Hf0tdh4#WSC zfFHt at 666fEDNO>T$5bAX-`oPq>7p^k|0;usBk>>)2i&w-cKcqC`vHI0&>w$8xAxbf z(lPqHh_iMQ*p(?9m8)XZ8+H z>u#}UI9Z+K_vEv at Qs2bO@On)yX~OoqY0?L|X>-(^5H78QiAR&RJ00NdMAw)FO?3 at S zTC-SAz+oH}ao25N+Q$Eli9^f3$?g9Jv9}inC0`gs--%_V27B|&9aIz#>?v$7S13YZ zhez6vX0u&CehbMS9}p?!Qf`oW4^L&Zui-lMP9~)&VY$160&FB?G5|GUB(x5`BSJwn zDGZ-(HiKPTjCxi?;p`VNdNF2$h`}+j$kJiTOBw)SsDEkYd%5zE?YPXUcPs at +ecnWU z4vSyT7IZeYpl{WEo5vX*)bKa at BbWC0D^NAvUsqH$Jy2JwYI?BFRyCdXjjE>i4WMdz zsLob3J?vIBJqcCQCor52*A4LA<=Y&H+CLXT&nBLW^!!LlJ{KWBUMs7EDT+U&G)wgO zV!dUtE at hVJiB*ngm7ZAcc$VvlMUH2YE{==esk7~2tVIL9%hhbbiZO9`FNI}UK$^ap zwYqtHSo*}UsFZ%YIenxV09lrd>(+DWjzES!H=PJ3I|5kQb7r?rPKX at Vd8s9*U<9tj z at WZ`>c_f(LHzfT|^LXtx7=xnae5G!#(bYf2BT%|Jf&!n?)Dc$P- ziUKX8kY&2Lyre+ybAiTDd6r(IJWDCr(*LybEGo(U1I`_0Q*Wn+iftYw#cr3|Z{LSk zJ<77J?_w=~uNmg^%_YTz*~a91&HYkj)jfp*LbeT(m{zVN24*D&_c2!<%k|hx{qlHv z_}Av)P9gGGUd&@P<*^!h9Czih=<0eL{rY+=x~d+Fig_%hJeDGl6P(9(7E-x=pZTP& zjUjX^b=C=4sJAZECD9SIb$>F7t^51UvVw4|J}do7-PSr=?YNfe{ER?xNGSiCs`n&S?@66TM-z<>YKtZA z9{OXnUiyP(TTe25jK(Cyu^W>gG-vrxWd+}^!1{5MXv`%g9k!)Vu#aC>xb9&-WD5J+ ztkXy>sYY>S$JY4E;#9cxRE0J6MN at X?>T2j*7x1eS;STWl37 at +ym0h)IPXh{>k#>46)uckNf zIyKP; zGac1wC{=L_7(`#%qh!-*?wEPvx3r7xcQvkJqphj z=WFwY0!#^gT4%p9^dXn+NAw&02}-sB6z%LlMfJ)0V5w-f9Yt`h| z$=Db%)(y^Vj3i!&7#jv>UWg=~j~Gu6&O8sbhzoT-zdumpejw=#9xb?7=ck4UQ#L;< zK7n+mqUC3Jg76D2LoG#qTztRjG|KQZ2)uF|_G{QnMDqf{B3F5t{dKG|akR?Z#sr)` z+2qkG2>20>KoHvg6jRAD>Gtb;d~a8Ejf%pc%U8s`iS)P4sgR24TgeAevSRedx)dzxsV1}Cb*I8lxt>xGL z_Fs=ALjSY$3lZ~0K37S>_k4+OqwO=hD)=6WLc1!GT@~hLJt|d~almFB#}x4y(j2$Q+Sr5my-^)iKty~9+FA4%~qQBzPF zt3u|>yw at teo)Leqs`b4pwDrC9_WN0%I27d1=r{TdKcBS;jd340VZRYIjq(bHs>O&( zyi5$<%YeO~x;7XBTfY}<{q%Y-LE*-7FLlUXJ(1U)mj*L=BzV9{a2N?5bP^n<1cwJm zFvCgkE)vXi61+*k!Y^r>db z9x=;HkSr}CS!yG>6wEATB#Vnk4l|O|&D2VeF5yD&VjJJ*sfQ2ftp{`|zDtko(xvhH z2-N(+dhH2Hw*%?$D&(1FIYS=$O=!S!;10#2XBDMu3s$4L8^s2kb2d`$T|| z=PftbCk|MS!A=V>^5ooLr){t-1l{6?^wvW<24`ozkLvUj>bVz!tyhKs7K%2s5j at +B zigHAUW2}Wb`W!)N at z%XklOU2ZCeZf!%3ork;m;U-FF8+N&6#Ks1+BsA< zkJ6Gb#C%6@{j3=$eA{)|&OfiKzvLpDJN4u_&V09?Jl|~Y*9}>}QAGWC3gOM6|8f+b zeASmgY-Yc1?$L|Fbm+U!iKC5hB64y%ahH=PNTdUNW{+;}bFw|6CogceZ|cd5&1Ua4 zvhAf8FR9A6B(~(d^07j%rT? zJE||63Y208w`dv za~Z$i=EG at rUpBqjZ=2p6i7m|cv#CJrpiibIX#YM07~f{T2LkO+4VP2CrpDIG%^u5m z<{O1VHof1+fR;^{6jF>IAW1fT;@bh5Sv{9eo^8&iSNWl at Lc6n7ezQl03K!n=0qZr*PYpVO?e0uym at r{qY3yWgNo;Rv>;t8-eWkINOJk{< zX-;p0?NRby53;(GdLy$_SA-jr ziWpLBBS|df316g}1wQuE4kP`lFN3dmMwKSJn2q9 at 2WMUqzhS2j6w~j41UjY1x0s%Y z#8%8~WNqL#X(fD90Kdt=7n?J04qS`XIXEiU?8i|8MSMbue1T~8E>MYofgKAO_YD&~*lOFdpw$CWO( zOWf=91WWCo1_PW!gcIZ93dV&nl*Yo=S$1oGCK_pF at mXZSDB!Xt at fxixujv*W!H at aU zy6!@U&?XKomH?LyAV#o;d at iHJ3QT(YXuzFqZftD5 z;?TbnL|uD09bZQLI3N45+(^_4)Ot5lYudas9iGKo^?6*(m$;a`|HQLLV(BGL;X~pT z!6AaUhex3OBI#$C%tKAFPsjeWPoD|%(y2hQPY(q`ejJ2ZiJlo6{zrErXupZ-imh%~ z)R!uDwmApOjF3YXyVDN}1zJ6FM2BhJaB`Y8vLvgh6fR+}o&@iYmxbeBxq1IhI{rp~ zDvJvW^iEej9v29^x3!xhw at b-Q*u&1Joq*RzyOOMNd`Rrk;m`qEQ11(QeKS0;a2de{ zDz-V|g`V at K2vLm!EtXU?buJWRDByN!WLq#2+PW$>w^^c^UDm&kbmeql>c<&s++6#b^ML~%(}1297VB~*I#wSu{B~m zKRB~BlGqk8whYc}izHr*7 at G!XUc~TNQE#1YCC=;iI}_*i#5pii=d8=1?s{AX;k at A( zUs9VN>+O{+YtlhAG18K z8<&S0%c3m#dRHiZKOq_uYPmlwd)Zdbj!L2l{5s_rpH{TEQW!-6{z&-GWs2!ZpVUQ( z$9Qa&*K;~2U8CIy2=Jk(N~2wHWhu2PWq*!JKSrZ=<$8@#K*0GGOFVR`P<Icj^2d z(#@#=#%){BdTEpDeq)HRj?uLra0ykM)H{`09t95D3Qqs3A)SKm!yw`&1+7Fx1feL_ zi+9S+XyY-Os7~#V3k3j(Msc^_ihWf)p at 2bMM@ZMETG4fqC%I|r(9p?@w2!b0^)n$nsjzwZ5qrVC& zfk=bmLuZA)fnQk7T#dj#^Ns~+C<*^F&)Bh)#Uu~102*Ykyv*LlyULY&Etqur^ z(S27DJ`8|brAx8^@E897QJgwXR2P-HG1|w}5=T-itTfeHLk}5nDKvg2wb3dJ!fLTA ziAur>u{@!R+h|O98~QnHp|ET|m35Y4TdV$Yp`iSO?5by#Q_rZ}^`BqUdQMdT%{8t1 z=9Y`$|G}m9wgt_tkyOw!%!G@;ZMIj2VJGuN+v{=6 zHhGzdctgP(o+uQ&FJ39^x>8v8Re?Opop$?)R;^Ti3M){poL5a-Fj{c38iD^#PJ{6O zYckqQ8U64|;jgb0Ru&4Ic_Nn=YhEd8zV33tY7wh(e4U!tCU-UBsV%!`f^Lkd7(2={ z#q+r5Ne@=gH@!!^N4 at ulA2jM`7?LbYGYzTCFFj;PwSH-qAyt-3JHI1^eA2^)RPL7^ zF{Ju(X~%b at igKybkizBCY(w%_R#rZ0NFJ{=$AEFJ#|(*fqxtVhRM^K2N%u=n7?SFj z<{FaEUtT`Xkb<)Gq#;#%r7lCNE0^XQQdPOMz>or!(n3S3DVNsOduX})saWp%XbH8i zjy}^_8?tc}xaFI`J$^>(8VyGm7O3rOO12?*q{M_hQU=au(|;URo-#0R?hf!>+<0IT5B^*(PPP!&I^XLGck#!l)m zpz=(;-x~<+^g5Q at h)Q~FgKi*%KCw>vnNkKsSWtXIj>)%9p;KzFX!2?y?L(|Y8#?h% zpRNa5^Fv+xbBh}D8f41pMMiAq2#?5TjISTr-)zkbub3BJF)zdzVFax+N62`92W#YK zx+vxtY?ZCj_^n0q06*yLS}z)7pX$gph%!wR>r%gDa<_J~tFXcJVuO>idx>y!1(wIY zYyz&3QfI>x)8wvO2$T9-)$O~YC{T at b z%V@~U(sCoxw zzrWop_CfW|*rTnG7<}2*2X2tD6|#ok;X)y&{alia>DnEVu_bzkbi2n+{`+Dga9To6 zTcy~P8>F!#@hDiBm51|GYucs|1Gv(V^Hld#04^8)O@%@p2Dj_IA%9?^D5_ORDlQbp zeKg8d9O|8>5g$9Pd+ at -~?cznDJb2tZ>kDHa%+lSJ7zUapLE3;*~2aS2Jl`Lj$N>dcNMi-Ck?BZnw|jrt|M% zlf3?_cjC53jMH*vJN6x$XbZ|>9>uHGuY!1@=X1R$706tq|3Q9AE7&gMJP=%L<^pKd z=K3-kl-L(PWf=cvYz`SSG~*%7m=!dh7-q~JW^@dVJ!OQb$+y&p2omC*sp2*5S3&c0 z-Mpxq7xb1?5H`%~?fcqwhI3zg9<@k8A*{YA{I{*%EADQ4jOT_?qyH7;q_)>bg_IWY zyvB~Gu`_DC8a1vAH(raK1|uIeCN&s)qsGhzV_%fI)#!~Hk2Dy4QQ9aNZ$*vC4aWZH?Q9VX7SHQT zUPyW(gul_Bqx4^(v0knp;2!^#`cfgr9rd;l;(Bg@^ELPzeF0c50n5&Tu)Nw2%WDpn zr`=dC0?TD!*;P;DiMM5M0Mvu07~8{<(O<}!b{4_rV~l>e^w8?*!wo3Q9E7u(j&Nc^ zIG33aPD~2tGn2v)(5=VQhSh|Q0L)3@){ZcGCg%iC5Wy2TIPdJ~pRxAzU(%kwfD-QI z68zLXWM7IHZ>gD=B8js at j1GV1?2yFHi1ArPW at jYva>V$uBJ*-2u_I!6`;zhANH!Oh^6~#@Z21p4)*JEDDA`Yq z==DJ?_%|)Tk=lRLo6EL7YsdtboLZKPJ!eR`9wER34EUR#o3oGfD#S6DHI6ESf`Ii( znJKRyRfb!YT%WONc;4a#i7FX~qIruK90BW9md^vu(0HR#_`D8h=wr^%7xLb)zEJRW z6$;|Fuuxbp*qVd&9xr!D(^|#C^i|2U8_2u}}1{+6K at yX>b|J(He>ZEAT&G|@}TGQMG3zzno zi0_VMqf#W7kL~8JKT;nm^Oda%7SA_2Y)&mCxVJLlE62{a0{@5~A2tDdU1I90zxE^p zD at ugL?TuMcizk;K>9{MXyuzE(F>>q$LmE|Pyc4xOUF7Z>#c at zP5!-$N#$lU`0Rq3b z9 at 6HOUj48jxj z(4NdAxy*Q>qQ=B5*JA|r?qiXECoaH}Yd|)+2 zB_5*`VKkDkF|+o?C_4B9KgJcRgA)}22q^AaQAu}p*`dfTYGqdpKZfj-V)n;H_Qh=B ztfCrRGxs3_oJldN6F?=7rmA_%U2X05%fK1F_Eo`d%=z4y)NSs8%A+GF#+EWkFx+iH zB52Oem*y1FK4XY^mZ|{@dVcK*P(Iyy0dVE-yV86>C zDrw|~cJ+au&efg~tn|C}0p08SB*3h6SyNkiQ(FZtfGY8?D$;&uTQ$sm)!-lXcbod{ zD4uz&lBah3SY z at tSO`Mvm9yU|6XqLVS5hc~V4W&6+7W`m=+lBKQpo_EE5JO6(00{LdcTM`3da4%9bm zERJ$gKH3D7rTkeMmgC#XQ~n$~DpKX+MNLx`oz^;(s<4B~?K2ElkiB>jsz#y&gor;r z at dhWqi<Ow)B-HqZ)Otu3RXwyIiG>Z!SfuI7KMFmC*Dxg_Z;08ejv}>?E+6}9S z?fKc2!niAi`>zxhUMY~Dwea7FU@)VnN2RcXd9OZL?kk&K<7^s<_WSt1U#!{z`-+18 zm+}8{M5Ot?qWy6hYln4Y&0hP~f)@BNgV)|EkHEn$IDvW&xelH?@4B9|8Fbb9PfHT! zB_Bf2%w*bGIyw2+ht^bHWvn%sc;;y1?O2e##T$txUY_Jfh(v~e$iQ1<>l{^%y-7?d zS7S^?tjCZvahMS_mdWCZfPu0CNU6s$k2&)O4qQ4+PVc|bsx4n}wH`Tq19JpP?m at AI=N_ at prw(zfNcinrTD+k=*9z(>hwU0}V8aMz)m=Hv0_Y zLKJq!SOGdj^Z7>D0lXy`Q1=!JpNR!cU2Dgfpf6B zdQ6r2d)!$E?1hzA)YyTDmq(77i!evTj1Dt05nZry;Ao)vD?*KTaa2{7C}#RoZiB_0GusbK?oP-w^bOvwl(Tm$gOMz*IVOyVmj_u>9AR6pZCD{z>Lb zlk`EOQ>d at T--=kwVXaY!#@>o}$8&4^B?Hz+^iy1&dU50{v=79+5FsT>)GbDA{7wqI z&gvUMI(eR5Pf&mlQvD_C2GV_acO#9ise%gJHAwk??FOc5j~PTsMIG!KA&c<0%MNZH zIZl?cD@$PKju-@&RlV8?)EBpLZLbqotpB%Jc}lbD-{-&k6<_0;a{7?Lx5tngmD7g} z`+3skIIHISC7R&G4y8ce-J>tQ=(otgMy_J>k6I<>Bb6$~_J}{&juXK#~c%O8fubNH}H}98- z*{7{F@&DT6vh*P_^~CLeMAenR?N?IRN!kIFgXtesE`4lB*ZHJR45`*9o#M->(|lNa zhOekT<-NdBmi~cI2{B^K*YQ?)WE)7e;Fy9mGg84^9#aBOc<^4KnT2oLd|BQIXLAfn zYzbQvO8N5SkLY|&g(I8~>b+&YvgN+wBdbtbEa at w1maZ8nPe|HmUi{5M`mwC6S=0RQ=z1 z_Io1X5QYUWPafAo=&|;q7!THh;KzbcE!zu1gH;{oSKL{O!-!z|oFS^nILiqM25s2{ zVwwM1tSX^Yu)(S~$ewZUO+_T&P^uz&gH5K5k1C3hpk?LOw!NAPcuOd#uB27MK3WJA zmwK>nl at a}iVsnmtgleHA&lEJR!VSROT8%|b%N^#c1}Gc>igF8Kz?v+T$eUKElV9-) zb&Jod{u4QA;0Ylp*ZWB~hQL*NM#*Dl_#XCQ2GyMBx$`*=P7w?N{hRJEEK$E~H9jo_Lczp}` zR_K4X`6C^|HZ}c)?5#*gn=cp9e;4$KAo$)VmjdOXJz at vV6QMffO9fzXHszm5K-dln zDWrTv{}HnaW%&th4P(5^#XJP9qgbqX0!#OY%|nZhLqWhbs7CxWm|9m(RmBcPS*8W}E6VpH5$M at x_v_XLg zcW}Oxj}67-#L>lv^#LMx|H^q=4R{}Ac%RjP!hKZ({y8^b5KsM-iH3?jSSjbkhc}X? ztrZlw#at=`D0?MbbNncM1!k3-fmVivtp at P zv4DT4J0A^zC{XB69fhJuXZmu03S0nG03;UmXeQ(?fOTsR778Wcgl}d($oIA{xg_n4 z20QM!RPDGaru0U?>{yI7#)pRJjw;i>e8=pVDyrbt9u}?r#_gg)9(2+0Wah+Zo@{Q$ zbL(H|UYY at Ki?^@{Xa78mb&Ykm&4^`sgeemZK4=AQj!^&)hS>Nn77TW?zIN2|h)18k z;%A`vS>BI5C6}Mxw&0(KF;BReKAykRI=#gN>Qvf$$w_s6)N_yMjGgA^wCMQt);N-& z>!B4FLlD$vSbfcLU*Y}aOTNQeBG&hzONU2vk%Z`f<%N9+4 at 41Kf%56YIc_%}Xho~+ zuNwPH?}x2SJ{qfKt0P=<<)fEx5Xt#y$Hunm@%;6B8|4{Z^ba at GaNgX4e;&$3quPQ( zL9j+&>2ANq at b>k-x5=iNx0J@?m*rHGWJgEUXQ12 ziP!OZqiZztR%xp)i1cgKNJyL+xWtW3AF&gxHr6YYcio<1M`&5(;O<F?l9++gsgNyRUZ;y$q zu!mgK8l$^8)M%-=Qk3j=+b~U}e89~}D(0qO at Lv?Az3&n39C%1|1&AVv){ z4zk1~iUNDpzgLP}OP)&2kBJK#2iqqLbMmYhF at bn0iF2qjhdkq(2TZvvF46UzXTb9^ zvxy5_r#zqYm-mjC;;mE`#vtC at 8M7HekFqER?(u8BPQHyc5oRHxb-?R&qj0dr1#$`v{t5h zg4a{%-iadQg)7GPMKY%k3Y_ihWKKVvWwhTTLjk@}AD$pWWqhH&UZ$1vh5EfRRAHYi zSMtenWx&<=!$P4lO1nm(a4G+5=YN;;zwh$D at AE&(rzmGiIg_(CXVN1%lLB!j^RTy{ z8%sB%z@{aQcxT294V@|c%p#n?kN`2o{2A26y6U9pm at 8V!R5L&A$t;eU4Sca8oj4pr zyYYTaT}iXL>#SxikCfAKxQ`twHqNefsH#{a`(*p8p5ePM6Xf%*l0s68qWQQa^r~y>7jKBv~ zL*--yJ_tI2l at a(L7|G=!+5Y9B9rh+f3e z3qxq|E26>h^th69tcXQjtFtRZ!735;N|muZw2C5-`?X^3Ue3LebN7ne6!lt_v4V2< zhNx&_ysvhCYzPlJsUo3kU*Kk+JHY~iWOtdGpB9`poT&rT) z9?sP>GS|vjYGur_4L=AB{>(*qCB!*x!;J$~(7{C+eot;$s5QPi<`&`8Bf at 606^0Xx zIudIFD_up$1;kG;mYDAA*xaOErj3K^ZNmPY=p`pmN7ryiDQjOZ4F^gslmp=`MT4_Q zmRjy8wGgFv;NT%)Z7g+njK5WUFO7*v8lKhDheRYHfA#cXLVIo%TwnKw^7QK^s-UQ} zr;nP}E69P8;PrYDg%^TqsP4s%ap61^x6l#C?M9y$hT`jDE`!kd4({(-QuoA)#{cu` z;EUl+;!`grKH4#&KM>}jD`I$QkH##N>O*HT>tog%ARNft8*62 at 8JQSHC4hiTh&WBXeJ at CI0+IBtY_nK$Y22x*@mmIn6(Jt1t;(!=MpD@ zFO{W+s1&cQj&;rgUMK>8wF-+|%W$4m_DyLzEvoqW$o9iQ?h?7|bSw#}=kG?Ca~8oB zbIaZgnbK6bSXD`HP}SXX&FZ+ at Dm+%F!kn`Ru9#c)Z75gl+s#&mLEfB;ablOAU*{37 zX-v^cEC4)UnEb0(!`n?sj+Iu9rNta~FoW<@co`;E03;NGh5xEhOLkR=-<1~er`wz? z14yx-y{4 at SRr8zYk2a%FD;2`)Om&u}@cpsTmcrdGwhp}mIq#&L#&EhnriohnwN%g<~i2W84>hf_rhD^#t4&96Kzj!;5nPFhi*=fPa^rz78i> zcXK+PKUo(tv+F|1HH3vVp%Jg-*iQ{Ft-}L#KNrvS+E4ZV!HB1NOJ3>yChzek?hi%_ z@%w{d- zV0>LrA&4db${xsW8EZnkGEx3J{0hN_)V;JBe^)kBY%X=iM?Zv`)JY$eX{9o-ysvWy zDk?W)4VDj{8vUWJD+bNXLxJKe3%#^yWHtqguV?m7rxjsK!14ORbXdc04wSqi>Lo6m zJ-nyA at a|64x2A5zPiBcf?dvsmRcDU5#)Y}nB9Qj=rH-{yu!S<>tq00`td;WOG1DV% z1dq4cFOjaO;}_mk`N)CC#cdzXk7zqyU at dJtUSA#2dS8BI>-~HJKfN=M_EL at 0JIiQL z%^bhgc~1z(v+-g6G}D2;T)M0eDX^<&vWqc~jwc at 4S5GW`KL*iP=T`Y?b&BR??Vz0{e$cH_dm_{VJb`7;vU at Pg zR at Ye%F>Z)m at FB*TF5B(#E>NrN0RSEqOR;ak>tCPs5|>Ej%nAH zifrp_IqCr^N8Lzr)ct7QJ!6S%>+4)Tk9U$SvUy2lJEacfvj<$T3^9YS49P(U;a;mE zV*`43AB%Lb=-xnU$Ocr<&vjQ*-5X`yv#9QWz`Ip-V;pmDsPp-~zFLq2XeRB;|0!zU z2v85yS?&ZMWKjP=|E|7?@RWT3$s4&Vmpd_ZJktJAAbUKLJQivHFpxbKNxm0pKN-lr z7fBwCw0{uD9tE}@EXCF)8(W)Xi)T}dA4C-oQIbC}qRE?SnJOFiU-L4sr4DWjO1%s` zjQahTzyf9ji98g-|EuQ+(#UQ?lOACR&CXu;M55koPb7IF67^(HM3NsyT2mjQqmL3_ z6)0<>+CGTDf;RadOwnQ-6LK(9|*Avkk>u!hntN at I6!$t+H;}SsL~Su zbds0)vyI5Dto#G_puG+qhGePO`!Xwa0N%(sUucP?9E5kAgK-q%X8+53oV^*$O* z=7?BxA!A>N7nxh!@mt*d(iYK0S$RF)PII%Pg)Vp3I2`Z{cow_5 at Mh+AZom&p8qi}k zV6D{vs$?xHiHudr<6H at 5(BqgvPtdknR!*CK^6`*ql+DgQjzNBsGfkjOPllipLqAiD zuifmq?Q3`D;n)?|ZAUhui?Wl9P%gX-&oA^b8CC_X? z-F15~s&s4*sz}@6ihBCY%%icsDaK+K7G>l1rYXkSV7uBceC`jl&zwO0be75i>w+?( zE;DmyQ!mxI6Vs&}eis)t4e<5upxJ)YgpQsjbsFu$h1v>eGvFQ1#heDSDjP>n14qx= z-F%w5`82xuoSoxgd0oI}15Jy54ox;XVR?V_)uXpW20 za+G|PioS|YL}VB8cFsZMeA%vKka7;9l0iGi^T;v8<2 at PU{nH_eGHW3s({J|q5DlyG z)7ccCgA!0K{>3Ev^W`Ma=&%iCFQB|)*Rz*O?nOPX+BtS3$7^}W~o zn2n at eK+>N9g;{@mUE-f!nb))2WcZ5vhcQ2-4qp$3akkRiSh<`jm zO%4-ZI~*!5EbrT}_5jugc5Qp8+#b~SVJXRe{FmP$c|PIFgs6sBx!%nlzKTk?;E0wT at 8c5dfc}eSI(vw z@&0+{#-=G|h7(OFh+-yv;b`iYTr-I at mkwe%ZdaiME!Qcck?DhCZ8AYHxCks_3~E-V z>pX6;{O at tk8cObQ6WZLy7yf~q8DBLK4E5K}DSXCJ at mU+vi@?A+8%Hlv$6o}F3UwY& zSvj`iEit)CFRwV>NI zkeTVqaVVFXiT2#g`5-dSbem8ONz!k2rkggk90%z)y8}6I;Wl0kcD5< z;NHbPBc*mi60VCcKlE^JHEP9?Vb|+T}nPJa-BYJE=9SuUUgezFqbRC;R7>7 zUG7M>%T2R7s?fCN!v{E_t>7e+l5l5d8nc>8;>yJ*O#%wxkdT31L8KJ6seaUL+o%FE53MLp7gJvz`cb zJP{&+oJ8sc%{2+Qb`Igw%*>Gd4Fq5_Wo8} zDoWBq+bK5fVWopIR)a7*`dif*$QR at y5MrPALx|0az!{ct;fVFec)!zMjLV+#CqLrw zSrPuopZt)+=S28JfASL!7ex3IKTYrIDi9{kZI#$glbB91H4j@;lSZnQMv8{0m4*n% zmh~PQ!-wDx?gvwUz_9iQW5;s+CCqG`--M#Q at U=FtfqH#fUd3q(a9iChas=wp%P6ji%O#C#&s z7mLuRB6JtQd^v>s>8R4hCO5?shAww%smT1f$h>^a%wJfUSBlJEiOg?>ICNUH<7;Zi zs^;W&T2|>l9&Bg&$GhDxP}~b4ISnNJ7k9 at 6yhdS37AbIjJvwTkW68X;-sdYTUk^2r zS)Cpe2Y)2X%o>z1vBA(T at n|TQeKbT1&Gg}%a3oxXU0ZMr`O6}`8*9>7nc6zNwDZ8~ z0@{sHTzceXuGP4kWksU+{3aJXm9X_&w9r98%1t7;*^9y+R)pUu-Hgd9Hdeo6DYh)C zx3(Q2d*Jl2UNV#5!fJW;jhm(_b|yZb{bv at OId^tMoDd$ZZ!1`VfgA at q2&@kDbj-|=L#i)|_> zZc1`nC=sLmacaz+-4;TOqAceYg at k}q#sZSr at pMdO46dlho-Q}|l^pQmVLZG5S3s!0 zh3-u1t9SBwtLi0N(1HV>`XXDfrryCXtgVMLK`@s)#t`#2@&BuDmZw13%|O+l!$Kpny<^el# zDJ3qY#HC1lpc;uAOY3{kuJ2h{-(0HiZPd4^9*1h8zK81Z at HS@rJ0W9TWA>epIJH_E z9H0fQebXg)Xd1^f^>~B~?m~FFW|o2F+k!eOzDIRDccJ6Bo*@2TJT?XjBlS2b#PeUbYKaZ?Q#aHr*;}>b$IuJ?IFvk&-{WX= z@>D4CNQ3cEllMMRa%t{8L9gS>Ewa$?~!Gka-ceZCpe%hr0Ar=s%vpkpX=4w&BUZYs{t=rY)8 ztm8QjPJuoSn+8x{J&gZoE+2!jtqIPRw9;?Sq1s-7dC*xMF2vnfef3B&S#1oS zdFN;?**RJwGc}`78}eo^{Y}o*D0?1xvvalJOh8F)2C9&fG&_=rr#5Cjs}Q;h5=`)F zR>|RKK9TisB@}d~9MWF4=VZyF zw3{8Z?{U(cJ~gd&rH69HO`*)5Na~zFaVC_Dp9!gsH~pThw+^o7XgFK4gI{6Ue7YXj zKbLXPtR;>sIiDIkUrSOj`;}cAb2 at iAT!8Md!Mq1BI~mL*4w|*_bWOk6_d=rJX|-7J z8N1;7d}?rdDT>8FH>}cgJvy;v_LH=gpw4@&a0LUfxjJ|4%le^F|&69Z@$Jq4#f9P0MLC5 z!c7|OmPD8rvAlb4qhR=F6MkoF;Ev49o*2lp`i(*%ag!FmNdx>Bxb6RsxR5hu z^9)Hm>~6nxQVUkVU;+<4JMJpZ*>@G^h&wnJ0#|j*B2tMi>&|apbx*f^WR`%;sJWWeFXw6$a92ItvW|#cNs%=np~7iL5?_FJCq4+pKL}x|sp%3Lwbf;^HqotXUG{`C`U06v z6M51}yV=hwEQ?JQ5 at K26cFkebrqcQuzg=^Ubwz+=Hj%TO+Ih663I$`nW+CYWA?e?E z+O=ZZoggF;*%w>;w_~>Uo3=!~Kugdq_;lmj*sEN3xi-2syXJ&GYg+Bz=6=GBFVEf> z%-o)jDXyflFQ%yF$_p`t1i}~Vz3z(2yZK6YNZg-_ePuBDyo&^1ac#M0LiUIoZWzBq zEfr7LQ#N9YV;%crigmfUyvaD?w&GumDIJGHE-J?k$afbCW>gs$!s#Z8M_tBekyR)O zml}x6O#qldHzj5V-N`%L1Wdek|5ClLqN4Im)(0P|>nyV_OFxTDACAHmkZf|Nx3$w} z!oQqUN>}=42p1Uq&KBiL2dHTTqphv8tmAaE!ndL6vb4V^^?F;nET8uDwsn at LPPL}X z1Lf0)(`A`stpuOa#iOn1vW}0b)JEZI9@))}@K({%>Dw7d2hv_`G0_A~>KdNg^0xI{ zNK}xV*Oc~w))TbR}d!mB(1 z$^ndtQ$8A~y!IXqRFCv$)vnq%zqbgTOu4$jdx`T(SbRQwNq^1lLRNhE60BR$!ms%c zLWPq{y2^S2oUkX}0B`H`;p!i4YQ#7lXA0{2C5dEdUuvlvZ>Jx1qj{7Bo&47d1$Y2J zfzZeTp42U#V$Qdi3Ch>IiDUVWyk>X1idg9Mz%mzTNP8F$xj3Z at b=#v{_GMy_D=H5# z at ui1MBfYuiIqGBD><~M4=A0j!_A?QP_@!HWa2bw7Mf$U2rT=>|eRl-jJ!E=vaka6y zI=i^KRM#W;(_;QTkx>0ecviMcirE%MLOS95qfoRE8aFDxeS1Gx551QS?7dq0bNAIngCe{<@kSP~VA?V7;N#zqZe zNyo!qLrke`GW$!Ask_rlU1kHV#npeUP~USD=c858Onb2s;?|>{B%MQ)E=2)Nq6_UIwtDkTc4T&cgcXt~_>>1QX#{Vswb1YOu#-Fimg2xf>QLT-;b}}bU zHXahT-}#u at F)m*@8&z|LF|EpIL=7Vu~Y;F!%#! zW30wrut_pgNy8k&6Pe<=>^Aid#_A^Z9#=p6OLo7^JEXw!Anlx!l~?OM9-(bS+!4;l z5;tn`8;RR9PK7#Nu2*Pl8O|rRRt*>@LsPd_xri;xx6;a=H at 3SimCaje#a&Uaw)Xd= z4mIa>3B^^(LjOtajSORI;7NL3QQ zocc?JgRQCCt4kJkA50YnX!$K}0I+}e)_i}&4lv2y0lbbKz%NXOX}u4MPcMN2z at v5e zd%%_Uw6D0xSg7%9g`W0>j_-Mn?_S6EQpfih)&3?CDRnGFzwd=oH&^5Fr0P^Res9%M zi|7})M$c(8*%eEBa;d>OH{7l+MDclQv zHCW-(H^=sJj=oekkzU%9dALb5}3R8FjmnSGlW}s#fXjBnUtz#Gcr9#0v)fiRN z>-BD8Er5V5GeYRBo2nwk&4M+l`} zMvk)7K#=y^v=19 at uKj+7m8210T-}jW(MkGVRE>F#|56aE70+Zj;#n5sO;yXsyEPSd zDQR!b at 6blQttL;D&kbBRvVq>zkhA&?sGfS?g5F#3JC at d~?T=nk`x7wG9)|PAL<>%y zdnlk$KXcA*=B!@koSlTNaB) z`6b at orWvvI6B^B!cfN>+&%5yv9^HA`urZlE!{0rKKB3luvH+UYHp z48fy2KO8~RAir?hwBdYiykERARDmR=l6dUg!fC at 27n$8LA@|Y_EuAt_FE&))Qz+2y z1JaeFX9lEZS%jCj94~J}F%mf6ZC0!XoQ#D2RZ1zf|KOBvF^zpuCAF=FWZV{#af$GK zHsSl}d$5j?L0`0S2MC}1M@|AwJla>1!qWh(iVq7zNi)`6Is$|J-|nxu$ZFUqv75_6 zT?tnf<(Cnu4qsS<__m at 3@jp_zxB0GO#1+pw^-_%)_YbQyHXX>W(x8g at z06><@es2x zl<{3Y7>M#4*xTL4dVW=fe#{hw)`-v=4pHW#eAIab?azIhe9Vx(*jk}hSS$2t(psVG z3ij%cdb_TGj+A}6x(=t#$9ZeUjn_KO%&ya%74Hs+!ngUR2OURY$-UIrUQNJV;y8W&&PbXGrE%6na zO6kU|vdmi(jZI7##qbt8yiUW&2+UG%EO>iL;Pn%{euB4V1iUpP;8FO#Qg{|U5uR?O zsl`?}8vV;`2_SAKv5fS!-p;pvWWLQv&AhAjrs!)G(DjLpz55XS)TX)n2z&QwPVyd$ z=03BFKMR`sd?d|1D`}2?v(J{&+#3!sU(|aoF7z^CP?Ol8#WxUJyG*NwCdx7`(O)sO zze33_(nZK4up?9%woCY40EwrF`<{Fx??8GpVB)(n(|<-28CtI*H- zvL1FI+g!FCNa8`w%*7wnT;}xQ+;n0~w$Wz0O#^8h^$@E_l#wnAlyzM}GQ}v&UdfXo zPaMuefj;`XCD11j9KXv2g5U(mSA_Y#_##0h+udT_7%`exGMdFWa*<685(NQl4Og>; z<2D5t)*1`eGOeqDO=pVD5H=|eAk&3+;;6Errf52x_D at fdu;POw+miUe^b`p}K8Bd; z7GIkJLZ$30KH}qzeipk6DLtZ`F(l4Eb2F^SH52=B1&P(8R-9R`VZ)hO!$2O1p2e(d zCyH%=$W9Yk*NUv29*W}=$71GHlsO93Gz~hT;%0{yS9!D3r$w at TnxrFY_GzZgi+N$@ zDbc49y|7_UpC*ovq1MO~pj(w!Nqw5sok~2Sbv&XeY|CC^;T7{&v|>_)t3`$GL9^!Diek3s8zKEP zFe2z1cS6v%LDr?5wOc;agqTBqt8nCg`Kp!KuHN5M%(1t^&e31Y!Me(4>%GKRf8`yx zytUImkonRxtJ6D>*+W0RF#SFRgEF)feM7z}z7C6q=AyLr3H|PAg!Z38DIT zcwue*%@)?RSfq{DUB-v*jUArMkDwcg%OA at 5)QVs4j?EbY? z7rT36Lcjf(u4s*O?SM3Jgm}d45s#>{O;(7NifS-!Z$cfSH{#`E#_$AY_wpMo-##{q zeHMy`c>*zt`w7Lr(O$C_E6 at lOudz|QrWD0%iYQ)Vp_tzbUZmxLR_^Xrnzki4{;mK- zUC*aI0?TU{%l8Q^TWv|AWOl2TBxu=18ZMUXK1wwEMxuF55zTU0g%^p#K=WabthG&6 z$#~w%c;0H^nHq>`l(W?+jf!4A=P{ljve| z&Hp46e22xi at 2HfPqtT(=k*Z`P!Cx5fOlm*pWk0Oqm8~3gi?vTUX^~GRS(gwb`$FB0 zs5bRV4BFTY9$HbhxJAvayop>Y{U3Hs==6(5+(A6g-I~z7omFOb%qmkeHe8+h3wp|A?q)iI&(^J#|;Lf(q$I&CWjU`%(V4-FSb zP8$-g6=olKKJ~aB~`TwX$k1gpD`!_Mu1%?ZqwaD<-zI?+UiGKVH$&{?>$+ z_FF0bw5O&0X-X?l+L}nqUsq(`(34BTe@)~TPCl#uOvyg0Cr|4XKCLI;)Dpj%`YR>- zrj}eE7ETJY>%++|WbX#)l%+)(5(|0s4qAh#CrK zh8t-RZs8b=$rsQ~x|M?soPiAsDBu3Z;`?iqWv&y(AYjb1VLTV6;B5^qUuD%v^&6 at V zF0PLB5At8BsZZaT*J99Y7&3|Hbc3^*!5(@j)6Z1g8B5cs=fXW=L!vL~+s#%$d at dYS zG6!P(hqVxg^F=Mh*=j!XNlV9U;=E=<&kYw1nZ3=udFUk4(&MO?7+sTfp{R1I8rpBU z8gYmFGo{#;G{msm8(i+nswvii1+kv~*q1H4IACMWF7LxBK<+!uO|xOcpq_FauyHPItP00p3%e4_!|~-|1?O~$eVR$^OMIW^PP`XxjlUPhLUSi0K18_T#^j`7 z4mAlrEgXp3Ga7L{PQ)E6BJLOvcNmCU+~Dz7Rtc38R=~L+=8icqcZ at K1Y!v2>G3Jix z0MD2^Y+=qgtr2cEg{Og|sQphErS?s0gHOnfRO|94+PHu|EWuBp3C7dmz6WI^_G>*g z6Gx$OjxNzSM|Z)PE%8Cvgo%6ngRndCpq2Qb?lL~$6NkiIdi*XbCooPZrY046%1vSL zloIJniby}~MEYSM{jP?x+RCb4FIbJ+E*i!k>{s+xzQQ?y)t1K5|MB!cNIjv=a9U@; z`Y21D*3jbs?JONfLayv-E%B6Y9Kq&9ZR+cIN_S1`EKke{r_1AW!m4pd%gn0mx~8*& zQlrW^NT#3D8qkJiYNi{1X>V5AS|~aGc{o5n7$U+WeJ!L#B6Bci?A3eiW3bLjE}yOx z0L1T$jlEHfQpe|EAG(k(V>y74Lk*Z&*;Dg96&ZN{?p+vFD$5vLXC=SQ%(clhS?9>8 zLor%`Dg}v6>l`mUJ04BY at jfgwtiAzgHCbE=gNwHAOA#tVvg=Yly=L)C8&t zWc>9ev;jX1gBss$=_WoG7ImT#hzyO46pM28%0dCIk<+5iO5A*Nmh?E$ED-E?Jc*5A zWk-Ld74HnDgX!^5JxUTep-6U97^*WABynL=Sm1ejXBB-=XXn$XtV-p}5JHY|mvW88 zcJUG~M=xE~hvNA%dmK|kWi&PTnDda9t{_=|cBP9bABRsm*s8l5%Enbzz3AcD4NvTp zr<3qZ^?#~^6o=(6tzSfabM?$;6|`pcjniS9vb>2##Et_WMT<^yhsL%wM+D{Wuqbzj z)(2{4%JqVBy(N_EqYTu)I*W4K97MZA5bX{P{`9QPje-hfFHLirQ9Jr9 zm1#L=;QToGn9gvrkLgTVkLg5NkLdy%a1%qcw1X&Xr?x6(atJxb#T at -Cw#m`cbQzPE z=^(HDWuPnHXu4Wzb44b&-DSKB!3xRL%&ZAMcDbEnEBK}r4KOPCDdjjyIgV-+YvL)v z#G_#|{%BZD-WhJm-We{{ge%?7#0l^FwZ#+Om3FlRExR=gKQ?x?pP|}5uJ#Xb(oZ>7 zhbh*qB|#_gDOLMvSiL|+&p0YtWmiPyv!9`&)pkY4fq^v*9?G!@?mdoE2ajt;uWku0 zy}I#0XfvCbFRP*Ki3dUy*YQ9|&1?)2{isF|CqsmvBf zac7b&US=gyz|yhb-(1wOU(2nnrq-_3*%oBAK80pB)w-_1M~qXx)EYz=)*!mj#Lnw% z4PskZ=p_~wdT_b4)Ch`1X{n(HXDR&XnpNm;6NcGgs8j!z6SLAtL?#yPM3DGrNQ9zW zgobK_k%(S2645bvz`66I9jtXEjXHFPQHP#9q)}g>%bq=?CGV at T0{mrl(X#GHyiN;w z_`GHvc|8};>$X`(vY*JdKYTu)Uia8U at ->KLJyRntLcS&%`C1!`?Cy44D&1=rUkhTs z&n~`}sBmp~tm4ZY#ry5zJ5l`p1}_!=M-CEcLIf~-YVaoAI%49m zao0rSjEG3?qzdUjy9Fcp%g!w{^hpl$$Y at cf7!CV1o;;1PQ@?2HEf*R^nx z12(35qEJ9J8*Pld3yeHyPwRK7fA3;iZ?bc|hS~8DkfMY^O=JeauPaw0qia7oUl)JZ=}CNnM#aW>*$Cy7Giw zycb=0(k7u^>PoLpIi9j}+<_cVa|KsYaCws%WuuB|gJ#sV2KOX8Rmx(!?XhpLE8iA!ScKUqA!ymp!3^vwZkE9LZhKcgPJKA8Q>@sPH&C at 3F1Rai(25o9 zu`8nT+2feLd+mx|Mn%utIbNn5FC)i3JICF~@dD?7XVkg6b>6}pFV0)A<=#yN?+z!Q zCWJn%CHGK^_So`uVwY~lcj;)$eipPz4mXCy2Kz~PO5!c8wc{-f=SnaBx at tb5YCZ`k zhY9|$E^D)^`I6A`C5xpm*{wf;)(`wTuqO!YiEwfSfn6~Q>@(E*XSnqygS`k~HMZIQ zMDJ3;R8SYF2jXlj_iOt=gZYV`JVCXb&_-4^Onn&UKIH8_yn;TwY)`;fsIjk%IRWo- zOu#{I25u*o)v(bXtHzs)+Fz{1+ZE;%GxJs~QdK0ZM4T(GfloQ!)9lc$5L>o+%Gp0-{0TEq(GG%(oz(2MuQG; z@{HD+J)iJxg^?a at wpJ}v~m3UsU>N&1E>v=M4)w3Z?c~QY8QO{=j zSwQt{;Dxo77FJ`v4pW`PVLg6WhYXJ=HF1Z*Lb(*Nrwt;8Pmbhq(X=5UbDNO3bKhNn#%F7~tfO58EKDU at FYr>wnjy9$S>F!`gke0fto>Ow1>sduyv2&+$n`$&qEK!Xyu z&I>2!(wa3loaon$lPZ>UsksAEnCYgt)zgjBBNKMGC~o!ic)#v~XMn`l|CPdD>#pRB zuGZ{}I9Yh3!BbUP^#E)1tDQI*DTldBMRK)HyG?erp4_9+M9J>aNXj#|gtPp&CG}w= ztyD!xudTC^guQfSppurZk`pTXe07+>7}sRZOw9A?#@9IApr#0UQe0$p{YsqZ2LGo# zoAHrY0p|f_Qbi$M#%JXl1b$pKcO)GUeM|e}JNzoT zw1>OY^6R>kSfi%{@ijWi7?I4w6OD6hFa;39ez3`JvIH3i*<8=&vES#{uW;IR{cMUN z0Wxt)%R`0ul%_(0q-l{izMVNW!FVvtupZa)+#6SpUygc;?ML5gaJ!jTCAhR(4&oV~ zGRkUNkcVC!W&cPieGjY4O+TXUvH_Wc4-26PbE-N=L7#*~fUz z1h(deEkpl#;Te2)H#V?A3z*4tcqh@}oi-ODw^rPaqj~EgP#kaY`zx!S;RWj&m?Zn4 z1bUa|GR}qJKZ)1;RpDH}w0AhDOND#mY0diDtn~=bx>ST6=*jxz#n}#>RZ`nJJ at I>* z6>(c)t7uv0=o`UBr9H8Zs1o1V^9^bDka6V()sq_6< zj~?&Q75ku<*3ucW63OUqoB at MCWId_HPZCoq`e=2+*+u8o^+tR1J5+Q!u}H`JV(r!y z%fe}$KHzB_RHa_k(!Thsnvg*T5E!7q942GtO69e}u)GmWo~6y}EI3uRR{T_-CKWnq zc&Awd|Cm05^{`$GQ}v**G| zYH)T{*t%GJFWiFrLmIwTNa5S5)b?=V5bi<_g;isNZW+Do*JfY_CO1&V4SM2eIDRyY zsrMe=#?GWYJ{MIg at pkm8@bn?V^7KK$uv#bd!-B80?t?l#^o6TqDAn+_4RjmIaM+Ny zcwA^5SxG~jglAR+Upk#|KZMKE-dx((3+4bXdGek5&GLN|7~8iFIxgTRDoP8prYCh{ zmlrJ5^r7O#c at 9_R#Y<;9^D*5x%S`O_;o at C!-h0?%vcB!;TTl<~l04vSVMo9cQ-~Io zPjTzK?>Xkk|?$T at ARKrHy=2cc(A;21ze(Xu<2m zZrJg96YZ5l!Jom0!1XxeoO%eSURonbX3~R^Q5_r3qs9Q& zn0It*h`Z(EZuuGc#7X!{x&=F at K-zr z)vNJ_X~4(Toz_uZt5w~sA!_Yu9W27wtws5qC~xaXN!&;c1VYV64Q`qw3suX-7t{`e z{|54Uf_F*W-SSd0KFuqgDK8TFWLpEu4(Ku(bv!{2OUm) zK_Brih!rZic{X9uQ#s)qfqnr?8!#1q}RDD z4nfc=c0YJzD{%sl9MTgrRY$I=GgVi1NdJZFS~XFqH5Ti}ux1SDQwz23_^lx|xu2%n zevKbDq;|4n97{343P2^`IyW!9>?){a_2q{r)n2T at V)2Dj0Ia`Psh3>&`|yn8KEmE` z-i|5m at ya_f#dVGHG4mtCIJAFStJzP>?9+d*7M%M1|NjTCEPpipcqTykZ zE$C;Dp7=6MDL8U7R*Wig!YZyz@5(22JXfi8Ep0%bOQ7ssLSjPsq`^yYGh at MxpRG`W9U~#x^l)Y(D*UF@;Gz9* ziW;oDh+A-!T2L-q at aY9wQ276(1)nu|Y0CVE!$*n55OO03p;WXQd-r~=gs{6oBOV#2-TVz5>jUnKOOMHdFTj{%!9;dh zbB3CEMzdVjJcN`xM9MA1Gu>hx?&IB0v;B6F@&={6VW<2GDLW$kl2cBLWOG{Z9~60F z=oMVv3}|;d at 9cgeHrTwSHX-a*qs7>?7fr9`Qg+ykACIX!y|iO_G}`-kA$+7zkiL%& zvD8v}veNlz|9=a*wyRGulHOK7P(OB} z>uX*k%x~hd$GA^o?mmIJ`<$3_xbd+uH^Pn2f#1T7&w<~_jn9EnH$DeOy74(sbmMd2 zJZ^jrd=oc52Vx}+D|O at Z$E(zJjOpjp?XIVU8=v>tjgP?m2kge at OoO|O4=5}rEHEfz zh0Su@#VpcS<=}kv94i=;V2-Q*0tYOQ!d;EXJ&v`yv4(;Pgz-uvB%WVV33hYyK`qry44zzJVubz*%(Byrocp78_b#BvLdWN#G;i3w?F+V$p0 z#_1&?r&qMTyAj7L-=X#h(P!F_ncE;=$jga!wf8~U25NDx&(d+AvNO3am-FsmwU@<| z(#Bdf_DwO$GasSgjg8=wH0rIG=nJD$jCmom<8TDmeX}^VmQ~@!9UHNrrl|6!Mw}4A zlkiKBcua^p9WUu9Hk*r$!)r8QvtvPM9NI<`X at Z&A-D+m`w6gP^VKD2$*^TriD0NH4x$VC at PCaRu zuuJ8k+UX$%R74W{S^3Aj3t5?_wNdRV+^t!W0 at Jvn$aLc-VKsb-L!DQqrvpeY1xii9 zNod-u{W}SLmC%uYM+cgXwfdCcpW=yV<9hkhdyWJT>IXl8n9JPg;sX{EN3U0>1TT}? z^+8L!KJ!>Jp#yw=j$i5+%E at gwH_+b$2M#cGxTO)Np&d-$!g( z)xqyLAe0zQ^JETv2Xi64PnIiZoOH3x(Zm-n40N0 zutPg8;danq;8kcTK@$MV!^p at nfUCGW9MSbF9K(q_2S0 at W0N%2YhXZY$zENd~S|H&c z5E6b4hAccGgVy9X_G(k8C9v~o#2vRV%b4F77*|7ar77&^c&I_T zv(?#bY7)o{HsaET=0mQL3X~U at pss&t>#X2ndFj~0cn?|x?8;!1p03E8Qu}*qwo*&p z(W~*`#GI2fdw>2eD3`-1SVMI|T*Pgv!d zC%g;e at 48(S2C&2nTH0r3PPL}Xs8Ta18e0gmLg=cvzv5`O}@uI{Dpxa3RX|@=3VRYaJ`VRy#~E!&d!fN^Y!nbFrh1kGVKn}&sl&}BShMn`eMI_+ zQ(=jM%2EBs!x!U)HTqlpBE&zlVG0Frjd0M6p9&Mnai&`d4udu0w}#!p|4t)PY!f5< zx2`F8FRgRcYpDAb(iq)Q#f+L;FPy{Pnd~$ zPuNM!d%`8eyeC{r%zMI&-xt3zF$)}<@fV4hcWY*Lx0c)+7WBI}3>C|?r%1rN`IMed z at MR`;wvgByri#t at W+K|%BnUE$b^tI9KP&5+F#8I@?8}s9U-IjfCIu&?CT%awj{nH) z_z}m#_^Z)Ni|r3+^PuHXSLIzt0Cyd9T7BY=Oa>uHCH2)+~@OlJ_on^4yI!$(FL$MWKZ;jIld>Xw&n(WJ- zhtYu&fd12ecw4-C!Jq>>}#EzP0W z^db2z$IX=nZVojxGyNhN(ls# z=Neu%{*FDHv&JeEd9Y at T6KloL6=wM+;EhK0*7!iSShvrlNda z^Jz at PTj952$e<`|8!P=)HO~iduGi_6_hmkQ%Mf*gdR at 6ls^G}VU#P9uRXHy! z@;I+cd#>YCJQ8w(>_dqU0I|EgsjGaR)H($vy^cFLsw=UEZ^k;-gw<)Nl`v+N!)|q5 zz*q>!0C<%PCu`EJ63+X2YBH)Knn`(TuHf6fn{?UUYs({eo0dMx`0jb{p&vjVylK5f84h at bu!hsD7z72GCJRcDqJ{ zNNfg50*M<`*+T#`9 at nSdpt`By at y)th?9Ir9?YRXXAiWj_(WQHnq6K0&B@`M<`zrJ9 zaN>cmJX4d(J{=E)U45+Evkf4qe^`t_c7Hhe1V=uhC%5V{l0OKxG=mIAU3{i# zu>#c9n$Mw=tk=BDDBr3J(lDOTr-3-=7j4{dT^04c6&Z?c-txQq#l1#SAsu> z*zv!cPLL)E<%q{gAm<$>|Ak0|(L+J+6t&O0!Mn+e%e<=3VoF)L at _9^gd6h3>O2DHW ziz)tc<;$4jyGHpcrc`;A(=o;4t*H1qrc_*`oQWyw^~%|p;`J)$Vv5_V6k<>c>YA*S z-=He%8Y=@;p&@=8sME)*l^ZKaWER$qF;}CsmxhfttunFZoT(5c#2Vuh3yfc&+b_`V z;|F<4am|SK1JP`sX`$Y4q23SFw>pLuK->-Ap}gOys6A at IAB*b;>_cb=uTd=UAhd%+ zoG#enjb zRkdmr7s8_4uelATDz0)}w!*cFUjaJhy3 at VHJtuU-Wd8{w6}QqoS#i5TLVhM87n6{e zNl29>q}eEUytmO8tgIUL4_wYJpsp7Vyl~9P{-CN_I|IpCBq`0(t%LqD at xd$nrf}c1&T<^X zm*KnUg~Aa&6zdgE7z97xH?HVFN}1MK(b`#=e2E77B`vs9CBb?dZ09FYzqjdao#Qgw z^c|h!)8o?PQ`_{w_`tXp&fbFTZ7$9(R3#TyUg9Y8^!dts8}DdZMf#=N2yPI46ro~Xf{)D0C^KB1nVsX{3i?zeJ+1*Gtpqdhe*>r0GDZ#l zA0pn}fvmG0%D}pk)S-GuT)MoEgfb!u zXClL at 951pt1~ze9Q(8Poo at QDi0c$D_phtQ>s}f%DQa6>QtBOw0s?t!$g!40ersk9- z0RgVlR=d+xILyET7+_u2mh1-os^o3qT=uqba%DK5T^UY(rkmN%92&0o at j2Wa40!vjr4u^$4d=7R_)Hfgi6=4P8Rc5ZijQ~^pGTQF8QN+$5_eLr%$f|f!kZ?8FCnhQ<5uo(ET>_<-l1Y7AshGA zu$r6{XMTSK_AMr9Mc8;JoVZDC5n7?L$kdxuuf;ZDBog-uJyNwLes9=)6|tF7)})(F zZBvyEjTOGCnw5fO_FP^huUQzY5|$ZGKFf-llr&1U*DA8DOe-Q1IapW-{snV<9_BN> zW42uKmkeL at _How2wg}TN0+M1AsX|Rmp{O_DHDlYYKnwvvh6qQ&3~p! zi}Q_4dfm3dU#nDtmL-0{YscvdTIa5FjiAOKoQE1e(u`wrOr at G~WDH`whr7?|uJ}E) zo~zL-zQ-!3H#0rnG8w|)gN^0Bs?ZXDkt%12HP6j!p7RU}aRGS(bx>_77RxqiITois}HBTKmxvVC&UT9hH+9T1*!ysO0g<8yyt=Z$U zXOGvOJ+3ilk89NIVPMs_pDP3(BE&DU5f5Glqh`<85knU|%cA(T0f}qaT!z-OS{)Bm zKn at fR(fEDI61<_5tJR4&sH7|%GY24G+M<%NdmZ=kuYEy_!hJPRGIz-NKeYzkb>2a at p4;Vq5j#A;$)O8AiY|FXtTTP; z$jr)~>i_ZUmm~Kt|Ls5LgtlKg*LAmRg$siCIq&Vhg+6?h+Y{OCt*E$lvQl<~s?43N zl$S$5_weEsa7!`$3 at vAFY>v`$cR+7s|l=HMh)vfiZgljt|;If}~VQ~Mfo`ONc;splK>Ir;-$E+=d` zH-A%*uW1aD5-Ej1J`a%l8VNLk+~)+jg;(PAz~z!1{aRsGZIRFsa>c z<5?}2_zx5B$)!C}@6`YBLI>Y)90YF#U_)Hrd6m8Ss;cM&<0T zN}@;B>XrG3rM-PJHSLkcqDR|D`5yld1}=v+_K-M-A7%x36I>o4@?KtAp&d$rd=LR( z7x`#lh6)9!X>DzU>Us?!_K=2?{@gU->Y9?a at x9Iuh2AH4TA?Ni!RSHxngzvfU`L?K zn at lNpPa7fuE=TfP%am4lPvX7jnQD9Q!F%HT*UNv4JI^evUYCk1kZW+RP4VOTZ;=01 z^WPfesl}fVGETToFq)EYVIBK~_Z|D7;uV-s#+r(xM}x?~jB zFw=1bBfV>0H;#9~m`A9HlRQ8TmA+TeS|Kq6NeDn{uMiokMKD+ZQ$Vc0*{BGiLTuYI zV;{S}WI&0>1&cX;VaMZP7uO4N4n$59+SqzwqJb_^2}Odbl#ttVA5v&yRnqxf4e;VO`}BwqqjU zh(yDvu+TYr@~WTr}oI3|Ie_mK4`qnH-^khSvH9!5iAxpz#RS at ow>M^@3BMQ?{t=t}^D-%hyz_ub2~BFnLqula)Iw=Y;Nu_#w$npkYXkC|GkryigKlIveKX`t0pTo<;v>GN?kdm15dfKX0qZf zSJqBeE-qKrO;&>C$~}{n3FXTA$;w6L%Ds~zFWfg-88=?(CrP1P+1>~v(7zG>ZAvV| zZG&(&vHZ5Mcrh)LN;osf^r|&ZbBe?PJbc*9hCk+p;#Z}brSzhwB*TC;f+s5U4w zooY>cGJBizy?EwKfEcok)r(f|`QFq)hz3tI!llJO3&27N>Q5bjSrh7dJc0|i>Xz{o z9v~)QOTtt!+YjVYAI9Jz+Yx0_6cnBE+Jl7x6(7eg=yS3KLN0lddE}o#F{$>?ZM{=w ziN&X-iFz=WLwk))y2+M5??8bVI%0S*+t?cxv3sCfYQ at 6U=QjoaI>5g^5_~rRb&muu zV+s7zto-yXaQM at Q&1s!thsonU(_`(q!!Rw&g^v~rdDZ);9@*~9vRH+zIv5&NMZ>f)gBFs=6NM=?49%8Z9S(}#_D7iDHmfV*Gm@#F)IFD^(R z`-)ZCqn_2>?5A$8-rYNWFmeB60u#S~vT7`GnU(kO&cWP#ej#6cQb>TL4JP_`(}$o` zC;Xx;arH$f;C at tGs$DiI?O8?tqH1s2*E at YE`LLe&s at Yf^&UJj%?8-i@=c$3Ue>B!r znzwkBcAocy at +Hp+1^0onp?+)jjoV-~SzHmxTfWilZV at +qTh|#hv(_bTwbsjNYH(GnnOQBY25Hf>ObgQ<`8t({-J%Fn!jWc# zzjT-pvfRlYwww`SQ5M#J2Vwj<)AfLOw~oIZX-*@-4Xh+w3TFuVEmDDVPN9%v^ZsSw z#2OmDHDNWoEFAo5RhAY1H at LB&BJkUdeCt)MoiIL!mQoI<2wU_bny_ z2r^{FAtW(LfUFQgLdarC*oKgh1d^~N1W15{1oZy at Kj&Uawjr6BK0e`l?^*wI&VSDN z&wu`9HCeo+MP)AYltUNm8GWR(nT>|JlA+qljal#j0&*(X+s_tR+FmvR)d;UHQTwM$ zKt;FP&6Fa^NC}|u4mp=`W^myKm|h!YvQ}1YqakcTJ+p7MS+vtG`&>5BANMi8nB&^a zXdOd4T7juzr(ID?!u&%aY!OqZU1Zm?%@SQC2O^7P5wbT+ at iReqa)Vh)ce8>HIUytv zKRAx8`@p-dAq9Pg-X~}Y8X2qg%jw_uGI}ujMSUjxMLlt<@DkThSDk|UU&2&VhXY%V zLdnapt4#Pd1$}fk3+Q5sDY!zjAFD_CXuJasKUOcrcaMQ- at H)+o at i2)_yU67bZ(MSP zWV91qn4X5S1QfAI4q`W6x!c-OdP=ghXfRRE!ntbrxAWO;a8f;gO&8y zJ&Mi)e?79Dj?$Fh+*rphGIbZ*1?QAq_0e^%$U4_he^TPOzx*q at Sz(Ie4+8U|TvWoqD20`P*~!7e21iLr_q(#m^R=v`IbOP`5QM>^67~%% zVF)Y_*0bDL3Y361pn|ZUh!kjdX7WI`+5ZUF7HpbMd9Sw$(8i*>-eUB*HOEfsp2W-7gEq3Hj;quP<}S z*5}gRb490%MVF?SXB?|`;&jnG1XqF+*kwFh_^@(1m;YK6ij26hOa4Ve>xv# zSCM=rU0H`=c1=+J{{P&JN3}iT0r4FXk7}Pvd8t#zquPM{zI;^1qnc?GNfSKMB8ZCc zXbVchvf~poTXbMlg{7+QVWl9jKZ9MF-bRA3?Tzftbhe1R>magrMi7g&?V$sI^g$nrg;#TvoXF%)&{6F zlD!5xEij=7vC2?W_c9{mBaHgR(ariC#u2>C$fJyJSr?OFAx;6I0T6p_2xN0!m$NCw zJA^^}1~3rjeNF8$u_ z%lOSfWFuP5gjuw{dJ<-lo{-*^-Y2>6*W}sCdNS*C9<{cU4FFkEGSRJFQU76&OF_~mHoT1hR zQ8bZ!@Y-I*$d&aakt^$Y4xM zSrLIMj7sf`M10~kVi8Be9B0jz~d9Ic&HORIj-emL;LKoTS9(ghOtIJyz{N~pxGD at _vg|3I6?hh#(sz>l{ zr4OB)Qo#9I6d^F-S9DK358_F~d+MdBB)wBciXcPfWNZ`4g`$4gMg8z|)aN2g2WOn{ z#?1bpHK`di8ih(H^TAVW_tAF9C7 at XK#O>VD!sLs}^$o0?Z(tRuEQjLs_#BdHYfJn= z6HiV?2uKGN6DA)dIXHZclLRL2f#r3L}6|iIeB#wieFsrwgttbt(1X at sazGbzn8Wq^>GO z$kgwGkm4VNkZeGzk&#xR>j+k())60uYSe(x)rYt?5h~SHM$fw_>3P at iao~vLRPy=D zBKt|wfZ(6#Y_(4#R%;=9A-WsatZtYKC=7+kh at wRv4SGi9heamf4~f_%7*GoZm%e`p z74hRCN59{TilFmYbh at i_9^;*Cc)Clz;#~fBb1 at E|X{>ulVgaA)#Z-QR+n%nD{hx$z zq>znkZ9%r3FTuB%k~teMU=!mz6bGKG-N;WfWECK1+umkIyVXFFqGB0Zh$tVVIOcEI!h6&-MHXa{^=+5yl14jsV50h#OmyU`VJ9za*Y zhQ9ShWAY$s_vtf>65_1D*%T|}CLOa%))l<)jf9URMg?DyJuV52BNZ|{JT${5GAXy$ z$usPAO19_ZV&r|vS9g_2`q|4Eq`Kzx$TP2P2tOpYt3Ht0RnI3N=KbdTu8WtY at UpnT zIf{leCZ=sx$zkJnS|WbR3pb4j=u++l@#I@%i1s& z-|n`5S^G at O^Y-)hFYOpMP}^BIs}A36&$b9vittqI+-*OG&0<^ibS z^QX;>?zzlXGkO-xIwM(&p!)^NI*Yxe{93noMT{>z9WHU9h2QkD$pFvh`YukxLgC!i z^yd2LVlf&+M_ at 5=;$l(6LO0htTFF^bh77!YJW7r&faP*Q{nz+HIyA~^u5xUb^$Rg} zybP9{E}5dsP_qk$0ticwT}Y&Y3C(~bsQ}~BNO{;ivJ9scq?%e|ljlKl8h3h=Vt)#D zdXrUQ8#X0G12^;~r#ZQ-FzX#s&95N#85jG4SbP?2&gd&GL=&5|$f^b2G?(7v>5J$& zm-d(|^sF`1?hGBTfUGx21?vS0w}wG$_tpXkr02>&*g7%@TNwyN<~9rv#LI$^u0s&E zUKRvgSSCmZ#B<^WT0#y=Uq}~;g=S&e8!r=|m(SxTXlgT~9J^^Lfb~K#35Q2nEme-^ zl_K3i7%he0+LVaOLzAq85W_DV$Y4q#LqSWZ$TY(Qh96{{foOCyEszFPS;jGiR!oj5 z)eKXLf-K#LDQ1)XQD&ThV at h=~rdYZ;rkI%Vl1+RvG4B(jbqWwmEGQSUg}f+NT|<$z zrI8%vax|>|brBju)g@?Xy%HM!$z^pbuZV{xh6L6kJR}%&JOmiFjts*Hc=#umRYAKV zA{N6yCSjy~(x=jSLch1<^YWK6zLhaVd0YWW}NLS7a=9A&mvR(&orLyQGs9p4OQ=)>23bDKqbO4cn(ZLo32XegG^ z!;M{KlAD8Fq_Oq6EAqKZh#hocQ0ky7`noFzIO*`n) zE6LVRb$o(c-3?a9|0Dz5`UdoH}0A0Ot^d;jrjn0G1vhX at onuW$h7$dZf`D$g(?$gD(3& zLfvU(`5QfNboW60kk02NA9r<)JPcVr4DcSER-cVbBWbjwqhxbsRfcf1k)vpbhy>it zfHuPc9wJ*dV2GAwnPk~O^wp*;lPep578qi257XvfJ=g!RJ-i0un}Y^-y=Wd8CQpw;u|F at J3(y_M3+;K7UMpxGz!X9K?=B*`VARGkqS5I6 za#kYm|8nYo_{I at HUEf?+QKi?iA_7B-uo^{7EiIr3Vm7pg>obwVG_=P?nU%__9;tBo zHPTuGL~9GVDMH z at Y~}g!f?GITDJ=~FdgTHj_P+u1PQM_zZM3gr#>DfaeJI49W at N^i~1>trYL#E>x(QU zf6Wanx*hz(DqHhARrblKGT4~DgO&YgAQQf4tiV?HA5Hv`03p$}u54tjON6zyFPqVh zI-7-Yt+IB&8JXK?MOS+ydHWUNT0myrvB*Q;eZI}+IoXwV!;YW|w5lwn8PK8OYm+AD>G zX?vR~VQLoC-TAz35me7j&abn2x!0k#I0tN-A9eL1UG%s!OXY at -JH^QCN)cD^0{G`NnzFKH0=m62@##q}>5pU>duB_qfCkmG&IkvGClL&>M9xViSDMnk|JlSun#)DsIO7kH$IkW3~lnHGK~;8-D?u7s?p+PY_%GF!lO-{_=HDHtycSfBE;xE zHZ`k0)&Y3X;flf13n>smOkJptopm-_ zQfHmfg9o-I$fXE zV_0;iB|QPX5B4d~%jhM`D at j$=^?l>}GNxT7?zpgE`+qh?kD=(NWB`)xxE at foRo>*& z{>THWSV%j^s{ov&Jfq^>S?FjDyd|?Op<^}8!VS^{_(G3RUPZ2|_V$g3_)H-dW_)y> znvKj;abV{Gnp9OInmmMg<`Wp`oZulflX^%kRJy=O_yi_gND0d!;S-SX2^CT-q98AI z at i6G2Su5=5Ux>y5)zx-4`cMcjy1TJ2vbzysS>iRBtExXnL8Sh%bPVGj!uqa%3D*3c zddC~&3^It$AQv`i$JiCiEaoHp-`Htk$!M|+(vR;;+515BDU}QQKBeLb=c<=Q=07{x zDRcM8WJ8~>M$ZSMJJcLYAK9Twx$x;RLMEL}!}=WEh$^nIwAP^vKxAV*b638xzPS!R z5UBO_1Qgl={j5IvED#Oavw+f-Xr%V6b290zJzGCH{H#-od`9cbQtGz|4E^;R>IG72 z>5uixTDCC-e?K$KPJisw{itJs8`-UBp&zDVOJav*ZM&2 at jBD94uc@l48c($(*Tt1t z(29Ls!K^*5t!23BZ11O4G}A0 at 8s`{`wV=-7GQ{LuOW}QQ3-?99yd`vRvhT>}QQYA^ zwR at SaJcSOInJ0M!5q*raTg68ZM5t+pnRJ3Y at WH*{qI%&nHmTqcVSkRwB<>_}Ss0^| z`)lZ;z3LeiO`I?br{QN*k&)q~gX8!<)03kQJU>q%hi5j{OjJXv`Jv*P=&#(q!FHANJ%Vy}atungCiNln3 z5 at t9I;5ub015F}eNZOewPg4jQ1x-{&);N{eMU+*+FWrjkMO at 96dreBUv&U2<2xl;u z$ud0}SgGcmz+w#zG-EK&=OEItr!ihpa9rQ+P{d!QKZpy}TQi74!#(y!|T24|O-(WFo8@)*H-1 zVotE&K-9-hw5v;EJFxg5GZ_W(E$Jy-?R5$bpDy>#`nAw&c=t;82Ng#DNyGb3Hl`QC zG}WFUOCS(h$*gTxkR^hOQh|D|Sj$AFD!RFPExs3K=*LdRs(nO_JfaFbZi2i;d{=y* zZewOi8>EMc9X?k{EBBB8gUT1E=yFxtqK22NVi)?zh;2}VsSRp$GloVst72 at 6s&$M>ZBe81)yRC6d^UL% zYgKg?JtrMel8;&Wj7HBe*eB^JgIsr+nK59y4Z172Ly=eylGhkao9o7;o2}hb+Dt~& z07aplT8Qh#I5RGz=;q~f1&O8OKt!WTk{u>}bq=w>WXP zDBvghIm`mS+y#(_mEeB at jVy}y-ZGUVZGza-*I!t6Sc z&tIo)Y|2JvT`l6abx7g9r1cHUj2DDu(XA@<$hhYre1t_DjtE%MJ at 5sczEw5vkaxiD zZg#$y&tJY;LAcXvDyypAsJphYWHGi9_nXt<$>OD;0W<(q*# z3%$2CVbmD>(~N(v!asp4cpktzP27_b7TjMF)>{!fg7JN$zHWEnia=#)?JzEH6~wZo!TZ$71ruZew80YK=3bbs7`(xx6tJ at SdUWO`ZAe`OX35lB z8*INc(Ryv+K9Q}<{uNh5D;3c&l^5Eed+5TooO$SySy!S}7;TwIUUA4m%-b!H!puG> zk%#p+GtJ at zJV38g{Od*`{szj{G&<@~wylr`4L8Aq4JDKJ9mS-4y=W!}rrBQ_a$6(_ z_yFc~L6)gxro#BdjOfM+jcia5x!LewkfT);v|PP|yfMIG77e->bz&XF^v42N4z^oC zLI#hA9R3v2JDBCjo3%Sx-EWS8$neb~L$F(XQw=3DUowL?*-kP5#M+EO)EUT^GI017 zmz at kvM%TEg1J)pOkVlZG_2>%>k{6stL|=T{7<>W_#xqT%4Nhi`m^%2#eT+M^N+Qb# z$N&@?Q7KJ_MbJ=1 at 4>`aBo2f#e2*&Q3 at bDmEe^3kb!nY7y$NMZnK zLZ$t*m($`;8P|buPYH^jijnhvIEPu2+sO7*AhN1lVrpMNus9hOA;Ch2;lr*1 zxf0$O6v;-Po?^n)GNN%8fsHWc664$tTHy9F`WE{4OT^Dk=l#Q%EF=nb^B at Fh$P4?IYq**QY zzXf9Azw3q-$@nqsSNc*T91S$Oj^C(F0G*buvLh%p#V`5qwZ*U81=Hp2mMrs_CA ztbwQp-eHlzotHafrFx3FM{J2fD^?W9_sUxgv4S~jmtz4ljH^z;9aLKe&t_EnfG6yu z+9r4gqKi~oE6(7I9D_7}Q?e6hKy#W2R=bi%G3u&x!CqU3)D7 at K7NROxvL+xm;1MV~ zt9PBVD}XDh#`M4~+KPKywz5#9xYyGD4Ah-^lb z9|H2aMmXFe_-;l5-F2JrJ{i*bphdWO_m+ zzFGLFMQB>+6 at cpZ$il}&%bz6r{gW19bd7MTMQGN~0jFC8SdGq*U9L}C1g|1|)*?8| zg|k5E*9hlY1ivVpZxPhngwI=qx^m$QI5E at +d#*+MTW~U{EyvSCgGsp1B3PRx`O6l; zW)i+?5iF+i@{27(qe-~SE1QTn2R)R@;sOINNUi6{$k8gVMX9cRsK_|Nw~^KvTda2W zts=QVU2kkgS+C64`ml3I{&!B9yeH?{U`sRAh^fjLMReZ&gsZN4)+~C%1g6kQf2;v2mY@)?zuK z)8%*#kQ`3Tb;59q2{xhFPNDMkx+}sTth-W>5G?g^8eOT5;F5HuYA*HE7Gg4S%|b2O z&iLkj4fA0AQ!XM z$aE0#r5sM(k`QM(XBNW|G2YQiri2tUwSyj00eP#ceSPDj+dR<=bs4mE5Iqaevvs02 z$DZxWP2A=&PY{H=y(UQb8yH6-TFcQIGf}KS(`J!e9n28b0 at PK`SL-L at B!iR*&^*h~#YnK^-U`op-c2by% z{L!MxA`~IK}EZ{#a2H| z`W7~53$^4H>+}2s#0 zphG3Ud0^aIHcFSnL(2$wXd8})oXWKtc&B?Fc!=R$nImeD!qy at 9SAtfRoF{2A at TyYB zD}z5-#G#bQR7iRXKOdWkX-O at DDob&!%)l|B7{|&Oj+HtJkem~S;F!h0G4llRZRwcw zF~bMg_Dwa&K!mjxhzKj;LwPx}2mv=3ws3S9AVm<4B85PR8v#SxB5yIhV`mlq)a8aM;Z~)Mf=6%$$|4!ER z$>b`rP|)LXW~w8O9hz;?9dV&uEny>*J>|MIk1&f^q-n!9WG+lOnjPtKp>zWt=C~(u z3R%ZOFe|gpe?JX_+BZJ7K at FrfsIe_-2072hJ?sNsQ$d95iuR$Mi=xd` zJ2=?rwVb^5L74WlUc=HKT^3_=dWbOn8J=%`CPrqDrQDA1L0b_-My^6F$S%f at XYm8* z*hG04Xjsk#N|dv7JzSXgaR3

    tHQbr*iR+Y|G?umfp;RA6u@!V=m#(ZPbDI#@ojRo0 zo`SREJfy71`y$;8K4946#AK#T*oW}!?!dX9Fn6T6ZYx^o{Qm;3Y1246Vhy};e~TRU zO&$D12~bS6|0JJ}J*i?;cj`$MS5;^fvO09r*%%8k+7c5QN{l_L=2Fk9!;Y at w+N_Ak z(aow4uRE at bKBp$ED2M)>DwaktDZ9IU!NA1XqD*pjWel58z^*J|5;_GnjcqfJZL^F;r}q6>z>;bU8_RBT8;G+l!zLT& zro-tQ at Mw-|Kfw$rnwl?7L64$S+APh=lr}3?hZa`gC=Npf2k|yD9>d!#cnEK^#$d9n zR&l9F%|y^}eV_+llo>aGmgNzd0}z1xI5^Aa&~maVO)~KbGalh{aDGo%u5@~b#M`BW z^@=BVz0%P0KCMTM98 at G5If8-Vov9iSU7bAR*OpMUwx(J8PO@)|wp|TyIbw9o=vB;B z)f?~<>n at c!V%KO$+_W6QOonin%WQ<9v*Ah8m#rVTA2#P{PBe=hY0}gqf at mYZwO7zX zq7}tVClA=rt=&8r-+3_ZYhh_yt47aAkuwrh%F(L37_CfM#3)lPp0ydKXVE)+k5_^9 z at j)&{+>guEU1a&Pw9P1)yr!AxqoSrmu*@r4r%Q_VXAA_+XuT>!;0$@W1Pns6J2O>X z?gG#uKJOw|X&0KMmfzJqRlh~#ikO9#rgv##9;J+M*bu%rtO zhs|;>hbGIQ$!6kj6vUVpBSHdImx7nYY8P7 at GPnRg4Ng^=c-(UL|qpbiIa1#--VrMn;>0 zS{hm1s4Pk7|5343rZy8gnuNi!8H|u;{kVxth27lD%udw_!pM?C*MG@;y3w8MDzQY9 z$W<`=OI}3_kPbq|rS)RYutW?{6|52iJ=aAN+XbZ|3v`OkuY8YS(VAv~3vgk)E|6AJDv;JJY??Zdr6cw{oVGaIpc=GFjG}SllJmxDD`-W) zPns6f=o27+lxEgmfSlk)QV!reDvKna*~3%|MjCy-y>GDkiW2pHlSCty#OB*5uG9l$ zgw05;s{nO5t{EyEcb~~<9n5|F)LRFi=v0d^|0*V&Hfl)j(n*8It4GG5!LFWb>g-0kXDFTRR-`W1203}0NzQI<2HI>` zvM?r_-HN5$H at 1iIG_csKJ`e$$ktva8;Fe4=5>HypcG$ct6Un;}uqSBopM|RuTk|>b zcEu$>*&Y05tG>ck{VS~OG<%Gt9o=V9&;A+pxlXr|^l(?l)GMG2S!r807DsIh_4S_= zx_bRsZtsYd!=0_JQL!E2HL&;;=iW-W{~U8~uYj8KLB$y!>meP1y#P^@};*lcyQ_S^KF|x0TQoLvG zrIr3YT=T>4nMLgld;E}DJ11ik=rBSq(Tt2)+PPBXmPt2rw~3D0dsx0{OX1E$2UHTUAuOX-?>?R3u`ZKjmXA|-s9CoC@{EY}m3 zQ^InVa2ZcnGeW`|N?5}Z{tE^Lh$(2&=GZZc-2s}k^xT|7Z*5}s^0z&jhDTg)g=9o2|Ksp*bFMMT-9l at c1sM-Uca<@%ry-u at MnE?}4C7|OAh zNf?l^)98gdvP*iQPK+-$m8QgaL~vbtfe$vavyqX&Lx>ZWF333OrsOaVg%kcsV0MGw zu(1V!6dGtNGqEE#sc6EQNDqcD at W zdr^(Ns0!DKcSr-$o6^~uvrX2QuMwq-mg1%P1QQFrj?Q`EK?U{N^`#deh z4VRN7zy5RDinn{MurR(s^k8BjgJv4HLE1!yEHn;e3)cA7L&wim9jgVyx)C~rFD;yH zvHB4O{Xb;U{1j2+RS8JFQFU-y>{ty3&uT at vdlJ~AL|JBU-4lOR%_06qSBVo>i2~j& z1>)z`OfF7J0xwesd=D!BxqzGf55)G{2lQ=@_VeU`E!#sEV3*Wvq~9xMwMof)ecBg3 zPVepZ%3^g5?B=m~&ge$>#Eo#8pXWry6sCyMtt+CS at O~`{@5heTg0N$qRn%XkjM~ZFFjxqR-`$_G!`QDoOjG z=<|Z89W44>B58+;KDS(@y=8m?Ft5TeRo9 at -&o8uj&hRf#=jSz;DAhIP)XEIB51m4I z%}RC6P9hhQa7YpdwfRmaq3$HhT-rNoq`gVX+!1=K8Lwzy3^FhXuzU2|{tU<=?dcFB z1y+gCr$d?W(;+eZnpx13)MJulFSDTIhSymW_n;aX?9{8oUI*+ivDwZ*YPK_)oR|~< zglr}ae`E8VkUWcNQ}dm+X;@0E7bR3NCYA}uFdSgS=_!&ET2_qhMocO)LqCx8P$hiL zns}&^${~Q|UPrT$9xW^l<}zsbh$YYIX{+=CAV6T1=^YZ}D%Y|1;2`jx8cj=)G#v!q zpbl;VOjK6bJNn_YY@?p^)hhIyfK0p01oU{+XCTW0JA7s+r;k1}wFo>sQw!{|d|e=w zuPaoMHS*0yMXj7Mqy`%9rJx!J+U?R_SChM4sogkn`;G9Ubr^oM4zL&9P8i`EDE$Uk zY6GSpG{WcDF?^0aH3z5LJ|nz?!aMA#4h-LKgu_M);e9rkFA)AFL0kq9lLi+6eZVV1 zN at kSgtzofm7sVbVEFM|^=N+JQt7{I at f*$MlcMNFxI`Z)~+lfwKpyz_nbJ}~VE}oXJ zo9aMb?R#*neoxio=R5UdK6HJT&M<{SraHnjXyMN*gydHh9RpdC|0JcZdCn}xtaM9L zOGDN|2m!&r*SIAB4(HU>$R;;ai4Z~xP8kECnDQAR6*_h4^FeIi_1ox5AQZFikhpskBt at fJ=$=Scd0e83{Z?M+hRBiV@{NouYAr zkTz$PQFVw3q zo>!@kTLpT0tMs+6_O?}_=Q6zaJjPr!GeL&K z{KFJGu0k`8tFgoQJgml!;q#apI}2lYR%LhfR^zUIs$-I9+=aH8vy)lnwpO$y&Tl?5 zqeM+kQEjb8j(4STVV)C$x~otnq5cSj;h%KB3Jl=5DWHg^99|Z42m*0CWeW$Zj9t7pHc-ngCqN+DGN{ zC<|T(i*2F|`ircF99N<4bVAuQ&Q7=w^_VtM9`r6{g$$B&H#ta1aA}zw&LX^%9?TW4 z;=^yW zfJBQ8TUtK!noZR;%+AatK)W-}jBfmAAo{inlzTS0v^CmQpW!xkf>|=L^R}u)_c=3$ zIf0+Sh))7?VzqDLB(Z%GSy36V0qyg?nhC$Jit!wLXh+m|KL%c?qvF`X0_Y$-8t;XO z44zd#Q^TLBf?;srh*yH1{4QNAGj3{DU#ha4J89!f50<&gEry=FiahQ3Cg9cYQEIIf zH3b=;>KJuqBX#BsRn*R_IQG%38p*0c>^w}V^T>fd?p2`P?W|{jQl8YS+f{WG=NJ z37wds9$X%%`)7fDu6Bzo6S|flQ7Q#6qFI1HxF(pGwM8?eO_+IJ-J&!FLH-83z3B^$JF>{e4bU~Pk4#S{+OoAB86_qMiOx%F4gO*zMR7TJze5A#M34Ad{x!& zhgV4O(bMHx=ukIfFeOCPZ1*mU6do}=57$E3bUN3)pm zJ=Dec)X{}a(V9%SMr6uKcmVE at SV*cv%9f_fmZa;HSV*c<>f at Ev-UOg_NLr_y)-H<4 zPPu!^IJ^g_c2c=`dD|f+OuE`6rj)ep5+&X)6({bb#GOTnA%x`8`baJAZRl)?)o zA$m{@9~1 at DlZu^|1F6$8u2t*U-ZlYon za#y*a1tRClh1hFq^tXMG^hOB}#3$t&;Yc;{w at T_Y)rS7|jO2R$b8;VY0$^qbU}iJx zJbh!dlLi5cCRDr at SQFsztizEZ1kW^!@Rxyuwoho~K*yee$Qz9avk&2XP4z+LXxzlB zK>V+0#pp+bL9*jD71t_wL>9OOZrND-Tt%66Twp&}gF#qL<=E#?%I7L4V2hx{a|a>M zy$pH!Gl)Ey66DdDd#7pFIaz$V&x~-of5BJxr*u*ApepLrJF~FMhY&_k9B~9~9eBhy zcm%(1DSuul&CVti zpW&+oRy-wqw_BWA{Jqi*@bt0uP|;URsIQO$7Q(KnFxLo2231(XjNr(i%CKQQ!v++E zf at 6JOa;pQj*et^e+!Y`_+YGV^1viRZVQZypw}i>@W4>p8Jxl^Wf6E8IQNr~=Z-!HziFTFrSIp6(!>v?zF`8Jb|y%F zi^MlNlAp(3s^UC*iLv!%o`&;OC0NT~H-yC;m$%B&{W3Fkz1{mG{g7-ltOUMIer1# z%IDZR35qK%>J61FR^lkr7db*c1p^IWz>}-pVjOlJUJ?RYln+4*Xu|i;d7%kd1-1dO zgbde?0Lu?Kr+b5y?2D!Axu-v|)IcM30=4RskVK(kYe7}-wOCbSk%9Nh`Xd_mA%6~d~Zn6HG&Vw2r&Aa*$gqgZ#wV z%!KX{Aq_>+!7N<^ul8o~0GdpD+iz!ax3}Nkrsx-)XMA;Suxxjft~ecru$ZDBGl1Yl zUrv5O;4}oBP2ea2Cn_b$PH*|hODfr6h`yu};P}n_VAaTiUg0Sp8p!(Q6^Ln-$;6GE)ZO zB$Ff43ku3iOffv9p+S2(r=dU}`KU+RLrl%bViQrW*_2JAi)c$ismA4UhXswtSC?^t zM&xCq2nD)`PAF5zHbTN=EGF`}HgmG86)>s*g+O}0SwFkNr_N6pXRj^}g=MtMK0lwQ z$zJ;}`s!3DtgWn+9b%!*ZfAFYGu z23LH83!Z&8G{2cm?smm@B9O`pfI at 04se^Jw*MNCPc-P5K-8{xP}=4zRDa%QO( z-;*6pSRcJW_^T{D2RPRuWs*mOX?W|DB3o)DpnP&b`3xsIp~t_XvyHoNd+|PXhv~Rp+z~w`yBEWvE6t*I6_h%bjuDY zn{nPtdUBVDw+66r3?K;kD`QaRiFZdI}GCLuDZCe4{DiGLl)@Tm~V at PAE7Cc`ToA&O?R^NETgNX~_ zDXu8FqLti at 7exkJ zXud6V(HHy57wWX7zVbn`nGK4`WF|$HZ0;v=UqVTBmO=Qe29szq*8>W at Uc77KD_wNiib8rBlpE6j|A`TB!j5c4tZ9oKz zs1A}DFLe^;V0>q7!W>!VhWF&oT3TcV3ie%J1n}q_X>bZNk%iYFY%3aMtI2#n+UA_? z*5vX(O?Q{oHo$kI&YL>e%o0{S7%cT~%VI%-Ik at c!m at Thc1%nO)G zpO$01sW{$5 at yMpV!+q6JAXL_;?r`@d%&9r>*OFT3Mv>4ubVAbMZqBDV+|dPYWFXS$ z|F$nOgL>^wKv|O+ufdnNVOFd(K9}R?UC`AgGv10XcV|+|5eHVm(rPkS;XIE+P1*QD z3|(y`oDB(QW6&B(I0=U7Chex=R^P;%#P*x|qf6b|r{&?LWCAynji0J8_eRGS6wt6N14njq95XaBdFG2nMMNM at L9IllIr at LuAkHdC-aHq8y= zpMt;B-TJllT=!JRT9iNXYxCXRQwCy7+}YF;H$aqS at C;@Fv`6^8^;~xsi7By83jj3= zS at WtLWxBCJPq798-Kq=I#_~YQ+W%*I4DF~nE5eli59jJ%05)_nCTqLTF00kgF`55$=)u9UWbfwO5|%P8Y6yDOhkBX zw~+%s8Q0QZka+9kZmMWqD{sdwCi at u5cBqn!U>vHXj659)=B{hC{Sk-IgmEmX5{9JO z2mbbz&B6Hk)}laSP))OSx_m>-)DTLlgdu5BLkfX2o5Q31qefe6{vM z$657`BU=FCEx-DA5^TMv6+I2z+bT$MW#xUXf>jjmZxzgY+`K-_u~Sn95%#g9~jY>X0a#`7tJ_pDcB( zS7cT27STSFAwM}L+t^xNsY~C(2f5z6MM1xSZZ1i#FiCQWjM~hrlx)`+()y1=Z$`O9 zf3e*gE_g1XEqxeo2bs4rQ74#rCzzT4&S~IE3FNd}b1nw_2jB z*l?~AN%%bS7z%f@*+FB>XPDU*C=Adj_R7T<ku=_#r;klA{KQ>aDwz__lge^M!X_DA#aoM&2lfwoL5%%5^`(?e25B3!mET8+oj83#pTf9*WU;oY=*NuIkKfyL|Z!F01ME$z-2LDxo~9+___gc zmJWy&k;M6s{oy at vSA|9W)RxiVsQ~NfE%zdYz3x$)Id48iLmvV5XM;Fa1tV zH)OSn8L^B~hbOnSh81x_(GJVQpQ3Oy8dah;(qNTC;smkv&%cjwh-{g7ohrIruL!n! zskAzuv2j1$+Myd?fn_bo7D%?+FbH6hgg$QKKsrL0nsCG46Bs*+TAukGTK at cHTfY5wX?d1f{*#d~%*wZ* z7g`PE^75dat(V2Cao_q*5nkosAi$2~E5%G-HaBsli06a74U(dmJgAb8{Hmf34?YLH z+3*3cm^|X0Kn$*aN at Am&+3-qH%!H>k;AS;oFi&3O%#)Q;Luw#lMYoimF_=X;7Dz}o)lAJ|n;DmmG6+eSS=Jong#+lzt;4hH`N^P~ z$@x+~PYFq0x0+pUzT67gq41tA++vKfpd1l$llW{yS(#!&;Y)sgMt$`xbXfSyAW*z* z=GY7FOzH)9>?QY&LW;pvsmnP}MOdLsbHWPHhP#@?$%DCO;SqLL$+5aOq9S`ys@<*P zWRgaE1bIUV18} zwAxNLdgDRaU83a1po;8E)Lqr21w29jM7KvUnVJP?_WQ=Pd4*4#)i7@6ur|QxzjIb%P{n4 zg95Xt`IlT88sa=dO!`hAwEou=Kb=)W_4MnB> zu|>m+Ix_|)AunnX7IlIEuPYQa&#x`^hfg)(8uHQ5dhWt{K8Ev%tSlf|R>N)5YN&Fx zKX%uM(0A$3V|3mBFv1tw!*4Z7GX}Du?C9F>30OYaC%cRIjNGsc


    awxEC5ZF&f0(LF{JJ%muGh+Wt*ZXJ9*8sbQ0lVfhz^*}H*IWkJ zQ$vAWi@=_SB}7(!03)~xAYA3=5cLFD&HVFz_Bj|^TLixuoe18DV6VktJ=0)@@~=U* zq~GqRHfR;ecSdUq{JP9#^bUXI4!;069AG&7DWSr6sC~X)>+;9v`(w-fTG}69?vDXh ztoFy}`D07{+FXBpsXw;ZudVXO7h}fH8k9;|x!a6O1-EH2gDnK`zYPAD^Hzq$!Dx9l z`D$ypT*Q~x at TD7lxqKxGWtnj18X>%NtN at u|_RE!J%<_MD1t22?DAg!Za<@NPldyzq zM3n3EOA`{731!jpO!!I2TP{v+vr^Vt)E&q{VptF;9j$5 at Rfqhmr4SCoG6Ariy-*QP z at FGl<(NvU?qF-<2(NqJ#GAx-<{&M7rWBK#>3l;DND*%Yz=1OcFOf=XCFd4jvBPAn# zWwY95rmDt{&CYtK2L4!8%?EWUH$?kM>C2Tr2maF<}bN6vj$|$;QXmo~0ms50k zakT9aN)KgQ+bTj`&4~&R8kG`k>wr35u78TCM;^LE|CDUg;q(5h_@``}0-s-u;h%~v z37>lc{L^InJNSH6|0Jj7q1W_J3##IUUe`aZ=u0~EhW=TG9_vDV`e!)`{oqV%uAu2& zVXYYT!+gHtuP^3jT+F|GF at NM@e(a at u4TM%Uhu#Z>4h2Go1EJ-1XhP`y zKXU(6K=1IM9%R&_{vL$3T1qLZ1XervjnVfzX*i=+i*xvq0!I&Wv zjhdNfXa*fVc4M z1~EC!+MZ-maY?*$Zi+(;#_+3dh!zLvq|wo#|LgnH}@=kH_SlJh?DK zFMoe)_)RK*=7{Cb(97RX<;enm8y3BLu4LlVR04oy8rPd!hE z=H;uyZxb~0M}%gc4$aF~12hka;)0@#2Pxyi5i>3*&Zt1fMS4Ttp at u9T3fdwav?YzQ zY_|Lj5*TFH^8QMcT-Byo!|y`Q5R*DUqmS8U0WIcbLaY4g{tTqcCf{qtMClp*D1C5$ zB}yL*FzJJ(mo4?sH!1bd$fYi8RLo{el-r6-a!n+;&BV*k{BNk?2jT>lYx01W42eG! zc|x*C4>3T|yod5trd;NKCrm#O(-4oeP1gAiTj!QH!rU>T$I8RB=5$q#*Zv89lh&ee zL~0MtpF0}y0^eCoOusiev|miUHyZDv0ZzJdDhNZ&EbB{{m`KEmMl;Mkygm9&K_X&i z845-G#su}>iS;W^bUj&+iAdwr{S7*uY1A)|RyXQ*OHX=yle(qUukqTo(Jq!(d?Xe_ zw$2~w_2KX3Cj7lZzgOw^I{iKyjlY+y`0Mt;?*?mpojL z&9~kkyGPIWQWO4m({B&`UZdZ+(fBLPcaJ~Tt>^1&!e5qtd+9eozpqB)uQXpbl5lrJ zS5#C^#9KTM9OtHcVrTJKch(&{;*S1u;x7fHuTn?cu`l^2kZP$f-LW*LPJ7Tm!AXB0 zb;SBL8 at ZTS`9gNWZYN_OxC5yV+y&kAbw=(*9yC2mxzRZ3B2Ol@ z$P=6938bcZ3hD1L(!YXaK&Kvq&aJ>_l|!i$^We9iU+iXMR42a0Nc(5P at a zwr+y~?R2ORM(#sM|6x)39wYr(1n?o)XDTX3L;ADsXs;&|?)8W`(2qf?wz8ekaZX#&ws9L>}6Hv*QSji)otK@`H$(K+GAm#z+gahsoDmj6bJZf~w zX)N|J8neGdhp^Cvt39#PIA+Lh1JV^O85IrOa(lQR22M_$cE?UYB`1n{y4NUnvjoL% zCTOiB>aiIKs>hA^FRXv{fCan2n(IvfFu&g7}1m;$mA0v>b>ML>w20|PckA26;5h8Dhzde30JPgA{r z3iZAR^}gqhoxyr>Yns8VG$}===+qf^Yz-8DlG0Izqhv8?9&-M8Eobh=Nem-w} zcH`%EJ`?WAl?kS%Xy(|$QCm3PP1 z1F-9hfPIl>2TBX1J&2;8yI~pB(l{Y^8I`9!#so`yV(&ub?-o`5l2Q3Zto&tT9$$q1 zyXcNR0#!azRON0XeJ57)ijlq(khaqk`wGhcs;K-uM*2rF{j0|Ktbp_ at Jh4YTL&pcu zdmbO3M?JAiP~N4Y@?N7RJ>7n{Jq+pgyQ6QqC%)+x zQu}c!if*au)9&<4+)^b$BFwtqO%`q;_WvTg<1;tO?ZM}7lJAYyW_svz&`p&Nx>=?0 zd;$Y`rGsu>X>^sRIUiZ&5u9WogUI-{Ky^o$Sr7x-1fz2wb&i~w~7BIqz<_u zgg~0Bg9HJA_^KLs1mjQ3 at BsQDz7fBHgowXHFRwPmpP-l5o8sN{^4aM4Zh8^p8~F1@ z{@l!;Yw at X#O>Pfr_fey;35QtYPcpDhaLCw=g=Vdf*dv2=6qBll!CR-I4Qd0meOp!_MO0aTnre zcp@`A0w72R at k4vWgVxev#s*==BDP(0$NL at 8^>VZ#G)J0PA(HUg2X4ndLaGnkL7-N! zf&K3IQ#c6xc%M=30XHQ+0OcOQa$!4$o%Aw^2i%T7 zfy4ulcz_aLj=G6&=TJ>Ip!(2_KB$3)2?7m+d8d0=rued2mUgL;_ORRW1GHHJX%AD{ zV{VOI|G^vN`mhzZZWDdB%kf>jvUz#|y2*vdJXGgn&=ZeQPn_j|#y{p+<{EXb_n^!P zYzINu4lr%r9bZse)DdF=`@b4DaV$&zuA7p-3n0ErAf`R>&)t_zevozZK}dd(lE3ec zFRL%<=DQ70rm+E>2B1tMP!@UOn>;0n*BXiEcnC4(Kw{X+ at A5?M0`x6Ty1+>KmD at 2N z`V4Td!dU9ioE4y_Qmb*Hs^;0b`e?Z!0tpR7TU?IrCkL?Z+tO(mob z%jlyW2$mgEs$Y&RrS}g-NS9`IlQNCvqbn-kszw7Hm1EnSW83O|?X7Jd{4;87o7Xp^ ztzm3iBO2AY89Q0EfI*R&kN?WCkq}8$Wiu$rUhfz7` zl7zLpSy~TYRS-|nCEYxwts2+tHBAX~qPja##_ezNN=NK}y@ z$a;z?friCEC>r61HYdt3k5Lvni-f>R3S@;ico0+!q20WI9uF0;AFov{(1va__W+;R zo?Mz7Id^%u!`>Ql*jsb$*vO71F;UYjjKfR`1sX?xBFhzXq6VGyLY_c;8j@?JSraBB zYbHBl#*IzM=dS(NHXCBX+k^@KIF}%+OZJX4CrAmcnyXRDB6~=ll$PBT#kb! zY{Dfep)5mp&}7P4m~xrmA_(>>0iX=9^b4bzSLZed)Sqymt7f3~c#CSWMbUdE8>K{Doh<%1Puv$s$ zp^)x)CnjaFkfkU#&12g=Nr=NKvL5EQ6Tdszl7`>)uru)9_7MJfuDz~Vph5N|oQZns z6nLYl2J-?!HVzGe`qHexv>thxK=wu`u+Loz7JPR~2+tB7o>2tPCo>2xoqX?cuL*O~&X5B=6^pFmdi(qmYFu4qv;Ek^xSOqV7XdsL{0+H4uM+#wY z1k=eOFr6%hX`v32N-(JgOsWBsIucCkm0(hb!K4~6sR$GKA!DA{G!ep$K5;}Ixa^{ zh(AF}?LkS1ZFgw%JgM!D*qaV*wI}taBX-cCopYxSI$}A8_MSVHbHv_oXe&IaH<0hQ zw^30lDwmUD(Dz8`3EE-rfN_7v9sP^wSEANi7ye2V!+#;BAY`=^UM&fbl3M$f86N2B zj2 at kqOmiO9!{7|69}e*#igaFdm#hb6BD-X%xF-j5yeF~h9CR8dGtn?=qYQ1st3v4G zdLplll97sj2gv7lkmEN%%~(*}j2 at 5uH_(h8&(LN-NTC at xOwf!TPmmEA!7;L>JL z(3P6eh$s?^Z at M=9I&gh4R zXSAFn9Y2KtYbP8i8lt^H0VtzL45zg+YO#8V-vL-e{El|daD;025Tm2rLu`+BPbfqD z4tU8CzXM+SiQfS)gT(KEmy^WrfEVI-(DOz9+{~YA at yYld>kNKJJL5>~V{DjxoDBmT z=1_cw0D@;9W5euohx!OCc*zo2 at UoY{f|mgT3tqk=u=a5_%s$SB*~i&1`#2kBA7{fX zHo)4)*f0wiKV^ZzPg&rBu+S-;pR&NilMz3q9V3wqvxH?^Vq{z5fw0hLdbTAj+Y*)y zYG1mW})%P4uuOS;gSGLCJKQ&a|EB^mbI@ zcE&Tkw`u0)lSVq4(dc%XepC3to!jrby~aSuvw;vE2_dn8BoN2pnMWZc*t|j#z(>M% z2ziqiAx{$WBA~neYoBu@*@h-FbMG(2I{UTu+H0 at 9_u6}}y_UFiTmYq@*~755%6E$2 z#hoH?K at D?(rj&6&#+6v6XfE(Te3=4Ud9#NLx(6rOvq#~k1DB3b&*UA&A|QAeLcR<@ zTSi6U1sM$7uJ5;HoSFmNMB`3#Vo$Le6*vswYtnDFLesR#Mq}%*hhlmNrV#ZqTEqt5mC9v!*8hf*3)Ws5mqyr zSJnrkvf+5Z2-^GLEif^Yilp|}Q|=O}0E@%<+6KNdipo(*@7xiz8w)7($qg_{pK0K0 zZ~e0RiL*m9m@&Q6#PoS!x}^5Y;AtC&=ZP_Ro|vBZ7!?=48lJ^*c)rYd_K&4#zj0i; zzdl-R*ysGOB(D^ezY5x8vJ={`Fxq{UcY2`}PJrJzpV^8jSFW}!RpkC18q7 at -=(NAl1XBT)LenEyyx(k6R7L#}E7T?)p%vCg z6V`BQ$PsGz2ByhT`s}8ey#e-sltR;j5j{^)`Ru#X)9+5dRI%5+UM#v4mmLZz$D2&` zwD%14v^BP1?jh9k7Hf!%LFh!2!=*Yd!j&p4y+ESATph~iPAEeKVHN)ECet9s`wa14 z>ED%Wq5QE8P~RD^ck*I$`xhkcLGR>U);n>VdM7YAYU!H%wxnya-3MKh-$K{qeM{Hm zgOaYv-Kg%jum(C2QBN`owG_D%+7ml{LR0TU66zE+P4di6Up{+sI;_#*^9m__a=H!-18ce5}6qlkNl7$i_7FEhMYgErJj6jbV-lC~w zfy$ZYh(w5?Y^eW7eqF43e*l#B_=F81Ba5a2=RH0fo(Yx5p%OC~l{-J4Zp+UYR5i5( zdh*6%u`n6XeZ9B3K#s1SYtmvZ>J1` zdV{{5px&UM9_OgFI%RMS?FPqp%HX(8IVae%PB|Z7qfbkHB8-o8K5 z9ZB)nd*bd;Cg={OvO5&3J7CPZqB{odqBjObZ%|7!Dw=8-OQF^ZV<~29U83vE?)a1k zX at Ey#RO;Luk!d(Zr5umAXi*HL&p1;RWn!vrpmLvcH+~Pj_^nYrbvi5rzSChnemX2? zzX(%wAs_!ki%bGvt?)uttH*2QJiN8d(DQu+^X0$g8GujmW)cEM+eqUmhyJMAG%I){1_WOp?x4DxWZBud^ zq4{!_TI8wu?w3>sdy7vEkNC3tTYB&#bi#_X7N6rrhtBK_BXOPDd7UbiR^hH at fcyc2#I340%sArHart^igwBZ2F%dc at LIn{j zP-r`riMh=(wGX-$cS9Ye+X}u1)r)z}vR2$gj#nnCV~tVAnlW{}LUp_{zK(0G zI*y@^g;tRDo33T*mFYKKXQk(n{rYA*r9+;iu->qm)*$-L<-s0IzWu%saS0`+u5)K! zX at -MIuu;Feqo3Kx`yξwzE$sb(J2tK0-3zRE49?nhkueHR*88T}-pOZ9;(C%O%M zRW98I-e9%jNwnfdZbd5%ktYfHCw2z!ovEi=kshz+UzxKP6zFjs?b0ddr|JZpP>% zi6ixlDoEW2x4O49JKUa{8`uWqr6W^Er)HFLx79-m`)jEfu~*4b>Rn$QI_h0t;?k(p z))tjIE>&{x`Y4`qi$Wt~F9bg+_o{Nz^s0iu$40=xHtAbLz^Ui6n_ZSkC!9+bgcozA zyC=QbMai37mPMzsWR;RPA$cn&JH{kCD0wTAw{WtPlmCo|wP6OkWF`t6L-24{H!&jo z<0Oa#>}_BCZ69tck=2E)Fea|UIgRE5N}>ifX?)C=J=QV@$JRRh)=(HXodCls+B~R2 zJmlt>E-H at s`e2MRwL^tVi4`jJ&Tr*TB|$f}*OxWC649rrkhQd#gy)*We8PP3aH#8h zBcZOfk

    Application error

    Rails application failed to start properly" \ No newline at end of file diff --git a/src/public/javascripts/jquery.js b/src/public/javascripts/jquery.js old mode 100755 new mode 100644 diff --git a/src/public/javascripts/ui.core.js b/src/public/javascripts/ui.core.js old mode 100755 new mode 100644 diff --git a/src/public/javascripts/ui.slider.js b/src/public/javascripts/ui.slider.js old mode 100755 new mode 100644 diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb index a7cf31c..5fd7799 100644 --- a/src/test/unit/host_browser_awaken_test.rb +++ b/src/test/unit/host_browser_awaken_test.rb @@ -1,4 +1,3 @@ -#!/usr/bin/ruby -Wall # # Copyright (C) 2008 Red Hat, Inc. # Written by Darryl L. Pierce -- 1.6.0.6 From jguiditt at redhat.com Thu Jul 23 18:10:05 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 23 Jul 2009 14:10:05 -0400 Subject: [Ovirt-devel] permit many-to-many vms / networks relationship In-Reply-To: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> Message-ID: <1248372605.790.169.camel@lenovo> On Thu, 2009-07-09 at 17:56 -0400, Mohammed Morsi wrote: > This patchset contains changes to the ovirt server > frontend, backend, and tests components, permitting vms > to be associated with multiple networks and vice versa. > > Also included are two patches which are required for the frontend > bits; a patch adding collapsable sections to the vm form, which > in itself depends on the second patch that provides default values > for the cpu and memory vm table fields I am about to send feedback on the 'add collapsable sections patch, and likely then moving on to 'provide default vm..' patch. However, once you make the request changes to the form, could you please resend the entire series with '--no-chain-reply-to' so the patches are numbered? This will make it a lot easier for me to know what order to test them in. Thanks, -j > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From jguiditt at redhat.com Thu Jul 23 18:15:20 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 23 Jul 2009 14:15:20 -0400 Subject: [Ovirt-devel] [PATCH server] add collapsable sections to vm form In-Reply-To: <1247176588-4280-3-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> <1247176588-4280-2-git-send-email-mmorsi@redhat.com> <1247176588-4280-3-git-send-email-mmorsi@redhat.com> Message-ID: <1248372921.790.175.camel@lenovo> On Thu, 2009-07-09 at 17:56 -0400, Mohammed Morsi wrote: > the vm form is getting cluttered, this patch simply add > collapsable sections to the form, making the 'storage' > and 'network' sections collapsed by default Sorry to have to do this, but NACK, I have a number of issues with this patch, outlined in detail inline. I have attached a patch that you can git apply to your local checkout on top of your patch to hopefully save you some effort. This patch redoes the js and gives as an example one closed and one open section on the form. Also contains some cleaned up/tweaked css. Feel free to apply, make your changes and commit --amend, I don't care about getting credit :) And sorry for revamping it so much, it seemed easier to show than to try and explain some of these issues. > --- > src/app/helpers/application_helper.rb | 9 +++++ > src/app/views/vm/_form.rhtml | 55 +++++++++++++++++++++++++-------- > src/public/javascripts/ovirt.js | 25 +++++++++++++++ > src/public/stylesheets/components.css | 15 +++++++++ > 4 files changed, 91 insertions(+), 13 deletions(-) > > diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb > index 0178ad0..d3eecd7 100644 > --- a/src/app/helpers/application_helper.rb > +++ b/src/app/helpers/application_helper.rb > @@ -77,6 +77,15 @@ module ApplicationHelper > } > end > > + # same as check_box_tag_with_label but w/ the checkbox appearing first > + def rcheck_box_tag_with_label(label, name, value = "1", checked = false) > + %{ > +
    #{check_box_tag name, value, checked} > +
    > + } > + end instead of making a new method here, just change the order this way in the existing check_box_tag_with_label method. I discussed with jeremy perry, and checkbox on the left is best practice, so the whole app should do this anyway (bonus points if you fix radio buttons the same way, that needs to be done as well) > + > + > def radio_button_tag_with_label(label, name, value = "1", checked = false) > %{ >
    > diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml > index 034c3df..373452d 100644 > --- a/src/app/views/vm/_form.rhtml > +++ b/src/app/views/vm/_form.rhtml > @@ -6,15 +6,23 @@ > <%= hidden_field 'vm', 'vm_resource_pool_id' %> > <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id if @hardware_pool %> > > +
    > + <%= link_to "", "#", :id => "vm_general_section_link" %> > +
    This is one of the bits revamped in the patch. First, it only needs to be a div w/js behavior attached. Conceptually, this is the right idea - header section/body section, but the content does not need to be injected via js, and the link is unneeded in this case. Second, at least in ff3.5, clicking the link would cause the page to jump down (need to use the jq preventDefault() method on the anchor, as we do in _tree.rhtml) > +
    > <%= text_field_with_label "Name:", "vm", "description", {:style=>"width:250px;"} %> > <%= text_field_with_label "UUID:", "vm", "uuid", {:style=>"width:250px;"} %> > <%= select_with_label "Operating System:", 'vm', 'provisioning_and_boot_settings', @provisioning_options, :style=>"width:250px;" %> > <% if controller.action_name == "edit" %>*Warning* Editing provision could overwrite vm<% end %> > + >
    > +
    > +
    > > -
    Resources
    > -
    > -
    > +
    > + <%= link_to "", "#", :id => "vm_resources_section_link" %> > +
    > +
    >
    > <%= text_field_with_label "CPUs:", "vm", "num_vcpus_allocated", {:style=>"width:100px; margin-bottom:2px;"}, {:style=>"padding-right: 50px;"} %> >
    max to create: <%=create_resources[:cpus]%>
    > @@ -27,6 +35,13 @@ >
    >
    >
    > +
    > +
    > + > +
    > + <%= link_to "", "#", :id => "vm_storage_section_link" %> > +
    > +
    >
    Storage:
    >
    >
      > @@ -35,10 +50,13 @@ > >
      Total:
      >
      > -
      > -
      > +
      > +
      > > -
      Network
      > +
      > + <%= link_to "", "#", :id => "vm_network_section_link" %> > +
      > +
      >
      >
      >
      > @@ -47,15 +65,14 @@ >
      > <%= select_with_label "Network:", 'vm', 'network_id', @networks.insert(0, ""), :style=>"width:250px;" %> >
      > -
      > -
      >
      > > - <%= check_box_tag_with_label "Forward vm's vnc port locally", "forward_vnc", 1, @vm.forward_vnc %> > -
      > + <%= rcheck_box_tag_with_label "Forward vm's vnc port locally", "forward_vnc", 1, @vm.forward_vnc %> > +
      > +
      > > - <%= check_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> > - <%= check_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> > + <%= rcheck_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> > + <%= rcheck_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> > > > > @@ -104,6 +121,18 @@ ${htmlList(pools, id)} > refresh: VmCreator.returnToVmForm > }); > }); > - > > + toggle_visability_on_click('#vm_general_config', '#vm_general_section_link', 'General'); > + toggle_visability_on_click('#vm_resources_config', '#vm_resources_section_link', 'Resources'); > + toggle_visability_on_click('#vm_storage_config', '#vm_storage_section_link', 'Storage'); > + toggle_visability_on_click('#vm_network_config', '#vm_network_section_link', 'Network'); > + > + // initially show general / resources, hide storage / networks section > + $(document).ready(function(){ > + show_section_with_header('#vm_general_config', '#vm_general_section_link', 'General'); > + show_section_with_header('#vm_resources_config', '#vm_resources_section_link', 'Resources'); > + hide_section_with_header('#vm_storage_config', '#vm_storage_section_link', 'Storage'); > + hide_section_with_header('#vm_network_config', '#vm_network_section_link', 'Network'); > + }); > This bit ^^ is all ripped/replaced in the attached patch with a much shorter way to get the same effect. Chaining ftw! > + > diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js > index 67dc455..6055e53 100644 > --- a/src/public/javascripts/ovirt.js > +++ b/src/public/javascripts/ovirt.js > @@ -394,3 +394,28 @@ function get_server_from_url() > var end = window.location.href.indexOf('/', 8) - start; > return window.location.href.substr(start, end); > } > + > +// hides the specified section, altering the specified header div with updated title / arrow > +function hide_section_with_header(section, header, title){ > + content = '
      ' + title + '
      '; Not a big deal, since I moved this into a css class anyway, but this path should not start w/ovirt, as if you run ovirt outside our default context (say give it another name in apache, or run straight mongrel), the image cannot be found by the browser. > + $(header).html(content); > + $(section).hide('slow'); > +}; > + > +// show the specified section, altering the specified header div with updated title / arrow > +function show_section_with_header(section, header, title){ > + content = '
      ' + title + '
      '; > + $(header).html(content); > + $(section).show('slow'); > +}; > + > +// wire up the header to invoke either the show or hide function on click > +function toggle_visability_on_click(section, header, title){ > + $(header).bind('click', function(){ > + if($(section).is(':hidden')){ > + show_section_with_header(section, header, title); > + }else{ > + hide_section_with_header(section, header, title); > + } > + }); > +}; All the above changes can be backed out. > diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css > index 41ad3d0..1409692 100644 > --- a/src/public/stylesheets/components.css > +++ b/src/public/stylesheets/components.css > @@ -339,3 +339,18 @@ > height: 11px; > } > > +#vm_general_config, #vm_resources_config, #vm_storage_config, #vm_network_config{ > + padding-left: 30px; > +} > + > +#vm_general_section_link img, #vm_resources_section_link img, #vm_storage_section_link img, #vm_network_section_link img{ > + float: left; > + padding-top: 2px; > +} > + > +#vm_general_section_link div, #vm_resources_section_link div, #vm_storage_section_link div, #vm_network_section_link div{ > + padding-top: 3px; > + padding-left: 20px; > + padding-bottom: 3px; > + width: 25%; > +} Likewise, these are not needed, just one class (in patch). > -- > 1.6.0.6 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-Revamp-suggestions-cleanup.patch Type: text/x-patch Size: 4739 bytes Desc: not available URL: From jguiditt at redhat.com Thu Jul 23 20:02:28 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 23 Jul 2009 16:02:28 -0400 Subject: [Ovirt-devel] [PATCH server] provide default vm allocated cpu and memory values In-Reply-To: <1247176588-4280-4-git-send-email-mmorsi@redhat.com> References: <1247176588-4280-1-git-send-email-mmorsi@redhat.com> <1247176588-4280-2-git-send-email-mmorsi@redhat.com> <1247176588-4280-3-git-send-email-mmorsi@redhat.com> <1247176588-4280-4-git-send-email-mmorsi@redhat.com> Message-ID: <1248379348.790.177.camel@lenovo> On Thu, 2009-07-09 at 17:56 -0400, Mohammed Morsi wrote: > adds a migration to provides default values for the vm table > columns num_vcpus_allocated and memory_allocated. These > propogate up to the add vm form so the user doesn't have > to specify them > --- > src/db/migrate/040_vm_cpu_and_memory_defaults.rb | 29 ++++++++++++++++++++++ > 1 files changed, 29 insertions(+), 0 deletions(-) > create mode 100644 src/db/migrate/040_vm_cpu_and_memory_defaults.rb > There is already a migration with this number, so it will need to be incremented. Once this is done, I expect I should be able to ack, looks pretty straightforward. From imain at redhat.com Thu Jul 23 21:30:42 2009 From: imain at redhat.com (Ian Main) Date: Thu, 23 Jul 2009 17:30:42 -0400 Subject: [Ovirt-devel] [PATCH server] Add network QMF apis. Message-ID: <1248384642-823-1-git-send-email-imain@redhat.com> From: Scott Seago This adds some of the network API stuff to ovirt-agent. It's still not complete but what is there is functional. Signed-off-by: Ian Main --- src/ovirt-agent/lib/ovirt.rb | 3 + .../ovirt/controllers/hardwarepool_controller.rb | 2 +- .../lib/ovirt/controllers/network_controller.rb | 30 +++++++++++++++ .../lib/ovirt/controllers/ovirt_controller.rb | 25 ++++++++++++ .../physical_network_impl_controller.rb | 13 ++++++ .../lib/ovirt/controllers/vlan_impl_controller.rb | 13 ++++++ .../lib/ovirt/controllers/vmdef_controller.rb | 5 ++ src/ovirt-agent/ovirt-agent.rb | 4 +- src/ovirt-agent/ovirt-test.rb | 40 +++++++++++++++++--- src/ovirt-agent/ovirt_api.xml | 27 +++++++++++++ 10 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 src/ovirt-agent/lib/ovirt/controllers/network_controller.rb create mode 100644 src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb create mode 100644 src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb diff --git a/src/ovirt-agent/lib/ovirt.rb b/src/ovirt-agent/lib/ovirt.rb index e215d3b..0177748 100644 --- a/src/ovirt-agent/lib/ovirt.rb +++ b/src/ovirt-agent/lib/ovirt.rb @@ -7,4 +7,7 @@ require 'ovirt/controllers/ovirt_controller' require 'ovirt/controllers/hardwarepool_controller' require 'ovirt/controllers/vmpool_controller' require 'ovirt/controllers/vmdef_controller' +require 'ovirt/controllers/network_controller' +require 'ovirt/controllers/vlan_impl_controller' +require 'ovirt/controllers/physical_network_impl_controller' diff --git a/src/ovirt-agent/lib/ovirt/controllers/hardwarepool_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/hardwarepool_controller.rb index b7ea3f2..fc5c198 100644 --- a/src/ovirt-agent/lib/ovirt/controllers/hardwarepool_controller.rb +++ b/src/ovirt-agent/lib/ovirt/controllers/hardwarepool_controller.rb @@ -35,7 +35,7 @@ module Ovirt puts "pool_hash: #{pool_hash.inspect}" svc_create(pool_hash, id, {}) - args['vm_pool'] = encode_id(@pool.id) + args['vm_pool'] = @agent.encode_id(VmResourcePoolService.schema_class.id, @pool.id) end end end diff --git a/src/ovirt-agent/lib/ovirt/controllers/network_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/network_controller.rb new file mode 100644 index 0000000..1a8ffd1 --- /dev/null +++ b/src/ovirt-agent/lib/ovirt/controllers/network_controller.rb @@ -0,0 +1,30 @@ +module Ovirt + + class NetworkController < AgentController + + include NetworkService + + def find(id) + svc_show(id) + render(@network) + end + + def list + puts "query for Network class!" + svc_list + @networks.collect { |network| render(network) } + end + + def render(network) + obj = to_qmf(network, :propmap => { :proto => nil}) + obj['proto'] = network.boot_type.proto + puts "network.type is #{@network.type}" + if @network.type == 'PhysicalNetwork' + obj['impl'] = @agent.encode_id(PhysicalNetworkImplController.schema_class.id, @network.id) + elsif @network.type == 'Vlan' + obj['impl'] = @agent.encode_id(VlanImplController.schema_class.id, @network.id) + end + return obj + end + end +end diff --git a/src/ovirt-agent/lib/ovirt/controllers/ovirt_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/ovirt_controller.rb index 4f58113..c831869 100644 --- a/src/ovirt-agent/lib/ovirt/controllers/ovirt_controller.rb +++ b/src/ovirt-agent/lib/ovirt/controllers/ovirt_controller.rb @@ -27,6 +27,31 @@ module Ovirt [ @@instance ] end + def create_vlan_network + extend NetworkService + + boot_type = BootType.find(:first, :conditions => ["proto = ?", args['proto']]) + raise "Unknown boot protocol #{args['proto']}." if not boot_type + puts "in create_vlan_network, boot_type id is #{boot_type.id}" + hash = { :name => args['name'], :number => args['number'], :type => 'Vlan', :boot_type_id => boot_type.id } + + svc_create(hash) + + args['network'] = @agent.encode_id(NetworkController.schema_class.id, @network.id) + end + + def create_physical_network + extend NetworkService + + boot_type = BootType.find(:first, :conditions => ["proto = ?", args['proto']]) + raise "Unknown boot protocol #{args['proto']}." if not boot_type + puts "in create_physical_network, boot_type id is #{boot_type.id}" + hash = { :name => args['name'], :type => 'PhysicalNetwork', :boot_type_id => boot_type.id } + + svc_create(hash) + + args['network'] = @agent.encode_id(NetworkController.schema_class.id, @network.id) + end end end diff --git a/src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb new file mode 100644 index 0000000..f5cb8da --- /dev/null +++ b/src/ovirt-agent/lib/ovirt/controllers/physical_network_impl_controller.rb @@ -0,0 +1,13 @@ +module Ovirt + + class PhysicalNetworkImplController < NetworkController + + def render(network) + obj = Qmf::QmfObject.new(schema_class) + obj['network'] = @agent.encode_id(NetworkController.schema_class.id, @network.id) + obj.set_object_id(encode_id(network.id)) + return obj + end + + end +end diff --git a/src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb new file mode 100644 index 0000000..59766ca --- /dev/null +++ b/src/ovirt-agent/lib/ovirt/controllers/vlan_impl_controller.rb @@ -0,0 +1,13 @@ +module Ovirt + + class VlanImplController < NetworkController + + def render(network) + obj = Qmf::QmfObject.new(schema_class) + obj['number'] = @network.number + obj['network'] = @agent.encode_id(NetworkController.schema_class.id, @network.id) + obj.set_object_id(encode_id(network.id)) + return obj + end + end +end diff --git a/src/ovirt-agent/lib/ovirt/controllers/vmdef_controller.rb b/src/ovirt-agent/lib/ovirt/controllers/vmdef_controller.rb index 1ee69a4..38e3694 100644 --- a/src/ovirt-agent/lib/ovirt/controllers/vmdef_controller.rb +++ b/src/ovirt-agent/lib/ovirt/controllers/vmdef_controller.rb @@ -3,6 +3,11 @@ module Ovirt include VmService + def description(desc) + puts "description property set for vmdef." + svc_update(id, { :description => desc }, false, false) + end + def find(id) svc_show(id) render(@vm) diff --git a/src/ovirt-agent/ovirt-agent.rb b/src/ovirt-agent/ovirt-agent.rb index 62834b0..425699b 100755 --- a/src/ovirt-agent/ovirt-agent.rb +++ b/src/ovirt-agent/ovirt-agent.rb @@ -139,7 +139,7 @@ class OvirtAgent < Qmf::AgentHandler @settings.host = server # FIXME: Bug in swig! #@settings.port = port - @settings.mechanism = 'GSSAPI' + #@settings.mechanism = 'GSSAPI' @logger.info "Connecting to broker on #{@settings.host}.." @@ -165,7 +165,7 @@ class OvirtAgent < Qmf::AgentHandler end @controller_classes.values.each do |controller_class| - @logger.info "Register #{controller_class.schema_class.name} => #{controller_class.name}" + @logger.info "Register #{controller_class.schema_class.name} => #{controller_class.name}, id #{controller_class.schema_class.id}" @agent.register_class(controller_class.schema_class) end end diff --git a/src/ovirt-agent/ovirt-test.rb b/src/ovirt-agent/ovirt-test.rb index 412a8ee..ab67d10 100755 --- a/src/ovirt-agent/ovirt-test.rb +++ b/src/ovirt-agent/ovirt-test.rb @@ -18,6 +18,12 @@ b = s.add_broker(srv, :mechanism => 'GSSAPI') # This segfaults in F10 (ruby-1.8.6.287-2.fc10.x86_64) # p s.objects(:class => "Ovirt") +def print_properties(obj) + for (key, val) in obj.properties + puts " property: #{key}, #{val}" + end +end + ovirt = s.object(:class => "Ovirt") puts "id is #{ovirt.object_id}" raise "ACK! NO ovirt class!" unless ovirt @@ -25,6 +31,32 @@ puts "ovirt.version is #{ovirt.version}" ovirt_by_id = s.object(:object_id => ovirt.object_id) puts "ovirt_by_id.version is #{ovirt_by_id.version}" +puts "Networks" +network = nil +result = ovirt.create_physical_network('ovirt-test', 'static') +puts "Error: #{result.text}" if result.status != 0 +if result.status == 0 + puts "new network created:" + network = s.object(:object_id => result.network) + puts "network is #{network} - id #{result.network}" + print_properties(network) + impl = s.object(:object_id => network.impl) + puts "impl is #{impl}:" + print_properties(impl) +end + +result = ovirt.create_vlan_network('ovirt-test', 'static', 1) +puts "Error: #{result.text}" if result.status != 0 +if result.status == 0 + puts "new network created:" + network = s.object(:object_id => result.network) + puts "network is #{network} - id #{result.network}" + print_properties(network) + impl = s.object(:object_id => network.impl) + puts "impl is #{impl}:" + print_properties(impl) +end + puts "Hardware Pools:" hwps = s.objects(:class => "HardwarePool") hwps.each do |hwp| @@ -33,9 +65,7 @@ hwps.each do |hwp| vmps = s.objects(:class => "VmPool", 'hardware_pool' => hwp.object_id) vmps.each do |vmp| puts "VM pool: #{vmp.name}" - for (key, val) in vmp.properties - puts " property: #{key}, #{val}" - end + print_properties(vmp) end end @@ -98,9 +128,7 @@ puts "VM deleted" if !vm vms = s.objects(:class => 'VmDef') vms.each do |vm| puts "VM: #{vm.description}" - for (key, val) in vm.properties - puts " property: #{key}, #{val}" - end + print_properties(vm) end diff --git a/src/ovirt-agent/ovirt_api.xml b/src/ovirt-agent/ovirt_api.xml index a5aa383..6efa58b 100644 --- a/src/ovirt-agent/ovirt_api.xml +++ b/src/ovirt-agent/ovirt_api.xml @@ -20,6 +20,20 @@ + + + + + + + + + + + + + + @@ -73,12 +87,25 @@ + + + + + + + + + + + + + The virtual NIC of a VM; ties a MAC address to a logical network -- 1.6.2.5 From mmorsi at redhat.com Thu Jul 23 23:01:43 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 23 Jul 2009 19:01:43 -0400 Subject: [Ovirt-devel] viewer updates for fedora submission Message-ID: <1248390107-4077-1-git-send-email-mmorsi@redhat.com> This patchset provides updates to the viewer in preperation for its fedora submission. Included are patches cmd-line parameterizing hostname/user/pass/vm, the addition of a man page, cleanup of the project's structure, and updates to the spec. From mmorsi at redhat.com Thu Jul 23 23:01:44 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 23 Jul 2009 19:01:44 -0400 Subject: [Ovirt-devel] [PATCH viewer] permit hostname / username / password / vm to be passed in via the cmd line In-Reply-To: <1248390107-4077-1-git-send-email-mmorsi@redhat.com> References: <1248390107-4077-1-git-send-email-mmorsi@redhat.com> Message-ID: <1248390107-4077-2-git-send-email-mmorsi@redhat.com> passing in --hostname will bypass the hostname form passing --username --password will bypass the login form passing in --vm will attempt to connect to the vm on login --- src/internal.h | 9 ++++- src/main.c | 100 ++++++++++++++++++++++++++++++++++++++++++++---------- src/wui_thread.c | 15 +------- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/src/internal.h b/src/internal.h index bd65922..8a857eb 100644 --- a/src/internal.h +++ b/src/internal.h @@ -70,10 +70,15 @@ extern gboolean check_cert; /* server we're connecting to */ extern const char* hostname; +extern const char* username; +extern const char* password; /* port which to connect to the server via vnc */ extern int ovirt_server_vnc_port; +/* selected vm name which to automatically connect to */ +extern const char* selected_vm_name; + /* vm currently in focus */ extern struct vm* vm_in_focus; @@ -101,8 +106,8 @@ extern void wui_thread_send_connect (const char *uri); /* Disconnect, forget URI, credentials, VMs etc. */ extern void wui_thread_send_disconnect (void); -/* Set the username and password and tell the WUI to try to log in. */ -extern void wui_thread_send_login (const char *username, const char *password); +/* tell the WUI to try to log in with the username / password variables above */ +extern void wui_thread_send_login (void); /* Tell the WUI thread to refresh the VM list. Note that the WUI * thread does this automatically anyway after a successful login, and diff --git a/src/main.c b/src/main.c index dd26c93..8a5a642 100644 --- a/src/main.c +++ b/src/main.c @@ -73,7 +73,8 @@ gboolean check_cert = FALSE; // do we want this enabled by default ? static GSList *vmlist = NULL; /* internal.h shared constructs */ -const char* hostname; +const char *hostname, *username, *password; +const char* selected_vm_name; struct vm* vm_in_focus; int ovirt_server_vnc_port = 5900; @@ -82,10 +83,14 @@ static void start_ui (void); static GtkWidget *menu_item_new (int which_menu); static void refresh_menu_vm_list (GtkWidget *, gpointer); static void connect_to_wui_on_enter (GtkWidget *, gpointer); -static void connect_to_wui (GtkWidget *, gpointer); +static void connect_to_wui_via_widget (GtkWidget *, gpointer); +static void connect_to_wui (); static void send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef); static void login_to_wui_on_enter (GtkWidget *, gpointer); -static void login_to_wui (GtkWidget *, gpointer); +static void login_to_wui_via_widget (GtkWidget *, gpointer); +static void login_to_wui (); +static void connect_to_vm_name (const char* vm_name); +static void connect_to_vm (struct vm*); static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); static void destroy (GtkWidget *widget, gpointer data); static void clear_connectmenu (void); @@ -201,12 +206,20 @@ static const char *help_msg = "Use '" PACKAGE " --help' to see a list of available command line options"; static const GOptionEntry options[] = { - { "port", 'p', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port, + { "hostname", 'H', 0, G_OPTION_ARG_STRING, &hostname, + "hostname of the server to connect to", NULL}, + { "port", 'P', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port, "set port which to connect to server via vnc", NULL }, + { "username", 'u', 0, G_OPTION_ARG_STRING, &username, + "username which to connect to the server with", NULL}, + { "password", 'p', 0, G_OPTION_ARG_STRING, &password, + "password which to connect to the server with", NULL}, { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo, "set the path of the CA certificate bundle", NULL }, { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert, "check the SSL certificate of the server", NULL }, + { "vm", 'm', 0, G_OPTION_ARG_STRING, &selected_vm_name, + "name of the vm which to connect to on login", NULL}, { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "turn on debugging messages", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, @@ -279,6 +292,29 @@ main (int argc, char *argv[]) start_wui_thread (); start_ui (); + // if parameters were passed in via + // cmd line, perform necessary operations + // (and thus hostname/login dialogs won't + // be displayed later on) + if (hostname != NULL && !STREQ (hostname, "")){ + g_print("connecting to %s, may take a moment\n", hostname); + connect_to_wui(); + while(!wui_thread_is_connected()) sleep(1); + + if (username != NULL && !STREQ (username, "")){ + g_print("logging in as %s, may take a moment\n", username); + login_to_wui(); + while(!wui_thread_is_logged_in()) sleep(1); + + if (selected_vm_name != NULL && !STREQ(selected_vm_name, "")){ + g_print("waiting for list of vms, may take a moment\n"); + while(!wui_thread_has_valid_vmlist()) sleep(1); + connect_to_vm_name(selected_vm_name); + + } + } + } + DEBUG ("entering the Gtk main loop"); gtk_main (); @@ -420,7 +456,7 @@ start_ui (void) g_signal_connect (G_OBJECT (ca_hostname), "key-release-event", G_CALLBACK (connect_to_wui_on_enter), NULL); g_signal_connect (G_OBJECT (ca_button), "clicked", - G_CALLBACK (connect_to_wui), NULL); + G_CALLBACK (connect_to_wui_via_widget), NULL); login_area = gtk_event_box_new (); la_vbox = gtk_vbox_new (FALSE, 0); @@ -448,7 +484,7 @@ start_ui (void) g_signal_connect (G_OBJECT (la_password), "key-release-event", G_CALLBACK (login_to_wui_on_enter), NULL); g_signal_connect (G_OBJECT (la_button), "clicked", - G_CALLBACK (login_to_wui), NULL); + G_CALLBACK (login_to_wui_via_widget), NULL); /* Tabbed notebook. */ notebook = gtk_notebook_new (); @@ -570,16 +606,22 @@ connect_to_wui_on_enter (GtkWidget *widget, gpointer data) // if key released was not 'enter' key if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; - connect_to_wui(widget, data); + connect_to_wui_via_widget(widget, data); } static void -connect_to_wui (GtkWidget *widget, gpointer data) +connect_to_wui_via_widget (GtkWidget *widget, gpointer data) +{ + hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); + connect_to_wui(); +} + +static void +connect_to_wui() { char *uri; int len; - hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); if (STREQ (hostname, "")) return; /* https:// + hostname + /ovirt + \0 */ @@ -597,19 +639,22 @@ login_to_wui_on_enter (GtkWidget *widget, gpointer data) // if key released was not 'enter' key if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; - login_to_wui(widget, data); + login_to_wui_via_widget(widget, data); } static void -login_to_wui (GtkWidget *widget, gpointer data) +login_to_wui_via_widget (GtkWidget *widget, gpointer data) { - const char *username, *password; - username = gtk_entry_get_text (GTK_ENTRY (la_username)); - if (STREQ (username, "")) return; password = gtk_entry_get_text (GTK_ENTRY (la_password)); + login_to_wui(); + +} - wui_thread_send_login (username, password); +static void +login_to_wui(){ + if (STREQ (username, "")) return; + wui_thread_send_login (); } /* Connect to a virtual machine. This callback is called from the @@ -618,9 +663,28 @@ login_to_wui (GtkWidget *widget, gpointer data) * makes a new connection. */ static void -connect_to_vm (GtkWidget *widget, gpointer _vm) +connect_to_vm_via_widget (GtkWidget *widget, gpointer _vm) { - struct vm *vm = (struct vm *) _vm; + connect_to_vm((struct vm*)_vm); +} + +static void +connect_to_vm_name(const char* vm_name) +{ + int i; + main_vmlist_updated(NULL); // dont like running this here, but need to make sure we have latest vm list + for(i = 0; i < g_slist_length(vmlist); ++i){ + if(STREQ(((struct vm*) g_slist_nth_data(vmlist, i))->description, vm_name)){ + connect_to_vm((struct vm*) g_slist_nth_data(vmlist, i)); + break; + } + } +} + +static void +connect_to_vm(struct vm* _vm) +{ + struct vm *vm = _vm; int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); int i, uuidlen, len, fd; GtkWidget *child; @@ -1144,5 +1208,5 @@ add_vm_to_connectmenu (gpointer _vm, gpointer data) gtk_menu_append (GTK_MENU (connectmenu), item); g_signal_connect (G_OBJECT (item), "activate", - G_CALLBACK (connect_to_vm), vm); + G_CALLBACK (connect_to_vm_via_widget), vm); } diff --git a/src/wui_thread.c b/src/wui_thread.c index 8bfa8ca..1688b83 100644 --- a/src/wui_thread.c +++ b/src/wui_thread.c @@ -179,7 +179,7 @@ wui_thread_send_disconnect (void) /* Send the login message to the WUI thread. */ void -wui_thread_send_login (const char *username, const char *password) +wui_thread_send_login (void) { struct message *msg; @@ -226,8 +226,6 @@ static int secs_between_refresh = 60; static CURL *curl = NULL; static char curl_error_buffer[CURL_ERROR_SIZE]; static char *uri = NULL; -static char *username = NULL; -static char *password = NULL; static gboolean process_message (struct message *); @@ -396,8 +394,6 @@ process_message (struct message *msg) write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); if (uri) g_free (uri); - if (username) g_free (username); - if (password) g_free (password); set_connected (FALSE); set_logged_in (FALSE); return 1; @@ -424,20 +420,11 @@ process_message (struct message *msg) curl = NULL; if (uri) g_free (uri); uri = NULL; - if (username) g_free (username); - username = NULL; - if (password) g_free (password); - password = NULL; set_connected (FALSE); set_logged_in (FALSE); break; case LOGIN: - if (username) g_free (username); - username = msg->str1; - if (password) g_free (password); - password = msg->str2; - /* If we're not connected, this message just updates the * username and password. Otherwise if we are connected, * try to login and grab the initial list of VMs. -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 23 23:01:46 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 23 Jul 2009 19:01:46 -0400 Subject: [Ovirt-devel] [PATCH viewer] Added ovirt-viewer man page In-Reply-To: <1248390107-4077-3-git-send-email-mmorsi@redhat.com> References: <1248390107-4077-1-git-send-email-mmorsi@redhat.com> <1248390107-4077-2-git-send-email-mmorsi@redhat.com> <1248390107-4077-3-git-send-email-mmorsi@redhat.com> Message-ID: <1248390107-4077-4-git-send-email-mmorsi@redhat.com> --- Makefile.am | 2 +- configure.ac | 2 +- man/Makefile.am | 7 ++++ man/ovirt-viewer.pod | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 man/Makefile.am create mode 100644 man/ovirt-viewer.pod diff --git a/Makefile.am b/Makefile.am index 50d85c2..6b0c2ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ AUTOMAKE_OPTIONS = foreign -SUBDIRS = src +SUBDIRS = src man EXTRA_DIST = $(PACKAGE).spec diff --git a/configure.ac b/configure.ac index 1cd0447..32d4006 100644 --- a/configure.ac +++ b/configure.ac @@ -57,5 +57,5 @@ fi dnl Output. AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([Makefile src/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile man/Makefile]) AC_OUTPUT diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..9649df6 --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,7 @@ + +man_MANS = ovirt-viewer.1 + +EXTRA_DIST = ovirt-viewer.pod + +ovirt-viewer.1: ovirt-viewer.pod + pod2man $< > $@ diff --git a/man/ovirt-viewer.pod b/man/ovirt-viewer.pod new file mode 100644 index 0000000..3f3f027 --- /dev/null +++ b/man/ovirt-viewer.pod @@ -0,0 +1,92 @@ + +=head1 NAME + +ovirt-viewer - display the graphical console for an oVirt managed virtual machine + +=head1 SYNOPSIS + +B [OPTIONS] + +=head1 DESCRIPTION + +B is a tool that allows a user to login to an +oVirt server and gain VNC access to the VMs under their +control. Users specify the hostname or ip of the server which to +connect to and login credentials, and then select the vms they +want to view. oVirt viewer then pops up the specified vnc window, +allowing users to switch vms at will. + +=head1 OPTIONS + +The following options are accepted when running C: + +=over 4 + +=item -?, --help + +Display command line help summary + +=item -V, --version + +Display program version number + +=item -v, --verbose + +Display information about the connection + +=item -d, --debug + +Display debugging information + +=item -H HOSTNAME, --hostname=HOSTNAME + +Specify the hostname (or ip) of the oVirt server to connect to + +=item -p PORT, --port=PORT + +Specify the port to use for vnc access when connecting to the ovirt server + +=item --cainfo=PATH + +set the path of the CA certificate bundle + +=item --check-certificate + +Check and verify the server's certificate + +=item -u USERNAME --username=USERNAME + +Username to use when logging into the server + +=item -p PASSWORD --password=PASSWORD + +Password to use when logging into the server + +=item -m VMNAME --vm VMNAME + +Set the name of the vm to automatically connect to when we're +logged into the server + +=item --display=DISPLAY + +Set the X display to use + +=back + +=head1 AUTHOR + +Written by Mohammed Morsi, based on virt-viewer + +=head1 BUGS + +Report bugs to the mailing list C + +=head1 COPYRIGHT + +Copyright (C) 2007-2008 Red Hat, Inc, and various contributors. +This is free software. You may redistribute copies of it under the terms of the GNU General +Public License C. There is NO WARRANTY, to the extent +permitted by law. + +=cut + -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 23 23:01:47 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 23 Jul 2009 19:01:47 -0400 Subject: [Ovirt-devel] [PATCH] updated viewer spec to make it fedora rpm guidelines compliant In-Reply-To: <1248390107-4077-4-git-send-email-mmorsi@redhat.com> References: <1248390107-4077-1-git-send-email-mmorsi@redhat.com> <1248390107-4077-2-git-send-email-mmorsi@redhat.com> <1248390107-4077-3-git-send-email-mmorsi@redhat.com> <1248390107-4077-4-git-send-email-mmorsi@redhat.com> Message-ID: <1248390107-4077-5-git-send-email-mmorsi@redhat.com> --- ovirt-viewer.spec | 25 ++++++++++++++++--------- 1 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ovirt-viewer.spec b/ovirt-viewer.spec index 1db5994..28b6357 100644 --- a/ovirt-viewer.spec +++ b/ovirt-viewer.spec @@ -1,9 +1,7 @@ -%define pbuild %{_builddir}/%{name}-%{version} - Name: ovirt-viewer Version: 1.0.0 -Release: 4%{?dist} -Summary: ovirt-viewer is a vnc viewer for ovirt managed vms +Release: 5%{?dist} +Summary: A vnc viewer for oVirt managed vms Group: Applications/System License: GPLv2+ @@ -24,12 +22,10 @@ BuildRequires: libxml2-devel BuildRequires: gnutls-devel BuildRequires: libcurl-devel BuildRequires: glib2-devel +BuildRequires: /usr/bin/pod2man Requires: gtk2 Requires: gtk-vnc -Requires: libxml2 Requires: gnutls -Requires: libcurl -Requires: glib2 %description @@ -46,12 +42,17 @@ allows the users to seemlessly switch between managed vms via ui. %build ./autogen.sh ./configure -make %{?_smp_mflags} + +export CFLAGS="$RPM_OPT_FLAGS" +export CXXFLAGS="$RPM_OPT_FLAGS" +make %{?_smp_mflags} CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" +%{__gzip} man/ovirt-viewer.1 %install rm -rf $RPM_BUILD_ROOT -%{__install} -Dp -m0755 %{pbuild}/src/ovirt-viewer %{buildroot}%{_bindir}/ovirt-viewer +%{__install} -Dp -m0755 src/ovirt-viewer %{buildroot}%{_bindir}/ovirt-viewer +%{__install} -Dp -m0644 man/ovirt-viewer.1.gz %{buildroot}%{_mandir}/man1/ovirt-viewer.1.gz %clean rm -rf $RPM_BUILD_ROOT @@ -59,8 +60,14 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_bindir}/ovirt-viewer +%{_mandir}/man1/ovirt-viewer.1.gz %changelog +* Thu Jul 23 2009 - 1.0.0-5 +- various fixes to make this, rpmlint and + guidelines compliant +- added man page installation + * Mon Jun 01 2009 - 1.0.0-4 - bugfixes, dns lookup and local tunnel port assignment -- 1.6.0.6 From mmorsi at redhat.com Thu Jul 23 23:01:45 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 23 Jul 2009 19:01:45 -0400 Subject: [Ovirt-devel] [PATCH viewer] project cleanup, move sources in a src/ subdir and remove currently unused files In-Reply-To: <1248390107-4077-2-git-send-email-mmorsi@redhat.com> References: <1248390107-4077-1-git-send-email-mmorsi@redhat.com> <1248390107-4077-2-git-send-email-mmorsi@redhat.com> Message-ID: <1248390107-4077-3-git-send-email-mmorsi@redhat.com> --- INSTALL | 237 ----------- Makefile.am | 11 +- README | 6 +- configure.ac | 4 +- internal.h | 220 ---------- main.c | 1148 ----------------------------------------------------- ovirt-viewer.spec | 2 +- src/Makefile.am | 5 + src/internal.h | 220 ++++++++++ src/main.c | 1148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tunnel.c | 327 +++++++++++++++ src/wui_thread.c | 1106 +++++++++++++++++++++++++++++++++++++++++++++++++++ tunnel.c | 327 --------------- wui_thread.c | 1106 --------------------------------------------------- 14 files changed, 2817 insertions(+), 3050 deletions(-) delete mode 100644 AUTHORS delete mode 100644 ChangeLog delete mode 100644 INSTALL delete mode 100644 NEWS delete mode 100644 internal.h delete mode 100644 main.c create mode 100644 src/Makefile.am create mode 100644 src/internal.h create mode 100644 src/main.c create mode 100644 src/tunnel.c create mode 100644 src/wui_thread.c delete mode 100644 tunnel.c delete mode 100644 wui_thread.c diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index e69de29..0000000 diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index e69de29..0000000 diff --git a/INSTALL b/INSTALL deleted file mode 100644 index d3c5b40..0000000 --- a/INSTALL +++ /dev/null @@ -1,237 +0,0 @@ -Installation Instructions -************************* - -Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, -2006, 2007 Free Software Foundation, Inc. - -This file is free documentation; the Free Software Foundation gives -unlimited permission to copy, distribute and modify it. - -Basic Installation -================== - -Briefly, the shell commands `./configure; make; make install' should -configure, build, and install this package. The following -more-detailed instructions are generic; see the `README' file for -instructions specific to this package. - - The `configure' shell script attempts to guess correct values for -various system-dependent variables used during compilation. It uses -those values to create a `Makefile' in each directory of the package. -It may also create one or more `.h' files containing system-dependent -definitions. Finally, it creates a shell script `config.status' that -you can run in the future to recreate the current configuration, and a -file `config.log' containing compiler output (useful mainly for -debugging `configure'). - - It can also use an optional file (typically called `config.cache' -and enabled with `--cache-file=config.cache' or simply `-C') that saves -the results of its tests to speed up reconfiguring. Caching is -disabled by default to prevent problems with accidental use of stale -cache files. - - If you need to do unusual things to compile the package, please try -to figure out how `configure' could check whether to do them, and mail -diffs or instructions to the address given in the `README' so they can -be considered for the next release. If you are using the cache, and at -some point `config.cache' contains results you don't want to keep, you -may remove or edit it. - - The file `configure.ac' (or `configure.in') is used to create -`configure' by a program called `autoconf'. You need `configure.ac' if -you want to change it or regenerate `configure' using a newer version -of `autoconf'. - -The simplest way to compile this package is: - - 1. `cd' to the directory containing the package's source code and type - `./configure' to configure the package for your system. - - Running `configure' might take a while. While running, it prints - some messages telling which features it is checking for. - - 2. Type `make' to compile the package. - - 3. Optionally, type `make check' to run any self-tests that come with - the package. - - 4. Type `make install' to install the programs and any data files and - documentation. - - 5. You can remove the program binaries and object files from the - source code directory by typing `make clean'. To also remove the - files that `configure' created (so you can compile the package for - a different kind of computer), type `make distclean'. There is - also a `make maintainer-clean' target, but that is intended mainly - for the package's developers. If you use it, you may have to get - all sorts of other programs in order to regenerate files that came - with the distribution. - - 6. Often, you can also type `make uninstall' to remove the installed - files again. - -Compilers and Options -===================== - -Some systems require unusual options for compilation or linking that the -`configure' script does not know about. Run `./configure --help' for -details on some of the pertinent environment variables. - - You can give `configure' initial values for configuration parameters -by setting variables in the command line or in the environment. Here -is an example: - - ./configure CC=c99 CFLAGS=-g LIBS=-lposix - - *Note Defining Variables::, for more details. - -Compiling For Multiple Architectures -==================================== - -You can compile the package for more than one kind of computer at the -same time, by placing the object files for each architecture in their -own directory. To do this, you can use GNU `make'. `cd' to the -directory where you want the object files and executables to go and run -the `configure' script. `configure' automatically checks for the -source code in the directory that `configure' is in and in `..'. - - With a non-GNU `make', it is safer to compile the package for one -architecture at a time in the source code directory. After you have -installed the package for one architecture, use `make distclean' before -reconfiguring for another architecture. - -Installation Names -================== - -By default, `make install' installs the package's commands under -`/usr/local/bin', include files under `/usr/local/include', etc. You -can specify an installation prefix other than `/usr/local' by giving -`configure' the option `--prefix=PREFIX'. - - You can specify separate installation prefixes for -architecture-specific files and architecture-independent files. If you -pass the option `--exec-prefix=PREFIX' to `configure', the package uses -PREFIX as the prefix for installing programs and libraries. -Documentation and other data files still use the regular prefix. - - In addition, if you use an unusual directory layout you can give -options like `--bindir=DIR' to specify different values for particular -kinds of files. Run `configure --help' for a list of the directories -you can set and what kinds of files go in them. - - If the package supports it, you can cause programs to be installed -with an extra prefix or suffix on their names by giving `configure' the -option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. - -Optional Features -================= - -Some packages pay attention to `--enable-FEATURE' options to -`configure', where FEATURE indicates an optional part of the package. -They may also pay attention to `--with-PACKAGE' options, where PACKAGE -is something like `gnu-as' or `x' (for the X Window System). The -`README' should mention any `--enable-' and `--with-' options that the -package recognizes. - - For packages that use the X Window System, `configure' can usually -find the X include and library files automatically, but if it doesn't, -you can use the `configure' options `--x-includes=DIR' and -`--x-libraries=DIR' to specify their locations. - -Specifying the System Type -========================== - -There may be some features `configure' cannot figure out automatically, -but needs to determine by the type of machine the package will run on. -Usually, assuming the package is built to be run on the _same_ -architectures, `configure' can figure that out, but if it prints a -message saying it cannot guess the machine type, give it the -`--build=TYPE' option. TYPE can either be a short name for the system -type, such as `sun4', or a canonical name which has the form: - - CPU-COMPANY-SYSTEM - -where SYSTEM can have one of these forms: - - OS KERNEL-OS - - See the file `config.sub' for the possible values of each field. If -`config.sub' isn't included in this package, then this package doesn't -need to know the machine type. - - If you are _building_ compiler tools for cross-compiling, you should -use the option `--target=TYPE' to select the type of system they will -produce code for. - - If you want to _use_ a cross compiler, that generates code for a -platform different from the build platform, you should specify the -"host" platform (i.e., that on which the generated programs will -eventually be run) with `--host=TYPE'. - -Sharing Defaults -================ - -If you want to set default values for `configure' scripts to share, you -can create a site shell script called `config.site' that gives default -values for variables like `CC', `cache_file', and `prefix'. -`configure' looks for `PREFIX/share/config.site' if it exists, then -`PREFIX/etc/config.site' if it exists. Or, you can set the -`CONFIG_SITE' environment variable to the location of the site script. -A warning: not all `configure' scripts look for a site script. - -Defining Variables -================== - -Variables not defined in a site shell script can be set in the -environment passed to `configure'. However, some packages may run -configure again during the build, and the customized values of these -variables may be lost. In order to avoid this problem, you should set -them in the `configure' command line, using `VAR=value'. For example: - - ./configure CC=/usr/local2/bin/gcc - -causes the specified `gcc' to be used as the C compiler (unless it is -overridden in the site shell script). - -Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: - - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash - -`configure' Invocation -====================== - -`configure' recognizes the following options to control how it operates. - -`--help' -`-h' - Print a summary of the options to `configure', and exit. - -`--version' -`-V' - Print the version of Autoconf used to generate the `configure' - script, and exit. - -`--cache-file=FILE' - Enable the cache: use and save the results of the tests in FILE, - traditionally `config.cache'. FILE defaults to `/dev/null' to - disable caching. - -`--config-cache' -`-C' - Alias for `--cache-file=config.cache'. - -`--quiet' -`--silent' -`-q' - Do not print messages saying which checks are being made. To - suppress all normal output, redirect it to `/dev/null' (any error - messages will still be shown). - -`--srcdir=DIR' - Look for the package's source code in directory DIR. Usually - `configure' can determine that directory automatically. - -`configure' also accepts some other, not widely useful, options. Run -`configure --help' for more details. - diff --git a/Makefile.am b/Makefile.am index b5a3e01..50d85c2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ # ovirt viewer console application # Copyright (C) 2008 Red Hat Inc. -# Written by Richard W.M. Jones +# Written by Richard W.M. Jones , Mohammed Morsi # # 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 @@ -16,15 +16,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -bin_PROGRAMS = ovirt-viewer +AUTOMAKE_OPTIONS = foreign -ovirt_viewer_SOURCES = main.c wui_thread.c tunnel.c internal.h -ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) -DCAINFO='"$(CAINFO)"' -ovirt_viewer_LDADD = $(OVIRT_VIEWER_LIBS) +SUBDIRS = src EXTRA_DIST = $(PACKAGE).spec DISTCLEAN_FILES = $(PACKAGE).spec - -valgrind: - valgrind --leak-check=full ./ovirt-viewer$(EXEEXT) --debug diff --git a/NEWS b/NEWS deleted file mode 100644 index e69de29..0000000 diff --git a/README b/README index cff4b1f..3b98bc7 100644 --- a/README +++ b/README @@ -13,8 +13,12 @@ consoles from Linux, Unix or Windows-based desktops. Installation ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? -Read INSTALL for complete instructions, or briefly: 1. ./autogen.sh 2. ./configure 3. make 4. ./ovirt-viewer + +Authors +??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? +Richard W.M. Jones +Mohammed Morsi diff --git a/configure.ac b/configure.ac index 13276ea..95b8b8b 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -AC_INIT(ovirt-viewer, 0.1) +AC_INIT(ovirt-viewer, 1.0.0) AM_INIT_AUTOMAKE dnl Basic C compiler environment. @@ -57,5 +57,5 @@ fi dnl Output. AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT diff --git a/internal.h b/internal.h deleted file mode 100644 index bd65922..0000000 --- a/internal.h +++ /dev/null @@ -1,220 +0,0 @@ -/* ovirt viewer console application - * Copyright (C) 2008 Red Hat Inc. - * Written by Richard W.M. Jones - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef OVIRT_VIEWER_INTERNAL_H -#define OVIRT_VIEWER_INTERNAL_H - -#ifndef G_THREADS_ENABLED -#error "This program requires GLib threads, and cannot be compiled without." -#endif - -/* Debugging messages are always compiled in, but have to - * be turned on using the --debug command line switch. - */ -extern gboolean debug; - -#define DEBUG(fs,...) \ - do { \ - if (debug) { \ - fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__, \ - g_thread_self ()); \ - fprintf (stderr, (fs), ## __VA_ARGS__); \ - fprintf (stderr, "\n"); \ - } \ - } while (0) - -/* Verbose messages are always compiled in, but have to - * be turned on using the --verbose command line switch. - */ -extern gboolean verbose; - -#define VERBOSE(fs,...) \ - do { \ - if (verbose) { \ - fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__, \ - g_thread_self ()); \ - fprintf (stderr, (fs), ## __VA_ARGS__); \ - fprintf (stderr, "\n"); \ - } \ - } while (0) - -/* String equality tests, suggested by Jim Meyering. */ -#define STREQ(a,b) (strcmp((a),(b)) == 0) -#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) -#define STRNEQ(a,b) (strcmp((a),(b)) != 0) -#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0) -#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0) -#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0) -#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0) -#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) -#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) - -extern const char *cainfo; -extern gboolean check_cert; - -/* server we're connecting to */ -extern const char* hostname; - -/* port which to connect to the server via vnc */ -extern int ovirt_server_vnc_port; - -/* vm currently in focus */ -extern struct vm* vm_in_focus; - -/* Communications between the main thread and the WUI thread. For - * an explanation of the threading model, please see the comment in - * main(). - */ - -extern void start_wui_thread (void); -extern void stop_wui_thread (void); - -extern void assert_is_main_thread (const char *, int); -extern void assert_is_wui_thread (const char *, int); - -#define ASSERT_IS_MAIN_THREAD() assert_is_main_thread(__FILE__,__LINE__) -#define ASSERT_IS_WUI_THREAD() assert_is_wui_thread(__FILE__,__LINE__) - -/* These are messages (instructions) which can be sent from the main - * thread to the WUI thread. - */ - -/* Start connecting to WUI, and set the base URI. */ -extern void wui_thread_send_connect (const char *uri); - -/* Disconnect, forget URI, credentials, VMs etc. */ -extern void wui_thread_send_disconnect (void); - -/* Set the username and password and tell the WUI to try to log in. */ -extern void wui_thread_send_login (const char *username, const char *password); - -/* Tell the WUI thread to refresh the VM list. Note that the WUI - * thread does this automatically anyway after a successful login, and - * it also periodically updates the list. This call just tells it to - * do so right away. - */ -extern void wui_thread_send_refresh_vm_list (void); - -/* Retrieve the list of VMs. - * - * wui_thread_get_vmlist returns TRUE if there was a valid list of - * VMs (even if it is empty), or FALSE if we don't have a valid list - * of VMs to return. - * - * NB: Caller must call free_vmlist once it is done with the list. - * - */ -extern gboolean wui_thread_get_vmlist (GSList **ret); -extern void free_vmlist (GSList *vmlist); - -struct vm { - char *description; - int hostid; - int id; - int vnc_port; - int forward_vnc_port; - char *uuid; /* Printable UUID. */ - char *state; - - /* Only the fields above this point are required. The remainder may - * be NULL / -1 to indicate they were missing in the data we got - * back from the WUI. - */ - - long mem_allocated; /* Kbytes */ - long mem_used; /* Kbytes */ - int vcpus_allocated; - int vcpus_used; - char *mac_addr; /* Printable MAC addr. */ -}; - -/* Returns true if the WUI thread thinks it is connected to a remote - * WUI. REST is connectionless so really this means that we - * successfully made an HTTP/HTTPS request "recently", and we haven't - * seen any errors above a certain threshold. - */ -extern gboolean wui_thread_is_connected (void); - -/* Returns true if we successfully logged in with the username - * and password supplied in a recent request, and we haven't - * received any authorization failures since. - */ -extern gboolean wui_thread_is_logged_in (void); - -/* Returns true if we have a valid list of VMs. Note that because - * of race conditions, this doesn't guarantee that wui_thread_get_vmlist - * will work. - */ -extern gboolean wui_thread_has_valid_vmlist (void); - -/* Returns true if the WUI thread is busy performing a request - * at the moment. - */ -extern gboolean wui_thread_is_busy (void); - - -/* Communications between the main thread and the tunnel thread.*/ -extern void start_tunnel (void); -extern void stop_tunnel (void); - -/* port which local tunnel is listening on */ -extern int tunnel_port; - - -/* Returns true if the main vm list contains a - * running vm w/ the same name as specified one - */ -extern gboolean main_vmlist_has_running_vm(struct vm*); - -/* Callbacks from the WUI thread to the main thread. The WUI thread - * adds these to the Glib main loop using g_idle_add, which means they - * actually get executed in the context of the main thread. - */ - -/* The WUI thread has changed its state to connected. */ -extern gboolean main_connected (gpointer); - -/* The WUI thread has changed its state to disconnected. */ -extern gboolean main_disconnected (gpointer); - -/* The WUI thread has changed its state to logged in. */ -extern gboolean main_logged_in (gpointer); - -/* The WUI thread has changed its state to logged out. */ -extern gboolean main_logged_out (gpointer); - -/* The WUI thread has changed its state to busy. */ -extern gboolean main_busy (gpointer); - -/* The WUI thread has changed its state to idle. */ -extern gboolean main_idle (gpointer); - -/* The WUI thread had a connection problem. */ -extern gboolean main_connection_error (gpointer str); - -/* The WUI thread had a login problem. */ -extern gboolean main_login_error (gpointer str); - -/* The WUI thread reports a general status error. */ -extern gboolean main_status_error (gpointer str); - -/* The WUI thread has updated the vm list. */ -extern gboolean main_vmlist_updated (gpointer); - -#endif /* OVIRT_VIEWER_INTERNAL_H */ diff --git a/main.c b/main.c deleted file mode 100644 index dd26c93..0000000 --- a/main.c +++ /dev/null @@ -1,1148 +0,0 @@ -/* ovirt viewer console application - * Copyright (C) 2008 Red Hat Inc. - * Written by Richard W.M. Jones - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include - -#include -#include -#include - -#include -#include -#include -#include - -#ifdef HAVE_NETDB_H -#include -#endif - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#include - -#ifdef HAVE_SYS_SOCKET_H -#include -#endif - -#ifdef HAVE_SYS_UN_H -#include -#endif - -#ifdef HAVE_WINDOWS_H -#include -#endif - -#include "internal.h" - -/*#define HTTPS "https"*/ -#define HTTPS "http" - -gboolean debug = 0; - -gboolean verbose = 0; - -/* Usually /etc/pki/tls/certs/ca-bundle.crt unless overridden during - * configure or on the command line. - */ -const char *cainfo = CAINFO; -gboolean check_cert = FALSE; // do we want this enabled by default ? - // would require a CA by default (self-signed wont work) - // (don't set to true, change var/flag to no_check_cert) - -/* The WUI thread has updated the vm list. Here in the main thread -* we keep our own copy of the vmlist. -*/ -static GSList *vmlist = NULL; - -/* internal.h shared constructs */ -const char* hostname; -struct vm* vm_in_focus; -int ovirt_server_vnc_port = 5900; - -/* Private functions. */ -static void start_ui (void); -static GtkWidget *menu_item_new (int which_menu); -static void refresh_menu_vm_list (GtkWidget *, gpointer); -static void connect_to_wui_on_enter (GtkWidget *, gpointer); -static void connect_to_wui (GtkWidget *, gpointer); -static void send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef); -static void login_to_wui_on_enter (GtkWidget *, gpointer); -static void login_to_wui (GtkWidget *, gpointer); -static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); -static void destroy (GtkWidget *widget, gpointer data); -static void clear_connectmenu (void); -static void help_about (GtkWidget *menu); -static void viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc); -#if 0 -static void viewer_quit (GtkWidget *src, GtkWidget *vnc); -#endif -static void viewer_connected (GtkWidget *vnc); -static void viewer_initialized (GtkWidget *vnc, GtkWidget *data); -static void viewer_disconnected (GtkWidget *vnc); -static void viewer_credential (GtkWidget *vnc, GValueArray *credList); -static int viewer_open_vnc_socket (const char *vnchost, int vncport); -static void add_vm_to_connectmenu (gpointer _vm, gpointer data); - -/* For any widgets accessed from multiple functions. */ -static GtkWidget *window; -static GtkWidget *connectitem; -static GtkWidget *connectmenu; -static GtkWidget *no_connections; -static GtkWidget *refresh_vmlist; -static GtkWidget *refresh_vmlist_separator; -static GtkWidget *connection_area; -static GtkWidget *ca_hostname; -static GtkWidget *ca_button; -static GtkWidget *ca_error; -static GtkWidget *login_area; -static GtkWidget *la_username; -static GtkWidget *la_password; -static GtkWidget *la_button; -static GtkWidget *la_error; -static GtkWidget *notebook; -static GtkWidget *statusbar; -static guint statusbar_ctx; -static GdkCursor *busy_cursor; - -/* Menus. */ -enum menuNums { - CONNECT_MENU, - VIEW_MENU, - SEND_KEY_MENU, - WINDOW_MENU, - HELP_MENU, -}; - -struct menuItem { - guint menu; - GtkWidget *label; - const char *ungrabbed_text; - const char *grabbed_text; -}; - -static struct menuItem menuItems[] = { - { CONNECT_MENU, NULL, "_Connect", "Connect" }, - { VIEW_MENU, NULL, "_View", "View" }, - { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" }, - { WINDOW_MENU, NULL, "_Window", "Window" }, - { HELP_MENU, NULL, "_Help", "Help" } -}; - -#define MAX_KEY_COMBO 3 - struct keyComboDef { - guint keys[MAX_KEY_COMBO]; - guint nkeys; - const char *label; - }; - -#define NUM_KEY_COMBOS 17 -static struct keyComboDef keyCombos[] = { - { { GDK_Control_L, GDK_Alt_L, GDK_Delete }, 3, "Ctrl+Alt+Del"}, - { { GDK_Control_L, GDK_Alt_L, GDK_BackSpace }, 3, "Ctrl+Alt+Backspace"}, - { {}, 0, "" }, - { { GDK_Control_L, GDK_Alt_L, GDK_F1 }, 3, "Ctrl+Alt+F1"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F2 }, 3, "Ctrl+Alt+F2"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F3 }, 3, "Ctrl+Alt+F3"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F4 }, 3, "Ctrl+Alt+F4"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F5"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F6"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F7"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F8"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F9"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F10"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F11"}, - { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F12"}, - { {}, 0, "" }, - { { GDK_Print }, 1, "PrintScreen"}, -}; - -/* Window title. */ -static const char *title = "oVirt Viewer"; - -// when running vm -// 47 chars -static const char *title_vm = - "oVirt Viewer: (ctrl+alt to grab/release mouse) "; - -/* Gtk widget styles. Avoid installation hassles by keeping this - * inside the binary. It can still be overridden by the user (who - * will do that?) - */ -static const char *styles = - "style \"ovirt-viewer-yellow-box\"\n" - "{\n" - " bg[NORMAL] = shade (1.5, \"yellow\")\n" - "}\n" - "widget \"*.ovirt-viewer-connection-area\" style \"ovirt-viewer-yellow-box\"\n" - ; - -/* Command-line arguments. */ -static int print_version = 0; - -static const char *help_msg = - "Use '" PACKAGE " --help' to see a list of available command line options"; - -static const GOptionEntry options[] = { - { "port", 'p', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port, - "set port which to connect to server via vnc", NULL }, - { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo, - "set the path of the CA certificate bundle", NULL }, - { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert, - "check the SSL certificate of the server", NULL }, - { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, - "turn on debugging messages", NULL }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, - "turn on verbose messages", NULL }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version, - "display version and exit", NULL }, - { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } -}; - -int -main (int argc, char *argv[]) -{ - GOptionContext *context; - GError *error = NULL; - - /* Initialize GLib threads before anything else. - * - * There is one main thread, which is used for all Gtk interactions - * and to keep the UI responsive, and one WUI thread. The WUI - * thread is used to connect to the WUI, log in, and maintain the list - * of virtual machines. The WUI thread is the only thread allowed - * to use the CURL library. - * - * The main thread sends instructions to the WUI thread (like "connect", - * "disconnect", etc.) using a simple message-passing protocol and - * a GAsyncQueue. - * - * The WUI thread keeps the UI updated by adding idle events which are - * processed in the main thread - see: - * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html - * - * A tunnel thread is also started to locally listen for vnc packets - * and make them proxyable, adding the vm name, before forwarding onto - * the server - * - * Note that under Win32 you must confine all Gtk/Gdk interactions - * to a single thread - see: - * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html - */ - if (!g_thread_supported ()) { - g_thread_init (NULL); -#ifndef WIN32 - gdk_threads_init (); -#endif - } else { - fprintf (stderr, "GLib threads not supported or not working."); - exit (1); - } - - gtk_init (&argc, &argv); - - /* Parse command-line options. */ - context = g_option_context_new ("oVirt viewer"); - g_option_context_add_main_entries (context, options, NULL); - g_option_context_add_group (context, gtk_get_option_group (TRUE)); - g_option_context_add_group (context, vnc_display_get_option_group ()); - g_option_context_parse (context, &argc, &argv, &error); - - if (error) { - g_print ("%s\n%s\n", error->message, help_msg); - g_error_free (error); - exit (1); - } - - if (print_version) { - printf ("%s %s\n", PACKAGE, VERSION); - exit (0); - } - - start_wui_thread (); - start_ui (); - - DEBUG ("entering the Gtk main loop"); - - gtk_main (); - - stop_wui_thread (); - stop_tunnel(); - - exit (0); -} - -/* Create the viewer window, menus. */ -static void -start_ui (void) -{ - int i; - GtkWidget *vbox; - GtkWidget *menubar; - GtkWidget *view; - GtkWidget *viewmenu; - GtkWidget *sendkey; - GtkWidget *sendkeymenu; - GtkWidget *sendkeymenuitem; - GtkWidget *wind; - GtkWidget *windmenu; - GtkWidget *help; - GtkWidget *helpmenu; - GtkWidget *about; - GtkWidget *ca_vbox; - GtkWidget *ca_hbox; - GtkWidget *ca_label; - GtkWidget *la_vbox; - GtkWidget *la_hbox; - GtkWidget *la_label; - - DEBUG ("creating viewer windows and menus"); - - /* Parse styles. */ - gtk_rc_parse_string (styles); - - /* Busy cursor, used by main_busy() function. - * XXX This cursor is crap - how can we use the Bluecurve/theme cursor? - */ - busy_cursor = gdk_cursor_new (GDK_WATCH); - - /* Window. */ - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); - gtk_window_set_resizable (GTK_WINDOW (window), TRUE); - gtk_window_set_title (GTK_WINDOW (window), title); - - g_signal_connect (G_OBJECT (window), "delete_event", - G_CALLBACK (delete_event), NULL); - g_signal_connect (G_OBJECT (window), "destroy", - G_CALLBACK (destroy), NULL); - - /* VBox for layout within the window. */ - vbox = gtk_vbox_new (FALSE, 0); - - /* Menubar. */ - menubar = gtk_menu_bar_new (); - connectitem = menu_item_new (CONNECT_MENU); - connectmenu = gtk_menu_new (); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (connectitem), connectmenu); - - no_connections = gtk_menu_item_new_with_label ("Not connected"); - gtk_menu_append (GTK_MENU (connectmenu), no_connections); - gtk_widget_set_sensitive (no_connections, FALSE); - - /* This is not added to the menu yet, but will be when we are - * connected. - */ - refresh_vmlist = - gtk_menu_item_new_with_label ("Refresh list of virtual machines"); - g_object_ref (refresh_vmlist); - refresh_vmlist_separator = gtk_separator_menu_item_new (); - g_object_ref (refresh_vmlist_separator); - - g_signal_connect (G_OBJECT (refresh_vmlist), "activate", - G_CALLBACK (refresh_menu_vm_list), NULL); - -#if 0 - screenshot = gtk_menu_item_new_with_mnemonic ("_Screenshot"); - gtk_menu_append (GTK_MENU (filemenu), screenshot); - g_signal_connect (screenshot, "activate", - GTK_SIGNAL_FUNC (take_screenshot), NULL); -#endif - - view = menu_item_new (VIEW_MENU); - viewmenu = gtk_menu_new (); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (view), viewmenu); - - sendkey = menu_item_new (SEND_KEY_MENU); - sendkeymenu = gtk_menu_new (); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (sendkey), sendkeymenu); - - for(i = 0; i < NUM_KEY_COMBOS; ++i){ - sendkeymenuitem = gtk_menu_item_new_with_label (keyCombos[i].label); - gtk_menu_append (GTK_MENU (sendkeymenu), sendkeymenuitem); - g_signal_connect (G_OBJECT (sendkeymenuitem), "activate", - G_CALLBACK (send_key_to_vm), &(keyCombos[i])); - } - - wind = menu_item_new (WINDOW_MENU); - windmenu = gtk_menu_new (); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (wind), windmenu); - - help = menu_item_new (HELP_MENU); - helpmenu = gtk_menu_new (); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu); - - about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); - gtk_menu_append(GTK_MENU(helpmenu), about); - g_signal_connect(about, "activate", GTK_SIGNAL_FUNC (help_about), NULL); - - gtk_menu_bar_append (GTK_MENU_BAR (menubar), connectitem); - gtk_menu_bar_append (GTK_MENU_BAR (menubar), view); - gtk_menu_bar_append (GTK_MENU_BAR (menubar), sendkey); - gtk_menu_bar_append (GTK_MENU_BAR (menubar), wind); - gtk_menu_bar_append (GTK_MENU_BAR (menubar), help); - - /* For login dialogs, etc., usually invisible. */ - connection_area = gtk_event_box_new (); - ca_vbox = gtk_vbox_new (FALSE, 0); - ca_label = gtk_label_new ("Give the name of the oVirt management server:"); - ca_hbox = gtk_hbox_new (FALSE, 0); - ca_hostname = gtk_entry_new (); - gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24); - ca_button = gtk_button_new_with_label ("Connect"); - ca_error = gtk_label_new (NULL); - gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label, FALSE, FALSE, 4); - gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox, TRUE, FALSE, 4); - gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error, TRUE, FALSE, 4); - gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox); - - gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area"); - - g_signal_connect (G_OBJECT (ca_hostname), "key-release-event", - G_CALLBACK (connect_to_wui_on_enter), NULL); - g_signal_connect (G_OBJECT (ca_button), "clicked", - G_CALLBACK (connect_to_wui), NULL); - - login_area = gtk_event_box_new (); - la_vbox = gtk_vbox_new (FALSE, 0); - la_hbox = gtk_hbox_new (FALSE, 0); - la_label = gtk_label_new ("Username / password:"); - la_username = gtk_entry_new (); - gtk_entry_set_width_chars (GTK_ENTRY (la_username), 12); - la_password = gtk_entry_new (); - gtk_entry_set_width_chars (GTK_ENTRY (la_password), 12); - gtk_entry_set_visibility (GTK_ENTRY (la_password), FALSE); - la_button = gtk_button_new_with_label ("Login"); - la_error = gtk_label_new (NULL); - gtk_container_add (GTK_CONTAINER (la_hbox), la_username); - gtk_container_add (GTK_CONTAINER (la_hbox), la_password); - gtk_container_add (GTK_CONTAINER (la_hbox), la_button); - gtk_box_pack_start (GTK_BOX (la_vbox), la_label, TRUE, FALSE, 4); - gtk_box_pack_start (GTK_BOX (la_vbox), la_hbox, TRUE, FALSE, 4); - gtk_box_pack_start (GTK_BOX (la_vbox), la_error, TRUE, FALSE, 4); - gtk_container_add (GTK_CONTAINER (login_area), la_vbox); - - gtk_widget_set_name (login_area, "ovirt-viewer-login-area"); - - g_signal_connect (G_OBJECT (la_username), "key-release-event", - G_CALLBACK (login_to_wui_on_enter), NULL); - g_signal_connect (G_OBJECT (la_password), "key-release-event", - G_CALLBACK (login_to_wui_on_enter), NULL); - g_signal_connect (G_OBJECT (la_button), "clicked", - G_CALLBACK (login_to_wui), NULL); - - /* Tabbed notebook. */ - notebook = gtk_notebook_new (); - /*gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);*/ - gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE); - gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE); - - /* Status bar. */ - statusbar = gtk_statusbar_new (); - statusbar_ctx = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar), - "context"); - gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, ""); - - /* Packing. */ - gtk_container_add (GTK_CONTAINER (window), vbox); - gtk_container_add_with_properties (GTK_CONTAINER (vbox), menubar, - "expand", FALSE, NULL); - gtk_container_add_with_properties (GTK_CONTAINER (vbox), connection_area, - "expand", FALSE, "fill", TRUE, NULL); - gtk_container_add_with_properties (GTK_CONTAINER (vbox), login_area, - "expand", FALSE, "fill", TRUE, NULL); - gtk_container_add_with_properties (GTK_CONTAINER (vbox), notebook, - "expand", TRUE, NULL); - gtk_container_add_with_properties (GTK_CONTAINER (vbox), statusbar, - "expand", FALSE, NULL); - - /* Show widgets. */ - gtk_widget_show_all (window); - - if (wui_thread_is_connected ()) - gtk_widget_hide (connection_area); - if (!wui_thread_is_connected () || wui_thread_is_logged_in ()) - gtk_widget_hide (login_area); -} - -static GtkWidget * -menu_item_new (int which_menu) -{ - GtkWidget *widget; - GtkWidget *label; - const char *text; - - text = menuItems[which_menu].ungrabbed_text; - - widget = gtk_menu_item_new (); - label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL); - gtk_label_set_text_with_mnemonic (GTK_LABEL (label), text); - gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - - gtk_container_add (GTK_CONTAINER (widget), label); - gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), widget); - gtk_widget_show (label); - - menuItems[which_menu].label = label; - - return widget; -} - -static gboolean -delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) -{ - DEBUG ("delete_event"); - return FALSE; -} - -static void -destroy (GtkWidget *widget, gpointer data) -{ - DEBUG ("destroy"); - gtk_main_quit (); -} - -static void -help_about (GtkWidget *menu) -{ - GtkWidget *about; - const char *authors[] = { - "Richard W.M. Jones ", - "Daniel P. Berrange ", - "Mohammed Morsi ", - NULL - }; - - about = gtk_about_dialog_new(); - - gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(about), "oVirt Viewer"); - gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), VERSION); - gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://ovirt.org/"); - gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(about), "oVirt website"); - gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about), authors); - gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), - "This program is free software; you can redistribute it and/or modify\n" \ - "it under the terms of the GNU General Public License as published by\n" \ - "the Free Software Foundation; either version 2 of the License, or\n" \ - "(at your option) any later version.\n" \ - "\n" \ - "This program is distributed in the hope that it will be useful,\n" \ - "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ - "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ - "GNU General Public License for more details.\n" \ - "\n" \ - "You should have received a copy of the GNU General Public License\n" \ - "along with this program; if not, write to the Free Software\n" \ - "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n"); - - gtk_dialog_run(GTK_DIALOG(about)); - gtk_widget_destroy(about); -} - -static void -refresh_menu_vm_list(GtkWidget *widget, gpointer data) -{ - wui_thread_send_refresh_vm_list(); -} - -static void -connect_to_wui_on_enter (GtkWidget *widget, gpointer data) -{ - // if key released was not 'enter' key - if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; - - connect_to_wui(widget, data); -} - -static void -connect_to_wui (GtkWidget *widget, gpointer data) -{ - char *uri; - int len; - - hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); - if (STREQ (hostname, "")) return; - - /* https:// + hostname + /ovirt + \0 */ - len = 8 + strlen (hostname) + 6 + 1; - uri = g_alloca (len); - snprintf (uri, len, HTTPS "://%s/ovirt", hostname); - - wui_thread_send_connect (uri); - start_tunnel(); -} - -static void -login_to_wui_on_enter (GtkWidget *widget, gpointer data) -{ - // if key released was not 'enter' key - if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; - - login_to_wui(widget, data); -} - -static void -login_to_wui (GtkWidget *widget, gpointer data) -{ - const char *username, *password; - - username = gtk_entry_get_text (GTK_ENTRY (la_username)); - if (STREQ (username, "")) return; - password = gtk_entry_get_text (GTK_ENTRY (la_password)); - - wui_thread_send_login (username, password); -} - -/* Connect to a virtual machine. This callback is called from the - * connect menu. It searches the notebook of gtk-vnc widgets to see - * if we have already connected to this machine, and if not it - * makes a new connection. - */ -static void -connect_to_vm (GtkWidget *widget, gpointer _vm) -{ - struct vm *vm = (struct vm *) _vm; - int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); - int i, uuidlen, len, fd; - GtkWidget *child; - const char *label; - char *label2; - char new_title[97]; // 47 chars for title + 50 for vm name - - DEBUG ("searching tabs for uuid %s", vm->uuid); - - uuidlen = strlen (vm->uuid); - - /* Search the tabs for this UUID, and if found, switch to it and return. */ - for (i = 0; i < n; ++i) { - child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i); - label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (notebook), child); - len = strlen (label); - if (len >= uuidlen && - STREQ (label + len - uuidlen, vm->uuid)) { - DEBUG ("found on tab %d", i); - gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i); - return; - } - } - - DEBUG ("not found, creating new tab"); - - // before we open vnc connection, make sure vm is running - gboolean vm_running = main_vmlist_has_running_vm(vm); - - if(!vm_running){ - main_status_error (g_strdup ("VM not running")); - return; - } - - // FIXME on notebook tab switch, change vm_in_focus - vm_in_focus = vm; - - /* This VM isn't in the notebook already, so create a new console. */ - DEBUG ("connecting to local tunnel on port %i", tunnel_port); - fd = viewer_open_vnc_socket("127.0.0.1", tunnel_port); - if (fd == -1) return; /* We've already given an error. */ - - child = vnc_display_new (); - if (! vnc_display_open_fd (VNC_DISPLAY (child), fd)) { - main_status_error (g_strdup ("internal error in Gtk-VNC widget")); - return; - } - - main_status_error(g_strdup("")); - - for(i = 0; i < 47; ++i) new_title[i] = title_vm[i]; - for(i = 0; i < 50; ++i){ - if(vm->description[i] == '\0') break; - new_title[47 + i] = vm->description[i]; - } - new_title[i < 50 ? i+47 : 96] = '\0'; - gtk_window_set_title (GTK_WINDOW (window), new_title); - - /* - gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-grab", - GTK_SIGNAL_FUNC(viewer_grab), window); - gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-ungrab", - GTK_SIGNAL_FUNC(viewer_ungrab), window); - */ - - gtk_signal_connect(GTK_OBJECT(child), "delete-event", - GTK_SIGNAL_FUNC(viewer_shutdown), child); - - gtk_signal_connect(GTK_OBJECT(child), "vnc-connected", - GTK_SIGNAL_FUNC(viewer_connected), NULL); - gtk_signal_connect(GTK_OBJECT(child), "vnc-initialized", - GTK_SIGNAL_FUNC(viewer_initialized), NULL); - gtk_signal_connect(GTK_OBJECT(child), "vnc-disconnected", - GTK_SIGNAL_FUNC(viewer_disconnected), NULL); - - g_signal_connect(GTK_OBJECT(child), "vnc-auth-credential", - GTK_SIGNAL_FUNC(viewer_credential), NULL); - - /* NB. We have to do this before adding it to the notebook. */ - gtk_widget_show (child); - - /* Choose a tab label, which MUST end with the uuid string, since - * we use the tab label to store uuid. - */ - len = strlen (vm->description) + 1 + strlen (vm->uuid) + 1; - label2 = g_alloca (len); - snprintf (label2, len, "%s %s", vm->description, vm->uuid); - - i = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL); - gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (notebook), child, label2); - gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i); - - DEBUG ("finished creating new tab"); -} - -/* Send key to a virtual machine. This callback is called from the - * send key menu. If finds vm in focus, and sends specified key to it - */ -static void -send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef) -{ - GtkWidget* viewer; - struct keyComboDef* key_combo = (struct keyComboDef*) _keyComboDef; - int c = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); - - if(c < 0){ - return; - } - - viewer = gtk_notebook_get_nth_page(GTK_NOTEBOOK (notebook), c); - if(viewer != NULL){ - DEBUG ("sending keys to vm"); - vnc_display_send_keys(VNC_DISPLAY(viewer), - key_combo->keys, - key_combo->nkeys); - } -} - - -/* -static void viewer_grab(GtkWidget *vnc, GtkWidget *window) -{ - int i; - - viewer_set_title(VNC_DISPLAY(vnc), window, TRUE); - - for (i = 0 ; i < LAST_MENU; i++) { - gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].grabbed_text); - } -} - -static void viewer_ungrab(GtkWidget *vnc, GtkWidget *window) -{ - int i; - - viewer_set_title(VNC_DISPLAY(vnc), window, FALSE); - - for (i = 0 ; i < LAST_MENU; i++) { - gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].ungrabbed_text); - } -} -*/ - -static void -viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc) -{ - vnc_display_close (VNC_DISPLAY(vnc)); - - /* Just close the notebook tab for now. XXX */ - gtk_notebook_remove_page (GTK_NOTEBOOK (notebook), - gtk_notebook_page_num (GTK_NOTEBOOK (notebook), - vnc)); -} - -#if 0 -static void -viewer_quit (GtkWidget *src, GtkWidget *vnc) -{ - viewer_shutdown (src, NULL, vnc); -} -#endif - -static void -viewer_connected (GtkWidget *vnc) -{ - DEBUG ("Connected to server"); -} - -static void -viewer_initialized (GtkWidget *vnc, GtkWidget *data) -{ - DEBUG ("Connection initialized"); -} - -static void -viewer_disconnected (GtkWidget *vnc) -{ - DEBUG ("Disconnected from server"); -} - -static void -viewer_credential (GtkWidget *vnc, GValueArray *credList) -{ - GtkWidget *dialog = NULL; - int response; - unsigned int i, prompt = 0; - const char **data; - - DEBUG ("Got credential request for %d credential(s)", - credList->n_values); - - data = g_new0(const char *, credList->n_values); - - for (i = 0 ; i < credList->n_values ; i++) { - GValue *cred = g_value_array_get_nth(credList, i); - switch (g_value_get_enum(cred)) { - case VNC_DISPLAY_CREDENTIAL_USERNAME: - case VNC_DISPLAY_CREDENTIAL_PASSWORD: - prompt++; - break; - case VNC_DISPLAY_CREDENTIAL_CLIENTNAME: - data[i] = "libvirt"; - default: - break; - } - } - - if (prompt) { - GtkWidget **label, **entry, *box, *vbox; - int row; - dialog = gtk_dialog_new_with_buttons("Authentication required", - NULL, - 0, - GTK_STOCK_CANCEL, - GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, - GTK_RESPONSE_OK, - NULL); - gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); - - box = gtk_table_new(credList->n_values, 2, FALSE); - label = g_new(GtkWidget *, prompt); - entry = g_new(GtkWidget *, prompt); - - for (i = 0, row =0 ; i < credList->n_values ; i++) { - GValue *cred = g_value_array_get_nth(credList, i); - switch (g_value_get_enum(cred)) { - case VNC_DISPLAY_CREDENTIAL_USERNAME: - label[row] = gtk_label_new("Username:"); - break; - case VNC_DISPLAY_CREDENTIAL_PASSWORD: - label[row] = gtk_label_new("Password:"); - break; - default: - continue; - } - entry[row] = gtk_entry_new(); - if (g_value_get_enum(cred) == VNC_DISPLAY_CREDENTIAL_PASSWORD) - gtk_entry_set_visibility(GTK_ENTRY(entry[row]), FALSE); - - gtk_table_attach(GTK_TABLE(box), label[i], 0, 1, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3); - gtk_table_attach(GTK_TABLE(box), entry[i], 1, 2, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3); - row++; - } - - vbox = gtk_bin_get_child(GTK_BIN(dialog)); - gtk_container_add(GTK_CONTAINER(vbox), box); - - gtk_widget_show_all(dialog); - response = gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_hide(GTK_WIDGET(dialog)); - - if (response == GTK_RESPONSE_OK) { - for (i = 0, row = 0 ; i < credList->n_values ; i++) { - GValue *cred = g_value_array_get_nth(credList, i); - switch (g_value_get_enum(cred)) { - case VNC_DISPLAY_CREDENTIAL_USERNAME: - case VNC_DISPLAY_CREDENTIAL_PASSWORD: - data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row])); - break; - } - } - } - } - - for (i = 0 ; i < credList->n_values ; i++) { - GValue *cred = g_value_array_get_nth(credList, i); - if (data[i]) { - if (vnc_display_set_credential(VNC_DISPLAY(vnc), - g_value_get_enum(cred), - data[i])) { - DEBUG("Failed to set credential type %d", - g_value_get_enum(cred)); - vnc_display_close(VNC_DISPLAY(vnc)); - } - } else { - DEBUG("Unsupported credential type %d", - g_value_get_enum(cred)); - vnc_display_close(VNC_DISPLAY(vnc)); - } - } - - g_free(data); - if (dialog) - gtk_widget_destroy(GTK_WIDGET(dialog)); -} - -#if defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS) - -static int -viewer_open_vnc_socket(const char* vnchost, int vncport) -{ - int result, socketfd; - char port[10]; - struct addrinfo* vnc_addr; - - sprintf(port, "%d", vncport); - - result = getaddrinfo(vnchost, port, NULL, &vnc_addr); - if(result != 0 || vnc_addr == NULL) - return -1; - - // just use first found, ignoring rest - socketfd = socket(vnc_addr->ai_family, - vnc_addr->ai_socktype, - vnc_addr->ai_protocol); - - if(connect(socketfd, vnc_addr->ai_addr, vnc_addr->ai_addrlen) <0) - socketfd = -1; - - freeaddrinfo(vnc_addr); - - return socketfd; -} - -#endif /* defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS) */ - -/* Remove all menu items from the Connect menu. */ -static void -remove_menu_item (GtkWidget *menu_item, gpointer data) -{ - gtk_container_remove (GTK_CONTAINER (connectmenu), menu_item); -} - -static void -clear_connectmenu (void) -{ - DEBUG ("clear Connect menu"); - gtk_container_foreach (GTK_CONTAINER (connectmenu), remove_menu_item, NULL); -} - -/* The WUI thread has changed its state to connected. */ -gboolean -main_connected (gpointer data) -{ - DEBUG ("connected"); - ASSERT_IS_MAIN_THREAD (); - - gtk_label_set_text (GTK_LABEL (ca_error), NULL); - - gtk_widget_hide (connection_area); - if (!wui_thread_is_logged_in ()) - gtk_widget_show (login_area); - return FALSE; -} - -/* The WUI thread has changed its state to disconnected. */ -gboolean -main_disconnected (gpointer data) -{ - DEBUG ("disconnected"); - ASSERT_IS_MAIN_THREAD (); - - gtk_widget_show (connection_area); - gtk_widget_hide (login_area); - - clear_connectmenu (); - gtk_menu_append (GTK_MENU (connectmenu), no_connections); - gtk_widget_show_all (connectmenu); - - return FALSE; -} - -/* The WUI thread has changed its state to logged in. */ -gboolean -main_logged_in (gpointer data) -{ - DEBUG ("logged in"); - ASSERT_IS_MAIN_THREAD (); - - gtk_widget_hide (login_area); - return FALSE; -} - -/* The WUI thread has changed its state to logged out. */ -gboolean -main_logged_out (gpointer data) -{ - DEBUG ("logged out"); - ASSERT_IS_MAIN_THREAD (); - - if (wui_thread_is_connected ()) - gtk_widget_show (login_area); - return FALSE; -} - -/* The WUI thread has changed its state to busy. */ -gboolean -main_busy (gpointer data) -{ - GdkWindow *gdk_window; - - DEBUG ("busy"); - ASSERT_IS_MAIN_THREAD (); - - gdk_window = gtk_widget_get_window (window); - if (gdk_window) { - gdk_window_set_cursor (gdk_window, busy_cursor); - gdk_flush (); - } - - return FALSE; -} - -/* The WUI thread has changed its state to idle. */ -gboolean -main_idle (gpointer data) -{ - GdkWindow *gdk_window; - - DEBUG ("idle"); - ASSERT_IS_MAIN_THREAD (); - - gdk_window = gtk_widget_get_window (window); - if (gdk_window) { - gdk_window_set_cursor (gdk_window, NULL); - gdk_flush (); - } - - return FALSE; -} - -/* The WUI thread had a connection error. This function must - * free the string. - */ -gboolean -main_connection_error (gpointer _str) -{ - char *str = (char *) _str; - - DEBUG ("connection error: %s", str); - ASSERT_IS_MAIN_THREAD (); - - gtk_label_set_text (GTK_LABEL (ca_error), str); - g_free (str); - - return FALSE; -} - -/* The WUI thread had a login error. This function must - * free the string. - */ -gboolean -main_login_error (gpointer _str) -{ - char *str = (char *) _str; - - DEBUG ("login error: %s", str); - ASSERT_IS_MAIN_THREAD (); - - gtk_label_set_text (GTK_LABEL (la_error), str); - g_free (str); - - return FALSE; -} - -/* The WUI thread reports a general status error. This function - * must free the string. - */ -gboolean -main_status_error (gpointer _str) -{ - char *str = (char *) _str; - - DEBUG ("status error: %s", str); - ASSERT_IS_MAIN_THREAD (); - - gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar_ctx); - gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, str); - g_free (str); - - return FALSE; -} - -gboolean -main_vmlist_updated (gpointer data) -{ - GSList *new_vmlist; - - DEBUG ("vmlist updated"); - ASSERT_IS_MAIN_THREAD (); - - /* Get the new vmlist. */ - if (wui_thread_get_vmlist (&new_vmlist)) { - /* Free the previous vmlist. This invalidates all the vm pointers - * contained in the Connect menu callbacks, but we're going to - * delete those callbacks and create news ones in a moment anyway ... - */ - free_vmlist (vmlist); - - vmlist = new_vmlist; - - clear_connectmenu (); - - gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist); - - if (vmlist != NULL) { - gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist_separator); - g_slist_foreach (vmlist, add_vm_to_connectmenu, NULL); - } - - /* Grrrr Gtk is stupid. */ - gtk_widget_show_all (connectmenu); - } - - return FALSE; -} - -static void -add_vm_to_connectmenu (gpointer _vm, gpointer data) -{ - struct vm *vm = (struct vm *) _vm; - GtkWidget *item; - - // TODO only present running vms ? - // if(vm->state == "running") - - DEBUG ("adding %s to Connect menu", vm->description); - - item = gtk_menu_item_new_with_label (vm->description); - gtk_menu_append (GTK_MENU (connectmenu), item); - - g_signal_connect (G_OBJECT (item), "activate", - G_CALLBACK (connect_to_vm), vm); -} diff --git a/ovirt-viewer.spec b/ovirt-viewer.spec index a4d826e..1db5994 100644 --- a/ovirt-viewer.spec +++ b/ovirt-viewer.spec @@ -51,7 +51,7 @@ make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT -%{__install} -Dp -m0755 %{pbuild}/ovirt-viewer %{buildroot}%{_bindir}/ovirt-viewer +%{__install} -Dp -m0755 %{pbuild}/src/ovirt-viewer %{buildroot}%{_bindir}/ovirt-viewer %clean rm -rf $RPM_BUILD_ROOT diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..cdd8f40 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,5 @@ +bin_PROGRAMS = ovirt-viewer + +ovirt_viewer_SOURCES = main.c wui_thread.c tunnel.c internal.h +ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) $(CFLAGS) -DCAINFO='"$(CAINFO)"' +ovirt_viewer_LDADD = $(OVIRT_VIEWER_LIBS) diff --git a/src/internal.h b/src/internal.h new file mode 100644 index 0000000..bd65922 --- /dev/null +++ b/src/internal.h @@ -0,0 +1,220 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Richard W.M. Jones + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef OVIRT_VIEWER_INTERNAL_H +#define OVIRT_VIEWER_INTERNAL_H + +#ifndef G_THREADS_ENABLED +#error "This program requires GLib threads, and cannot be compiled without." +#endif + +/* Debugging messages are always compiled in, but have to + * be turned on using the --debug command line switch. + */ +extern gboolean debug; + +#define DEBUG(fs,...) \ + do { \ + if (debug) { \ + fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__, \ + g_thread_self ()); \ + fprintf (stderr, (fs), ## __VA_ARGS__); \ + fprintf (stderr, "\n"); \ + } \ + } while (0) + +/* Verbose messages are always compiled in, but have to + * be turned on using the --verbose command line switch. + */ +extern gboolean verbose; + +#define VERBOSE(fs,...) \ + do { \ + if (verbose) { \ + fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__, \ + g_thread_self ()); \ + fprintf (stderr, (fs), ## __VA_ARGS__); \ + fprintf (stderr, "\n"); \ + } \ + } while (0) + +/* String equality tests, suggested by Jim Meyering. */ +#define STREQ(a,b) (strcmp((a),(b)) == 0) +#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) +#define STRNEQ(a,b) (strcmp((a),(b)) != 0) +#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0) +#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0) +#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0) +#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0) +#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) +#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0) + +extern const char *cainfo; +extern gboolean check_cert; + +/* server we're connecting to */ +extern const char* hostname; + +/* port which to connect to the server via vnc */ +extern int ovirt_server_vnc_port; + +/* vm currently in focus */ +extern struct vm* vm_in_focus; + +/* Communications between the main thread and the WUI thread. For + * an explanation of the threading model, please see the comment in + * main(). + */ + +extern void start_wui_thread (void); +extern void stop_wui_thread (void); + +extern void assert_is_main_thread (const char *, int); +extern void assert_is_wui_thread (const char *, int); + +#define ASSERT_IS_MAIN_THREAD() assert_is_main_thread(__FILE__,__LINE__) +#define ASSERT_IS_WUI_THREAD() assert_is_wui_thread(__FILE__,__LINE__) + +/* These are messages (instructions) which can be sent from the main + * thread to the WUI thread. + */ + +/* Start connecting to WUI, and set the base URI. */ +extern void wui_thread_send_connect (const char *uri); + +/* Disconnect, forget URI, credentials, VMs etc. */ +extern void wui_thread_send_disconnect (void); + +/* Set the username and password and tell the WUI to try to log in. */ +extern void wui_thread_send_login (const char *username, const char *password); + +/* Tell the WUI thread to refresh the VM list. Note that the WUI + * thread does this automatically anyway after a successful login, and + * it also periodically updates the list. This call just tells it to + * do so right away. + */ +extern void wui_thread_send_refresh_vm_list (void); + +/* Retrieve the list of VMs. + * + * wui_thread_get_vmlist returns TRUE if there was a valid list of + * VMs (even if it is empty), or FALSE if we don't have a valid list + * of VMs to return. + * + * NB: Caller must call free_vmlist once it is done with the list. + * + */ +extern gboolean wui_thread_get_vmlist (GSList **ret); +extern void free_vmlist (GSList *vmlist); + +struct vm { + char *description; + int hostid; + int id; + int vnc_port; + int forward_vnc_port; + char *uuid; /* Printable UUID. */ + char *state; + + /* Only the fields above this point are required. The remainder may + * be NULL / -1 to indicate they were missing in the data we got + * back from the WUI. + */ + + long mem_allocated; /* Kbytes */ + long mem_used; /* Kbytes */ + int vcpus_allocated; + int vcpus_used; + char *mac_addr; /* Printable MAC addr. */ +}; + +/* Returns true if the WUI thread thinks it is connected to a remote + * WUI. REST is connectionless so really this means that we + * successfully made an HTTP/HTTPS request "recently", and we haven't + * seen any errors above a certain threshold. + */ +extern gboolean wui_thread_is_connected (void); + +/* Returns true if we successfully logged in with the username + * and password supplied in a recent request, and we haven't + * received any authorization failures since. + */ +extern gboolean wui_thread_is_logged_in (void); + +/* Returns true if we have a valid list of VMs. Note that because + * of race conditions, this doesn't guarantee that wui_thread_get_vmlist + * will work. + */ +extern gboolean wui_thread_has_valid_vmlist (void); + +/* Returns true if the WUI thread is busy performing a request + * at the moment. + */ +extern gboolean wui_thread_is_busy (void); + + +/* Communications between the main thread and the tunnel thread.*/ +extern void start_tunnel (void); +extern void stop_tunnel (void); + +/* port which local tunnel is listening on */ +extern int tunnel_port; + + +/* Returns true if the main vm list contains a + * running vm w/ the same name as specified one + */ +extern gboolean main_vmlist_has_running_vm(struct vm*); + +/* Callbacks from the WUI thread to the main thread. The WUI thread + * adds these to the Glib main loop using g_idle_add, which means they + * actually get executed in the context of the main thread. + */ + +/* The WUI thread has changed its state to connected. */ +extern gboolean main_connected (gpointer); + +/* The WUI thread has changed its state to disconnected. */ +extern gboolean main_disconnected (gpointer); + +/* The WUI thread has changed its state to logged in. */ +extern gboolean main_logged_in (gpointer); + +/* The WUI thread has changed its state to logged out. */ +extern gboolean main_logged_out (gpointer); + +/* The WUI thread has changed its state to busy. */ +extern gboolean main_busy (gpointer); + +/* The WUI thread has changed its state to idle. */ +extern gboolean main_idle (gpointer); + +/* The WUI thread had a connection problem. */ +extern gboolean main_connection_error (gpointer str); + +/* The WUI thread had a login problem. */ +extern gboolean main_login_error (gpointer str); + +/* The WUI thread reports a general status error. */ +extern gboolean main_status_error (gpointer str); + +/* The WUI thread has updated the vm list. */ +extern gboolean main_vmlist_updated (gpointer); + +#endif /* OVIRT_VIEWER_INTERNAL_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..dd26c93 --- /dev/null +++ b/src/main.c @@ -0,0 +1,1148 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Richard W.M. Jones + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#include + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#ifdef HAVE_WINDOWS_H +#include +#endif + +#include "internal.h" + +/*#define HTTPS "https"*/ +#define HTTPS "http" + +gboolean debug = 0; + +gboolean verbose = 0; + +/* Usually /etc/pki/tls/certs/ca-bundle.crt unless overridden during + * configure or on the command line. + */ +const char *cainfo = CAINFO; +gboolean check_cert = FALSE; // do we want this enabled by default ? + // would require a CA by default (self-signed wont work) + // (don't set to true, change var/flag to no_check_cert) + +/* The WUI thread has updated the vm list. Here in the main thread +* we keep our own copy of the vmlist. +*/ +static GSList *vmlist = NULL; + +/* internal.h shared constructs */ +const char* hostname; +struct vm* vm_in_focus; +int ovirt_server_vnc_port = 5900; + +/* Private functions. */ +static void start_ui (void); +static GtkWidget *menu_item_new (int which_menu); +static void refresh_menu_vm_list (GtkWidget *, gpointer); +static void connect_to_wui_on_enter (GtkWidget *, gpointer); +static void connect_to_wui (GtkWidget *, gpointer); +static void send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef); +static void login_to_wui_on_enter (GtkWidget *, gpointer); +static void login_to_wui (GtkWidget *, gpointer); +static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); +static void destroy (GtkWidget *widget, gpointer data); +static void clear_connectmenu (void); +static void help_about (GtkWidget *menu); +static void viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc); +#if 0 +static void viewer_quit (GtkWidget *src, GtkWidget *vnc); +#endif +static void viewer_connected (GtkWidget *vnc); +static void viewer_initialized (GtkWidget *vnc, GtkWidget *data); +static void viewer_disconnected (GtkWidget *vnc); +static void viewer_credential (GtkWidget *vnc, GValueArray *credList); +static int viewer_open_vnc_socket (const char *vnchost, int vncport); +static void add_vm_to_connectmenu (gpointer _vm, gpointer data); + +/* For any widgets accessed from multiple functions. */ +static GtkWidget *window; +static GtkWidget *connectitem; +static GtkWidget *connectmenu; +static GtkWidget *no_connections; +static GtkWidget *refresh_vmlist; +static GtkWidget *refresh_vmlist_separator; +static GtkWidget *connection_area; +static GtkWidget *ca_hostname; +static GtkWidget *ca_button; +static GtkWidget *ca_error; +static GtkWidget *login_area; +static GtkWidget *la_username; +static GtkWidget *la_password; +static GtkWidget *la_button; +static GtkWidget *la_error; +static GtkWidget *notebook; +static GtkWidget *statusbar; +static guint statusbar_ctx; +static GdkCursor *busy_cursor; + +/* Menus. */ +enum menuNums { + CONNECT_MENU, + VIEW_MENU, + SEND_KEY_MENU, + WINDOW_MENU, + HELP_MENU, +}; + +struct menuItem { + guint menu; + GtkWidget *label; + const char *ungrabbed_text; + const char *grabbed_text; +}; + +static struct menuItem menuItems[] = { + { CONNECT_MENU, NULL, "_Connect", "Connect" }, + { VIEW_MENU, NULL, "_View", "View" }, + { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" }, + { WINDOW_MENU, NULL, "_Window", "Window" }, + { HELP_MENU, NULL, "_Help", "Help" } +}; + +#define MAX_KEY_COMBO 3 + struct keyComboDef { + guint keys[MAX_KEY_COMBO]; + guint nkeys; + const char *label; + }; + +#define NUM_KEY_COMBOS 17 +static struct keyComboDef keyCombos[] = { + { { GDK_Control_L, GDK_Alt_L, GDK_Delete }, 3, "Ctrl+Alt+Del"}, + { { GDK_Control_L, GDK_Alt_L, GDK_BackSpace }, 3, "Ctrl+Alt+Backspace"}, + { {}, 0, "" }, + { { GDK_Control_L, GDK_Alt_L, GDK_F1 }, 3, "Ctrl+Alt+F1"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F2 }, 3, "Ctrl+Alt+F2"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F3 }, 3, "Ctrl+Alt+F3"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F4 }, 3, "Ctrl+Alt+F4"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F5"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F6"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F7"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F8"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F9"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F10"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F11"}, + { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F12"}, + { {}, 0, "" }, + { { GDK_Print }, 1, "PrintScreen"}, +}; + +/* Window title. */ +static const char *title = "oVirt Viewer"; + +// when running vm +// 47 chars +static const char *title_vm = + "oVirt Viewer: (ctrl+alt to grab/release mouse) "; + +/* Gtk widget styles. Avoid installation hassles by keeping this + * inside the binary. It can still be overridden by the user (who + * will do that?) + */ +static const char *styles = + "style \"ovirt-viewer-yellow-box\"\n" + "{\n" + " bg[NORMAL] = shade (1.5, \"yellow\")\n" + "}\n" + "widget \"*.ovirt-viewer-connection-area\" style \"ovirt-viewer-yellow-box\"\n" + ; + +/* Command-line arguments. */ +static int print_version = 0; + +static const char *help_msg = + "Use '" PACKAGE " --help' to see a list of available command line options"; + +static const GOptionEntry options[] = { + { "port", 'p', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port, + "set port which to connect to server via vnc", NULL }, + { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo, + "set the path of the CA certificate bundle", NULL }, + { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert, + "check the SSL certificate of the server", NULL }, + { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, + "turn on debugging messages", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "turn on verbose messages", NULL }, + { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version, + "display version and exit", NULL }, + { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } +}; + +int +main (int argc, char *argv[]) +{ + GOptionContext *context; + GError *error = NULL; + + /* Initialize GLib threads before anything else. + * + * There is one main thread, which is used for all Gtk interactions + * and to keep the UI responsive, and one WUI thread. The WUI + * thread is used to connect to the WUI, log in, and maintain the list + * of virtual machines. The WUI thread is the only thread allowed + * to use the CURL library. + * + * The main thread sends instructions to the WUI thread (like "connect", + * "disconnect", etc.) using a simple message-passing protocol and + * a GAsyncQueue. + * + * The WUI thread keeps the UI updated by adding idle events which are + * processed in the main thread - see: + * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html + * + * A tunnel thread is also started to locally listen for vnc packets + * and make them proxyable, adding the vm name, before forwarding onto + * the server + * + * Note that under Win32 you must confine all Gtk/Gdk interactions + * to a single thread - see: + * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html + */ + if (!g_thread_supported ()) { + g_thread_init (NULL); +#ifndef WIN32 + gdk_threads_init (); +#endif + } else { + fprintf (stderr, "GLib threads not supported or not working."); + exit (1); + } + + gtk_init (&argc, &argv); + + /* Parse command-line options. */ + context = g_option_context_new ("oVirt viewer"); + g_option_context_add_main_entries (context, options, NULL); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + g_option_context_add_group (context, vnc_display_get_option_group ()); + g_option_context_parse (context, &argc, &argv, &error); + + if (error) { + g_print ("%s\n%s\n", error->message, help_msg); + g_error_free (error); + exit (1); + } + + if (print_version) { + printf ("%s %s\n", PACKAGE, VERSION); + exit (0); + } + + start_wui_thread (); + start_ui (); + + DEBUG ("entering the Gtk main loop"); + + gtk_main (); + + stop_wui_thread (); + stop_tunnel(); + + exit (0); +} + +/* Create the viewer window, menus. */ +static void +start_ui (void) +{ + int i; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *view; + GtkWidget *viewmenu; + GtkWidget *sendkey; + GtkWidget *sendkeymenu; + GtkWidget *sendkeymenuitem; + GtkWidget *wind; + GtkWidget *windmenu; + GtkWidget *help; + GtkWidget *helpmenu; + GtkWidget *about; + GtkWidget *ca_vbox; + GtkWidget *ca_hbox; + GtkWidget *ca_label; + GtkWidget *la_vbox; + GtkWidget *la_hbox; + GtkWidget *la_label; + + DEBUG ("creating viewer windows and menus"); + + /* Parse styles. */ + gtk_rc_parse_string (styles); + + /* Busy cursor, used by main_busy() function. + * XXX This cursor is crap - how can we use the Bluecurve/theme cursor? + */ + busy_cursor = gdk_cursor_new (GDK_WATCH); + + /* Window. */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); + gtk_window_set_resizable (GTK_WINDOW (window), TRUE); + gtk_window_set_title (GTK_WINDOW (window), title); + + g_signal_connect (G_OBJECT (window), "delete_event", + G_CALLBACK (delete_event), NULL); + g_signal_connect (G_OBJECT (window), "destroy", + G_CALLBACK (destroy), NULL); + + /* VBox for layout within the window. */ + vbox = gtk_vbox_new (FALSE, 0); + + /* Menubar. */ + menubar = gtk_menu_bar_new (); + connectitem = menu_item_new (CONNECT_MENU); + connectmenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (connectitem), connectmenu); + + no_connections = gtk_menu_item_new_with_label ("Not connected"); + gtk_menu_append (GTK_MENU (connectmenu), no_connections); + gtk_widget_set_sensitive (no_connections, FALSE); + + /* This is not added to the menu yet, but will be when we are + * connected. + */ + refresh_vmlist = + gtk_menu_item_new_with_label ("Refresh list of virtual machines"); + g_object_ref (refresh_vmlist); + refresh_vmlist_separator = gtk_separator_menu_item_new (); + g_object_ref (refresh_vmlist_separator); + + g_signal_connect (G_OBJECT (refresh_vmlist), "activate", + G_CALLBACK (refresh_menu_vm_list), NULL); + +#if 0 + screenshot = gtk_menu_item_new_with_mnemonic ("_Screenshot"); + gtk_menu_append (GTK_MENU (filemenu), screenshot); + g_signal_connect (screenshot, "activate", + GTK_SIGNAL_FUNC (take_screenshot), NULL); +#endif + + view = menu_item_new (VIEW_MENU); + viewmenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (view), viewmenu); + + sendkey = menu_item_new (SEND_KEY_MENU); + sendkeymenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (sendkey), sendkeymenu); + + for(i = 0; i < NUM_KEY_COMBOS; ++i){ + sendkeymenuitem = gtk_menu_item_new_with_label (keyCombos[i].label); + gtk_menu_append (GTK_MENU (sendkeymenu), sendkeymenuitem); + g_signal_connect (G_OBJECT (sendkeymenuitem), "activate", + G_CALLBACK (send_key_to_vm), &(keyCombos[i])); + } + + wind = menu_item_new (WINDOW_MENU); + windmenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (wind), windmenu); + + help = menu_item_new (HELP_MENU); + helpmenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu); + + about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); + gtk_menu_append(GTK_MENU(helpmenu), about); + g_signal_connect(about, "activate", GTK_SIGNAL_FUNC (help_about), NULL); + + gtk_menu_bar_append (GTK_MENU_BAR (menubar), connectitem); + gtk_menu_bar_append (GTK_MENU_BAR (menubar), view); + gtk_menu_bar_append (GTK_MENU_BAR (menubar), sendkey); + gtk_menu_bar_append (GTK_MENU_BAR (menubar), wind); + gtk_menu_bar_append (GTK_MENU_BAR (menubar), help); + + /* For login dialogs, etc., usually invisible. */ + connection_area = gtk_event_box_new (); + ca_vbox = gtk_vbox_new (FALSE, 0); + ca_label = gtk_label_new ("Give the name of the oVirt management server:"); + ca_hbox = gtk_hbox_new (FALSE, 0); + ca_hostname = gtk_entry_new (); + gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24); + ca_button = gtk_button_new_with_label ("Connect"); + ca_error = gtk_label_new (NULL); + gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label, FALSE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox, TRUE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error, TRUE, FALSE, 4); + gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox); + + gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area"); + + g_signal_connect (G_OBJECT (ca_hostname), "key-release-event", + G_CALLBACK (connect_to_wui_on_enter), NULL); + g_signal_connect (G_OBJECT (ca_button), "clicked", + G_CALLBACK (connect_to_wui), NULL); + + login_area = gtk_event_box_new (); + la_vbox = gtk_vbox_new (FALSE, 0); + la_hbox = gtk_hbox_new (FALSE, 0); + la_label = gtk_label_new ("Username / password:"); + la_username = gtk_entry_new (); + gtk_entry_set_width_chars (GTK_ENTRY (la_username), 12); + la_password = gtk_entry_new (); + gtk_entry_set_width_chars (GTK_ENTRY (la_password), 12); + gtk_entry_set_visibility (GTK_ENTRY (la_password), FALSE); + la_button = gtk_button_new_with_label ("Login"); + la_error = gtk_label_new (NULL); + gtk_container_add (GTK_CONTAINER (la_hbox), la_username); + gtk_container_add (GTK_CONTAINER (la_hbox), la_password); + gtk_container_add (GTK_CONTAINER (la_hbox), la_button); + gtk_box_pack_start (GTK_BOX (la_vbox), la_label, TRUE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (la_vbox), la_hbox, TRUE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (la_vbox), la_error, TRUE, FALSE, 4); + gtk_container_add (GTK_CONTAINER (login_area), la_vbox); + + gtk_widget_set_name (login_area, "ovirt-viewer-login-area"); + + g_signal_connect (G_OBJECT (la_username), "key-release-event", + G_CALLBACK (login_to_wui_on_enter), NULL); + g_signal_connect (G_OBJECT (la_password), "key-release-event", + G_CALLBACK (login_to_wui_on_enter), NULL); + g_signal_connect (G_OBJECT (la_button), "clicked", + G_CALLBACK (login_to_wui), NULL); + + /* Tabbed notebook. */ + notebook = gtk_notebook_new (); + /*gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);*/ + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE); + gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE); + + /* Status bar. */ + statusbar = gtk_statusbar_new (); + statusbar_ctx = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar), + "context"); + gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, ""); + + /* Packing. */ + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_container_add_with_properties (GTK_CONTAINER (vbox), menubar, + "expand", FALSE, NULL); + gtk_container_add_with_properties (GTK_CONTAINER (vbox), connection_area, + "expand", FALSE, "fill", TRUE, NULL); + gtk_container_add_with_properties (GTK_CONTAINER (vbox), login_area, + "expand", FALSE, "fill", TRUE, NULL); + gtk_container_add_with_properties (GTK_CONTAINER (vbox), notebook, + "expand", TRUE, NULL); + gtk_container_add_with_properties (GTK_CONTAINER (vbox), statusbar, + "expand", FALSE, NULL); + + /* Show widgets. */ + gtk_widget_show_all (window); + + if (wui_thread_is_connected ()) + gtk_widget_hide (connection_area); + if (!wui_thread_is_connected () || wui_thread_is_logged_in ()) + gtk_widget_hide (login_area); +} + +static GtkWidget * +menu_item_new (int which_menu) +{ + GtkWidget *widget; + GtkWidget *label; + const char *text; + + text = menuItems[which_menu].ungrabbed_text; + + widget = gtk_menu_item_new (); + label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL); + gtk_label_set_text_with_mnemonic (GTK_LABEL (label), text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + gtk_container_add (GTK_CONTAINER (widget), label); + gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), widget); + gtk_widget_show (label); + + menuItems[which_menu].label = label; + + return widget; +} + +static gboolean +delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + DEBUG ("delete_event"); + return FALSE; +} + +static void +destroy (GtkWidget *widget, gpointer data) +{ + DEBUG ("destroy"); + gtk_main_quit (); +} + +static void +help_about (GtkWidget *menu) +{ + GtkWidget *about; + const char *authors[] = { + "Richard W.M. Jones ", + "Daniel P. Berrange ", + "Mohammed Morsi ", + NULL + }; + + about = gtk_about_dialog_new(); + + gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(about), "oVirt Viewer"); + gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), VERSION); + gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://ovirt.org/"); + gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(about), "oVirt website"); + gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about), authors); + gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), + "This program is free software; you can redistribute it and/or modify\n" \ + "it under the terms of the GNU General Public License as published by\n" \ + "the Free Software Foundation; either version 2 of the License, or\n" \ + "(at your option) any later version.\n" \ + "\n" \ + "This program is distributed in the hope that it will be useful,\n" \ + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ + "GNU General Public License for more details.\n" \ + "\n" \ + "You should have received a copy of the GNU General Public License\n" \ + "along with this program; if not, write to the Free Software\n" \ + "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n"); + + gtk_dialog_run(GTK_DIALOG(about)); + gtk_widget_destroy(about); +} + +static void +refresh_menu_vm_list(GtkWidget *widget, gpointer data) +{ + wui_thread_send_refresh_vm_list(); +} + +static void +connect_to_wui_on_enter (GtkWidget *widget, gpointer data) +{ + // if key released was not 'enter' key + if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; + + connect_to_wui(widget, data); +} + +static void +connect_to_wui (GtkWidget *widget, gpointer data) +{ + char *uri; + int len; + + hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); + if (STREQ (hostname, "")) return; + + /* https:// + hostname + /ovirt + \0 */ + len = 8 + strlen (hostname) + 6 + 1; + uri = g_alloca (len); + snprintf (uri, len, HTTPS "://%s/ovirt", hostname); + + wui_thread_send_connect (uri); + start_tunnel(); +} + +static void +login_to_wui_on_enter (GtkWidget *widget, gpointer data) +{ + // if key released was not 'enter' key + if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return; + + login_to_wui(widget, data); +} + +static void +login_to_wui (GtkWidget *widget, gpointer data) +{ + const char *username, *password; + + username = gtk_entry_get_text (GTK_ENTRY (la_username)); + if (STREQ (username, "")) return; + password = gtk_entry_get_text (GTK_ENTRY (la_password)); + + wui_thread_send_login (username, password); +} + +/* Connect to a virtual machine. This callback is called from the + * connect menu. It searches the notebook of gtk-vnc widgets to see + * if we have already connected to this machine, and if not it + * makes a new connection. + */ +static void +connect_to_vm (GtkWidget *widget, gpointer _vm) +{ + struct vm *vm = (struct vm *) _vm; + int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); + int i, uuidlen, len, fd; + GtkWidget *child; + const char *label; + char *label2; + char new_title[97]; // 47 chars for title + 50 for vm name + + DEBUG ("searching tabs for uuid %s", vm->uuid); + + uuidlen = strlen (vm->uuid); + + /* Search the tabs for this UUID, and if found, switch to it and return. */ + for (i = 0; i < n; ++i) { + child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i); + label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (notebook), child); + len = strlen (label); + if (len >= uuidlen && + STREQ (label + len - uuidlen, vm->uuid)) { + DEBUG ("found on tab %d", i); + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i); + return; + } + } + + DEBUG ("not found, creating new tab"); + + // before we open vnc connection, make sure vm is running + gboolean vm_running = main_vmlist_has_running_vm(vm); + + if(!vm_running){ + main_status_error (g_strdup ("VM not running")); + return; + } + + // FIXME on notebook tab switch, change vm_in_focus + vm_in_focus = vm; + + /* This VM isn't in the notebook already, so create a new console. */ + DEBUG ("connecting to local tunnel on port %i", tunnel_port); + fd = viewer_open_vnc_socket("127.0.0.1", tunnel_port); + if (fd == -1) return; /* We've already given an error. */ + + child = vnc_display_new (); + if (! vnc_display_open_fd (VNC_DISPLAY (child), fd)) { + main_status_error (g_strdup ("internal error in Gtk-VNC widget")); + return; + } + + main_status_error(g_strdup("")); + + for(i = 0; i < 47; ++i) new_title[i] = title_vm[i]; + for(i = 0; i < 50; ++i){ + if(vm->description[i] == '\0') break; + new_title[47 + i] = vm->description[i]; + } + new_title[i < 50 ? i+47 : 96] = '\0'; + gtk_window_set_title (GTK_WINDOW (window), new_title); + + /* + gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-grab", + GTK_SIGNAL_FUNC(viewer_grab), window); + gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-ungrab", + GTK_SIGNAL_FUNC(viewer_ungrab), window); + */ + + gtk_signal_connect(GTK_OBJECT(child), "delete-event", + GTK_SIGNAL_FUNC(viewer_shutdown), child); + + gtk_signal_connect(GTK_OBJECT(child), "vnc-connected", + GTK_SIGNAL_FUNC(viewer_connected), NULL); + gtk_signal_connect(GTK_OBJECT(child), "vnc-initialized", + GTK_SIGNAL_FUNC(viewer_initialized), NULL); + gtk_signal_connect(GTK_OBJECT(child), "vnc-disconnected", + GTK_SIGNAL_FUNC(viewer_disconnected), NULL); + + g_signal_connect(GTK_OBJECT(child), "vnc-auth-credential", + GTK_SIGNAL_FUNC(viewer_credential), NULL); + + /* NB. We have to do this before adding it to the notebook. */ + gtk_widget_show (child); + + /* Choose a tab label, which MUST end with the uuid string, since + * we use the tab label to store uuid. + */ + len = strlen (vm->description) + 1 + strlen (vm->uuid) + 1; + label2 = g_alloca (len); + snprintf (label2, len, "%s %s", vm->description, vm->uuid); + + i = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL); + gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (notebook), child, label2); + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i); + + DEBUG ("finished creating new tab"); +} + +/* Send key to a virtual machine. This callback is called from the + * send key menu. If finds vm in focus, and sends specified key to it + */ +static void +send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef) +{ + GtkWidget* viewer; + struct keyComboDef* key_combo = (struct keyComboDef*) _keyComboDef; + int c = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); + + if(c < 0){ + return; + } + + viewer = gtk_notebook_get_nth_page(GTK_NOTEBOOK (notebook), c); + if(viewer != NULL){ + DEBUG ("sending keys to vm"); + vnc_display_send_keys(VNC_DISPLAY(viewer), + key_combo->keys, + key_combo->nkeys); + } +} + + +/* +static void viewer_grab(GtkWidget *vnc, GtkWidget *window) +{ + int i; + + viewer_set_title(VNC_DISPLAY(vnc), window, TRUE); + + for (i = 0 ; i < LAST_MENU; i++) { + gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].grabbed_text); + } +} + +static void viewer_ungrab(GtkWidget *vnc, GtkWidget *window) +{ + int i; + + viewer_set_title(VNC_DISPLAY(vnc), window, FALSE); + + for (i = 0 ; i < LAST_MENU; i++) { + gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].ungrabbed_text); + } +} +*/ + +static void +viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc) +{ + vnc_display_close (VNC_DISPLAY(vnc)); + + /* Just close the notebook tab for now. XXX */ + gtk_notebook_remove_page (GTK_NOTEBOOK (notebook), + gtk_notebook_page_num (GTK_NOTEBOOK (notebook), + vnc)); +} + +#if 0 +static void +viewer_quit (GtkWidget *src, GtkWidget *vnc) +{ + viewer_shutdown (src, NULL, vnc); +} +#endif + +static void +viewer_connected (GtkWidget *vnc) +{ + DEBUG ("Connected to server"); +} + +static void +viewer_initialized (GtkWidget *vnc, GtkWidget *data) +{ + DEBUG ("Connection initialized"); +} + +static void +viewer_disconnected (GtkWidget *vnc) +{ + DEBUG ("Disconnected from server"); +} + +static void +viewer_credential (GtkWidget *vnc, GValueArray *credList) +{ + GtkWidget *dialog = NULL; + int response; + unsigned int i, prompt = 0; + const char **data; + + DEBUG ("Got credential request for %d credential(s)", + credList->n_values); + + data = g_new0(const char *, credList->n_values); + + for (i = 0 ; i < credList->n_values ; i++) { + GValue *cred = g_value_array_get_nth(credList, i); + switch (g_value_get_enum(cred)) { + case VNC_DISPLAY_CREDENTIAL_USERNAME: + case VNC_DISPLAY_CREDENTIAL_PASSWORD: + prompt++; + break; + case VNC_DISPLAY_CREDENTIAL_CLIENTNAME: + data[i] = "libvirt"; + default: + break; + } + } + + if (prompt) { + GtkWidget **label, **entry, *box, *vbox; + int row; + dialog = gtk_dialog_new_with_buttons("Authentication required", + NULL, + 0, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); + + box = gtk_table_new(credList->n_values, 2, FALSE); + label = g_new(GtkWidget *, prompt); + entry = g_new(GtkWidget *, prompt); + + for (i = 0, row =0 ; i < credList->n_values ; i++) { + GValue *cred = g_value_array_get_nth(credList, i); + switch (g_value_get_enum(cred)) { + case VNC_DISPLAY_CREDENTIAL_USERNAME: + label[row] = gtk_label_new("Username:"); + break; + case VNC_DISPLAY_CREDENTIAL_PASSWORD: + label[row] = gtk_label_new("Password:"); + break; + default: + continue; + } + entry[row] = gtk_entry_new(); + if (g_value_get_enum(cred) == VNC_DISPLAY_CREDENTIAL_PASSWORD) + gtk_entry_set_visibility(GTK_ENTRY(entry[row]), FALSE); + + gtk_table_attach(GTK_TABLE(box), label[i], 0, 1, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3); + gtk_table_attach(GTK_TABLE(box), entry[i], 1, 2, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3); + row++; + } + + vbox = gtk_bin_get_child(GTK_BIN(dialog)); + gtk_container_add(GTK_CONTAINER(vbox), box); + + gtk_widget_show_all(dialog); + response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_hide(GTK_WIDGET(dialog)); + + if (response == GTK_RESPONSE_OK) { + for (i = 0, row = 0 ; i < credList->n_values ; i++) { + GValue *cred = g_value_array_get_nth(credList, i); + switch (g_value_get_enum(cred)) { + case VNC_DISPLAY_CREDENTIAL_USERNAME: + case VNC_DISPLAY_CREDENTIAL_PASSWORD: + data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row])); + break; + } + } + } + } + + for (i = 0 ; i < credList->n_values ; i++) { + GValue *cred = g_value_array_get_nth(credList, i); + if (data[i]) { + if (vnc_display_set_credential(VNC_DISPLAY(vnc), + g_value_get_enum(cred), + data[i])) { + DEBUG("Failed to set credential type %d", + g_value_get_enum(cred)); + vnc_display_close(VNC_DISPLAY(vnc)); + } + } else { + DEBUG("Unsupported credential type %d", + g_value_get_enum(cred)); + vnc_display_close(VNC_DISPLAY(vnc)); + } + } + + g_free(data); + if (dialog) + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + +#if defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS) + +static int +viewer_open_vnc_socket(const char* vnchost, int vncport) +{ + int result, socketfd; + char port[10]; + struct addrinfo* vnc_addr; + + sprintf(port, "%d", vncport); + + result = getaddrinfo(vnchost, port, NULL, &vnc_addr); + if(result != 0 || vnc_addr == NULL) + return -1; + + // just use first found, ignoring rest + socketfd = socket(vnc_addr->ai_family, + vnc_addr->ai_socktype, + vnc_addr->ai_protocol); + + if(connect(socketfd, vnc_addr->ai_addr, vnc_addr->ai_addrlen) <0) + socketfd = -1; + + freeaddrinfo(vnc_addr); + + return socketfd; +} + +#endif /* defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS) */ + +/* Remove all menu items from the Connect menu. */ +static void +remove_menu_item (GtkWidget *menu_item, gpointer data) +{ + gtk_container_remove (GTK_CONTAINER (connectmenu), menu_item); +} + +static void +clear_connectmenu (void) +{ + DEBUG ("clear Connect menu"); + gtk_container_foreach (GTK_CONTAINER (connectmenu), remove_menu_item, NULL); +} + +/* The WUI thread has changed its state to connected. */ +gboolean +main_connected (gpointer data) +{ + DEBUG ("connected"); + ASSERT_IS_MAIN_THREAD (); + + gtk_label_set_text (GTK_LABEL (ca_error), NULL); + + gtk_widget_hide (connection_area); + if (!wui_thread_is_logged_in ()) + gtk_widget_show (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to disconnected. */ +gboolean +main_disconnected (gpointer data) +{ + DEBUG ("disconnected"); + ASSERT_IS_MAIN_THREAD (); + + gtk_widget_show (connection_area); + gtk_widget_hide (login_area); + + clear_connectmenu (); + gtk_menu_append (GTK_MENU (connectmenu), no_connections); + gtk_widget_show_all (connectmenu); + + return FALSE; +} + +/* The WUI thread has changed its state to logged in. */ +gboolean +main_logged_in (gpointer data) +{ + DEBUG ("logged in"); + ASSERT_IS_MAIN_THREAD (); + + gtk_widget_hide (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to logged out. */ +gboolean +main_logged_out (gpointer data) +{ + DEBUG ("logged out"); + ASSERT_IS_MAIN_THREAD (); + + if (wui_thread_is_connected ()) + gtk_widget_show (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to busy. */ +gboolean +main_busy (gpointer data) +{ + GdkWindow *gdk_window; + + DEBUG ("busy"); + ASSERT_IS_MAIN_THREAD (); + + gdk_window = gtk_widget_get_window (window); + if (gdk_window) { + gdk_window_set_cursor (gdk_window, busy_cursor); + gdk_flush (); + } + + return FALSE; +} + +/* The WUI thread has changed its state to idle. */ +gboolean +main_idle (gpointer data) +{ + GdkWindow *gdk_window; + + DEBUG ("idle"); + ASSERT_IS_MAIN_THREAD (); + + gdk_window = gtk_widget_get_window (window); + if (gdk_window) { + gdk_window_set_cursor (gdk_window, NULL); + gdk_flush (); + } + + return FALSE; +} + +/* The WUI thread had a connection error. This function must + * free the string. + */ +gboolean +main_connection_error (gpointer _str) +{ + char *str = (char *) _str; + + DEBUG ("connection error: %s", str); + ASSERT_IS_MAIN_THREAD (); + + gtk_label_set_text (GTK_LABEL (ca_error), str); + g_free (str); + + return FALSE; +} + +/* The WUI thread had a login error. This function must + * free the string. + */ +gboolean +main_login_error (gpointer _str) +{ + char *str = (char *) _str; + + DEBUG ("login error: %s", str); + ASSERT_IS_MAIN_THREAD (); + + gtk_label_set_text (GTK_LABEL (la_error), str); + g_free (str); + + return FALSE; +} + +/* The WUI thread reports a general status error. This function + * must free the string. + */ +gboolean +main_status_error (gpointer _str) +{ + char *str = (char *) _str; + + DEBUG ("status error: %s", str); + ASSERT_IS_MAIN_THREAD (); + + gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar_ctx); + gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, str); + g_free (str); + + return FALSE; +} + +gboolean +main_vmlist_updated (gpointer data) +{ + GSList *new_vmlist; + + DEBUG ("vmlist updated"); + ASSERT_IS_MAIN_THREAD (); + + /* Get the new vmlist. */ + if (wui_thread_get_vmlist (&new_vmlist)) { + /* Free the previous vmlist. This invalidates all the vm pointers + * contained in the Connect menu callbacks, but we're going to + * delete those callbacks and create news ones in a moment anyway ... + */ + free_vmlist (vmlist); + + vmlist = new_vmlist; + + clear_connectmenu (); + + gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist); + + if (vmlist != NULL) { + gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist_separator); + g_slist_foreach (vmlist, add_vm_to_connectmenu, NULL); + } + + /* Grrrr Gtk is stupid. */ + gtk_widget_show_all (connectmenu); + } + + return FALSE; +} + +static void +add_vm_to_connectmenu (gpointer _vm, gpointer data) +{ + struct vm *vm = (struct vm *) _vm; + GtkWidget *item; + + // TODO only present running vms ? + // if(vm->state == "running") + + DEBUG ("adding %s to Connect menu", vm->description); + + item = gtk_menu_item_new_with_label (vm->description); + gtk_menu_append (GTK_MENU (connectmenu), item); + + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (connect_to_vm), vm); +} diff --git a/src/tunnel.c b/src/tunnel.c new file mode 100644 index 0000000..17b512c --- /dev/null +++ b/src/tunnel.c @@ -0,0 +1,327 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Mohammed Morsi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* ovirt-viewer starts listening on network port to + * encapsulate vnc packets including the vm's name + * so as to be able to be proxied. + * + * This operation takes place in another thread + * which can be started/stopped by calling + * start_tunnel / stop_tunnel. + * + * An additional connection thread is created and maintained + * internally for each vm / vnc connection open in ovirt-viewer + * establishing a connection w/ the ovirt server. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "internal.h" + +/* constants */ + +// max length of a vm name +const int VM_NAME_MAX_LEN = 250; + +// max length of vnc data +const int VNC_DATA_MAX_LEN = 800000; + +/* Private thread functions */ +static gpointer tunnel_thread(gpointer data); +static gpointer client_server_thread(gpointer data); +static gpointer server_client_thread(gpointer data); + +/* Other private functions */ +static void close_socket(gpointer _socket, gpointer data); +static void wait_for_thread(gpointer _thread, gpointer data); + +/* tunnel and main threads */ +static GThread *tunnel_gthread = NULL; +static GThread *main_gthread = NULL; + +/* list of communication threads */ +static GSList *communication_threads = NULL; + +/* list of sockets */ +static GSList *sockets = NULL; + +/* thread termination flag */ +static gboolean run_tunnel = FALSE; + +/* internal.h shared constructs */ +int tunnel_port; + +///////////////// + +/** public implementations **/ + +/* start tunnel thread */ +void +start_tunnel(void) +{ + GError *error = NULL; + + DEBUG ("starting the tunnel thread"); + + assert (tunnel_gthread == NULL); + + run_tunnel = TRUE; + + main_gthread = g_thread_self (); + + tunnel_gthread = g_thread_create (tunnel_thread, NULL, TRUE, &error); + if (error) { + g_print ("%s\n", error->message); + g_error_free (error); + exit (1); + } +}; + +/* stop tunnel thread */ +void +stop_tunnel(void) +{ + if(!run_tunnel) + return; + + DEBUG ("stopping the tunnel thread"); + + assert (tunnel_gthread != NULL); + ASSERT_IS_MAIN_THREAD (); + + run_tunnel = FALSE; + + g_slist_foreach(sockets, close_socket, NULL); + + (void) g_thread_join (tunnel_gthread); + tunnel_gthread = NULL; +}; + +///////////////// + +/** private implementations **/ + +/* the tunnel thread */ +static gpointer +tunnel_thread (gpointer _data) +{ + struct hostent *dns_serv; + + //char vm_data[VM_NAME_MAX_LEN]; + int local_server_socketfd, ovirt_server_socket, client_socketfd; + unsigned int local_server_len, client_len, ovirt_server_len; + + struct sockaddr_in local_server_address; + struct sockaddr_in ovirt_server_address; + struct sockaddr_in client_address; + + struct sockaddr_in local_server_address_lookup; + unsigned int local_server_address_lookup_len = sizeof(local_server_address_lookup); + + GThread *client_server_gthread = NULL; + GThread *server_client_gthread = NULL; + + int sockets_param[2]; + int * c_socket; + + DEBUG ("tunnel thread starting up"); + + // ovirt server address + dns_serv = gethostbyname(hostname); + if(dns_serv == NULL){ + DEBUG("ovirt server lookup failed"); + return NULL; + } + ovirt_server_address.sin_family = PF_INET; + ovirt_server_address.sin_addr.s_addr = ((struct in_addr*)(dns_serv->h_addr))->s_addr; //inet_addr(hostname); + ovirt_server_address.sin_port = htons(ovirt_server_vnc_port); + ovirt_server_len = sizeof(ovirt_server_address); + + // create local net socket + local_server_socketfd = socket(PF_INET, SOCK_STREAM, 0); + c_socket = malloc(sizeof(int)); *c_socket = local_server_socketfd; + sockets = g_slist_prepend(sockets, c_socket); + + // local server address + local_server_address.sin_family = PF_INET; + local_server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); + local_server_address.sin_port = 0; + local_server_len = sizeof(local_server_address); + + // increment ports until one is available + if(bind(local_server_socketfd, (struct sockaddr*)&local_server_address, local_server_len) < 0){ + DEBUG("tunnel bind failed"); + return NULL; + } + + getsockname(local_server_socketfd, + (struct sockaddr*) &local_server_address_lookup, + &local_server_address_lookup_len); + tunnel_port = (int)ntohs(local_server_address_lookup.sin_port); + DEBUG ("tunnel bound to local port %i", tunnel_port); + + // increase client buffer size? + listen(local_server_socketfd, 5); + + while(run_tunnel) { + // accept a client connection + DEBUG("tunnel accepting"); + client_len = sizeof(client_address); + client_socketfd = accept(local_server_socketfd, (struct sockaddr*)&client_address, &client_len); + if(client_socketfd < 0){ + DEBUG("tunnel accept failed"); + break; + } + // TODO check accept return value for err + c_socket = malloc(sizeof(int)); *c_socket = client_socketfd; + sockets = g_slist_prepend(sockets, c_socket); + + DEBUG ("client connected to tunnel"); + + // establish connection w/ ovirt server + ovirt_server_socket = socket(PF_INET, SOCK_STREAM, 0); + c_socket = malloc(sizeof(int)); *c_socket = ovirt_server_socket; + sockets = g_slist_prepend(sockets, c_socket); + DEBUG ("connecting to ovirt server %s on %i", hostname, ovirt_server_vnc_port); + if(connect(ovirt_server_socket, (struct sockaddr*)&ovirt_server_address, ovirt_server_len) < 0){ + DEBUG ("could not connect to ovirt server"); + break; + //return NULL; + } + DEBUG ("connected to ovirt server"); + + sockets_param[0] = ovirt_server_socket; + sockets_param[1] = client_socketfd; + + // launch thread for client -> server traffic + client_server_gthread = g_thread_create (client_server_thread, + &sockets_param, TRUE, NULL); + + // launch thread for server -> client traffic + server_client_gthread = g_thread_create (server_client_thread, + &sockets_param, TRUE, NULL); + + communication_threads = g_slist_prepend(communication_threads, client_server_gthread); + communication_threads = g_slist_prepend(communication_threads, server_client_gthread); + + // send target vm for this session + //strcpy(vm_data, vm_in_focus->description); + DEBUG ("sending vm %s", vm_in_focus->description); + write(ovirt_server_socket, vm_in_focus->description, strlen(vm_in_focus->description)); + + } + + DEBUG("terminating tunnel thread"); + + // wait for connection threads to finish + g_slist_foreach(communication_threads, wait_for_thread, NULL); + + DEBUG ("tunnel thread completed"); + return NULL; +}; + +/* the tunnel thread */ +static gpointer +client_server_thread (gpointer _data){ + int nbytes; + char vnc_data[VNC_DATA_MAX_LEN]; + + int ovirt_server_socket = ((int*)_data)[0], + client_socket = ((int*)_data)[1]; + + DEBUG ("client/server thread starting up"); + + while(run_tunnel){ + VERBOSE( "accepting client data"); + + // grab vnc data + nbytes = read(client_socket, vnc_data, VNC_DATA_MAX_LEN); + if(nbytes <= 0){ + DEBUG ( "error reading data from client" ); + break; + } + VERBOSE ("read %i bytes from client", nbytes); + + // send network_data onto server + nbytes = write(ovirt_server_socket, vnc_data, nbytes); + if(nbytes <= 0){ + DEBUG ( "error writing data to server" ); + break; + } + VERBOSE ("wrote %i bytes to server", nbytes); + } + + DEBUG ("client/server thread completed"); + return NULL; +}; + +/* the server thread */ +static gpointer +server_client_thread (gpointer _data){ + char vnc_data[VNC_DATA_MAX_LEN]; + + int ovirt_server_socket = ((int*)_data)[0], + client_socket = ((int*)_data)[1]; + + int nbytes; + + DEBUG ("server/client thread starting up"); + + while(run_tunnel){ + // grab vnc data + nbytes = read(ovirt_server_socket, vnc_data, VNC_DATA_MAX_LEN); + if(nbytes <= 0){ + DEBUG ( "error reading data from server" ); + break; + } + VERBOSE ("read %i bytes from server", nbytes); + + // send network_data onto client + nbytes = write(client_socket, vnc_data, nbytes); + if(nbytes <= 0){ + DEBUG ( "error writing data to client" ); + break; + } + VERBOSE ("wrote %i bytes to client", nbytes); + } + + DEBUG ("server/client thread completed"); + return NULL; +}; + +static void close_socket(gpointer _socket, gpointer data){ + shutdown(*(int*) _socket, 2); + close(*(int*) _socket); + free((int*) _socket); +}; + +static void wait_for_thread(gpointer _thread, gpointer data){ + g_thread_join((GThread*)_thread); +}; diff --git a/src/wui_thread.c b/src/wui_thread.c new file mode 100644 index 0000000..8bfa8ca --- /dev/null +++ b/src/wui_thread.c @@ -0,0 +1,1106 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Richard W.M. Jones + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* For an explanation of the threading model, please main(). */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "internal.h" + +/* Private functions. */ +static gpointer wui_thread (gpointer data); +static void wui_thread_send_quit (void); +static void do_curl_init (void); +static void write_fn_start_capture (void); +static char *write_fn_finish_capture (void); +static void write_fn_discard_capture_buffer (void); +static gboolean do_connect (void); +static gboolean do_login (void); +static gboolean refresh_vm_list (void); +static void parse_vmlist_from_xml (const char *xml); +static struct vm *parse_vm_from_xml (xmlNodePtr node); + +/* Messages (main thread -> WUI thread only). + * + * These are passed by reference. They are allocated by the sender + * (ie. the main thread) and freed by the receiver (ie. the WUI thread). + */ +enum message_type { + QUIT, /* Tell the WUI thread to quit cleanly. */ + CONNECT, /* Tell to connect (just fetch the URI). */ + DISCONNECT, /* Tell to disconnect, forget state. */ + LOGIN, /* Tell to login, and pass credentials. */ + REFRESH_VM_LIST, /* Tell to refresh the VM list right away. */ +}; + +struct message { + enum message_type type; + char *str1; + char *str2; +}; + +/* Start the WUI thread. See main() for explanation of the threading model. */ +static GThread *wui_gthread = NULL; +static GThread *main_gthread = NULL; +static GAsyncQueue *wui_thread_queue = NULL; + +void +start_wui_thread (void) +{ + GError *error = NULL; + + DEBUG ("starting the WUI thread"); + + assert (wui_gthread == NULL); + + main_gthread = g_thread_self (); + + /* Create the message queue for main -> WUI thread communications. */ + wui_thread_queue = g_async_queue_new (); + + wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error); + if (error) { + g_print ("%s\n", error->message); + g_error_free (error); + exit (1); + } +} + +void +stop_wui_thread (void) +{ + DEBUG ("stopping the WUI thread"); + + assert (wui_gthread != NULL); + ASSERT_IS_MAIN_THREAD (); + + /* Send a quit message then wait for the WUI thread to join. + * + * This "nice" shutdown could cause the UI to hang for an + * indefinite period (eg. if the WUI thread is engaged in some + * long connection or download from the remote server). But + * I want to keep it this way for the moment so that we can + * diagnose problems with the WUI thread. + * + * (This could be solved with some sort of interruptible + * join, but glib doesn't support that AFAICT). + */ + wui_thread_send_quit (); + (void) g_thread_join (wui_gthread); + g_async_queue_unref (wui_thread_queue); + wui_gthread = NULL; +} + +void +assert_is_wui_thread (const char *filename, int lineno) +{ + if (g_thread_self () != wui_gthread) { + fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno); + abort (); + } +} + +void +assert_is_main_thread (const char *filename, int lineno) +{ + if (g_thread_self () != main_gthread) { + fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno); + abort (); + } +} + +/* Send the quit message to the WUI thread. */ +static void +wui_thread_send_quit (void) +{ + struct message *msg; + + ASSERT_IS_MAIN_THREAD (); + + msg = g_new (struct message, 1); + msg->type = QUIT; + g_async_queue_push (wui_thread_queue, msg); +} + +/* Send the connect message to the WUI thread. */ +void +wui_thread_send_connect (const char *uri) +{ + struct message *msg; + + ASSERT_IS_MAIN_THREAD (); + + msg = g_new (struct message, 1); + msg->type = CONNECT; + msg->str1 = g_strdup (uri); + g_async_queue_push (wui_thread_queue, msg); +} + +/* Send the disconnect message to the WUI thread. */ +void +wui_thread_send_disconnect (void) +{ + struct message *msg; + + ASSERT_IS_MAIN_THREAD (); + + msg = g_new (struct message, 1); + msg->type = DISCONNECT; + g_async_queue_push (wui_thread_queue, msg); +} + +/* Send the login message to the WUI thread. */ +void +wui_thread_send_login (const char *username, const char *password) +{ + struct message *msg; + + ASSERT_IS_MAIN_THREAD (); + + msg = g_new (struct message, 1); + msg->type = LOGIN; + msg->str1 = g_strdup (username); + msg->str2 = g_strdup (password); + g_async_queue_push (wui_thread_queue, msg); +} + +/* Send the refresh VM list message to the WUI thread. */ +void +wui_thread_send_refresh_vm_list (void) +{ + struct message *msg; + + ASSERT_IS_MAIN_THREAD (); + + msg = g_new (struct message, 1); + msg->type = REFRESH_VM_LIST; + g_async_queue_push (wui_thread_queue, msg); +} + +/* The current state. + * + * For safety, the main thread must lock this before reading, and the + * WUI thread must lock this before writing. However the WUI thread + * does not need to lock before reading, because no other thread + * can modify it. + */ +static gboolean connected = FALSE; +static gboolean logged_in = FALSE; +static gboolean busy = FALSE; +static GStaticMutex state_mutex; + +static void set_connected (gboolean); +static void set_logged_in (gboolean); +static void set_busy (gboolean); + +/* The private state of the WUI thread. */ +static int secs_between_refresh = 60; +static CURL *curl = NULL; +static char curl_error_buffer[CURL_ERROR_SIZE]; +static char *uri = NULL; +static char *username = NULL; +static char *password = NULL; + +static gboolean process_message (struct message *); + +/* The WUI thread. See main() above for explanation of + * the threading model. + */ +static gpointer +wui_thread (gpointer _queue) +{ + GAsyncQueue *queue = (GAsyncQueue *) _queue; + gboolean quit = FALSE; + GTimeVal tv; + gpointer _msg; + struct message *msg; + + DEBUG ("WUI thread starting up"); + + /* This checks wui_gthread global which is actually set in the + * main thread. Of course, it might not be set if the WUI thread + * runs first. Hence we sleep for the main thread to run. (XXX) + */ + g_usleep (100000); + ASSERT_IS_WUI_THREAD (); + + g_async_queue_ref (queue); + + /* In the thread's loop we check for new instructions from the main + * thread and carry them out. Also, if we are connected and logged + * in then we periodically recheck the list of VMs. + */ + while (!quit) { + set_busy (FALSE); + + if (logged_in) { + g_get_current_time (&tv); + g_time_val_add (&tv, secs_between_refresh * 1000000); + _msg = g_async_queue_timed_pop (queue, &tv); + } else + _msg = g_async_queue_pop (queue); + + set_busy (TRUE); + + msg = (struct message *) _msg; + + if (msg) { + DEBUG ("received message with msg->type = %d", msg->type); + quit = process_message (msg); + /* Don't free any strings in the message - we've saved them. */ + g_free (msg); + } else { + /* No message, must have got a timeout instead, which means + * we are logged in and we should refresh the list of VMs. + * Note it's not an error if we temporarily lose contact + * with the WUI. + */ + refresh_vm_list (); + } + } + + DEBUG ("WUI thread shutting down cleanly"); + + g_async_queue_unref (queue); + g_thread_exit (NULL); + return NULL; /* not reached? */ +} + +/* The WUI thread calls this to safely update the state variables. + * This also updates elements in the UI by setting idle callbacks + * which are executed in the context of the main thread. + */ +static void +set_connected (gboolean new_connected) +{ + ASSERT_IS_WUI_THREAD (); + + g_static_mutex_lock (&state_mutex); + connected = new_connected; + g_static_mutex_unlock (&state_mutex); + + if (connected) + g_idle_add (main_connected, NULL); + else + g_idle_add (main_disconnected, NULL); +} + +static void +set_logged_in (gboolean new_logged_in) +{ + ASSERT_IS_WUI_THREAD (); + + g_static_mutex_lock (&state_mutex); + logged_in = new_logged_in; + g_static_mutex_unlock (&state_mutex); + + if (logged_in) + g_idle_add (main_logged_in, NULL); + else + g_idle_add (main_logged_out, NULL); +} + +static void +set_busy (gboolean new_busy) +{ + ASSERT_IS_WUI_THREAD (); + + g_static_mutex_lock (&state_mutex); + busy = new_busy; + g_static_mutex_unlock (&state_mutex); + + if (busy) + g_idle_add (main_busy, NULL); + else + g_idle_add (main_idle, NULL); +} + +/* The main thread should call these functions to get the WUI thread's + * current state. + */ +gboolean +wui_thread_is_connected (void) +{ + gboolean ret; + + ASSERT_IS_MAIN_THREAD (); + + g_static_mutex_lock (&state_mutex); + ret = connected; + g_static_mutex_unlock (&state_mutex); + return ret; +} + +gboolean +wui_thread_is_logged_in (void) +{ + gboolean ret; + + ASSERT_IS_MAIN_THREAD (); + + g_static_mutex_lock (&state_mutex); + ret = logged_in; + g_static_mutex_unlock (&state_mutex); + return ret; +} + +gboolean +wui_thread_is_busy (void) +{ + gboolean ret; + + ASSERT_IS_MAIN_THREAD (); + + g_static_mutex_lock (&state_mutex); + ret = busy; + g_static_mutex_unlock (&state_mutex); + return ret; +} + +/* Process a message from the main thread. */ +static gboolean +process_message (struct message *msg) +{ + ASSERT_IS_WUI_THREAD (); + + switch (msg->type) { + case QUIT: + write_fn_discard_capture_buffer (); + if (curl) curl_easy_cleanup (curl); + if (uri) g_free (uri); + if (username) g_free (username); + if (password) g_free (password); + set_connected (FALSE); + set_logged_in (FALSE); + return 1; + + case CONNECT: + write_fn_discard_capture_buffer (); + if (curl) curl_easy_cleanup (curl); + do_curl_init (); + if (uri) g_free (uri); + uri = msg->str1; + + if (do_connect ()) { + set_connected (TRUE); + } else { + set_connected (FALSE); + set_logged_in (FALSE); + } + break; + + case DISCONNECT: + /* This just forgets the state. REST is connectionless. */ + write_fn_discard_capture_buffer (); + if (curl) curl_easy_cleanup (curl); + curl = NULL; + if (uri) g_free (uri); + uri = NULL; + if (username) g_free (username); + username = NULL; + if (password) g_free (password); + password = NULL; + set_connected (FALSE); + set_logged_in (FALSE); + break; + + case LOGIN: + if (username) g_free (username); + username = msg->str1; + if (password) g_free (password); + password = msg->str2; + + /* If we're not connected, this message just updates the + * username and password. Otherwise if we are connected, + * try to login and grab the initial list of VMs. + */ + if (connected) { + if (do_login ()) { + set_logged_in (TRUE); + if (refresh_vm_list ()) + secs_between_refresh = 60; + } else { + set_logged_in (FALSE); + } + } + break; + + case REFRESH_VM_LIST: + if (connected && logged_in) { + refresh_vm_list (); + secs_between_refresh = 60; + } + break; + + default: + DEBUG ("unknown message type %d", msg->type); + abort (); + } + + return 0; +} + +/* Macro for easy handling of CURL errors. */ +#define CURL_CHECK_ERROR(fn, args) \ + ({ \ + CURLcode __r = fn args; \ + if (__r != CURLE_OK) { \ + fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r)); \ + } \ + __r; \ + }) + +/* Libcurl has a really crufty method for handling HTTP headers and + * data. We set these functions as callbacks, because the default + * callback functions print the data out to stderr. In order to + * capture the data, we have to keep state here. + */ + +static char *write_fn_buffer = NULL; +static ssize_t write_fn_len = -1; + +static size_t +write_fn (void *ptr, size_t size, size_t nmemb, void *stream) +{ + int bytes = size * nmemb; + int old_start; + + ASSERT_IS_WUI_THREAD (); + + if (write_fn_len >= 0) { /* We're capturing. */ + old_start = write_fn_len; + write_fn_len += bytes; + write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len); + memcpy (write_fn_buffer + old_start, ptr, bytes); + } + + return bytes; +} + +/* Start capturing HTTP response data. */ +static void +write_fn_start_capture (void) +{ + ASSERT_IS_WUI_THREAD (); + + write_fn_discard_capture_buffer (); + write_fn_buffer = NULL; + write_fn_len = 0; +} + +/* Finish capture and return the capture buffer. Caller must free. */ +static char * +write_fn_finish_capture (void) +{ + char *ret; + + ASSERT_IS_WUI_THREAD (); + + /* Make sure the buffer is NUL-terminated before returning it. */ + write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len+1); + write_fn_buffer[write_fn_len] = '\0'; + ret = write_fn_buffer; + + write_fn_buffer = NULL; + write_fn_len = -1; + return ret; +} + +/* Stop capturing and discard the capture buffer, if any. */ +static void +write_fn_discard_capture_buffer (void) +{ + ASSERT_IS_WUI_THREAD (); + + g_free (write_fn_buffer); + write_fn_buffer = NULL; + write_fn_len = -1; +} + +static size_t +header_fn (void *ptr, size_t size, size_t nmemb, void *stream) +{ + int bytes = size * nmemb; + + ASSERT_IS_WUI_THREAD (); + + return bytes; +} + +/* Called from the message loop to initialize the CURL handle. */ +static void +do_curl_init (void) +{ + DEBUG ("initializing libcurl"); + + ASSERT_IS_WUI_THREAD (); + + curl = curl_easy_init (); + if (!curl) { /* This is probably quite bad, so abort. */ + DEBUG ("curl_easy_init failed"); + abort (); + } + + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_CAINFO, cainfo)); + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0)); + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0)); + + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_WRITEFUNCTION, write_fn)); + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_HEADERFUNCTION, header_fn)); + + /* This enables error messages in curl_easy_perform. */ + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_ERRORBUFFER, curl_error_buffer)); + + /* This enables cookie handling, using an internal cookiejar. */ + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_COOKIEFILE, "")); +} + +/* Called from the message loop. Try to connect to the current URI. + * Returns true on success. + */ +static gboolean +do_connect (void) +{ + long code = 0; + CURLcode r; + char *error_str; + + DEBUG ("connecting to uri %s", uri); + ASSERT_IS_WUI_THREAD (); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri)); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_connection_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + if (code != 200 && code != 302 && code != 401) { + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_connection_error, error_str); + return FALSE; + } + + return TRUE; +} + +/* Called from the message loop. Try to login to 'URI/login' with the + * current username and password. Returns true on success. + */ +static gboolean +do_login (void) +{ + int len; + char *login_uri; + char *userpwd; + char *error_str; + CURLcode r; + long code = 0; + + DEBUG ("logging in with username %s, password *****", username); + ASSERT_IS_WUI_THREAD (); + + /* Generate the login URI from the base URI. */ + len = strlen (uri) + 6 + 1; + login_uri = g_alloca (len); + snprintf (login_uri, len, "%s/login", uri); + + DEBUG ("login URI %s", login_uri); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri)); + + /* Construct the username:password for CURL. */ + len = strlen (username) + strlen (password) + 2; + userpwd = g_alloca (len); + snprintf (userpwd, len, "%s:%s", username, password); + + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd)); + + /* HTTP Basic authentication is OK since we should be sending + * this only over HTTPS. + */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); + + /* Follow redirects. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1)); + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10)); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_login_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + switch (code) + { + case 200: + DEBUG ("login was successful"); + return TRUE; + + case 401: + error_str = g_strdup ("server rejected the username or password"); + g_idle_add (main_login_error, error_str); + return FALSE; + + default: + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_login_error, error_str); + return FALSE; + } +} + +/* Called from the message loop. Refresh the list of VMs. */ +static gboolean +refresh_vm_list (void) +{ + int len; + char *vms_uri; + char *error_str; + CURLcode r; + long code = 0; + char *xml; + + DEBUG ("refreshing list of VMs"); + ASSERT_IS_WUI_THREAD (); + + /* Generate the vms URI from the base URI. */ + len = strlen (uri) + 4 + 1; + vms_uri = g_alloca (len); + snprintf (vms_uri, len, "%s/vms", uri); + + DEBUG ("vms URI %s", vms_uri); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri)); + + /* We want to capture the output, so tell our write function + * to place the output into a buffer. + */ + write_fn_start_capture (); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_login_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + switch (code) + { + case 200: break; + + /* Hmm - even though we previously logged in, the server is + * rejecting our attempts now with an authorization error. + * We move to the logged out state. + */ + case 401: + set_logged_in (FALSE); + error_str = g_strdup ("server rejected the username or password"); + g_idle_add (main_login_error, error_str); + return FALSE; + + default: + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_status_error, error_str); + return FALSE; + } + + /* If we got here then we appear to have a correct + * XML document describing the list of VMs. + */ + secs_between_refresh <<= 1; + + xml = write_fn_finish_capture (); + + parse_vmlist_from_xml (xml); + g_free (xml); + + return TRUE; +} + +/* Functions to deal with the list of VMs, parsing it from the XML, etc. + * + * A vmlist is a GSList (glib singly-linked list) of vm structures. + * The caller must call free_vmlist to free up this list correctly. + */ + +static void +free_vm (gpointer _vm, gpointer data) +{ + struct vm *vm = (struct vm *) _vm; + + g_free (vm->description); + g_free (vm->state); + g_free (vm->uuid); + g_free (vm->mac_addr); + g_free (vm); +} + +void +free_vmlist (GSList *vmlist) +{ + g_slist_foreach (vmlist, free_vm, NULL); + g_slist_free (vmlist); +} + +static struct vm * +copy_vm (struct vm *vm) +{ + struct vm *vm2; + + vm2 = g_memdup (vm, sizeof (*vm)); + vm2->description = g_strdup (vm->description); + vm2->uuid = g_strdup (vm->uuid); + vm2->state = vm->state ? g_strdup (vm->state) : NULL; + vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL; + return vm2; +} + +static int +compare_vm (gconstpointer _vm1, gconstpointer _vm2) +{ + const struct vm *vm1 = (const struct vm *) _vm1; + const struct vm *vm2 = (const struct vm *) _vm2; + + return strcmp (vm1->description, vm2->description); +} + +static GSList *vmlist = NULL; +static gboolean vmlist_valid = FALSE; +static GStaticMutex vmlist_mutex; + +/* Called from the main thread to find out if we have a valid vmlist. */ +gboolean +wui_thread_has_valid_vmlist (void) +{ + gboolean ret; + + ASSERT_IS_MAIN_THREAD (); + + g_static_mutex_lock (&vmlist_mutex); + ret = vmlist_valid; + g_static_mutex_unlock (&vmlist_mutex); + return ret; +} + +/* Called from the main thread to find return the current vmlist. This + * actually returns a deep copy of it that the caller must free. + */ +static void +duplicate_and_insert_vm (gpointer _vm, gpointer _ret) +{ + struct vm *vm = (struct vm *) _vm; + GSList **ret = (GSList **) _ret; + + *ret = g_slist_prepend (*ret, copy_vm (vm)); +} + +gboolean +wui_thread_get_vmlist (GSList **ret) +{ + gboolean r; + + ASSERT_IS_MAIN_THREAD (); + + r = FALSE; + *ret = NULL; + + g_static_mutex_lock (&vmlist_mutex); + if (!vmlist_valid) goto done; + + g_slist_foreach (vmlist, duplicate_and_insert_vm, ret); + *ret = g_slist_sort (*ret, compare_vm); + + r = TRUE; + done: + g_static_mutex_unlock (&vmlist_mutex); + return r; +} + +/* Called from the message loop in the WUI thread, with an XML document + * which we turn into a list of VM structures, and update the vmlist + * if we can. + */ +static void +parse_vmlist_from_xml (const char *xml) +{ + xmlDocPtr doc = NULL; + xmlNodePtr root; + xmlNodePtr node; + char *error_str; + GSList *new_vmlist = NULL; + struct vm *vm; + int len; + + /*DEBUG ("XML =\n%s", xml);*/ + ASSERT_IS_WUI_THREAD (); + + /* We don't really expect that we won't be able to parse the XML ... */ + len = strlen (xml); + doc = xmlReadMemory (xml, len, NULL, NULL, 0); + + if (!doc) { + DEBUG ("error parsing XML document, xml =\n%s", xml); + error_str = g_strdup ("error parsing XML document from remote server"); + g_idle_add (main_status_error, error_str); + goto done; + } + + root = xmlDocGetRootElement (doc); + if (!root) { + DEBUG ("XML document was empty"); + error_str = g_strdup ("XML document was empty"); + g_idle_add (main_status_error, error_str); + goto done; + } + + /* We expect the root element will be either "nil-classes" + * or "vms", with the former indicating an empty list of VMs. + */ + if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) { + g_static_mutex_lock (&vmlist_mutex); + vmlist_valid = TRUE; + free_vmlist (vmlist); + vmlist = NULL; + g_static_mutex_unlock (&vmlist_mutex); + + /* Signal to the main UI thread that the list has been updated. */ + g_idle_add (main_vmlist_updated, NULL); + + goto done; + } + + if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) { + DEBUG ("unexpected root node in XML document, xml =\n%s", xml); + error_str = g_strdup ("unexpected root node in XML document"); + g_idle_add (main_status_error, error_str); + goto done; + } + + /* The document is with a list of elements which + * we process in turn. + */ + for (node = root->xmlChildrenNode; node != NULL; node = node->next) { + if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) { + vm = parse_vm_from_xml (node); + if (!vm) { + error_str = g_strdup ("could not parse element"); + g_idle_add (main_status_error, error_str); + + free_vmlist (new_vmlist); + goto done; + } + new_vmlist = g_slist_prepend (new_vmlist, vm); + } + } + + /* Successfully parsed all the nodes, so swap the old and new + * vmlists. + */ + g_static_mutex_lock (&vmlist_mutex); + vmlist_valid = TRUE; + free_vmlist (vmlist); + vmlist = new_vmlist; + g_static_mutex_unlock (&vmlist_mutex); + + /* Signal that the vmlist has been updated. */ + g_idle_add (main_vmlist_updated, NULL); + + done: + /* Free up XML resources used before returning. */ + if (doc) xmlFreeDoc (doc); +} + +static struct vm * +parse_vm_from_xml (xmlNodePtr node) +{ + xmlNodePtr p; + struct vm vm, *ret; + xmlChar *str; + + memset (&vm, 0, sizeof vm); + vm.hostid = -1; + vm.id = -1; + vm.vnc_port = -1; + vm.forward_vnc_port = -1; + vm.mem_allocated = -1; + vm.mem_used = -1; + vm.vcpus_allocated = -1; + vm.vcpus_used = -1; + + for (p = node->xmlChildrenNode; p != NULL; p = p->next) { + if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.description = g_strdup ((char *) str); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.hostid = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.id = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.mem_allocated = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.mem_used = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.vcpus_allocated = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.vcpus_used = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.state = g_strdup ((char *) str); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.uuid = g_strdup ((char *) str); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.vnc_port = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "forward-vnc-port") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.forward_vnc_port = strtol ((char *) str, NULL, 10); + xmlFree (str); + } + } + else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) { + str = xmlNodeGetContent (p); + if (str != NULL) { + vm.mac_addr = g_strdup ((char *) str); + xmlFree (str); + } + } + } + + /* Make sure we've got the required fields. */ + ret = NULL; + if (vm.description == NULL) + DEBUG ("required field \"description\" missing from structure"); + else if (vm.hostid == -1) + DEBUG ("required field \"description\" missing from structure"); + else if (vm.id == -1) + DEBUG ("required field \"description\" missing from structure"); + else if (vm.vnc_port == -1) + DEBUG ("required field \"vnc-port\" missing from structure"); + else if (vm.forward_vnc_port == -1) + DEBUG ("required field \"forward-vnc-port\" missing from structure"); + else if (vm.uuid == NULL) + DEBUG ("required field \"uuid\" missing from structure"); + else + ret = g_memdup (&vm, sizeof vm); + + return ret; +} + +gboolean +main_vmlist_has_running_vm(struct vm* _vm) +{ + // TODO ? get list and wait to be retreived + // wui_thread_send_refresh_vm_list(); + + // find vm in list + GSList* res = g_slist_find_custom (vmlist, _vm, compare_vm); + + // return true if running + if(res != NULL) return STREQ (((struct vm*) res->data)->state, "running"); + + return FALSE; +} diff --git a/tunnel.c b/tunnel.c deleted file mode 100644 index 17b512c..0000000 --- a/tunnel.c +++ /dev/null @@ -1,327 +0,0 @@ -/* ovirt viewer console application - * Copyright (C) 2008 Red Hat Inc. - * Written by Mohammed Morsi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -/* ovirt-viewer starts listening on network port to - * encapsulate vnc packets including the vm's name - * so as to be able to be proxied. - * - * This operation takes place in another thread - * which can be started/stopped by calling - * start_tunnel / stop_tunnel. - * - * An additional connection thread is created and maintained - * internally for each vm / vnc connection open in ovirt-viewer - * establishing a connection w/ the ovirt server. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "internal.h" - -/* constants */ - -// max length of a vm name -const int VM_NAME_MAX_LEN = 250; - -// max length of vnc data -const int VNC_DATA_MAX_LEN = 800000; - -/* Private thread functions */ -static gpointer tunnel_thread(gpointer data); -static gpointer client_server_thread(gpointer data); -static gpointer server_client_thread(gpointer data); - -/* Other private functions */ -static void close_socket(gpointer _socket, gpointer data); -static void wait_for_thread(gpointer _thread, gpointer data); - -/* tunnel and main threads */ -static GThread *tunnel_gthread = NULL; -static GThread *main_gthread = NULL; - -/* list of communication threads */ -static GSList *communication_threads = NULL; - -/* list of sockets */ -static GSList *sockets = NULL; - -/* thread termination flag */ -static gboolean run_tunnel = FALSE; - -/* internal.h shared constructs */ -int tunnel_port; - -///////////////// - -/** public implementations **/ - -/* start tunnel thread */ -void -start_tunnel(void) -{ - GError *error = NULL; - - DEBUG ("starting the tunnel thread"); - - assert (tunnel_gthread == NULL); - - run_tunnel = TRUE; - - main_gthread = g_thread_self (); - - tunnel_gthread = g_thread_create (tunnel_thread, NULL, TRUE, &error); - if (error) { - g_print ("%s\n", error->message); - g_error_free (error); - exit (1); - } -}; - -/* stop tunnel thread */ -void -stop_tunnel(void) -{ - if(!run_tunnel) - return; - - DEBUG ("stopping the tunnel thread"); - - assert (tunnel_gthread != NULL); - ASSERT_IS_MAIN_THREAD (); - - run_tunnel = FALSE; - - g_slist_foreach(sockets, close_socket, NULL); - - (void) g_thread_join (tunnel_gthread); - tunnel_gthread = NULL; -}; - -///////////////// - -/** private implementations **/ - -/* the tunnel thread */ -static gpointer -tunnel_thread (gpointer _data) -{ - struct hostent *dns_serv; - - //char vm_data[VM_NAME_MAX_LEN]; - int local_server_socketfd, ovirt_server_socket, client_socketfd; - unsigned int local_server_len, client_len, ovirt_server_len; - - struct sockaddr_in local_server_address; - struct sockaddr_in ovirt_server_address; - struct sockaddr_in client_address; - - struct sockaddr_in local_server_address_lookup; - unsigned int local_server_address_lookup_len = sizeof(local_server_address_lookup); - - GThread *client_server_gthread = NULL; - GThread *server_client_gthread = NULL; - - int sockets_param[2]; - int * c_socket; - - DEBUG ("tunnel thread starting up"); - - // ovirt server address - dns_serv = gethostbyname(hostname); - if(dns_serv == NULL){ - DEBUG("ovirt server lookup failed"); - return NULL; - } - ovirt_server_address.sin_family = PF_INET; - ovirt_server_address.sin_addr.s_addr = ((struct in_addr*)(dns_serv->h_addr))->s_addr; //inet_addr(hostname); - ovirt_server_address.sin_port = htons(ovirt_server_vnc_port); - ovirt_server_len = sizeof(ovirt_server_address); - - // create local net socket - local_server_socketfd = socket(PF_INET, SOCK_STREAM, 0); - c_socket = malloc(sizeof(int)); *c_socket = local_server_socketfd; - sockets = g_slist_prepend(sockets, c_socket); - - // local server address - local_server_address.sin_family = PF_INET; - local_server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); - local_server_address.sin_port = 0; - local_server_len = sizeof(local_server_address); - - // increment ports until one is available - if(bind(local_server_socketfd, (struct sockaddr*)&local_server_address, local_server_len) < 0){ - DEBUG("tunnel bind failed"); - return NULL; - } - - getsockname(local_server_socketfd, - (struct sockaddr*) &local_server_address_lookup, - &local_server_address_lookup_len); - tunnel_port = (int)ntohs(local_server_address_lookup.sin_port); - DEBUG ("tunnel bound to local port %i", tunnel_port); - - // increase client buffer size? - listen(local_server_socketfd, 5); - - while(run_tunnel) { - // accept a client connection - DEBUG("tunnel accepting"); - client_len = sizeof(client_address); - client_socketfd = accept(local_server_socketfd, (struct sockaddr*)&client_address, &client_len); - if(client_socketfd < 0){ - DEBUG("tunnel accept failed"); - break; - } - // TODO check accept return value for err - c_socket = malloc(sizeof(int)); *c_socket = client_socketfd; - sockets = g_slist_prepend(sockets, c_socket); - - DEBUG ("client connected to tunnel"); - - // establish connection w/ ovirt server - ovirt_server_socket = socket(PF_INET, SOCK_STREAM, 0); - c_socket = malloc(sizeof(int)); *c_socket = ovirt_server_socket; - sockets = g_slist_prepend(sockets, c_socket); - DEBUG ("connecting to ovirt server %s on %i", hostname, ovirt_server_vnc_port); - if(connect(ovirt_server_socket, (struct sockaddr*)&ovirt_server_address, ovirt_server_len) < 0){ - DEBUG ("could not connect to ovirt server"); - break; - //return NULL; - } - DEBUG ("connected to ovirt server"); - - sockets_param[0] = ovirt_server_socket; - sockets_param[1] = client_socketfd; - - // launch thread for client -> server traffic - client_server_gthread = g_thread_create (client_server_thread, - &sockets_param, TRUE, NULL); - - // launch thread for server -> client traffic - server_client_gthread = g_thread_create (server_client_thread, - &sockets_param, TRUE, NULL); - - communication_threads = g_slist_prepend(communication_threads, client_server_gthread); - communication_threads = g_slist_prepend(communication_threads, server_client_gthread); - - // send target vm for this session - //strcpy(vm_data, vm_in_focus->description); - DEBUG ("sending vm %s", vm_in_focus->description); - write(ovirt_server_socket, vm_in_focus->description, strlen(vm_in_focus->description)); - - } - - DEBUG("terminating tunnel thread"); - - // wait for connection threads to finish - g_slist_foreach(communication_threads, wait_for_thread, NULL); - - DEBUG ("tunnel thread completed"); - return NULL; -}; - -/* the tunnel thread */ -static gpointer -client_server_thread (gpointer _data){ - int nbytes; - char vnc_data[VNC_DATA_MAX_LEN]; - - int ovirt_server_socket = ((int*)_data)[0], - client_socket = ((int*)_data)[1]; - - DEBUG ("client/server thread starting up"); - - while(run_tunnel){ - VERBOSE( "accepting client data"); - - // grab vnc data - nbytes = read(client_socket, vnc_data, VNC_DATA_MAX_LEN); - if(nbytes <= 0){ - DEBUG ( "error reading data from client" ); - break; - } - VERBOSE ("read %i bytes from client", nbytes); - - // send network_data onto server - nbytes = write(ovirt_server_socket, vnc_data, nbytes); - if(nbytes <= 0){ - DEBUG ( "error writing data to server" ); - break; - } - VERBOSE ("wrote %i bytes to server", nbytes); - } - - DEBUG ("client/server thread completed"); - return NULL; -}; - -/* the server thread */ -static gpointer -server_client_thread (gpointer _data){ - char vnc_data[VNC_DATA_MAX_LEN]; - - int ovirt_server_socket = ((int*)_data)[0], - client_socket = ((int*)_data)[1]; - - int nbytes; - - DEBUG ("server/client thread starting up"); - - while(run_tunnel){ - // grab vnc data - nbytes = read(ovirt_server_socket, vnc_data, VNC_DATA_MAX_LEN); - if(nbytes <= 0){ - DEBUG ( "error reading data from server" ); - break; - } - VERBOSE ("read %i bytes from server", nbytes); - - // send network_data onto client - nbytes = write(client_socket, vnc_data, nbytes); - if(nbytes <= 0){ - DEBUG ( "error writing data to client" ); - break; - } - VERBOSE ("wrote %i bytes to client", nbytes); - } - - DEBUG ("server/client thread completed"); - return NULL; -}; - -static void close_socket(gpointer _socket, gpointer data){ - shutdown(*(int*) _socket, 2); - close(*(int*) _socket); - free((int*) _socket); -}; - -static void wait_for_thread(gpointer _thread, gpointer data){ - g_thread_join((GThread*)_thread); -}; diff --git a/wui_thread.c b/wui_thread.c deleted file mode 100644 index 8bfa8ca..0000000 --- a/wui_thread.c +++ /dev/null @@ -1,1106 +0,0 @@ -/* ovirt viewer console application - * Copyright (C) 2008 Red Hat Inc. - * Written by Richard W.M. Jones - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -/* For an explanation of the threading model, please main(). */ - -#include - -#include -#include -#include -#include - -#include -#include - -#include - -#include - -#include "internal.h" - -/* Private functions. */ -static gpointer wui_thread (gpointer data); -static void wui_thread_send_quit (void); -static void do_curl_init (void); -static void write_fn_start_capture (void); -static char *write_fn_finish_capture (void); -static void write_fn_discard_capture_buffer (void); -static gboolean do_connect (void); -static gboolean do_login (void); -static gboolean refresh_vm_list (void); -static void parse_vmlist_from_xml (const char *xml); -static struct vm *parse_vm_from_xml (xmlNodePtr node); - -/* Messages (main thread -> WUI thread only). - * - * These are passed by reference. They are allocated by the sender - * (ie. the main thread) and freed by the receiver (ie. the WUI thread). - */ -enum message_type { - QUIT, /* Tell the WUI thread to quit cleanly. */ - CONNECT, /* Tell to connect (just fetch the URI). */ - DISCONNECT, /* Tell to disconnect, forget state. */ - LOGIN, /* Tell to login, and pass credentials. */ - REFRESH_VM_LIST, /* Tell to refresh the VM list right away. */ -}; - -struct message { - enum message_type type; - char *str1; - char *str2; -}; - -/* Start the WUI thread. See main() for explanation of the threading model. */ -static GThread *wui_gthread = NULL; -static GThread *main_gthread = NULL; -static GAsyncQueue *wui_thread_queue = NULL; - -void -start_wui_thread (void) -{ - GError *error = NULL; - - DEBUG ("starting the WUI thread"); - - assert (wui_gthread == NULL); - - main_gthread = g_thread_self (); - - /* Create the message queue for main -> WUI thread communications. */ - wui_thread_queue = g_async_queue_new (); - - wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error); - if (error) { - g_print ("%s\n", error->message); - g_error_free (error); - exit (1); - } -} - -void -stop_wui_thread (void) -{ - DEBUG ("stopping the WUI thread"); - - assert (wui_gthread != NULL); - ASSERT_IS_MAIN_THREAD (); - - /* Send a quit message then wait for the WUI thread to join. - * - * This "nice" shutdown could cause the UI to hang for an - * indefinite period (eg. if the WUI thread is engaged in some - * long connection or download from the remote server). But - * I want to keep it this way for the moment so that we can - * diagnose problems with the WUI thread. - * - * (This could be solved with some sort of interruptible - * join, but glib doesn't support that AFAICT). - */ - wui_thread_send_quit (); - (void) g_thread_join (wui_gthread); - g_async_queue_unref (wui_thread_queue); - wui_gthread = NULL; -} - -void -assert_is_wui_thread (const char *filename, int lineno) -{ - if (g_thread_self () != wui_gthread) { - fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno); - abort (); - } -} - -void -assert_is_main_thread (const char *filename, int lineno) -{ - if (g_thread_self () != main_gthread) { - fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno); - abort (); - } -} - -/* Send the quit message to the WUI thread. */ -static void -wui_thread_send_quit (void) -{ - struct message *msg; - - ASSERT_IS_MAIN_THREAD (); - - msg = g_new (struct message, 1); - msg->type = QUIT; - g_async_queue_push (wui_thread_queue, msg); -} - -/* Send the connect message to the WUI thread. */ -void -wui_thread_send_connect (const char *uri) -{ - struct message *msg; - - ASSERT_IS_MAIN_THREAD (); - - msg = g_new (struct message, 1); - msg->type = CONNECT; - msg->str1 = g_strdup (uri); - g_async_queue_push (wui_thread_queue, msg); -} - -/* Send the disconnect message to the WUI thread. */ -void -wui_thread_send_disconnect (void) -{ - struct message *msg; - - ASSERT_IS_MAIN_THREAD (); - - msg = g_new (struct message, 1); - msg->type = DISCONNECT; - g_async_queue_push (wui_thread_queue, msg); -} - -/* Send the login message to the WUI thread. */ -void -wui_thread_send_login (const char *username, const char *password) -{ - struct message *msg; - - ASSERT_IS_MAIN_THREAD (); - - msg = g_new (struct message, 1); - msg->type = LOGIN; - msg->str1 = g_strdup (username); - msg->str2 = g_strdup (password); - g_async_queue_push (wui_thread_queue, msg); -} - -/* Send the refresh VM list message to the WUI thread. */ -void -wui_thread_send_refresh_vm_list (void) -{ - struct message *msg; - - ASSERT_IS_MAIN_THREAD (); - - msg = g_new (struct message, 1); - msg->type = REFRESH_VM_LIST; - g_async_queue_push (wui_thread_queue, msg); -} - -/* The current state. - * - * For safety, the main thread must lock this before reading, and the - * WUI thread must lock this before writing. However the WUI thread - * does not need to lock before reading, because no other thread - * can modify it. - */ -static gboolean connected = FALSE; -static gboolean logged_in = FALSE; -static gboolean busy = FALSE; -static GStaticMutex state_mutex; - -static void set_connected (gboolean); -static void set_logged_in (gboolean); -static void set_busy (gboolean); - -/* The private state of the WUI thread. */ -static int secs_between_refresh = 60; -static CURL *curl = NULL; -static char curl_error_buffer[CURL_ERROR_SIZE]; -static char *uri = NULL; -static char *username = NULL; -static char *password = NULL; - -static gboolean process_message (struct message *); - -/* The WUI thread. See main() above for explanation of - * the threading model. - */ -static gpointer -wui_thread (gpointer _queue) -{ - GAsyncQueue *queue = (GAsyncQueue *) _queue; - gboolean quit = FALSE; - GTimeVal tv; - gpointer _msg; - struct message *msg; - - DEBUG ("WUI thread starting up"); - - /* This checks wui_gthread global which is actually set in the - * main thread. Of course, it might not be set if the WUI thread - * runs first. Hence we sleep for the main thread to run. (XXX) - */ - g_usleep (100000); - ASSERT_IS_WUI_THREAD (); - - g_async_queue_ref (queue); - - /* In the thread's loop we check for new instructions from the main - * thread and carry them out. Also, if we are connected and logged - * in then we periodically recheck the list of VMs. - */ - while (!quit) { - set_busy (FALSE); - - if (logged_in) { - g_get_current_time (&tv); - g_time_val_add (&tv, secs_between_refresh * 1000000); - _msg = g_async_queue_timed_pop (queue, &tv); - } else - _msg = g_async_queue_pop (queue); - - set_busy (TRUE); - - msg = (struct message *) _msg; - - if (msg) { - DEBUG ("received message with msg->type = %d", msg->type); - quit = process_message (msg); - /* Don't free any strings in the message - we've saved them. */ - g_free (msg); - } else { - /* No message, must have got a timeout instead, which means - * we are logged in and we should refresh the list of VMs. - * Note it's not an error if we temporarily lose contact - * with the WUI. - */ - refresh_vm_list (); - } - } - - DEBUG ("WUI thread shutting down cleanly"); - - g_async_queue_unref (queue); - g_thread_exit (NULL); - return NULL; /* not reached? */ -} - -/* The WUI thread calls this to safely update the state variables. - * This also updates elements in the UI by setting idle callbacks - * which are executed in the context of the main thread. - */ -static void -set_connected (gboolean new_connected) -{ - ASSERT_IS_WUI_THREAD (); - - g_static_mutex_lock (&state_mutex); - connected = new_connected; - g_static_mutex_unlock (&state_mutex); - - if (connected) - g_idle_add (main_connected, NULL); - else - g_idle_add (main_disconnected, NULL); -} - -static void -set_logged_in (gboolean new_logged_in) -{ - ASSERT_IS_WUI_THREAD (); - - g_static_mutex_lock (&state_mutex); - logged_in = new_logged_in; - g_static_mutex_unlock (&state_mutex); - - if (logged_in) - g_idle_add (main_logged_in, NULL); - else - g_idle_add (main_logged_out, NULL); -} - -static void -set_busy (gboolean new_busy) -{ - ASSERT_IS_WUI_THREAD (); - - g_static_mutex_lock (&state_mutex); - busy = new_busy; - g_static_mutex_unlock (&state_mutex); - - if (busy) - g_idle_add (main_busy, NULL); - else - g_idle_add (main_idle, NULL); -} - -/* The main thread should call these functions to get the WUI thread's - * current state. - */ -gboolean -wui_thread_is_connected (void) -{ - gboolean ret; - - ASSERT_IS_MAIN_THREAD (); - - g_static_mutex_lock (&state_mutex); - ret = connected; - g_static_mutex_unlock (&state_mutex); - return ret; -} - -gboolean -wui_thread_is_logged_in (void) -{ - gboolean ret; - - ASSERT_IS_MAIN_THREAD (); - - g_static_mutex_lock (&state_mutex); - ret = logged_in; - g_static_mutex_unlock (&state_mutex); - return ret; -} - -gboolean -wui_thread_is_busy (void) -{ - gboolean ret; - - ASSERT_IS_MAIN_THREAD (); - - g_static_mutex_lock (&state_mutex); - ret = busy; - g_static_mutex_unlock (&state_mutex); - return ret; -} - -/* Process a message from the main thread. */ -static gboolean -process_message (struct message *msg) -{ - ASSERT_IS_WUI_THREAD (); - - switch (msg->type) { - case QUIT: - write_fn_discard_capture_buffer (); - if (curl) curl_easy_cleanup (curl); - if (uri) g_free (uri); - if (username) g_free (username); - if (password) g_free (password); - set_connected (FALSE); - set_logged_in (FALSE); - return 1; - - case CONNECT: - write_fn_discard_capture_buffer (); - if (curl) curl_easy_cleanup (curl); - do_curl_init (); - if (uri) g_free (uri); - uri = msg->str1; - - if (do_connect ()) { - set_connected (TRUE); - } else { - set_connected (FALSE); - set_logged_in (FALSE); - } - break; - - case DISCONNECT: - /* This just forgets the state. REST is connectionless. */ - write_fn_discard_capture_buffer (); - if (curl) curl_easy_cleanup (curl); - curl = NULL; - if (uri) g_free (uri); - uri = NULL; - if (username) g_free (username); - username = NULL; - if (password) g_free (password); - password = NULL; - set_connected (FALSE); - set_logged_in (FALSE); - break; - - case LOGIN: - if (username) g_free (username); - username = msg->str1; - if (password) g_free (password); - password = msg->str2; - - /* If we're not connected, this message just updates the - * username and password. Otherwise if we are connected, - * try to login and grab the initial list of VMs. - */ - if (connected) { - if (do_login ()) { - set_logged_in (TRUE); - if (refresh_vm_list ()) - secs_between_refresh = 60; - } else { - set_logged_in (FALSE); - } - } - break; - - case REFRESH_VM_LIST: - if (connected && logged_in) { - refresh_vm_list (); - secs_between_refresh = 60; - } - break; - - default: - DEBUG ("unknown message type %d", msg->type); - abort (); - } - - return 0; -} - -/* Macro for easy handling of CURL errors. */ -#define CURL_CHECK_ERROR(fn, args) \ - ({ \ - CURLcode __r = fn args; \ - if (__r != CURLE_OK) { \ - fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r)); \ - } \ - __r; \ - }) - -/* Libcurl has a really crufty method for handling HTTP headers and - * data. We set these functions as callbacks, because the default - * callback functions print the data out to stderr. In order to - * capture the data, we have to keep state here. - */ - -static char *write_fn_buffer = NULL; -static ssize_t write_fn_len = -1; - -static size_t -write_fn (void *ptr, size_t size, size_t nmemb, void *stream) -{ - int bytes = size * nmemb; - int old_start; - - ASSERT_IS_WUI_THREAD (); - - if (write_fn_len >= 0) { /* We're capturing. */ - old_start = write_fn_len; - write_fn_len += bytes; - write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len); - memcpy (write_fn_buffer + old_start, ptr, bytes); - } - - return bytes; -} - -/* Start capturing HTTP response data. */ -static void -write_fn_start_capture (void) -{ - ASSERT_IS_WUI_THREAD (); - - write_fn_discard_capture_buffer (); - write_fn_buffer = NULL; - write_fn_len = 0; -} - -/* Finish capture and return the capture buffer. Caller must free. */ -static char * -write_fn_finish_capture (void) -{ - char *ret; - - ASSERT_IS_WUI_THREAD (); - - /* Make sure the buffer is NUL-terminated before returning it. */ - write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len+1); - write_fn_buffer[write_fn_len] = '\0'; - ret = write_fn_buffer; - - write_fn_buffer = NULL; - write_fn_len = -1; - return ret; -} - -/* Stop capturing and discard the capture buffer, if any. */ -static void -write_fn_discard_capture_buffer (void) -{ - ASSERT_IS_WUI_THREAD (); - - g_free (write_fn_buffer); - write_fn_buffer = NULL; - write_fn_len = -1; -} - -static size_t -header_fn (void *ptr, size_t size, size_t nmemb, void *stream) -{ - int bytes = size * nmemb; - - ASSERT_IS_WUI_THREAD (); - - return bytes; -} - -/* Called from the message loop to initialize the CURL handle. */ -static void -do_curl_init (void) -{ - DEBUG ("initializing libcurl"); - - ASSERT_IS_WUI_THREAD (); - - curl = curl_easy_init (); - if (!curl) { /* This is probably quite bad, so abort. */ - DEBUG ("curl_easy_init failed"); - abort (); - } - - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_CAINFO, cainfo)); - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0)); - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0)); - - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_WRITEFUNCTION, write_fn)); - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_HEADERFUNCTION, header_fn)); - - /* This enables error messages in curl_easy_perform. */ - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_ERRORBUFFER, curl_error_buffer)); - - /* This enables cookie handling, using an internal cookiejar. */ - CURL_CHECK_ERROR (curl_easy_setopt, - (curl, CURLOPT_COOKIEFILE, "")); -} - -/* Called from the message loop. Try to connect to the current URI. - * Returns true on success. - */ -static gboolean -do_connect (void) -{ - long code = 0; - CURLcode r; - char *error_str; - - DEBUG ("connecting to uri %s", uri); - ASSERT_IS_WUI_THREAD (); - - /* Set the URI for libcurl. */ - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri)); - - /* Try to fetch the URI. */ - r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); - if (r != CURLE_OK) { - /* Signal an error back to the main thread. */ - error_str = g_strdup (curl_easy_strerror (r)); - g_idle_add (main_connection_error, error_str); - return FALSE; - } - - CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); - DEBUG ("HTTP return code is %ld", code); - if (code != 200 && code != 302 && code != 401) { - /* XXX If only glib had g_asprintf. */ - error_str = g_strdup ("unexpected HTTP return code from server"); - g_idle_add (main_connection_error, error_str); - return FALSE; - } - - return TRUE; -} - -/* Called from the message loop. Try to login to 'URI/login' with the - * current username and password. Returns true on success. - */ -static gboolean -do_login (void) -{ - int len; - char *login_uri; - char *userpwd; - char *error_str; - CURLcode r; - long code = 0; - - DEBUG ("logging in with username %s, password *****", username); - ASSERT_IS_WUI_THREAD (); - - /* Generate the login URI from the base URI. */ - len = strlen (uri) + 6 + 1; - login_uri = g_alloca (len); - snprintf (login_uri, len, "%s/login", uri); - - DEBUG ("login URI %s", login_uri); - - /* Set the URI for libcurl. */ - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri)); - - /* Construct the username:password for CURL. */ - len = strlen (username) + strlen (password) + 2; - userpwd = g_alloca (len); - snprintf (userpwd, len, "%s:%s", username, password); - - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd)); - - /* HTTP Basic authentication is OK since we should be sending - * this only over HTTPS. - */ - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); - - /* Follow redirects. */ - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1)); - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10)); - - /* Try to fetch the URI. */ - r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); - if (r != CURLE_OK) { - /* Signal an error back to the main thread. */ - error_str = g_strdup (curl_easy_strerror (r)); - g_idle_add (main_login_error, error_str); - return FALSE; - } - - CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); - DEBUG ("HTTP return code is %ld", code); - switch (code) - { - case 200: - DEBUG ("login was successful"); - return TRUE; - - case 401: - error_str = g_strdup ("server rejected the username or password"); - g_idle_add (main_login_error, error_str); - return FALSE; - - default: - /* XXX If only glib had g_asprintf. */ - error_str = g_strdup ("unexpected HTTP return code from server"); - g_idle_add (main_login_error, error_str); - return FALSE; - } -} - -/* Called from the message loop. Refresh the list of VMs. */ -static gboolean -refresh_vm_list (void) -{ - int len; - char *vms_uri; - char *error_str; - CURLcode r; - long code = 0; - char *xml; - - DEBUG ("refreshing list of VMs"); - ASSERT_IS_WUI_THREAD (); - - /* Generate the vms URI from the base URI. */ - len = strlen (uri) + 4 + 1; - vms_uri = g_alloca (len); - snprintf (vms_uri, len, "%s/vms", uri); - - DEBUG ("vms URI %s", vms_uri); - - /* Set the URI for libcurl. */ - CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri)); - - /* We want to capture the output, so tell our write function - * to place the output into a buffer. - */ - write_fn_start_capture (); - - /* Try to fetch the URI. */ - r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); - if (r != CURLE_OK) { - /* Signal an error back to the main thread. */ - error_str = g_strdup (curl_easy_strerror (r)); - g_idle_add (main_login_error, error_str); - return FALSE; - } - - CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); - DEBUG ("HTTP return code is %ld", code); - switch (code) - { - case 200: break; - - /* Hmm - even though we previously logged in, the server is - * rejecting our attempts now with an authorization error. - * We move to the logged out state. - */ - case 401: - set_logged_in (FALSE); - error_str = g_strdup ("server rejected the username or password"); - g_idle_add (main_login_error, error_str); - return FALSE; - - default: - /* XXX If only glib had g_asprintf. */ - error_str = g_strdup ("unexpected HTTP return code from server"); - g_idle_add (main_status_error, error_str); - return FALSE; - } - - /* If we got here then we appear to have a correct - * XML document describing the list of VMs. - */ - secs_between_refresh <<= 1; - - xml = write_fn_finish_capture (); - - parse_vmlist_from_xml (xml); - g_free (xml); - - return TRUE; -} - -/* Functions to deal with the list of VMs, parsing it from the XML, etc. - * - * A vmlist is a GSList (glib singly-linked list) of vm structures. - * The caller must call free_vmlist to free up this list correctly. - */ - -static void -free_vm (gpointer _vm, gpointer data) -{ - struct vm *vm = (struct vm *) _vm; - - g_free (vm->description); - g_free (vm->state); - g_free (vm->uuid); - g_free (vm->mac_addr); - g_free (vm); -} - -void -free_vmlist (GSList *vmlist) -{ - g_slist_foreach (vmlist, free_vm, NULL); - g_slist_free (vmlist); -} - -static struct vm * -copy_vm (struct vm *vm) -{ - struct vm *vm2; - - vm2 = g_memdup (vm, sizeof (*vm)); - vm2->description = g_strdup (vm->description); - vm2->uuid = g_strdup (vm->uuid); - vm2->state = vm->state ? g_strdup (vm->state) : NULL; - vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL; - return vm2; -} - -static int -compare_vm (gconstpointer _vm1, gconstpointer _vm2) -{ - const struct vm *vm1 = (const struct vm *) _vm1; - const struct vm *vm2 = (const struct vm *) _vm2; - - return strcmp (vm1->description, vm2->description); -} - -static GSList *vmlist = NULL; -static gboolean vmlist_valid = FALSE; -static GStaticMutex vmlist_mutex; - -/* Called from the main thread to find out if we have a valid vmlist. */ -gboolean -wui_thread_has_valid_vmlist (void) -{ - gboolean ret; - - ASSERT_IS_MAIN_THREAD (); - - g_static_mutex_lock (&vmlist_mutex); - ret = vmlist_valid; - g_static_mutex_unlock (&vmlist_mutex); - return ret; -} - -/* Called from the main thread to find return the current vmlist. This - * actually returns a deep copy of it that the caller must free. - */ -static void -duplicate_and_insert_vm (gpointer _vm, gpointer _ret) -{ - struct vm *vm = (struct vm *) _vm; - GSList **ret = (GSList **) _ret; - - *ret = g_slist_prepend (*ret, copy_vm (vm)); -} - -gboolean -wui_thread_get_vmlist (GSList **ret) -{ - gboolean r; - - ASSERT_IS_MAIN_THREAD (); - - r = FALSE; - *ret = NULL; - - g_static_mutex_lock (&vmlist_mutex); - if (!vmlist_valid) goto done; - - g_slist_foreach (vmlist, duplicate_and_insert_vm, ret); - *ret = g_slist_sort (*ret, compare_vm); - - r = TRUE; - done: - g_static_mutex_unlock (&vmlist_mutex); - return r; -} - -/* Called from the message loop in the WUI thread, with an XML document - * which we turn into a list of VM structures, and update the vmlist - * if we can. - */ -static void -parse_vmlist_from_xml (const char *xml) -{ - xmlDocPtr doc = NULL; - xmlNodePtr root; - xmlNodePtr node; - char *error_str; - GSList *new_vmlist = NULL; - struct vm *vm; - int len; - - /*DEBUG ("XML =\n%s", xml);*/ - ASSERT_IS_WUI_THREAD (); - - /* We don't really expect that we won't be able to parse the XML ... */ - len = strlen (xml); - doc = xmlReadMemory (xml, len, NULL, NULL, 0); - - if (!doc) { - DEBUG ("error parsing XML document, xml =\n%s", xml); - error_str = g_strdup ("error parsing XML document from remote server"); - g_idle_add (main_status_error, error_str); - goto done; - } - - root = xmlDocGetRootElement (doc); - if (!root) { - DEBUG ("XML document was empty"); - error_str = g_strdup ("XML document was empty"); - g_idle_add (main_status_error, error_str); - goto done; - } - - /* We expect the root element will be either "nil-classes" - * or "vms", with the former indicating an empty list of VMs. - */ - if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) { - g_static_mutex_lock (&vmlist_mutex); - vmlist_valid = TRUE; - free_vmlist (vmlist); - vmlist = NULL; - g_static_mutex_unlock (&vmlist_mutex); - - /* Signal to the main UI thread that the list has been updated. */ - g_idle_add (main_vmlist_updated, NULL); - - goto done; - } - - if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) { - DEBUG ("unexpected root node in XML document, xml =\n%s", xml); - error_str = g_strdup ("unexpected root node in XML document"); - g_idle_add (main_status_error, error_str); - goto done; - } - - /* The document is with a list of elements which - * we process in turn. - */ - for (node = root->xmlChildrenNode; node != NULL; node = node->next) { - if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) { - vm = parse_vm_from_xml (node); - if (!vm) { - error_str = g_strdup ("could not parse element"); - g_idle_add (main_status_error, error_str); - - free_vmlist (new_vmlist); - goto done; - } - new_vmlist = g_slist_prepend (new_vmlist, vm); - } - } - - /* Successfully parsed all the nodes, so swap the old and new - * vmlists. - */ - g_static_mutex_lock (&vmlist_mutex); - vmlist_valid = TRUE; - free_vmlist (vmlist); - vmlist = new_vmlist; - g_static_mutex_unlock (&vmlist_mutex); - - /* Signal that the vmlist has been updated. */ - g_idle_add (main_vmlist_updated, NULL); - - done: - /* Free up XML resources used before returning. */ - if (doc) xmlFreeDoc (doc); -} - -static struct vm * -parse_vm_from_xml (xmlNodePtr node) -{ - xmlNodePtr p; - struct vm vm, *ret; - xmlChar *str; - - memset (&vm, 0, sizeof vm); - vm.hostid = -1; - vm.id = -1; - vm.vnc_port = -1; - vm.forward_vnc_port = -1; - vm.mem_allocated = -1; - vm.mem_used = -1; - vm.vcpus_allocated = -1; - vm.vcpus_used = -1; - - for (p = node->xmlChildrenNode; p != NULL; p = p->next) { - if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.description = g_strdup ((char *) str); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.hostid = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.id = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.mem_allocated = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.mem_used = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.vcpus_allocated = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.vcpus_used = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.state = g_strdup ((char *) str); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.uuid = g_strdup ((char *) str); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.vnc_port = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "forward-vnc-port") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.forward_vnc_port = strtol ((char *) str, NULL, 10); - xmlFree (str); - } - } - else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) { - str = xmlNodeGetContent (p); - if (str != NULL) { - vm.mac_addr = g_strdup ((char *) str); - xmlFree (str); - } - } - } - - /* Make sure we've got the required fields. */ - ret = NULL; - if (vm.description == NULL) - DEBUG ("required field \"description\" missing from structure"); - else if (vm.hostid == -1) - DEBUG ("required field \"description\" missing from structure"); - else if (vm.id == -1) - DEBUG ("required field \"description\" missing from structure"); - else if (vm.vnc_port == -1) - DEBUG ("required field \"vnc-port\" missing from structure"); - else if (vm.forward_vnc_port == -1) - DEBUG ("required field \"forward-vnc-port\" missing from structure"); - else if (vm.uuid == NULL) - DEBUG ("required field \"uuid\" missing from structure"); - else - ret = g_memdup (&vm, sizeof vm); - - return ret; -} - -gboolean -main_vmlist_has_running_vm(struct vm* _vm) -{ - // TODO ? get list and wait to be retreived - // wui_thread_send_refresh_vm_list(); - - // find vm in list - GSList* res = g_slist_find_custom (vmlist, _vm, compare_vm); - - // return true if running - if(res != NULL) return STREQ (((struct vm*) res->data)->state, "running"); - - return FALSE; -} -- 1.6.0.6 From mmorsi at redhat.com Fri Jul 24 18:02:57 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 24 Jul 2009 14:02:57 -0400 Subject: [Ovirt-devel] [PATCH server] add collapsable sections to vm form Message-ID: <1248458578-20248-1-git-send-email-mmorsi@redhat.com> the vm form is getting cluttered, this patch simply add collapsable sections to the form, making the 'storage' and 'network' sections collapsed by default credit goes to jayg for contributing alot to this patch in terms of simplification and cleanup --- src/app/helpers/application_helper.rb | 4 +- src/app/views/vm/_form.rhtml | 35 ++++++++++++++++++++++---------- src/public/stylesheets/components.css | 1 + src/public/stylesheets/layout.css | 11 ++++++++- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index 0c6562e..7922c6f 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -72,8 +72,8 @@ module ApplicationHelper def check_box_tag_with_label(label, name, value = "1", checked = false) %{ -
      - #{check_box_tag name, value, checked}
      +
      #{check_box_tag name, value, checked} +
      } end diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 034c3df..8373bf4 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -6,15 +6,19 @@ <%= hidden_field 'vm', 'vm_resource_pool_id' %> <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id if @hardware_pool %> +
      General
      +
      <%= text_field_with_label "Name:", "vm", "description", {:style=>"width:250px;"} %> <%= text_field_with_label "UUID:", "vm", "uuid", {:style=>"width:250px;"} %> <%= select_with_label "Operating System:", 'vm', 'provisioning_and_boot_settings', @provisioning_options, :style=>"width:250px;" %> <% if controller.action_name == "edit" %>*Warning* Editing provision could overwrite vm<% end %> +
      +
      +
      -
      Resources
      -
      -
      +
      Resources
      +
      <%= text_field_with_label "CPUs:", "vm", "num_vcpus_allocated", {:style=>"width:100px; margin-bottom:2px;"}, {:style=>"padding-right: 50px;"} %>
      max to create: <%=create_resources[:cpus]%>
      @@ -27,6 +31,11 @@
      +
      +
      + +
      Storage
      +

      QU8zbm!PeoD{4GN}>n$I}@VO0rvC;eELQ(2jgjHZur>arJ zIBiV41O=;?d~)Jhd^}4+K*3kHl)x8^L%o8JV)*pzb?Z>C;G at +J=VkPj)fHavP77X%&6p>-+)=1wI{ZHD&^=Ggj_1 zWw}4{b$&#-pQYSH5M$a&9T18f2Yjt4d&Ebx49>JZvazXy!#?4Wq4TiMmip9Je-#XH zQ`S#?sWr%G9c4>BhbJwL^mu8f_25*cSd@=U)%pQrVG^^xEO2_T|2$~Gj)byMEY&s%n*{(%I zfz=114wQjD#HvU>HEd_K3HDCK5l`9-J&N&TcPdXAI6~z5djtYBi5WT at Pughe5rttR zuGwz))Jzqghz_VWYPO#5T(qUPZ zvc6|rl_?abDHWOTYVCuM^a`5L_-3OAe9P7>&^O+ObOI6vF z0{elmYNlptr)DNi&3LC~>ZWGur)Chq_>Wdm0(K6rr=_Ljfu zvuidsF|@SMLTRo%Z8%pq54{F#nrmbI|FlWEhU~rccdM&+cE+7}$2X<(9hzOiYM%;@ zd!+^Pwz%D`;tSTQZ%#FCcdMWh;?`W9yCRBK#n;4d`S==hDbI_X2AzR}KEt~0KZZ9F$kCd^Kr>p^Yvky~;Xh}B1_P>$R?W^-gnF0| zW9vTS%dX=~cPA9eKzEXvWO=AUfE-xbcDC>mOaJSSVVI!`Mjhxv_S-lQ0j(rf5FrQd8t zHFfEet!!WZEpZ9>j<3~VxmLk)tzbFYPHpE|gaFLW)Z=4+%j`@8KHj95MtmG^g(3oN zxymTtYsgEC>@gpw(2P%ll$p}YjpUh{wP$I5V(2_%Hqu0^)r)#fmoHj_$Y6yV1r}C# zvOq?>dMWLWRyHfN<+{;uhBEZGYgI^SQ3+4L6R-I~ov-=iA15wL991ZKN&KiH$1jt+ zE*r_G?)7h}@4&O?Ya_TpeMIG3&_~pSFK^6t?0XjXr)EUHkiIrT0+|s*G8t8oM- at 1_ zu&R@!JDL?*ZT_9lXQ?0S{uOHTDC_#ySVI7qFZ>D22rv0Z zSay281uy8D?If7O_P|?~!srp-oYItj#G2BN(3E}zCnzn2ArvAz~Oc at ATyjQ`ND8XS<{lgGy7#;^lgtITo0?ja_;kTm^c87X-4`TQ< zeEtHV`)WS9;#EsXYy>NHPq=3`znEUlTUES87fTWz-Zk5ZDBl51eDDtE)5RVmyF;C2 zT|QE{fU(i#qedeGA4?rTkT+j?_DzNAi}Ct6eUD5U#@ zdmz0f^|=owI_#FapnX<2`xXp&47R3D@!kZF5t|jF`;UoR%S(4fDocxSdFgvp8{v|= zIU>-&hYn7@*E(`|3r7y?tl$v@*IU6e{P?i)fT)id=g)CZe4CqAAnq|DmpO_S*o~Kj zu$w*P;}?8<$NM2|az5ng>wYFwGmPy&+#)5GDom{*UoX>1_w_;!!1F||>Oy`BXna4nS%Do^fo>Fd z%&Mu|sEK}wYNFuXWd$E^hK=o6P#E%???%_Vpbp35o~V4YzbtUAzpvgj3^klO%en}9 zO3pslzJ3_ZH-cxTQ*f?-N?FPIRMJR&o-$){+(|TaRNrqecN=D-p9O44c!B9Vtjk|_ zjVu9NBb$>4Eb1NaZz%Gt&qR8zzqPO4NN2|I{yiLr`mq-m=JzO&Jv$wBOk{r!=I=s{ zRc2;^3L=+YZTBvtDY*prQ?|eRD;pG7j2f at ygpRW6Md5gHx)jYx2u_EfSPNRl1*L1f1F*I*0rq&#nV z$VpsOo<^aHI_C at a|CNKK3;tx9#INz&h+e5h{`M~-?farM&s-tLpNrV3V-q*Rf1A7G z#zxt0`@7UXfyHJokgt>R;qGVNnn%24m*&SLry at Pj4xU@wE;&?*_|I;a7Q`f%B3%=c zRJ*iqn&eib-OV&Z*W7PeYMu(7nrh4{1`|=YhZy_BBsSPPNuJG0o|8(9gSgq}G at o1v zz*YgkRt`X!kgsSEmsu2k=MRN5V$@ zV8}kh7MSrHSo*-7Y-&tPksXiY2_BZiXd8NbwZ86|*D# zwvFmkWg-7pvdcb$wr5oKKr_4m!SL;KK|OmU*zp`2uRYjYVZ`iEv#Ag9`tFb^g}t_AEs!kB;V|)R+PVm{UbPDyE&F! z6zlq3TgDk`_%`dMxHV^YrV1)mZmmjlHz8xH7rH_v;2d6bC*Y$3!Wj#w|rBQJ<{Q3*;dxP;b!PG-OgtRzsoohD`g!v z)~N%C>hZ-f37+xPlxIubpHfFX5RN|(mSBGg^`|@wJ8y}}1>^csQ`>2mw3)n?q55EKpR-P#i6o z7%IRJ8$M03YHO&+RjCP(T9N3?#Y2pSazx$8Yz`xfS4&=gb;XE3jvHp7Z{mj};WLg)66UFwQSzjP at nta?BSeockI{@mvQYby=nU^qAmFQ8 at TyeDP-{OC;k0)m zGrZGT_!oee5n!2sFkt%@u at S~z3mufWjj+6pk^oYYUTC(v74?1V-iPgIw#y?>;+QQg z at Ou9emNGRJ8s`f}y(^%--M*={XT%$o at wpkF7xCv7eAe>kR(#s|^Fe%m%EcbS=R5rQ zFg{;lbA$P2&!1suC&d?jQ;vG$w at ig5J*@jeMHVf6q?KFxhgQNa- at dI?bM|PCo*tTO zR9GR!$8v&@akak`)m(((g zA2)*zOQ3hMK<{+9{NF7Wo$sD4e#Z6}Cl!mnV)5(6VysxC1Ssxl%H%(sEfRM59{K~_ zq8FQ?eEGwnSi8`QZvj=%WHzn_v&TIndGJ&ryCMd at qDGoZ@`sDX_UoDm)QwF5^-{B= zN_B0l*y<^~*0EE7m9pg(pk2nINAoQ&cVfyl{C3>#kVaXsSy^E3YzJ*3ir(eq5fx at p^bJQJ}qh90k zP*h&+PxbiYCxTMjaBj6 zx=|Bc-JLUMDW(u^gy{#75z-PN(2gfg{_OkJJ)Q3aW$%5(BHoj{L2JLFz9G)-6ZQ@} zbW6w8pqscnjPqVAdN|y;6!NEl3)ypIT+z{RVoJGXH-A(t{!OuXMdiPwX#8=D_17A#-%qT+65RXNMCY9+%%eK7u)l4|zjt%- zyZ!k8$_F6-t_6^HIKLP_ at 6xx!X_b8sWmOXC68BhT*Yf{YZeXjD_W;2AtP?c`iw`KW zrnX*;#P?l7D=_yyjI*93S*@lPb_Iu&?e+#A$&+kQaG#ym^r*}iuhS>Z&h3Mi=ZAz- zO|6^y{u5XBL=#cuBa-B5Y6Zrpnr-%)+E$22KN(5AX44xkWO%RHQp;_rpDd)i)#@dgEuB#M at Ah0y?6Z(k%L^dFNMx(E$_Cldp@=BEr7l}^3NDQTq z;3n&HPz^S}68FZ=e}w%V`gi5VGUuhadqL3CjKvs&e$fn{vsTxfF at vdoOk8Jagw#%& z&OyBa>Ou6I>-Q(`^H0g$=daM4ytnLlw%80;t$#t~4yybn8XCC=Rnu70K{Zb at 6Vy}= zs;Td7!EsLFd-NUu9+WHEUkpL-11h~CJ7M!DpxdA-rS9WXQL4L@>P9~tQ$^WhDoA-u zm4^t=&QDd8og0FqUl?^Gl$QbLK0>_ at gy;^z8!Ta$Sog;%d9NS;U%9Em7wmd~nHQ2| zr<1izL#AiCE&kAzn{Bxf_%*$eVymjh(EFwk-aOX`2L80IHTP*7oT`Stiha#Je%RNf zze1P#<`AAAg$N|^y)Y;!IDb2J(sqBz=-Z- at WzrCxyEtT8$lFSk*;?M4uw-YnU&)~FM|mHnybqT(`3 at qihcIm~_|xk#6jxDJPi>pn z(Xn|ulG-llxIpTyxNfskp4Y>(em0vt&U_&W#u5`oSGa%#IDx`Nb8t<+1Dk#8jT_>CLM?RFehCU^SV z`&x24{i%Ka(lhLRe)Gi4;POrI^nfp=P$nU%iJe!VO8X0nEnSS>6~d$O z-x#Kss5)aOn|cWNXV0|tvyWCYlzYez9Xle?=PeTD=`rjaQN#x=QjWe#5U9Y}w$w5E z2ABj(auP*EZLorbD*#tOsK5IVp4%;z#q at cnVgtSYL;?<0<ME_ldqOF5JTuZRXOuitbvYrme^Mz)Z@<310>Zq!ANz#h&X|W z8btK9zz9(LiQ0^k+U>{kNFWvrr?&Z*wc*}jT7FLbcL=yM|3fM6lnH at C>SgDaWkNH$ zklHP_*~#5BZ|(MT*#bKW-bNAI{0V2`l%mH^DY8+xj<%Nh`ft&nA4+JAuK<#y*SiXQ z>+j)P{{rs(L02YQ!BQ7282YT}(p{5ysogaRrWAx?GloZU*Q7#j*Cc9-sZW4_uu7NM zEmJ7ITc*8DdJpELwbq=p2XoThSQDyZv?lb$&ii(AOX=q4d&cfTRVZ6z^o9uQhT5up1*c+)i51hgIxE(_kFLhflm>$9Ns8_*sk zXmj~4etPP%0Sm?h28 at RY#uC8TFwu&*F@$IJH}i?v^pWJK|L0O})So=y zze36#@F$1yJM0$~6#P*8c;4AAv&sj3lekduBaq7b>t^6pir+M9koQwu`@+e at 5cB~} zHP|-D`*{M)`&mtLBoAH3!H5mN(W*8mN9Lg$iEICmgAiM5Ag0&pK0W zd#B-!6S+^x+1_as#Tye2hbWc4^zH{(KOw7{0YfNz1-MGyIL<3K-pdY+wr z^63H8ywzg-0vP{6mSyljWx$pyxO-9~?p|mqSkY8gr|1J5{jfDmHk^N$JX{(k3&Bf| zOvI0W2-%#9+G_ISbAICPqkiIU8ZeqdBL;TokNDvk{8{k(Hj7s`8oc at l@#=imWeAl# zYCK9{RYwabc)3gQcq(ONgSQt%7b%)R?hIioN8)DcHNPl_H;Ds0n*?DLoo3co{fU?TZNu4@{X5}> z{AIt(<^lx$O({TZ^(QRdV5suNaX2q zzz50z9}LN=`F+UDgc3t#HR|P{^5ua-NOqXH4u at pL{2ngPG!k-B9arIyC!^@uuD~qJ zf&D at qC7;^s*-($ug_C^Sy`Uv;+;wM^#1{>k((y%uY}d0ddAjVQiN$hZC at SUk?A$(s5h#4VDuo5C|v$17qsQ_SZTF&io7PnkfVfOiujj)4C zy3uB$>_Y0_k$l}x3F7CKagc)%_sdPOY5C6AX`lIlzil-8fqx*Q6i7z>z>ib%JaK_= z36^++wxMtMW%xhGcbvX?my%ny47;1%- at DSHUkYg>Z6x3BQ-djS>cbp779lA4}g+xpQ;GHf08!=ori|Y&yLh zQaxppiktsdnJzMMOQL5#X(;oCi7U3mtukg8g=&c3N^=wnQkHR>l4|70oq}aSf`PA_&$mHffrwIs5BQpDlW$ zWrS#>{wWcotu0g54x{|!p<>bdQ_(f&rd3;}n#rYAyQ*=&aXzx~<)3r%{l#MP13xX@ zMn`Fm-k#hCe1pS%d)hW}-?onH+Plu*wM3nenA=Lddi*@S`XA{)2Y28F6Z8jl1>40K z-x-$||6`r;>z(Sd-mwzR-kDEB ztGcw#wbj*#Y^F%jH?|-v5DzMYs`v7Wj`g5pY0Acm=+-<2s!Uf8(Us;>q*PfcQz%K{ zq;vAZ(=;$|kiH7tDmb8=Ol_GOL8%&74YR<13{mEdt9dx#94pmS-+I}0*mjPV`JwB zaJ|Ue;Qyr*`>cmHT2ZOb5OkmS%mMNwX at 1c>BQ at +X0;ij2Bo at h(Ilf4ihvHj!`}`Ke z3YB0n@}|W%6EI~wF+s+y6J>mx9*c#i{O6ZL*bq3;yDn0e~iR3x>* zpg*KaB0OAM59^|yNp+%6hCyJWuZ(A@^j+3EJ^rTOh9v3?9ep?KfhCU*tcspDE20`9 z$S~j~>_?lbTY}1>MHpHkYJ8=Np67~Aa>p6l&<{fjG5WK1i_wKXXNWdnQ2(xA`!QXL zW9lSRx}2YW{LeBWISZ;SzmmC|p^^E|a#XrnHF-J3Ta7Q8X_Fy)-!Qa8!32!~1k46s zt%{7NMaIXhjAxn~Hd*TOi%I4b$d;PU$~X^tWPyfrnWb?~qFp~cZkR-)%Fggkbg|fp zUacffH$aD`<23C?311Y?zTg)~bKrSDV-p)Uw$2ZW#+lHjXKj^PLXpVMnlY-uIyGhV z^!_=wmhkl$(L8LiQ!8)i5Uc-CHv^QIB2>{E(3(LDe=Kj2H`=z^zG!%Un)AV!UnTv4Mm+_##U!;#+8&dfi)HDf5MJeco!wWax&ypz7Y$r08aPbt z)46fDIVV}1$6#_kG28q=9+xBLo#Lz0N_UDM*^0i54m z6X5eZ&yeO6$LgP12ZK9}T6Rz^3sB4F6V-A$1eMCLS<7`+EuS=?mKXSxkRg9zK^`z5 zKSq$R0pv3i9gm+iPglhSeKDlEsy#AYM)fVLyH(MmSeK;kxkOnat z1HC>xOINbTTiI&{!Y5lbM^<kB1yy4g3$6JNxlwA7wQ^%f~)CF+XWb4%>C2Zf at 8Pl{s+(n!LrH zN8BwUj&C^UhRuWWUFSb2f4p>1etiJi4&iYpvd`nvB=>xCa$C6l&;_||;pEAt_62J0 zWK(i`xc%N>ZaZY3d0~YLMrk2>BwR;}O7f9#VyT>ZJ>338E6F@^d?~TQBVkH~^I@(9 z%HQ_XgW}`N0 at f5G?Y`19iqTvE_m6lP%-aLMV2Bi%7%q^^&l8EprP5 zvRlKZw#O|rFW(Y4kG99saD}$VvhWyfkL6bSVnANOkm0;=aUikHr^mPX=j+5v-A%L8Mtz?TOojjw9?(tCNpm>Qt)5r$ph6?{%}@d)c(M2o{?R4Hg-gK9!x53;Vpzpx9}XrxA3Xk0;QYx+pL;yqnd6D zP#1(6o}j8$1Tc**4rIklnp+V-;DiDlb at 4^bqeA%~N>of#dz)D;)rLHaR2)i+FQLQE zY8Xge5y-wXIX&NLe0N`vzQKu)CnvL!0pg7nNTca#gCP`C;`XnBY>hB2Ayb%ejlcUf zId?>cTuX7GF`r(BBq}xHU_c`b6^8?|q5OgQ5+uR3%58z{nKlY?#Zjf-sG5Su5v&&g zqY8|U(62XrycO at fR|+$2IFkmQ;U;VLFwK~{p(a9cLQTZ`b3%@0`IQO`JWXs0GhHi;R=$3GVn0D_7+i59y%?6V~s&Rn|gZF6LG;Uh~dLmRp z{5{jw!-?WsKw=$(uH)`uKGS_nEJgTI46qz7X_Iiy7!cS8>PdLCu ztHLUg`?oMUa76CP(-{mzGq24 at AohSwKCD!b=9JAY9*1G$| z`PbdS(z?3{lYb8{D6Qq|?v8MKUof{LoP0FgzCM_HG at N`a+}RUhb!dB{_uG;4yTP>S~l|1ve8^x z9y3D)7TYebJ#}HeG;?%Z-!w-rX0z?BBa&? z;2ALSZ>jY>mHwNYU0O$3bxMe at yam|_+i;37i2a+4qc!7%pAB(%x*$_Ek;>slUiTvAy0_6<_ef~BbPj2V zLFur+Y?bP_R;hkkrTPP)QOVVdmDop#eE}k3DRHxvxS0|+2U6?G64zOY>nL$u0G+i% z9 at klI83%gn&Wq$Zu&QnLTwBIvcHN&+e{Br3%UIkt2B4dTdE-sPz}zU+U7%%hv$0HW zw!{U>%-W6Xd1d_t-YfFTYH!Q9Qx^+!Fc)iXR%{#gHsh)UPsc7`y|))z5(=d$GG0)M zEZ`2RsNbll-&&711^{`HDiGA1>E~NSLzZ78*LSfyLaS9(_5YnBs?aus+Hd6-s?;FO z$q%t!4)Id{y3x83?C`a!_WiAmzVd9ZiEOW$+48LB*~bDypMhYXiJ++ewu|`6xCBGL zkYT)GS?W{ZN`ZPyQwYjB1Iju}+Hs417^CdDEv!1LJ#S%U9G*Hdb+pyAI4hJtiCr!d z%kpNdgvOe^*<4wPaP{rP6k9;T_?Cd2eK0V4xGeZ^VD?B^aBE=pXj$-~z#O)T1co at y z?a&mq(~<9)jaRbRiRF2_#eZm#5qXbs-hzm9lt~!|t{Z_XPQo2MQ780K*~>ch^Ge#M z^lFY?l2Oo(jYd0&8B8gnf2U{Lqe`!c zgVgfl%03&d?6Ztg*=aNdRrZ>rvez8-tev1xw^ehu(RW$DIV<{2Q$?(civ1f77En+a z(5uGG2BUWU0j;_K+6#qW92QVWM2Ie7ajZ|Yx(~T~Xg}UpQJ^m%B1He7fcHv<4d=qB z!da@}+!cIXRNyQDpBOe~Pav}{pldD|N?ccg%({Sx5RAYDc(S3Sja0%(C3-RGn+nS7 z!AfkavaIW$oV<)pl~qnz at q3*TvO at Cb=t at z)LO^vxK>afYAq3QZA)xjf!9Ee}GlJ_x zaGfE(K7?5o;_Ls$5b_v7CGoXih_C$=-*1Yq09eZa^B|QGxlTl`qsVoZp!*MFV6HJ> z)>XhX;`>B=39J#nPQ;fWPbe67Pbe5yL0gB&O;RtD``iL at mG`oVa!w#9SB({vtI+LL zhM at c?3mSg`k%|2cE~{_^Scgv@|=M at xX(2mxv*w`+n(O;H97&w_oywqDw^GN)Lo741LS6LWup4cC`8IKH7Rd z(K;pme4Cvgc2C36#${U|!X+-#c4ZDx>rPkdPFMDU=8ijEa at V&<^+b~ioVML!lg-LkAjj%fd=NF(v=;K&t*z;; z`0Z^?_u{vwHQj^XyIRwC;dh}sz0h4qG&3H}a{4}(UNH85N5)j&|Nn3G&sH+p(Xi at J z)Qg5&>Nh~7)+)`*t=SYAU9*TltwOUcvu@*}MjCOx3<7&;g_3R66b0~i;F-s&~ zD|gk>riNx>p_G}L7copkWQ_ at ezG#Fy5VqJ;Uy-nHY!hKjnsMLWNd!?C^aeLXBHd0>* z_d(f`^;wal>swG8(41Z0>iQP#xrDnOn4;C$pXf6IT$LuD30xuNo(UvY$Q>(WscVHy zTNjZvebfd+D(PJT%1g`AriiSkezpZ_h>4%+ at t?`rxe>Nr>=ePy`BTN!G=~z;yT!+p zj_2LB^xTLJ`wJW==Iq`En{&RUs=gKOoHlJQ at V{$ZE+|GY- zMreE)!cYMy4CM(^7j1y`fP&>$t4i$(rw0h@)Z^jw4oGNll%Q2*cLq8?3fnk5jPTsbTj|sbafSF(eAcCBKb&Hod+E4XqBNShbR1xjumYQ} zI1TGDlLl(5(MksJwnS zCDpKy3KZ&mJm)Ozx(qu=kU$~ErW9t^e0Em=JYOJQGnnh?78(3lCn<}1!+87M!6nec|G{bo((VQe5Qp>CKk+SBMx31!|EhOy@{_AoH%Lu8jS#F z9+Ci_mcc>~tkGv2w4%};sjN90^Fs3zbRZnNio*X!NwddLdk=GV3u7NL+5q z)MO9Fc4lg|8m%^cFy^Xt)f6;W!MO8`zt$pmzR+URRB%3Q8 at MT6us&^XU~7lAIqf~6 z_-9k)^gcVg7;}*@z0a<>TGQ`_H8(GC8DedI&XcMB=G56450yn%INERLl8C8hOYqg2 zC;Crp3BH;*yPJj)F$DhD>Cb?T&B9mXoljnKrD at em4PuPsQ#*NxxOj- zAO^?_id}Ql*tn0T+CedZ)YR1MXW`USrO at Z$)FBaKR!KY&$ag*w;LQf^a%p(MX=NBM zPMT?1pdQDxZFRbr1*8+B^mZe^FW`~=)>>a&?X>U_o8#FM9tJ8NkX)c#5y|zhn(tj$V?h#R|DT+~d z3e<-L>a;6;jHXkd#&f-i^AyI}Y!EzKhVhtVpGjkg>!7idY3#CSJc{wB6fisrCXq9W zNo8WC)6zIHs|aQH*+FZS2uV~1gAuMOfIUbo8)^13jx5p|DUaBdX@ zeVuQr|6EL?8HLtlOX-6Z;-3i=vO_Umz!kz^x=cWTPS9Xy3dTt)$7;>6VHvIHQP z5P)dy#%f>9SyhYyNIxpX4d~6g5b^$X=SiC`8kXrc9v;h9*JiRv(NF&=l;}sxZY at x!icdV9b`H zLcnvCR|xanL^ebD9=gHkgw1E6Bl{*V%vYLSkQgFP9}39jWWRJ=ckZF7Y_%)7+?6!W1|mqGCT|WX||rAI)i;rgrPsBl~^xAOK at 4E|FWXdc(_IYZg6!) z+F&}5W_3K0)f7zYK!eqT9t$)&MC4-uroeWlz}sQdBwgOPSle$4=Jo~>1ta(b1y=y^ zcfu3c!Ff0AaHx*oP~KcJ1Uqx$>8!bR%k0BZ`vcv9*FUUALfcC>zDaN4PebkeHx80!3x>%5RrNWBEm4+vQ;^^%8QmVPuY!^X-C zr!0W40^rACXbaM`MVa;Z+ at RcLA7QubsTTr?=ZG at T1!O`qB%HHTro53UA8I^OEKY%; zoiLsP#SAy-B{-uH(%kt%=LZp)VhcuWA=LP$;TAM(_o$9x_WLy|oSi1ErM~o5!Jetk z8}%n%C*ZG>tRd4h|2p9!zFlm(Pjki>MJ0aH<}$u`UTF9qMDLdZnp+ at 1O9}f8Vh5_P z1!zWmH4wk&BHNrXQEMp*#}-z^nu~0}kClyI4d7E#iD71qGP56!0W?W=0#lOl+`@xB zZT9wh%omC-eAoS~SmanX;AUVw%xN5BU_ZjOz)2A*VZFCzG_$O>yjhtp>Vjrl!F*cv zS=jDU9bJY;72{YgQSbeLD1R+VqADTXaoSu7Ed+ zF`fT5P*VsT2myn0M+}2=N8b^^}hhCqn`Lt1FLlct1oDW=%{{EHI0i_S`X)ng+&^J`Tkj^0l^B* zG$s~YZNu>D3bJ|J(^F(@#!4}%R|52pI&=wPmOdP#(Zp{I4#)6Rvrh0CYfTI0>o#r- z%3_)Su39ItrP^P{oWf5&`R$2e-r45|{37^oueJrFkD3GmQi?IEM)4Q+TQ zIE`wC)(=N65iX22)*oi1F#pN@#u5ITfn~z7e&`%{B##B6a_(3F^rm*iUx-MeA1X*{ zT4-c+N`zBNV|h#@Qxi9}CJ~hq5&3n8xtLHpUglbfXeLbtN;L7_X|+jeyb||=0$o}q zXnK#Ip>>45-V9*oAe>K#ce_{1-d{=*o`IP8Cuq4rlhzqR<(z1lcn_K03+!w|uJ<^X z(Sxv2f%&y!>1drni--4$v&9LZPnjVvRsW#wP_e8iBW#y(lx;9R!w*f4u)W4JJSLu2 z%EyGw8XDur1oepV5?S;Op2U1W6av>ZKETZ=^+)MkoejeJHoK-&rd2?L#gD_;dT zyzaak=zN#5I&t5QOG>wqn_+8;aeSxInz^Cw=`x8`26fH|icfLezI8mCP( zjA1q6y+-0iNx&f9&sQ-1__YeoJ&5=7JO)am!5k)HA{wa}5*P!t#iKzWY=mPRs4fNP zs8nYGg8+B(71DBdNtVo)jo+HGC=A}ZzbY1!CT}bu3gMMOr1avgJ5u&OY+gjERUJ<% zrcZLi>W1ACm}Q(0{0^QgGgYk_H;KoP>P%tzcOAczkjio=q{$JM&NYh26UO>QGiN-5w#Ac3cl;cocs5k810p^E=j zm%rx}Wv7UDnxV$YvSflF3JHrw0Q4(ow%fzzQx*hi5}x6GjtzTrf{-zI;a at 3l!Ho@` zAJ%^lzpbWi^Z0WyiIMr0i&a%W#Uk$N{4^k^<_Fma(1Kw4Xe?Re_*^j{a>&TRsQJ9c zQ%^e{+Fot8Aebwxp>uLNFqd zmP=EmSeB-)<$4x!YvK!OR3?pWM#Ha-wkp_+o88FvG%T{$n?yyCYN}jNu0$X+o}pKe zs+vg!BH;h3$+SadI>>8n_uDqw at E*K+?Q|N89?$bJiP-Ulm{e6Iy@STau z*Kss}JNH{7IM?|mrSJB{cYBBrM_NZ=)ySPEjx$ai^4Rjs87(ZUdqeE1mqtovr3pO!Lj12& zw{#^(3;Blc!;BZK!4$+a^dTQ0=V6&AZ^rP%L~|7i=860Gl7YT|c{DZ+2V1xET;io^ zam*~WG6G>&I44PpGY$IRSrZ_g|trNTQ6B;Od3W?x1}Tw at Z%6ukoUAM?|5??Z+%hhV7Ogh@ zXF$VCxEKLdhLrL| zh?V<333PrEkW8-ie#a(hRVC@`Mkr61f{elOty4Hhho%6WJMme7!XG!pKMTm|j~gI+ zT*6LwSok=~2Zv6cu-rUhX->>jUq_=OCqnUl}D`2{} zSVX at A$YtYz(0H^{nhoEg~h<(T5FUP^(o`Ctj zEc&}kJbd|=8VFySt2cdRuEuGbRyJo&Db1PXZQp0M-2#9Yff2`K)v?9Fwl_`ww}bzm zx(HOlZIcGWSl0$Ko^W at jhW@V&b!9V?I0r2bnL7HvUN89Gwn^STn}k9Qv}$!{8ojL` zO{3P()ZLb;(R~-POrX_(PH)6g*9I3dSiYZVY{2U1YIHSd9?SYR5mD&LCRWM?I0TEo z8I$72V^UP^W3|$S!2%Zc)KeEN%--j*AZi}NI=1GC-x8FT)&IU!Gc9($i+Rt0Qryf` zF;M?Cesaabxi!6Jx`xQ?Te0+9p;)z$3Y#@?8QQG=8RUrgiI{{fa)k(2Ee1cyT2O2v zzNR1L3OP+(?bfQf&mNO2DD}U~B}&!mzMsg9Z!Lz|Qy1Y*-)bH0YsWOsC&RGbTvsZ) z5FKL{{OS$;HIlen(cap!_7Vm#GOc+l_32P#_TV{{-+6b>IFm-9HDkJ%^i;BlsRE*&ai|*F>#M|ZP6HnuO;x-KE zyC*t-xQBNHQz&v-(6kP=EC^!{HwQa!4njF#t}C%L*twJ~RNTv(6d0|T>x#b&##YEM}}{3HGF1I(pMG3MCO|!kI07q1+sP{UpA{0b3nJ?L|R~%V*z>buJ3ZJm(!J z*=0$Ped6d{S$<@a75=~CS-C5lTM|tEKA5_xG5%sm%Kbi=ydfBsayJAkK>rCqUuS|& zQT`i(&_L+vye?=<+z`aA at eRS9Y{$isz9HyxxKyru9|^!W62QjrSYA`ageta7nqo|u z_i^V!*}u=Dr}la}`#k8{zKBXBxyNv>2YQUPVT_mb%YracbS7fjFdj_|PAhZ_PLu6A zX1ir#y36>s6-(NoAB zOuxk9VCQ0Hvil; z#SUFlb(%P_F{Q1!%3E`lx8{lKX}j8aJ)4Euz=J*uEQc}fxo`M-8uv8AxVPj%A)19j zfzMyQ8=IyN3=?TCe9`34=1Pi>cZkAI5;uCpwbB z&UiC-%C~q*?aB`Gu%0yn^&oMMFbTdfiEc!+cVpBDoE_vRza_TSTO!b4@^N84d8KLj zeVUdNOCv+ at yGvQNau!V4H&I*qU`y{oK5}V=JK$j3aQ4O^o6^5A2qXF2;#%AZ<0E%t z5M~27{{^M|1Eu_w{ICV?CM4e^z}+Ok-DHBpNA4zod&B}~0aGX9 zZl-hIoK4Eud>e1%hU{Y{R}xU&^nVpeq#0TA?k2*vpNS at 58B39S7GNGjHt!GR2@{;K z9}a$^EY0W0;&nh;Q{o at QB*ZD4`J_}H$N7wuXQZ*Yos5N2s#Ld+v})C5-cCo&pzwD3j2RT(P7j+w;qCN at 85G`5hs~hyb~-xA2#)vKSMC45 zi$&+)+2YZ&#j|INUn>^BQ7ry-vG@z=xe9O1A3c{`wZjsNJ}MWB z4nQor7Tow)1olT7%PpVmhRf^SFmv*FL~&Kuej-~gRTl^i58*DA?)Gf!?b@=T}r`a at 5+ip`uL|LJV>k+AW$Ho z{8X&f#5O8ZXvz#p`HXd6K95OCRyr*_lYYSp2|FWrGJ73SCV5-%3MqG6Fu4}LYlF!H z_&pFz4&iqwm^_5vL&4-W{B8>-*98+ZJ7!Aphrc1^)&-Nhji64!-NEE0or%dH6NxfQ|W-N7lj zyMrZ at D7&mecc9Sjh-nh#4$6NA7-Wx?V-0fbwQ{VX9BYtcpOs at Xay${iGv$Aw9ET~# z;b3xeP!Bb*1S_+Jh8vak>!aKBj3CMPWoI>*4OVV>M(G>coPXMa9 z9G7S!Nbo1Y3x^eg(v_7HRbE(;xdui_WI(5*2-S%J1?Z4LDyq$3E8d^vNjXh zu1nHg2C`jTaFU5^m#E)BwyV5~u5rkU3~UmNMighY zXNhX;|7MQR|FkcgDJ*Ic$ofH&xp3E)*boc}Tsk+WG48*v;y42RQheis82Xf+Tsruj8jAlzM%Zv1cx+|PS6JeeZyzYN>^ z3EP>)qWE`9u}I-TSvRKXpVH=Bya>_f;_Y at I5=JEoz;ek&VzS*YSh^S z+iAOSO|8av1#QUA*Uv;tic`4fksNiJ$$0tjf at rH)^jA<@HwCHU^j;5rz=M at 3K9NMG zlR%byB1j38H}^y^(I4#W=d+q4)>OO)g`SNV-hC;kL*z-)Z*I@{DgG$0MDRrRM3CRj zms38m7 zJ;8sBOxuH<+bPp(%0xslWavu7Qg;XI->3e*JJ?ET=CLazQK$kEP{>3D4C at ua^t?+_ z2Sfxy5I+l1=j2ghNL!vhNCKt!x=y%c4YH83KRCTUJ^zx#J*u at M!0Sd1iXDL!6gvVd zsJEp0IEWWAC2%B>&A-ItjIXlK5J3Tzf~i$ObS2TE6%zV`!OjPRQtIho{l8G;(-cYI zXI4mC-G;Q)T_NIi8zNqJ$4nleiRD3F7-?#r5?>xl5XwrdA=?K4d9l$qlh!S^X)4QdqMpBR>*6a;QDt)n8a*a$e1k6-{k z5={2u7k at G?$ja{2Kt0RE1NEAVdUOHi)^?dNNGyYC!exrw6~NKjJbUVqAS**Z64VgP z@$Y~w)hnWVMUY^_?HjSd>SB>#v>$E#4*^L&X)ENTj at 3O>BI;b-qbY{RW)eqNQ+AGH z+j%g%K5Q4+a>KA^E|PP%;AG*Y2&~wR at WVUYd4AurS@$2HVqtd1NvN~wb+!iQTqErM z3d+J3hNhOqQga-$GcHUTRH`?~Ce!wp1qdg%x0gp+rixd58fuywD at 7Ik0k-ge1SJH9 z8eYBv}!(Qfi2phG60xfDEfusJX|Vg%oB?&HB$F`<`|Kci$# z%+a5TIodv2fK}KboB^*5*8jEfQsPeyGAV>O&6IHVXa;%Ko5L$oc}yX_ at Dd!nQX6&^ zi^7T-Ogls at YpLr=`X8uI9S-IO>S4?w0(+A$%HpL4xJE6;@Md+$_+Si; zjx6wsKgPycTLx!4n#1Mj`aX#PaK8=L0IS&vcfDcq%<@#gchp!~I7V;VIgGUh?-&{{ zg!GWd%#s&20fgu2%J-mF;}cixOVAtLh> zS!JHxC3=Fej}-3-+Gb^{69XbNfRM-7F}G!EAW&&leL at 1$Y9MN9?!GyhT09ZI}riFTi=ZtQSQXEQ!h7AjNxe*R=&r35mxJke=u!12+n+iND$+Lr|`$3nQq$fLkD# zm|PL$H_TSmtr%zZQ(!2!KC4Im-{!sqO3Lb5x8|WIdJ+ at l|LN+#>>{M}@-vfn- at 9?R zorL73?@QkGx!zh;)L>W28v|K1a5JoX7c>Hn7)4rf2n`|;kd{dh0U3-6%FtCscOxZ-4uo|F6GVP<+0()>{j@&ba at 5&e><5v-de?ZxCLh8dQ_0kXK9-RgLSY zUFG<@T*?Tqk&N&o;!=Ac1nyCyIRcl?DGf<4sPHv~$SY9|l0L^IP2ebUEo+P5g3q7vJ_A}JA01ZcldCH4!nU2s;_0 at V;|5i(2K?tZA8iBrN9NQg`ooehhLzgfpP+|6DM~qI$&QbvUOZ_V5(3OW0u( zQrmya=b>xxOI<@r8oG4M24n8|KypMj-(<3(5en531^r#MFu-Uxez3I`GEqURe7fF6 z>oN>8PRe8 at yq+ASPc*xQEd;R>PplE`EtPDd1uDcd%nw_bbx}na#?Ay?i{VX8TwDm= zfe0<~9J~H^PDDZ3L8XTg8rTCzRoS3Z`d`Zi-Db#VhDt;oRn!^OgY(co*E{^BWk=zK zme?Fg&BOtr=E;DlXtARoFJ5I5ZEd3jZxGbxrC%FPRPj#Dznzm8isaI3&x$VgUQwMB z`EBgCsbho2{nkG1w?mS?TzrNfm+&k4 at kZ-8HFZ1?pT!r8`1PWhe3X-+0Hfu*Q2Gcb z*NEh at QX}~ol5uYAlZTw|*E at uIKJuH`Z&F7Fjr)y#+HbnEu}w;RW1}Me+JN32;+kpz<8nB36lhFKHKEn zGMVs at NSLly$b|pkgcpWjYe$t=7&TK+5)p7HO>#+^0||eTipXE2BJvlhi2PSj5&5s{ z5DKly`2i&!+Tr}ne7-H8r#M?#{D8&qMRs$E&Q$T~UZ@)^xt3XS#=?z#xv zo{+|NDCqO(8rr?AQKFlaOnQ@&4b}dbRWOt*PEI&T0BP=zn`DPZ)krY|(6mn1BRiBK zJ*gdv^m)8PfzRX0#RP4Kyq3b69Rn(Fc1)m^KPOCfKzX}EiLFs!2$&GFsDjdk7)jA< z6j3c>DN#1KpgXc86K0?L+V}dTOxNKkk-1!;QzkNt>ln;-yb`YL4c}kX-gIy<-_!Vr zcc$MFZkt2A!CPgV{Hzp9g_!~sv#>GZ$U>Qy5zl?jvrsVGWe5e6um$R=e4jQkka-Lc zIRUf-yER%^_*~vhpRSi9*+I!QMadN|5OC6!EkG#aC)7drB0Ty2FHl~arDc|vv+5{I ze&14LC7T6v1xvZiYnWW$gk(Ya^XXjD`kcNIaeO_i3nCv0emh%L!zpLmjf4V4c(^l_$Tx?U at DJq zrN0%X@)%shYbuYyHTuh#i at 8RrjJZv&;bE>plG%0h at _A2JZ=U`g=*@p8pa1Xq{J-Y& zl!_Tm6j8uRUbM|;#!MS>TES at f;w^a>@lzSa=DC}w!h<`xwD%Rs36EUw zGYJoGf8E%Z^oX64yRdU|MKZ*g8#9XCkb8GaArspSmaZck6d04;pg3?{mUdEz$&FOD z|NSJv3wa>v1uQRMS#@gEstY`)?&j479zy;rt;3zB7+7^c2y_>vKu03FA<*?0#UkDu zOLf>nkyKZx%+g(dmhLQxB;_4bA>|d)Sla6^q&-W?BB{@kZt7eM;%XnXg-1V^u|c23 z`VaGKR;7rM*btwC-nhtogZ5FDEH(vu(AowTjyns&F3)(AiY`3kap_|R6R at ULzP!u^+2SaW|BxP*7%_z}pGfSMWgQ_7H^j4Jd z&%zcH&i+EFb9H#nP1)@USGg$IUMToNq2Ot)_F6vouwgF?DbOK5d|#o^8Ljr4vJi8z z7TzU_79_bRTBVLWlUf);SmCg!y>}4pGRrIpnL+HPnJPDMWh#HI%)WlJeYxDqBDT at K zTrAxMSW>bmG_(VPVy5Xm&6b5{2xAgU5!Mq~LiGgK-SYZ5oR2b at -pd-BN!^!+a0fvs*2c at jjaUvk@HX*kA+|?Gssv-A zOw6r4_}f-J5FI7X?uqea!FQ at Ge8-B?r}eIi(z5#`{g+$xwA~hMY){ciN9sPE7JVdn zijv&LNhG(j?$i at eGpRR18DaW+no8VoS3Pk z^aY``gw@;?d^ZA&+Lv2M(3w}aBuiMSl3hrpWlAYhN`oF)XOXcIBUS=|vDBjWErTR# zCP*DARKd&nbM)ad;$?EI-MAvkuPFEyrY^HT^c=35VfSjf)d%0QK#j* ztgraWL;YiOsFUSX8(-duzsu#jUP_|R?!@2a%-41*e^&+?AYO$?ZGulu-IPPh*Kx zJe2q0L$G at W{#4#z;YgJO?Y3faq2di-_ at SxEicD&O6 at m;sMX<&YN3VbxZRmMQ6D3#( zan at a)DCgS(wY-br9NNb4XK_4Sxxy5DsQSMP8O2UB at p^HzA0$Q&P06<`f`sL4UwG>@ zneAmhU^Ghwn_dv&{m11o)UFDU!Lh%>)M+S8oqk09m<|1Siw3n%nML1orD+PM&-I2& zj)(Lwbjh)&;F9COH{lYRUl?6uy-ItjF8n}c`cfTyL-R%Ve!@4$51gl-fM`RY>3VBkK3=fNNAd-;Q}wRf5+IA(!bmH;`;7>Xy2!6 at QxsD0BN}D zUP0e~2luevQG~~Y85&+$45l5}JG8-iSa0pl##*%jHE>IMU8b{fS-{cSjl699VIAV~ z-CQ9|>mjAC6_T8#DAyJi-DD-$OPHw{7mXibJlo3f|MpH`dLNW9voyHgxY^n}ZwA6! z?CCcH(YFHOO-lN$Ky*hS{F0L15s2;#gg>*VcLt(w1j6f-^c#Wb>w)k=NBVVKOP;O4 zNNo*)I)a8CQG^-S!|0HEG`G*~`>9buatskypgNUrL8Fr*SW zp+t@;jmH!c*BehL4v`D7Bz9OyJ*r!8A2;;9eLPPGOw0A-xw?7%IA>lzHh{JL-vHJw zT86vPjgRTJ7ICwgc6?#jm{L-Y>s8oizce^}I0Sg@;Ul4TWX#js!$(8yO)m|$<4zLS zvR7!L>@ivPc%kfxP&+=t6(YwKzKlH|C_0*sD-PM6Y+Pk7r+(k&dP29kN^DQ+HoMFA zln&c%PwO^k%66~j^tyfb*rmbSLrT at Z5i2-^Q9Ps&YcZt5=dRqU+*t3tCmlBm&B9-n)d%URV;J>~K_vpxs9Cuqa=n;`Rpl?EzEVfdle zTzO~S=CY8(wsM-}H+0lee?72us$2{;76-?KS>&ttfN#xrHRUvv6h?#b9mJjv#t zQzW#{6edG5tw~?y52dj%jq+g+tzDm%f{_j}3f~ZN2~EG$LY=B*@m6TA*t`(^oeK3K z(P2JsRR>{32*N42=JL4QL-`38YaMAG3F~BpvpTaUo6F<~Gy%k9IVmO!nA(Gy;i7es zE=)&f97_5iSjHT!;MV9NW-SASL8@~~CVftczKfOWT_tixX*$Da_P^7dgv4Lq3v?#V zJn>K~GRHn{j@>qGj=d}Lk<#=Lt%Gg{v}B(ssqR30mnRc`HyA%xDxp-aH!?FKb8ELiu#?uN9AGT%w$Vnx3N at +X^ zDYT0B9jBD=C$!&&y~U4}#*bxLyuza#e(X~BeZYM^L<#NTvmpU|FBeFf zF_xbN%%R0tZVZ`2i?RG;&>Y(TX&o&WkEJFhWOz=2jboy}YgJ z*516J at es#VDk zoZ}Cm__TVsP%lQZP`+B0|A@=u&J8~oy;AY26M1gPHnp}9IBZkD6o;#V*P|{k at Q6QL zzr6Y`e*0H|j`WM#@?u6(apiUyIGn#a93uU)I9Kn9v<=NQwhgr#cZBEE=Wxj0#?}LJ zJWPQ4F>%uH1;L)!wwva5y4>&xeoBd*QzGx;RVw}AXRPn?v((1FHQ0xArB5jtA6_;+ z%0dU*T2PwaCv at IZn%<)y5u*iio(8vn7jX_vt^F)ed&*|cdEC~6jO({_&Xa+ at GG*iT9%D)AdCDKLy|d$Nofe?7!z}-9;cX za#9b#T(f7cVE)2>-Nt<0q3837d|r61Xwx7FDxGZX&A>hK0gu at SG-hYG)MLEWz+0nm z77AB!i*HNF3{tNc!B;}?$MCoz_he{dNM8+^@Ml^F?A2)wF^ZYSSvS~-QcfxvxqLxV z&W8W0$r{=4bJwHB6Pk;lRnaIeRWq at 7G4|t at s!kG-pEUPM&&a(}mi9^uuY<)m%UzCd(O zAm~c(2}FAWK~K6T5bX>Ez3I+Cbax==OYaUu_XdLg^xiRSsE{L0p zwIUCM&ze|0E3w)~SbYM7muQ9!)DF$Cf!eFo-QML$&troJ+GTuFuGE~A7@~gIAAo0o z=-YwV$w2B9wI{VikG~y=?q?^J`vX(}1_G$O%Y750Ln-WO=VHI`HjPbQ#NH0TBM**P zD9EmDV$)PO^Agd0TI?Q<(wA48BQRaXYkJL9<8r at 7i~3-8PhB^yKQ7zdn5?FJR+i;b zyF3h%2(y%p_i=oWCU2$0?pCF$HJ4f&np(w=-KMJz>FH``CO%t@&Qc?7_DptMo86I~ zrDjrh*Qa(5jm=Ww%P`@px(R+`>%&{V(Ri%hKCSKUmMEh8LD3CBd4 at E3>#?v5AhMXJ=6mO)FJEKiiN*0e&_DuN~f^b?-*68)qj&VInA zAAHBT+vAU`A~wU3 at U+M0ajcyFq#jon!f`4^D-fMW(HV#$(&~smp~~?At4A^s8Cochd()>iU?d1WTo^amZ(wU#F|UaQYiS;IyH9-j}D*NdDpOyms4M*YEo zsq?goiY0&`-mkXtsVb$v9GKa~RS!b`TgN0jP*g2tps9o2ne1qV3LhFYlL&b|!~ZV( zO+sCGk%-M!*=JQ(MpE1^%}zf-ThrSPJJDqw(PMNr{ScK-S3^XxFx6o4q`QfT??%!F zOHxQAy=*ds=OjbuCWdec3}K^&*Y$lS{3Z<#mj9V2aAzic$lIAsANI=8hwmnFQZz?p zf0j8hX&*FV-%6bi#M*cYBl2+|_B&%fZ`N>UcMR at h=bg6L(?J1fzABz-1Y at PC?V;FQ zjz&&98&5lJxD69I?`S&D&yrrzJkHXxl~}hQP^aD4Wu1)Ov4dVQ9%q|?u z$-Ip+?=v$mWab?jB=SBr^GXr at s}ar6Cu at eo@P?X6KcL1QQ)4TAsn-V8z?}c5PKr0Z zJ-W z^46)AQ%OoXsfmN3=u`Cdr_ at NRJ=SVZJw0e#DNwR#802D)sx(h&f^q1X5Y4T~00i0Dui+|`l4M-C3(e+)1Kn&+ z_|K)>Y*UA3kK~o6JnyfzYIqk0CpnL(nWjfntS#FvleV4HuEojbd_NID4Z9YeL-}(! z|5eT55tq2A!5P_B66OT{xH%}AO~2f3`sIcvT+($f0^D>2xa|qI*=P~$+vJ;RaV^(|6S z08&Am8BamLl`r^UP2Uf9d?g9L at H|mkc>`2c{zPeNnGW}RC7sy#VkwRExDq9eJubUA z>V~Vl(rw97n2Y8u%WxleMlte?wqt8mXCYH$bwR3P%#n0T>;dQc_mX?l=J_H_+! zy)y2?M8}n(=T%(m+*gi!ofP(&LsHC6;myy&6_>aj7Vb)!1 at 1bnzKCIRNrTxU4@(~H zMdters|Q9CmbX=}f at w_VACG@6{sBWvS3j3xMv9zGSz|I~jk%O?iTldTPnPQrgRfNf z8G-`C{t>n0d9`!g^QxV?6$YNz!R5oef+RdKg1s-|KhS z;TF at wE->d1tbOI3&=E3p=UqnXI+ajrlk|YTBQd)$HuLu>N zM8(&c61ezck2z8w+kp-TA9nyFp9IHFIprA5ztR{*2E`0#8bY5v}t(u-!3ezUE z;qv+*BOHM6v5Ro@`ZSdL at xoGjWUZQMTuVc;R;BJ+pvD)d*;u<8Z%2D}178m=m%d=) z``w|j-j}m6Kyk4eUrZ=2Qsay0=UFxWEd4A|<4b@>)1AqkxtdEaN0oOp!^-6{NQwu|=yF64nPvQ$xhAJ-?SRChI$Yb$X7Vwa~oyk<~VYOsDq`{2Q; zi-D}vVWm0#R~dIy-cIq-)M^=bvMTzmx~x=8e}_wdDs$aXMYH4HtW- at yKek(z_L}-J zAM3|_KF4(v at _FB--n{kSu)O>?fpuZ;k>KI=&38g{A=VsSXS9?8e=46guB*@!a=|Th zz|`tx)L`kQBa>~nEQ&k{`!K?ioe4GwgM)?O)D{~CcDoOe-SsxPBO8> zkm7uQMkJl$q~T2N+iUEiUS!z~62P(>rb at NYi>j~%MITtFin^!GsrQmO^=_h{8vMWg zu$X#lG0V^0_WwS-k;-!Y>@_2nQ?*7!v`ZRk_4p!X7oq4vD3J0ef^w_E)Y+s#X(ssHV zN8 at -3L-bSSgaUXfq~Tm-)sRef+^QiC_G?BmLHuP5%tz+HY`o0C91#Pv9%FF+as%^m zpMklcIVrx;Zj4MO_U$#s1zD2DzYV=l^%H{BSM;6WYx*Xv?3>WhH(_<(gjJGBz`E56 zHS+CD7ny19;Tk6BAGpu`X z&)}qMaMC?E=^32#4o>=*z+xM{F!3lz{}nyqkBr9takTv=`?q}lLi1o99f+9CYwO#* zHonwJ$gNZtPGQX!V>u;ltyMiy^qHvAD(^}YvqZ331i=wzJ5yUEpMX)YHL5k88V(H& zr4~RzAR|;M)?V~+QKqBNh7PsR-)(*STeN|Cah0VFn+c4ymeGb)U($wqircVSwWiYu zaT`{v1yJ0E4t%`5=;N!+=}Hakz%-@)SY0$-QFN;w`{C?qKkUj>uj4EAE*kV~YPD}l zPbM|PnN6+rEu4~cWmBtsQsJ+^fgpP`Cr0v2+oZ^7`8se|&k{AC`FVN&4i6$XR zKzxt={hI3A?Y8L402Kbf*Z7)lPg8{cwQBHaB$6r8^t$edZsB6*L@|nNu@*aL7Td(d z&Wd6b*<>wt)-1M_i+w1HQDm#N*oS7Z&0Or^5-vuO&DLTMml(xf;{ zm}nW4Eo_E;i- at riWgZ6@CV7Q>^$k7EfrK}{O&3itt8Fc=wq1vIXd74Cp{F^J at Wr=- zjeerJd|uyiR!IJ^o7hGX(;#NibV((ip|;73z1wMtd|ZH)y^47P8AipeL`Dp0XBt zqD7yfiQ-K=sY|I1a|c1z_)pE{^7 at uJL|efKdnC=hspqV0(D^w6ds*et=}S;sL~>sO z+9Z0~ViusOg|pyTazOjsK_S2~&PL`PX+fk00Osi$wDs z!Zeu8arzEs4=!0+xS~2#eeos z at d@#tHBb5?Fu(ZK_?DmFIiY2Ia>}HZiIYUNiBp@$1K8-{BgPCJWxM8ULHp626MinB z4IQhG8fo)FPbMzZn^P)H2-SykM2w~Pl`{zbK->YsfiON>$ZW^==kL3#ygwu zy5oz!Hu0Cu6Hu^eYV)M=!-kI;K2{$yW`xaQ8-EM;!T&+8-8`k`*P`-xoknxyu(4x? zjv%VIs+Qk8DLx^YHac$jh!JB(4IMpVgzXz&YY>0Z{IAW)$>a60BS#G%Gi=PrQMQt+ zL+7W>zl>AA5||M~hmIUGV${eHwz97I+jng?+y4gIbd>aLw_9ES diff --git a/src/public/.htaccess b/src/public/.htaccess deleted file mode 100644 index d3c9983..0000000 --- a/src/public/.htaccess +++ /dev/null @@ -1,40 +0,0 @@ -# General Apache options -AddHandler fastcgi-script .fcgi -AddHandler cgi-script .cgi -Options +FollowSymLinks +ExecCGI - -# If you don't want Rails to look in certain directories, -# use the following rewrite rules so that Apache won't rewrite certain requests -# -# Example: -# RewriteCond %{REQUEST_URI} ^/notrails.* -# RewriteRule .* - [L] - -# Redirect all requests not available on the filesystem to Rails -# By default the cgi dispatcher is used which is very slow -# -# For better performance replace the dispatcher with the fastcgi one -# -# Example: -# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] -RewriteEngine On - -# If your Rails application is accessed via an Alias directive, -# then you MUST also set the RewriteBase in this htaccess file. -# -# Example: -# Alias /myrailsapp /path/to/myrailsapp/public -# RewriteBase /myrailsapp - -RewriteRule ^$ index.html [QSA] -RewriteRule ^([^.]+)$ $1.html [QSA] -RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^(.*)$ dispatch.cgi [QSA,L] - -# In case Rails experiences terminal errors -# Instead of displaying this message you can supply a file here which will be rendered instead -# -# Example: -# ErrorDocument 500 /500.html - -ErrorDocument 500 "

      7|A82;dQx6tgBS837dLiTXtM`cSBpz#?}T!Oxa=q&6J0taU(Q(KFa!=a#$qTB5H&JBFurOp=gfO`^ zFexG-eF+6J0e2ALFrV8o^qaz-LL>DW@!#So{*6~9v;#Hcy_03_Q+TbYkZmV7v91L| zaE)m9GDLW0+mB@=D8}ogX3hB#X5b{EwsnZkqbSRi!em5=M}_$UMZgt{MlswV6L3); z_O{bYw%O(h9K~xZ^D+2aaiEmj3)}0hTKhtj&Jq+k4FNLIY>(D-t_DwVjII-GkBeS~ z-3w2C3aNu)svBZT#BtAl*peLB5JV3SQRk7AFM}OOceReG09akpBRWASKPUxGnLJ{XY5 at VX z#3jeMFe}=S(F6EJVR46v;5onGI#!fWWH1OsVNzO at l^ka=7J&mKIXM)Q_I8)B5qrLd z3OlteN)BTsPGhXg78Ur6kaiW?$XRGH)_V$dCx{!aKIZE5DHP;4h*gAv9;)Gi!~DcpNpvC_xMPc+xsbZtB{6%Zs3z7VPT$z-RbAh(krq zbjKwm^OX;gFlFQ--$~Q*qg@`K>_DxlE?w*-KuwUqndndiZnh18?X(1xF&Y{#ZY6<< z&=#C9t9CAB=L}z`bXbBIP_O^a<`VuLv zF?%|A^vIEGEN?Z)%O=+sC*mm!B<1UU=c_c)-xr;vF_6+$5fS2Tw6rZ`vrHKz4yw;n zR9uTmD>)t^6s5vRAww^|B0 at 3Gk#f=}xQ4>ku+D|3l?X_ww5SSa`HlEV>q4v{u>q%)gsl^@X?@qT*t!xL9`jy3ge5V}xF$&*B>MtMrTv;}Y*imKDf1t)KU*z^{uAV5RLnlfNKH3*a5>2Q?q^WfZtdRPPN9#aOKmBD8%7kn)Qf8xCPKs!pi*2 at HW{h6+(?fFUq|2`kXzSeZ(ZWk=PqiJ zuN)O+S~J=-@=5w;n(cb0+w~$Mb#|{61^@^@>{|!JMA|&VKW7Ubqo&#?$f`Vq4y5Fo z?i>-B>W-vP1nW+-b-;hK6yAo#)$&+~%xgy5ogR5PZ;Y=IHU=m_5$S!!!@qmv at rS2l zp8dYQi>l8hafp4S(q at CTirbCe@r3b0SPO=-91=i~(#3YI1>JHtO`W1f+w6gzxb?DXbv3{!1s)0l#BXpl^bx6$VS$A?|AHD;VN&W+sZJ}iU; z{a%bzR7uJ^!iH4Npq{7B*hs5Y#C!O1ydkl7&C*P at a(k&-781?~q>kG^vhC>CWOtt%=Spd$o0OC1AWN1reI~XBL8r-$gOWRj0m< zCJb3Ij<7W>Z!}4{RMI_+PI7uf`#Rhswg4mAVzc+c&hALHbSj555eg}j`0-|=_XXnZydp#755HI5~J=h{0P4|~W- zdR}cDHIM9U(7%brP7zQ1;uadObc at Y)k<`J12-CB(v)3&wEL?XM{uLkbh?9$pA%5as za%`_IDaYD zE}m2Gn{bzoUD zRys_g=>L)5Hig*}Hz$}4=zLg+FMH+(ICFMG7|WBkQsApc1HEH09IjQs4IOs`z`sd8 zF;H)w1a!r$ne7e_C^W4BO+#dpY7Jg!He7T))$MJ-Oub`tq~FsuJh5%tww;bKv2EM7 zolK00?M!S=tm)XctvA2_y`Ht+)w{3jLx1YC&aT>3r%n|~_#w1oPa}IRz^-edjL*Jq z>{+D&NEa?u!}s?W+8NeQ@>Q7M?=P9udWlL-w41eh;xq=fQl2j9#W&1wems-$8E73O zMXvcA#mMb|>DP>t;~KB#g!FhP>D-rX^TmUFj`;XTNqpHV=;rr*!V5SeS-SDaA7jxu zq(3TUG=8WZ{A8g`0Db%ZcIXo#9DA#KJ!qQHQNYlxN2V4NIWd!|n1Qjf>H)^^Z|`U$ zVt*bcJRs4z^m&`Q>LqI at yZX9A{A1Ozm}~JGW at V$`8?2^ZIY>(@Ny`}Rg;oN`4X6A8 zN*zVBH8|-e`{(|YBNRoC6=E);)k%KxO&j}>qk0b+OAI&_byjf>eVUN zu&(nWmJ=NIZ!2!8Tc3m at K@*7-?QdMz#EfoCHp|Jp?Zu2UR&%|fp|L%JB*Jjr1qce6 z&2E{qA5?0`sSBsN=Sq) zarE_~yd-LERP$>Wa2N3r7`PYJqO--LmZ-tMEbK(30_ul5-HhTDO0$%eX&ZQavJfPN7^yDfB at l^d%F10v at mV}Nr8&q18K>_StQerhKuvP0Anf|V89-_1v!TM$Vn*}(}S zvKD`Qb}U85?e3xC&2TUl+${`NOh*3Dq|{y70Dp3TI~xxgQuMRF!4pvv_e)Qzm at Cd- zNdFE;Pd2+1ZMiW;A^6Q~()NruysVnU`J+qXxP(a-*!>Kf*8T4m>S8*83Wi>o1l(%xyHK at 3ZYR>3uenw}=(j7`fYq&o>I~no zq#JKGpl1?&hgUrmPt=Lv62dpZ33vEm!?@-+sor$lRojl{@RC0 at AbP;@%ihUlt)$W# zko84+pEqikViy3lSYs&GhPB32jZAK7b>glp<2k>WuiP?-K zgex at DQi_WM4P`p(!prP-FZ0(U#Hfg`#xKE=O%s13Ug_}1PcaYhWYK;ib^zTX{p7VTOg83Q z-0x=?|9R1yXA5BvfCwn9eQJWMOPI{lqi0YxkL7E$rbyYjINZy at hPhuzHuuv!1^=!q z(3MuPm0Q8cb8JHXE}ds7;bG+F+PGj)^&nk=q^fVoygMsMJm0uFm*pAIN);Y4{kfyh zD*9WSRy1Tyq&e7*9=mBoi!~UGM7&Fs+%)AzIu}e%il%c;+!rwg(SqqPbCwmy{|_FN zFIiNy>mSZk2%4i4nl)L*{tP)%jgVv$CNd5lzD&$?sD<}-{j3b*ai86}o@*8~pAc5j zff<}^Es9ceF)W1?Yq})&hz-diE8z at g37vUtZ4sT&Y7tbda>SidXmkyVO_V}C{8jNG z3ZSTNA|}2JBJ?Y$6?>yStoS?*ia4C_9D=U?3y3oA3^^J-(F`Xgzlf&-pF*LJPN zFFM1Ysvxu(vSv6#%_T;6cFh|ycG582xMPu}@+LMhX_B>RAT<@=;(`wC^CB@ zZMB3;8WR`}0#q&bm-|}%U_iB>1>buG`z~#oEIS#|GIv>D&?rxck|@yELb;L3vvq+} zV06Wc9x=f4{rsH`3CiJ!AhJ}jLI}6QdwM!n*f!q~JN%K`fA*T?{23RI`HZ)R+{40g;FDRO{spL^En#&Xg$`p)4O-DIFx%1G$4MvlgE zNv9 at Pw<}TC!*Ot(83DHmXPm^Yh)<}i`u$qc;~mf4ohc;?(`fKyt6z;z6gV4 z|8*O5F44RFA=G at s9H*hV=$PQ4$ zj)%8vfvgrqzHwTYZDcwG zT+yYS$DF7U9292XD$<2*4P<@hD1h(cF9REe-of at Bw)JBsIuiHjA^-qHv1(j6q^>yI z6>t*$cRE at PHhmN5B4!8&=wMX)zT*^5(T{tnmhll24iZ(z?moK!3txjLl0(Q)3?`Ki z2)UYF;5HO|(8lVn at 4#F<^x%zc7>hB_O>U{7AMj$`rVR^}D3}fj3Ck${c~P)5hmVT@ z5($+7SAY{)IFjr0#F5=B`1B#~XEtJhnJ4XXqCD9UhiMFXo at 2FvI zRx^Ej0A2~QFrE~t;Dxso0I at C+EYDP5^1)0`N=;PG$DLGW at 0Oys!C+V5l-CR52YY at F zu8pwFk-iSmMgWPM4jPwR?hHk^yn&E|)ow%q-f;vLw{gwOQ=9+?3snpMKE3;BUa%I| z*V=$$v|NVma-s}RXW+WMZ#@LWP$tmY!tmAln}y`cl!;m!2jtg{48-nQ at B5~q at r|uT zXPedXyi zP>Uy1u=F?lFil*hVGn{3%2sdH#u0a%`qOlBJ=Rw+a8t8Vd>M((AoG`@s+<|FLHta) zUrw_+-{#-+?oD_5z at +DnMLWRJ*EEixIru9a}ygHV@ zM;T$mMtCn!)#_~>z1h{eVl!L&TsJ4n?NnXo4L|E2R99Qn)45;OPs`s6d6o8jDjwW% z6{jy3747ye)-B=lT5IBv^vr*>wg~8nbTr%i-5U)P8bwSo{0_`9E&fw0J&+c#10of{^2G)&Z2HY^FSw=NC$ zcwA$>&Y&s3g&I_d_6Xu+ejM$%#8 zBrAcTR(k at Sck-=%G#CdQQ1pLX*`o=!b+0(`P7FooOI%L5&Z9vFoy~v?qBGo&soqd; zO2o at c@?u5mU~6_D*uTDaEKBR1f)*m#{K`7O+>XoMtEBY|lc%C7BQMjuEfLa5Z6sO7 z9vl^Bs8WGb`B%ZW?O<=MW-8(4ZBPX`WlFt zxa=7^!WbH_fbFYtY5A4 at L&?~uGv1{i4(1nuaO|Bvc^{xMpnF9Oiv2w|!Ut?NiPErM zenI8*HGH&0W?n-S_0QxqCPBb~``#)C1}$jUP?gh0>uR(wO145I2_@`QBj?EMpITh= z)7!zAsW?(v5acw}_}{=`nl=pQ?3pD-(jC8V|2GsWg3X#6cG3n`rfCKGh3G`Ch?#^I zm=4{BY7naL+7R|`REfw(M+4 at o=<)AibF?7u at kEQ37i85iB_tfx6wq8J>XL8=7bnT# zV#gyls>K at na>90>ml!u%!t>R)il8#uoL520dJq{Pq*l!40E?v!G1GK1=3NuCU-&(q zHD~QQ7%q|DRB at Cm+cXd>{n_L)fESsO83%_CLiWJl9Nd{BT3{WvegJ*hsccVFL3X_t znOu0D#Fscl;@T43b)c5m7gZR9i`V>PiiD4_*Hgf}?r*UG9h(P%J2ta3&`E_hSHeLV zbi%5!tO#U06r08PFCj48)&x$%!vgIn4`rS-lRfZqun|jDj+CA575?u9GO at AqSb2E| zc`iqt``KtSQ at N*U@(zC_6Ujy-2&x~wM95IGL?xcM)+teyTLe8BMjf9Fn%dKYR|kM= z^aLHgpb_nY9hMNUWM`-+oTtdd?5FbLt4BlMT8e-a3h3qJsS3#{QWCRypi5Bd?=d*j{y<$&~rzUD= zrFP}jyP|ZgA^*yXrK(+xMP`0!Sd80(-?!qxW8usS^2$M7G!V1ONc^Br;e%oQh-4zq zAXqMk-~_Hj4B!*fb_eRfwVj;^ig6o-&cvgUC|Q;nac=OK(s2jz)xocOS!_=~^yT6O z at vQ~%bq4YI1d&Nf#N+dzwz;geEilK0Cb1We>^-GfvFx#YSmxwckjAo${Vn|U`25-2 zmG18Bi6ThR(j3(vl2#z08##J@ zMUG6O&Pw^CYtA14I7<8+amOWPzK at yI4~k*#Y3L;r5xSW)kZ@{9M(X13fjLs9X)&jv zDGJ_AJ_baBs%nz!|7$G`&%A)a7c#b&BE(+_>v&a;L!VG&1>M)u32wM_^T<>daKfIs zb}#eGCGKL?c3kG>FNv15+vu0cLL^F$*i%KoBH;059eI*p&2yVNgO)4`^a>W|y!jC! z;Hc_n-O1oPIxyM9h4x%soU|(!K*!qWH@@@frqv3eI at Fb40!>K z+lCG|&T&GK=l<(IUgf5w{pcSF$GNw{V&5}xkD*-pUN-S|@SIsFaG*jP2zZ4LC+lor z4#5dcVj)R?*yz at HG{W4u(|M%*nO)Xfmhe5w0W8vGSv{W_lg8W*D6n)cXv z^4PihEAd$&@wp=L$w>C)O!h?!wyPhJ(7L at xAn}<7a#sg-cm{g71wP3Ib^8Q4=>akM z{Y3f()MNlm=x6#v9mtu>wT(ZAp&*lKHzl}I9rWNH!u41Hy{#WUm;r*00i2EjlFk;i z&KA7R7Oc(|qRtkK&KJs`9=tz2Sbsj*Z2g36KZtEVm~B6(Z9g=YU$5#P_Ua!}>K_*B zADZeP?&=?6>K`WR-3*q!MTfkNhhDoQABrO%_9GusBOewcADSb-hk!rCMm|hNK2%3O zoJT(7L_VxXK6FL8X$-x7KsWDFpq!bZoXwB|H&D*hQ11Sr+_j*b;h~)Apq#Ct-0`8D zm7$y+p_~a$zKJjRHd=i&SalCHf6lWTSY_3**7S0a9LSeGU at mxfT6GVyZ<%EgvD74F z0Y~NUQc54J7G7hmKJX4-3$6Tyn|sW)w^(Z&vVl+XXHeK5S>+F_)bBll)*teV-eQa1 zDvQ3di at rLG9aPrceAeAe*8arSe$Y+6=tN(!2{YS!O-g5X&u<~+4- at 4N%^7YQlio&? z-b(NzpI|I!WGvt7ob at F!=`FhM?Tqg%$YC!)W-q{IF90W27pqejf3$_%x(WaLU1txt zwg(h&I?HuB+i*G~Vjg|FzG3n8rLp(pu=gV{=mkN#<8!)`bh?v3UV~+_ zXp;iWzJ3Cez6+5)%TESUF+Xgj09#oeUue!CXzlVJ>&=p zLW(*bWp{N_Iss{%2H(j3qn|M6FgV)%?8zi37?LovyGLjBodr7MvpVa<_O^$t3Y7dz z!#S&Cx#snKXAs;BPiWoS8^k#ydw!$vU5`lUozebE3{$?E(as;!&L7v#pVt0 at MFC=6 z-!xS}OjNrO^$0RqbQ7ls7)^UCO?yjEzYpd-+~WJQT6B|Id{FfC at LF_pT67Cqd?18x zMkaXd?GZTxeYUy2Hgg_6a~^)@JS6a(6=htXM)J#Fij5{_E0+X(0q@&claO(t at gPEc6*)aJ--HZJ at j`y82NsPZ@!yuzGC_M z9fm&Sz?`+C0rk){zyDK6f}(7wqkB*U(C(T=(Fcm at q_>S2ehDIPy*B5URBEB5 at pTR?peaM z)AujM!4I*)57ogBIiQ^7hTY%+U(iGWph#D}QIe-5AHATA at C;<2S{RWT`2s(mbqlWB z!NO*UsC{>YcVeJm4S0V(-v)`!zK?_4 at j*>q5ikch_Py_b+(|-B5)m;6xcB*Pirhg! zO<@tT8 at SH;-ih4hg}glBPxf9Fy!3}0Hq`}Qkw7zck>&)TL2e>X2jKft#lBnAjRjmr zzaM?ScB0?0zF#NNprYnuw;Z1 at ykEX40X@S)TZj2q*E0dvGafHTKJRz=?{|LBH1C01 z0b at c!yZK+=ILs$9>B+x-Q+zqHc)xS`e1Z6SN4?t^y;6*vok4p!dU`&Re!oNi^uqn( z&ke}y7n%vpA8301Sb4w0|MX(~;!g;;9PsT`yh3Xjm>>;HCk$?-gWVc<&X=UDWm+G_ z*&+5Mqtw$7#uj2xc at E;a85iEUn;VcUI>ch8Fz{5GeBULhJgtz}A^VXG2b~TFokUWr z>}lS0y~zCsju6cu5^EEmsq1chptCk~R%eGpzYR(Sg%}H&7)wwB0?r^#(G|{v0`^JI zs^^*ZjzS}osTDq}%5cwD8e~8As9Do9y0ToqvexdS}Y}0yn zZwxea-mL@;mWq6K`Qd_=Kts$=PfR`Ju+10T3mV)TrC|;W{Q?Vp1q*%j|FBOyQ3RJ? zkUmhhU3ML23u2_VWAQ9Zt>Fpri>fh+7&Y_bPg=^FIb{JDA5wo0Ww{XA8M4F`3%IuwEqWR=fZ0mIj>wL;Y3y8MD`oo zqU*aTP*L2-b|`#I=c2Ja7ezSeo#87!`Q)r25MBX=E91BQxR?ausq%)Ab6P5rjY4Y_(6f!8A{e}K+Ior3(_C*9Kg!yiPIPH%g zk`L{l9O9Bcu-ywTG}W&o6w)7n at QDYd_{9e)g!hgQMVCLijTLOLzfIN?+Rp^lJJ7$@ z6Ww12(L3G$;$V24-S6P!xU+^F>qlkz4&_#P{kXmB!7`W#3{IQM1{dJ4P z@?5kX;B=kz71vJ$6)@1BmT!D*(Vy?)_<)7|Zgwq*sGg4;yqyvG6T0U+%Ned2$_${o~Ow1^$5rO zJTOb-c$&*JupJ}Dw6k6Gg&e|!&>b7ndnbwQ>vf#KWBCOG`Ne*3fnWW>19^qm9USt| z+qZ2H{uvz-r(t}p_Ph76;LaMphw@$<(Y`C*8=YNWv4;04faRI%ckjXCokQO?o|wa~ z;oTIruk3LRzVGB3^Yh~@AGhbu74!3#=#yi;8xwfZg~tGAe at Vv+BlO570qdtA`z_M% z&Jdj|m~JfN*rer!U at GI0H_@wzF!Y`w- z+WcW5C&Iggv29D-*9Ws$VlQ77zF~V*s8^h^@7;;_YLjn_dp79#Z{A~Leq{IZ zZ~m`OPO6u%CaWo+y+n`cazaEZ`HB^ah+PFj4;3I%ek3J=6xUE66^f&aSW2f0piGyt zQ~Y=;2BH-+uXKF*HvTz^F(^3__h=dibx? z`HM|hH0R&+QToO@!2-hA8kyc?%0KHsZ#2RUAc)@2f(|7N2smrI`JS_`dqedJJQ*;( z?E1zex3E3+mdAPtqq|7114uaE)XNVUf_h;_8?t%{%17Fk#=1#t_cyUe4gwsu<#&uG zgq|47Df~UIr)D!eAU9D7ZKsd7OyXOWO|}R;bv!i*_qv0Yp`7?P at jQK=33iacKZ&0` zW{1{=MCZP+rj5V^QQN5apI^>}#NrbmE)NvCTncv2sfa)Jf;^mFJE7)X@(U}XzrN19 zy*$;H{GKK at -*TFo*$GHYka>xm;F?9Z}G-BL>JUMaid z7D^q~h#?W6VaP#5!9YobiG@*(QSa!5Cy5WeP>CsFz(I$u{d*(>`)37PpS&(zzS@{B zo;rJaw_abf6sLmhz&-5;0XeRn8I5f9p&;RX{Vtu*i2q+Ad=g4tj?<+tT)__JrR z>(Mqk%Wjh!gzm#RrI!Rjg15QO9+2)3gz{lgzS7f|*OHDS8$}vhGNuFHC9EYb_Odfo zy~nG at M1E^nQZ!F3op1lf$T5Lnja9z6mm*VzVjxG3345hJ;F9+QOFXL!hjvfPDV(_x z_KwA)()*sm6K~L0;@hU%s at s2`%O0m6=5rSW2H98n?%stDEAFuqI0A8}1%wh!y6=5w ztPtS26%nvNXC%8+h2fO22JZ}WmM}e)zIrYpb1sEFvvV#sR8;P|-aw8b-vX@zB$b>(HA%@VcYnS?1rh_`!|M`$5uSfHP;Lk?=!q}6va>A zE+>*^#KuN6mBB-dmxJYXd&7pLx=^R@=hy9&Dz9bEod0hiiiWQ at b$Y zC1*w8GD3gK;&MmOLLq@~kn1VRXAN(}DQ*vE)B?ZP33Mcmxt2pe^=25G3i!MiyRg=F zLpeLj&4jlw^WA79$x?1?IcP+lHK{H`INAv+vz#3b+jd<;{@!NfGr6JH7PQy6Ye{|* zOYR&iPFBnzYuf?)NtV*Re8=j;0q1LN#Anpgfb&u#_D&7Wb7{zLN<3hrZHu`(V41j$fz|#0 at Z0OSi?d^BeN4(S5avKrO zlVa?P3fh~+$lLP$ORdw_dfOHo(^r_+3)9p at en$G&xaStvA=Jo+jfubI&LRE5DUzgC91zQsvrl95F5;wDeC9S{a5?V=P>zamGnE;-g~E$ztQ{P@)9fl z9ucAhw|O7+DEy?TMUe|W8VBw^@1)_S?MwU#ACToQ#6H$86X;$)_-Gp%!5&74bUX`1 zd=!J5oG}iy$0QxbB1c$PMRA%TZaL^eKm0ozjmkwvDlYRVOUPmJw2PcI!OKIubjDRi zGi3db?nGCVn-~JNwxmYc%&!T2Hd6>qX(A1t!!ZP8!2R(uTv#r%u2XG8143wOV5aC#Nav$rvO80XiDW3yeL)*&{w3EPkee?W%G zPx)G9SoEa!w{qpUf}cpuz!LznU>b!v$NIT&_k&sTq-O at xkw$`@mLZD63ZMn at yeShE z$p2sy6`e6xo}|>v*2{|EP><%x=|XtJo~SD8&(F%tTNOmVpBO at Vv59baWBThGzS^#q zi`)(5@;o0U3n48Qo?YKjMgUW$N0)>qw7_<90;5 za%h5i?oDeS>={Uo$4VL0^#sSIS}Et3o3Jeh+f#1Hbi{RC7(zWN)We-a`dnYA3GfdJ z&VM{328Jw0)FY(?fa61q_BlC%eo4=(Y_q?@-&pkXxYG699m5^R1Zwd$BWzLJW6W0- zFG;OQ^YytIJ0#vF)&I%<&DGCv&gh8e7xCVCUxN2W*p~1Scu(Rrs9EsB8gBkPIlFU= zVn~i1mJ8E3yYhYVoyERnF+ZAk_%fM(dlLspAYv9u0Ky^Mdma+TrX at Y{X|Z3<_B9tRX_c`?N6}7!1m`urCQ8Iqot6S0+27zdgN|s zdWr8nc3e_SPmgHuHEVW!VEDZ~397~n5P*ht6nc^!g!RYp8-d*@8{+Gf^Ha+ at QrXh2 z!FGmx%Oal&^LqOilpA6l>CF)5U~l~(zEEC3vEqn+6i4(o>=P%);Ea at Qbndt>c(*)= zqcIx_fx4r)jza%LeR4apM^vx$IgLe5zn)49%C9Wjh;~tbD5s@G1#vYbpujLp2J0SPZ zAFv>IyEUZqH-QZSHlQcsKjA5ZQ2R2v)MT_F$NNoxf|LeU1X}o4CA3j2 ziwDx}npMxWV{zclS=1V6{;nh4Iydb;PvF0INxlK$o-%65>oe;ktt0gm^s)VeJMs$o z05OHIUl2AvXm1;wJE~XI?>e(p at kM@{y}{x-ci01QL&lr%4fF&s&*>=b_BP3v{hmw!{&8>(%Zjk#&7J?Ikrd)tk>p}63!i)>L`PORL}?C^-DYk3-+neA|r2kWYPn*FX5d z2jQ#Gu at NN#lMK2n$_Jm2OLHFWbh|r{{f{jh)c_6qcj+NOv zO}z8mS zL2KLbEKF{AXg*umUtlJmcGz2Ij?AP3pP3xJ*ce9sN&mC{AVngK*kHH9g4l2)L7;s` zA6Q|<07L$aAC$qy%rL2e%0K^OYgRUBX|N1)oUDUd=#6=zx?~0luU_2jjeW5S8pssN z6vh1Ragd0>3qa)eJc_(TtpP-3sND;w`%^L#Q>Hl@`fY$ye!DNyo7cqM`v)q$9LT znZz7Q&1$NQv}kr{EUG3s1ckY-h8m$!%LGQZIRf{dOk~YW1F>$1#E8j0S zrBV at kN8Isf+vJ&LeK?>q5*Zol>;f)~fiWl}%}M$o8vPuz3Ks+ at rIRP<8$&KJ+8up% z_XP8uD5V};GlFz#x69k^HM;6v{6*2orRx((mR5zq(dDoyjG1QZiy# zBqZ`~?@C5c1rX5_)u@@)OfyxAt8dyU1EUHwFXG#bs~Lsa(;`wa)&ZUApb+*Mo6WPFH-^6} z2$nqXYV;(O8`cQH=4g-F_ulm&w#AK<^($Fg-hOo)I-Qxk>{m89 zc9^^3**$u}COu7J!JQIuE?Ujs!?!Zpr|oG?N-M;?<9QIF7M310RnD5P#ozk;9e?0A zAGU}A7poq2$iARQ;{vg(RL~Y%saY#LEB{;ue4- at tVTBLb$@1 zLe4pgIkP#8Ikh^$s7hAkqxezIbEZmRmeH1#<;t#d z6lK!#^E~Rt9hTvirRCb}vh3NkHsptqhtY>ou631Z_^-nP#rczg-i}^fo|5GllO+{nXDg2!vM9{0tVKriRz>kVqT^#DTu8^9<{?{>ZT1Xnbvg4 zOoGaa6)l>jQ|cz!Lw5_i)Rm1Qpk!wE^ayoR8C5OKIH$FdIbi9vXJ!Q-!Vcvo>Cz>u zX4Vb?7;Z6_QI|1teHf3=! zs$3;ycGvm8-}iLPVw}Z`0PS<@c*24O-9rs?j}}Rf`D9Z#WyH9&=okRDHFC;GN>pun z7Mj%@H<`Z^Nz`Jd6MFI6P6~V}W@^X+5HqU60gJh83MI~^QI)E$9g6}V^ah*JYqNZ| zZodH8p$BvBX6(~y+!~VEsT^~Mg`972(;b$U-IEA~aje*KhdC~6joeP&DuP?7!mAwCXN=Fb~aJy|~b~pWaPLJHGxx;uD`AWT>c*}(vrh)d~ zFeEcmmUpu!104vLV=i2)@E(IibKRC~g^~i1oMb+W_1J7Ov+rk(7MZsX#a+?oL}hccWP2M^JuV-r{)7?)N+ zJ=!evv#?E~*QHgpL1j$je(PWk4n(tP at nA%j5E{c8)0TrTM3$50)<)tKW@`TPwD_A5 zSDuuCW at 3Cal5tb}Yg at bP|7h|pI`t6J0=EXJmrnS9*QLfTOHRt0GW{ zxK7#!U5p(hr{rgpZ2(M$GLOGrcp>5QS7U84$B?#b-y*tK@({;tebh5hq;7#I*r>r5 zWf?0i6J?@?9+L%>1vM5lQygEz{+DxO!*Kv_YvMh=v*bi(OiGzNQ3x5*en^9%ZuJf9 z7#@+0G38K}Ogf)s$po$13FaOnNJ#-bFymHpO;L!O%e+G;=qi3FRnw9d$nh%y?^bo3 z4Pi!2ORcdq#(cX&dX}NxVqAqGE9|X9x5?~Z|9}$*Y z=YMXQ^e(XW>$qr}n>)xM3N~kmo5Bg#2{H$)rZPW9?iB)t5&YjJV7NJoF~lUOp5DvZ zG;U-(4V!Fya6BD51{;c(!ye*R4&i5LiSsX=$X at 7JD728$5eAgB=<#6lKY2|f4C#NX zYMrQL)gnuO8d}eGspFVB!DqE=iyyYL)tf`cWn!?q*~eKT0 at n*FJZu16)MbK}xVntI z2#im77J?MG`*Q*oL7}zWa1 at wHEt=Wx`g+~5Oiv$dXab}l at P=I$Y za+yvZBY!J~tE4MdmCkNn9wvX{%};iE1rfwll(GRrYft=ik(aZU4w+cxO~C`R at yX=B zT&)spE;ZQ<1`7B#^bjun*gUtplinWmKyDa9w{9I`HELSao04jkm}yTfG;uV*uquai zVd#8`6KwP_ZAz6cyOyfRAv#PC*~dOQNfoMxTF%O`0 at f7r6uR1ICZ&ga&dPb-<0Pd< zuPd|UFaJ(MdwjxpJNZj~nWf=O^H#xWTcw{(d_+s%i3 at trn7v6(kEqDEQO;JS=o#be zox?bE6M70S7^rlBD)B0S&2?RxCe`lS`a!Km6J!HP{EWmU zC5q1hqesf3Iq09jL+%9+o1B#zrm0MuNqs*xA9nf2)0m)L;+~4`ON9ZC;Uq7q%(b)? z(l7gWJT>QhnbRj_;KXW(51Ij%0g%ummuOe|@ifHw&u#m;S~n8 at x?GsRCG* zxe7$~Khf~2=z}A%t+UH?TtlLsXp$C@{Qr1dvaAOD3AXSocG=Ks?Sw zY#a0zBg0vW-PCO|-EW_Tu1 at Nn@)sy%lezT8lATGVp_5Hz7)Lp%oU|O at AIBxz%`Au0 z=&5dqbV7wF9AKWaM7g}#c~uqR%!Bcy99O9ZdR2p6l2;fhQ*^f at Q#Z+}Djb_e{V!J0 zO+#HkRzsbv1iN&t_;yf*O4ild*1sw*=eZ|;X^~}-WmUy;#?A9*wiNQ7R;On$StBzb zGoYJQDjcqXcqC1tuwq4o?3GtYbQ_R1DGlB3(5u0DEDo@?|B8{QT7=EgqOnP{Y>C8R z at Qd5+RGV47u0M0TW_+tXvqQXfTANDh%C<{7tnX~#S`=rPYRvMTE!{kf>;!z8&tW31 z(zeJi(zF-mh$};ltrU`D6=5 z_++PCDs3(`Y#xUC=>GCY95LUvBr@!k77|(hyLs5`!vV_6;3na!?y9gz%gfY3Y^|{l zsQ53u;do(`)q~N%cnk{}6g+U3`v0kQ at m7F`_ZjE{^`-*Ik!hyrBv;cPhGcs*CDA+h0`!$uui z+M?gLo;o}AJ4JOJ7~<(Nawzg~T>55R(iO at TI5&qKI`W_N%sP3vjfcUjo_ETAT_?_X z;`r^a^pRX1>wv=VkcTwHNKN`^RvgMqa|na zU-LPT)k9?V<^Z2C8 zHg5vJJ^8*S`G~A$gS>RGMW+$Q`jBnN&}cqhiSjQRS6mkGsx#{sZFoj_a*pL zGiQa8FZ}8smaB1yP$^J+*e1&>N#UBv-^b~$4JmsirP%vE^fzxa60!W8A#Lh5vpnSH zuw~Hog_;{OJ_IdLCESH|vp6rBi)T{{|7cgfw#;P4D;AEG`+DCSP4~EPGRKl_)nwVZ z`;X@$#QFWG=r%|Zt85oQ2qdCwaxcHO1m&k<;|>PVK$LC=&U zv#ue+TU6sv%Y9k%Si)f(3OW__U(`D0 at s{_py5alQ_a+w)E)T!{?MK{>nRv|H-5zL@ zq$_+Oq4UIdfwuFw7h_eHl4_k{uB at Tk z-goa(#L{4^7_h$RQ{-6{$c(to!OzJlwOC?iHi^W=@)WigdKGG`sZmv8O{Ld~Gz%Y! z$}l7kJvF!XDJ^=gB6jDAt2C=6>l;H?Np7P5&eN=u0`e_;j{|+7VED+L zGOQB}&)H4of#rm0ZyCqjUR2}?BnnmMI9mV93*6=+c>r9M7BT(J8H>_Wy)rB70;_KI z(OP17%ddlQI~mWu!=fE7Kx^a`=$bLJJ>3-|P|xf;Ff!Ume+#OPP<~_IAAB`5zZXHc z8q_O8TEh2|_?trWzsP6|Uw-xx+q^M&WP2=SOyf~=j$0|H{3i}Tc at HL=*oL$aTQ0d( zJWsj8{-H>&@;|5dd!0sE#mcG5|5ln;KAv{S)jIQcqI}2pgbstvFH6oB4pf)@d^!$A zRk}*Fe{?2-YV3g!thnqPShNmFmJPnL#kc at j7#+%fX`ohZ7e--Pn}&3?M0Jv8Re_u4 z83pH9OVTF$&Wd=4Ry+eKW_g2tQ}CZ?g{l|3 at nuFAs2e`B3)BnDi=+T1xAWOIJCg6C zA*L&k0~d5fA?p6*j4OgklbCoL*;$7Eau=^V`5q_65l9;K?uJ7uim z`;goiR6A$A0;LYAP*!e_#yD{4p3H#iI9`#y_$Jb$G{rxZ%5zBP zJmEGzAnZsuKNO^S*FJd at R4=+at_|!mxnYInWP>TY*oW55V5kk2JLP5Ll~?0Ab{+((KJMfl@*{>c;1t0WEtIsPxC8`)S-FNN-bM9h^4XPpDv+# z4{RJp+~BFYXAmeRZP(9ej>ey4I~Wff=As5F=klb^dfJ-`=-$ip==5(zGPMddBfo1*JeJfu48>tZXp|k`k;mWu+yCg;Az|Rf!$6~m|q3r#F1*KfA)v9$XMhv z9gM`=gn)gA1=ON=xX}QWGDYFK{f^vAI-e3?T$P!X{gXloEskM?fEq^fn|zgI at l+;_ zR2czBOtRzLy<9^Hn!;wXC zCG>%eXMVbf*B7YsWw&!#lf>e#oeKMC)QkRxond5Wwi%ufz zMKE*YP&)|Ci!9 at -x#o!izq=_XgDBvNhw*cK{0|b>sw*sbNlN)+CHrVrmo|6_f*KkfF>1tiH=_ zt`vMkXSS8xQdZR#Ic1va|DEZ0oME_BX=j at S7Si3_JDid?*}x*c4(VT!$xY&oQGcl$ z7r&$gm>Nn1;McPCp3cTy%t~i0cq}F~7V96qx)pWy5ETc&g4e*vAy5ABE-aAJ3=2r at 1#t^LLwz^xoJC9-p&MQEk2qy(pCyOH*~n_b;H3Ev^Wyy=!~GlL>Q`IUkEY4Q zoBwHjncxI)Jy;fCTz%gXc}JQ-1`b(bKF&ii${J^@rli|nk5j?_>}sSklP`Lx32>g9 zySN1(Ds_xy>T3b!jubAHpY at 8?2IP+d9pPEEe)T75NZ#rRBnC2G7Cop>zP;1jivXO* z=W4Q0PLX{EU z^$}YG*?|9>T2+X%kF{twN;YaXG6PMX7u81LYB6k}GJA1nkpIW^T}E)D&R_)D4Up90 zHU4?qBI7}h3E15-DHy%a7R^%R;O$}$Yq)*TEx*3HN`VR+yBN`h at rOblJ|F{}LTR>%surI^8{n0Z-v-tc$q&R#bLQRLA0>+I#5cl(;Dh7OTIy}%* z{+j=(^Qk>J4mi3<=?aRWV6Ty=yNxgDwx~)xQ^OftQ%m at 4@L8GU&J+Krj|+NiKMs=Q zLjEFVAjY3v|pw|By6$sgw~vtnTloe^yR4DN1D2 z!#{-JFtdniPi0Pw29q8cCp69 zzj~PM66-Qqf|%YCOa;^hzR(j?AU{#GKY?55JdB4J11jOhVgE5F2#Wr5TgW_e+|wxE zDLXaeQ<`ruwQrc?HsJnCc8@wAT^Y2!cBfAQSKM}ZbJsg!Yn9e^!Ni85#jClP&!=2 zgN>SAgTtDv1>;x8q$(33qLmSM9~f8f+fka&zeOGmGf1km)!URd(sVY z%RW29J4ICCEQFUmq-n8m0`gFK3t)qoU?O`kYq1%j$9rXK5&yzj_h{Fm{)H#(k(`0y z@}GoWxtFrI0I|);1DCip_Baw-aBQIYdzfqC8<9DZ5v5mzXIkNVw*7~QsCSopl(W)L ztR-?tEhw15 at vWZffk#^uZiKBc%xD}pK9!F8gRNXM^Uv3>%Cs#jr~*snt~sFjC(UPt z&KgDh&rvy$e9ZL(}GG1|d&p_i5><365|ag zb^3nSqqJ`h^QEMsxmvTK~#Mfrdnc=-if zJC6ZW0kQ%eSO4yaC at Mcw@noB5U(rcV4gq+L6k0CyVVEu-E4!M&tjB60J+iD?k5 zu((xOp+H-$GFZt^p!oj(dBGo+PIQ~hSHoD<5HlrHwuP*StB zUSS^rp{$}BKxr1_Il6a}ySTVhCC7EKkT?a8GI~F~F(cPZzm?(IHRzG at XUJqsZ>D1- z$AsnaW%)&)w0-p?(D)bIjWsVTLxRGN>>!S(8ymS=@A00H8_G|lbClD5qcr*W=x6S_ z#uf2$1w-~_sEpo4llFs%W$qV|b||w# zdjqyc;T5hK$3sJ>+C{u|(l5=|VcKM|%6GuMnekqU at ZJWnri0Lm&9xr7p1j%lU6WeE z%L={nr=fsMnKv79OT44vBA5f~fX<9{5DEL6E&q|L1GLE>MF*^PD7`JzR;0FGK?h;p z_ at _(@(l{ZQetnrnd_#IKo+G_$$v7dZ|3|R?FT<3fK;bKIvPrutaO&d#FPP#&)EeFe zgE)gEjU>Y_Q>t?C*%ISk_10sSel~gDUCGto$S3g!0-XlwC at E5PyjF-__5BP^k=i^! z8KJ~K@=>SP#f99|EN*kSnkF08EKmU>+-Ewx8SaB4Mm at rrW&Vz)2Y=k_GlQLKL3oS2 z?HA&4fNo4$k5d$>k8qVmzKWp2C4Ty8m9)nhZSP+~m;wdYs9m_M(ci?=S*=EWDvTOe zity)zaG8UZ#Bez?n*`5AlOfi0M|m2q^ktJxCAOC&3T35nL_n7r4xQI+DZ<-X-)=vH#v>kNyB> zFiGem_}`0bm&|+SLF>_8GFC)2_&!$RFD(Qc3Nf@}PO%l^hR z^u~1T8|h>ZeAh|Rm{|M!)&Azyek~xWCEPc2`Qx^RYXll#R!6o}8RK^o_zHbK)Frmb z{|L&jVo^IEpTEQLF(7|ucTv zop60Ovvf9}R5Pbpkh6yK$If zeX*-cdef!%xq5EV*Z8b93}n)<>v-}%v7hRxdQM)1$P*Jk<&Gf#2xt3S``vU9^2b3Ck@{fE=xinO34R?!8D2DlOJ=FvHNEx-6wn0^>;@>f!_oU$*@DE|rs5>2BfYZ{QCaA(Dt3KIkq9NT&B>?PTX at r~OI7|3S?S z1azPG5qU7ssv-hu#{Z%#{I5b7ULigw!7k1&Q5m4zG|vdM0m^DLDXLRC$5>Q at GoCq= zto}tT$X-Qf-!I90fOet2e%=Dt4`;EEI4P^I*w2&)t?P2)|A|DV+YxXp+bZ4mfWu5K z{$sR2cIf7zclVb``w*eVU1Q(+g}rK6jm_Lu#S10gCk(7R45?W$2cMn5r*OI=!o_ejZR0kZoBo60zFed^yxM01`Ej`!?Sjut6 zPD&oLQJ*R%OQPh|9#|jwvR9Jv-cl$JusRlo%a(EwI%@PU5yp8iF#cz0T+>_vFbg;` z2qHlvQ4Oh#V`p7>S1Zy(e>=QC1WY*!uSOyNjo>PTqAo960Mf84r&y&*NzEQz1a}QD z+E+f1(4srf;9SI;E!2MxOku;O2UVM1;u> z;qO250&GJEeXyU&JOAJHBuoVf?HF2B?+PJZ-DV|)Z7{Ymn~>rP1$UG0*oQq5W=7Hq zAFzl4A`k7aUaN=+Z!kbZrc1a3k7omE_GvZ-QnKKSsD?i+jp&TBgH1oxanYFKS1}aY z0p_VC at 5DoFo++iP!mlCOgvKkcegiX*5vpqGru{C`I<(^YPw!!9Jhu<`Vua76_px^% zZ$(r8Y2a#y&_d3YAJKDN=kEJ*il?wy={u_y=TtqbkPiAw&fB at tY0xrg=nX9&u=SoI zoBmPi#lBq7@`|PoL9-cx7#3t+*%q(-%$;<2XK5$9cW<4j*SGKQ7UaB*9a_nElt z1+zCm^Q+|tn3ORftWrC-fNT8`ljH9Y8>PA|5qa~YI at uUZH|2M16wgA{&O8pG*NCt9 z^K(~suY{Xudl9}1o|iVWef at 0?Biz&wAjFUNx-l&ZF#+7Nqdt_Xp7oC8vIf7e*dhBF z#=-*Z%$;~1UmkvbRB>&_ywa>CdimHJf&sIuoK5^8llg+lf^UG;$|PRvch1`y#!ti@ z&m*~OD~=G{iLYS~ATdbHgFd~BFlUjW49u5$$Coj~L2EqPpc8HeL8qD9s+XM+FOC5j7t zRH_ZG3Y>!1rRo9=9 at 56sXv>Ai>*i+jj>>0{fzKi{|HIUk(GyX$t-VktvIYLmLa3NK z?cB7Z%fv{}9P;{!jy9QC at sZpfOWa7H6vzih0TJ6ij9MTC&0d+gEWjwf9Jp+0W4LIy2{%wVhvq6UMi7T}6=D0MPPJI%f7Mnki6!xI z-7>ujvH6 at LOcmui|LSoqlYqZ$a_T3)fYn`v**_OZ*V5rOf^0nL8$HMln9^G$60Zh@ z;@UpfVIXrU4jIxDF%ytyWFbZ;8A&iaR3Sd73KCEol+ek!XyLD@@>1N_JAt5|+)x7~ zLm4zQ{;ATwtZlde-71e*1N#HxN~3&$AD%7n>wm7TIu!xSvF0Qe_-vrJnO?}k++xp~ zY!@31b^ll at fI2W=RR!O1-|*T{l6I*MCoum?RjsHbf?fi}uG))DfvJ`|^`)YS9&YX5 zRqxE#;l){1-&by%)Xc->&;mxZG29Kjst}7>v+udGIYOjQuga5K0<_|Mh?E`$+__jo zVV{PczY38@!Cnd15$+1rp6vR%B^jTVJo*fC4Fe2Y1{Y2F-+WU!5mVM(K53YVR z<)MD3%}y_RQLy;irMtg`*31;KiGQ&gsP4=o)Rz-MI*do~>RI;Z-Us%%0;y4*6FCGo z%)_<;8VP=lg6$L*uWhd8M`FbfN?g?HIW+vrre*tku}Js^)HwdtCo=VsvYGW?YI{Ku z9Ly2~2^1(;jWp%bV#o{OtFIC*+(RVSa0a=5164+IyWKv at 0TJ2e at +>6;6x374&Ya zicxsq*Auc9ABavIS0;SBWke`{lG z!>m?})=RcplIz6P+#W*aP&9Vu7;U!9qK6m5A!zm*4jQuV6NwfAi#l|e&3a50U1b0| z8dr|@ZBRx}d-C6vLz_3ZS1&vj8a(w&mOW4atpg~Nriw5tOp zMTvU-aJzz93%^O#=cBROm)$7OY3jgAw}o-OV|fQrETTjn zk*V;4K!Q{R#`}0>=Fr4~u2WrK-~4c&ZbxNzhk_vzde^^a8WW!R_H|hFZUJfq3f_jZ zmCEi=>J%FE^jNGKtI+$ZOuoh^bfVOQs0Sub^x|szO-qF%Y0p+Xg-O=Bd9}hxH5m*z zY-bvoIK)$vVSBBLwRCR&H9yBSc#JPAkSTQvF6JVvFiuZ zh|AAft>0GY%{_obshvLXe(_C(8qrJ%9r4MU)n-3xZClxSPYY)dz&MeGq)$t?d)f)HZfm*B*Yl!Nr zvCt0taWm?7M)c0bRv&e$%o*fu6@*9fq^KV`=J9kDrXQhOXw zY3FU&;)DN8b}ua`Z at 5wA8gK%)Tn=o?aA>L^IflJQ_1ENK^*GK_Ccf!2C~ZMSk*iYU zclAP=8F-J+_uAC4>g=c0sNp+qav)mp{j17A!+o>j6C*$2rSShRc?(bV&aQ5+4L{9e z09~~{7HV5$>@S0wlH;b)CF^3EDc-RtKCXd-5$DDW_~vqmcmC69xZ?NuJTZ$v8l>a0 zwLFsFn+n!5jm+ zUz*t!tY|@gxK2JpOy^PDZR>)|`(Yeehtg_iW`i3Fu|N&RnBRnPhh;%Vw2jLMQ^(ek zSO84zKG at 0NF-$;T1LyGyLAIfkQJD(#df7#d3W#*7&VQ?Jb3K>96<{?_T(0s^B8h^% zl8R+3v7>ZebU#ZP3USXa9h?IQ at ODn~`Z_&(y at djZmOvlS5Es=k(g=v5FFeYp zYm#;Wph>smj6yf=+l9n(x|}HGkx)r{$GAr~ExCjF8tctmBYX^z+_0;$2n`L3LDgaX zt%T+!s0X^C(4=UVxD zpxP}a$@h0zA1O6>OS}Sjk=%N5O#Qz7%)+p{U)rA=)`jVtn~p}mp;Q1u3VndAguQpu z=D09kANk9Bta{KKj?%%B`bs(7r#3*4Xm96sRxDrF2Ox|u;Y+?=7Fbx0R5X%82{B10 z5jYQhrw$>6IkVx at z(f`bB5be4E1QGet{~dGU9^+*u$*v`9RAq1-s#KJJbq}OSZT*< zLS&%^yGJ;N=h^&pRaJE+*Q1Pq zz{5f*<8ZW>S at GBFFt2NUZw_MO!8Ic?n=$xbSaTKk`vON|-8i0qg-xGp_ zpv3Vg<*t@^jqqrs2aUR`NB6OD`N at 7Kks{Fr5=ZnE7(u at _PgsB08~0|Ieg7)gz}pSu zY>uHXI?CsE2-Bdo%SUA=64I-+B~BysuIhCv4l1S55?Y-sh3w)REqGn;6h0Qt5 zVL1s_+Zy!-c(FZV88j{nYLBf$Y&1NNZ_e>&%!RgSNuTCGTS&NuhmMKhb2gxLSPvRW~x-xg>SL zn)R^4;MpuJ$g)#B+`lCuNl25rYZ(g^6V^`K$Z2)Kk$VvSrI||@kz<_j0%nvw(1{&1 zzp4==Saw)mW6RUG7mRs7r6Uvq{LAGBy+e9eoX=11y5V;L{1D;n5T$dmSk%2~v&Wpx z3f|98JY+8vl&&VfCza3D7y?gdXaVof$4jh z2uXb(F*ij+ at hI?#O at 2Wo+U8??-R2WO1#}IY|F}3>7YC`Ky&3T12?0=iy5 at HoRHn1Z z&wvAaA=i861hI!|+S- at j6klGay}Lw%2>Tj_7uXwT&YyvGIK)r4LT)nfRT;K{H{*k%bgUq-=xyw z7YzuhZ&_zoJ_X3bt79|O0}@OMW{2N8L{n0Ny9_ at ZRiX5OocxRA7weFMSE>%H-UTBR z7J8%BO?SnSD{XwwF7>0h2gfe7%Rm%zQA02=;^S3R1Y1r=E_3x&So|!Miu+OZ??i7;_ at pO>A>Vno48-7+Mnk!~sVm^HX_TbMDBv zAk80tf at Zo3ec@rG|9r|vLC3TZ5&iFjgefih0?Da3FY%XTPrMj~Kr+lXg=+`C3&&;|AzEe_b z3rUqcoYe1^Q46P<)Xs9SvNbG&E|eF$8Bao1ijXyw7Sqx!HkOB7c04u>$aj(l6a}mV zpZWJ5vx~-jJ5pk~Z!8Lbd6Pn7Rjq`HgQ)~r->0i9_DU_|p{tnqzgw37G^bfAQ-x1l z$uz!O98Nf{QpwgM#G+>MOuCxu6^94_A&^7t`gMzjqYP!pDCgq1BKpw82`i;EDf_qX zf&6YC9Zf3voABE**zD?ZpBoFA!e`NH113`9PD5W?j^#w~|4J^wjL~-zaNN?L%w-AXvXX;mXr=yO!(~RrSNYk3p5o5~g1_s at kRcie?~K z3eaiIiNu0$%%;2U!>4e*msMWWdRLf`PLT<2Nv8ebs4=?K=c>j}8|PHsF@;WiLU^Q9 z7g+loC57|N%~mtqk{96}iXRKzKx>YpY3rhsB)6XP;X;k>fmB%*@aW|Od5GP)be64_ zi$Q*!Tj+LdKX43AKIPub3QsPttBE?js=R^>i@!P+z(v|i_yvd6P%YmXP!DM(UU0=W zsCFSPRR7YotUF`8sWgbzOw6R43`*rHK$>NoDB9q{U&JJ}O+#eD6--&xD%5dux+|uo zVS$YTrPalT#M^wFS at mCkx%x0+s2_BtIIyu<(Z$->9)-W?IA&MXDGg6kb}3%chS#0# zy5KE!VL&%!kvF6miqDL2pyTk&?g03>ys0vMz4|U~CRjCGBAK{mB=-hrtBTqf7ys?G zX?<2JIfali9z9qqTH`VsWtFKg8T+F<-ATjF$uNA;w(_BBYyB|nrKN8&cYW1!gt!e(9pE5nn9 at Gk_Pv3Kfo0L?U*xg95j7($* znRFArV7-7 at 1e;0RZ@&shDlD&(0+9L=zPHF?td4kAyzIB{c4G_keJ#IDDyxtAXUn?J zh_F*LPN%~@)!N7F$WI7d>; z`2H%8QE6-`xWXc9zK_%sLSM~vs+77%9>Q1`a5v_^Nfu{fWUGovy($SLSfMO__ z<=h0*Qe_t27gh5LFKn2SLGbe;wim}pZYEW7hORfi%5O{2fiY4Ish@~y2TZ!>sRN4T zyA-?XHriXKYh<8^=IcxZ9b?>CZ_4i_O`Hp>C04cy`|^pIKU95m at vmV_>9$$Eyt6qz z(JE_HVC1>erFR#Q0g^rV28@~}_hrh09^vx-n*M?SRimk{yDGD-4M=Pi2Ay+m=|{cq zu;h${@zW-kfW%OG{ngdr1RyXcO z4aq-Enwmb(U)C=4tob<>!V(Aue18B+zTiPaG9r=RCv8&=0>ml zN|WXv%Cr=%xf0Bd-9+Czg};hM`y~UAGTT`aHsmG%c{7F+(~^+;tqQ&iZKhRS48&ly zZ!@6Pn{q*UH8Oyo#j7$M9IdjXlgZs(&}`JzsMWWZnT+P18Khp$QlF|Ja28j3^|tdWm*@&A~wB5Y5abBGexuUEm*|6+%S z=U=|cfW8(hW4Rd(*|Aq5Lj*AZ!iz)rmiLbd>?(nDHA{ugvPQUmTp7VsOtz%XRzmvm z)C5rBp(naI-L!~X7soPO5R=yU{MTxoQaP;C4H)PVBH`0C>La0Su1$|u_=m at F=UxA! z at CVT$T)W#=67D!id;r9_FN_4gjis(d|GJC(D4XaP$`vq|M~+6tZP#_p`vk_7E{W at y zKz^r)UPIzkvZ3P at KbyO8U{q`oEUchBK00sU6KLf#N!2OJ at mxPXA`6%gFOa^6BA1cb zm;H4&zsJ2IMGTXm|6=|M*$qOyukKi_8GgBc3?Tt241 at Awupj{tNHP&5FBaf+&;&>7bB=W>$Nyr&+)+kQ7GL~HsNwR!WTCOmuAA2zv zJINev*4Sa_e&C~xs+q+!u?T0e-E^Q;n*#2DGrqcS$iWd;n(YdH3}yXIaya*Ahf~j5dL-IA#%Mg6|+x7gwgKkAV&N8ti~;tctq}A1S`!&P;Wtq+Z(k1mm0P z_rztzT_u;|7mjyeof%dtHLj1%!(&HU?Oa!pSWH${Y&#t}4#Y#uJ>ccxY6aYYCFlmc zTavneA3onM>RA*2v_gZ$_$-O`*}OpSNH)zdp`*t+cB4r4+0OA4GG_c*NcHB+$ijrx zap5_-ox at -5$aZ`HhOsBw{k?M`x?xNTikU?V$J2LhLm*s$K328aq3n)_ttmPG8{?Id z+Uj^HdrF!s)4EP at VF{G0Ha>Ll{7v)DwVcj6*zW*^eKW8sx1iYlR zd+L$0ek{ZBB_S<|WrgG`s}*L++0Ujg<$q0mWjHSpL}+Q9y!SrV?QS|1(i5GJzzWut zfmd#-!A_#w^%rB?m>_o{P||*JMLNNcJArRKbaF++w1Q(N?X6}L>2&v+Dhi?r~u+UY<9Wn!R6#<5=t#yAD8Nm0V^DZY<@qrSXLOi*U8RDOz&IBguIoUulk1;VcLN~|u0 zTPc*uj^bwQ$WJEhP=6c8x<;^#J;VEQyjhd{2c&AJvDbQw980#%EhLoy4L0UvyMt z!&-*mqiL;%W(gFcWafz!Dn>Ub3h;s?hoIsf7!VF3hm3!X!T`a(`So3vl%7{WB-G%h$>jy# zkPvUZ(&e5-9}+7I;PSDzS#_ASs57 at ymGvFMs#CH%xrtWqPQWcMiD8UaSLmiFEEI3` z{s1O9mUadh=}I-Ry)GZs$tJ*D*w{VT^^Q({V@(}aNq0gb z)NeLa34tadOz5Le8j>wVRLh;b>qa66O30SH=bP`r^2tyG&*VVB=2`EWKgOh)+=MKFF_%-K85MTR0$U_zpW&8vuJ)f^PnuVq{K8}oGoi|w{Eg0o-b{9xSLP* z_j9JbEVM at u<+Wi^ce(>BeufT}kC4PWX?Mfx;skC)rODNJ`ox;+40- zSJHo?0_XR&$)mvzMughnPkRc^0p?kC0Ev0KLEyi>lFs+yrN4G<28IoBRRIwSrRP(oKjPb1pD3l43uO7UCVcf%nTQG;)@wy#tOiX5hI zE%NromimP5#!On(({uWAA*6JydU84=fEBjqA`kM|e7LBXkYL>b+JxAs!1x at EK9KeR zaoq#<@qQrgT1pCJ*g#|?kRTF$x5Wv(tB|sM=b_VKKkP23F4vVEXXldfb?!}F(*zQE zg1~;o35^B(CP!>N+WR1+?|IoWP?k&*4Mzf54XL*S#fS=6hra~b)3Q1%SjI~#Y(OGQ zNsKkjI(E9U;?kGZ6yi at Sp5%~M&`bt~!mkWwiR?p%6z0*ayv8O`n~n;aH?R|o_l*6l z^zN#^8;`x|MLgQw3fykEmq)l_7uvJcgpZP~|Sa7}FS^K{2A4}E_ z?Kz#dUu%rG2m44;4rSxXshPsunhd%3IY$kohD71kWC&!CbdO2R$RZ*(3cDv74TFiRxaagZV0C zz%QJedNaX8XNNc(8uLQ7gH}a`Fo}ois%roqlT?;S6G1p3X80B1vb-o)#)tVTcmO&2 zyP8poI1Zc6tQz%!bQXN`f^a#YO8t at 1lg;S5yONv*OwR^irE)yb`j?L|xnCKAVTtwWEh7JR*<5sN zvpBZ!UsrreB?I~KZfUxZp1y6hn~r%m%FNX5vmN5N6aCXQ(WbnkPu>GSY zHXu|5$6K+p-lb&fCLAz%&_vJeYSG@(T1XP#dgfZpbcyivvOD3W1fK`ayyv zdT at 4&)p>BJ4(A<+OK at iylKcn0VXct{rR*lR9u-4=(Oy#u)Nd&hYqrr|1H at alw+Nrv zt$q~RQ-O-T{(XBCjGn(Fb7Kg2Z?K)t`ij$~_xkWI^rIIEC_I-L-3A|^R2{Ym3=f~O at Eujyj1$p zuJC(}*PSJ}Qdyw-|0N1oYg)gR2 z%j*-J_o8c!Q6i%gTGYjd27R5oG+vP8 at BJe)Jf(VXT`d%NdxjD~;$tK31-(k6z9lf# znQ6Mk)M(?((~Ov*(n8*Y(WQsu0DlIm&AwdDKTG)S9=KU~7XNB%(XQVGoi;cw19ynS zrc4cGJA04FTUHU*bfE>EazjAfM6$AqSX_(N%WMI^WwpKDVi0$v8o9Ujy?~=RW=ZCE z72TV`AH!5#`@0Unhxg1U_x at gR`^T?Kh9|=inqpAaLfpte;*Aml6 z*1x>A9*H|=8M7vMq0$d_zR}+R*JM^tq|wa}C&2E;dP_ZToAZ9ymu%W{33f%4`;Szk zUvg?Dn|DjnE?2x&Qn_6h1(Fbc>lGZZn}>_vGgALIgnLCk zlClatga1knrdLlJD!KXnZHu+S{4=1);u3O#0Hwc0Or~{JS|L4+Ua8 at Ys>v={{wH_? zsV%A--K5iOhF0z;SN&xbqlevgdRy+Bb&BG&Q-kZTa5dK$l2mA)fzl)WpTlBwFj1vj zF4 at Sd_-%$S9 at UEJ9-n~1=?Idv0I2%R>DeQDk%`69TKOq4XP0`H!()@(sYHzHAEXP{ zuJs{ERJ3Xjb%1m)tLv!As;Xv(=MPwkA^XWoU0kn>g)F$ESDeC)I5E-MO?S&=ZQK zmmPMlZU_%c<9<@q&(B%ZniO at WMmP>XbxL&$#?=Qv_f4}M~SlqDAFTBKTzfZ&Ht=Ovwe&dSP!*OaN zFye6ba7yd_2tI>;LoV;p4K(S!9`xHX9++K*nj!EmT2NJ^km$t?=d%)0LCd1u{GE~n z{cN>dS5<4Up0e7#~zc?!osWWSgOjp{yU3;OA_Y$of>HnKf^j>B=klKmsK9cB6fC z-N7~y#u)DbF|Cp{$z&}a-JT))C`Ba4W#s3=ZZNc=U+43bvbmAOq zSUyXbSeE)Bv`SuxC>`Tz^UNX at B!-P|)hzPI%XhXG at a_Bl>`mUG|2ersF#C`4`3DGH zuqShekHTiro-AF at OStGqaPbC=eWxGN&wNlxOu1G*9Q^?P02uxM0AN6$zjtJ21|aFO zxC{E8&Iake85>gR)uN=k#C=E{77rltis0!FWr>I3IxYBzNNEp=M><_2eXL6RM3=MC zQ(Y>pXApit^y;K!pCbe7^cRQ<4gFL1F+%o=3=8IOJrlTB&$3XpXM^6YPqoO&C&z-> zOiZ(oTAvP_VPU!O->T2F2-YWNQ7ASWoFaV=uu`9k2!}*J((4lQz}c?nS*Eaaf`7Of z>E06jr}GhFzbJrTx!#TJ=jf!^?~C~sS>{5E58AZ|fqFzS61gOfp$I$Bi!^pXEI_p7 z`ZYwz)fZYOu}7i=oK^ZF;6}X^oM)mDhTd$Ql>B3{n64DU0G-69>Prx2vAz_zLVth^ zUKPtgFV(L?@FjArGAQ$*^6Q~qLk`;fF*i4aG72~t>$p_ zLrNxEU9jWg1uEsKs6?7O^t}jFsNaTfwq7Ow#LG(LqFwM;a?s%l^c;jbDymgKD1#R! z`pGqxS1{e}Qwi_4$T7Pa>DP!_#mh(B>3W?-S+w=2kQd?rd9j8lZx-?}S8tTOCPeKP zbCJ?vaZsg{2giBQjLdD(mmugI{SX4Rio>c3kHBM*e$*nZrDG7ErMJl5IUix?>8-#GsL9xQ<>;onVC;evc~Rme>D_}EP5Non6jbO(&p=k4-fL;I7 at uWF^v_&| z`>dV|OR`Ot>ymy&%DF0g=rvWkK8Wa+9lJ}vZlQsG!$Jz8Uk1Gi`kGE{cuvoP8fn${ zV^w-gzoqhZ9;@?C{Wbz!)$hoDG64F9eph+lgQHI$l#+&s#qP_bAAo;Hzl=hk(Wz=q z>o*{%Lw_iJhv9o$r&hhFKSI8)=#N!apQxXw at V~1+1K!l1!~e4W0(f3O1MSkTXQW`2 zr{96RUY**#N6!SOU(ZUBMuTNCYtge)puF{KNcn_574#`6&&lklo+HCdOPRzj=+hDF zmOg|2Xb8(qnaqysGgD;pvr_0f#BAW46gD at d*J6BuJ<$F0QUuGgBYdO`aF0+4gSmm<+TvZVZB5-7NyW1uP9BSb=~3=`imAz z;Qd5js=N!3T83pASkLt3DZ-dSs&WO0Ux#XBD?xmTnjktrk`W;thtOz5&GVvPuiv2;%n`#^5Fpzt8F|Y%_>I zU>HDKK>Q)Y=-3M4kJxbw+Xmt*tkc4_gZN{H{=5UkSE=Lf1o1V7UbqXy*U{71ZV-RM z(0BHL_y+Zoau9#Y9$8ogh;Opz7FG%3O_rU)_Ja5`hBm1J at hz(VY7lRsHdqaaZ!^^7 zJ`mrbD%%g@&rv6=7R1{qFRKIb7bpv>2l1E4Cp!S*uaF1U0OGrl&l*AeHR7 at +5Z?oz z9R%?=>`)482JyG7B?YuFzRymiu)`pJz)q*IPYL7iSXYX1B!#;A(G(h+d*#af7 at DX% zrA6{-Yvz1PYYJ`OwV~UcOW7|M(J7xYe#kDRq#T!>tzG(`kn~B3$Iu~rQaU7Uvl~BR zS5ov&={f}|S5uGyNI8R0*HX?ROMlP$Q;-{ByvuH<=;x&Gd5PWBh1ms3UrZ79e_(f0 zkTQ4=QnViQsrxBC5IL0cS=eH|DYT#1F82{HTfcu9C26u=p`l~F3OsAQ2Aly7>kv5A*8AY>wmtyeXnhF&H0v-pk5V21%dC%qE!HQ%t=6aToo#&vI at kIfxWxJb z_$(#E%Gp7r3j^DlX@%mqW?8XVwPpj?ASZ%NwN3 at U6scoaS#v25wLbsrL!Z(RrMMI8!O zU|kQo)>;O$?B7{|4HZyrHU5Kbu%R;u6a$>jZt&b_&>HI}L1wWLPlp)mWwN z3^?t!v%teh7xs-Ui@#=MaCX*ap?d1Fby-Otp0i5LWTpAE8~7!(u`n{BFm34dcEnd=V?OW* zLTaqSC5%unBt}`jWvuVmI<( zry3dszHY2U&3%kbLOT_}!ZfU_kj5=zwTd^}yRkne_f6yHUv5 zJwwB|XKAt}291q2VHCj5j7jTwuvR at L$LM)X` z%XS;J`3{LYCGN5*C5QE|xf^9D!3y7GpM2F=gvFRSWsgm*D`?HX#4NYT6;cICyxOd^ z(T6(rN~}Unl$q6_Yi!UI(7NV+8`&5zT|tIm}~-++u_3p!~FvBwZYb|8lck zi9G?wX7eQV9*7leo!J2)`^-+Xaiw_*^a1lU!mKyXfUY&qf?i>E0XLfGfNRY2)Njmg z(0j}az+I4rzH45DguNzo3#&1EfYoL%jRf;Du+BVzw6>U6R8wE2A5uJQx7i2p2J^bC zjvF)-%zmP+;wJTf^A at C5nYR&ilX-{wwmASv%YakZQj@=W*Y-(cF}lBLJ!*S}9XIbG z{0Z|ae}%On{41>8tjF4_6{VTPP9hdoD&{?17)v-m!cUowJ#?OEF2uU$f_W91<)V2X zil^H=1O7R)2CJ#l=Aezf;ok(`v*r+Tb;&%1Fg at luaJtMc(C5wj=rzmG^G)bNVPB4} zZ$ga<`wA?ROg4;Mu0)qMEsv0odoq7_Wu69Q{sv^u?wF5hOg%LD6O{CU`4lex%=X;W zpTqswd;xrFK1cbVm>CB8yqRg(jWy7Xc1xB)6_{<%r!3S!nQFu)?ksx_!mY&=ZMRG_ zXiQC)ely}Brr8JNSj#npeI1r&b~cj^+~?T&ETl8rKHI>^E#rKSk!MezYrqb(&ok(+ z5oC)gti;Y;c~TUe=`OMJkIgk;W!UK~t=OI|1Y2aM z_Bh{OAVmz(iJ+bKh8NrCBkh%TI)z+lrxVYm_AKCXJM9*i+IecBj8P%YZu&WD?tAUk-YweFbp4eI;-k1WaO^RpmiHtfFe#O4YPSRa3crwX7y}7)8}m zVb4ZRo9t_34(sUf+rC!P>s0wsUA6Z05Z-JrlN>rMtF!a}l~7qVb~+qruy2T0*+Dy< z1s<^TpR1#@E<1P7p}>AS|9QGeP;aN|*@vX3utqyi*=WSh?W^our$gsz`zAE~cFZ7l zwwbD62c{`|a0~L$Y2ONLv2UaA7ht}^c(LyQcG-6VPuX_?yY0JyCuDUTx9Z?AUTZ^m2Pj6aSuvPv&2J^^ZUZsjZtj3ABJt$WIr8~ z0b3-tN^Dbsj!TD%Ns)}5mHaM=yCmnFvBWScIJ<7=RJS+m=M7<0a^7v+ zvVZA|to=WNF%V7{XVxXjUuPWsS(8T1^-72qTLRbYvO*4#yoYcwPseZXADb=AN( zNE6xV%=?MG-=M#_aTA=Gj$6Q~j at t(Psf|0JXE+9c3mtc5!uLQII0k`*4%(r-Yab#_ z<4&jh{Lk!8{!+|C9)%Q4l|*swjvl;xOaQr at SVazBdp%LnW;5Guow z3w&;$37qesJ2OM}StjjN&Nk_fhRiVq8??_=(dHr03P&C}$l^d>D*)%IeLe!+u@{1# z?kECgIp!egd-h_`a~%tSrH=XF+_oL;5Nrv$yq0}xE`Dx zjxu0{V}nWd%0|#T9h-o=9Gk(}Vq$*hY>#7`jIbT_UdIj at bSLQDj$P&tj2cY#jt}hy zcaMo6lh_eQxy(Zau+oHWAb+Y5VV|QKxZhC&);<$v5`FNj7OZ+l9U>lb)Pr-tgk8X3 zD#3<;RpK}ZtaUUipREYi>^KBgjpMKibwo-z3Or_FlApv5J6cU?1|d|P<2bO<(GF~I zoB;o%$vWUT==f7%)I!-g^iGrZh~}kW_?Wc&#ZH-+{E2r8#jAsUa~NkJx5IH(6{So4 zoKs1jS3ljVOjH4#jtjt(j*CdA-SO!+j04b}4(la2FF44I>2mZSzadDU1dv76<#4PaBP#U2||U75W at sz*N)3`8D&F<4b*}wC%2=A7nYN zqi1A0Z>Ub)567^hA07`KHz9tG^A at ncc^g>dyaOzD(o~q~q?>5B9Rmpe#BtZ8KWuW3 zI;V3GoO_NTYHVHHr(xrKpk%{NVTI0NQ*QS^f+OGg7zy2RJOK_mp2~04KST6qj^`@I z3mQYt3_H|=Gt;iI`;Kg+o#V{1%h5wKa|RN`s?w2dm&-TA9zdpG{Xi<#qK>JE_Q;U~ z5sRGD>}2gsx6`MpXV_&5uv{oN&k*COV4%jekx^p4;ZbQHTVzW^o!5%mk zsTeR%*gWTADRzm(rS>lv&2Tsc3>3D^xg1=F!?D6Xi7j;&rYyA^hoD-W#!4A_71*nt zt4TgA7sA=#ta7fE^g4;_C6-CtVBcsrj&MtbysX&HH`&)Yf0phU9&m0(a{HZhFL9fb z?jmk?Zc%xxgWu zxs$HsRXXYBL5;IQrkX8YVH=#Zzgy>|dn#L;uxnV8lkOi>IO&$b9w%L7s&~@0fm$bB zRN3p?Yu{iuTCj9*YE{&Ss_ncQRd>W$LmsUh26c;*)8S>SbDzD&ZnR-wJH=sC#!2M# z71rV0kMNz&TJk)O5vx4UO3yB59X!uD>q%HU=O_3Dr{{poF5M2i)YIHlwo*IS+vzb{@8qxqk%o6X#Kxm1CeEAPk0~v&BxJ5a4D7wG z=LoXf^#XL2E5m`&;>rY8xUw8m*baos#_x9gP6c<5D+jKfa7}}&+%+9o1cTnC z&v(!aUMR5$_-)k3B-ZNscCo|g;bMVfp<|A5-1Vy^i1yUA7Hi~tu0=9*DLC2g#lZWn zCBQ4Lr4IUIG|NCgbS-z#q`AT|iQRUs1m~J-73iC;)s8Q+i!T0&HQ;5qX$^hWwH9SP z?c)D?9X}J6yZLdF1Lw;&)wpKwoy11FyO&AmN6q67)IO zUf@)B6|m1$4gP?O)>!SX8qoc&eMqI+3iCy7?I2K(s}A_WRgVNtxekE70D21Rbn*Yw z0M~g}BXG#o1l|dh{~*NNay5h3Iqr*a&4BBYgN8|u@~ZkfUQ!%HaAuM zT6dpJih4?!n=Y%bc3+nbd_!ix-$8#UhiY!4n;LJsoBG!VH(mK&*mF|0JuvP9s8gy>zL7Ut|;IDAsrzYsU_;I&xhR)fUR!o*9YCyb(-7{Wj-m-``p70ISW$HYLLY{;-)TAFMHD=_ak5n zYD{C*?$w}cWdZiPsn6EAAIr*WLLREzPrzw+KLxhAX#^Z_KclhjehxhDrqj!#?iZks zxv52J+!;>l5t+_I+_;66wcC>Al-GQ+)z4HX7TWF{V5fT;@RWPH6TPX+4GRfT?r at Ro ztiasZ?Y7d?ebG&m_67G$Cn@<^5^09+b<^zIuW{W47dpx|vIf>2iEF!T_F|T-7 zDUr|6={;;Qkwr$n2?iabkDPsN_46f{F)B_3F9ATu?# z*u$2?o-nd7lX&E;v|L79;aPz+S9(^GXEtU9<+)CJuJ^2hXPIX;c}|6Oq&&As&#j&{ z at Z9EEOP)Ef$W*TOc-Dbk;aM-sOaaS1RPx=PG730dV>KSJ0omN|*$AxlY(nsTp3PJV zxf(m*(Md{!XA9zg)D$G3(X$m{nmpT_o1Ml?jUDpv?O+`C>~Own%+g>vws`cN5Y{d; z*5=s--{T&V)9Tqx6);C*9UjyfsT zhis<-4_Q-pJ!Csw^^hHP$3qs>6%QGdH$7zi-uBSo=<|>rb;I*Hd5!e3M^q!}N1hsF z=Aow=IPBR+gW;KneO?#^ScG^(`%wc^y|qr7$?7E5gEP&00GR7-a7wKPdb+nsmgb<7 zlykF_HnOUEt20VvCT=k?YNZC4&Q9=QDi66djj+f?@1?C z1%(=$?KL`3t#iGd&e%#|j`x(LPfI)_ at vOuyDfpbEzX&x}r1A64eDAMyL-1lRtppc& zFUT;o9^CG|h@^IVFFEB3l9r35-X5^_czY3HiFY&VZ-JL;eZH5Lf<@p9w$w|D!yVqs z2(!#f3&;)LA&6e#y at I&q-m6Zz2pFb~3GX!oD)7?MajW+MIP1K$KwRj}h4r-B+vk)D z^D?9^_g+Wr3P=)crT2#N=m)D3 at dR7%y{W|Ba>_#vT03s?4%0>mvZ=9RFD(&EytIrg z^WJvS;_wb)Z$V11<-7wS2}HxpRzog$19jUDt_>!k&i1?C}dHt?`_Dt%WLb~WKNaGJf-K?{fD zOTt*Bv14A_405wv__cd!ho;p#6ZCQKEXkh at y3ISsrLh+8Tq)(Zs0(;g=fUHeHxC|X zfH3a8`JgX&3qW^!=fn2~ID%dD76Q+Ci{RVgEe72K#Bla50QSOHV_n{bz*C5;vGbs@ zZ19$VzT#a3Iep$zU_Uq*u-?U>uS1$(osfWm?_J_TcU_|KrLF<*7nixD4Y-_s*b0}d z+Leg1+_wsWpL#daZUicRIs=#^Vtp>fqM=w7fcxynHBKI15=G_N6$47^t*}nat3w`Ur zf8wo`rK$rb7ny+0 at YRD}=sN(M=W75i_B8@$`kH`KeFuTlkrTm&z0IH(_$ol(^Bw{{ z*LT<@d+`xCmiUeWm->!TjW|RLLS-P2f<5-O!ef at N4LHZwf?T}t9tU0GYX|20V6U>- zzLPFtUj~!R$9_i`%Q1v~qQszX)ge1qv0O}by3kacec}|NZ1BOTXPbOyfE#_^6UIu7 zZS$p^MQ~GyE|Zxce{4_UcCSb`+OHwQ7$2Wdwe}E zxf#_fyYXcdqSAK-9=m*3CFcq_yM5POa)|aJ`hMSaV2$sFi?(h1Wh>nTr`C51xYu_Z zc+hu;hM;c%*yy_ptn%Gci4Ll44k_1t8n3*@xv7YO~#^O?7Lm%{SF8 z*m2*FUp3ZgDF^f%_cV8tKHWXT-Qjx&MmxLfo9UM6(k1OnKK@~>0obr_HZa#e$BmV~ zZ!RSC`R0N1#Fyu$3%@D(a6I%C0JHq_fz$kj@>|$Nh;YYO3{Jmqfm_}}U#P^FP((8J z*&KfQJiBgECOJi`B#t&oouBFvr7G}c28lwKK{v4#6Rm> zqnK+U_=RsBMd)yGx^jKaw;sGlzB1yWOKqUvvo5|7iC^$-0-pD6Cf<1$-{O`kW2;+M z{Wc_U#m6Uams#EcDbIX6CB2KnUv}}`^gCeVd+7I?i)xQ_4>HfJ$ z=9;gHg7v$2wF*FY0Z#d9=yUxr`zNt`zWqq?rjP$}Eh1(5>wp>ldN*CgKOnIIp>q6< zBxcaX7bEm_pZ2NOnuvj3wHh^X*>{j!4_&;Oew!`)5dA)O at xu@|(|?3q=txH)>bCEg zY_0u>cgsgl3_5*uy}JvA-U!}BUklLIWENd zB<2d=Mdi5UmN&k8kkFv77olhSFDsubtk3xG%c^MS4YLWKAM)<9Tb`iniU z8s(Z!dWW=5yZ_S*Jn=Db(!UUa9{5WF2L+ at lXe+4tLSN zRVKR4L!Ep(1a?CptQ-FhRjxIV-sdNkbjH6^N!$flxBR4u&iZ#l`a?hIoD0bE9u=Sr zaW4DIf#>}d9y!1&>4)t_%-iso!p`~mr>hY6uAi>MU-ef@{~8s4pQqSkRBG(8- at YGV zvI6w#;Hke!B0JGhcuo%-1I`Gv0CNK~xZSA1vLj$=m3^koLmyv0EY}8^M5cuS2 zWO8 at lj4Je5ILZTE%5e^koq_Yn)%rlUVqE}hSKuO8)qzXY=L0>c;N^i{@GAn_P(vkw z%V at dMz!msz4eWPMW{U!|Fv`~ku7a~Ua1FR6&yg>@UIFCAk7s>O|Zp*yYP73;kXBnGGu8ATNdEIG6+{?Uh?>HV1o&sN5TGSM&qEFe=6h*LQwaZ)fgP+*}~DuW_q zx+$;#@tXtu6D3OMJTO}W`_O+Hu*L{j7D33lKq(a;OIpT?Jy at v9pQZ3QA6VuU#zBqs z1yYuKX;W+kxHkeTDLE_yIlCEH4eSrBK{D3^Jhaxk%xfId*gzm<9Wrw-uwDi&L%{|E z8zj9N^iW_OGIBTY55hPC(?4Ktgp}dHCg9V+X5ixhDd8u9EubF-w#taxWG=UR>9bio zyz<0c at +-|ZjAI%1|VHaF>@f^KRf~L%waw2-4f5slh$cgI+Ub2g{Lhu#&Uz9N~Ak0xEDAlSOqK&RwL?KFBYDR{R7)46W%Ye7T!g{I+;0| zd*%o0sjjgS#pDod02T%t!D;ehIVx|?fHgZvAU|o>Eeolou4|&Ie-RKn?gZ#4>6q8i`hTuhHw>EeQxG&g) zxa~oDU%EBOKi7*$hl4Z=tP0X3(HOiebA1J2>w{MzY;%x*{+bd-6GVBCPwqpcGr{Y? zQ^6ZvIls|tupL?dG))9+gZ(PPO;k`#@D_FJ;BCdaqkaZxaAMKP+1Vh?!z+U{^{xog zq`N*ylkUObJr#6Njje9T+7uj8y!)z7Xf{0_q^Y(H*?OR|@=ygCM)DYE+b9b-|}fO*~Wbu7GzS_*}((L4!9)6L(WE!$-4i zrjKUfs|d6unB`L&FTP3aP>}RkYjCPhW4nVn%43>Oc9mhISsCPCq<4o^!RbEw>xeUa zvP734XMd3P(DwvsX0Hh5`Z&7~oC)j>(qz0NIE#c~Sv!dx3DWF)A~;8hpX*cCczmxK zCpE1%WtzJu_=P;>mk$9`Lj}q)-xoK4E(Z&hgSvEoun1DKL(~?%vU}eQ7W?QH$^t~s z3 at xPmV{uI%S6l>6Zm1OD9|aftpm%~xROyz2H5^<9?-`-xz>JXU-79?Z=mM4hJh)Qj zbQQd(hgPc$Q12fKieLTIC)UVhNR3_#@=vT)-s>cDy^pTRlquE*AALn}BWn0cP#P_= z8E(j$e;lNiy at i(D zgWJI>#C#U4L^keF;A)@j){W*opV6tzQ(6}1g?>__#O_mt+^>FW5pQpZ|9TyMi$eTM z^@@E!{WKt*WuZo-&=P7w`ny91l}8;M#UXmB+!$(x_x=#QdT$6Ff|$|}|Bb`4R*y(L zitL{V9a9lo;5Zy=MX>4+|IIeI4uy^@uXZ?&hEBk1TZsQwm3W)whj`$m?+a*N{@Wek zSA_VNJE?qFW6HxNGUt&dERE1@u=R2`{ErqWuZ2jVHdG>WFdxwtg!oq$DX&s^)rZK$Yz{5< zV-`P+rA>%00lO>oAJtaljK-JxFND&UA?Zt@r}Ay{`j}bE{DpL*9Jto7TV~KeVFKaXp;)HS@~~KuC3~4oBG-Ar|+EYK-8Zvf_g)H{8Nl`8oLwH%2ASmP=#v6O0dssY%rwl1$!t| zrP$xJ8{M$(LfU{m_V8yoRPFzwaY18GLf)Guv|Fe~^@@E+D<`$v`3id$+7EhaYAtYD zYB3#4rk2u`nN)et3#)fn^sLv3^;_s3L}aAaBjk+K1HkF2B?$REMCbTfsST=pjs9xC zaS3`h^%vIL1zFm%b;YpfaR0C-tNZIpmzg3R62Izf%cXkIdvD^OIO{>UDT8 zOUo(&w-m0fzFm- zl{AShfNYHwq^e7Am!xlxUl_fxxKgq1zw9?IYiw;Qr!(<&seeYdCopnTLswKyUX`-1 z0sH*yI{L-d)ElzA{h&9d-jsvnI(qcB)U|XqA at vrL-jRA6Sdl7^ds6R!u1p;OZcn`n zEKj{Bg$^Rtmee6BZ4hH=b1MJie(*M>QeO6?-uKh;<88ZfO=H!mDe76-1F2;m`eTof zYf^{(u{HI+)JOisHlt5tb*cO at 4ad|c{`+p at hQ=CGvGfPoud(J-mOTt4b);h9FN^_=-AKja zAHwfy>{hDC4al$j2Q+p&brvGuNu5oegBrV+iey~1q+H~L^JU5f0d(kLtW&}tnjaWBt}K*vQDEdSvslvV08<@uE|8pybOah+ z2$6YV>XI|V>Zr3s`Yr-LKU at l&7gooli$NEJmq=gg=Ci`;n3Vd|obXb~UnX(6MC#46 z!z%((3#<&tzSHD=)p)FFs{+&V;lxC_HH6)X#{rT}bBU=mv#UK^lxT^Ar at mKH86 z!|TB>4VM9zglTE8AWXK=BA~`f!en%>2-6Z}d3XcDEDbCBZX;^EIJ^ms^HgJN!tDFP zc&4%SVJqDc*%00=lj+jv%|M<-&K;BK(n(o3acgEv;QK}f=KnC?D#LGsxJ-2JFxwv3 z5iqh~G=$|)*%jUy*d8#lh4nBWd6m8^P!XQNcdJ?;`}Jg)u8!Ua??H7IN6Mwl3S_-7 zLYGPV!&l*ZHB6tkoEo7c_n8qoEUpjJ8Zs|Zf#PLGDg*N6=3a;>jnF6E8p2fp`rDfH z70Z3$nVQCqgsTJeT{gO4-x{u=c8sj?OlDQ#eKJY<1Y~xEzO=S7vR`(nT8Z at G$o*mZ z_S%Cm-2s>t$pt+S&ZG}NhUqJ6bzwSxYYNwcb1h6?POA;m7v`RX>7vV(Fns{?X_!75 zximtbh};`iA4{VPu+PJE=_NN(7m#ZU`e at vW2t7Gy3)A-{mq+RYv8$h_!t at EYk_df{ zZFYoiw%!Z(0WX9PpfHOgbc6Lwm?pl02+fT1BMqpVj0k;FtutJXqE?3Kn~$p^jR?OW zLf=)p9HzH9&EbuRdo)Z>pzelgm2o0W&tLn(O#yoTN*{)N7(S?aYz;gPhv}n_2g1#= zh7L)jYrfCI^v%X4k;AHW=_s@@d_+0uONxsk^r^tr5xP-vD|`hqI>Pkrvfi-#P+6G1 zW>yoXi$NE|bpLlSd=!yCYE3z&WY8OsX%YI6SyqG|DBTIuHxidc=w8R^aEr=dD at r## z(grMw(3Rk`;p6D`!{PRTw3g`ehzlbpP&F^YberR5m_C)56B(kgQrW-EUS;z~r>1JbI)@Z(!tc&}E324nsb|4%2S% z3 at l$G+LZtuXkSGg?u`8KRRdX`=H3(e!ZlUHeSs-#e}sQkexWLIT`Kb%0k!c*TWQS^ z+9j=w^sC at E17yhGg0Sa%Lu3&AeUTwV zI2^f;$fqL at fSr+t;2enz1CK`@1=JzdWB8nkJON&cJO!SN&>HGQ>$YelJvR zGJ>!GBD4*-H$v;z>InZtW>9)%!K(|YPhkfl{A<~8osH0 at Wqo97a1uKfk=ve;9PrOY zrU8#ermKiE)K7HpGFQ1~B2G(WR**I_X9sCLJ_nqh2<=_gMfhiE5qdB(7jn8Iv|m{j z$&x#m^T0V1p~Y!UgqEUB5&o$>B{H9WSOLVeMdl+|M}&4r4 at C-tuNt!jyAt_yQBb}2 zMax``6bGlUfe8Qlf*{E)4q|ji76#>>TM1+gM;0lur7F_mpuEVp1cIMMmI9wgmdON{ zlfV3u*o_GP(25|{=E@*l7<&fZ?Fj#qRWbx^;arcb4$}9h)*yaH+FB*K35&y9k#$no zdg)DHQ5%eC;Rt;wt3OhP>^zLfk3nH+LiPM8vO&e&2th9*n}QmfnzkAAlgJj(IcZyE zlH0&}9Ep7~YCBw+X*+<~X**?VyFh29?UrHb<7-3mvu%Bp@*a3SjWl7&cQeAj at eH1K zBIQBZg7mez2NC*k+r0>VjqH9zeU0o|q(Vxnl(OgX!k{r%SPtm4E0E*f8=R3gtkK}u zna00LU3phpRZtC!YWlketcG%%z7O~?Z9ni*TCHko601l_o&P;0>mVcda_NCLo z)uFUgpj(mBWVRr!7wHvHdZ(!g($4_zq at 9J(OxjI_Nt|{LO?*E6yv%(!ycefmP#L%w zq$_Qgz$#Dg0hXlq0{5j~mi#N!fazCN&}%{U=`{%4oOWGS&<)CEdcX3&iTXH3*}R1i zooTn>cRT$Kurr&b_km at 3xj`j|x4S}vs??r*;r`@Nd+ at cE= zvMud_O7|iBSELW4Al>PYKp#kdtlI5Kw3SI&>`i;Bs{9#3+)H~7yq5L?VrtVeLX_jo z5GJ7XtPqxdY1ttfnNz`8o1OzKNS_9*OK(F3m#0mKua0%f4Djx!*A2Yof|Cg_&50?;?p=Z7>_npUVJ7J)S_ zy%@MM9ZPoBoW4+Iw*>V1^hF_A1Ep|urK?T7#l)g*4K^=*De!dqGL`glIA*1<0QRJ< z4AFR56;cXhGRsd}4Ssg|8d>nQ64xOc+tb#EsJYAFyE%PB2z at 1Oqska3e`z z(#nC$(kfKhD&d%!zE>q$rHWDwUTt~}GB`VJp9;1gtP|^$+>+iI`hPfk5Ae8><4(Nm zy*Gmy00b}qmLMj)G!%EWqStHLXW2gKEU#sVUde(jdu7=&pdo3&a5#t4?K=BFf*=BO z0vG at i1en1L<{ZIHVgLeQ&N=5y;(pZ+GXt(x{`-I5M^;x?S5;S6cUOn^=0X1H+b(35 z1&%q~$1#c)_)qY- at Sg;{A2{U{oaT)G4B(2uSx3t`WHtxR13nF00NfY2$YUsQ3GjB{ zGGJHWiX(j$nMHwXfE|JBvbS#lpNS!+idz3oIg)NUthZ6HA#ew9Zs0E9lfXT|^MU(P z>;dpefrouLTt4E#=zlEfPk_JjKLsogJOf-Dcn)|V at S?Arr!SGY9^j>Qy`O&L6>@6= zubukdNcnD%ssr7K3tRoe(K*NbB{?`h1cv4C=45z|bMFSygTM&Di-EEn`Qg*&f$Jp;#9CShySuW-~Mg%(;qC$k+)eGuJir$r^~^9 z!@nX&K8eKQae#;LN)RsvngLe^RIl%i$@oK1WWr4Nmz+n6Svd z$*Fd;Q?v!#a{^ld*9EraSmLCAdk#;q9iW^D>;zmI*d@)~4g64`1+Xcw2OUu9-|J}F z2eo_r`=yZwQ2fx(x7c(16Tr07e-Lnte+poo{}4)V`ws(F1daff!9$8@^0xwS3q+x( z4u1MbtoGB7+N4|B*Z~dCxo7Pap6m>xus=%8v$GF*w`&*QCUC32#WlZ#os;LNXi9E(e|h)&!pMzz96g;b8wlHsmGRcHRF9^v8kM zfMWx1a>kK&yh<0{BZ8l7!E!q**o}fUK|Y#}36}Je8^dA!uww}F4Z_&q at P3w97Ays2 zQ*Z>}hTu+!%?*};z9KlXA2+5Pcx|u(aC5M-pR+~p_mQX!Qo$&Q%ny!6QB80R;FjQ6 zP*w)V0ZtE&cbF$2vpP5taARZgjT;8cfo z8VaTbr}yJ~%Nf8Y25bBAe5(WNqTozWmIkjl4=`sr_0>DcY!o#I8vvIF8v$nr=Rl+` zI2UkXa9%&GGeM3|6~Xxs(AFl=0+cKcE(GuP;37u}N2`&+CKRj>F77A$W{Fc$<3nf* zE(P=W;4;V5<;YA5t^nK?Y;lD}d2l808Np`MRTf+YB at 2S9`*GK;;TpwSRw&lVrmaWm z)Zhm2PY7-V-Vodb*cjXlI61h*sd_6iOM=_LR2|$7d{wYDBP`|wcL1Lm+zI-W;I4i= zLUx1F6l?+9iKePzbnx%r%XnrsF|=z2-s z-=yN$a52a)Fx?5>my`$KJR5unsgB?yz+s`sj+Ia3-0C^`I)~k-PRTP_G7Nj4D?#V{ z%bSwf3^KVV2hxoT9b35OS~eN_oqD%Oe$rjD%W|y2Cn*0eF}Cr zq0zFwF`(3i#!9AfQhL0 at Iw4o~1Yh(wgeHP(R%jC7%n<*CqcJqu;o at gAvqMvu*W>;N zKDmZctHHJ?R0FsoG!<}ZXc}NsXgc7^&?nL-l}5LbCyv zhob9Ie_aza{)Jn<^iq`%?I2VT97MTv at myPNL_^dwont`_RwO$ z9ib(;s&|H}EX`e(+Y-uH4%(3rf5z%yXa(@r&`QAe5P!z%K&Tn`{?IDGXlQjV&*e41 zkA~I)9ty1kYzwUi>T|@iZj%v7!c5 zya>^LD0&$>ko#kAqblBn`W)o%!srJfHc96@>4{`egyak zoqw9z9ik6gksGc at 0Y~d?fMfM`zzY3eQ at nFkQLU@aXR#9FxJLhHbjl2!j)HHx{!atE z^HpVJdU^Uxy^=bd!P)5yjjmjN5_v3_U-CE(cp_Jv%#FV~us}b>gT|+x<|I=+;}o6E z<%b35oXmOYmV0zhUp>dAmMW=l2FS>h$f)4gg|6oxhRaFgT!7 z-W4Z%RnDSo(x?0;!5*D{m){#$s$ZAG{st!DHvI%jR_HY7rX$49D4O+KXzOwPHhg|Y z=l1~)>UX3~cXQ&_GZz4}9Lb(Z=F znlI^(rGh6C@{%`8e+qh+{w!A&E&B6Z`2?RI*RR*<_g|uWA+R$%_cD>ZejPT2!QTgE;eI(#Ty&Uj@&X3d= z>--pfr;e>~gn^cjFhb$(lgX=wwT}c*r3-r2G3-r=_RR=BY5i<1K?AI4MZ2Wq~T741h z+^X}-603E21FJXrd2BD1ZCwKCBl=QkI;k)7tD;d~?kMNi9d_v}WK;RgoP9d|_DZLy z8QiCJe!XE4WIhrNI{kPR^hEX5e)&@K8o%mYsM1>hRb6d$%lBK>`T0g1hf8ry-{Aji z?;=&))RmhWQ!C~Bga4A^ZBoS at T}@2#yZS~y50|-Fs&}y}9_T5Xpx~jt8Ssg|1 at N)H z)$iPeOZROD>AAkcFSjN;k$I%=0(`3Tufd<`yMe#ZTm0YgE>)GNt33U+{_!5rmKb{h z8;pH^`Q3W{Ec_&6zh6Eb$H_~KG!CMCzH!KD|6xDx*ZKSNl?L at cf|gG)_!ZINMk}{p zL?z at uQ`4=_fvy=wo8S5VT)Q0iMp at p z?5z{PXBr#^%8iqb8K?a6%N|fxW1K zTL!~%iZE`2(rDcA^Ud~MStXCRX~sR!7aANCDvbM(tu`JwEqI86xyB>FS;k|)rN$H4 zrl(Lh+IZ$LJ%>}q88rAQ(oqHtc>%`7#!Kj#YPBo`jWN)(!59mC z8*qeAgNpbygf+%E(DoSP1NGC%u$~)Y zGB{VO;-ul8f|4^vHQ-sJ2Jnj84Ci~wabz8z~FuIL!*xWq>8~C)F;MeB`h8r3j^~5-c71_Wh5;MIBOfc z^Tt?=`i7ZHVx*76YlHq`DHN8Ntgzd_0m7y2)aFwgwP;nQdl@=(gt;8TmF5b-a&sAA z87PXV00q<5Tp1YP-Kr|hf%5b*W>{WBnX6=HuMWttv4)ers`LA=wX$R#>Z~*6{gf$x z+=a9AdT6UQJ2Dh8+1wE5dF}WkG2W!`Mkl)o=Fc&Cj88Iaa3Gj&Zk8Rf1=?nsTT$9* zZi9W(%=LgZ=5|mTOujFfVD13VEORF)vrV4i6H${Yrhp&YadVeb!R`PL at 0I|cSNK9? zhPfxeM(>4iy;+K}v0WAOO=TaqNSsOKTcBZ}?m(0_T?^%3qu3viH*g1>PX at S2 zr=(YVH*x~~$h!wK*L3&(T+V69%^%Gfj~OD_`P(_0&B;){R~1{$Ii>l=+)D z*N`g>Vq;*A2+H at V&_{={4KtIRF;ga~PM?kp@)&VOQMuGmA>l~K98twf)8)Jb=5RZF zj+RQvR}~!KZAI)dl~F-Ubep3AOROcG|SeYD6sx6KzGpz~Gu)!}TO7kb7qUqLTG_%g)i|DDAe0|vBpf%B& zg3=il2a+jPHSlScd at t9ca7~b7$kd>mlKfn#2E=JW4kOdSSZmD)swo|+n9VnR{F0&N zstYP&fi)9wo;3?_zEvN at 83#}ibFA60xr?9RwrCB(GMv5^TY953aSqD2OM{kJbD?{k zH4l^>)_lNi(x9cNdx5OG6VYz1#jRaoEp*zs2qNoc)3?eNuC{n9vB_$Z0*ixur?N!i z-124CQt)rEIuW4P$d)g+xOuBk1&7tm)-o8~Y%Q1VUjg2YpeSOc#bJ1hwNh$o4sP&! zkE>$0rLB_vwOYb8vai=lyi&f^XSvqF^?R-LfcvctfCsFNfcvaXfCsIq7(XXf(Q2h^ z4)S(^-w5-IEkXI05Wvx9)kw-V;8AP4nPwws{`H<7z9Sh3eF$H@GhUsz{kSMwAeZg*h9 zKC;fDwA4Na`ZQQ3^<9wgVo-h(fT#6i>r#*-;2GfetjoZsBVO@$!|W>%e`;Mt*F3PU zNnczK%20O$1y8J-a?;-dUSe}-cx`cLcxAPt-|t$tr8OKTURtN2`=NCX at V>?I=(%+V zQr*^FM;%9^H`W;L&2pRWp=hK%jbqq(RjEn6AFQ(N2eP#fQ8debBsD+g`DQ->Y_y*O zPPd-{&bFTe*4i%sC)ymz$J!k9C)+PUX|P`b*4rH0YwXufTi?hC#{s?C=72uV?gnKB zU+BpP+#wR{3up1_j6IsrHBv$6b!oV{DGoqwTT~J8~pgC)gaDr`Q~`C)wqo zOtU#QkFvQ-X4(}YwCj6RHLNC^zn-<&=527Z%{I at MPFQM> z2j?bx0^nMkuYwlZ6M?U?CqYk;v%KPs}tc6%Pm at 7VK0av!;X6R|KP2l=9q+$3~B z^KqN6b`IOmauv~KH$miu&A$kV+KXYsS$hd65ACH=JIAeddl at JX?B#3&sVii`N(q}o z@^=iYoE}&WwyX9UD0yn%hQb5(T2QXoyvN;P^94eyz0PUKdK5geTfo#|^QF%TdxKQC z5%f#;CRQsp1HWnWJh9^2aiZ`eBk at 7g;7 at 1Y(=9I}ij;%ZGF*uN&wE3Fil+AY$`|UQAUbfpE;iFQl!^unn?>@T|wVt!P zz;(+$7E1A6#oBEve6#4h+}&+g5q}|16~n`>;~_aEPsq`IQcg_%3|?uNKY%wPd at 7`R zZ(yGs_MDbwXB_skGII`9j0&FztPEcOtO{QQ938#{SP`C$^VluS`f&PX>9{MPP6=NH zoEp9+tGmthNvA>ESzoHQ~FmrhAhAJ}8sJ4|vjt9|BGckHr#n zM-_Ep_alcdMG^Jk$FjbIZbi%tA9Sl?R`^~DR{QW1H0B=G?68=^A#q9A{S;ajg`WW~ z2tN;D@`hgkZwkKzTo`@@xH$Y8QV&#m6Iu~Y?dAy^F3~Z0!^3pvS`V2u;Znd=;Smn0 zOvjoV9*ImfiE^E&c|rv%lTu#(-P+D zwuJc)&TS6!cW%CxnmQH*yTaoDcZQumt}|Y@#P;w6Q1*l;0&WdY0^A**41K>Urs(o_ z4e}50RD*L{xJH*FbgC?v2G$+n=@OqIVJ+a7MV&q~#rp)KEv(Ii)WPs9Sy->fKKykc zJeygbs^Uo4-4G|Yh8w{i4bPF3xf0HkaK5glJj2upiv_@*tD-9`7V3}Fy)Q6T!`dRY zN<87g9&Xa*pJTylcPhM?Yk8%LvtiE?sb{H#%TUYt at N$W-kZ`4h&2dZ4g;(jDvb?WV zaVhNK_w+CKw1MBdzZPDt%l9^tEpa8xZ`@xEJ8fH|%d5S$EZeP$n_(}@-U>JLY6@$( z8(s(3-3zmZ+fu`ww;H{k6%5nF!*Dh$cogn!{RW*cC^kBy#W8u4ByE<^Y04G}w at S)3 z3Aam1??&%%h7KF?SQ_yp%*H(p at 00=@gHnZ0WD5&@`hoam@!MjM_&8&iE>cp(Ze8^G ziD=Q?N_szakM4G-r>c8(w<|qY-KPt0s at Sg!EL;b4k@^#HP|sAqb4Yh5 at mHI4w+5yo zy3h4{t-46=Bci&Sv}JyJlI+vI+orqKbievk5le8iR+U&`J8IHa`e~CECmq#=!p|2p zwFCP1YoDV()TmPz#P{Vc>e7AcNLQtUjB<^2knyfb4l>0x)j_7a>KtU2tHD9$xaK>^ zLf2vkS?XHhAkD5d4zkX*(Lpx5wmHal*DjZG4Ap-iINRdd=g<$h4m(JztKC65T*n;b zgzL0}oONArd5-HM2|G+QN%wgm!s{H>$EQ1ABZ!$P(Ki7b)kJAt~#>UUAG+M zj_baIJaj#AkY}!!4)WSn;&z>rQs<=xCJuLBkhNTtHI}(0XSut|K}NgBImiU}WCy8s zPjiqN?wJl!?{0LEx$XrHvdF!}L6*5!I>;*bS_fJ0-sDyo+2Xz=>)r0qce+~~WUu>x zgB)_VI!K$l!$G>-C)^b4>~jh9l3TsZqXhf280iW|lh)#=>u%*LByYH{p)y0`p3#OcXQ5|xnlF2vXN=)XuJw#Hd`Z(h z;|yO$gJ--U-25|C!{?dqnP~XlneUlo_*^xf$%fEWF~#tu&h%6pzCN=&HAbd7#na^B zzl62IBaVBLrW$a at 3qMUW;H+1Enr^^ZZ~QdFfU`;hRBOOF!vk?=)fsT^hyb1TPzO34 z8xBumq%8;RGSen_SEOZT*LWM at j_KYxhOgfY@4B=s zSB3X*T2 at xIcP=U%2 at RRaW4!ZVLS35Jn3mONJcP5-Cwdp8Wx6W?vy!SHkeN9J!kJpR zcVk*+>Ns!A&J}4{?@aa1hqd1p^u7P5cZDBjjN-uUQS#6|mv7I at VdsfkHf zh!z_NaNZ?e(dOkp+;Y;ZF5!|10b0s>CnfxP)+;V~-OJ#0>;smgFgZnCfxbu%s4L;} zdPHimP6A73-Lu(6O3z*9PdOH%_|m)z&cA%mmk3?%DbPtz+(n06q4mtcSBu z?FK{8v}Can!^RMsFsk^KJtNEg+`9#yeFB)JJ at szIP!5UBMyBhTcN^Tq at 6uwdg7;OT z-fr(Zm(sG5%aeCt_>W7zoR;Ytp1jlWX~U9tL48JiUcE~5`Nk&i=Cvov3yDllic zOOsnLlg20SLB%D>dr{5lG;uA>mp(fAR+=wkO!DnCUrI&t{WM={W%AiHU)reT^J$q$ zBa$zrWqL;f&+?QdPfCvW(LPl0RZ)|y?&nrtN}&Vnm4%6bH9f(55G?6Q>cQlAVP^up zl$?IZaC^1p03BvWt`5)c3{V?au{l8PhC5Z;8laTCjWni-`j~cJx&(SlSN62c81k<1n8^`28qFbj;lQupb;r?C!J at O<3trH z+J2Vm<IAw!<>z+Np+x-`F=VsF*%Wa;MO;kb2lCev zDsJ_vE>$c`6`NAUp;YA(#`m9ytw}!haB4JF97`-{=Tp-!W4vo;19Zg at sp+~}stQUr z)Tp~97jb5j-C{45`w3MYQ-G-ZRW-c)i z4s3O5dc15FcJGPY(FA%TU7SwjE~krY>EdR(xRWmKr;A7F;%T~gkuF}R$6YbZ^r>&s zhnw86kr{ExsthqELyS)lre=s48DeIFFef9fqf|ka4>-$^@%&acx?#h>RsPNDEPK$ z%#4#Kq4TuTAWb$gdCP({FEcK`FjFi`a*$I+Sux-)^EK(x$GpKtoSSF#tFSxJ|&7n%rXVv*@fzM9=+BKnEN at M)%4U}m~6 zW-l at 2ac`-ash-PTW at foAWG^>;p3B)QOvEs;%*@ozXRkDcro~6`o$PprHlqXXX0PI* zup~%Nvc;Q(B^B?8Y47lF730zOot{38uLg2*jOnYbK|hV_`*z`4s2+62C0Py&hCT}d)SDFDYTcz+QL-o%IUig zbKpeIemV0Gp!?6}95j7>F610CeVJEt4r9KY$~l6*KAqEw8FVryYWh;n<+Pc6nrJtD z8JBa8a)`<4Fn!tAb2?35>iL{5)0cKJ=a}hBzm#*x?em_=Id1y0uH_uYS?PGr3Df7h zl5^6OyT$k_ooL_v1o}9~dkTF2Ax^`?Pjk+|)@M0qQQwoCbFk)l&Usk>sjtVC{>X zi*OE!H#u?LYf^n$NxzM$KD9fiv|nQACD6(J;+=5~3we52ozqXd#0!t6UPgyuDUTm_ z;lkwp>imT8;sm;^pX(}j$!!l^<9@!AI1gQyo$R3-+y(bMv@#*Jre9Ju*X2 at ga$V`_ zEjg2JV;UxIZth@$@<-h;1=seQm#f`leb<9 at pX<4q=;8;g|7IF3%uN`Wt2~7NmgYW! z=a%HI$Q7$|HOwP5RXpZ;vl3ea?$*@QO9|O>e-GzVr|20ABo@%pU}9G-C+6;;>+Ko( z9DTMo_yYCs3BE-AEy1$Hx#blbUz$O$xiO^A>!B%@FXLfoyp`GKUZ~pQ>wy}}=X(^IYWdP1geF^ANjE~%tW5PvXu2gl zBxYEm4~be!@L$KYGE?q^>MW7W59ocFkK+xSWreP^xq+4vBjad)zFpz8AYP^0uVKpLaN>9GQmH? z!ts`!kfHBzf^bq2+QZr at Zq;;^PIGHkC2j=HaBB{Qs59(3%SA^*bdDPy4bgcPX;1X& z1!nCC(ebc2lc?dMtl?t9oEr)BK>|IhxT%&jfhal)31!f1BuW-FB2i76heUgimLqZf z0bRQg*@^x3GKDndhQ; zNc%OpXg<>1qp$Q7wK;tb<;ki z|NXd|_9OlJSvUPRO?=~`n{b5qAJ^RE(Zp}wa?=T9zj at b9Cz1Zv12>&R`q5K2ok#lH zmy_rln)ueuBwB>i#P8fsq9&wykCW&tn)r6PhcYxVXq1O)(AB>?$wM1Z`tcHvvKi at j zHv)WTxrYX6;`feu$kxR7PI+iJSo6<%=qyTq|DuP^A^n4E9y*Wo`_DYoPZNJQ!b at RI z{Lw5gJq7iXMlU@>`h)pi8mNgsUhJjc)x-~@UV2v(f70otTuuDx2`|m?X!N5qUYd!P z{n`Ig zdWQ6WK1-$-NdLAwnO-6NyV4XIh8gnrK2yjNw#8d;8ELP)5 zu0FH{$XGhvhqeJ3M|b zv(xBDny98_X;i3*8rqsh1)7*j2h%8`iD^`mt`643bUKi#iCX%xCT7sgbh?0C9j!~J zE2-ZSGl{<1o=%TJn?=XdX;>dk)YGYSDhD!~E~QfikOsP%PNRV|(v5T)17r@}PN%Uz z=F+`%8V6(^Jxr(ZK<3kvbeaHU0XY}TrY4qBO$OBhSw_<{XeN;5RF^@sfUKa#40u`-D``;%H2`U*6&W-K$SPW&LGysD zrtKND5Xc(ZlR-;>tfd1Pv>eDfYR{mRK-N=d2CW9Nflg=88Xz0#aR#jevWZ at 1(ErfH zW_puBTR_-CRX+MnO>CucJ~|4*Hrn8$-_pc(+TufZX|#iO`{=hdv6Bw?=p#++qIMsB zOB1{4wvWE8i560ieY?=o at G%rLey~@kxg4NG&(_5*|ZmwlQcG) zDiDfJ(Zp=}p(ajKbv7M>&>7m1O(X1TW4dgz}$)O8C z9?*gux(MVUZOEZN)5Ig%nnOc0 at tAhz(DN*fo={5;y#&Wo+LuGGfIOqaIaGqyKd1H_ z>IUTnb>`5>Y)!nRn>kbt0u6y2J(g;<VC{^b5qs5?%P at 4MDUn9;aOZ(AM5JoC1`q6SA<;v=Q zv;s(lGB=l+fmA9Ba_JP1Dy1oxP6HXGEYGD&Kt?O8a%n$$XpC|(m;R2wEI86nlixw0 zQHBR;9NINr*%hQWIS4e$h!Fif;*3%eqSshkCMgp_^ajXeWnPH>9braU9io3gm{GQb zXgXwTlwBcOfHh^R(ix&9K&B}tuyM}SXu5JHL|cK(P%edNJBn(RXV{1WsZ(BICyJt( z%4D4mfHF&&s?)zB+$gm={WGGCa#^Q;La$Jv?V58j9X#C=C|P0J2kAWKk`UUCK_2W&_!+v{=*(q(wPwQ6&`a zQEpjE3n=@PsmShCM%Z)`+5Jj|O=CbgpvzLZfzIFiJRPQmKu#$a!n7F3Y2|vDRslJq+z-=Q zAZL|lVOkI5oHEuFf>f2hEreSkQc_z#EQq?yABy5yrX|G+Q(}cXHS>p5Jd0_mvBHd4 z;aoC*s1(m;nop3XF_IN4Y(=43+{mSOtWHY--{J?pwLykoM~T< z75+l3unUFm;#ExhK#9M^Ttmyq0Od9FzQr3sX?IABCP`@3;BSJoc1*J0{qP zGH>xNrVS!eHMR9apuelAnk%FA!`9ywtLrire(U!+QU|#fSC6y`1jV3-{<>8vniGJ$m6Gi5$`k_v at dC!Ta^X z1A4(vBA2a!$3h?fL>YWQFWlWjI4%jhbyWBruF!7%Fw&~~_+t#MdO at Ga6>H$hP~J|1 z^7_!=Rvl%7g!rzadXxKtv&r**N6~s?TW7Rw at Os0ec-ZjYQ#5b#pR|5xmSlB+1Lgfi zTAmQh-HF`xuC{jORy|6rIQv}`U$u&^S^Y)Pbqj*g&)&~Ooc?mO=!W%Krbh at y@^4xK z6?_jz+mvw%TvM(5KonOig;$lxEelqP;;TyGEj2QrXun>3OD*i|K|9sL8WpTHYT;`Z zd94)Q<@{Z>@VfGeD88-~&SUyK1-(#s+j_V7wpDn?YK{C+bMYOk at Gb~`fWr1eU)82>Z*mjzFi$W3)X{!>LMXjBW{i at aC#*!uWA z%5Rh-^$%hSZn1*zvjP(eZb1S5XZoO^N9sv0sXvTKm1>asBLz~)kSf)p#ibg`KjHFz zD1W77deP6Xl#;Xz7myktkoZAN;uY$7r9k43St1t_C90GtQ6YiR(5m%_{E!)vFlV&k zZ0Vn{rSCb}Xf|QWwMJdTm4bRDzh03xrwrUpAE(4D{?oX4DU1IoE?$}tFMV6QrI+}h z#Ts14(gmuToboq3+`roynW06q(lB?B7SGTM>a_ei9MXZQ|xfzFQ8V zHVk#R$vj;zvImdy8K#I at BUFcZhXxY(A%aE9s^Q>iPOw0M0 z79hpvzfjB0URwS{1-5Z<=-yw?^* zjPerW{h=Di#UWaBaILySZEq`jW4&{#yBpR1Rm=_5TAqUH4YM7!^rs93P!wvvR*PEBC7<+MkS(?oG8 z7ys`>F+2U!Ud0 at dKZ_S{XX$@T6mMtge~K56;NpKy6p!HIe~ESIL-y{!#&T1c_upc< zS)BX#SO}ek2{Vgj|3kr${}vCrj7|=;{*DZ+vEFFpYt7EWI`Fm at UB%vt0^>C|qqA*L zv`Df>eMh^yBg;L!qC)%sVeP+$y6!1B{Rt$4dQ|$Q%I`PxbSe0LH1GRVG|WzDIp5uF zLcWAz=G}vN_t?B)6tn*-+dmw;q2!bXVtf4^W=R?*%GbIg`Hvm7}Ki9wqHXBWz}ZTiS{wSDWuR#^7f4-QvwE>>0$L=Hu4*&>B<<=AJ?{Y6Qgs z*h+=BFun|UB`Z>9x8{wsDKCp8G4-vj&Y&n%mc_lXfk)9uQj?NXn#ldNecdE`P>^@Gdp3~U`@<=-eT`NsQ0-5ddixMlej_YQ4{*8a{0 z8HLK at ZCS_Os)+afI(Tax_kCrYV-2^qia0(F=Ei;v;bODPncd%nO$8^lcG-2 at NtoO? zii?ad at dPibup!;_z7J-8*~y+wD(cXiV!^S(mNOqV6|RA6)^PufidTGtO&cB0zU1tf zxTP;)=}WeBEGZ;sWMqTV+BArNQ_R!q>ul?QPV{n7rCp+SL|e1J#2eHqyJ(ah8BmV0 zHuhgzQ4dFkic<+TF<>3UKN)8wAISz|tY>wy_UwUjT*{VqM0quiqES50FmA!q#-uiA z8)Od3;KgYJ+dKg$3mLY1MjX$>Xwhi9B-^Pn8$1)^%{}1E;s}!h;dwP`8z|o`SwJ4t1N7)c1hiJaqEw;2WHCleWUfmXeJi6f;HH at tWhZ1Ukt{c77$xg zbMP3u at Cf{KM9tvIeM*IB{wb9}G(m*YS#hOX*d_IGr)_~Nwy at J?$2q2RwGDBO=}ulQ!#v;CtwaJHAR5(_RjI)c!s)Z;mUZN)Uwr_7}p?68b zHa at T{jq9DLeSzNXQtx)P5T(Tvwf{u#G8rv!(1CoUNs$_OxopC6*}*MXF58&J=jPZ3 z7f5jvH+DsD;i`mi6@(|U at XEMwRZJL2aTN(um}2S1}oX4e+mt&;uM}HxVtvQ6_1PA0Hk=F_Fqzbq?h82amCNs;!SbI&tnz?DSrN6 zv-m(S#hc at bkMhvo5?6dQ=64{)N0tAQ;*MU5x5gEZWsA2-#e-$`3r6k|IdYe%m>N+` zjGw9P=N|f;io1GMyggp=cCL6wyyER&R54}=DxRoe?nE(j{{L3It5?N4;}zfLig(G1 zztMBly^TZd2dEEe at on`#cjKX6N_UeAr8*xpRX%|s!9ScUCSgPNQ+ed+MrAE>P6Qpc zbjgODwVsB|w6IJ?cyPIkNpAyDv8Bm@!>4 z#w)<2`+<-(?2p$l5~F4$&(;I+2CPTHY8<_+r^=ZD6UMSFKS=up_cv>90hKCfoJ>mHJfv(+|lYF#*Z!{z_wgUNQ~p zVL|OtIRf5C?NhL=o?=s3M|9vwXtU|3oo)TQv#_2=+n_72MwUUOEk1_s@)&B5XGd{7 zIVv6SuFT5Tj?!B5r+cVy6ru|Ln^`g9Gfb#RjnZ`Tg!V<0EkUQ^?TNIaQph4#eiXSoumvG!Z z8LwokQq~bUqLdJ3P&S_nEA_U4ReIi3o9YLS*6ZsXcb)O4Qgu+|rOM|TBt_HgK?8Ywo{y_vismn6 z$qQ1FSD^&^bbHVb_^5g at UiC4q`cho_7^IJ}^yN6mTIRSSoByja3m>lq+uE3q8x6Ja z77ZxAMQ>dNZtvyst5SSBN%?ALhYSo;5gWbC0T|9$Pj5RbGJIOFS{=AK355LBs#3T> z8Bn}H$@nUout4EGB`EKLQg~Y_I7eX2gOiGpky1<=qce$xc?gZwc?d=qFS4Ac>Bw3txY`I3&vq!j+0Hf7I#b|_v`hz>l)Fo zjohy{NW*UUudqe!IUD!rZHrvW!Z{r00XPlY(a2IIenP)PIG}P6th4)zR&BFuQr?AZ z<)?iA%ekNaw5wo}8qJ at Cx$#Viw&y=nl!8|5eOs~jg%vl+jVg;{Rd%)aC~l8e+5W{< z_LEh%yt@(Ay6gmACWfmwMZ&B(&S*c`x7nEv6PK;$e>;D0zI`b=S|$81NS7ada3$ShCKonN$C%BHdGEKf9VmWPV!J+Kax7=i7o z_i*dE at lI=7^BZi6Mm}klMtu^^`-CDVspoDehrRlZX11u{8_~RP#EP!CqY*>iLPcs- zc|mAI^9&j=xK@=N!~9>5=6#(8j>i;f|2?N~iF;`#H)Og@@5o>bc&OrGZ#!(Idr|VC_~# zMkZVMHAjJTOa6SD6+DxdFwVo==Tg}R&hW_$$2c)~qFHe{6QiUDW5+`5g`8jLN`oNa;K^VFf>lA3=>6$R6^{OOt~DA)4KkxWtZr>JbhOS$rMv_6Z8a+YJpD at n?R z&H+XHbTf-r$^otU`*g1Gb$sp&k3DByXb+Dq6xiuj505{9U4((JW1e7x3nuJwc_UiX zWMePNo5rHWwzF at 1BNq_I58c%B(C>54Zk)#tU?@@8x?h5I9g6Q=XzzgC9SR06>h-V< zGJJgYxk$oS$8pKij^t at bp8n4zXGzK7ammAuCkS%M+E)Fy|`amnY{9CWPSBw2d4i at hG#HD at BQ zNj=MKk|X}RJlVfXeBP1t<)5QFmlI{O?P>Cvy=>SF*|3ocF9^Ti`r&&W at 9}d2AI=W% z4vLf_h&J_%h3$N2m1#p`m6~6LAvhM>8AW{BQE-Ox&XD-DvtSx8q|>mFIvfMAWRxqs zSKtFdwNlVR(YB9qoXTHelf0>v>o_;HT|VCbRW#{lA*P+)!Mnv=+80sOJ5y;x?t-qq^=<a@>$F+dYAXz zwOW3y#@#$hQQXNX70$y&>}$K;?Fb?SIDSxF#Ua3P|jm59j!pBPFvBLOcrQn2;e?sB7_%7mN zV(?Y>1iAe@{+6(B3ME>+UrCI&UQC;nWih7BF{Y}wm at cXyIGQ9{d{Iq|n_f&6+Oil^ zg$Ab2x3HmC3md+mg$=!0*nk%Do};IEP=Wm(#9wjxN-db7?z36`B)8e&C;X1X~MB?&c zJ at OYJzfK*{k+I4-3VyK(pJKFDs##uXX*&!W(DC!_k<6OItbti`n7`OKW?rjE=Cw*Y zOm&#Q*ivR*=a{h$X0Y9S+8HC`U>JLg4)$>ex{A|@Dn~1 z_9#E1Q+R9VEj^ftb$L{Lz0Fh{XpT|kYt{~{l%BMNfMG}4MRMce+4d2B;=g_LPI=x zA>xvZ?{oI!kZ{-{>CJr99+Hk$6d<3MPEKPQzO;&Zn#1e;5R?_Iw*fXV*vMcLgUvRx z4Dm*`*hO3IY#i!_q&he^p?nAo9Fm^D4O((yMcaR-D4UB=y&KgdNkWpp!^RTiOXrek z8?4LUX=@qZ;k^OdY@!wELtJ3&uQYr*v`-JykUm5D3ng0NVDQHfK`aZe at ihofm&gi8WqS&sB)UqT${sV^E3XQqJje|6A#F1 zy?IK{n*u#Izp(>k(P13arKyY-ZFcoUH0+l7)dnUyZ$g#$Ve@=N!}j&G$`Lk=_tGB> zb at 2gEhW%!&1d1r}ccKd3`XYgJNse==oKuq16^%vvlWKM5FA@%Jd_Rk=09Zh$zch4Y z<@1{%eEWeQl;zCztRY%n7MX*O*ctL#30b}fX|-iQZ_adHmBNWNi`toqPq1I({MV3g zwfXY9Szdm#oozi1ZX4oiu9j;}8(#vWp`C4f%|68S5gXq&aHT$Yp$CO)6l&x10BilE z&AGlBT#mTTcS))8E~$<0l3E8xZGH>;D at 70V$Z2%|xMyWlLC=ZaT`Q)#d4 z0OVE9$18t3A7hVKj$#a!1xk`9Ic1m1!JbbjW{E!3{oc@|D988K5X*S9|$ThJy=KMC%Tl9Hi5x*$uOy>>$7XHpC4EoO$a9gO77K at RlN1%6CdwHxPVS!pR(TGVM}G^5%D;k$k1YVeGr at d|7kM z78&pGq4j){GceC5wdb8rqWlX<9NQKuJ$ID_UyO5}@i`t2i{c6f%7OFoKq?ss1vT2>S5Pop9sDw(phm+6sklZf zoULLzR6Ls%G|BDBpYx7!r-E}l|El221?$yl-g=csL8_BQp(IrE-A-W(;(Loycobp$ zD8GFzZbs zJxl6}998AT^g|_5qC{GhyoZWXVmq@sLwaJmr+p;)6Sh>{`6!~I9!VsbV{A~;+# z!NjjT$s4dCSc+<5Pe0>h!C8vFFCW=R|hJ#MEfdy`LlBH`Dj)FG*es0wR(KC}#3fNRX#WDT;ugC}NxlG82$D zDi3)G@)D7}>$mnf5hs#N=$=1 at kBqY)Ywx|*ey+XuT5H$4Cv+wD*XKv=ua~GPSIG)Z zWK(4*eNhSAQeEWCS&gPRc~&>597(9;(~G=T&#V&2r4&2JhWbj%$G at V5MQT*ZO$(Fo zXfdhLQ!|WkCywqwy=y|FC#5xz13X&=6^7GM8pq_ujVh>2sT>C>44w{zv;T(fZVo3T zYLUhPrKFd7+l9&>f?#Bfb^2sx-2Zy8#n)1+4SS`!q{u=Teoi(>XlGh{( zany>602aqvmt{F@>$=R^W~eSv4;4zucn4UxP|J zgKk-5KwVs5Akx)TTg7wfhw7!xRCEfs4g8wQZ+P8{Ub~Jf=%>zZ(+Sqzmupz)b)f?l zs)&}jV=Tk`rYw8P$`|nbvfSIK(KNz>()y3G;04Wo+;=*1;@ipS97Q+(QD%H7*|0_} zz0wi?QNDeh;I4g) zJ`TC1RrcMNeoZc1iCvR{B9 at u^$xVC;XGwWf+PA;yuS7r@cIEAyUp6WXgP8u zH47-CMT~lKs5u{Ibjv0y2 at O|*nL}8+~qZA zX`A$S!#YO%o*3 at q^-oIpN>3{I41Usw&!8vEelO`K>a+2a)M5jllz%ThAqV@@_W1Ww z;&?rL at o(x~u^oP*KYpBGkPqfsN at l4`h`gTgVT3*I8u at z(p7g2u^yzx5)Pe-qlWvZ6 z$0quzr^L_H%N&G8IE%vEsAb0R2?Z-SzLX(m%qLart65)<_4U>qq9*DqlR?OYTq=C= zZy~Ywq*~L(_^)7S(N|FqPn2Ocvw+JQ&ebEK1{loI^!31RxxIiB?o#xyAu3ImIFEvu z at c4K2Qv7^hH31fjSf~;I$u+EV*odEUgHdpu7tP`hf{DXa!|8VUOS~QA?RWWl_pPw3 z8O4ks2S11v)A3Q_usB9lPqe>Gz}g`+~WP>rvHB86L?zSNrqQ05oFo|4m5ZE zaxLrO{*oeCzyWLt%QtWU1RR7i%{*R+{>UuXP{_R3r<+11^+Dc_pK`<5%m__7Vt0y5thj}1v91_7>io at nK|+ zJSt5E{UVgn5jinv)F4;p2<8Yghtw&SmrNWZM)DZ(qCTEC>?ljjETxX&nw_DTmbjyt zw}c(v#H2BJ@;5?cXP^`KbP(sj{ek5`p1 at u8#GU zAPbV>7O5;&e-6?hqFOg|wY~@a_419pX1T at X9TqMXEQv;?SS#2XXzXB0j4)}|?v+x& zv!R at KuU&8-wzsvp;PS#ve=a^QgiA26d at O?JW3jJA^=n(2$J9Q8!)t{VK*a)Qisg+b zTobxNAPPq)D#{%nLb>FoV#)n<7da1eaIdA-{&%9 at UU>vbP>l1@%Hgq>9M0TZB#PWN zP2_ZvWX%Y6?qitUrTX1WEGToQ0~ZlDc_WqE*oSi zGV+$`TY98#foQ!9mD7L16>JW*iRF1hD502fMT$?L%IV}`Ty4gy#Vt7BX4IBhA`}w= zp)d2~!uQ5c`DJQMxZ~`maqy;_sv*zPmgL+}o>Htnx{)`*&wxLq?c`#2Q>er2cL|bg zAsU+LXF{=K{`j*Y+G{34iG6Z>pX|zw3dsP|EtxC=n;bHxgxK|5D0a%BK1(K2#RC%5r@$jlitM2Z(e#!TUQF%&ybX=e#vOURf_u16=AI*?CqNkq5^1#=Ml{7EDg-mgFNueLgULxS{vm^2lc1#QnyN2lpDBc}* zjT)*p4ec`1xVoY0X?QI}yUAR`W||3=e7c$TOylrJyRc34eLWPG7g!%$7Eq at a2mV{R zQzuzSm1Q(SKv2XQ>h&x~T9V zOUz&jW)SmvMha3LQ!(3QMX1k;`qfv+LDVqCd?ecf$g3fl{c+*AVKsHL)qvQ}!~K7X zPNJW6o8Upr^b~J=uLs*-^q4 zwn_+|YObuTe47lhn66+G3&lj83)zH5FMWNY^qEg})*PU+QBR*hbsP266dlHu32X+e zp;{77WCLedYFL9yuR<+b%gO&NE+#iMqPQaeNUg*zRL;GB?6i=Ij at +hqRVp*i!p ztPvA&l9@@VllTYbPjC_tY!aHNHJ4d!y6%V|E#Q0cl%H-(p)&j!M0YgPVPo8YQT3R z at jj_1@j(!OzJz$bW_VoenDZE7Imf;^Bxu%(GrNBrZESpWqe9!GUVuwdpfc zKC3oOo^dhQpF^dX;qCz*D0u_PjvRNt7n!+_uMDBb<7L5{!Y2XRe2gXRc2f z&nBtMwjGssj41T3#5O6@V`3$&8(0cDd}Gj&8-tGW>OBP1 z%<*OWm)?yRZ*2+kOJ+^l#Fik~^o7Q&ftlW+>E86${sGuR;&QbA?bJ))$Q+mK`<$=+ z_I=Jlg8E)!2AAi}@A*2_KDY1G#?(H+i>p%eTP)uc!fXtR%cu8g1V7*}aN2o5<6H6c zL2UxtIr&!Bd`enPj#eh35tBTOnJ|Xd@|Adr&H-r;%T*bh*A*8<-%H`wlhvatF zT09|*OrDSu!_8cN)Nps^goF^6>jdZY6Uu3aKk=MQvGM0*H*Nmn8$-~#Iw`B(vho=; z)g at OF%ehjftt0NlzvgXsar;O?-*C``F1XJLH_fHkFhW!qo4-aaIa5h|EYo6_(&8V> z?hJWfQuLV$AF0n&ng;UDYn|LQFfohvy|e12_)Ch+EJF}$j`=H>U=W~kC(8<}|6+MJ zk%O6we}MCcE}ZwD;(IvSa_cK?ZGqlU?{J{r=|H_J$CwiVKwL%L;0gz|(=?o>{2l(3 zW>r(uKyWlOm3Hj(Pje3$Ae26Rtn_KdH_ at _)_3sQv4lIq_Op&Y;dbYQuTdbtt$~P*L zei#}VM#6I(S6dRdHN?rz_m<~2$}LDfG)3PWy$ZPk%=Ak34bAqc7A>tVg+foTy;@a zZ<*!R(kx$+MV4V#A=1v0tzBKBt*GStOB$&kg>?4_eS>F&K6r#47@=2<(0?*Q-!9>A zba|clmx&}kW;plgvbxMRjN;3(ZIyGm+cuH;p%NTjk$DJ~INWyZ%0lcmimlB26NRmE zVZ#Tmeul#zb)Envu)A=`d$HT+Xbq_CT_hd>;m;_ zfNtP^W`)*s<^Ghdm%Z!O_Xr7!Jp at L+SWnW*CY+AS;P6_^zD{qmSj zeWyI8%hK+$RJGVPj=onh8$J#7VjE|oYGx*?EH}4Tp*$OQgd_?KQeZ(|ZM`}Mjc0<$ zW*Hg4HBLq*a80d8PW$DYC^avncHd=8*;LzmKy?HAZxGAl at s`whRg0 zcgxh#{!&=3NG>Bp>5mn}OElwdVqRt=cldi at MntjqRl5}ZnpYfqUh|SKe$6YTfABWl zi=|G#H@!X3bT3Zl3b>rbIM`J19)x#uVOSibVQ~<{VoL#HF_8a|A#P`{ur5j8%wn~u z&bPIY;9VrxR><;Q0(=(-As-bWS^@EK0iu;4S^=@W0P!*)K4A!&eUmQ}#LH^pbJcA4 zoK5k5T7YZ@+a|WXs?b z2>QH`as!IU7Q8uRWO7EZu6R|6WUea79Oai&N~|mHcW7eg3dv_8w|vj!GYRjRYDw}V zj^w)v$yXzJN1^UkQ at YityH4&LmieHqYQcQaR&}KQ0n&7Fk$+8De4tuJXvmTIKuumF z6s{?-iHVbttrWPm6qDT!Om-JA*@Rs8aISZ<*X&;hb4iqbQ)&8rj`VvA>D!V13r}LV_Jgu)mOC2PN2HB{<+naKP=R1UPWp!wMesNpJT)&6C0__4-$e zzD{kUdHcu!R^m8V06#9kkC%YIuj*@^;D-w6nCJ(G3oT(%2QV>ojxYo`zfO^1C*S)E zLz=Kq!3Dx7%Q*$>a`K##IIm@4 zjep^^P*F&J0a+jKnfwAFexbDI9&#i@U2h9 at z1M*8 zsY2I#&F*^S$h_9G>usUHEq!;r(*;aEM6PEFUGGDy>(Mv!VQKngTmRXq9pli2+Baxo~$vsL-PjnH-q#Q5A6^P}%%x+~Q{juuR zZY6Q=sC!+R-3o=EvX0SjrgmRGzbu{{b_iFoXQqMG`jr+0?h1q%z$=T1-7=`o$2oYRwcdUiQI9ijGG z;_dYD>hS0aOMZ2{ien!7{QyFKDUUH;^?JM-PWA=~t1a^a5&I(WW#ObQb>6LLSgZBTQqCB{|Q^h9~lEXEaSj4+|$F(VDDZkN#^ zF7g;%QZONG>Psa1&}fNd#+OJ^dL^a%GfJ0LSiL)|6fO&I2d|n{^6AcyIk=4D^5Xh} z`P9SR-L_Gen;{bgG9ar$kOgY_w4oP6FqgOzYjI at cwr#=0g%y>{991VcM4!kC(Ic}| zHD>EWU$Fk$6=r$=mIYEkgA at 27fKA+`v0eiU9%ge7y`)N%L)%kb`LJt+Q2odg&$&v- z+d`=Gd*(GmT#3#MDJ3$rGw1~DRkzzyUa_2Oyet1yqmp~7QAMJN-Kbx?Wny8+ScZ7F zE5dkzm}P5z4y7RE_*7SpGHs=2Rd3XOqq9r4R-Nk|ALzY>V=tH-I##p>Lsi`9}l zc~${v=B$!^tedr)espVu*js!-a#ZceJ_ef|hn0rI#NnS%igR&s<}HTxoRb?)C~ke4 z8pH)uvO^(frbB^UTx=2^T?@(j#G^P#eN;+Q^d)b4UtmCufF;ifKfwF3-vLK~x&YXJ zBlP3YN4B&21$mac`Q+`WO0M`(RU)ElYDB}Q_le?ecW|QubSzO<>e115Na&fh zKeudB at nkZDCX;ObAy${d1*xq)lv3iqlY~J5Oe%Y5QV}ZWYzp0cYyo%Bv5HD7+ZGM| z;9k~W#l095aPDOFn;s(y`s}OJNM at Cqee4?Ld{d>)a;2Q`&@v$2JSaor$e6BQnu3*^IHq)l_7%%Z>B-lRaDbIjA`UA8(cEfST zZETW>x3J4RPGy!u0Z6e9i5(mT at j<3p;A7gr^Xw^rUFiX4vo&o7yJ~3!Q>oyZ+K at -v z)1BbYu_d;$dm7wXP9$R8-aX7?5r)XD&C0D|mLek|vO~;jWJl3YccBm*kz0Tia9j~t z=Q+FM3YSksTLmI9XE#aZDPfX|2tGl8o3i0E1k6O>^_qo?I6MlwD8V=ree5W>ILc}W zOFdZMNsDQrJ>GQ-?L9V34CZNmK!|GL+*X0iSB;Q|N>o#)td at jORh59afG_1PBtm8nCpnAYjE at Jf_HdDky}|vJu}|ph>h2YN)mC=|;_}Ueqr99)e7-uZJv( za3#W2a9B#IP at xq;Vb?J5T2P_2pg*bSgPWbSgIChmhs#STOdE!PjD~W9Qs0KoQl0B^fzyXIVbm_6P;M!{v at Cz_HRvSn96` zKMwhfJt2H((_^rS>TwE|**i*?S!mQcRBOlg1_KRShpO!ah6yl?WgX>=Y{t*XQj@>K z0XOkRIIAGQnUut2dvueyL*)6gt&0W5g6$3~N at H*z)r-L;!HXbHfG!BM^LgSf1=6F` z=MsZ)hBjE at bHiPc3o+> z?kr^yrM!o`tKIpizd~7cuxQ;y0qas|Q9Z>#bv198^~LmyDgB?itch7 at W{UKGBGNAo zSQkmi;5cVMte062gG!2PxqOGfN zhMY|)5B={5=)Th3or6pnE#tJTWBbiYUDUv=!j|{53Y$3rOMKxR`C0A{^Q4kBo~rwp z*q=0&9%=tKhpBr2+FTzI5D}8x5t^%LQ((zNvn00&GFj63PF8IGrETT!mlS0gt1Ad| zE4DB zNNAL9g;;`gePGmYq^93==CFk#fE?>}^1=3&pmnfKSZ7&|bOz^iFHS5T!$z3FKD5C& zzqfkJkCMKiXtY;t=F^VHXajs>9pE~H2<{|;`Pl;^7mvK%SS|m?8n|%-I8(tv!B2(S zV at Kf?iyGYg+hdz$!7%<#c6Vd5{vr>+!Pybe(DEhrz|z|uS>g-5pPc3~I*V)PAB0@@ zL1BtfKPlT>>q(imiM%&t(Fk at s_J>P)?hmmKN}iO>%t at KjW{r2yrCJp1xW{06^*~ne z(MFB-U?y$9%n6;QwfDkY$GbAZqcHvc1<>{slpk0FANnVc3bU1u(q#Im)buEhvS{0g z?G}dW=c=VrvWZEY&2!~!NtCZ7*QiuLW{sN6Dv!G|S=w5OW9z)**jiLB8(2F706t-f zpHXDwpl2!fLR~|RcbY1wuy?e=zn_+*GF#U;YRoT7wxDQzjoOxJQHypfUs9ABLQrKX zUr(^<4p@)5zp-cnhP!P6 at qF6Uf|e#_;w?2F ze at nIXk_8$OLL;Bvp*7u2_4q4S(-Me#<NB6tX~F&hHZ}7_2s}xuoOOJRFG3sy0ONg6KK-f2f|jtE z#}s%$LAXdkMarA1And7eh?a^x&E=^|@+A1Z>;fePZx3^8f>BNt{g^j at hd8RA=IKH5Y$f#SY;W#%~UPLmzYRg z#6*Sq1eP!y#jX%dW?I;npuVXD^IDm1FH*OV71T at BFoMgoR&dfch1aCJJ1C~bvZ}jWjW1VSV`~3N>{=FxP4?X#X0ZQ1(5R;@e&wP~8l{M> zS7}0qK*W8*p2~gL%H1c?PTkng;n at 9Dv8f-#SjK)Fn?B+O4us^Q1i%uaprYSi$yRoD zA^-{KC#1x|Q0(aA0Yc8}Mx z{Va$l2aW6Ahs6yjLGV6wo%%E%avzX!CEAwbGs>sCC=o}mwW80saTnT_<1`}WA zC|e#FK%AqQ7=R1_MN`M90SXm`{iP#ZNG;?cfgmIjXz3ugfQ3mP-dg=HG|qsIXs5nd zt)&bSbJh4<)kQ&xl{7!DB*v!`L(x+hin)IyqCp`)iBA(&>Y&z zG80;*B5tST6^$nrkilNc~+096I?J*GjMaBt37O}}|#9sSZI zzt*dNtKgu5eBK+bz&Wtzjd#k6SIiW(vb^FejRoYrPj{knsoxv`4%A(qZsem at zS$Ed zEmy-crctxdf(=Uv at 5|xr&S6iq73r_qajJ;BT8LxAS4#ZLkY#O{cno$UJjYHpXvwZF zaq3i2a!%hOEFjy*<&CO1TB;~H^A|vheMFD5XJTuq_B_ at xF|8`(UX1{8POKJ7X!I5{ zYV at VNV=Qw%KHH&k;9EK4c5ieJ-I*jQ$jaKcXmyFVv) zGh7=Z(H5>^kiXd)Y^mTNRwHo(rlBXsfMPud?CBFYD(>y-EooKaq2~3S3ae->61&c zLz>K5m8Psb%~z>%SPD~AnweI4Sciso+Y=f!v8%kN(HrHXKiC(!D`$Q4VrF|*ZFp97 znYAA=^8Gf at GPXxaob!%4=XGWFC=?dkpwcqtIEkfwV~>;CYQbxW)7g|(U^e#f2{YEb z8}A~>F~OHU9MDfIybO|`_!kzZg`GHI)}JyEmf~#QQXC%RHP7RUt)a}%%Ae$1pj?Qi zm at BS73I#xO#(@!*?>)5S??JmzlMtWQ=%Z#y`Mf at AB>e-b^!^ZTf}QSm>>0rQrgS(ABE#>KWP;1G2&Rq!0g?aDsxsO;l?SN5LXDjQzN>FDD))0$PH-!2t5 zOoj3ab$`52_s6O3kE0V){}pvleHeAWR at Gm#>mH7x{1F<~?d-%BD z{X4$w?Dw~LAX!bcmyc>McV#B26c#&-b|Hs#I_w>OCm#%Zp`6Zhhjl- at rJg1Z`*v>< zGki7T4%KEKCkgg(60whyXi~tloV=yLlhDx)?t2wg#l9EmfL=WX>)X_-atrNQ zD*Pia0YURr^!*Q1gF;@jbF*sG%I!5(|54%G*u at c!Rqiq<>axVU zt%HH0fOof-cLbZ+RtSWK_QK{gZC?05;IH|J0a8W}*(VH*YA!LDdVqE8q2}=H4EDIt zZB#c|GH)NT%o{6#4Sct|JGoxXW!9 at iK@=3MS0h5xZIz;V$}6(6xTRyZneVG at j<59I zp1B&w#XD6EF{m*d0(_vh at -04iY%tWcd!{OF<*RrOBQCm81qV+H4KjKk{sNQQsO1Wl z2=eR_cTy>h3SMRTRPwDY_%vn~ioVst$4tc(>I-l1`XcTL_`~<1QR5uHx63yKLQE0N zH-TK_msaRgP#mBpYPQY{t=5CLm-ef2itINu`LN?$&3k(o=bYdZQ^lwWKB;MfZy0ff z#RX at HO33VDgAUKAOW@Ge?xf zbQK29$$!zC&ITH$tBTq1b;woo-z5iRTTjT(9FPwcAt$RhKz{Ckyu%53L+Pdi@|=Yy zO%?BYdjfVi06#7QZodJr%K`W*0oKgL9`;xWYicO{Z7`({)-dkbjxdFsw?m|w|0qH} z4_F~G7(axVb_hR=hL95>D at 3XBi;$Cc2#-04Glexi|JS_g9sk_jt&P3j-G05h<9c`Z z_3pd6yZ=LXH-%yVuXb^ZeS#J{$}RTyAn>R{K4Nj&GDmF+k1~aw4ho-kLbmpV-0gsT z6m=1I*2VE+UDz}f>%yi%W*TIoVM}+n{NKB~=X7^(5oR70O%qFuxHc9RlF%HQJM0yI(1YoyH39#qNTiZ(Rz^dpyVZ3ybTVVAd(Zz{t zKcd*BAXDz86IgwRyY>XoPq at CtXE4ojsb!FbDlCH}x6>3&f1M8Hk0dL9&^lMaN*%P$ zZDo6%6o at s`$Dvp=x2iX+m|IoxnjBWutyopDbZ*6B7wJT*>Ge<%DN6e%S=!$!3y#bd zQPvh-QGFbW6?Kbx!+N^KE{oROEh??KSXj4MWo_ZIwx~IW%o at xP@u} zSc|x9Y at zZit1V->v9*Ds-tl at xqbmFbj76C=y{@etjz>C_r#sR8m~zqkL|4vH6wSkC zoTlLPM4#0Rf9x%fBgjLH=8wG2*3D7Wjd{GINW|HAvm>~RLAgwUqABS4rcX7?2>l;j zRcC8jJ_<`|d{WonsUNw^A2oJC@##H^tEl`n#qClPulPp2;&Wwosma5tzQUI|tS0x- z#^0COrzQ`okGnDl)np#uyqY|O?;$n&h)dt4W{o*M%ItkbqRgOqk%#8RMymdb%D*kj zXy?`bzre7x<*;EEHt`&+%bW_upYussW15fGfn&r~=*LK&NSmT{=J zG8S58EbLLnAu8k0jb&^ul+lGUwkU47qH=}AezX4q>V)*obg4u+3k>Wc4q%RtBK2o| z=_7#wkfo*SJgkm7tfD$=ek}s_siXFBfCc(#U;x(LSA3L1{1u<1FGm3y zVtJ_W-YyQlbez6_$W4ov+;&@tv4ZqgCaxM!gzlA*SHoOpDz at PmyK*qAC_MHVare6* z)IHB9TT)3-&0~<;1PH|GR;m4pVoP|@8XdI!XVgJY->ic^O8-Y!*A+TxmbLmA-QIiY zCycWXiij|vN>qHRC{ktBlLY;YMji1PExFViTQ at jE+l+Q^VosU<^^n-QKzvI;iq9#N z44MZE_qZYtAWp8V(cy<;Fj~hqY9IH0tA5e<_i^7}B}asw at D3W0l~qFO_%p({vI+tc z%0}5bG0zHNXUkHqekPPY8{(4!IKK^z1Sr%C9oc+N+n36!YF3ABb{G3#))D@!(c zq|G_pc|}z!t0tn8o={me=Y%R$b55ufLVR~t;ge}>kN+s73A7yZj^vnd8o^J!VX&pOH(cV z)_m-wgkIWtW5j-r=qhAz5gF{}>b{{j?Z?`$flTs`b`$g#Z(x^fo+r+w_G{koWH6+;C^; z^M?zeyK?D^R`@0v?hJkYa4`h+grM=MmN>6AoL60}S0LV$STjVw#Aga-{8Gqe*^|^S zhtgLqwO8Pk!La0=U9IBrzNa!TbsgcY%6}P(t1jQo<5c4SK z)F=T!K~qtvLH-k#P at 1Y#m!*w&Lzje(4|Tw+;KJ5;TUpYW*4a<^7UUB-hhB-t$2#~~ ztq!*c=wJgf+~)nngmy%AOm&B7&abSxk42bfD3Xxj1iE(>FU;V2pGOi4tRybGDWc&r zPripaXIPN^9X2jzcEB`jWyiUV3!$98Nj5Hqu;?2gN$_a%-<2tz} zsc?0N5_4p{w0G1EnR8-8 at Qj*5tOR|JC`BE5*qABm(6eunSTZK5{A$QV%~BGWsL46% z`meYpqbZE5nInwwnCysLc0eP)i)B(LG^xW`Qu8d61L64}LslteLQ_9|J!BPB#s!(_ zZUhjPX8Kx4;Ay6R2!Y{omY+4#RJ0{OcT|zJ%Bl%ciKW#+jcWFxvyy8}huQDH2>Yj- z1Hxc(iP2NbE~#3yOR75Tk~S9&in4vs<{J%+dL0Hv`6;_Ed3mYFh*(OCI{l5ju|c`^aDAI~8k==4Vl+Xm-qMFRs{OScjJq4SZAw zl!3YX2Z{B>9Kw9}C3j-KFFR_#Ps&_!o7sluFh2&*gA@|Akc4~Q9fa{94C#Q_;|Itc z4 at mmBurV(DaAJIT)cCNgX?(cDY?u(H0Xs1qF(!p+@~-`hf_U5(OFS<5oR*lXC{DOcV{(`k%%+4PxAh$T=8UOf0xz#w48i7Sv{ZR|S~xv5{8VGn zHzmw{r07jg58GmKjb$l~z{B1B at oIxF+}&>7Lm at QfTfx_152O&pRmZ~UCPJ?iBd8$064*YF*cgh2W4j{!96|v0Flb@=A!q(K|nHBpK z_p~ZrSp8OQ_*TU%c#_x7J7_sQP1E*i=yFAfBf~3GV>RoQsc&fFsOo=RD%yH?3sdj+ zJt at P|YIA at WzAE&FLb{qAjD?6&)h$7-%Rugv0_r)izy8etw)8kLoHNh~BWBkYpzbG;CDNp9CE#jRFWJ<79MqY{%k z1kq0yj>pKK2UQen#;%z*k8Kw5SI#s*cl?W zs(%~zgsu^6N$~el&JtvyNfo-UgVAz0UxuG>CT#a z2vIO0&&tA51abDZ{~sZ$Kz?%TKLUI~_#YRF2brx0o-3`_OW!L>HQ3L at +EL`iaZ)GC z$D>5z&}^KV-8bO0Nq)MkfK#*E!l~J9`vp$TZgK;sO)}%u<#_U06r6|ms3k;Uzea0E zO%^zHF-|BETu{69EKZ(6+FN+!mcIW!N6xW+U2)@cj=TL&aF`QL%*K*2YIazPbp;w) z!mMMDy(bLkBwh;NeD9f6 at i)4ruDAm|3WoDL-eO?$&`BH5PHa3c5Fb!oT at icM9gVdG zhTr)+HlU at H%hfdxV!DgHttPbCOppE-v3KNHhxR*p6!ka``AdZC)?zPsVwa at YSK9BS zSc~U at 3(D^l0(o5i9rhfT6iu$GZiRKXd74OeR0Y^Yg>%Zx(~>i_#EaUf7d2OArk0#t z at XXeda|)h0T5^8DGhf4@%vVF2=7bY-!@`j at Y*%7lcvy#Urssvt2VHl0rEF|ZATwFX z#-{hrToIp->9aSWw`kT2TbHT!tE%z9fXL2vn_yoDi;7|whc4K0W-&jsbU!NzF@%&z2G*!E7RvY zfahyr7og_bQ1dM)Q-4J>I_Wb*Gj^9H)|G*PaawGDAl(`swXRHVdZN?J>FdfS8Pl~~ z!^$#OqN|MZ7}ZrKH9gT~=JhU$dO at RFyGqkgRrSxP(zs&M)3t_%@)4d>?81N+*mNPI7 at 4orLlH8=Kw2fGc>bd z23uacuDGckAF-eBkvgrO&}Mb4bX!>5%#;R4405fgAJ%0-EePk)=dkm5y0sj{^HXq(pMg%G=! zudLmRSQD{D{=LMa^av6Fh=?{U48x|boL&?TLLmn32PUY-BI0J{#k5)taxJ0NYM^Tp z6|blo8 at Gc>9%ypc*fhCE*VbiPf#$OgG$#{~qIj~y?=oHkcRZyJWKZ4-*^@Uxc5*Ms zK39tD)Sk%pugkOn*=Y`Br!%s{vozvf4`*Xp%~%@FhRcj)Ni!WJy;$fbv-&{u)U9g%)JmmFx$;hL|`#!mT-l~mh at tBqBt+qr!pefm~NpS}swb9zB~UMbQq z_e6S7U1kxGe#L?GeAP`zH)iz@%8k|G?C?8{HH7-vfU%Z7=K{vMa8`e(+<23^iekJ) zp7+a*w?)``;rk<{8fLsh;8z01yX5)l at Yv(Bu|WX67v`+rM at 78aM@7t%Zc!1lq`oU+ zelHc#T3QipJuBiTb(zJehy{*{Sg5L05n~lp!NzblYJ5PXlo^}ocet^cs5|2|wvgv; z<3p+o$=DjsMvQF~p1;HR2=0%=7<1bxWPH&01XVfw9{L;cxMF-tQC?#Qh5f1V8U5aC z?4*!>#^>bznUM`=hhCKo6NTnDlRW?K3TN-BHad`ZCy&%F0v}=Q=HmA-$X-#}7v!lj z_EA-DE;sfgp92&k8wV+*v)nkuu!rgQAB`iN`Y5G4!mm-f>oJ^Sfxb(tmTr>{Bs>FYep z{45(=7c{0b+0PF}pPexj4fMj$tbV-Qn8^_@^6xCJi53vHv=72o-D&`?y2${1y%)lk zl at iw8ld#+CGOvNK7(iek<5F&MSJsd1Wcit9s%* zxGwWLa9-`ec at 5+2P~4;pi+SKMaIqfYZz#>>=cPL><5h+3adG14#^3$HRrEC06f?*fpxz7G=DXtyA7 zjn+4bZ}&ptA4^GmrzeSjQkPi<65n- at xPcqw(QNEf*;vTkf6-9Px{K-e7se7I^i;X= z8b`cNzdmCrkB((SvAnf&_;Q*I`Wq`aYiA}@Gp(!QyPB6Bp?m_u3uX1DDyTRCoU>|Ys1!M2` z6}K at M`=GD5LzS_Qr9R>|1!J50O1C8#`>?OLt-;u~zT!R##wMtJ#C;r$E$EB(?ZMdg zzVQ4c82hxZ@^%DcpY;{DGZ_24uefY5X7&~LRb}iWflFf5&;WNph`(4qs2{0lST$6J zXID_aSnzZN^{?3zyXKEg^~b*R$L0rO*8{P+!B}@7cGe%8=8v at oV^{sL%YoR{K&;yz zJK>Lg?T;-A#-;>gKl)>r{ILz?v2*^|vS93cf9$Y7cG at 5N#vfZ1jI9pFF8E{HYGR$i z*mZxbD;S#@jO`9S9=4u-supic=JM&)LmSo%Ej;XWTs#3*+Hbe??6>~9%yRVG4=KW} zR6qgFuRm95?8br7R*P%a$5t9^X?*VX8|yIYw)M5i^4 at BbA5$33wR z)MZux`|XZV_X*FWQLI&O^6L8*{oX(Pmo#g>&91*D*LqB(#vehq$(oF7pP6+36tWb8dl3kns*P5$^)!Y#%7kycMr7^CrB)=e?k8mZF^N ziSm%T%u1k~cc8qBQT`iVcQ??owLfUQ#}oGZM8Mw|8 at WwB;0b;cPw<;Lb_ at T0$iG`b zWJe!F9=KJz9Jool?COQc&Qc<~dJ? zzh9qLY3%nW_xSY}Dvdpu|Bf=*W3u{wzp;zD@{yp?!4pR({Z<=YG$row8 at uUqz;Ems z%7-|68Q=@9pnVi`&~NOg&tAWA0G;C?x%c^vLt-y*m_A?ljU&7{I!eE_#+RJ_D^%^5 zeN^p~TMhFmHyP$fd#T#5N~`wko>e=vF0&R at d(6=#jyu at vuYv7}z7~aBb%c{Q=?KSr z!S-Y+w%_!`Hd2>a2W(F{usv;`|IoJd*ihcu9tYBA`at at dTOs|;O^`m_3({vxk^Z(P z($Tuin?U-U1L^aQDn0>hzw7IW?N->Hx(T-Dd%^ZXDYh4TVtZ#@<}F}*$${-<2ev1H z?UlaH)^3IE>6>7Cxfg7|FU9t1Pi*h1%e)P2e{f)Xjjx;;1cWX#-pn4d50HFgD_HOI9;nFKveI zp3N|euI18b|%f!+!nMO5?CUdC0HNt27Q_79Oji zslr+Pu-|BoWc8zdV;p^s_>J-OIpjAc(C6h!V`2nWGqv73-NxH)(djnc)_12HtKHb? z#%U$gj at Non`_sD2A3^N|t=Q=%YSs|R-(gGwwv!m!|8N7g|G5O&+iyVjKlhF6#9nZH zrWDs_d*T|a%e(_zCp&PRqFEO!4;s$^)#v)C*dK2-nt!~>Xr9swvQtZuoz at fC;dPmJ zf$Ve#vd?Q^$9{wH^lSvblOs4bokG9A8h$@9UC&V$X{|J-(q~bnF)e~&JzaR7=L9qO z_XP at CP-)C$&x;^?MjtKo>sz$YulpwY`Cf>Ap_J&EJ&C@%F0%ndzvv)(7Prt}^D*(P z2+np}==a|lvm=Znk&^TAKub1Zaf#$?pp*is;XwK~g&6i8je5EIvBkD5m1I_sk zG+*VWh at scK%C+Ab$;Kq3EyA3}0`5)=`F9cjE=I+-_Ce94TTwLWCKSEe3q at _E6fNjU z(Vy03HiDvs4vH2PDt>VvXg+f*G at rQ%nu~ftb4e+huk}Rp-nz^OK=XA6noG4J)3F4V zx~vaupS=~f&)x*vrM+O=UW)DVp4k4ZF0%>Pu5e)c24g!Qt1qoIUZbgINu}|6gxH0p zz0%tQzU;2%=Bm7w}K7S$%B?-A{ENEGHc+|ELFGqG<*%{--xk4T z{YMd8wSF8y$aX>MC+zu at em%wxc7MjdJDJAM*_{QYANN7&zCpJ-${f@;r62V|>Go1e zKj}&7U)E(l0;QiiDBZ!7MlrU`2u?b3)M_g#jl7sgc13XY+rht`Am+2a=9KDN%_-G= z6SJciVs at 4i^LbBV{;DqXF^I`Jh%vRIJV}>mrQJX}*9X$GZZ%2Fy2&JA_JVZ26zN?( zk^V(pW;>AXa3I~uNZ*d(v4;oAUjF at pfA<0Xu0GIjxfS{?H$lI%7xZ_RqQ9pn`uEpm zJ^}iB3v-;UM+qf3O;Htf1Z!zfdT7cvH}az|zLb*pQ((@_rT0fN`y=-%?taI+yZQNb zmusKqRuuJVNc~A2LdRC$sqrgdJGGJe=UT8o-iFz!kyC$1jvt6f8S)Uw&ln_qkhaqu zCgjSCI=NM_%9p3&D1~t-!m{nBy1Vgs66N$em&gIH_ at Vk$NaXI+f)t8#RXZs5(hz;4 zj3-VH+EtM_91(T#%eu^;L5YiDdsM|?O_o&+VuR)*VAhThkg6fWId+Ceks&c1BN9hA z!#|_{qyM_DE&~L<__1^&Oi2bwocF(RXVEKgtUg$>Rm$jx%xP^ndi<5OJ^( zc*4>5Pik(jTUjRylUQ1?>9>uZTvMPchi72fc#W`Q%Vzg;?JWGsmj6>qdbTVqyuqSk zpsR+TjtF$6*ytvdF78Vymcf}kzZBbSDK-(rLu+lrVu^LDq;N*Tj;{`fW7e)GG# z^}Sm9xL=qNV>4>(4_Y=}QcAGB_MaIRMFmo7JRkw*lu|U{|Ipgn(G+QwuvTg6H>^=( zBQ?cR_1{iv`t7hr4@{&|5tNfcIT=*4mhw=faU#Oj3reGc6pS<0(&2iF`)EI^^^;Z~ zeV{J07hU0WZ{zihCewHoj}~Xm%mFQ%%k0z8i4JPnd}coqo-Jhgjg{qtb(t>^^KId{ zsL(}E_+zKsnG^oxH-3nBf8$S{^v6COoH>co&XwYNzL4=LE8}0)Wey?Z?`VtTX6f0> zfRPF7#8E9c`rl;^Bl?PFKhyk`$9|@H-GJceUuS-aKJV#;&|@BjhQbrs5tdnuaLtmT z-O}s1R{DiXf%T%MZ8an8?KPTB6(YXZNHrg>S)5YB-o;B(>QW2=kDChpOv;Y9)3W7} z3%dsJJ0j$$7~yh-|1sCf`WFR+7ChZmD18AHXxc6nmPv9Yz(r6S5J=EB-*U1pgFy}q|5KkZS<+{%|cuMBGRCGI!V zW)?=^4)*QlEDa}gKpiwo61PflN7xfZ5SQy%ICun6)k%`LfT{9C`9A3a)MnTxNl zl+t6p0zD`Z){MqPr=`5<^ATvH!rD^&>v|alAibK4Sn1UimRKuJ>5+HJmp)c6h=2=3 zY=%)#&%joi&CT=|At>zNTgV at iX-aK!8(-F!0w_)Zg#gZA9y;SuD&5M*R&AH#r-CUi zJV*+SAe+Q at Dx2KGd@iGxYr4OYpUL(S1kIzO77$2POL_GBs=J$9s1 at o>cD;JIURlqw z=5ADzhlmr^RBl4ElJKPLJ5uEq at i}%e at jj|8 at 4!#GyK~ej(3WMj|1XhKHY>KXV!%Cc z>4St7zd@<8_#g at 7u(gbD{bu5R3c6p)i^}a_Q(VMoZStqeS_L!KSdpp#E0U at x!K0#p zM}>t)4i#Gi^T1z(oS;}>&w5mvJ-%U8euNW5D!D91Q<)r>+Q z`dI`vd+*N?p43^57LQ4oi at qsBxoa4cJrDPQ5}q>=L_vT>P?oa@>%u7dE+XLht%E{$ zr)ALD{gpgyIOJ1GVz3Dta#p|0r__=Fl|~iHO{pl5mXy*Vf37g(FIYo9UYBVH;CT<& zqk+z~=e$2QLCu`U*#FLR16Q=%1FmQV(q8b`tjkY}tjltbzQQx=r(j(!dTiF^Vv%)u zm{^ye;$qEXuT7J at _TqD(DmZ#%rUsf3UZC|WCt4T$v97_H3;yI~e{BEY%w>P_l0Wvv z;LIg|@}fVsXK?1CKlzs3@$t9H_`2gOeIs)YR at 7@C)g z= zH8KXgIvlz*$%d;LL<5(4N&5Rp)8EjHnlpA(rj!}~16))dvQmdwR%*J}?QttRS^b-) zixy!QA_tb0p92CbH;656phTXO&Di`Q at gE|EXEuR9Is37*NC6c*1|~IV7EuAx+^Dg! zFj!yqPZ);xkK)&8po-6czs8)GqY=>>Z>g8q^nNw39J$!DjxfO==J>@1dLGFx5C^iPTSZnv at Z{x^j8JD?sHkkG1$Ipwm5|LTF*K8I-4J z!3`5i@!9oI8JyvD%Wj3bq<+5=n z`rCsv2eIn+Q=Pe)Pl;zTDIlNjj-;ESO%G!Ip5rqIFjEhOwEQMKRD0WQ?pJfXDs4OO zB}`uon3QBfSDyD8nt0=`y#;#VxjgjCPgF+rHg9 at OpkZtjRbKGI)TO zJu2k^F69A95cGrx>lD7o*_h*lb0pTjQq}4 at uVP^+rN>8O3vv3Ow8keyC4sO?ezle< ze?qko4~~>3OFv3uWoSuzCq`Y0+chcb@;u;rChAh}c0C()X?MFON1^FHCF*i3uIHjI zS#eE`y1Z`JJg>V^ey8xN99JF*XCjn~} z%&9TsHHGv1 zCI)c8 at rpAvYKP7*fsI~tp{-ucr>H-=#WOFMub}L&>EpTgi;$WSy8n3Ei=zKe zi<)>**Y*RN%{Hho)j6<(cq$FD2eGMMGq!X{*cDqwKfiNZxe8Mbx!S3;jKg?mN(0L^ zJvyV2_{Nmim>%_dy`Geoem=_9PKCuI7~QO3qnrFXcEosH!n!CDKOcR1n4SE2PF|W) z*a$AXti4Rq4{0y~j{UY7HD*L>t^t|9Vq+E4so4I5ig?W|!RrQ0qQE4>DS>eRL=mZ( z!*0aMdSwZP&@3>*D$%ay+iEj)~R at D+Esi zQBrh`X9GFY=#F6e%h4Biki-P2W%oG_8Yz+=;(al|qq;jX5}a^$L=MBgi${G-8d>M1 zDQ%=wTQ>xwSei+rRdFr!Y92+M?Z(&~))^F=8XBTiVu+gQ^;NoOtuWe)wWNwybA1@~ zggbblCjMqv`rHufDJ0AP?T~huaY+m_5ICQYXlI7fcrTa__Wv8|`svxSUNxzy zia4cUf4sUyyEVMJGYr#RbsgT(iHPUEB6-S5ZwlmLa at c%aY53GF^V_iF0w#!S7#DyJ z_ad)aqo~)F;-q3voy2JQ=&ua(X*qr>@KcGODlEIzi5b*)sBYpj0&>#>U3qTByuPb) ztzC^&Z$C|+r{N15U9$tRxt`dHA#8Jpw&NG8Q$Bu_E|;DijSgr)-R{tc<0=!0tgUsK z9!oC^6n%H5zaQfCU3K`%(+2}QSI{Ed^)TjpoCE$ZoCCsxw>(~oV&_J3(dij)VdfnDHc4^1N4zyq`(hynX)cXwX& z7x790HAjHz8zEVGi!cm}L60i-H9`*@cu0of{Hy(S|nh^vG945RZf(5YfP&?XEa zs)((vq^i>wE62c!G`R>9M3L)6Wy74PoT`o*7b{cM;EEV`lr-=S3;jxXo+JuPW1KIq z2h(o`zy`fp1xpRd$$`8l2Pw at WPXrORLenB%cU|l zsU2y!#Kr*@6CB&O)sy)p%si at -m0NA7~Sg;%C!A(9<2q#5 at o6w};= ziw<8t=Wp(ajJ($^_d7 at X4!2hZld_C91*%q5HS`wGQrM>05p2Ym9*iHOxUFJPNzu~pTR8|%C$sc%vqyMkWNQgF2tjZ70 z?k>b0D+`W(EQ2S1_jqwvQnHJl6Np_KoS73yz7&XE9h`Y7kenBYHQkY!7f8+x#I6s{ z%nc-83B<M2!Ve z`Ywzb3!^y3*$4X6+bOF>QENZ`=k$Mc18h4qqXzanD*r&~X6;~Mv$oi(-hW at 0If&{# zE;G`~Y54O3n`?Dq(N+i#oQqS}7Ux0~AN7{ZIgH6`-wwL>!=NhcIYdGFk%^ zN1)fdRn at 8%LKS6BQ`ctm`TXJ~za&Fbdj-wERgY9c_SYUH`Xvt~z3#=09&QOQdCbfu zkVTc_RyAF{vX;d)HS3iE|!(Mo6C; z(r1O@=R9tGogCj7lA8u%@@Q|t!9Bm7s-N~W4a9u6WuySmNyghXH6F{kxF)4#jb%|P1Fyv5kdgSJL2QT3Q+yzj>LT7AmBtv$qkI9TMah-Y z^z$C$JgsW_S&wlR-!mTL48GrajPE=-@%*TX at -*Q>abZ{d-5ROsPZ~XZ2$=HVLcxQF zu~tMSW1QO#*d%knCOL)bZT8U$pgjmftR5U+(kK0H$ZQv9Ht=l->FUkfB2Mt^==^yh~qp5F&>SE3R+S!W7!@auw#bmwlS1sQqk6OfB)5RX9?9#R(msR|TtF^L-_2 at T=TS za4NmhxxD_4I?83tHLboI1Ok=@VmT?ZG?08f5SwyG=5;K1ZN3|hzOV9v_dPOM;KR{3 zR--G$#X!I6(ZBbMy5a$*3w`!9zp6ORKTb$oLHb3$8;;$V1!CK!%rca{_{OrYaoIm0 z?Gm4T>>eu?ea)l);29M|$*&d0$?9Use at 7+9!1LT#aC;#3wUlW`!ApIz#^-ohy*=%OkEjR|mX8#vxbD%rJ$jQ@|Iwq5^^SUkU)C#-v!+N6w9y{{FXgk>L(^xk zhx;poqyIhw_9It_?JUHurPcC3Wp0Nk5O-z4FYNZY2^*R|va$6;V%Mu40f)SY=fCRg zh}a*8#NHY5JIT6H+RGRXvSoQ*_Tq2!KM+duK-m{Qa9t%GY(*e8vcac=@1SCyTMo>Su)s#K-Hqp(Yo3s=cq-4L2IouqSdLdG_uF&NBfBiYQxc#vmojBSkZtg5mkunh)mjExPLc}92r zYoBxKlq8!@a=$!(a-BV_z4o;B+G|g11$_PqqQQpGoQJdZFWP$f8(g zaV+zMVoOh%Rk6%$wQZpS!)ULlmYB4iJ;>(Gk)v>RB=JCFVRan#jLGR%>PaGZ^G&KXcNV0?y?#Rd_=iYDm>0 z)WX9G1;DnHni{CdJfaly>te})8o7P2M%pSDDWr1b@(xoL3IoFxv=TblQZZfe?(oQ$ z9Fb=%GE1#@GO`R+q#0*=T#>3?pslT>UZ!}ZqCKaAU>LGQ+A>VdT{&tk1Q}a+H zWQ*s7Z1G$q6^3lFQOFiyIs06M;vlhVR)u!J+r?g$z4=}iv^2Ad6jIFgg2`X+1X>TE znN>jR0W`}AvLR>- zou3asoyni#T9Dr^OZ-L6%uY16D8+GO_7uO=0iqle~c zKFXuf*+V-cc!fSKD*ez?*olt1Q>(h0wNjyV_zI=3Tv(-G4DY+DH178YX8j%|Mh+d;Ums%+m^A*z2cIa`$u$u;D(opOKo= zQWCoQ{n)?n!08_n^kj}8M_RJ>EGjL|ys31K$w`th2qZ;5NW|V)c4ps~hG8j=81GdZqS%P*bf}QvcZYk5Xa1LLoHCx#^SWY|ics z!SPIUb}2hHH)nUSRA07h27C8V#G*WFJPNjwI7_(1nUXLDF3FXVTxuUXA7Ej%eFU(ae zq=tUa4Li_cpvI;R(l{+EZVc1?Rt6?&HU#3V3XYEm=v}~>qb(|7G~sZR!f4)vFT(`Q zi1sKWkl+UOdPcI|LXz!58Yn_Ue^|BOFnS at TNZc#$0?tg2F)N^ZxHJt65|xI9r(Rbw zWpEnso3#pCh~A+PcPs2b+ilcfhH@#5=y=ZCSIm|b;)OVqsKFMVrvv5~iXFrop)S27 zE*oc`W?$L#?No>0fsJxujp1qc^tzj5i3g=g_D_Q) z;3KRD_f5%cRVv1WOH(p~4sUr%=7bnZ8}a_no)zQB$dJjRZ|4!y;tCkQrg*RI&T zm;e at m>vnWtFM!zeaXIiOu*IdA9-W5;bXnupgb3_lP*!HmWzX+&i3pGb?y3!sHM~qH z8?9jxmAW0hZ_XGf5{lM-Cd56 at cgQn4I1K%QcC-_4H*>9H^&j~dc zXT1hg<7t{naLX;S=bwvZj*7b2ZTUPePX2+~ax?Ww!x$?&vnZKu z3g2l#>`+6E9lK2l at Qo2?0W3SsdzJi$T2x+AH=er&rRib8GP^xSerUJR at IIA0o;MDa zV*ATX3@>p1-G!5nH-|z04WHDCW;zRV3Jy+pfTt z+)+2BE*!;q!vSi$K=AirjfX7_Q;|;9EVRc?K$N4 z#7f0KlNB_^K at A5i<3+q>z+rE!TrB;WOF{D-8V$j|3WCF<#qY1gzdTxeUnTyP(c+7h z_#>mmmn!kEas?(-SD-8^AZ$>)HX4Ql6%4PB7T;fqKWcBj449iQ|6@|&ecVGn#=Ucs z?A(1Bj3jn676v2fQY5jdu~3Sn_eBz`8w>j)>HbJ!TVtU=lJ1Kn);AXVBI$uhVtZp@ zAd-GDlGxc;crlXR8%gvw7WPKc#Yp1m#zHZYJ{U>tZ7dv&q|1 at S=Eg!flHMOltZgjp zkE9Po5?dMz2O{a0B8lCNg_n3Yy?W>9xLx=`v+#d2sqhi+rgPyh9Q6g^p-5tXW8qLF zeK?XRHWm&?(l196<;KFxyuV(Z!$~`b7tI|0pGk#}k;7ZszwM3UscOWhZNx8`h_9Yh z_ymaG9*uFl!{azyVH~@`IIv;vU=(MnIm3I^eB=EQtl*Tz3bvc9-~(m_S(6pKKPoFY zGn^F^g&}qh?FVeX9a^+QWf5w#jSp?Maqt`2#x)hT at hPhOk!D}{(5&;Cuau^%{Zc}l}uUofhZc~ee+kj=B z4RJRh?zAE9CWyNMahDBo7a->AIN$mndyDnoBDsyeg}cd8^SEQQJnrN??o=tWB4u at sJWe?Bc+k#cKJw`1JetA5=Bv04nVwJB=Bpy%c1OY< z-Q^bdwMyB|v{IhoHlOG8eRyRppr!vR&MLDc3Y2Z*2TJY*WwlD5#?`b zJlz6J59yp|A#2<{s4VQQf>-lD>!Pbq`QJrdLkwAJ+-H z>3+fQ%*Dv^XwnUy=*NbHRA7rq6S) zO5v%6YI30}O&^F#9fR~AH+4I3)w=^%XE%ATotE=kFKLHxI($jdP?Lu|GAmD*mJ8&M zPC at 4z%9flGK7 at y@u!G!@3zFkKIBH6Dzv4X at Si246UyU)n;C5Ib|5}Xq9gSc|*?we? zJI>~-dBY>pyzWeMG}gswYP$I8y3eSguP>Y-QF+uH1azNXnhwKL zdDCyy7(U_ina_GY`+6v~NHvt?B316lX{jE#d*IfKWkc)E_PdLQ$I!d8caX!2kX9xw?3+KAvkJ&lvR4Sp1BO=f=l#6XLmv at m!b{Q6T5s>ugc>H;OzttPwSQpArkqcnA($LL_UXECSh+sMYlBe^#xtenR1im3$$Zoh+#x4D*qvkQ65sb^f2_N(Z7~_C*V)ED&G?~!|2aF+O ztJ)Z6Of(wdGeUgxGQbV}f+zEd!q1#^u0V+Tg9tca)Hr}XtpW-fHPrmwJOC;86R{pc zS!DPqpn>AXF3}*BsM#f%jG-weyVdq8{Q1_cysaa&_k at ew|hP zy5FrK{FFk4+KPHE)h~yY3S)`D$5yw)*wL#^U`MB5$};N7c`{~%yMfrO&BH at OVHJ%r z`#;ne0jn{B$-BR)F&uq)_Qt5Q8l#R_OdVAcRXlrR1i5Ms8u-S>XyE1(jnPnP44l~N z<{cx*qfe^&U6Cd8adNBUuKAx*@VzQCsDTm<{1S|SLm!B|OzdWv6c$^8TfAk0HCwhg zkGF>j(F#WNCsl}65Tf1+BDI2Ox{YVaFgz<+Y>Y9UL?=hi>@Ox>3pEhGzrcN2F>ywL zGb&#e#X)MX6aKb{1%E1H!@pYvzsi;w-r|Y6-;4iOO(&UQ0Vep>qfOUqs;BFZ05aGT=~82$Zv~{?O|ZsYGZrY#75u3!{@?w%7Ja0jqOojdyY&0$Lt-Yg`VIRdP1e9 zDpEs at ng@2(ZKRI?>GKug_YoodK0?VKF@@jf9i`bZO!(d6vW4F+_b}o21zY$9X^gc= zVjINSrz>fn2<`*qwtY$1fp)hngKay7jU z-*swwy(;t&6xN$^d7tj~dVT9;T!rMfr$WRUD3(BYOA{}9<>#Lt!QI=#p5d4*x(tCtvCW3LD|^) zir#zCdPUu>d3}dnLc(u?AlTH|06l4!+Aw zPRm)_yYIw!@-V$L7_#riEL4t!xK+ApRHO?{q>BluT8&h#AU!h@=|T%>s#k6ARi*R> zwW+W{O+Bq9pH?L_5DDY7T4Zswn?+MoLZ?y0qG&gZrKYrA(mesM?*V>XKZvIFaMNns zw7O{8X*n-{$BV|eOmt5Z3Cg3 z70gw_7s?ew at aeTQ#@DKuwQAj799#+d!P8&6BR%Ly4>}U`;{*Kw?qbo at c3x$x|Fr6+ z)|~xE7>1Q^%(~IgEfna`b9yy8-Pv*yK|LnK0VH?SGhnxrIi!jeN&SZlhh)eKLz1XfACt9dW6>FkuXwNMWy4NLe3Rv;Qo&`nM17QA2%Qd=c&QDuT9k*{WJkc3852TffvYVb at e zP+dE1cPsxf6WnB>RpXvCns ziF#*RezOZ_5#;EiMfYTCZzImj>KYMOYsS$J(`|&h!)UFWj0f%lqMjjU(4uRKiRFCi zudNM6LsbDl27f&?L^)I1%mZrTKy#wM*$7 at KCl9x{spnH4hKvbqABJQ%m7ofyW;B at A zYM9U{Fku=x)P at snxB*icf&&JtvxqUdhi at +RSJ*;jAo;~WnhafjKAKW`I;Q2ul=H8} zAUomPL5srw!b7KQ?UKb1^J*;mtHs4d6rEb88l0q at KNd?aQ>m at SOwCQibMdm+CK+Q) zqAVN4lvH7S=A3jZQ9;<*&)+g?o!*2|=k$KtsCRn5!$k2Qh+?r?gKS4SrEW2?L}}C--{BnU zhvz`U{5!)4Moe5kA%gjqqoW6#jqqs1kf?^K9S!E&)i8CV!F;D0rhYV{`XeefI%~}6 zoi)F%LT at jQPH)Xdlc2YO3Zt&jn`YBn$xwR?4fHm)oIh&P+fkyou{OQE?x45VL2uAp zIu`WCNy_;*OnMtTHPeU?+1;;o~7Y~BjKE^Bj7F2ctyj`-$OV7!HP01A8#TvEJ&^%`wzy{ za(>s89wR`A$1~ypIRL2Jb*QuI&|-|`g6iEy)GX_GvkoOY$_l^SIp?7cVXi|HO?XyT zDjW&tl#Dvmavhqfe+VZacpkNUfE{Pun=0quH#fn at b(r-VUy0i}MZ}G#xbbCUoH3p^ ziYAK9O=C=Vv8K-KfN?!wB4U4ZjM2ytY9+ zVs8|x(RFXo>s#&T2W-$OXlnMl1Z$t4=X~*;Kb}+KIaM5Z#B+hn29;lgZ%{9zl>jd; zX*B>Lg(P-yzhB&TU8_#7n at f#XE#M6fm+x5lAc;PF>sKwqH;{b8&-yGj8S z6A0{5D6B|~QpGsP at UWj@KZ-ehA5+R+N6KD)m$$(sp~S_CB;y at 0h+bxi}^2>Fx56OQYG z%EYi8y2=clj%!|o0W*vm&W=F%=jTA^3|!^N#R~Q2KVdZ0nT%e8*UwRd6{FUmcUTR; zv$RSLUOz_-R*Y7I-cf7tX>|=algv@#D!67&U&yrhsw7(DtC9SfT21{$p7zx$;#geq z!+~i?VTT(HK at Ln_n$5S&Oqc|0-f0F3dhkPpBzZDm^Aj`r1e(@n9%kT4l$KTnU^7sG zp<&;hh!c|j29^3uoa7LVR|0z#;)@9ERkq5-(wIN}Y6#;`k_Plpi`Ta#P|qH_E(KbGNFRp0|Q{B8hxv-_*y$1(g) zmEgD58FeGpH{T<1wW01opeSbdP||##+GVLetIzLJ=XaT^VA$t~>Jw3Z;oD7fCZyx~ zsGQxW@=0dBF_xqw^2M+~q*miyocjD8HM3mh2FrgIgVvgr>I)f z`=V-h>G=EsvrETUx^%oT(U>rzOUI*2*PXRX*A44Zf*g-7P5tyNij`=Y9&Y<-P3KQ@ zlQQeoqA at A^cFdSWlg6O#8RPZst+99Nc?t+J|F4PXLh)QU&X*{fFm{`WRvLy0as9)D zxPg+`zcLRHi>43FlU}RF3TxFLnOm!s+c2r00K-pYb5qy$LTORemaZKG?L}3ayH`S^ zpT at sB{|1UeD%9M(-+8sLO2aHA7sNyy%-`r at K^~&MZUoVQ;XfDRNf2t2gv=b|T^yU% z=kv-yGr=49#GKt0%+%~V*nLIrV{R5ZxJ_a!#?Z=+?Ux|+5VAqFu-mFxT^bPOenghU z&?Wa(>`u(k+CPuUtl#n?S>iFz+xS15maEV2Q;9F at vc#MN3rEVaSY#g*TRU at xzItXi zPO!d_U{e at umx{%#1-5Gh81o+?28YSLU*$ECaFRR|4#ycyQDxE(v$I>k?yZFF4G%3bPY9ko8lH905u7kEulNoOP zddND&LjU|8hFrkjrUweF0x7Sk#Cb)fVHu&rcu#XXbr^|zbwHCfyIHU9W{L}{V#_oP zEg+~<9aA4JyND&|H`gm{(FyMs-z5m^e34_(k>NCKS(5C_x+HnC&TCO)X=rHVC at BdF z`*CHIl*|K$Tn&1oCg0_DjFKAXD5-%iF~=yW88J#~9HWHY7K}McO2bFVVr!I``%yK- z#gGk#_-Lc#v at 8j3NbbAY?HnclfKjr}93}13at&gXw9zPOAkNI>zgR7m*k{qTm2()T zDH at 1pJHZZ&1Z!ikQWaQfB-j)N8x(`d1RERyMu9Y#cqv&+?w7=15>D#N+VGz;n*K_7 zzX*?Wcv%dnT6T5|90QfG0SYZNV2CO-&hAyMN8>c=i{PCcTyzAh zWz)n$nTGjvJ^}QV#fbNXFU1??>9!G=?+x2}DbjQrb at d2_H&~5Gb0T6SEWCI)5=7@` zcQ at 7~ctbVaJihSEhYr?Z*{oDZ+OqiSFkE^SF5n4 at Rejl<0H)*RdYQT~DwV+#i8z at V zdQ1hdpkt2>3nt at XU;^X5&c!a0?F_`FLPcUTv?^NM0cNar8@|9eu?wmrKSS+tW*IbB|3PfG`We!X z^Ct~5T#qx?phyMs)p2{YY)=$Akt!-%GW4E~0RRN35oj*)*dP>O1O!X~x;OAZY&0S* zsYKsIq=Wd5PNZd%8q3v0I$#D5j83FmTsD!45p!-LT}af^YZ2*UCy at e?=0xNqQbbxr z%CX--q{wE4MWlDqK}WDi~-LkrFcj**!LbNG;eZBHiM$p~dc at O{Aq!h*WhD zsVazca1gR1gmpXqwSS}?#RFi9D znU-?oepg|4uR`wIHFh6F_&XweCBn}L_bRyG7VZ^r-yz at +skqp9h%ZROERV>qm|3GG z4mKwaHUH3+C`FR*v_PRh0uML;P)>dk!-h5z9_A}yIp7UcDsBd^!1 at 2VYjL>;S+dczTQygKSlzBl(1YOODw7eN1M(xk#t zHT9Kw>+S!WRQPG?Yt!@3lL|je4Vj*QnN(;`b;gC~e at -g=9Pb$Ua_z87qbDg}pWn{+ zE#0I0{Oeq-biv+CwM?5db^3D}bgbO3y6pD at FR`UdW)R|e0Ihqm`k%mj>qI41JQ?8F zx&y>M>?a1E$`bxzkTvBmaKP at C3x^7E}!z2Z0JF4Mc4jJ2`$A#Mi5M>fRG1aUJUKDHs=L7hIaAw*B$;VIud zg(B`Z)Z`mP=Q7Ohex}3Lz>j%25monRj;OX_+*a5OIHH#Gv*P*LaqQw_*0h$LeCjEl z4GCMgMPUo~bIuwDua2tJ==ryr^Ltx5BmJqj)nf8(6)$PO&}9Nqs6E?nh+|ZLspHM~ zJ#5C{mxHMYb5aVL% zGE6s*Sm$bdHJL(C)%5LJ;%IZ>cJ$cLSul16&`(X&e)BZ!N2lj$Qw#G at Lsl(ui#E0W zMkeFy9DsEKFjfT^GXc8-J_3B6S~oLOV>2#x*JP&UZ;EGLS7T9WD$RplXT;6%%$Lpx zHa3&FLlY`Z*#BO0<}KCon+f&&E%D5a+DMFg{?>Tr<0_1-PU%e^%<#76-*3*`CNfCR zq-xLP+K^b8df$a6eBXuo%m_du_yXBMVpctPsvcekGq6&A==yM$U-+xbUyaEuyM at Ur zcV|m=xd{=KJ4WTktLOj*!o__TGM_GHE;nt#q%JQeFPHOk;;dIs_%185)#m0Up0a zNGBndP9U1RJOyG?jIz7lWw{7=Vt=vJK;w5R9#;pz5iW;pb6fn{!4iIH28<4|-hC}U zmjVr#-cu|uELnP%nL1|1v!TnSrg9l{-3gUSdO~$w3w`VXm^RO0oL at eeeh<^bd#cHE zV5$SxAK@<%&mo91WlkfhPDt#j5d0(^i+d8t|2?(L##CITA~yZVpSe?Ozn#hc#(+on zc%i?C?SswC`RKntArB_xoF{RjIj4x}r*mQs*V%S>xWsbOelJ*~pjpf?&$ z-Vv8)3In!LPXKqNXzGu83{vBBv9{PGXJ<3iCh~b%OoY~z&L)=Bs5GHqo>&!lacgR{6LJ>Z7y;u9SWyP;z zu#ZjgxuC4cd>MD zT=LXN_r)czCf!dI=hd_ai2No>a{{m!lr&i=v8Xk9WX&MP4M{`qHjB`*2rZja zeYvz`e(fuQhu+5ME&&~X78*=_M3T=(s%#?5d=?U8<+D&R`B_K?NU5CvJk-GPNIy3K zb;o~)^q1s3Y$7E(bQzJUC7VG!gP9soGL3~Il9n%HG3=z+7>!k8`TmsHLlKs}i^&CX zBB)B5GNl=U$<%;1;PD=G33F0 at l-^ocO~m^T^6F+Oa^B*#IbS#?OC-z^BPnj_hbpnH za=9)+qOI8|tiKL-4cxL+ns0^~Uba{gewY3?;C7+6+weYgZNK3wHXwWud<2CrQsGI& z{DQ>3%@j6~q&_ngVjp+Ww49&Ya{;%f;Wr!9T$cT_Xar)p#4je|sRv+l0#-9&r{#2N zd_$oh%Tk!0{~|OU<_S{~0*lDRLdfM8E}OS11T~*cPQRuKb=$A0p34mN+I~I~rffx9 zjrk`?#^L_2Sw6laU at j&EUo+per*6`U`JPzvCXG14Knd1?OPP1YNY`Q{(6m|N#;fPXE7@&_W}7*f`bsS&zfxtA1!8zvf`>tn5nv}BL}w9= zXKKz%twkh=S1#0C*XI19R}H_}tx$RrfT6E{Aj#>!pG9)2 at -Kv+O(YH%cDkmcp{8~* ztru+}tBJ`BOT<(J^JPSCxLb%_cLhwf7AZ)CNw=JQFfJwDZ^lw-egMngnAjk?S2a0A zenAws>%?MK1cP-!vkFgUv-r8)fFsRdnJNS##Dw+>uoQDfYxp7dEN{`+Hk&EF(^{vA z);DF7$CLb7HW at rbQA#K5x0$m6>O_;8=<_iT4GpEvsO4Du85J8`_XM!WdQd$7D`H}} zornxpAB*N(Kq$dF{tnz2>#ibA7v=(x*It+N48$yD)KZ#}W?8%AE-OHk*bERQF3-Dm z&@45vAhX1?gdt)UoWquQPrvP5QMBlNBO`iv&c2@`ra2_w>s{6gY^KIzbU3aD0 at eyy z6auJE&?&Cxj5bi~*(vDa7zl-AYVLMB+GOE01|&XsDEbr at 0R2ik`A*JC&yK zl9N*lW_QkPAmPjr&wB#&f?~3b#I~Cj-s=E$@f(A9mgJvL at YiyX!60blMnLRq0Ef1ff- at hW`uq) z!kmXVVOelOW^`UK?U-k*Ak9N#w9k%CpXv!`A}_tln2xzrolYXAo`Zc zA at zZ34~XOkst}KuGR3|_iI at TQDH$#AbC~2&0?~H&&|D%#R2QR z^ke3h at c)`rc$t+C1uV^IPXWe-{i}ubO;>fwg?o{5S)j5-y_^YEfugbOBZyq#2w04O zm5zW%5zy-hcme^d90AJ_u-Xx at 5&>&CppnXPW!sff=av1bhqPk*Lz%^0(F}pNdPVl9t)6yg&x!#ndh9CHjj#F^9W3X)QV8>V`HLb z%=}eyvtP=;s5KSaUzzGkEz{bUv5w%)0UR1lrBav0)^AyBf4Wvmmo+{aI-t!ilr;qI zsRj76++x2ZaDng+U+17kt at eb54lZRL)zGmeD{ZnXETI^vOeOH9u+-iQ=;a!^v}6`# zOTaw8N6&kj8bHJpRW&#dd*vWgND&duj2eNvmjS=a#nrhs)sl50~M2?pUGY at W-xVVo6hC zMoar at w=2C`!(rt0EkBeCt65WV0Eb-TN1>P|A{FSa2tYvw_J#Yk_I^$3`)(#^7fx1CHb0 at xXHJH4_j%5$*%p zHI4Yb5WeO-e7}FqBz&F;Uo%-4R`1c;H+az}kJPq3QY&>nQhSXnvq5XBzY0A?uH;i* zsZvVZd&hu;3E|Z(9LGAh*q6V;M*Y0^ zBX at whD2UCAgT}57Q6vaBFhs$N_ at 0|iH^xMQlxmx=5-7l;f6v`FgO*@CRE)~TRj z)Pmd-l1pl({OWiI#sFeSnAXLHAIL2HyjhIoGru&|Kz#y1Eh7JsD|#j+>T!Z$#Rkt;L8zfas#D0a}-x;bxfc z<@Zf7`Tc;kQ)q_n80Oz-`|1CUQ~%w%(zkdqwWRf z|1W?#TvoRvAF7qxUI@!3le!xJ$5|C{E$$X{*bSoUY2On at yPn5`1db at K9@3bATQqv= z`)%Ks1f#opQq(2tKPl>=AqocolY7IGXw*rpwgCh(b4EpOu1mMdg@=a}D at H1WO7^v^ZFx6}7ZU)$>ex%2hFHNMOXG?!q{;sq^pReo_( zrjPef(l2PKZCcwlVtN!oe~)U}xn6Q$r#gFgtrHx^4%Pz5>|aryJIwrEtrOpQrvFa! zdzbm0UsWD|W~*7At!4(#it=o=%R>?Lx17tf73J}xJPvTPJX@{uRN7o2XeDts+MFQZ z0Z4k6mfEhhZ71BjG?KHid7izso?KJ2$7|EOwbb;s=~7`g1Z>dJTaoNmSH*n-?$q;~ zEWbXUd|s0i2b;UCkPRHtEwUs?p0d^=pAp5w#H}Xos}xV=;yq^UWxckJ(@c+ZLA zZ8nRy8B%+tc$*!?+iVukJEC~rVa3~Q7wY1~B<+h!lfEzQk4s}iQeRveuSvzYq|*{JE={Iv%W)|fk`BbB zF(IixE=>$c1GJbANrQ1olcg8q64BpFap}Al!qT3AWvBEYulTN}zpJ|BNs|hzAY$%y zEVK6oa5o6*`Hw}Ku<4LK7U}z;lsy)q0B9r&RNWNov)7V`%(dhUT1);Fw0Sw!k|ldB zdGH(8lCONjTC&Wm8(y+Eu?^q at PSN>+`o6XY>P^wP|C>eUl^RxC4$*ml*Bg6POLP{C z49Lt1dsN808h&*AR0-1YYB`#q>-e``h|3M8q&&uyl*a;Y9Dc at E3&{{$!K~YUBMH8J zwa1k1+7E*fJA`OHCQI}}qDa4N3wSSUnU@^`EqS615FKAOrQVk{a~ev`54X*yQ9D13 z)4!{IiB&Duxf`ZS9#1bbvouF}1>O&qm3Tg!IM#?f*!TJ5#21ro-|7}Sx!)EmUls%M zuwB-}TIR4Rlpi*Wci0rl4?BhOS8SpD6^)2QNZYU2LisDEP=3f3n-6K3LnhcEr%-;# zDU=^_2<3-?ml?-lB(+>Dl)uVMf@^SC)Zn0<(Ls&Kx+0$+bjariMU6P!!I1*B!ajw? zW9FKph{qL)TVs#bag8qOt(g+H?v%K7TcAEv5vT)}V2rt6H3e!@fVTze8e0?&0ty0k z$V#UQL7WBZun?$+5je&P#h3y$W++$wWmBMTu;kkYl-81OAta7L?(bRhE&JXP^6eqO zk1^$2fYz82CX2c(RE3?ADhE_as=O?)BvsDLDXG>9Nwt<+-4pnMDeD8K1A~&?U9Jv{ zO*{1k4T~zJK*YEU#w_aQ(gB7d?hXk`O5tx`9;s;>QWUm+ITV1(8Ms7CWa$4 zt%NY^c!{6T+}3cdMDv`gVFuE9P8&mW+Os@$y-HITjeUE9qsfP6HfrtHXqux4n&1K< zOi?Zk^P3`L(yG at mL)G%!L{l2gSy~8lQz#_8&a>Z9njC4)vL~oxl?m#4Oi+Vha$S=K0dFaU!iu)cg$%b$VGkwY|l1cEURDZ)43&wmD0%h-N>XOtUkKwE6HdadfisW0yXh$-IxYKF zBz-E9c(XBkDv~}P`S#@O at ksjZNaC%=?AuVd`g*{!Og_V1q?~ah-VWO0DhkDT$EszT zH&~{rqsksPWtv$e(;NpO9_Qv6Mu=}xV6di{lZ%&@sW;>1vrKYOEAA%w757X}1Y}pR zM#w}bkxSqb%H`xYH0k&0 at 2c6AR-IJ%1KgDljG~13RKV@?cz?x*z%g;wR;v4jP^I~2 zL}XJ%7_3ODMZ=qaD_$zo52gBYi4op8Tr1Ky*y9{hln~epKG;6SxV0!$QgRrL$f*+ zAho`}ZGF8|U{_|Io?Krq7ZWemF3CP!A6{g_tf_BXBVg7r%o at P#ke6iF)rbGpg!y6H z4*@~ZiP`SN4el#l*_k!r%*fcU+=<>rAJ~V0S6>@fVO?c|axJ6c+HZrc;ihD;I z5x3BaD~Y%!3J*UoN#U*3V*^y#(nMT+mBKekQsQ%6_~h`vknaUq_*~(?CEpIy=LT<2 z%=8MMC;WerZ>JgO4gUrCt`7*GFZ at 074Vpeb9v3GzwFsXAWuJ*>OrMHz3eF$Z*uJBt zxEe1X8qzXf57FQC{P)<`L(hLbbnNRPX=v!Yp`q^&4PDN+FFvvR{4LPgr#4l*MRfKS zSlnlPgaJF<3v|m);{sirqvHX9jo<_jd|@LvK?qI&!Ium%h9JJwSX0 at T+GVL*^yDo% zbjyEbC-{;Qe2D~K+bACZ9S+$L4-ox507~r)qD-(lH%F9&4Xw^0!I#GHWw_dNT at b6n zi3~yO#g#3xxS0fBG5Z#nZ&%RDW)8BM5yT at mSQ+GNccppP4#Mm$9`?T#Z^8k6(YnmC zWw0ET#vL6RqOo42p^od{HnLejHZy3Ic@{y;0>mta at b+DJC8|9;==OVk{{U7Q%lV3U z at 6(q*sddWzPzf+lG_Rj at nEd&B5D0G41&dDJq|2SLX*nNmU(h`6%g<5_Pj?o-nKRH| zEt~%}O!z zg_fO7pHo`)dis2*W$&cVje7Pb`h2QUQx-EHYuUSDkL at NVjK3o8z0qH+D%m4mmm^G+ z3>UR{c!*aum{`fr6Wl~r=dPPn#8)w2<3V_H&_l%Vm~2r)lX;hd+NWvB{5pcje9aFd zGXDb8OYJwgrOcf=)CQ#I>Zy0oGqL15nyWBZN5BUW3P^qsk;$LBLnjYNtNjk$mAad{ z at NS*2A&47#OkGnzU9qwtE_7wimvWQn>6S~`cjH7#w4B=x=){9cL5dbKAL{+)6J&f+uZe>3%X5Y(>`C3_)hkl+z7XFGyfsC!Jw6{ zc1L^7-&Cr3Ri(O(QGVZnvM$cOcy~tH#auitk{6zqZSMQWL!LCrmdaq!77$txYX_t%)d6pRr=3 zrrIbFRcdRK?c4Lmh?7_pH`SuJJE=|Y1c#d!#I24i_!+Moc%W2l7Z|(nFxfz#L at bkFfc>PWwc*H8oeI_nf%6-^b zZpgCpgLpqpYvWRJ6=<0y?aMM^p-iXnZ>F2Dk1HZO_ZzZ zrV-BDbVC^iC%n#(_SX;R6>feT%7}0)01K}7;w}^CCTd~9 zj^F+ooZuc3CVcuf8GF<@Z`E6#GIov)4{sXt6OmPQ_u}H9ZW1^@0rB?=vf0HGp(*=H%iZ! z5kIv{|EKhK)s5H;C at e&odV+r0=b!2{IrCveteg at zdlR$Vi8=1Xm+mWl*$=THvWT+r zD~`HtH|w^T#8tlnf%Zcj^E^Zi>{srSxvo7_*X>l-?e%i!_Il{f&O9R2Kf{O(xwDVx z2zi7`hyx#U1U{x`ALGDB9f6PP*+)6>VMpM at diG%seB2TExSoAnFJ%_#*+nSw z!)P8~Q{RQZmK?3KP{;qPZsPrt!ks>ttRz6cQe~FsqeyB}+axLb5gO%DidWR};^q4~ zJ+sSISf{6#>zSkO!gA#F7&VTfqH&f(+JBrHQBkqzUQW{fa^2iLgo#TVa2)`b*nsN@ zaGeA2Ne1+S4HDeaD&JE!lBa-V86&~k;weJ%6w0&QhFArN6?QhO2x65Zo0T@;Y5?}y zs8$o;YM@$WL#zeFY8zrLL97MD8XKY)5Nl}=DC&9aE!IOOrElg3vkSeBeAf~AC~A=J z)poi1O4%8p@%2HMM^V4qcOeOU)FAaZT@(#7b&y)N!~yiBCY?$$AKuT}7&mY!%nPhR z*=4ym2Hg~nzFk3b$qGHSRL{(CXSTT7m+HuK6T?!4DyW{}?rxb|~lQ+R?ElH0eoxbRk!0Oa1fzkpsEICSBd-N}!ZXyWo;$zn>b z#?Wv-Cn}vkBqJ!N83B?UDD`Zk1}byfpKYP(trq%p0$j=VZ7m)=)ul>>SOEm(?4_Ke zu0?DJl7Wn%5p2KH;|tSNnt86p3iakcCrKs$1%AH7&sXvGoh_=NL%D#V=f5UVVj9(- z5MRKELk`3gKNP2*PP`Jk4nJM3c0Rvn<})f#2-${^Z3x+htZb<*AjJ=&qs>9}|T|{ZBW^Y&T4thPR)+UZ3i0HXz2g~h` zPLZg0Q?JU2Ijrd$PAKvDOOR at 8mfMcvO{g=qGo{7w*OpPkXnb7XX at mC+_-RHQVj*5KskA4jf` zvLAz$mx5kDHwLc-GOyPYQ6x7txo74)nU5oQ)6-hpO#KM=6_7--vsw+G;xnNQFTuAz z(_{lfkrfqM)9pGOU at v%eJw^80z6PiR57vY={e)uo1zP`WRxb0~LAW>h*`4#ibDgDi z2A)}L#*qexFSE3Tmf*8nWd!$H!Tw4|{x+wiK8Y9#bjRdT{HRQy7=`#G)NN|nN#1qa|7gI6x->H~t{suhOzwtk#h|kFX&LZA~BHsL;QN%`hWM^Lx z__ucU1r28 at CeiNlCNT`c%V|LrUP5E=aU~Z@>}WAU=AL>7cF`@b9L{5_-154 at e@Wh5 zme&mpbBW!S*8{x;i9MFr8>W?8Vz1@(!JchmpXK%2+vf^zpDRkJ@$ZL*l<{8;(cfnN zGrq>3cL;wJXVjFzEUCmaF at HH#PV8 at K_!F2 at DMl5lXiX1pE5R2;wHIID?#7KJE85%R z^a)@#W2f;wq3yFc=ayACKiT__U$2vePJOeKJB6HEO~y4y?5J#UBaeeQ67vHcMwz2@(N-qSzbX*Wy>pw>44=G z#MEzj1u+d+UO`NQmUkpE`I(rKLqqiU6aKrL|I+*iV)7Z at 5!CcEh*LCd7J~qyu5RvR z4v4yYM3^80Gpu_=n3a$MSUs*IuxAu(-J at XZ9*)gKZ_?V!Hm%+I4YYQfO>57B)?OJ+ zG&oW%8oX+G6Wg%Lc#XZdxp9*xKL^wB>m@i2|8%)|*N{^-ZC(lxm zKBrU$02{4w-qffw%^Yba`RgG6^xW2RVQ#CTB%b8AgDjo1mXoh#R{Nv3ULltG!XR}{zafte5$h3L5Qd z5j4E6oCp8FJXQ%>KL7u&cz7Rc12;=42!eI8Qs;LGd|kYbskOSQ)~Ln8g+R^VQL?dM zMB5HSAC&Co9-hM}4fJo$4mViA^P1}$cunnB$El%EGMyVVHL)9F4in)Y!0(+X)@D>Mn+rbUqak$^Lmm{~IV>vR{ zT8?Z*E at wuYNZ$*(X|9Cb=6*eMx*4ZJN8Y8-MTfvYONa6&wXkOtF%Adh1>msZvL#q&%F|BPWkckxdL|Q#Is) zA`reFN!U?Kk(HM-n#CxO*0&w4m&~eP?aR+=rS`tFHB;2hr%oJnP5!RdOt~s(erx{j zN?7WS*0wuZC9~h7pr__n*FGT(Dvrnz9U4#7~x z at M}5KuP45)NvwymCAVqT at ERrc5#FdNe8-nMo;Qqk=>ZR^h$xkKzIOy?e`azk%? zvX3iew%S`3CU48_yQj#dKIj#@Hs>i(#?YP7-TulH7rD#X9w_MjB*0%E8_g0YJ_pvB&^D04txj2o9VhIdhB58MiB>d&5$@l~>)5Sm~&=|aT!`St}|CRSzuU`!2mPNE<>e+CUi1waT&oS z^Xpo9lTJuOAxJ|;=-Y9K%SMPMH8%XRt;Iw0qL at EE4SGD9{$sxI2$Na;DC3=C)5JYs zDXJ=g<-nYC;*FN%qE`9Z!E$s6&$CjYQdBA at 7q_}#QHyWleMa+vri=OD^Pt7Pgj&}HT#pqzM$->R3P)ErW>P~mc%BrWJShf4Edp(`l#%!trc6#DLn&|U at C z{iq2``A1v7v0P|IN$RX|x%9wakmzHzVKMQ!$967`*v|D8=Q3&`Ketd5Drid**VVY) zdf;lJBQxviVqB89B(Bf0&U0EEm2_A7WojA{7<8%dvYtMy#|nq_^qV at dhQgbA`XxgB zlAb<9^(q|F(}Uz4)KdrbVyXQgEv#OF;}t%d>8d$zsvg)r5p3KWm41r@^WWzDO*8q< zJlt9=r{f#IxrI2qfH#DnFn((BQx}7kEH_M=TlUP!HzA&DjOWg4>bx{Jsj2fKHnAAb zH33O8Qnkd`+PQ-s2dg$*J+a&vb5%Bx_tNX`4M^b^bV&`*4TxeeMX|3j zR`@0e-#Fom3*Y$6%Q}u9CNw>1p1Mp($WQWxA3w^RdQCUR8Dov{sgN;&SW at +;{WaYe zuK!&!fAim;`FLaO*)yL{)vp?%|5KJKvqDF%5uUl`V%ZC#nA-%RIHXtB%AktF?4QAa z$;VoyVdXk0!|Bbc-N|fYnq{4}GE at WMpmZCZ3oa z^(2;9`@+~E_FUol_FqKh_BFcy8=!a#`GRp#=}OmGwuR?m%NMEL)v4V`UCBd^x&Z1v z8|a0|$le}jNIN6jK=mEe#-h9=9iFAgM&JUso#}r@@u+i}&?kSd3>%_W(?)5enK>P) z+KIPmB78BW4PQV%MLwBj=>-R~x+GcXiROX*MUzvJNJ;C)T*Nu_nv6)@!?I*dWCKs) zZWyeC)kI~QVSdX8w8To`HkxoO2*_!?O*pYA-rI7>G{n)kvJ{Hl?aH%9vpty~^wxcw z%0?sn2O=c-10Lz0#WB*IfmApOq{87|$06Oa$!kp&PS6<)>fj9ZPC&F~R4$h0p^GHZ zMNdv9l+;o5OFds>Gz}+G(GD_=+FG`o#wVC zJdu9h=moR7^$&f5YV4E1pp&zDt0yP;?zeo+ at Rb>GdQMM8!BnC|cP+#DE)}BI*^o!9 z3Jbm}{+P2;nVy_4bj)RzwgBObDI^6nJ6j2yxW({}))*ClpwWC*fT(LRni>tfgnvgK zS=mhu*pWG$J at o~3O_^s}@>^P(i1$-`sfg1!5NjY=_{mnI!DyhV59><$ZOA`Gb7pqA?OM^A8PqvMd{?xR zBu6!RdpM at 00@D)4v}6=aD~DlPX=1X)_KSgOl)|jEUA=Zv@$EHHygMAl at KUT8fntRd zMdlFewGkGyrcB410DEG02oQ#!m&Ox0LP!aQYN*(BRmG+&eJavJ*egqvN!`i{HuC1V z{&}uKo=cNo(dv{NCcRnq6jq}X|!+DZKt6tM9hp#8^n=Pi-#m9*! zEU%l76VF({9zIUIzSi`5`8e@)E7r%yi6^bJem+j@^O;_Sj}wnrz$2e_o!{8?#gKOU z2Sf8e7^?qpXfFS?d_1(~lcD!N8T#atq0^rY5d;ip)To*sxWR?@eC`ff7hwNN5v?Y! z#8oKAT-c~ZCLa|$D}F;^LQ!!e2pju7Rsf$2s3jBBiX}P55nHxm4Q1Z{X75elq^Pod z;f&14r7|NUvN8)T8*b at fbtI(*%eX;jphlija-zIjif z-}~P8dWu3sK~c8C-6}v-5D`#uK at n607p%;*s&GL?6hU^BRV2UjKQ|&WvZ?@ex at YG5 zee;9Lh`9IM{oHfUx%b?228iZE1&&V)6zeUeM!5q(`0Uc%X5DJ#PP4vl|ZLk_X% z46KF`>c?tG7OOG$oQwwH=`__B4k-__+p|zTiEvb}Ib5%LYJ_IiT!b|4t2V`gfmAmE zw;=9dPVP0ClVGXCh}u+rM>O8#851pho>}{gNgzL|9U-_ePwoi0CkdS~qawMw8ad)3 zi!<60TMK8675axEOq=wFq0~pA2_J=A*$+b$W>hpk+JI-M`7u0E_EN4{^Q|@XJx;@h zGR1qFA7?mU^BR5znxEjOzxhc#X+{bC(5D)_xMHcgJt5`qx-NpN!K$Gy7l|&9U>u)6?P z3*9-}v*Wg!_Gq2x$$LgwTh2^Lf`V_(3$B?cd7d{uw=olmOPQ)9g%lj|&4w=TjjCIc zf`8g|QR?ZlZR5Vke-r1}rU0@%K%W`v0O}Pp)iU*pn^ccn5xjh0B~kYU7r^$CelXDZ zVW^50;({p*7EDdYuS6rhN)RVwI^1e`Hz8&mA zaII#B|8GZlxCEXl2e*Qoh-3L{z8=3VZe5SZ<@KZ0*7bN?UO!fCU5~do91n9io`C!O zWyPZB>SFQ#DHi7yi}Zo|yIc!+gZo09?K?KJd5ardan9Vz-bZrUKF3z}ZEAoP{l~om z)$>A^$GOA1&8dT1CSE^dV}?VqhXuY{Oog++xIv(Df_n_HP6xnWSP*UFr zMLyz4#Pard$gdv|bn$>q7Y~Fetkwg%NY7t at E*=2)PxSDBNe>UOl0^qdqV>9_^!h~U z^+~)^rKoI|RW?Xud(??H`22>^C!( zw;;DlsdWvt?%H3}y2fb})-}jiO6Y(;cc_rohR;+`1>(%2w7jKa7OWk_;o{u^bG8Dz zTxNl_p|h4)8?wqXcebcB3KMTZEB0(90rN453N4|=SKLO(#Bwn9+v4V@#EkvXYV*_0 zjQug|(_^E(%xEtMLgznREXw&WikZ{J!s+5)7K;xH{}}dK at Z6VJ3+9*Bg1w*@cc=rn z_PkT|P#`X#oEQZjHhp4LSL2E5A4|=Urv=1m zg7_Q|r$?E}SAk#Nc?zy9sJI~*qWV)z at -YRgcb7VV>7ATv8jDY_ zR4kRBl?wXeStYgB-!);aU&=nKbmg*3k#Am=JZv!{^}N#byuy|ZRalG|O-0?${1`nl z8rF6=N|_%wVT+0#-ux_qz*+SIP6HU)lTp-61-_Ac$pHsY$iD9Nbr`5tDvx+{YAhK9m~4Goe97!Y6#$Yq{yU=#>43J^nai{fUJC)-uo z=k>t at 5!|ZqeV05J!gAtLrQJLh=i){l-7IaDK;>jNG>m3m;Af;KiSQ@|wdP-(vVmYCH%Qw>*q z$r at S)9+oJRqs|(BpKECPk=HjUu%l5AhfCa;Dko@!-_7-!;h>jRQy!2$NH za2=hBj9x)Wmmq0Q|NG4Pc=BM(!>S}zrVBSa?l}?HA{Vad5w6 at saqP-1KYoKdZ^-BE zV!v!~kB17!##%F;t-_rtnRk##hAN}-)bSswZok*pCYe?cEC)Ll1WZo3xQ4FrY~_aC zEG|_Fgh>(-q&6sqoZO&Du*2=BLW>Wg5$ZtF_wzIkRX8g4!L_hyPi}j$Xqu>a2!83H zXU(JC%qY*b`D%EdyQT+dc%F^=$O8B#i(s`?F%?zbQF=} zxzzItID|DyzG;mjnU+KL6^ju;O`C4zNxn;!2yN at xibXdPtDHyjU^NK?-4QYElCu?j zK|g3WPxQYOR2uG`rdI*{HJ^krv0OWPkjNutUTB!izH!X&b1 at s?FE%Ip)>EBivL zCCl3E81^_h4O*Nz)A#1ptAXgk4ru1ZhBo$mQN&&U)HI*`KN8dod(s^GXI%pXrO1H2Fjv=a-Rmu+(6Hn)J8pL zZJ}4R&?{QFO0kRN&`PiDq859CrMAkNYp)8c!Sl<<@!xa= z*}EjayB;aoXrPI)e7tnQRW0n)b|HNkOmh8L zo%Y1s*xDOr6V0hLgtTt#=beff!3n2g%qtlF#B*qbQPH%j+5 at AX?1K*KW5pKlhJX=Z zlRKkGwB|)pI0_Z#;ZYN+P!{f5!MCFgW(p at 5Qmc`m-n_ml^L)JdwBNu^-O(65pD?j7ggWghs|#`O0RRSs+cgR5NB) zm%fe at OlX$JOIKum%@Yhn!UOUkF+udmt`jP(TrH3U52!brnsdo!DMK;^F)Bms z9=cxiP%qW3z!ej4t<638%v^K3=f9AkZ6p|F;~3gt>yc7;NpIYy^lB*9*d74=bCnvcbD znWM3$M-{h_!-Y@~c>(Val;R(tc-+xT-B}8|lfv#o*uxyw^spjDZo;=|`6Ti$uwr!D z-y`tN+Ee-&)r$#VGTs!#R$=E}m$o6T9_%*eD`^b?cX)VBmFAVhx(h)$TO zOb-}T^SDPaqYEUaLcMI+Xq#z%l_j}d}KwI!gtjJK4`aah6j#iX|8R~u{- zvO|kS(-Ii9`A at mV+-46g&r}N7ywrWf+o1{g$v54nc))Sx`O~&&h`iQ|yiB7?-xG#r zmB=?t8&J-AtX>v{|ujt1T!N^Xw9RGx5F@ z4ux-Gm;L?_NvHajhNkr#c3O2&7~YD+w);ZR*Dg5>bF)!*5C6)7{PC?K!}NgO55=FM z0+%aI%ZUus_obfh-$_=ayKRT^WD8}4PGpnTf at kC8o^!XL2G=`TQx(NxyDw%Cz zawz*D0s0Wjwn1T6;t*OC;Z}+70Z|75imjx}ro(X$+=r(*xjTEmg5_JYk=Xi27uTuhYz+(<32tVus(z4+|8FAwXWBL-XhVX?Q zClJQejhB9O*8ZCa*F(zTbE*WwL1K?#T5jf-}sgpfa6QYQ&gk?GSYgR+tM#~v3$oQ2s2V_+Id zQ+A{Agw7)cT#0umpKG3=*Z00 at 78P{dWFL)|iZgSKh?&6i(--*;6LFuQtbST!4L?@c zYlxszy@!at4v4-saBL0w8lX%dJo7XqyzIwyUEX&LDoTw<#oEQPZ*p&Z1VTeDcqVji zh0T&xn7uMplc}YvMN4I at U;P#_v(36?Jy2#+>va&^8q<;>R;-o)k)S}PHNY+M(d!r7 zIJQ>DCPRq>FucUzKcILB%M2gA5{^YK7_e;{MceXR`&^&OU#EfT|5MOeqnn6VJxG`S zAdC3Uyt;FOHxTpAGN3k1GaY`Xi=%3b+2W{lYrOV!wl$8!W6ekW{lbvRf5>E0|gwv*#7TnSv*)Z^{$z zQ{9I+Qcikt6eS|qID at dyhO7SDq#LS^R7>4NF|Kk=*RWR#2e~~;Q;*`J8sQwdZ|HV} z-pKRdFG`_rDVZZ7Tc23prc6eK^-FBnkB3moL9V* z7gu(fxE^9>On@^70Bns90CjF) zFFuc%zRD(iPHeM~i%ITRiQy3=%0DsT;Dev2B`0>uV3Hi{ypk at iv|&?nK$xqSB&rx+qH}_*Yrd#q4#Y{ zbgaupW2UV+mX~g^r{r(QX`=m~}FPc?F8p_Nl+P- at aIJBQG)0WqYD~(T>ZcnNpYZhIA^nt-<#$bFuGX zpV94kA5-1!+0MM~2Q*y2#L)R<*F2sf- at u+oxBj{kEzRfoqG{dKvK%)H+Ac&Ne4*{K zW1)^b&5ovs9vzL*sD-OU6U-HvWup$XCgFC1DMR{$bpotKjG%z3x`Jn?=riS#Gotv= zz7*22cw-~F|8%*_s`PO#r_!RFC>YiFOS2jrtyB$M*)P%RKcRBUi9lr?gWAp5u7FcD z*%ZEqYFF}@%h_dB-#TaerGeVN5~@N at wuY132{np#vz4d)y at Kg4nEN};)^<+8+}|-b zd=8H+nEN~ChR;hTgr~IOQ!w{;Mp%191ydE<+VJr)hwKY@&i|KU(YN`F;=wP9zdv2v zdAe9rEdHukq|nRU|6`UkhX~EW%H!@?o;e=8NjnH{4OhE{T{2>B(%Xp$Z&&gVFK|9R zMjw;;uwlX&>x6oN27|*tgrg+ZXlPgPH}Osw9KI^Gv%z76_%4LKqNG0Nz+ck;iFY8V z8A?pr0CtIQ*Gv<^!qv1WKjd&sj&g3CMsR`Q*U{9K=7MVRLFGZ%+NB#hY3rnS~`6NmA<~iHaJ8bN-)X zmW|Rs6>1>_ at aqE&4OVS>@j?MDvc6YgKpetEK at EPwpA&u}Ve8VXzoBO4u>j}IH5ci( z(A-MmVOR5%0Hrj%d at O+N`vU zXz15D at V|gP2YJBmadyUqKq$Umlc{@;0w>*R!>kw~mpF_M=nstb`*Kvrq z+R6i=<{{prWntnF at 3FElaftW0GtBI$HClj5`4zJW)E87Svp2p;b8k0xads;>_&=ny z^@1!_6!Ig9nb<9kDlYc8EB6$OU|}I`Qv#gQA5!9tch%H2-d)r9fOMsFg**9b+>`mV zfwF?hYNp&nh$?;Nq}=y_B<}>kliC1UUr%w}zeRzqVQcT8H4H;fCzPfWilEb}qS91U zT-jw zg7E7#y_N<^>T`vH@|hzIO`j|9>QNZ9yI4#gQ*tOW*UG1a*yza~Q~ISnZKpk5N~<5o z*2d_*dpWXm1ICceaeUTp$4RtfolPT85{=|uc#iSu`)5>=Y7L)JNs5*tsvBjyUXv at l zzBz7j=vdDuteEwF^iv>j=UUQ*vT_|r(}?m#n_E4-c*`{(X`oIlp!=vpa_9=t1@}@H zyeRgCfZdac)V^4v50hJbhIg{iRW^K+*_lKc?ev3oit`0OQ%!%a`3oFQFddCv+_!@R zrOmPcZZl at kITE#xDxe21Sf%Q-s<8AEzlo*kMa?aXO(Ii)kCl at i=`HYSg{i57T5sg)rN1Ut z)Jn-!)$Upsk75svUYUBg;-d8Txf&lFfA>q9(p=oB1l<;5{% zfe_rLBW2SnfmO31C-7~V*bUE#aa|U at q#uJ)pdL1XA<(1Czwoo$RC;R-pbTqq?qWXk z*UUB%k-fIp9M-vU;ES7^vMd3lS znWDdgszYVw%_4J}j|Q%h2F|)vq!&lC(fw3$bS_yOEr}KVl&zHu)nT4nWBMkl>YoU_ z3lxj(rR|tg0J#@S+Yt=x8`=Oi47)Yi9}MjSQEeU9;)9K+<<*U!1d`jUrPlMhbB%9{ z%QYAFuur2ceny=XRCnRrvm&f^(jy~X9e200>S{~s?qwiFnfcs>_J{1Htj+7(C7w3 at IK2? z_g3rISy8=q)Do{9wZx04tsDj2aqid58`ZT+NAnxiI=2#^5tQ%g-{C>PY7sEuV#psKXt2W8*CWXe4y_dafwSzHIG+_hf$eTOdA4p^Xm`^MXwXO8 z0wK2U)XHvYeT)o)7gL6xpE1K5!uXrGsC230Cgi%yk?ZdATtBgMJ$0^J7ngFq8M*Fp zW>2u|}q?GF|$n}sT*Tdzxero4hJXfyw zmvX%oxqjxz^+uE)!BJz?iM?($kt8P_$zujk{^AaMyaLWUJf}Gr^nO z at NxWGB5`r-K4+#TY9%P7){S?BxJoLjjrZ1I7u^ZItf7vgQr9IoyXz8?SqhGl2hI+Yg4U>T8~w7!mLXeDLf1hVbiL2zXjE zVk15|J>e26hh`OEdo)pR)wWimue$Lr`Gy3%klmPY$r;y-gv)cM zS|eUX32zkkNXyKld5No#XM4w|ro>^=aY|g8+ at 0gSLs(UUg7_=LS}%c;LA>>n)=PTI z;*y(&x%fIV3|mgZEgW9$^O+qrli3Ao@=y&t#u~CdKMX_RBZS31`ZAqiheUvBexRK< z!P>*|Y%tK!hDN4)DEPC^z`o42ThE>u8330D at S_5EXqPDhOhw zfLI+USy*>K1PS6%Cxoy;^&RR;M`v-y+;YkidZ1^h4?jYb{bxO|<8*+Aj*dB_?unDl59r;oQ?P0?wIvLK4BW|}6o-F*#$4W1y-2G0xQal z1y-=Jz>5CH0xQ^9V8y`30%s-4j0N73u#5%DWyS(;P2hKSLL{*uZCgKn+gPA5(C$A| znv#)>zQzKf3=|peRl-jQ#l>D4Ukv5D;*zvw8Y2<4Oiq{vBMuUxOf5H;K at xk2Z2}PW zE*Z;^zl5<2`Cn-)1=U9M>aCWXF7U~>}ZxJKKjaM+PueFg;Rcs^Esm at L)yo3wN2f6BEOGo!7|aGW%`bQh&qZfDZ=z{I=sKwl%=k zNZ?p!ZsJ;W at C9S8t=%gOdD`ZCZ~;AE>*2X)_ at keJWRc_%D6mPt3Rr4 at 2vd<`iw$h& zhb{U!hRJ~4MEY at bwc#raXtGUMVH-G&X|B!dFxS>M?U at o9Uc#ceiF2aG0Eum+xG`dx zR+yJKvuTBgEYk|NVR&amN*vY9NEt_UQ^ew^=AVnBT3+I)?m(_LJ93>>p6e}kuI=Z_ z_2E*kcOutYBLkR$m>q%ncepl~Xu3P>76r}Wa&gHzNHbw>+1I=XY?}0ygXTwI z|5xFx2w5m6*p-NQ<&bg#oLQEPgf^B)s@*TIpZsme=wQ at zNm3N_) zw>kRm_VT{F!_M`#bLIL-Dc5_D>z$5V?<&vrZadf8&z0-SQm*$R*LxxZ2)y at 3%KRsF zL at fVFP?hPlzp(eVFfzy=4DFQ42uIPPu0#q7CM76Qa54R_#f36$n!E+Ku2G*y1 z`h$VS^`p&AD(CY22(HbJ8wwNkWoAlf&ib5_-7yIzdlJH4qHQ)23max#AJ=2Vcl%PS zM384ut$QK+|5{}mKUoy9ES2ApIJ>3tRkldofsS4r>F4fmDnyj25K-=ncmox|zZW-O zeZt)zu~^T<GabELX z2`e!T)spg2y}xW!AF#hLyB>^;I4>C580F=)r4m0;{KW86LPb3!Yrsd?$MDn`0OwDK8P2Rbd^-0CDT3T`) zXDC+yFbqKDO8}|^Sc+Bc4{SoYmbFiA(JVe0^27yWCiR*aEUc)ya_~`QgbTt at Ze*vA zM7^^5v(24K_B}^ozEW7UgxeR4A+`yYK(y#DU8S4}SxIaRW$sGw4-EOTL1RK(5&~%o ztUA*akR4ONP{kC8EH|^x8%igtZ78*JN163kyLv+5>8WTJ47MPBZ2%Q$9f7+a zJS!a$D-hAAZYZ}@I~osODEpN@*IFsdd)Sce`cPe>W?d4Z7pM}dSqYOg^eg==J!26E zSvU$*jY=b0YOggECWR=q*BQYjvf*AW`d!Bm%ALYJStkaWnT~)(cVyeGp-Fcl6U~Iq zNJF*KQO?61HL`OyD>c}xR9Vh;wIYwmKuqX=yP=T^Q#=AkJr+L3pvpM_?h at 50CG&}7 zzEZL(PSzj2tNs(czY=;^P<`&U&^uNd=syO2$Be3h3(-oLop&J5HEu$l{(~3(&%tvH z-aY?DgBQx&i{^KnrMNQ;<3lco%`1CWmN(^s at 0OXNw~M`Cj0lhXqss*qRJgDl6cM_R zKZJMF(Z(QC6LK89n_8c4+>L5{&H7|z)5aw==BF?(8W_KYiOL_*kW90=#)W6)8rk1Q^P)r< z6S+8HG6AeF+ugh89S+ at D`)%;DD ze3d>>vrKH^TAo{jsq5pkl*+tdEffpZ!bI|VlgIp09HC-VOg3#@EA7|{Kf|B&Ukx!*xdupz?eC` z#trK~_pEC!xC zUaUV+{MpIknv=yJoGQL}s at VNS@$%C}iX&D#DD3!0zYPaO%!~SjKl at y*m%7Gh`ns?6 zmflS$n4!MdO2x5uIuVOh)|(B(U*^vAN}HK{;2g~4qc$_S7|i6V{>LRW|CM*s1)nds~vbd&!O zTaY%Nn7RSHAa*<+6gwUdCXzSAJ#0^lH?2*_bU{8hHN0X+F=T$tH8&W`GY||PPyYwS z^f$v)$G#!^yM~*Apu;)Wm)y}N5qN-E4OgRvYHa|OXsx-Q{iKadOV5E!kJ-qy1jzIh zBhzpvGTjJdx-p)ff#(b$)zfD|svChP%LG#0Xd~6Kfsx7t#*KoA90-QLiZ;&-p`Np> zq|H+bacc7{71}%w at N;VOJal$#p5+P3)S=C@?5x^658K*2Gc0YMWzgoCVQKR$JG(Z| zGv%V$vk^Fby|8Sq%p45_huTJJDGtb%hTUvGTa(CVnVRRB;!n+tLmQQ_pN3c_>|d5h&g3Z-F!4W<6_&U4Hn%?2?OY%t_u4VF zQU^uSuVd8;WL6~T=aEF;Ib(7Y10-{@72f`g2r~@{Dpc4f5xlWn>lStQ(1T}g1 at p#b zZ4%)Hk?K6C79dfflqML73WWoN+9`r(ZXFyL|H0aHC9baijaY3%jmv6m8{k+XAF8o! zfMbbVUSrz;?`!KmbpF%DqG#gi;(tF~{P=W{K45ZuWdb{V6ILeNY;GKvVj@~77#OT<49eKm`bxR*NoXQ?C zZsfSg*Du|PRzaXu(1BLVtxthg4_Tl6(JIJjbOrhf8wS#QdA?ll<%Njll*5N>J at yw=tv5#CYvk_+^fjV?G`jWn+SH>7 at sfEo!RiFy z*Yx#n)4xapY at 6kKgyUF$#nLc%$yWAw>@3P2Yb<4t%Q0-MJBKd7nCeo%uOJ(jwJ&U|H;{mEQq zE;4vnNSQlStay&yNc;?nw_ezC9^MAyX9)WA9@*{KIs7A at eWzyO*c)8r8o@=bNhD|S zni&*q=oYcKhVWHkEwxI at pE@Ep-ZVOSM3$0%snwro31b^Lbs=)UiFttJg;MK}vJ5gj2(eFwtU)YkrVc7q|J%7d^ylUGsHr(+%A`&cFy~cGcH(K)XqsQ znh~ZKj_n{^!pT8qcRkJ=n|iC=W&CoaoL{cANUafc^=Y-(abn(ijo_WluvluIESTm9 z_F9<%*dYlTwnO?dOtW_`an1AO5M^X&L?X>qcYw?YDuCh~yfyZoVt{e*)@8*SnpG_Q zl*`|uOsD)Ul+r4pl2AE-Qd*$AARJYkt1M($R-B>Db+nP at _dMBew!EWXL`1Ln@&nQL z2ziY-LSExM3+|s)Ro^|!HDb at wj2>j~S(cYJ$T92{hVq9S_5m{c!wq|Cb=at^ORf&P z%Z8t(jhRUaz^F0?i2|s0r4str<=-4>D#nds-1tlXf8xdfFO4IHI`^zdV$M!O zb(T@@KobIRQ6~T1SW-qqEIb+R{aVEQ|2wm~4>2EbHU~3LB`qe+Kd_Gp_GY{s)Tu`kOv#*F&k4p!h?+={YN-{?!jdX?UCt1R2|y5O*Mv^qAE at lj#-~<6tHnc zjrr;36NQhhSP#p!k65uX%eAjtvHh)e4LyHWvFQ8ek>XQFi#^ASr%x7NI#rx~x=5i$ zn4~6QY-)L!IPK+OQ$i~=KbpWL`AEPMQohr5Q6AnC7;^}RazguP>Bt-uR&g*xF*$); z%jjh(BUr148c|@~aENYCol!Tuj{Z-4vZNWl3ZmN^4t?|Pa((k2Ti^WYbLpEuRm$}- z z+@>;{+-Yc7JCK>XUp>nqO`@)WsA~fdb$|JRi{lzmN1i+2NsnsPUK+6W z{gxyZwO7Ad?Y{#qpbuZZR`BI(o!Y|v2Q=1K&t$F8Gg<4D0{V}6)t4UauiRTPqw0^` zTS52NnmQGkXA<=DtUYa{KOQ>%qcq_38)WXya6}mcAjZbY0G!tcpK1O71C7uiHqgWFYY>3JnuCAKgs|9nfJ1{+54#L&as#Mw7rk|IF`+KBLnzD zeJ|qn$li!Bq?yk?6}GHtz8`_r$qHK3o(yN6!!o!%0;6wnIO=vdna8zhT58#l^V4Bw z7e0u<`zb7&Jsr+vpGMg`oVk5iO+X(;B)4VN@*~cnM9R3xLdbI?{a{ zu^g&CiNpV0cDYYdxlf|p-Oh5~u7R6522wagzthwFHa2(nL_7r6D8O>n`lYM+6y%9; z>QWJsyj0Sk3Qu?<>>e)yD5t$vaTJnk&cUF}J`M_rpyYjQS!#b-*oiR+I}q`BtV-na zO?fs=bkGUdM9esheQ^Cn8OcL+uzPj5ENya0HH?XUT9$TEHPU`oR`9_(b{q!e at xW}( z`h at T@yH*;JeF9x|G~$wcJ|z4+VmXRkR2852jFkN$ENet8t2d`3merdLiTK24rR=jv zUK|L!S}SF*?44klY26qm6k8weDrBDv!>jBI;q>d_y6o%W^yYAVc5^uWGCf`nr(dGS zOW|}^xVD(>3hPh#bB5g2bYdi|sZFW0#{6}Rd0QnKc^(z}I*mE~v0K*H!cDJ*!K+QB z3V4+jPR|DVtKrH)!tic927{IB!fb(iU0AwI-yEKtsH}pP>?>j0DcLLG%x3z+$sSd$ zj;eMURk)5SoZ1lX(hDN}hOj3RE*V0;2!`Qni?)XYiGnDk4bDmcSJ6;m$N`x^DAa8K z`BZ{yKg{p`Ec0uZ>u3M-f4f}L^EjASlXi zl}-J=Zo==S*57A2E?3w50=WNam2O$~Udjw{H>bG(M6QQk+j^nQ4||ammC6(59UmDZ!LGNL?Rxu4zHx4;>SBZJw^Xrf92)-2$&vaC;miwc?=1Din8uYg^xe_8 zs-^=eI+~ssBaI07;I&R@x*4KxV8^Ug&n6pt;WsQ2GrX$BK z(q9T&Cb_qSCr4Xh^qi=%wA!3;xp4Begllq}>BBY`&OOvdo%wz7p=VaNl>xSyu5fcA zCdO9j!VEc`!lox*47=Of*#-1-sBEt3-8y%ga?R$#){J(!Zmg7jUS*Qyqtild{pD~J z3Is%)>WDbeGy!5`ra*1d_OXfiF4KIkF~eXzmnOX229HJ7g!cx#JmF=N(v>Fski>;9 z*N`9n*yVC>Nx&`A>j^lhoKb0c1 at AKH^*W;0e}-%Nm43J#yu}Q=v zHGbh~T<5vc*E}0y at lBOJw?A;9 at a>(RAJXSkW#@;|ZK3*XTPXcS&`il`8gKMm>CJu- z#1(#`w*CO9?QEFLo>l4g_yd0pS~91qrz`i<&XAOvQ@zZ1^uz2W9w zdh~>wd+70IxcN+0=C|nacDVU%bo(u6B3yl^ayCEnql@%=Lin5b9P=>Q zH~8j%o%Kdj&52SDIDNke)nA at Qr4|Ysq zN49e-Vf!)xBL~7F4Me2!A?wINObqQizbK at cYZ(P=|`0*Hk+#3uyt z2_P2P5XS+r*oHVx5XS*=pAB&o5KC-`qXcmj5ck^&YKOmMfy4)qfR$fIeJGBCr)G7!OV1YCJY495-rR=A{$p1v( z?G<%(P5Fd7eT2xzkuZJhV=^a3>$}5Eg at ikD1!Rz)!P(TSzMjU{Y!8o+8(y*9I}WF@ z7?U|UI)4VG7x(#Vi$%}WVv+veRxGY%LE&K@`N`Cdr~O?`b4E!hyKXi6V6LP0WS!L2 zblNS^D6>%>M&TXdc3d$|?+d541P~)l*b;DO_l48j!>KnC$u|-F=Mh0Nq&cO7El*+8Iv0A8vY|na5RJ=FjaigLawi&O(owh1PRU)Pt28 zzhz#mr?^gGw(MAVQZs#vj)JN*=RKVAX6h87F5eZVcbbu%wNgiR7b^Pbm#pYxl|F*| zKM5UaKD#Kb)xVFOxqlyX^zT53 at hTw(@Mh*HbpPXwOVbGB^_}5%^A;Nl1CLWBKB)G~ zO(%$G)3WG)fmSY at 6qOK)KEED9 at vGh7HQX@?%vvgm=31Icl(L@%(reB&(^l0 at 5X5u7nTT=d#XfIkfus>%z*{#yHC3gurmB#5U#^rZWbcD)y=#KQjUPN0 z=1kM=#M%Cyo%JEV)(IrJ(AegyvbfEv>es2sgVn<36npv;p0EFcl^`?SDtq&LGeQ6L zwwi|s%}2A^XQn+=is=JL>^&=o!^^bcAM{wUxB(3Hp#N1Yf at glkX6D{9nYkB;nfozR z+y)w!*LYY)Qs7MW3RiZfn!W+g8`ShHjLIx?RJPax at hp`JdA$IW at lp75SHQzwRPvlI!Fi$^ zVUnLsh0}L|Aag=reuJlwhpBM%L&5Hccg%ixk^12;ab9F&?>-xQ?@{9uH%i&dU{G}+ z2Jip}o<|j!6X*4JPQ2^KuI4#$`i4JtCKbe9rkAPu#>(t6bQ?vnuZkmDZNVfH-bL zyhaeO0pf%Wu^131nQ(;(VzHXj$4}2L2GHj=&{6=Mva?xA*(?Rb7dAvEAWqv5odnSd zh at uVg93WbwRzc5+f}R7=l&A%?3_xvBE1PAM%`!ktjbd&1XM$LvR>t8hTP0Fo>oO_* z3YEeIaeqZ+;=V+HEhz(AqEfg3dsTp~QY+yXYE>E7DmDGGN^%$qzAg$Q<^R%kQJ!X} zhh}GCGF%1Z^lxYKHF}P7!AGG+e~3R)530H3gQ~<9_FqzSO)sfZfPbOCS zC0<8*`Y21kn$oXU*|p|b>+y`U9xr_LdOYK-#|vkx$MsRH9201XYd7s$X6~+S7k>Bf zn)y^>tL-5(Ds!6~%e06$0nG5a? zou^Nj|BIS}7)8Yq1(BM=U^eY(4{PD4UZi9%QnDvheT^C;wkP$v+Vr~WO0Q8VEV~9d z&0s`?dwXV~bNVCFYM8T6FIQ8KNV()Al9X8~WtXe^ay7op;zpLMjgoNko_>N*^9eQG zL5R|!UY66>Q2H?<{TfQYhSEQ2rH_>*sOJQk1 at 6{i-MPxPVv*9s36db<1Od65oAey7 zI}4<4{bdy_Pij%1X;HvMZ8Ena^as?*QNr at n0!d#WWsZ)Vut1WKAV)FHZ%PEo{#{IG z at znO{uZg~*5mnq#BHeL29YJsceUZv;k+{vPRANR&IR&+;pt at 3BYEu_Io>!Zmr^lme z)1$;j;Yt$C!9^-q&=so5g5DH`6w?ChbX?YF{mRN^-juKxlzu?PYxV&Z8yWPKTB%N0 zsk$hDT2tTrZX&e_!49d0V{?)@!MS at HU|!0>VRn4WlR6?l at 7Fco= z^GrB1)ibHrd|mIJL_AW58eJn1JH;KjfjY7Z{K}(&TDfCVH=FvMJE;fQ1Fh(SIzeJv z$Snc>tzc`xB+F0gwm>LdL2ZFU>dsoZ6PAZP`brgcmfugn^sxD51e)JZ4A9?;Ml~L- z=AL>$ZCv9QznSfc=IsePX$2)^$8%pPIqJ8&zSiBrjZy~T$QIV?t>Df{J(1sV;A(17 z$D|(H=rCV8J9oX0ovGfIMdPx3(1;RFrAFrR6Gpl*aukBGOARypxMS2-ZTOO}62mwW z3}c&{2E~_|>ZZ#6)Vc?h`5;jnqR}$Hb|h-gqw0(CnA8%?89^EnA8zsmX`n=dC$)s~ zMySJ(Rll(H(a9-td8oQ$(~D>6KY`rDdd!)6q#rf0hyx)Je5gL5HlC97wY=CwhEZe7 zrY9{WT{ZebMsrGENAs7a1Cz0*)TXDHILzi^Z?VW63MZ2FWhfebv8*Zx18OvcYXtGK zR54n`B2dGjJk9#8>0iHAp1 at qO`_!iUxMFj7Qr^lHi*B77qfpt*b=8zn&PlUgt&xbx#X;Uysrvm)Hdl!f?pKAhUKSzf>D;I%;Pw4?qNf`x zCv2>AwQj70-yDY|q(Ha8b7Uh@}IA^v6MooFuN?D86vQJmVIPl4?)LEaupcSE%8Rvnx84 at 4~~ahI9rX3BE{ws51Cl=yJe zI*Q(%h);Z3%J!hh6$2IdNO_G{4g|A`10hPz7oy~R7A5D$>6?&fHP`z8pfo!nfbUe( zMLdgYdIz36ShAcSH<|ccdmj-qhfPbUS7>}rY!ZS`5)^>FKC6EEr;xKAi(0x2pAv8S zDVp`T4RI0>Yix*<1aT4&PuLJ21L8>=;$wpN7!XfIAuPj};U|Jhf1=`67WSq3$JC3M zwNX!n?463Ez7JGTEc$(}C1Chz3I0G01OonRTD+<3gsiJIn*haYen$npdYgXp&2Q0f zp}EbCi`&gp-TD+wSo5Zr=2dPSq0zh56%?ZH1dYSq#7YrUYu$6L*%QvWE}r z`?!gE%W at N?7uD2pHLpKZmDw3j9#`FHF)pe`p@;%`tBBncT5F)y`<|K#Qb6+EYF9ES zHND5gW<%8LmA!L(Xx=DVphs~NiAHi8MdI(eUHUP#vX)T!6E(G2ny^`NWj|4AY#SG= zaQ1{j{-7p(j3lLFDmO%Qg#MALKN85iI0{5Y-M+}3 zSY~SNWURb+(SMcMpU at 97-<1B0I^{E!(r|*FlFtz{LH^7Mrd6+oCp4-+YFae~B&Su= z$|)6SZR()8P#%7pyFlwz2TcyR|vH);Fyc>PT6|@Bk7ph!E2)^T}vp zsB$&>7iyi}8OW?2MU|D)AE_A6>_=*PJ15I-SJNMIvh0Uy0P%-s==n&Wo+n7^`Fiv`;pr1G%)jTO zP?q_=(=FUu|7x7j4kYx?)uw|9cTNvw<~8U?&5z$^=35`Q*8L^&IONRZkTZ`% zoX3lt2MnAXCfqn&>Z5#(-m00d%r-L62zVzfi^WFI7sMQW!PEVvDE3*$64Z`9`>Z=? z)b=ED!`w8b?0r at ui7wZsDE50-xjAb_%8UCWrM_Jq2q*OqRYt4{HcDRD7bP7jOBiXT zi!wBU2nY&p22*x>1qkj5wcQ+2eO1+Jag>(N98Tz;CNiHTmO-FPpP(K)p{C!b$NOsJ zTa?`iY!3`MW2$!EsyIQYK*`MheqV)uXUe3WGBKAl6HWBPBF;o}u at IW14j1Bip=qE+ zZ@~J7(th8s*mp zX5)36gDTh@)HE$Vaf_6liugBZ848BZcNW+l*H>3%+T(DZ*A5o2I|`QzS?pYE#QnUc zrRU)}Ps6qO_2QRaYQ%82EW_X%rco0aqc0Lx9#hU&|Rpw)O z{Rx%6cgL48uih at _lI^mDe0O;ABss7N?99CCj`4c0mf2imL2yv&W;uDY?5Y$lsZ0p` zzFY&1iC$v8LP|Xih5WQ9$2)pmIq%b+^nF at g_C77u;cn`1yGnkBwlaJj)N{OVN;z9qv9R%1u=YqffMErGuowX6^1G%D>aq97rq0mG%`vh)WPsPsJ=HqNnaE at ki0z`#&Y zoL(t|x9tp`K?d(cJ#t8SL1ORhquYAZ&rm+kXnME35O-_(!&R%ftI1NIqO8 zWmZ&WK2NmuG)ir~&{?41=tz1j(9i?|r8fqkh|H~C0`hV&{Bw&_H5SvmG&XgbzDvvN z-s#!9v~(A>xJxTIfwpR5hTrNfWN-DRw-Urw4JX}W^f8&&Y9@@4OgVC!-MATO+`HVj zB=e+aP^)HW>G!E)-*@!pdp7)|I9Yt3;REaz?$xCjO2=b1|BEX?kWqw7RrxUP5(coyK-%(Fj^C-y3B%gExTIX{NlCZyylI z8sJlj(g5$ZtNH}0x{s at RAyxGWs>Tyq`aP=gdwnLE=)%t&UARALI{edrm)Sfr9s{TQ zyRN3qBN2HZDpTb52nl}rArX;Ytr8>LtEGOBIwcV$OrDaY5<)a{!dZ)1KySHTnk4s?G)tdoDoX|S*vN7J+(bM!rWya!Y}6!m$O%2ht7PIj|$@Ws#uCK~kzJ{k(pARH=F>MyFk4^Brv$JP00Fbx z(%qi)%jh0|q9^;ZJ0EaoDP)c(y~6`23M2-D0_S)xlirc`NC%`N+L3Ctm~i; z*Nm~Qis7!AV_kmDb<=%R3AkVkUwu5Z4-I;ohaJkry?q? zBED at EA;?OJqI94%ky6l<5;;K>5YN^Td|uN}23Ff at iu5@>yt$5cdovO^i__C%| znnz!)h%O|wpj^4jF+55<~ztGAh;XhFT- at 0v`gwN|S<4`Tuv z)J3txnS*`piqlIvPxa1&*{dgJFHL1-S7RysjM*$GN)kjJL709^+Z}F8v4>zbKedG9 zd|H at 4b4UBF){!63BuK7ka5bJ(g}Hvdt|St|@r8)o+M-Ex=4zD0Lx(^!qQQ=2xe8-c_I zG`>-TJQ}-eru9Vbr%2KLhvQX_gb$htmpc>oh=hAoO88H9!b4>Vd(4C{ITQAp35Qd{ zKeZDcj8}~*C49w9__#CSR*`TwB}~mGRB4*cjO?+fhmf?_BajphUgNEct7#g*%302~ zan at U?{FQ0?Oy8`Pg7tSbh$a+9CEcx+w12)&(Eh at YT_*0z`gZ^7cEM>Gvi^d^d!p%g z2|?b~IJF_O``NoGc{(;0MEFuYBB_$uRZo{sYcS_7wtmJsD56H)F3qB!u75ZD2A zzX`RJp~gGw>418}gnE&os-5*6RD#-QLOlv7X!D-n>Yd+pQEH}^oJq at jcMi(1@$so! zv|REQ4W}3<8K%ksgUBX|gc at Mf;|nQw at cQoWt#6Uz(^|ZJu(u~)nX#~UQwe)}^mhdI zZUXi$w6Hfn8BU;6{=A82y#*6_LB0w$?!H5q`i{nF3yiu8DPUn=)a@+KES1C7=>DqrrXwb;ryyyqH?0 zc3^H5pc at +XjtVI&LV^dZ8q+d7-a58r1XwT`KMsxzKLPwy;3tTmP%X?s;wS8owpvt& ztkt49B(0W+L(XccL>5u}#K2bmpf6ilMQmlQOC1Vh_J-yZ!wf_b#}lO&C`iTFpGCXe zICW$xF-H`5ZmrNtJmw(evzzmo)^eU;nJj{IDj+Q7oMJ}oR9{A{WpHj%%aFVg=`<=k zC$$VU1~Xqa#25^|Y%usT3Laum1o*PS;L89<{f%8~da$lf054GPsW=OhdrWu)H^s6R z#R#xwA10a+g=VIg+d~obrH&O$%H7penLwwZbQ)@>p_!Mr5-lTXG>wsgkEw*T)5Q!V1m98WbDt0w$KFr`DRa z`BSSl6}S!s$Qr9EK_s7xpolObq6UPCkwu~?xq}-OG<(O7hQsG9MPvQ%8R)Su<9Y(mL*3*d&xC9om=~HwhdLD{Ten+lwb0Nek z3Kk)t1tDY5q?Td$IiKbwc=KUAFFO!=KF>&cJs%8!&luLxd>GB69M11(-i*zz=r1Tq zkYcDoMu>tb%hXtXZ07LD31g+!v5A%un74=Pn;-HU{JOcOdAT37pL#cl!AG9rGsK^q zuQ8(O;Tq;@Kv{NTKks=k+4?iyxlKDgZWL|DY$%KAb;R^KW1ztw^^OcF&jn4w4XQo7 zOoE3v at U{knRc~va%f2*02T5TB`_~in#(@Yr?-Q7W+uA4~`aOIg`dx}Y7a1qIJUCCJ z_g^b$4`qTr*cb|eKGY=Wg9+7#f}jsI3Hp$)L(u>1D-rZRv1T@~X|R)~8&S+&i?WZv zY#k98QBK(%p*DSpiAg^KlNnJonfuY~AC}YX!A|PR03-T_i)9ARVtGrVv_uRQ8x}-Q zF~>_(94Z|SN=p@^Da;Eha$%B6VctGr{>U!}CX9Zu1K23_p^TT?lyPwRQaDi8ST!J0 zGC(&uXk&R|GsNyr6;#pg(;)Y0F!Ft?PtQl7J)AU<)*=^#2VH{a at bV6Ikk)Zb28Wnr z(5kQKAhD_#CMD&CWGs9Eo*5j#A)~?s_d96kP?L5JEz!=QCEAJlgM3=#PK1DpmMGr2 zDe3SMB^~}1DCzJ&3MDLb{#7s!(@CzX zC%KB`t$LETU{WWBWB32kby3=q46?KAQ_%JG<{_DWr^fCn?qN#)QUEgw8ol>u;)>NB ze5g*Jrtb2T7|yExhHAIUEl%OLX?0Dl)gJnWPD&`VpplKI19D9cAbXJ zR4d7hn5q1LBtC?%siBhBX?*FYT_Z8Jy{TQ3Eclr`ez0iPE8CjZ$zEA<_Vr;$c~-)k zxS;dwx!{Dsr6bPwD#z`!fu_l&qQ9Wr-w&lRc{<~LC(m1bCeJJ`XZTsl!Tq2Z?46FI zM&2^^tTt3%FIhYN9qxa`a7x|R=m`ZX;X%Sh#SC|CFgwGYUZmA!7Xf+a(3Dfc8qS at m zBT^r0O&@cA%#FFdN_Yq>Htg5B^38j+u0r!}t;>*`4@Db1X|1NmeocX5 z>waxgFLoCTl#7a;vON?~YCxhzuA{z5-{&r5?{gb6wFbM7#Lpej^xaw~_xA>DSFk!>f3-Rp+BKfxbOsbu9DrQ7 at h! z)f-CfKWMlKH7UZL&6I_;|FCEh_%hS5EL at 5i0o<>HoH7ecLce^+<6?~w>VTJ&ZB-Mt zRk^5P5H>^2?^p53+4lP89pdf7`sNSt7H(eeX3h0iVSm7nt-nZ$i#my-R07>uUn?yW z5(RyIj4vpN839yYg==oSgzn4v`|GG0U-^;gbyq#{!}5j-IN%zz?sU=c zMi&zayd$2+&>Ozw9dQ}_rZ7Mt`q>bs5h^mX$BO1;Uab&)Jy6A}t%~_G+pDa)`AM*{ zs~6$wA)3Sa5KUC5y;v;sEDA4fx5sn&rrYC$&M+s$c=I6R%^wkELT5pl?Iy~6SZ{>N zQ6_W-lsRdmjB+NFQTn5dVxi0q8)ZJSQD&-VV3axZrBEhp4U5_bWx|HqA7ys`TcXTV zPYGoN`jnvz+D+l-K$+WsGPjv1b63nuCUn2&togA zhw2;oZ!TBlDtPv#sQF`6k>C8WsB*|qzkGhGncry2Ph&w%L1LfSQZ<6JsWFt1chI)Vsh9czu|@nzZUNp(CT%tG^(ZUaF4CH)sHeH1V*u)-`sLB@ zouM|>ypvc#bk+#gN*z3#{BuP`uo=l~zo_gas;uJNr>y)twvu&E%=1U+-CbrA-~YGo z-Lj(3*}ET}vv<+4j4xlOckhjrsib$rES2>3u|g$1CymG+0`J)wE7?U_7<1Z1dSf)( zMOqXKxY>|PW6$Wuw`&@=j&6LXrm=T)Tl6~&l!Q*r at U^@xVQi3Q~H8FG0b=8|L?pby0?9s1kG#wWfZWsf3$ zdCbzWUv0KoJGe zMpUqQKbn`ID9S at nK|!>u3cBSbDhenHh#)9n{nu~pb55OV8ui|P at _p5P_G9g}-)pbE z_gZUX*Ziz2{tDusbK+WD9 at 40>r8+yR9dSEKCEgJnMdSs6iF#>9pU2Xunq- at BQ$; z=Ct1vR{Ql)`~59+P+aY|Tes{#ixztrcwg7OKA-av;lI!m z*~=#wL!t8ig4d-cvQ%lzELAKCb|~BM5rbywha@&D*+_S1W;439*EwKYkFn9mkUs&& z^$KH5tXHy`^-7|faCR#wKC6O|Z=RjQK-vZuT%z)CIe9#WJof82%ls8a>{eHedDpO6Cbkvry-DZY%uV^20q{Zv6VDniR+v69OyRuUVP zrp!iVAT5srdWqJ?LA{^W#vvzu9m;%|+a(QiUR3jqN>+skg^ccW^!pYm-Qvh;9o1=_ zVzp6XjuOetQ4)(NaM9Pb*hN>1z0JcBhUXV4)Kye~BTji%qdf2Ew#LS4!n+zh`K|*o z8xZd?#4z;a3ack)6X$Dqgj@@u`2p2Wg27=t6P%}o*jMM^O*Kqx{J3e&s zUxWNVa=Z)SJ;@DwovoNb(-(V?l}fbGx>8}~5C%AQ4YiB4=nbs_qLNDM5=F9Rg*D-< zmbJ>?AGz7B-~^L`X=){}1SoA2W)%7F4J5w|m`j~_p**pa;+HDsCMS5dlAIW%OqwVY zbAw{bpw3gqYY<(gsaYie7b(foUJdB!8bic2n zaw^yNfM;Ox6`xN;e7+)+tBYJ+irgOV72>=(+T|7RfxBD+&m;RXyFB at j*AeoCQe5$3 zxXUE~O`u1;0A~mi```tIq0Sw*z{FHxw4tJ4( z`7ExrR>|YyXf3TJxG@@_vTZ at CV=5>riHG48?N-sY8n(5#pQc%IqG=6noOM?NlS2sp zmYJIstp7bJe#{$4VP-edL6jGoL42eR12h;P#7-pED*bhI)+){*AV*_zK at k2G=zVfP z&W%3CdkyBJ3LD`e&(Q|+8OQU!?a4+9Vccr5st*-V$i*paJj-hbWm|XfE!P>o8oHGW z22rbG9__D^YQ~q1Eem4F&pKIn|@V z%t9rxhTU_6nKdZtXL^8EB$x;O#??6MdcKTu}-^*oryl5S+CjIOpwFfDCtt z71m+K z*tWP_gud7Fb

    • action: <%= link_to task.action, { :controller => "task", :action => 'show', :id => task }, { :class => "action" } -%>
      - vm: <%= link_to task.vm.description, { :controller => "vm", :action => 'show', :id => task }, { :class => "description", :title => "cpus: %s mem: %s vNIC: %s" % [ task.vm.num_vcpus_used, task.vm.memory_used, task.vm.vnic_mac_addr ] } -%>
      + vm: <%= link_to task.vm.description, { :controller => "vm", :action => 'show', :id => task }, { :class => "description", :title => "cpus: %s mem: %s" % [ task.vm.num_vcpus_used, task.vm.memory_used ] } -%>
      state: <%= task.state -%>
    • <% end %> diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 8373bf4..97b7188 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -50,19 +50,35 @@
      Network
      +
      <%= check_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> <%= check_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> @@ -119,4 +135,188 @@ ${htmlList(pools, id)} } }); }); + + /////////////////////////////////////////////////// vm networks config + + // number of rows which we are currently displaying in net config + var vm_network_config_rows = 0; + + // last row currently being displayed + var vm_network_config_last_row = 0; + + // value of current selectbox + var current_selectbox_value = 0; + + // create list of nics + var nics = new Array(); + <% @nics.each { |rnic| %> + jnic = new Object; + jnic.network_id = "<%= rnic.network_id.to_s %>"; + jnic.name = "<%= rnic.network.name %>"; + jnic.mac = "<%= rnic.mac %>"; + jnic.ip = "<%= rnic.ip_address %>"; + jnic.static_ip = <%= rnic.network.boot_type.proto == 'static' %>; + jnic.selected = false; + nics.push(jnic); + <% } %> + + // adds unselected network back to selectboxes indicated by selector + function add_unselected_network(selector, network_id){ + for(j = 0; j < nics.length; ++j){ + if(nics[j].network_id == network_id){ + nics[j].selected = false; + $(selector).append(''); + break; + } + } + } + + // show / hide ip address column + function toggle_ip_address_column(){ + for(i = 0; i < nics.length; ++i){ + if(nics[i].selected && nics[i].static_ip){ + $('#vm_network_config_header_ip').show(); + return; + } + } + $('#vm_network_config_header_ip').hide(); + } + + // show a new network config row + function add_network_config_row(no_remove_link){ + + // if the number of rows is equal to the number of + // networks, don't show any more rows + if(vm_network_config_rows == nics.length) + return; + + vm_network_config_rows += 1; + vm_network_config_last_row = vm_network_config_rows; + + // create the content for another row to be added to the vm_network_config_networks div above. + // currently a row has a network select box, a mac text field, and an ip address field if a static network is selected + var content = '
      '; + content += '
      '; + content += ' '; + content += '
      '; + content += '
      '; + content += ' '; + content += '
      '; + content += '
      '; + content += '  '; + content += '
      '; + + if(!no_remove_link){ + content += '
      '; + content += ' Remove'; + content += '
      '; + } + content += '
      '; + content += '
      '; + + $('#vm_network_config_networks').append(content); + + $('#vm_network_config_networks').ready(function(){ + // when vm_network_config_remove link is click remove target row + $('#vm_network_config_remove_'+vm_network_config_rows).bind('click', function(e){ + remove_network_config_row(e.target.id.substr(25)); // remove vm_network_config_remove_ bit to get row num + }); + + // when select box clicked, store current value for use on change + $('#vm_network_config_network_select_'+vm_network_config_rows).bind('click', function(e){ + current_selectbox_value = e.target.value; + }); + + // when value of network select box is switched + $('#vm_network_config_network_select_'+vm_network_config_rows).bind('change', function(e){ + row = e.target.id.substr(33) + + // find nic w/ selected network id + for(i = 0; i < nics.length; ++i){ + if(nics[i].network_id == e.target.value){ + nics[i].selected = true; + + // fill in mac / ip address textfields as necessary + $('#vm_network_config_mac_'+row).html(''); + }else{ + $('#vm_network_config_ip_'+row).html(' '); + } + + // for the other select boxes, removed selected network + $('.vm_network_config_network_select:not(#vm_network_config_network_select_'+row+') option[@value='+nics[i].network_id+']').remove(); + + break; + } + } + + // if we are clearing the row, do so + if(e.target.value == ""){ + $('#vm_network_config_mac_'+row).html(''); + $('#vm_network_config_ip_'+row).html(' '); + } + + // add unselected network back to other selectboxes. + add_unselected_network('.vm_network_config_network_select:not(#vm_network_config_network_select_'+row+')', current_selectbox_value); + + // show / hide ip address column + toggle_ip_address_column(); + + // only add a new blank row if last row's select box was changed + if(e.target.value != "" && row == vm_network_config_last_row){ + // add row + add_network_config_row(); + } + }); + }); + + // show / hide ip address column + toggle_ip_address_column(); + } + + + + // remove a network config row + function remove_network_config_row(row_num){ + // if trying to remove the first row or a nonexistant one, fail to do so + if(row_num < 2 || row_num > vm_network_config_last_row) + return; + + // get selected network, add it to other selectboxes + network_id = $('#vm_network_config_network_select_' + row_num).val(); + add_unselected_network('.vm_network_config_network_select:not(#vm_network_config_network_select_'+row_num+')', network_id); + + // remove the row + $('#vm_network_config_row_' + row_num).remove(); + + // when removed, set global params + $('#vm_network_config_networks').ready(function(){ + vm_network_config_rows -= 1; + rows = $('#vm_network_config_networks').children(); + vm_network_config_last_row = rows[rows.length - 1].id.substr(22); + + // show / hide ip address column + toggle_ip_address_column(); + }); + } + + // intially show only one vm network config row + $(document).ready(function(){ + add_network_config_row(true); + }); + + // when vm_network_config_add link is clicked show new row + $('#vm_network_config_add').bind('click', function(){ + // TODO check if there exists an empty row + // TODO check to see if we've already added as many rows as there are nets + add_network_config_row(); + }); + diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml index a110011..e3fa0e0 100644 --- a/src/app/views/vm/_grid.rhtml +++ b/src/app/views/vm/_grid.rhtml @@ -34,7 +34,6 @@ <% end %> {display: 'CPUs', name : 'num_vcpus_allocated', width : 40, sortable : true, align: 'left'}, {display: 'Memory (MB)', name : 'memory_allocated', width : 60, sortable : true, align: 'right'}, - {display: 'vNIC Mac Addr', name : 'vnic_mac_addr', width : 60, sortable : true, align: 'right'}, {display: 'State', name : 'state', width : 50, sortable : true, align: 'right'}, {display: 'Total Run Time', name : 'calc_uptime', width : 50, align: 'right'}, {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } diff --git a/src/app/views/vm/_list.rhtml b/src/app/views/vm/_list.rhtml index 42300d6..54ae741 100644 --- a/src/app/views/vm/_list.rhtml +++ b/src/app/views/vm/_list.rhtml @@ -4,7 +4,6 @@ Description & UUID CPUs Memory (mb) - vNIC MAC Addr State @@ -15,7 +14,6 @@ <%= link_to vm.description, { :controller => "vm", :action => 'show', :id => vm }, { :class => "show" } %>
      <%= vm.uuid %>
      <%= vm.num_vcpus_allocated %> <%= vm.memory_allocated_in_mb %> - <%= vm.vnic_mac_addr %> <%= vm.state %> <%unless vm.needs_restart.nil? or vm.needs_restart == 0 -%> diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index ffe5055..1a3f81c 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -106,7 +106,7 @@ Num vcpus used:
      Memory allocated:
      Memory used:
      - vNIC MAC address:
      + vNIC MAC addresses:
      Boot device:
      Provisioning source:
      State:
      @@ -122,7 +122,9 @@ <%=h @vm.num_vcpus_used %>
      <%=h @vm.memory_allocated_in_mb %> MB
      <%=h @vm.memory_used_in_mb %> MB
      - <%=h @vm.vnic_mac_addr %>
      + <% nic_macs = "" + @vm.nics.each { |nic| nic_macs += nic.mac + " " } %> + <%=h nic_macs %>
      <%=h @vm.boot_device %>
      <%=h @vm.provisioning_and_boot_settings %>
      <%=h @vm.state %> diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 074cc09..3844611 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -339,4 +339,44 @@ height: 11px; } -.vm_form_section {padding-left: 2em;} \ No newline at end of file +.vm_form_section {padding-left: 2em;} + +#vm_network_config .i { + float: left; +} + +#vm_network_config_header_network { + float: left; + width: 20%; +} +#vm_network_config_header_mac, #vm_network_config_header_ip{ + float: left; + width: 33%; +} + +.vm_network_config_net { + float: left; + width: 20%; +} +.vm_network_config_mac, .vm_network_config_ip{ + float: left; + width: 33%; + min-width: 100px; +} + +.vm_network_config_remove { + float: left; + padding-top: 5px; + color: #0033CC; +} +.vm_network_config_remove:hover { + cursor: pointer; +} + +#vm_network_config_add { + color: #0033CC; +} +#vm_network_config_add:hover { + cursor: pointer; +} + -- 1.6.0.6 From mmorsi at redhat.com Fri Jul 24 21:01:37 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 24 Jul 2009 17:01:37 -0400 Subject: [Ovirt-devel] [PATCH server] permit many-to-many vms / networks relationship redux (test changes) In-Reply-To: <1248469297-28899-3-git-send-email-mmorsi@redhat.com> References: <1248469297-28899-1-git-send-email-mmorsi@redhat.com> <1248469297-28899-2-git-send-email-mmorsi@redhat.com> <1248469297-28899-3-git-send-email-mmorsi@redhat.com> Message-ID: <1248469297-28899-4-git-send-email-mmorsi@redhat.com> implements changes to the tests to test newly added / modified features --- src/test/fixtures/nics.yml | 16 ++++---- src/test/fixtures/vms.yml | 9 ---- src/test/unit/network_test.rb | 69 ++++++++++++++++++++++++++++-- src/test/unit/nic_test.rb | 93 ++++++++++++++++++++++++++++++++++++++++- src/test/unit/vm_test.rb | 13 +++--- 5 files changed, 170 insertions(+), 30 deletions(-) diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index 97397cd..aff7e9b 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -3,21 +3,21 @@ mailserver_nic_one: usage_type: 1 bandwidth: 100 host: mailservers_managed_node - physical_network: mail_network_one + network: mail_network_one mailserver_nic_two: mac: 22:11:33:66:44:55 usage_type: 1 bandwidth: 100 host: mailservers_managed_node - physical_network: mail_network_two + network: mail_network_two fileserver_nic_one: mac: 00:99:00:99:13:07 usage_type: 1 bandwidth: 100 host: fileserver_managed_node - physical_network: fileserver_network + network: fileserver_network ldapserver_nic_one: mac: 00:03:02:00:09:06 @@ -25,32 +25,32 @@ ldapserver_nic_one: bandwidth: 100 bridge: host: ldapserver_managed_node - physical_network: static_physical_network_one + network: static_physical_network_one buildserver_nic_one: mac: 07:17:19:65:03:38 usage_type: 1 bandwidth: 100 host: buildserver_managed_node - physical_network: dhcp_physical_network_one + network: dhcp_physical_network_one buildserver_nic_two: mac: 07:17:19:65:03:39 usage_type: 1 bandwidth: 100 host: buildserver_managed_node - physical_network: static_physical_network_one + network: static_physical_network_one mediaserver_nic_one: mac: 07:17:19:65:03:32 usage_type: 1 bandwidth: 100 host: mediaserver_managed_node - physical_network: mediaserver_network_one + network: mediaserver_network_one mediaserver_nic_two: mac: 07:17:19:65:03:31 usage_type: 1 bandwidth: 100 host: mediaserver_managed_node - physical_network: mediaserver_network_two + network: mediaserver_network_two diff --git a/src/test/fixtures/vms.yml b/src/test/fixtures/vms.yml index b2711b2..a7e871d 100644 --- a/src/test/fixtures/vms.yml +++ b/src/test/fixtures/vms.yml @@ -5,7 +5,6 @@ production_httpd_vm: num_vcpus_used: 2 memory_allocated: 262144 memory_used: 131072 - vnic_mac_addr: 23:51:90:A1:13:37 state: stopped needs_restart: 0 boot_device: hd @@ -20,7 +19,6 @@ production_mysqld_vm: num_vcpus_used: 1 memory_allocated: 2048 memory_used: 512 - vnic_mac_addr: 15:99:FE:ED:11:EE state: running needs_restart: 0 boot_device: network @@ -33,7 +31,6 @@ production_ftpd_vm: num_vcpus_used: 1 memory_allocated: 1024 memory_used: 512 - vnic_mac_addr: FF:AA:BB:00:11:55 state: stopped needs_restart: 1 boot_device: cdrom @@ -46,7 +43,6 @@ production_postgresql_vm: num_vcpus_used: 2 memory_allocated: 2048 memory_used: 1536 - vnic_mac_addr: 48:24:12:93:42:11 state: running needs_restart: 0 boot_device: hd @@ -59,7 +55,6 @@ foobar_prod1_vm: num_vcpus_used: 2 memory_allocated: 4096 memory_used: 4096 - vnic_mac_addr: FF:FF:FF:EE:EE:EE state: running needs_restart: 0 boot_device: cdrom @@ -72,7 +67,6 @@ corp_com_qa_postgres_vm: num_vcpus_used: 2 memory_allocated: 262144 memory_used: 131072 - vnic_mac_addr: 00:16:3e:04:de:c8 state: running needs_restart: 0 boot_device: hd @@ -85,7 +79,6 @@ foobar_prod2_vm: num_vcpus_used: 2 memory_allocated: 4096 memory_used: 4096 - vnic_mac_addr: EE:EE:EE:FF:FF:FF state: running needs_restart: 0 boot_device: cdrom @@ -98,7 +91,6 @@ corp_com_errata_vm: num_vcpus_used: 2 memory_allocated: 2048 memory_used: 2048 - vnic_mac_addr: 77:77:77:77:77:77 state: running needs_restart: 0 boot_device: network @@ -111,7 +103,6 @@ corp_com_bugzilla_vm: num_vcpus_used: 2 memory_allocated: 2048 memory_used: 2048 - vnic_mac_addr: 77:77:77:77:77:77 state: running needs_restart: 0 boot_device: network diff --git a/src/test/unit/network_test.rb b/src/test/unit/network_test.rb index 64c5df4..73ea7eb 100644 --- a/src/test/unit/network_test.rb +++ b/src/test/unit/network_test.rb @@ -1,8 +1,69 @@ -require 'test_helper' +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# 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_helper' class NetworkTest < ActiveSupport::TestCase - # Replace this with your real tests. - def test_truth - assert true + fixtures :networks + fixtures :vms + fixtures :hosts + fixtures :nics + fixtures :boot_types + + def setup + end + + def test_vlan_invalid_without_number + vl = Vlan.new({:name => 'testvlan', :boot_type_id => 2}) + flunk "Vlan without number should not be valid" if vl.valid? + vl.number = 1 + flunk "Vlan with number should be valid" unless vl.valid? + end + + def test_vlan_nics_only_associated_with_vm + vl = Vlan.create({:name => 'testvlan', + :boot_type => boot_types(:boot_type_dhcp), + :number => 1}) # need to create for id + nic = Nic.new({:mac => '11:22:33:44:55:66', + :bandwidth => 100, + :network => vl, + :host => hosts(:prod_corp_com)}) + vl.nics.push nic + flunk "Nic assigned to vlan must only be associated with vm" if vl.valid? + nic.host = nil + nic.vm = vms(:production_httpd_vm) + flunk "Vlan consisting of only vm nics should be valid" unless vl.valid? + end + + def test_physical_network_is_destroyable + pn = PhysicalNetwork.new + flunk "PhysicalNetwork with no nics should be destroyable" unless pn.is_destroyable? + pn.nics.push Nic.new + flunk "PhysicalNetwork with nics should not be destroyable" if pn.is_destroyable? + end + + def test_vlan_is_destroyable + vl = Vlan.new + flunk "Vlan with no nics and bondings should be destroyable" unless vl.is_destroyable? + vl.nics.push Nic.new + flunk "Vlan with nics should not be destroyable" if vl.is_destroyable? + vl.nics.clear + vl.bondings.push Bonding.new + flunk "Vlan with bondings should not be destroyable" if vl.is_destroyable? end end diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb index 46fab10..adaa2b5 100644 --- a/src/test/unit/nic_test.rb +++ b/src/test/unit/nic_test.rb @@ -24,6 +24,7 @@ class NicTest < ActiveSupport::TestCase fixtures :nics fixtures :hosts fixtures :networks + fixtures :vms def setup @nic = Nic.new( @@ -31,7 +32,7 @@ class NicTest < ActiveSupport::TestCase :usage_type => 1, :bandwidth => 100 ) @nic.host = hosts(:prod_corp_com) - @nic.physical_network = networks(:static_physical_network_one) + @nic.network = networks(:static_physical_network_one) @ip_address = IpV4Address.new( :address => '1.2.3.4', @@ -74,10 +75,98 @@ class NicTest < ActiveSupport::TestCase end def test_static_network_nic_must_have_ip - @nic.physical_network = networks(:static_physical_network_one) + @nic.network = networks(:static_physical_network_one) @nic.ip_addresses.delete_if { true } flunk 'Nics assigned to static networks must have at least one ip' if @nic.valid? end + def test_vm_nic_must_have_network + @nic.host = nil + @nic.vm = vms(:production_httpd_vm) + flunk 'vm nic that is assigned to network is valid' unless @nic.valid? + + @nic.network = nil + flunk 'vm nic without a network is not valid' if @nic.valid? + end + + def test_host_nic_cant_be_assigned_to_vlan + @nic.network = networks(:dhcp_vlan_one) + flunk 'host nic cant be assgined to vlan' if @nic.valid? + end + + def test_nic_networking + flunk 'nic.networking? should return true if assigned to network' unless @nic.networking? + @nic.network = nil + flunk 'nic.networking? should return false if not assigned to network' if @nic.networking? + end + + def test_nic_boot_protocol + nic = Nic.new + nic.ip_addresses << @ip_address + nic.network = networks(:static_physical_network_one) + flunk 'incorrect nic boot protocol' unless nic.boot_protocol == 'static' + end + + def test_nic_ip_addresses + nic = Nic.new + nic.ip_addresses << @ip_address + nic.network = networks(:static_physical_network_one) + flunk 'incorrect nic ip address' unless nic.ip_address == @ip_address.address + end + + def test_nic_netmask + nic = Nic.new + network = Network.new + network.ip_addresses << @ip_address + nic.network = network + + flunk 'incorrect nic netmask' unless nic.netmask == @ip_address.netmask + end + + def test_nic_broadcast + nic = Nic.new + network = Network.new + network.ip_addresses << @ip_address + nic.network = network + + flunk 'incorrect nic broadcast' unless nic.broadcast == @ip_address.broadcast + end + + def test_nic_gateway + nic = Nic.new + network = Network.new + network.ip_addresses << @ip_address + nic.network = network + + flunk 'incorrect nic gateway' unless nic.gateway == @ip_address.gateway + end + + def test_nic_parent + flunk 'incorrect host nic parent' unless @nic.parent == hosts(:prod_corp_com) + + @nic.host = nil + flunk 'incorrect nic parent' unless @nic.parent == nil + + @nic.vm = vms(:production_httpd_vm) + flunk 'incorrect vm nic parent' unless @nic.parent == vms(:production_httpd_vm) + end + + def test_nic_gen_mac + mac = Nic::gen_mac + flunk 'invalid generated mac' unless mac =~ /^([0-9a-fA-F]{2}([:-]|$)){6}$/ + end + + def test_nic_vm_xor_nic_host + flunk 'host nic without vm is valid' unless @nic.valid? + + @nic.vm = vms(:production_httpd_vm) + flunk 'nic cannot specify both host and vm' if @nic.valid? + + @nic.host = nil + flunk 'vm nic without host is valid' unless @nic.valid? + + @nic.vm = nil + flunk 'nic must specify either host or vm' if @nic.valid? + end end diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index a28f183..4fdef9b 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -38,8 +38,7 @@ class VmTest < ActiveSupport::TestCase :num_vcpus_allocated => 1, :boot_device => 'hd', :memory_allocated_in_mb => 1, - :memory_allocated => 1024, - :vnic_mac_addr => '11:22:33:44:55:66') + :memory_allocated => 1024} @vm.vm_resource_pool = pools(:corp_com_production_vmpool) end @@ -76,11 +75,6 @@ class VmTest < ActiveSupport::TestCase flunk 'Vm must specify memory_allocated_in_mb' if @vm.valid? end - def test_valid_fails_without_vnic_mac_addr - @vm.vnic_mac_addr = '' - flunk 'Vm must specify vnic_mac_addr' if @vm.valid? - end - def test_valid_fails_without_vm_resources_pool_id @vm.vm_resource_pool_id = '' flunk 'Vm must specify vm_resources_pool_id' if @vm.valid? @@ -201,4 +195,9 @@ class VmTest < ActiveSupport::TestCase assert_equal(5, vms.size) assert_equal('00:00:00',vms[0].calc_uptime) end + + def test_vm_gen_uuid + uuid = Vm::gen_uuid + flunk 'invalid generated uuid' unless uuid =~ /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/ + end end -- 1.6.0.6 From berrange at redhat.com Mon Jul 27 13:20:57 2009 From: berrange at redhat.com (Daniel P. Berrange) Date: Mon, 27 Jul 2009 14:20:57 +0100 Subject: [Ovirt-devel] [PATCH server] changes required for fedora rawhide inclusion. In-Reply-To: <1248371639-24255-1-git-send-email-sseago@redhat.com> References: <1248371639-24255-1-git-send-email-sseago@redhat.com> Message-ID: <20090727132057.GB9029@redhat.com> On Thu, Jul 23, 2009 at 05:53:59PM +0000, Scott Seago wrote: > > Signed-off-by: Scott Seago > --- > AUTHORS | 17 ++++++ > README | 10 +++ > conf/ovirt-agent | 12 ++++ > conf/ovirt-db-omatic | 12 ++++ > conf/ovirt-host-browser | 12 ++++ > conf/ovirt-host-collect | 12 ++++ > conf/ovirt-host-register | 12 ++++ > conf/ovirt-mongrel-rails | 7 ++- > conf/ovirt-server.logrotate | 57 +++++++++++++++++++ > conf/ovirt-taskomatic | 12 ++++ > conf/ovirt-vnc-proxy | 12 ++++ > ovirt-server.spec.in | 59 +++++++++++-------- > src/app/util/stats/Stats.rb | 1 - > src/app/util/stats/StatsData.rb | 1 - > src/app/util/stats/StatsDataList.rb | 1 - > src/app/util/stats/StatsRequest.rb | 1 - > src/app/util/stats/StatsTypes.rb | 1 - > src/app/util/stats/statsTest.rb | 1 - > src/dutils/active_record_env.rb | 1 - > src/flexchart/flexchart.swf | Bin 370538 -> 0 bytes > src/public/.htaccess | 40 ------------- > src/test/unit/host_browser_awaken_test.rb | 1 - > 22 files changed, 208 insertions(+), 74 deletions(-) > create mode 100644 AUTHORS > create mode 100644 README > create mode 100644 conf/ovirt-server.logrotate > mode change 100644 => 100755 installer/modules/ovirt/files/cobbler-import > mode change 100644 => 100755 installer/modules/ovirt/files/ovirt-appliance-setup > mode change 100644 => 100755 installer/modules/ovirt/files/ovirt-storage > mode change 100755 => 100644 src/app/views/layouts/ovirt-layout.rhtml > delete mode 100644 src/app/views/quota/show.rhtml > delete mode 100644 src/flexchart/flexchart.swf > delete mode 100644 src/public/.htaccess > mode change 100755 => 100644 src/public/javascripts/jquery.js > mode change 100755 => 100644 src/public/javascripts/ui.core.js > mode change 100755 => 100644 src/public/javascripts/ui.slider.js ACK Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :| From mburns at redhat.com Mon Jul 27 15:58:27 2009 From: mburns at redhat.com (Mike Burns) Date: Mon, 27 Jul 2009 11:58:27 -0400 Subject: [Ovirt-devel] [PATCH node-image] Expanded the size of the livecd image. In-Reply-To: <1248466691-3187-1-git-send-email-dpierce@redhat.com> References: <1248466691-3187-1-git-send-email-dpierce@redhat.com> Message-ID: <20090727155827.GC21595@mburns-laptop.bos.redhat.com> On Fri, Jul 24, 2009 at 04:18:11PM -0400, Darryl L. Pierce wrote: > It's not 600M in order to accomodate new RPMs. > > Signed-off-by: Darryl L. Pierce > --- > common-install.ks | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/common-install.ks b/common-install.ks > index a20a5b4..2a7fbb7 100644 > --- a/common-install.ks > +++ b/common-install.ks > @@ -4,7 +4,7 @@ timezone --utc UTC > auth --useshadow --enablemd5 > selinux --enforcing > firewall --disabled > -part / --size 550 --fstype ext2 > +part / --size 600 --fstype ext2 > services --enabled=auditd,ntpd,ntpdate,collectd,iptables,network,rsyslog,libvirt-qpid > # This requires a new fixed version of livecd-creator to honor the --append settings. > bootloader --timeout=30 --append="console=tty0 console=ttyS0,115200n8" > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > ACK From dpierce at redhat.com Mon Jul 27 16:06:22 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 27 Jul 2009 12:06:22 -0400 Subject: [Ovirt-devel] [PATCH node-image] Expanded the size of the livecd image. In-Reply-To: <20090727155827.GC21595@mburns-laptop.bos.redhat.com> References: <1248466691-3187-1-git-send-email-dpierce@redhat.com> <20090727155827.GC21595@mburns-laptop.bos.redhat.com> Message-ID: <20090727160622.GA3705@mcpierce-laptop.rdu.redhat.com> On Mon, Jul 27, 2009 at 11:58:27AM -0400, Mike Burns wrote: > On Fri, Jul 24, 2009 at 04:18:11PM -0400, Darryl L. Pierce wrote: > > It's not 600M in order to accomodate new RPMs. > > > > Signed-off-by: Darryl L. Pierce > > --- > > common-install.ks | 2 +- > > 1 files changed, 1 insertions(+), 1 deletions(-) > > > > diff --git a/common-install.ks b/common-install.ks > > index a20a5b4..2a7fbb7 100644 > > --- a/common-install.ks > > +++ b/common-install.ks > > @@ -4,7 +4,7 @@ timezone --utc UTC > > auth --useshadow --enablemd5 > > selinux --enforcing > > firewall --disabled > > -part / --size 550 --fstype ext2 > > +part / --size 600 --fstype ext2 > > services --enabled=auditd,ntpd,ntpdate,collectd,iptables,network,rsyslog,libvirt-qpid > > # This requires a new fixed version of livecd-creator to honor the --append settings. > > bootloader --timeout=30 --append="console=tty0 console=ttyS0,115200n8" > > -- > > 1.6.2.5 > > > > _______________________________________________ > > Ovirt-devel mailing list > > Ovirt-devel at redhat.com > > https://www.redhat.com/mailman/listinfo/ovirt-devel > > > > ACK Thanks. This is now pushed. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From jguiditt at redhat.com Mon Jul 27 18:47:30 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Mon, 27 Jul 2009 14:47:30 -0400 Subject: [Ovirt-devel] [PATCH server] convenience init script starting/stopping all ovirt services In-Reply-To: <1247676404-13175-1-git-send-email-mmorsi@redhat.com> References: <1247676404-13175-1-git-send-email-mmorsi@redhat.com> Message-ID: <1248720450.3369.0.camel@lenovo> On Wed, 2009-07-15 at 12:46 -0400, Mohammed Morsi wrote: > installed to /usr/sbin/ovirt_ctl > invoke with "sudo ovirt_ctl {start|stop|restart|on|off}" > --- > ovirt-server.spec.in | 2 + > scripts/ovirt_ctl | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 68 insertions(+), 0 deletions(-) > create mode 100755 scripts/ovirt_ctl > One change that we discussed in irc, then ACK, works for me > diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in > index 1bf73c7..5fda872 100644 > --- a/ovirt-server.spec.in > +++ b/ovirt-server.spec.in > @@ -141,6 +141,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/db-omatic.log > %{__cp} -a %{pbuild}/scripts/ovirt-vm2node %{buildroot}%{_bindir} > %{__cp} -a %{pbuild}/scripts/ovirt-reindex-search %{buildroot}%{_sbindir} > %{__cp} -a %{pbuild}/scripts/ovirt-update-search %{buildroot}%{_sbindir} > +%{__cp} -a %{pbuild}/scripts/ovirt_ctl %{buildroot}%{_sbindir} > %{__rm} -rf %{buildroot}%{app_root}/tmp > %{__mkdir} %{buildroot}%{_localstatedir}/lib/%{name}/tmp > %{__ln_s} %{_localstatedir}/lib/%{name}/tmp %{buildroot}%{app_root}/tmp > @@ -218,6 +219,7 @@ fi > %{_sbindir}/ovirt-update-search > %{_bindir}/ovirt-add-host > %{_bindir}/ovirt-vm2node > +%{_bindir}/ovirt_ctl this needs to be %{_sbindir} ^^ > %{_initrddir}/ovirt-host-browser > %{_initrddir}/ovirt-host-register > %{_initrddir}/ovirt-db-omatic > diff --git a/scripts/ovirt_ctl b/scripts/ovirt_ctl > new file mode 100755 > index 0000000..e9eddde > --- /dev/null > +++ b/scripts/ovirt_ctl > @@ -0,0 +1,66 @@ > +#!/bin/bash > +# control script for oVirt services, use to start/stop/restart services, and mark as on / off > + > +. /etc/init.d/functions > + > +SERVICE_CMD=/sbin/service > +CHKCONFIG_CMD=/sbin/chkconfig > + > +SERVICES=( ovirt-db-omatic ovirt-host-browser \ > + ovirt-host-collect ovirt-mongrel-rails \ > + ovirt-taskomatic ovirt-vnc-proxy ovirt-agent ) > + > +RUNLEVELS="2345" > + > +start() { > + for service in ${SERVICES[@]} > + do > + $SERVICE_CMD $service start > + done > +} > + > +stop() { > + for service in ${SERVICES[@]} > + do > + $SERVICE_CMD $service stop > + done > +} > + > +set_on(){ > + for service in ${SERVICES[@]} > + do > + $CHKCONFIG_CMD --levels $RUNLEVELS $service on > + done > +} > + > +set_off(){ > + for service in ${SERVICES[@]} > + do > + $CHKCONFIG_CMD --levels $RUNLEVELS $service off > + done > +} > + > +case "$1" in > + start) > + start > + ;; > + stop) > + stop > + ;; > + restart) > + stop > + start > + ;; > + on) > + set_on > + ;; > + off) > + set_off > + ;; > + *) > + echo "Usage: ovirt_ctl {start|stop|restart|on|off}" > + exit 1 > + ;; > +esac > + > +exit $RETVAL From arroy at redhat.com Mon Jul 27 19:36:59 2009 From: arroy at redhat.com (Arjun Roy) Date: Mon, 27 Jul 2009 15:36:59 -0400 Subject: [Ovirt-devel] [PATCH server] Added ovirt-host-register to ovirt_ctl convenience script. Message-ID: <1248723419-3588-1-git-send-email-arroy@redhat.com> --- scripts/ovirt_ctl | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt_ctl b/scripts/ovirt_ctl index a42d997..22d66fe 100755 --- a/scripts/ovirt_ctl +++ b/scripts/ovirt_ctl @@ -6,7 +6,7 @@ SERVICE_CMD=/sbin/service CHKCONFIG_CMD=/sbin/chkconfig -SERVICES=( ovirt-db-omatic ovirt-host-browser \ +SERVICES=( ovirt-db-omatic ovirt-host-browser ovirt-host-register \ ovirt-host-collect ovirt-mongrel-rails \ ovirt-taskomatic ovirt-vnc-proxy ovirt-agent ) -- 1.6.2.5 From justin at redfish-group.com Tue Jul 28 05:40:53 2009 From: justin at redfish-group.com (Justin Clacherty) Date: Tue, 28 Jul 2009 15:40:53 +1000 Subject: [Ovirt-devel] poweroff in vm causes the host to become unavailable In-Reply-To: <20090708181843.1758d23d@tp.mains.net> References: <4A553EC7.3070305@redfish-group.com> <20090708181843.1758d23d@tp.mains.net> Message-ID: <4A6E8F65.2030207@redfish-group.com> Ian Main wrote: > On Thu, 09 Jul 2009 10:50:15 +1000 > Justin Clacherty wrote: > > >> If I ssh to a vm running Centos and then execute "poweroff" from within >> the vm, the vm shuts down as expected but the host in the admin ui >> becomes unavailable. Is anyone else experiencing this? I haven't >> checked to see if other guest OS's do the same thing yet. >> > > The host becomes unreachable when the QMF libvirt object representing the > host is not available or the agent backing that object stops sending > heartbeats. > > Can you run: > > # ruby /usr/share/ovirt-server/qmf-libvirt-example.rb > > And see if the host object in question shows up? Hi Ian, I've had this happen again. When I run the ruby script it shows the host which is unavailable and also shows all the unreachable VMs (some of the output is below). How do I get the GUI to recognise the node is there so I can manage things again (without restarting)? I'd rather not have to restart everything as then I have to deal with the iSCSI wierdness which is going on. Is there anything else you'd like me to check while it's in this state? Cheers, Justin. [root at management ~]# ruby /usr/share/ovirt-server/qmf-libvirt-example.rb Connecting to amqp://management.ovirt.priv:5672.. node: node91.ovirt.priv property: hostname, node91.ovirt.priv property: uri, qemu:///system property: libvirtVersion, 0.6.3 property: apiVersion, 0.6.3 property: hypervisorVersion, 0.9.1 property: hypervisorType, QEMU property: model, x86_64 property: memory, 8195028 property: cpus, 4 property: mhz, 2666 property: nodes, 1 property: sockets, 1 property: cores, 4 property: threads, 1 getXMLDesc() status: 0 getXMLDesc() status: OK xml length: 1087 domain: Trixbox, state: running, id: 1 property: uuid, 815ad1a6-95c3-fb43-710f-dca6441476a8 property: name, Trixbox property: id, 1 property: node, 0-1-1-29-1 property: state, running property: numVcpus, 1 property: active, true getXMLDesc() status: 0 getXMLDesc() status: OK xml length: 1039 domain: Alfresco, state: running, id: 2 property: uuid, b028e774-67fe-8d4d-69be-45e081d8fb01 property: name, Alfresco property: id, 2 property: node, 0-1-1-29-1 property: state, running property: numVcpus, 1 property: active, true getXMLDesc() status: 0 getXMLDesc() status: OK xml length: 1086 domain: Ontime, state: running, id: 3 property: uuid, 1e3692a3-3166-8070-908a-e8a1f8d168c2 property: name, Ontime property: id, 3 property: node, 0-1-1-29-1 property: state, running property: numVcpus, 1 property: active, true getXMLDesc() status: 0 getXMLDesc() status: OK xml length: 1086 domain: Services, state: running, id: 6 property: uuid, 23a47161-3004-495e-da6a-28ec494a6960 property: name, Services property: id, 6 property: node, 0-1-1-29-1 property: state, running property: numVcpus, 1 property: active, true getXMLDesc() status: 0 getXMLDesc() status: OK xml length: 1096 domain: Kernel Dev, state: running, id: 14 property: uuid, 935f781d-9aad-a75d-b546-4478ebbe94e6 property: name, Kernel Dev property: id, 14 property: node, 0-1-1-29-1 property: state, running property: numVcpus, 2 property: active, true getXMLDesc() status: 0 getXMLDesc() status: OK xml length: 1279 domain: Repositories, state: running, id: 17 property: uuid, 9017129d-0a66-cfc7-328c-0ccf827c966b property: name, Repositories property: id, 17 property: node, 0-1-1-29-1 property: state, running property: numVcpus, 1 property: active, true From dhuff at redhat.com Tue Jul 28 13:04:12 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 09:04:12 -0400 Subject: [Ovirt-devel] [PATCH ovirt-node] Removed subpackages, stateful, stateless, logos, and selinux for inclusuion in Fedora Message-ID: <1248786252-32225-1-git-send-email-dhuff@redhat.com> rhbz#:51422 --- ovirt-node.spec.in | 149 +++++++++------------------------------------------ 1 files changed, 27 insertions(+), 122 deletions(-) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 3138011..b4e660d 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -43,76 +43,23 @@ Requires: nc Requires: grub Requires: /usr/sbin/crond Requires: anyterm -ExclusiveArch: %{ix86} x86_64 - -%define app_root %{_datadir}/%{name} - -%description -Provides a series of daemons and support utilities to allow an -oVirt Node to interact with the oVirt Server. -%package stateless -Summary: oVirt Node for running as embedded hypervisor -Group: Applications/System -Requires: %{name} = %{version}-%{release} -Conflicts: %{name}-stateful -ExclusiveArch: %{ix86} x86_64 - -%description stateless -Provides the oVirt Node functionality needed as part of the -ovirt-node-image creation. This provides a stateless oVirt Node -that runs as a livecd. - -%package stateful -Summary: oVirt Node for running on Fedora Hosts -Group: Applications/System -Requires: %{name} = %{version}-%{release} -Conflicts: %{name}-stateless -ExclusiveArch: %{ix86} x86_64 - -%description stateful -Provides the oVirt Node functionality needed to convert an existing -host into a Node in a stateful manner. Presently intended for use on -the host running the oVirt Appliance. - -%package logos -Summary: oVirt Node Logos -Group: System Environment/Base -BuildArch: noarch -Obsoletes: redhat-logos -Provides: redhat-logos = 10.0.1-1 -Provides: system-logos = 10.0.1-1 -Conflicts: fedora-logos -Conflicts: generic-logos -Conflicts: fedora-logos -Conflicts: anaconda-images <= 10 -Conflicts: redhat-artwork <= 5.0.5 - -%description logos -The ovirt-logos package contains various image files which can be -used by the bootloader, anaconda, and other related tools. - -%package release -Summary: %{product_family} release file -Group: System Environment/Base -Obsoletes: redhat-release -Provides: redhat-release -%description release -%{product_family} release files. - -%package selinux -Summary: SELinux policy module supporting ovirt-node -Group: System Environment/Base +# selunx-stuff BuildRequires: checkpolicy, selinux-policy-devel, hardlink %if "%{selinux_policyver}" != "" Requires: selinux-policy >= %{selinux_policyver} %endif -Requires: %{name} = %{version}-%{release} Requires(post): /usr/sbin/semodule, /sbin/restorecon Requires(postun): /usr/sbin/semodule, /sbin/restorecon -%description selinux -SELinux policy module supporting ovirt-node +ExclusiveArch: %{ix86} x86_64 + +%define app_root %{_datadir}/%{name} + +%description +Provides a series of daemons and support utilities to allow an +oVirt Node to interact with the oVirt Server. This package +should only be installed on the oVirt Node machine. %prep %setup -q @@ -196,24 +143,6 @@ cd - /usr/sbin/hardlink -cv %{buildroot}%{_datadir}/selinux -# ovirt-logos -# should be ifarch i386 -mkdir -p %{buildroot}/boot/grub -install -p -m 644 images/grub-splash.xpm.gz %{buildroot}/boot/grub/splash.xpm.gz -# end i386 bits -mkdir -p %{buildroot}/usr/lib/anaconda-runtime -install -p -m 644 images/syslinux-vesa-splash.jpg %{buildroot}/usr/lib/anaconda-runtime -# ovirt-logos - -# release files -echo "%{product_family} release %{version}%{?beta: %{beta}} (%{release})" > %{buildroot}/etc/ovirt-release -cp %{buildroot}/etc/ovirt-release %{buildroot}/etc/issue -echo "Kernel \r on an \m (\l)" >> %{buildroot}/etc/issue -cp %{buildroot}/etc/issue %{buildroot}/etc/issue.net -echo >> %{buildroot}/etc/issue -ln -s ovirt-release %{buildroot}/etc/redhat-release -ln -s ovirt-release %{buildroot}/etc/system-release - # ovirt-config-boot post-install hooks %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/ovirt-config-boot.d @@ -229,6 +158,13 @@ ln -s ovirt-release %{buildroot}/etc/system-release %{__ln_s} ../..%{_sbindir}/ovirt-config-boot-wrapper %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"98_Local install and reboot" %{__ln_s} ../..%{_sbindir}/ovirt-config-uninstall %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"99_Uninstall node" +# ovirt-logos +# should be ifarch i386 +mkdir -p %{buildroot}%{app_root}/images +install -p -m 644 images/grub-splash.xpm.gz %{buildroot}%{app_root}/images/splash.xpm.gz +# end i386 bits +install -p -m 644 images/syslinux-vesa-splash.jpg %{buildroot}%{app_root}/images/ +# ovirt-logos %clean %{__rm} -rf %{buildroot} @@ -236,8 +172,6 @@ ln -s ovirt-release %{buildroot}/etc/system-release %post # Setup basic collectd configuration sed '//,/<\/Plugin>/d' /etc/collectd.conf.in > /etc/collectd.conf - -%post stateless /sbin/chkconfig --add ovirt-early /sbin/chkconfig --add ovirt-firstboot /sbin/chkconfig --add ovirt @@ -246,55 +180,27 @@ sed '//,/<\/Plugin>/d' /etc/collectd.conf.in > /etc/collectd.con # /etc/chkconfig.d/collectd file, and then have to re-define collectd here /sbin/chkconfig --add collectd -%preun stateless -if [ "$1" = 0 ] ; then - /sbin/chkconfig --del ovirt-early - /sbin/chkconfig --del ovirt-firstboot - /sbin/chkconfig --del ovirt - /sbin/chkconfig --del ovirt-post -fi - -%post stateful -/sbin/chkconfig --add collectd - -%preun stateful - -%post selinux for selinuxvariant in %{selinux_variants}; do /usr/sbin/semodule -s ${selinuxvariant} -i \ %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || : done -%postun selinux -if [ $1 -eq 0 ] ; then - for selinuxvariant in %{selinux_variants}; do - /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || : - done -fi - -%files release +%files %defattr(-,root,root) %attr(0644,root,root) /etc/ovirt-release -/etc/redhat-release -/etc/system-release -%config(noreplace) %attr(0644,root,root) /etc/issue -%config(noreplace) %attr(0644,root,root) /etc/issue.net - -%files selinux -%defattr(-,root,root,0755) -%doc SELinux/* -%{_datadir}/selinux/*/%{modulename}.pp -%files logos -%defattr(-, root, root) +#logos %doc COPYING # should be ifarch i386 -/boot/grub/splash.xpm.gz +%{app_root}/images/splash.xpm.gz # end i386 bits -/usr/lib/anaconda-runtime/*.jpg +%{app_root}/images/*.jpg -%files stateless +# selinux-stuff %defattr(-,root,root,0755) +%doc SELinux/* +%{_datadir}/selinux/*/%{modulename}.pp + %{_sbindir}/ovirt-awake %{_sbindir}/ovirt-config-boot %{_sbindir}/ovirt-config-boot-wrapper @@ -323,13 +229,9 @@ fi %{_sysconfdir}/ovirt-config-boot.d %{_sysconfdir}/ovirt-config-setup.d -%files stateful -%defattr(-,root,root,0755) %{_sbindir}/ovirt-install-node-stateful %{_sbindir}/ovirt-uninstall-node-stateful -%files -%defattr(-,root,root,0755) %{_sbindir}/ovirt-awake %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) @@ -339,6 +241,9 @@ fi %config %attr(0644,root,root) %{_sysconfdir}/default/ovirt %changelog +* Thu Jun 23 2009 David Huff - 0.97 +- Removed subpackages, stateful, stateless, logos, and selinux for inclusuion in Fedora + * Thu Dec 11 2008 Perry Myers - 0.96 - Subpackage stateful/stateless to separate out functionality for embedded Node and Node running as part of already installed OS -- 1.6.0.6 From dhuff at redhat.com Tue Jul 28 13:06:18 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 09:06:18 -0400 Subject: [Ovirt-devel] Re: [PATCH ovirt-node] Removed subpackages, stateful, stateless, logos, and selinux for inclusuion in Fedora In-Reply-To: <1248786252-32225-1-git-send-email-dhuff@redhat.com> References: <1248786252-32225-1-git-send-email-dhuff@redhat.com> Message-ID: <4A6EF7CA.4030707@redhat.com> David Huff wrote: > rhbz#:51422 bug 514221 https://bugzilla.redhat.com/show_bug.cgi?id=514221 > --- > ovirt-node.spec.in | 149 +++++++++------------------------------------------ > 1 files changed, 27 insertions(+), 122 deletions(-) > > diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in > index 3138011..b4e660d 100644 > --- a/ovirt-node.spec.in > +++ b/ovirt-node.spec.in > @@ -43,76 +43,23 @@ Requires: nc > Requires: grub > Requires: /usr/sbin/crond > Requires: anyterm > -ExclusiveArch: %{ix86} x86_64 > - > -%define app_root %{_datadir}/%{name} > - > -%description > -Provides a series of daemons and support utilities to allow an > -oVirt Node to interact with the oVirt Server. > > -%package stateless > -Summary: oVirt Node for running as embedded hypervisor > -Group: Applications/System > -Requires: %{name} = %{version}-%{release} > -Conflicts: %{name}-stateful > -ExclusiveArch: %{ix86} x86_64 > - > -%description stateless > -Provides the oVirt Node functionality needed as part of the > -ovirt-node-image creation. This provides a stateless oVirt Node > -that runs as a livecd. > - > -%package stateful > -Summary: oVirt Node for running on Fedora Hosts > -Group: Applications/System > -Requires: %{name} = %{version}-%{release} > -Conflicts: %{name}-stateless > -ExclusiveArch: %{ix86} x86_64 > - > -%description stateful > -Provides the oVirt Node functionality needed to convert an existing > -host into a Node in a stateful manner. Presently intended for use on > -the host running the oVirt Appliance. > - > -%package logos > -Summary: oVirt Node Logos > -Group: System Environment/Base > -BuildArch: noarch > -Obsoletes: redhat-logos > -Provides: redhat-logos = 10.0.1-1 > -Provides: system-logos = 10.0.1-1 > -Conflicts: fedora-logos > -Conflicts: generic-logos > -Conflicts: fedora-logos > -Conflicts: anaconda-images <= 10 > -Conflicts: redhat-artwork <= 5.0.5 > - > -%description logos > -The ovirt-logos package contains various image files which can be > -used by the bootloader, anaconda, and other related tools. > - > -%package release > -Summary: %{product_family} release file > -Group: System Environment/Base > -Obsoletes: redhat-release > -Provides: redhat-release > -%description release > -%{product_family} release files. > - > -%package selinux > -Summary: SELinux policy module supporting ovirt-node > -Group: System Environment/Base > +# selunx-stuff > BuildRequires: checkpolicy, selinux-policy-devel, hardlink > %if "%{selinux_policyver}" != "" > Requires: selinux-policy >= %{selinux_policyver} > %endif > -Requires: %{name} = %{version}-%{release} > Requires(post): /usr/sbin/semodule, /sbin/restorecon > Requires(postun): /usr/sbin/semodule, /sbin/restorecon > > -%description selinux > -SELinux policy module supporting ovirt-node > +ExclusiveArch: %{ix86} x86_64 > + > +%define app_root %{_datadir}/%{name} > + > +%description > +Provides a series of daemons and support utilities to allow an > +oVirt Node to interact with the oVirt Server. This package > +should only be installed on the oVirt Node machine. > > %prep > %setup -q > @@ -196,24 +143,6 @@ cd - > > /usr/sbin/hardlink -cv %{buildroot}%{_datadir}/selinux > > -# ovirt-logos > -# should be ifarch i386 > -mkdir -p %{buildroot}/boot/grub > -install -p -m 644 images/grub-splash.xpm.gz %{buildroot}/boot/grub/splash.xpm.gz > -# end i386 bits > -mkdir -p %{buildroot}/usr/lib/anaconda-runtime > -install -p -m 644 images/syslinux-vesa-splash.jpg %{buildroot}/usr/lib/anaconda-runtime > -# ovirt-logos > - > -# release files > -echo "%{product_family} release %{version}%{?beta: %{beta}} (%{release})" > %{buildroot}/etc/ovirt-release > -cp %{buildroot}/etc/ovirt-release %{buildroot}/etc/issue > -echo "Kernel \r on an \m (\l)" >> %{buildroot}/etc/issue > -cp %{buildroot}/etc/issue %{buildroot}/etc/issue.net > -echo >> %{buildroot}/etc/issue > -ln -s ovirt-release %{buildroot}/etc/redhat-release > -ln -s ovirt-release %{buildroot}/etc/system-release > - > # ovirt-config-boot post-install hooks > %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/ovirt-config-boot.d > > @@ -229,6 +158,13 @@ ln -s ovirt-release %{buildroot}/etc/system-release > %{__ln_s} ../..%{_sbindir}/ovirt-config-boot-wrapper %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"98_Local install and reboot" > %{__ln_s} ../..%{_sbindir}/ovirt-config-uninstall %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"99_Uninstall node" > > +# ovirt-logos > +# should be ifarch i386 > +mkdir -p %{buildroot}%{app_root}/images > +install -p -m 644 images/grub-splash.xpm.gz %{buildroot}%{app_root}/images/splash.xpm.gz > +# end i386 bits > +install -p -m 644 images/syslinux-vesa-splash.jpg %{buildroot}%{app_root}/images/ > +# ovirt-logos > > %clean > %{__rm} -rf %{buildroot} > @@ -236,8 +172,6 @@ ln -s ovirt-release %{buildroot}/etc/system-release > %post > # Setup basic collectd configuration > sed '//,/<\/Plugin>/d' /etc/collectd.conf.in > /etc/collectd.conf > - > -%post stateless > /sbin/chkconfig --add ovirt-early > /sbin/chkconfig --add ovirt-firstboot > /sbin/chkconfig --add ovirt > @@ -246,55 +180,27 @@ sed '//,/<\/Plugin>/d' /etc/collectd.conf.in > /etc/collectd.con > # /etc/chkconfig.d/collectd file, and then have to re-define collectd here > /sbin/chkconfig --add collectd > > -%preun stateless > -if [ "$1" = 0 ] ; then > - /sbin/chkconfig --del ovirt-early > - /sbin/chkconfig --del ovirt-firstboot > - /sbin/chkconfig --del ovirt > - /sbin/chkconfig --del ovirt-post > -fi > - > -%post stateful > -/sbin/chkconfig --add collectd > - > -%preun stateful > - > -%post selinux > for selinuxvariant in %{selinux_variants}; do > /usr/sbin/semodule -s ${selinuxvariant} -i \ > %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || : > done > > -%postun selinux > -if [ $1 -eq 0 ] ; then > - for selinuxvariant in %{selinux_variants}; do > - /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || : > - done > -fi > - > -%files release > +%files > %defattr(-,root,root) > %attr(0644,root,root) /etc/ovirt-release > -/etc/redhat-release > -/etc/system-release > -%config(noreplace) %attr(0644,root,root) /etc/issue > -%config(noreplace) %attr(0644,root,root) /etc/issue.net > - > -%files selinux > -%defattr(-,root,root,0755) > -%doc SELinux/* > -%{_datadir}/selinux/*/%{modulename}.pp > > -%files logos > -%defattr(-, root, root) > +#logos > %doc COPYING > # should be ifarch i386 > -/boot/grub/splash.xpm.gz > +%{app_root}/images/splash.xpm.gz > # end i386 bits > -/usr/lib/anaconda-runtime/*.jpg > +%{app_root}/images/*.jpg > > -%files stateless > +# selinux-stuff > %defattr(-,root,root,0755) > +%doc SELinux/* > +%{_datadir}/selinux/*/%{modulename}.pp > + > %{_sbindir}/ovirt-awake > %{_sbindir}/ovirt-config-boot > %{_sbindir}/ovirt-config-boot-wrapper > @@ -323,13 +229,9 @@ fi > %{_sysconfdir}/ovirt-config-boot.d > %{_sysconfdir}/ovirt-config-setup.d > > -%files stateful > -%defattr(-,root,root,0755) > %{_sbindir}/ovirt-install-node-stateful > %{_sbindir}/ovirt-uninstall-node-stateful > > -%files > -%defattr(-,root,root,0755) > %{_sbindir}/ovirt-awake > %{_initrddir}/ovirt-functions > %defattr(-,root,root,0644) > @@ -339,6 +241,9 @@ fi > %config %attr(0644,root,root) %{_sysconfdir}/default/ovirt > > %changelog > +* Thu Jun 23 2009 David Huff - 0.97 > +- Removed subpackages, stateful, stateless, logos, and selinux for inclusuion in Fedora > + > * Thu Dec 11 2008 Perry Myers - 0.96 > - Subpackage stateful/stateless to separate out functionality for > embedded Node and Node running as part of already installed OS From harsha at gluster.com Tue Jul 28 13:49:36 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 19:19:36 +0530 Subject: [Ovirt-devel] GlusterFS patches for ovirt-server, ovirt-node, ovirt-node-image Message-ID: <8a80e9760907280649t76277c00va56c859a9cf9ba47@mail.gmail.com> Resending patches again after rebasing against the "origin/next" so that it is helpful to merge it with master. Requested by Hugh Brock. Regards -- Harshavardhana Gluster - http://www.gluster.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From harsha at gluster.com Tue Jul 28 13:50:40 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 06:50:40 -0700 Subject: [Ovirt-devel] [PATCH 1/5 ovirt-server] Add glusterfs to task-omatic API for {task_storage,utils} Message-ID: <20090728135040.GA2137@dev.gluster.com> --- src/task-omatic/task_storage.rb | 50 +++++++++++++++++++++++++++++++++++++++ src/task-omatic/utils.rb | 40 +++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 0 deletions(-) diff --git a/src/task-omatic/task_storage.rb b/src/task-omatic/task_storage.rb index 8165818..0272fbb 100644 --- a/src/task-omatic/task_storage.rb +++ b/src/task-omatic/task_storage.rb @@ -202,6 +202,8 @@ class LibvirtPool return IscsiLibvirtPool.new(pool.ip_addr, pool[:target], pool[:port], logger) elsif pool[:type] == "NfsStoragePool" return NFSLibvirtPool.new(pool.ip_addr, pool.export_path, logger) + elsif pool[:type] == "GlusterfsStoragePool" + return GLUSTERFSLibvirtPool.new(pool.ip_addr, pool.export_path, logger) elsif pool[:type] == "LvmStoragePool" # OK, if this is LVM storage, there are two cases we need to care about: # 1) this is a LUN with LVM already on it. In this case, all we need to @@ -293,6 +295,54 @@ class NFSLibvirtPool < LibvirtPool end end +class GLUSTERFSLibvirtPool < LibvirtPool + def initialize(ip_addr, export_path, logger) + target = "#{ip_addr}-#{export_path.tr('/', '_')}" + super('netfs', target, logger) + + @type = 'netfs' + @host = ip_addr + @remote_vol = export_path + + @xml.root.elements["source"].add_element("host", {"name" => @host}) + @xml.root.elements["source"].add_element("dir", {"path" => @remote_vol}) + @xml.root.elements["source"].add_element("format", {"type" => "glusterfs"}) + + @xml.root.elements["target"].elements["path"].text = "/mnt/" + @name + end + + def create_vol(name, size, owner, group, mode) + # FIXME: this can actually take some time to complete (since we aren't + # doing sparse allocations at the moment). During that time, whichever + # libvirtd we chose to use is completely hung up. The solution is 3-fold: + # 1. Allow sparse allocations in the WUI front-end + # 2. Make libvirtd multi-threaded + # 3. Make taskomatic multi-threaded + super("netfs", name, size, owner, group, mode) + + # FIXME: we have to add the format as raw here because of a bug in libvirt; + # if you specify a volume with no format, it will crash libvirtd + @vol_xml.root.elements["target"].add_element("format", {"type" => "qcow2"}) + + # FIXME: Add allocation 0 element so that we create a sparse file. + # This was done because qmf was timing out waiting for the create + # operation to complete. This needs to be fixed in a better way + # however. We want to have non-sparse files for performance reasons. + @vol_xml.root.add_element("allocation").add_text('0') + + @logger.debug("Creating new volume on pool #{@remote_pool.name} - XML: #{@vol_xml.to_s}") + result = @remote_pool.createVolumeXML(@vol_xml.to_s) + raise "Error creating remote pool: #{result.text}" unless result.status == 0 + return result.volume + end + + def xmlequal?(docroot) + return (docroot.attributes['type'] == @type and + docroot.elements['source'].elements['host'].attributes['name'] == @host and + docroot.elements['source'].elements['dir'].attributes['path'] == @remote_vol) + end +end + class LVMLibvirtPool < LibvirtPool def initialize(vg_name, device, build_on_start, logger) super('logical', vg_name, logger) diff --git a/src/task-omatic/utils.rb b/src/task-omatic/utils.rb index e3005ed..cf68cae 100644 --- a/src/task-omatic/utils.rb +++ b/src/task-omatic/utils.rb @@ -114,6 +114,8 @@ class LibvirtPool return IscsiLibvirtPool.new(pool.ip_addr, pool[:target]) elsif pool[:type] == "NfsStoragePool" return NFSLibvirtPool.new(pool.ip_addr, pool.export_path) + elsif pool[:type] == "GlusterfsStoragePool" + return GLUSTERFSLibvirtPool.new(pool.ip_addr, pool.export_path) elsif pool[:type] == "LvmStoragePool" # OK, if this is LVM storage, there are two cases we need to care about: # 1) this is a LUN with LVM already on it. In this case, all we need to @@ -195,6 +197,44 @@ class NFSLibvirtPool < LibvirtPool end end +class GLUSTERFSLibvirtPool < LibvirtPool + def initialize(ip_addr, export_path) + super('netfs') + + @type = 'netfs' + @host = ip_addr + @remote_vol = export_path + @name = String.random_alphanumeric + + @xml.root.elements["source"].add_element("host", {"name" => @host}) + @xml.root.elements["source"].add_element("dir", {"path" => @export_path}) + @xml.root.elements["source"].add_element("format", {"type" => "glusterfs"}) + + @xml.root.elements["target"].elements["path"].text = "/mnt/" + @name + end + + def create_vol(name, size, owner, group, mode) + # FIXME: this can actually take some time to complete (since we aren't + # doing sparse allocations at the moment). During that time, whichever + # libvirtd we chose to use is completely hung up. The solution is 3-fold: + # 1. Allow sparse allocations in the WUI front-end + # 2. Make libvirtd multi-threaded + # 3. Make taskomatic multi-threaded + super("netfs", name, size, owner, group, mode) + + # FIXME: we have to add the format as raw here because of a bug in libvirt; + # if you specify a volume with no format, it will crash libvirtd + @vol_xml.root.elements["target"].add_element("format", {"type" => "raw"}) + @remote_pool.create_vol_xml(@vol_xml.to_s) + end + + def xmlequal?(docroot) + return (docroot.attributes['type'] == @type and + docroot.elements['source'].elements['host'].attributes['name'] == @host) and + docroot.elements['source'].elements['dir'].attributes['path'] == @remote_vol) + end +end + class LVMLibvirtPool < LibvirtPool def initialize(vg_name, device, build_on_start) super('logical', vg_name) -- 1.6.0.6 From harsha at gluster.com Tue Jul 28 13:51:17 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 06:51:17 -0700 Subject: [Ovirt-devel] [PATCH 2/5] Added new glusterfs models for storage_pool, volume respectively Message-ID: <20090728135117.GA2227@dev.gluster.com> --- src/app/models/glusterfs_storage_pool.rb | 33 ++++++++++++++++++++++++++++ src/app/models/glusterfs_storage_volume.rb | 32 +++++++++++++++++++++++++++ src/app/models/storage_pool.rb | 6 ++++- src/app/models/storage_volume.rb | 4 ++- 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/app/models/glusterfs_storage_pool.rb create mode 100644 src/app/models/glusterfs_storage_volume.rb diff --git a/src/app/models/glusterfs_storage_pool.rb b/src/app/models/glusterfs_storage_pool.rb new file mode 100644 index 0000000..0d5bbfb --- /dev/null +++ b/src/app/models/glusterfs_storage_pool.rb @@ -0,0 +1,33 @@ +# +# Copyright (C) 2008 Z Research Inc. +# Written by Harshavardhana +# +# 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. + +class GlusterfsStoragePool < StoragePool + + validates_presence_of :ip_addr, :export_path + validates_uniqueness_of :ip_addr, :scope => :export_path + + def label_components + "#{export_path}" + end + + def user_subdividable + true + end + +end diff --git a/src/app/models/glusterfs_storage_volume.rb b/src/app/models/glusterfs_storage_volume.rb new file mode 100644 index 0000000..3cf7879 --- /dev/null +++ b/src/app/models/glusterfs_storage_volume.rb @@ -0,0 +1,32 @@ +# +# Copyright (C) 2008 Z Research Inc. +# Written by Harshavardhana +# +# 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. + +class GlusterfsStorageVolume < StorageVolume + def label_components + "#{storage_pool.export_path}/#{filename}" + end + + def volume_name + "filename" + end + + def volume_create_params + return filename, size, nil, nil, nil + end +end diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb index 40333d0..9416bb3 100644 --- a/src/app/models/storage_pool.rb +++ b/src/app/models/storage_pool.rb @@ -40,7 +40,7 @@ class StoragePool < ActiveRecord::Base validates_presence_of :hardware_pool_id validates_inclusion_of :type, - :in => %w( IscsiStoragePool LvmStoragePool NfsStoragePool ) + :in => %w( IscsiStoragePool LvmStoragePool NfsStoragePool GlusterfsStoragePool ) validates_numericality_of :capacity, @@ -52,9 +52,11 @@ class StoragePool < ActiveRecord::Base :eager_load => :smart_pools ISCSI = "iSCSI" NFS = "NFS" + GLUSTERFS = "GLUSTERFS" LVM = "LVM" STORAGE_TYPES = { ISCSI => "Iscsi", NFS => "Nfs", + GLUSTERFS => "Glusterfs", LVM => "Lvm" } STORAGE_TYPE_PICKLIST = STORAGE_TYPES.keys - [LVM] @@ -72,6 +74,8 @@ class StoragePool < ActiveRecord::Base return IscsiStoragePool.new(params) when NFS return NfsStoragePool.new(params) + when GLUSTERFS + return GlusterfsStoragePool.new(params) when LVM return LvmStoragePool.new(params) else diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb index 45cd5c6..0570f14 100644 --- a/src/app/models/storage_volume.rb +++ b/src/app/models/storage_volume.rb @@ -40,7 +40,7 @@ class StorageVolume < ActiveRecord::Base :unless => Proc.new { |storage_volume| storage_volume.nil? } validates_inclusion_of :type, - :in => %w( IscsiStorageVolume LvmStorageVolume NfsStorageVolume ) + :in => %w( IscsiStorageVolume LvmStorageVolume NfsStorageVolume GlusterfsStorageVolume ) STATE_PENDING_SETUP = "pending_setup" STATE_PENDING_DELETION = "pending_deletion" @@ -56,6 +56,8 @@ class StorageVolume < ActiveRecord::Base return IscsiStorageVolume.new(params) when StoragePool::NFS return NfsStorageVolume.new(params) + when StoragePool::GLUSTERFS + return GlusterfsStorageVolume.new(params) when StoragePool::LVM return LvmStorageVolume.new(params) else -- 1.6.0.6 From harsha at gluster.com Tue Jul 28 13:51:39 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 06:51:39 -0700 Subject: [Ovirt-devel] [PATCH 3/5] Storage views patched for glusterfs drop down as one the Storage Pools Message-ID: <20090728135139.GA2240@dev.gluster.com> --- src/app/views/storage/_form.rhtml | 2 ++ src/app/views/storage/_list.rhtml | 4 ++++ src/app/views/storage/_list_volumes.rhtml | 4 ++++ src/app/views/storage/show.rhtml | 4 ++++ .../views/storage_volume/_new_volume_form.rhtml | 1 + src/app/views/storage_volume/show.rhtml | 8 ++++++++ 6 files changed, 23 insertions(+), 0 deletions(-) diff --git a/src/app/views/storage/_form.rhtml b/src/app/views/storage/_form.rhtml index ea2b0c4..1e7f22f 100644 --- a/src/app/views/storage/_form.rhtml +++ b/src/app/views/storage/_form.rhtml @@ -12,6 +12,8 @@ <%= text_field_with_label "Export Path:", 'storage_pool', 'export_path' if @storage_pool[:type] == "NfsStoragePool" %> +<%= text_field_with_label "Export Path:", 'storage_pool', 'export_path' if @storage_pool[:type] == "GlusterfsStoragePool" %> + diff --git a/src/app/views/storage/_list.rhtml b/src/app/views/storage/_list.rhtml index ccb0dbe..32e905a 100644 --- a/src/app/views/storage/_list.rhtml +++ b/src/app/views/storage/_list.rhtml @@ -11,6 +11,8 @@ target <%elsif type == StoragePool::NFS -%> export path +<%elsif type == StoragePool::GLUSTERFS -%> + export path <% end -%> @@ -28,6 +30,8 @@ <%= storage_pool[:target] %> <%elsif type == StoragePool::NFS -%> <%= storage_pool[:export_path] %> +<%elsif type == StoragePool::GLUSTERFS -%> + <%= storage_pool[:export_path] %> <% end -%> <%- if defined?(remove_from_pool) && remove_from_pool -%> <%= link_to( 'detach', { :controller => "storage", :action => 'remove_from_pool', :id => storage_pool, :hardware_pool_id => hardware_pool }, :confirm => 'Are you sure?', :method => :post, :class => "remove") %> diff --git a/src/app/views/storage/_list_volumes.rhtml b/src/app/views/storage/_list_volumes.rhtml index ab4f623..b30e83b 100644 --- a/src/app/views/storage/_list_volumes.rhtml +++ b/src/app/views/storage/_list_volumes.rhtml @@ -12,6 +12,8 @@ LUN <%elsif type == StoragePool::NFS -%> export path +<%elsif type == StoragePool::GLUSTERFS -%> + export path <% end -%> size (gigs) @@ -30,6 +32,8 @@ <%= storage_volume.lun %> <%elsif type == StoragePool::NFS -%> <%= "#{storage_volume.storage_pool.export_path}/#{storage_volume.filename}" if storage_volume[:type] == "NfsStorageVolume" %> +<%elsif type == StoragePool::GLUSTERFS -%> + <%= "#{storage_volume.storage_pool.export_path}/#{storage_volume.filename}" if storage_volume[:type] == "GlusterfsStorageVolume" %> <% end -%> <%= storage_volume.size_in_gb %> diff --git a/src/app/views/storage/show.rhtml b/src/app/views/storage/show.rhtml index b0b1d4e..1366b13 100644 --- a/src/app/views/storage/show.rhtml +++ b/src/app/views/storage/show.rhtml @@ -29,6 +29,8 @@ Target:
      <% elsif @storage_pool[:type] == "NfsStoragePool" %> Export path:
      + <% elsif @storage_pool[:type] == "GlusterfsStoragePool" %> + Export volume:
      <% end %> Type:
      State:
      @@ -40,6 +42,8 @@ <%=h @storage_pool.target %>
      <% elsif @storage_pool[:type] == "NfsStoragePool" %> <%=h @storage_pool.export_path %>
      + <% elsif @storage_pool[:type] == "GlusterfsStoragePool" %> + <%=h @storage_pool.export_path %>
      <% end %> <%=h @storage_pool.get_type_label %>
      <%=h @storage_pool.state %>
      diff --git a/src/app/views/storage_volume/_new_volume_form.rhtml b/src/app/views/storage_volume/_new_volume_form.rhtml index ae65e18..d066ab2 100644 --- a/src/app/views/storage_volume/_new_volume_form.rhtml +++ b/src/app/views/storage_volume/_new_volume_form.rhtml @@ -19,6 +19,7 @@ <%= text_field_with_label "Filename:", 'storage_volume', 'filename' if @storage_volume.get_type_label==StoragePool::NFS %> +<%= text_field_with_label "Filename:", 'storage_volume', 'filename' if @storage_volume.get_type_label==StoragePool::GLUSTERFS %> diff --git a/src/app/views/storage_volume/show.rhtml b/src/app/views/storage_volume/show.rhtml index cefc51a..e1de593 100644 --- a/src/app/views/storage_volume/show.rhtml +++ b/src/app/views/storage_volume/show.rhtml @@ -27,6 +27,8 @@ Target:
      <% elsif @storage_volume.storage_pool[:type] == "NfsStoragePool" %> Export path:
      + <% elsif @storage_volume.storage_pool[:type] == "GlusterfsStoragePool" %> + Export volume:
      <% end %> Type:
      State:
      @@ -35,6 +37,8 @@ LUN:
      <% elsif @storage_volume[:type] == "NfsStorageVolume" %> Filename:
      + <% elsif @storage_volume[:type] == "GlusterfsStorageVolume" %> + Filename:
      <% elsif @storage_volume[:type] == "LvmStorageVolume" %> Volume Group:
      Logical Volume:
      @@ -51,6 +55,8 @@ <%=h @storage_volume.storage_pool[:target] %>
      <% elsif @storage_volume.storage_pool[:type] == "NfsStoragePool" %> <%=h @storage_volume.storage_pool.export_path %>
      + <% elsif @storage_volume.storage_pool[:type] == "GlusterfsStoragePool" %> + <%=h @storage_volume.storage_pool.export_path %>
      <% end %> <%=h @storage_volume.storage_pool.get_type_label %>
      <%=h @storage_volume.state %>
      @@ -59,6 +65,8 @@ <%=h @storage_volume.lun %>
      <% elsif @storage_volume[:type] == "NfsStorageVolume" %> <%=h @storage_volume.filename %>
      + <% elsif @storage_volume[:type] == "GlusterfsStorageVolume" %> + <%=h @storage_volume.filename %>
      <% elsif @storage_volume[:type] == "LvmStorageVolume" %> <%=h @storage_volume.storage_pool.vg_name %>
      <%=h @storage_volume.lv_name %>
      -- 1.6.0.6 From harsha at gluster.com Tue Jul 28 13:52:17 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 06:52:17 -0700 Subject: [Ovirt-devel] [PATCH 4/5] Added glusterfs for new search controller parameter Message-ID: <20090728135217.GA2250@dev.gluster.com> --- src/app/controllers/search_controller.rb | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/src/app/controllers/search_controller.rb b/src/app/controllers/search_controller.rb index 0fb6456..fb78418 100644 --- a/src/app/controllers/search_controller.rb +++ b/src/app/controllers/search_controller.rb @@ -37,17 +37,23 @@ class SearchController < ApplicationController "NfsStoragePool" => {:controller => "storage", :show_action => "show", :searched => true}, + "GlusterfsStoragePool" => {:controller => "storage" , + :show_action => "show", + :searched => true}, "IscsiStorageVolume" => {:controller => "storage_volume", :show_action => "show", :searched => false}, "NfsStorageVolume" => {:controller => "storage_volume", :show_action => "show", :searched => false}, + "GlusterfsStorageVolume" => {:controller => "storage_volume", + :show_action => "show", + :searched => false}, "LvmStorageVolume" => {:controller => "storage_volume", :show_action => "show", :searched => false}} - MULTI_TYPE_MODELS = {"StoragePool" => ["IscsiStoragePool", "NfsStoragePool"]} + MULTI_TYPE_MODELS = {"StoragePool" => ["IscsiStoragePool", "NfsStoragePool", "GlusterfsStoragePool"]} def single_result -- 1.6.0.6 From harsha at gluster.com Tue Jul 28 13:52:47 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 06:52:47 -0700 Subject: [Ovirt-devel] [PATCH 5/5] added new glusterfs MODELS into update and reindex search scripts Message-ID: <20090728135247.GA2260@dev.gluster.com> --- scripts/ovirt-reindex-search | 2 +- scripts/ovirt-update-search | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ovirt-reindex-search b/scripts/ovirt-reindex-search index cf4e38e..a896581 100755 --- a/scripts/ovirt-reindex-search +++ b/scripts/ovirt-reindex-search @@ -2,5 +2,5 @@ [ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails RAILS_ENV="${RAILS_ENV:-production}" RAKEFILE=/usr/share/ovirt-server/Rakefile -MODELS="Host Vm IscsiStoragePool NfsStoragePool HardwarePool VmResourcePool" +MODELS="Host Vm IscsiStoragePool NfsStoragePool GlusterfsStoragePool HardwarePool VmResourcePool" RAILS_ENV=$RAILS_ENV rake -f $RAKEFILE xapian:rebuild_index models="$MODELS" diff --git a/scripts/ovirt-update-search b/scripts/ovirt-update-search index 2b2c107..7aaecac 100755 --- a/scripts/ovirt-update-search +++ b/scripts/ovirt-update-search @@ -2,5 +2,5 @@ [ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails RAILS_ENV="${RAILS_ENV:-production}" RAKEFILE=/usr/share/ovirt-server/Rakefile -MODELS="Host Vm IscsiStoragePool NfsStoragePool HardwarePool VmResourcePool" +MODELS="Host Vm IscsiStoragePool NfsStoragePool GlusterfsStoragePool HardwarePool VmResourcePool" RAILS_ENV=$RAILS_ENV rake -f $RAKEFILE xapian:update_index models="$MODELS" -- 1.6.0.6 From harsha at gluster.com Tue Jul 28 13:55:01 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 06:55:01 -0700 Subject: [Ovirt-devel] [PATCH] removed fs/fuse from unused kernel modules list for glusterfs Message-ID: <20090728135501.GA2296@dev.gluster.com> --- common-blacklist.ks | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/common-blacklist.ks b/common-blacklist.ks index 2452237..7c19aa4 100644 --- a/common-blacklist.ks +++ b/common-blacklist.ks @@ -44,7 +44,7 @@ done # fs subdir, your mods entry would be "fs/nls" fs_mods="fs/nls fs/9p fs/affs fs/autofs fs/autofs4 fs/befs fs/bfs fs/cifs \ fs/coda fs/cramfs fs/dlm fs/ecryptfs fs/efs fs/exportfs fs/ext4 \ - fs/freevxfs fs/fuse fs/gfs2 fs/hfs fs/hfsplus fs/jbd2 fs/jffs \ + fs/freevxfs fs/gfs2 fs/hfs fs/hfsplus fs/jbd2 fs/jffs \ fs/jffs2 fs/jfs fs/minix fs/ncpfs fs/ocfs2 fs/qnx4 fs/reiserfs \ fs/romfs fs/sysv fs/udf fs/ufs fs/xfs" -- 1.6.0.6 From harsha at gluster.com Tue Jul 28 14:05:19 2009 From: harsha at gluster.com (Harshavardhana) Date: Tue, 28 Jul 2009 07:05:19 -0700 Subject: [Ovirt-devel] [PATCH] add glusterfs-client dependency for ovirt-node Message-ID: <20090728140519.GA2349@dev.gluster.com> --- ovirt-node.spec.in | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index e5d1d51..f7fa1e7 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -21,7 +21,7 @@ Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig BuildRequires: libvirt-devel >= 0.5.1 BuildRequires: dbus-devel hal-devel -Requires: libvirt >= 0.5.1 +Requires: libvirt >= 0.6.3 Requires: augeas >= 0.3.5 Requires: libvirt-qpid >= 0.2.14-3 Requires: hal @@ -31,6 +31,7 @@ Requires: cyrus-sasl-gssapi cyrus-sasl cyrus-sasl-lib Requires: iscsi-initiator-utils Requires: ntp Requires: nfs-utils +Requires: glusterfs-client >= 2.0.1 Requires: krb5-workstation Requires: bash Requires: chkconfig -- 1.6.0.6 From dpierce at redhat.com Tue Jul 28 17:01:33 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 28 Jul 2009 13:01:33 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Creates an iscsi initiator file for the node on install. In-Reply-To: <1248467258-3608-2-git-send-email-dpierce@redhat.com> References: <1248467258-3608-1-git-send-email-dpierce@redhat.com> <1248467258-3608-2-git-send-email-dpierce@redhat.com> Message-ID: <20090728170133.GC4178@mcpierce-laptop.rdu.redhat.com> On Fri, Jul 24, 2009 at 04:27:38PM -0400, Darryl L. Pierce wrote: > After the node finishes installing the image to local storage, the > /etc/iscsi/initiatorname.iscsi file is generated. It is then persisted. > > Resolves: rhbz#513623 > > Signed-off-by: Darryl L. Pierce > --- Can I get some feedback or an ACK on this, please? -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From mmorsi at redhat.com Tue Jul 28 19:02:08 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 28 Jul 2009 15:02:08 -0400 Subject: [Ovirt-devel] [PATCH server] Added ovirt-host-register to ovirt_ctl convenience script. In-Reply-To: <1248723419-3588-1-git-send-email-arroy@redhat.com> References: <1248723419-3588-1-git-send-email-arroy@redhat.com> Message-ID: <4A6F4B30.4090804@redhat.com> Arjun Roy wrote: > --- > scripts/ovirt_ctl | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/scripts/ovirt_ctl b/scripts/ovirt_ctl > index a42d997..22d66fe 100755 > --- a/scripts/ovirt_ctl > +++ b/scripts/ovirt_ctl > @@ -6,7 +6,7 @@ > SERVICE_CMD=/sbin/service > CHKCONFIG_CMD=/sbin/chkconfig > > -SERVICES=( ovirt-db-omatic ovirt-host-browser \ > +SERVICES=( ovirt-db-omatic ovirt-host-browser ovirt-host-register \ > ovirt-host-collect ovirt-mongrel-rails \ > ovirt-taskomatic ovirt-vnc-proxy ovirt-agent ) > > ACK'd and pushed -Mo From mburns at redhat.com Tue Jul 28 19:41:51 2009 From: mburns at redhat.com (Mike Burns) Date: Tue, 28 Jul 2009 15:41:51 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Creates an iscsi initiator file for the node on install. In-Reply-To: <20090728170133.GC4178@mcpierce-laptop.rdu.redhat.com> References: <1248467258-3608-1-git-send-email-dpierce@redhat.com> <1248467258-3608-2-git-send-email-dpierce@redhat.com> <20090728170133.GC4178@mcpierce-laptop.rdu.redhat.com> Message-ID: <20090728194151.GA3501@mburns-laptop.bos.redhat.com> On Tue, Jul 28, 2009 at 01:01:33PM -0400, Darryl L. Pierce wrote: > On Fri, Jul 24, 2009 at 04:27:38PM -0400, Darryl L. Pierce wrote: > > After the node finishes installing the image to local storage, the > > /etc/iscsi/initiatorname.iscsi file is generated. It is then persisted. > > > > Resolves: rhbz#513623 > > > > Signed-off-by: Darryl L. Pierce > > --- > > Can I get some feedback or an ACK on this, please? > > -- > Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. > Virtual Machine Management - http://www.ovirt.org/ > Is fearr Gaeilge bhriste n? B?arla cliste. > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel With patch applied, tested that I could successfully mount iscsi target on node. Seems to work fine. ACK From dpierce at redhat.com Tue Jul 28 19:46:33 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 28 Jul 2009 15:46:33 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Creates an iscsi initiator file for the node on install. In-Reply-To: <20090728194151.GA3501@mburns-laptop.bos.redhat.com> References: <1248467258-3608-1-git-send-email-dpierce@redhat.com> <1248467258-3608-2-git-send-email-dpierce@redhat.com> <20090728170133.GC4178@mcpierce-laptop.rdu.redhat.com> <20090728194151.GA3501@mburns-laptop.bos.redhat.com> Message-ID: <20090728194633.GF4178@mcpierce-laptop.rdu.redhat.com> On Tue, Jul 28, 2009 at 03:41:51PM -0400, Mike Burns wrote: > With patch applied, tested that I could successfully mount iscsi target on node. Seems to work fine. > > ACK Thanks. This is now pushed upstream. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From jboggs at redhat.com Tue Jul 28 20:27:51 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 28 Jul 2009 16:27:51 -0400 Subject: [Ovirt-devel] [PATCH] update dhclient.conf location for fedora 11 Message-ID: <1248812871-10901-1-git-send-email-jboggs@redhat.com> --- installer/modules/ovirt/manifests/dns.pp | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diff --git a/installer/modules/ovirt/manifests/dns.pp b/installer/modules/ovirt/manifests/dns.pp index 7709cbf..7c867cc 100644 --- a/installer/modules/ovirt/manifests/dns.pp +++ b/installer/modules/ovirt/manifests/dns.pp @@ -55,14 +55,14 @@ define dns::common($guest_ipaddr="", $admin_ipaddr="",$guest_dev="",$admin_dev=" notify => Service[dnsmasq] } - file {"/etc/dhclient.conf": + file {"/etc/dhcp/dhclient.conf": ensure => present } file_append {"dhclient_config": - file => "/etc/dhclient.conf", + file => "/etc/dhcp/dhclient.conf", line => "prepend domain-name-servers $admin_ipaddr;", - require => [Single_exec["set_hostname"], Package["dnsmasq"], File["/etc/dhclient.conf"]] , + require => [Single_exec["set_hostname"], Package["dnsmasq"], File["/etc/dhcp/dhclient.conf"]] , notify => Service[dnsmasq], } -- 1.6.2.5 From dhuff at redhat.com Wed Jul 29 00:14:47 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 20:14:47 -0400 Subject: [Ovirt-devel] ovirt-node-image patchset Fedora Message-ID: <1248826492-13035-1-git-send-email-dhuff@redhat.com> modified ovirt-node-image for inclusion in Fedora Removes ovirt-node-image-pxe sub package adds get-ovirt-node-image script adds generate-ovirt-node-pxe-tree script (replaces subpackage) moved all the scripts/tools into tools subdir adds new rpm, ovirt-node-recipe includes tools, manifests, and ks file/recipe Process for building upstream ovirt node packages.... 1. Build ovir-node.rpm in koji (everything in fedora) 2. Build ovirt-node-image (manual step done by ovirt project) 1. checkout git repo run make recipe 2. output = ks file, manifest.tar, ovirt-node.iso (unofficial) 3. output checking in to fedora cvs as source for (3) 3. Build ovirt-node-recipe in koji (requires extra source from step 2, mainfest.tar [includes recipe]) 1. does not include iso image, spin hosted on spin.fedoraproject.orgor on ovirt.org 2. recipe/ks and manifest.tar built at image creation time (2) 3. includes tools/docs for downloaing ovirt-node-image 4. Fedora project builds official spin form ks in ovirt-node-recipe 4.1 (congentincy plan iso image hosted on ovirt.org From dhuff at redhat.com Wed Jul 29 00:14:48 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 20:14:48 -0400 Subject: [Ovirt-devel] [PATCH] Move tools to tools dir In-Reply-To: <1248826492-13035-1-git-send-email-dhuff@redhat.com> References: <1248826492-13035-1-git-send-email-dhuff@redhat.com> Message-ID: <1248826492-13035-2-git-send-email-dhuff@redhat.com> --- ovirt-node-image.spec.in | 8 ++++---- .../create-ovirt-iso-nodes | 0 edit-livecd => tools/edit-livecd | 0 livecd-rpms => tools/livecd-rpms | 0 livecd-setauth => tools/livecd-setauth | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename create-ovirt-iso-nodes => tools/create-ovirt-iso-nodes (100%) rename edit-livecd => tools/edit-livecd (100%) rename livecd-rpms => tools/livecd-rpms (100%) rename livecd-setauth => tools/livecd-setauth (100%) diff --git a/ovirt-node-image.spec.in b/ovirt-node-image.spec.in index e64f4de..1c89246 100644 --- a/ovirt-node-image.spec.in +++ b/ovirt-node-image.spec.in @@ -63,10 +63,10 @@ mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{app_root} %{__install} -p -m0644 %{image_iso} %{buildroot}%{app_root} %{__install} -d -m0755 %{buildroot}%{_sbindir} -%{__install} -p -m0755 create-ovirt-iso-nodes %{buildroot}%{_sbindir} -%{__install} -p -m0755 edit-livecd %{buildroot}%{_sbindir} -%{__install} -p -m0755 livecd-setauth %{buildroot}%{_sbindir} -%{__install} -p -m0755 livecd-rpms %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/create-ovirt-iso-nodes %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/edit-livecd %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/livecd-setauth %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/livecd-rpms %{buildroot}%{_sbindir} %{__tar} -xf %{image_manifests} -C %{buildroot}%{app_root} %clean diff --git a/create-ovirt-iso-nodes b/tools/create-ovirt-iso-nodes similarity index 100% rename from create-ovirt-iso-nodes rename to tools/create-ovirt-iso-nodes diff --git a/edit-livecd b/tools/edit-livecd similarity index 100% rename from edit-livecd rename to tools/edit-livecd diff --git a/livecd-rpms b/tools/livecd-rpms similarity index 100% rename from livecd-rpms rename to tools/livecd-rpms diff --git a/livecd-setauth b/tools/livecd-setauth similarity index 100% rename from livecd-setauth rename to tools/livecd-setauth -- 1.6.0.6 From dhuff at redhat.com Wed Jul 29 00:14:49 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 20:14:49 -0400 Subject: [Ovirt-devel] [PATCH] added tools/get-ovirt-node-image tools/generate-ovirt-node-pxe-tree In-Reply-To: <1248826492-13035-1-git-send-email-dhuff@redhat.com> References: <1248826492-13035-1-git-send-email-dhuff@redhat.com> Message-ID: <1248826492-13035-3-git-send-email-dhuff@redhat.com> --- Makefile.am | 12 ++++++--- tools/generate-ovirt-node-pxe-tree | 44 ++++++++++++++++++++++++++++++++++++ tools/get-ovirt-node-image | 28 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100755 tools/generate-ovirt-node-pxe-tree create mode 100755 tools/get-ovirt-node-image diff --git a/Makefile.am b/Makefile.am index a44ae49..2be9146 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,15 +38,19 @@ EXTRA_DIST = \ .gitignore \ $(PACKAGE).spec \ $(PACKAGE).spec.in \ + ovirt-node-recipe.spec \ + ovirt-node-recipe.spec.in \ common-blacklist.ks \ common-install.ks \ common-pkgs.ks \ common-post.ks \ $(PACKAGE).ks \ - create-ovirt-iso-nodes \ - edit-livecd \ - livecd-setauth \ - livecd-rpms \ + tools/create-ovirt-iso-nodes \ + tools/edit-livecd \ + tools/livecd-setauth \ + tools/livecd-rpms \ + tools/generate-ovirt-node-pxe-tree \ + tools/get-ovirt-node-image \ README DISTCLEANFILES = $(PACKAGE)-$(VERSION).tar.gz \ diff --git a/tools/generate-ovirt-node-pxe-tree b/tools/generate-ovirt-node-pxe-tree new file mode 100755 index 0000000..f4d69ab --- /dev/null +++ b/tools/generate-ovirt-node-pxe-tree @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Script to generate a pxe tree form a node image +# Copyright 2009 Red Hat, Inc. +# Written by David Huff +# +# 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 Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +IMAGE_NAME="ovirt-node-image.iso" +OVIRT_NODE_DIR="/usr/share/ovirt-node-image/" + +if [ $(id -u) != 0 ]; then + echo "You need to be root to run this script." + exit 1 +fi + +if [ ! -f ${OVIRT_NODE_DIR}${IMAGE_NAME} ]; then + echo "Warning: ${IMAGE_NAME} not found." + echo "you can download it from spins.fedoraproject.org" + echo "by running: get-ovirt-node-image" + exit 1 +fi + +cat < /dev/null 2>&1 || : \ No newline at end of file diff --git a/tools/get-ovirt-node-image b/tools/get-ovirt-node-image new file mode 100755 index 0000000..1a3ee94 --- /dev/null +++ b/tools/get-ovirt-node-image @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Script to get ovirt-node-image form spins.fedoraproject.org +# Copyright 2009 Red Hat, Inc. +# Written by David Huff +# +# 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 Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +OVIRT_NODE_DIR="/usr/share/ovirt-node-image/" +OVIRT_NODE_URL="http://spins.fedoraproject.org/torrents/Fedora-10-x86_64-AOS.torrent" + +if [ $(id -u) != 0 ]; then + echo "You need to be root to run this script." + exit 1 +fi + +bittorrent-console --no_spew --max_uploads 0 --save_in $OVIRT_NODE_DIR $OVIRT_NODE_URL \ No newline at end of file -- 1.6.0.6 From dhuff at redhat.com Wed Jul 29 00:14:50 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 20:14:50 -0400 Subject: [Ovirt-devel] [PATCH] added ovirt-node-recipe.spec.in In-Reply-To: <1248826492-13035-1-git-send-email-dhuff@redhat.com> References: <1248826492-13035-1-git-send-email-dhuff@redhat.com> Message-ID: <1248826492-13035-4-git-send-email-dhuff@redhat.com> Modified configure --- configure.ac | 2 +- ovirt-node-recipe.spec.in | 82 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletions(-) create mode 100644 ovirt-node-recipe.spec.in diff --git a/configure.ac b/configure.ac index af2cccd..d7e8fac 100644 --- a/configure.ac +++ b/configure.ac @@ -7,5 +7,5 @@ AC_CONFIG_HEADERS([config.h]) test x"$ac_ct_CC:$CFLAGS" = 'xgcc:-g -O2' \ && CFLAGS="$CFLAGS -Wshadow -Wall -Werror" -AC_CONFIG_FILES([Makefile ovirt-node-image.spec]) +AC_CONFIG_FILES([Makefile ovirt-node-image.spec ovirt-node-recipe.spec]) AC_OUTPUT diff --git a/ovirt-node-recipe.spec.in b/ovirt-node-recipe.spec.in new file mode 100644 index 0000000..113939b --- /dev/null +++ b/ovirt-node-recipe.spec.in @@ -0,0 +1,82 @@ +Summary: oVirt Node image recipe +Name: ovirt-node-recipe +Version: @VERSION@ +Release: 0%{?dist}%{?extra_release} +Source0: %{name}-%{version}.tar.gz +%define image ovirt-node-image +%define image_manifests %{image}-manifests.tar +License: GPLv2+ +Group: Applications/System +buildroot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +URL: http://ovirt.org/ + +Requires: livecd-tools >= 020-2 +Requires: bittorrent + +BuildArch: noarch +%define app_root %{_datadir}/%{name} + +# disable debuginfo, makes no sense for boot image and it is created empty anyway +%define debug_package %{nil} + +%description +The oVirt-node-recipe provides ks file, manifest files, client tools, and +documentation for building and running an oVirt Node image. This package +is not to be installed on the oVirt-Node, however on a development machine +to help in deployment on the node. + +%prep +%setup -c -q + +%build + +%install +%{__rm} -rf %{buildroot} +mkdir %{buildroot} + +%{__install} -d -m0755 %{buildroot}%{app_root} +%{__install} -p -m0644 %{name}.ks %{buildroot}%{app_root} +%{__install} -d -m0755 %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/create-ovirt-iso-nodes %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/edit-livecd %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/livecd-setauth %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/livecd-rpms %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/get-ovirt-node-image %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/generate-ovirt-node-pxe-tree %{buildroot}%{_sbindir} +%{__tar} -xf %{image_manifests} -C %{buildroot}%{app_root} + +%clean +%{__rm} -rf %{buildroot} + +%files +%defattr(0644,root,root,0755) +%{app_root}/%{name}.ks +%doc %{app_root}/manifests/rpm-manifest.txt +%doc %{app_root}/manifests/srpm-manifest.txt +%doc %{app_root}/manifests/file-manifest.txt +%doc %{app_root}/manifests/dir-manifest.txt +%doc %{app_root}/manifests/rpm-manifest-post.txt +%doc %{app_root}/manifests/srpm-manifest-post.txt +%doc %{app_root}/manifests/file-manifest-post.txt +%doc %{app_root}/manifests/dir-manifest-post.txt +%doc %{app_root}/manifests/ovirt-release + +%defattr(0755,root,root,0755) +%{_sbindir}/create-ovirt-iso-nodes +%{_sbindir}/edit-livecd +%{_sbindir}/livecd-setauth +%{_sbindir}/livecd-rpms +%{_sbindir}/get-ovirt-node-image +%{_sbindir}/generate-ovirt-node-pxe-tree + +%changelog +* Thu Jul 16 2009 David Huff 0.93-0 +- Package for F-12 + +* Thu Jul 03 2008 Perry Myers 0.92-0 +- Only store ISO in SRPM, and generate PXE from that during build + +* Tue Jun 03 2008 Alan Pevec 0.0.5-1 +- Initial build. + + -- 1.6.0.6 From dhuff at redhat.com Wed Jul 29 00:14:51 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 20:14:51 -0400 Subject: [Ovirt-devel] [PATCH] added make recipe target In-Reply-To: <1248826492-13035-1-git-send-email-dhuff@redhat.com> References: <1248826492-13035-1-git-send-email-dhuff@redhat.com> Message-ID: <1248826492-13035-5-git-send-email-dhuff@redhat.com> --- Makefile.am | 34 +++++++++++++++++++++++++++++++--- 1 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 2be9146..5cefa83 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,14 +32,16 @@ ARCH = $(shell rpm --eval '%{_arch}') CUR_PREVIEW = 11 PREVIEW_URL ?= http://markmc.fedorapeople.org/virt-preview/f$(CUR_PREVIEW)/$(ARCH) +RECIPE = ovirt-node-recipe NVR = $(PACKAGE)-$(VERSION)-$(ARCH) +RECIPE_PACKAGE = $(RECIPE)-$(VERSION) EXTRA_DIST = \ .gitignore \ $(PACKAGE).spec \ $(PACKAGE).spec.in \ - ovirt-node-recipe.spec \ - ovirt-node-recipe.spec.in \ + $(RECIPE).spec \ + $(RECIPE).spec.in \ common-blacklist.ks \ common-install.ks \ common-pkgs.ks \ @@ -186,14 +188,40 @@ source: src.ks > $(PACKAGE)-source-$(VERSION).$(SRC_FMT).$(SUM) rpms: dist node +# fixme hack two specs + ( \ + gunzip $(distdir).tar.gz; \ + tar --delete $(PACKAGE)-$(VERSION)/$(RECIPE).spec -f $(distdir).tar; \ + gzip $(distdir).tar; \ + ) +# fixme hack two specs rpmbuild $(RPM_FLAGS) -ts $(distdir).tar.gz rpmbuild $(RPM_FLAGS) --define "source_iso 1" -tb $(distdir).tar.gz + +recipe: dist node + + ksflatten $(PACKAGE).ks --output $(RECIPE).ks + tar -zcvf $(RECIPE_PACKAGE).tar.gz tools $(PACKAGE)-manifests.tar $(RECIPE).ks $(RECIPE).spec + rpmbuild $(RPM_FLAGS) -ta $(RECIPE_PACKAGE).tar.gz srpms: dist - rpmbuild $(RPM_FLAGS) -ts $(distdir).tar.gz +# fixme hack two specs + ( \ + gunzip $(distdir).tar.gz; \ + tar --delete $(PACKAGE)-$(VERSION)/$(RECIPE).spec -f $(distdir).tar; \ + gzip $(distdir).tar; \ + ) +# fixme hack two specs rpmbuild $(RPM_FLAGS) -ts $(distdir).tar.gz $(MAKE) _publish iso_srpms: dist +# fixme hack two specs + ( \ + gunzip $(distdir).tar.gz; \ + tar --delete $(PACKAGE)-$(VERSION)/$(RECIPE).spec -f $(distdir).tar; \ + gzip $(distdir).tar; \ + ) +# fixme hack two specs rpmbuild $(RPM_FLAGS) --define "source_iso 1" -ts $(distdir).tar.gz $(MAKE) _publish -- 1.6.0.6 From dhuff at redhat.com Wed Jul 29 00:14:52 2009 From: dhuff at redhat.com (David Huff) Date: Tue, 28 Jul 2009 20:14:52 -0400 Subject: [Ovirt-devel] [PATCH] Edited ovirt-node-image.ks to write redhat-release and splash images In-Reply-To: <1248826492-13035-1-git-send-email-dhuff@redhat.com> References: <1248826492-13035-1-git-send-email-dhuff@redhat.com> Message-ID: <1248826492-13035-6-git-send-email-dhuff@redhat.com> --- ovirt-node-image.ks | 16 ++++++++++++++++ 1 files changed, 16 insertions(+), 0 deletions(-) diff --git a/ovirt-node-image.ks b/ovirt-node-image.ks index 802b6a5..dcf2465 100644 --- a/ovirt-node-image.ks +++ b/ovirt-node-image.ks @@ -85,6 +85,22 @@ mv $LIVE_ROOT/isolinux/isolinux.cfg.standalone $LIVE_ROOT/isolinux/isolinux.cfg %end %post +# release files +ln -snf /etc/ovirt-release /etc/redhat-release +ln -snf /etc/ovirt-release /etc/system-release +cp /etc/ovirt-release /etc/issue +echo "Kernel \r on an \m (\l)" >> /etc/issue +cp /etc/issue /etc/issue.net + +# logos +# should be ifarch i386 +/boot/grub/splash.xpm.gz +# end i386 +mkdir -p /usr/lib/anaconda-runtime +cp /usr/share/ovirt-node/images/syslinux-vesa-splash.jpg /usr/lib/anaconda-runtime +%end + +%post # Create post-image processing manifests manifests=/tmp/manifests mkdir -p $manifests -- 1.6.0.6 From justin at redfish-group.com Wed Jul 29 06:50:18 2009 From: justin at redfish-group.com (Justin Clacherty) Date: Wed, 29 Jul 2009 16:50:18 +1000 Subject: [Ovirt-devel] yum update broke ovirt Message-ID: <4A6FF12A.8010208@redfish-group.com> I did a yum update on my management server and now the ovirt gui is no longer accessable. Is it supposed to just work or do I need to do something after the yum update? Justin. From justin at redfish-group.com Wed Jul 29 08:23:45 2009 From: justin at redfish-group.com (Justin Clacherty) Date: Wed, 29 Jul 2009 18:23:45 +1000 Subject: [Ovirt-devel] yum update broke ovirt In-Reply-To: <4A6FF12A.8010208@redfish-group.com> References: <4A6FF12A.8010208@redfish-group.com> Message-ID: <4A700711.3020508@redfish-group.com> Justin Clacherty wrote: > I did a yum update on my management server and now the ovirt gui is no > longer accessable. Is it supposed to just work or do I need to do > something after the yum update? It looks as though /etc/httpd/conf.d/ovirt-server.conf references the ip address not the domain name for the virtual server. I'm not sure if anything else is tied to the ip address so for now I've just changed the IP address back to what it was (it was dynamic and I was trying to make it static). Is anything else tied to the IP address? If so, perhaps it should be changed or there should be a way to tell ovirt-server et. al. that the ip address has changed. The problem I have now is that the node doesn't become available for some reason. It boots and get's the node image but "ruby /usr/share/ovirt-server/qmf-libvirt-example.rb" doesn't show anything. The host-browser log shows the node though... Any ideas on where I look next? Justin. Received info='CPU' Begin receiving CPU details Jul 29 18:07:25 192.168.50.91 ::Received - CPUNUM:3 Jul 29 18:07:25 192.168.50.91 ::Received - CORENUM:3 Jul 29 18:07:25 192.168.50.91 ::Received - NUMCORES:4 Jul 29 18:07:25 192.168.50.91 ::Received - VENDOR:GenuineIntel Jul 29 18:07:25 192.168.50.91 ::Received - MODEL:23 Jul 29 18:07:25 192.168.50.91 ::Received - FAMILY:6 Jul 29 18:07:25 192.168.50.91 ::Received - CPUIDLVL:10 Jul 29 18:07:25 192.168.50.91 ::Received - SPEED:2000.000 Jul 29 18:07:25 192.168.50.91 ::Received - CACHE:6144 KB Jul 29 18:07:25 192.168.50.91 ::Received - FLAGS:fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good nopl pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 lah Received info='NIC' Begin receiving NIC details Jul 29 18:07:25 192.168.50.91 ::Received - MAC:00:15:17:8d:01:1a Jul 29 18:07:25 192.168.50.91 ::Received - BANDWIDTH:1000 Jul 29 18:07:25 192.168.50.91 ::Received - IFACE_NAME:eth0 Received info='ENDINFO' Searching for existing host record... Deleting any existing CPUs Saving new CPU records Updating NIC records for the node Searching for existing record for: 00:15:17:8D:01:1A Updating details for: eth0 [00:15:17:8D:01:1A]} Jul 29 18:07:26 192.168.50.91 Ending conversation Disconnected from node91.ovirt.priv From mloiseleur at linagora.com Wed Jul 29 14:23:15 2009 From: mloiseleur at linagora.com (Michel Loiseleur) Date: Wed, 29 Jul 2009 16:23:15 +0200 Subject: [Ovirt-devel] patch on vm nics Message-ID: <4A705B53.6090101@linagora.com> Hi everyone, Here is a small patch allowing to set netword cards with the ajaxified edit vm form. It's not complete : it does not tolerate empty field. This patch can be applied on this morning git version. Regards, -- Loiseleur Michel Responsable Technique OSSA Linagora / 27, rue de Berri / 75008 PARIS Tel/Fax : 01 58 18 68 28 / 01 58 18 68 29 http://job.linagora.com/ | http://www.tosca-project.net "Ce n'est pas le logiciel qui est libre, c'est vous" -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-vm_nics.patch Type: text/x-diff Size: 2455 bytes Desc: not available URL: From mmorsi at redhat.com Wed Jul 29 15:19:59 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 29 Jul 2009 11:19:59 -0400 Subject: [Ovirt-devel] patch on vm nics In-Reply-To: <4A705B53.6090101@linagora.com> References: <4A705B53.6090101@linagora.com> Message-ID: <4A70689F.3080109@redhat.com> Michel Loiseleur wrote: > Hi everyone, > > Here is a small patch allowing to set netword cards with the ajaxified > edit vm form. It's not complete : it does not tolerate empty field. > > This patch can be applied on this morning git version. > > Regards, > > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel Hey Michel, Thanks for submitting this. To be honest though I'm in the process of completely rewriting that component. We pushed it the other day as is in its ugly form to meet the Fedora deadline, but now that we have some more time, I am working on cleaning up the javascript for the frontend, namely so that the static html contents isn't in the javascript code. Just looking at it, your patch looks like it fixes the issue where the nics / networks are not saved on form submission, and if I end up using your fixes, I'll credit you in the commit log. Once again, thanks for the submission. -Mo From imain at redhat.com Wed Jul 29 16:12:51 2009 From: imain at redhat.com (Ian Main) Date: Wed, 29 Jul 2009 12:12:51 -0400 Subject: [Ovirt-devel] [PATCH server] Remove ununsed utils.rb file. Message-ID: <1248883971-9918-1-git-send-email-imain@redhat.com> Somehow I missed this.. this file is no longer in use by taskomatic. Signed-off-by: Ian Main --- src/task-omatic/utils.rb | 221 ---------------------------------------------- 1 files changed, 0 insertions(+), 221 deletions(-) delete mode 100644 src/task-omatic/utils.rb diff --git a/src/task-omatic/utils.rb b/src/task-omatic/utils.rb deleted file mode 100644 index e3005ed..0000000 --- a/src/task-omatic/utils.rb +++ /dev/null @@ -1,221 +0,0 @@ -require 'rexml/document' -include REXML - -def String.random_alphanumeric(size=16) - s = "" - size.times { s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr } - s -end - -def all_storage_pools(conn) - all_pools = conn.list_defined_storage_pools - all_pools.concat(conn.list_storage_pools) - return all_pools -end - -def get_libvirt_lvm_pool_from_volume(db_volume) - phys_volume = StorageVolume.find(:first, :conditions => - [ "lvm_pool_id = ?", db_volume.storage_pool_id]) - - return LibvirtPool.factory(phys_volume.storage_pool) -end - -class LibvirtPool - def initialize(type, name = nil) - @remote_pool = nil - @build_on_start = true - @remote_pool_defined = false - @remote_pool_started = false - - if name == nil - @name = type + "-" + String.random_alphanumeric - else - @name = name - end - - @xml = Document.new - @xml.add_element("pool", {"type" => type}) - - @xml.root.add_element("name").add_text(@name) - - @xml.root.add_element("source") - - @xml.root.add_element("target") - @xml.root.elements["target"].add_element("path") - end - - def connect(conn) - all_storage_pools(conn).each do |remote_pool_name| - tmppool = conn.lookup_storage_pool_by_name(remote_pool_name) - - if self.xmlequal?(Document.new(tmppool.xml_desc).root) - @remote_pool = tmppool - break - end - end - - if @remote_pool == nil - @remote_pool = conn.define_storage_pool_xml(@xml.to_s) - # we need this because we don't necessarily want to "build" LVM pools, - # which might destroy existing data - if @build_on_start - @remote_pool.build - end - @remote_pool_defined = true - end - - if @remote_pool.info.state == Libvirt::StoragePool::INACTIVE - # only try to start the pool if it is currently inactive; in all other - # states, assume it is already running - @remote_pool.create - @remote_pool_started = true - end - end - - def list_volumes - return @remote_pool.list_volumes - end - - def lookup_vol_by_path(dev) - return @remote_pool.lookup_volume_by_path(dev) - end - - def lookup_vol_by_name(name) - return @remote_pool.lookup_volume_by_name(name) - end - - def create_vol(type, name, size, owner, group, mode) - @vol_xml = Document.new - @vol_xml.add_element("volume", {"type" => type}) - @vol_xml.root.add_element("name").add_text(name) - @vol_xml.root.add_element("capacity", {"unit" => "K"}).add_text(size.to_s) - @vol_xml.root.add_element("target") - @vol_xml.root.elements["target"].add_element("permissions") - @vol_xml.root.elements["target"].elements["permissions"].add_element("owner").add_text(owner) - @vol_xml.root.elements["target"].elements["permissions"].add_element("group").add_text(group) - @vol_xml.root.elements["target"].elements["permissions"].add_element("mode").add_text(mode) - end - - def shutdown - if @remote_pool_started - @remote_pool.destroy - end - if @remote_pool_defined - @remote_pool.undefine - end - end - - def xmlequal?(docroot) - return false - end - - def self.factory(pool) - if pool[:type] == "IscsiStoragePool" - return IscsiLibvirtPool.new(pool.ip_addr, pool[:target]) - elsif pool[:type] == "NfsStoragePool" - return NFSLibvirtPool.new(pool.ip_addr, pool.export_path) - elsif pool[:type] == "LvmStoragePool" - # OK, if this is LVM storage, there are two cases we need to care about: - # 1) this is a LUN with LVM already on it. In this case, all we need to - # do is to create a new LV (== libvirt volume), and be done with it - # 2) this LUN is blank, so there is no LVM on it already. In this - # case, we need to pvcreate, vgcreate first (== libvirt pool build), - # and *then* create the new LV (== libvirt volume) on top of that. - # - # We can tell the difference between an LVM Pool that exists and one - # that needs to be created based on the value of the pool.state; - # if it is PENDING_SETUP, we need to create it first - phys_volume = StorageVolume.find(:first, :conditions => - [ "lvm_pool_id = ?", pool.id]) - - return LVMLibvirtPool.new(pool.vg_name, phys_volume.path, - pool.state == StoragePool::STATE_PENDING_SETUP) - else - raise "Unknown storage pool type " + pool[:type].to_s - end - end -end - -class IscsiLibvirtPool < LibvirtPool - def initialize(ip_addr, target) - super('iscsi') - - @type = 'iscsi' - @ipaddr = ip_addr - @target = target - - @xml.root.elements["source"].add_element("host", {"name" => @ipaddr}) - @xml.root.elements["source"].add_element("device", {"path" => @target}) - - @xml.root.elements["target"].elements["path"].text = "/dev/disk/by-id" - end - - def xmlequal?(docroot) - return (docroot.attributes['type'] == @type and - docroot.elements['source'].elements['host'].attributes['name'] == @ipaddr and - docroot.elements['source'].elements['device'].attributes['path'] == @target) - end -end - -class NFSLibvirtPool < LibvirtPool - def initialize(ip_addr, export_path) - super('netfs') - - @type = 'netfs' - @host = ip_addr - @remote_path = export_path - @name = String.random_alphanumeric - - @xml.root.elements["source"].add_element("host", {"name" => @host}) - @xml.root.elements["source"].add_element("dir", {"path" => @remote_path}) - @xml.root.elements["source"].add_element("format", {"type" => "nfs"}) - - @xml.root.elements["target"].elements["path"].text = "/mnt/" + @name - end - - def create_vol(name, size, owner, group, mode) - # FIXME: this can actually take some time to complete (since we aren't - # doing sparse allocations at the moment). During that time, whichever - # libvirtd we chose to use is completely hung up. The solution is 3-fold: - # 1. Allow sparse allocations in the WUI front-end - # 2. Make libvirtd multi-threaded - # 3. Make taskomatic multi-threaded - super("netfs", name, size, owner, group, mode) - - # FIXME: we have to add the format as raw here because of a bug in libvirt; - # if you specify a volume with no format, it will crash libvirtd - @vol_xml.root.elements["target"].add_element("format", {"type" => "raw"}) - @remote_pool.create_vol_xml(@vol_xml.to_s) - end - - def xmlequal?(docroot) - return (docroot.attributes['type'] == @type and - docroot.elements['source'].elements['host'].attributes['name'] == @host and - docroot.elements['source'].elements['dir'].attributes['path'] == @remote_path) - end -end - -class LVMLibvirtPool < LibvirtPool - def initialize(vg_name, device, build_on_start) - super('logical', vg_name) - - @type = 'logical' - @build_on_start = build_on_start - - @xml.root.elements["source"].add_element("name").add_text(@name) - @xml.root.elements["source"].add_element("device", {"path" => device}) - - @xml.root.elements["target"].elements["path"].text = "/dev/" + @name - end - - def create_vol(name, size, owner, group, mode) - super("logical", name, size, owner, group, mode) - @remote_pool.create_vol_xml(@vol_xml.to_s) - end - - def xmlequal?(docroot) - return (docroot.attributes['type'] == @type and - docroot.elements['name'].text == @name and - docroot.elements['source'].elements['name'] == @name) - end -end -- 1.6.2.5 From jguiditt at redhat.com Wed Jul 29 19:04:29 2009 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 29 Jul 2009 15:04:29 -0400 Subject: [Ovirt-devel] [PATCH] update dhclient.conf location for fedora 11 In-Reply-To: <1248812871-10901-1-git-send-email-jboggs@redhat.com> References: <1248812871-10901-1-git-send-email-jboggs@redhat.com> Message-ID: <1248894270.3369.1441.camel@lenovo> On Tue, 2009-07-28 at 16:27 -0400, Joey Boggs wrote: > --- > installer/modules/ovirt/manifests/dns.pp | 6 +++--- > 1 files changed, 3 insertions(+), 3 deletions(-) > > diff --git a/installer/modules/ovirt/manifests/dns.pp b/installer/modules/ovirt/manifests/dns.pp > index 7709cbf..7c867cc 100644 > --- a/installer/modules/ovirt/manifests/dns.pp > +++ b/installer/modules/ovirt/manifests/dns.pp > @@ -55,14 +55,14 @@ define dns::common($guest_ipaddr="", $admin_ipaddr="",$guest_dev="",$admin_dev=" > notify => Service[dnsmasq] > } > > - file {"/etc/dhclient.conf": > + file {"/etc/dhcp/dhclient.conf": > ensure => present > } > > file_append {"dhclient_config": > - file => "/etc/dhclient.conf", > + file => "/etc/dhcp/dhclient.conf", > line => "prepend domain-name-servers $admin_ipaddr;", > - require => [Single_exec["set_hostname"], Package["dnsmasq"], File["/etc/dhclient.conf"]] , > + require => [Single_exec["set_hostname"], Package["dnsmasq"], File["/etc/dhcp/dhclient.conf"]] , > notify => Service[dnsmasq], > } > ACK, moving this file fixed the issue I was seeing where resolv.conf on the wui server was not including the admin network. From jboggs at redhat.com Wed Jul 29 19:49:04 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 29 Jul 2009 15:49:04 -0400 Subject: [Ovirt-devel] [PATCH] update dhclient.conf location for fedora 11 In-Reply-To: <1248894270.3369.1441.camel@lenovo> References: <1248812871-10901-1-git-send-email-jboggs@redhat.com> <1248894270.3369.1441.camel@lenovo> Message-ID: <4A70A7B0.8050108@redhat.com> Jason Guiditta wrote: > On Tue, 2009-07-28 at 16:27 -0400, Joey Boggs wrote: > >> --- >> installer/modules/ovirt/manifests/dns.pp | 6 +++--- >> 1 files changed, 3 insertions(+), 3 deletions(-) >> >> diff --git a/installer/modules/ovirt/manifests/dns.pp b/installer/modules/ovirt/manifests/dns.pp >> index 7709cbf..7c867cc 100644 >> --- a/installer/modules/ovirt/manifests/dns.pp >> +++ b/installer/modules/ovirt/manifests/dns.pp >> @@ -55,14 +55,14 @@ define dns::common($guest_ipaddr="", $admin_ipaddr="",$guest_dev="",$admin_dev=" >> notify => Service[dnsmasq] >> } >> >> - file {"/etc/dhclient.conf": >> + file {"/etc/dhcp/dhclient.conf": >> ensure => present >> } >> >> file_append {"dhclient_config": >> - file => "/etc/dhclient.conf", >> + file => "/etc/dhcp/dhclient.conf", >> line => "prepend domain-name-servers $admin_ipaddr;", >> - require => [Single_exec["set_hostname"], Package["dnsmasq"], File["/etc/dhclient.conf"]] , >> + require => [Single_exec["set_hostname"], Package["dnsmasq"], File["/etc/dhcp/dhclient.conf"]] , >> notify => Service[dnsmasq], >> } >> >> > ACK, moving this file fixed the issue I was seeing where resolv.conf on > the wui server was not including the admin network. > > pushed From jason.guiditta at gmail.com Wed Jul 29 20:09:28 2009 From: jason.guiditta at gmail.com (Jason Guiditta) Date: Wed, 29 Jul 2009 16:09:28 -0400 Subject: [Ovirt-devel] GlusterFS patches for ovirt-server, ovirt-node, ovirt-node-image In-Reply-To: <8a80e9760907280649t76277c00va56c859a9cf9ba47@mail.gmail.com> References: <8a80e9760907280649t76277c00va56c859a9cf9ba47@mail.gmail.com> Message-ID: Harshavardhana, I successfully applied this series and rebuilt my server to test. However, I have been unable to create a glusterfs pool yet. I have installed and configured a gluster storage server that is available on the ovirt admin network. Form the server log, it appear ok, but node cannot mount it. I have pastebin'ed the section of taskomatic log with the error and glusterfs.log from storage server. Please let me know if there are any config bits that I should check which may be causing this. http://ovirt.pastebin.com/m67e75c21 Thanks, -j On Tue, Jul 28, 2009 at 9:49 AM, Harshavardhana wrote: > Resending patches again after rebasing against the "origin/next" so that it > is helpful to merge it with master. Requested by Hugh Brock. > > Regards > -- > Harshavardhana > Gluster - http://www.gluster.com > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From justin at redfish-group.com Thu Jul 30 03:36:54 2009 From: justin at redfish-group.com (Justin Clacherty) Date: Thu, 30 Jul 2009 13:36:54 +1000 Subject: [Ovirt-devel] build instructions Message-ID: <4A711556.6030307@redfish-group.com> Are the build instructions out of date? I've been following them but when I get to "make update-app" I get make: *** No rule to make target `update-app'. Stop. I took a look in the Makefile and sure enough there's no updated-app target or update-host for that matter... What's the procedure now? Cheers, Justin. From justin at redfish-group.com Thu Jul 30 05:32:23 2009 From: justin at redfish-group.com (Justin Clacherty) Date: Thu, 30 Jul 2009 15:32:23 +1000 (EST) Subject: [Ovirt-devel] build instructions In-Reply-To: <4A711556.6030307@redfish-group.com> Message-ID: <2107720567.11421248931943826.JavaMail.root@mail.redfish-group.com> ----- "Justin Clacherty" wrote: > What's the procedure now? I'm just going to try changing /etc/yum.repos.d/ovirt.repo to point to the repo created in the ovirt-cache directory and then follow the standard install procedure. Can anyone confirm that this is the way to go? Cheers, Justin. From harsha at gluster.com Thu Jul 30 11:11:29 2009 From: harsha at gluster.com (Harshavardhana) Date: Thu, 30 Jul 2009 16:41:29 +0530 Subject: [Ovirt-devel] GlusterFS patches for ovirt-server, ovirt-node, ovirt-node-image In-Reply-To: References: <8a80e9760907280649t76277c00va56c859a9cf9ba47@mail.gmail.com> Message-ID: <8a80e9760907300411j34c1aef1w1a1a41c8875aa735@mail.gmail.com> Jason, Oh this problem is becoz of the mount.glusterfs in 2.0.1 had some issues taking command line arguments for remote directory to mount. We fixed this in 2.0.2 and above, this is why i kept the version starting from 2.0.2. I did log a bugzilla case against the Fedora glusterfs maintainer to update to our latest release 2.0.4, and also its updated in f12 rawhide " http://koji.fedoraproject.org/koji/buildinfo?buildID=124719" And yes there are some configuration mistakes you did for server side configuration. I have attached files for server and client which you can keep it at 192.168.50.1 and test it. Here is a collaborative documentation also http://gluster.org/docs/index.php/GlusterFS_oVirt_Setup_Guide which could be referred just in case. Temporarily to build ovirt-node-image you could get glusterfs from " http://ftp.gluster.com/pub/gluster/glusterfs/ovirt/10/x86_64/" which was released just to make sure to handle this inconsistency with rawhide/upstream releases. PS: Also while providing the input for export path don't provide the backend directory path, in glusterfs case we need to provide the export-name can be anything of your choice this exports each client side configuration served from server configuration file. in the attached server configuration file is "vmimages" eg. volume-filename.vmimages /etc/glusterfs/glusterfs-client.vol Let me know how it goes :) Regards -- Harshavardhana Gluster - http://www.gluster.com On Thu, Jul 30, 2009 at 1:39 AM, Jason Guiditta wrote: > Harshavardhana, I successfully applied this series and rebuilt my server to > test. However, I have been unable to create a glusterfs pool yet. I have > installed and configured a gluster storage server that is available on the > ovirt admin network. Form the server log, it appear ok, but node cannot > mount it. I have pastebin'ed the section of taskomatic log with the error > and glusterfs.log from storage server. Please let me know if there are any > config bits that I should check which may be causing this. > > http://ovirt.pastebin.com/m67e75c21 > > Thanks, > > -j > > On Tue, Jul 28, 2009 at 9:49 AM, Harshavardhana wrote: > >> Resending patches again after rebasing against the "origin/next" so that >> it is helpful to merge it with master. Requested by Hugh Brock. >> >> Regards >> -- >> Harshavardhana >> Gluster - http://www.gluster.com >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: glusterfs-server.vol Type: application/octet-stream Size: 629 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: glusterfs-client.vol Type: application/octet-stream Size: 405 bytes Desc: not available URL: From arroy at redhat.com Thu Jul 30 14:29:40 2009 From: arroy at redhat.com (Arjun Roy) Date: Thu, 30 Jul 2009 10:29:40 -0400 Subject: [Ovirt-devel] build instructions In-Reply-To: <2107720567.11421248931943826.JavaMail.root@mail.redfish-group.com> References: <2107720567.11421248931943826.JavaMail.root@mail.redfish-group.com> Message-ID: <4A71AE54.7040102@redhat.com> On 07/30/2009 01:32 AM, Justin Clacherty wrote: > ----- "Justin Clacherty" wrote: > > >> What's the procedure now? >> > > I'm just going to try changing /etc/yum.repos.d/ovirt.repo to point to the repo created in the ovirt-cache directory and then follow the standard install procedure. Can anyone confirm that this is the way to go? > > Cheers, > Justin. > > You might also try this: http://ovirt.org/page/Devel_Quickstart -Arjun Roy -------------- next part -------------- An HTML attachment was scrubbed... URL: From hbrock at redhat.com Thu Jul 30 14:48:21 2009 From: hbrock at redhat.com (Hugh O. Brock) Date: Thu, 30 Jul 2009 10:48:21 -0400 Subject: [Ovirt-devel] GlusterFS patches for ovirt-server, ovirt-node, ovirt-node-image In-Reply-To: <8a80e9760907300411j34c1aef1w1a1a41c8875aa735@mail.gmail.com> References: <8a80e9760907280649t76277c00va56c859a9cf9ba47@mail.gmail.com> <8a80e9760907300411j34c1aef1w1a1a41c8875aa735@mail.gmail.com> Message-ID: <20090730144821.GO13765@redhat.com> On Thu, Jul 30, 2009 at 04:41:29PM +0530, Harshavardhana wrote: > Jason, > > Oh this problem is becoz of the mount.glusterfs in 2.0.1 had some issues > taking command line arguments for remote directory to mount. We fixed this > in 2.0.2 and above, this is why i kept the version starting from 2.0.2. I > did log a bugzilla case against the Fedora glusterfs maintainer to update to > our latest release 2.0.4, and also its updated in f12 rawhide " > http://koji.fedoraproject.org/koji/buildinfo?buildID=124719" > > And yes there are some configuration mistakes you did for server side > configuration. > I have attached files for server and client which you can keep it at > 192.168.50.1 and > test it. > > Here is a collaborative documentation also > http://gluster.org/docs/index.php/GlusterFS_oVirt_Setup_Guide which could be > referred just in case. > > Temporarily to build ovirt-node-image you could get glusterfs from " > http://ftp.gluster.com/pub/gluster/glusterfs/ovirt/10/x86_64/" > which was released just to make sure to handle this inconsistency with > rawhide/upstream releases. > > PS: Also while providing the input for export path don't provide the backend > directory path, in glusterfs case we need to provide the > export-name can be anything of your choice this exports each client side > configuration served from server configuration file. in the > attached server configuration file is "vmimages" eg. > volume-filename.vmimages /etc/glusterfs/glusterfs-client.vol > > Let me know how it goes :) > > Regards > -- > Harshavardhana > Gluster - http://www.gluster.com Harshavardhana, does this mean if we pull latest glusterfs from Rawhide everything should work? Thanks, --Hugh From harsha at gluster.com Thu Jul 30 17:01:22 2009 From: harsha at gluster.com (Harshavardhana) Date: Thu, 30 Jul 2009 22:31:22 +0530 Subject: [Ovirt-devel] GlusterFS patches for ovirt-server, ovirt-node, ovirt-node-image In-Reply-To: <20090730144821.GO13765@redhat.com> References: <8a80e9760907280649t76277c00va56c859a9cf9ba47@mail.gmail.com> <8a80e9760907300411j34c1aef1w1a1a41c8875aa735@mail.gmail.com> <20090730144821.GO13765@redhat.com> Message-ID: <8a80e9760907301001q79d0d269y48eb82d78e70c961@mail.gmail.com> Hugh, Yes it should. As i have provided the server/client configurations which i am using currently to test in our testbed. Let me know if you have any other questions? Thanks -- Harshavardhana Gluster - http://www.gluster.com On Thu, Jul 30, 2009 at 8:18 PM, Hugh O. Brock wrote: > On Thu, Jul 30, 2009 at 04:41:29PM +0530, Harshavardhana wrote: > > Jason, > > > > Oh this problem is becoz of the mount.glusterfs in 2.0.1 had some > issues > > taking command line arguments for remote directory to mount. We fixed > this > > in 2.0.2 and above, this is why i kept the version starting from 2.0.2. I > > did log a bugzilla case against the Fedora glusterfs maintainer to update > to > > our latest release 2.0.4, and also its updated in f12 rawhide " > > http://koji.fedoraproject.org/koji/buildinfo?buildID=124719" > > > > And yes there are some configuration mistakes you did for server side > > configuration. > > I have attached files for server and client which you can keep it at > > 192.168.50.1 and > > test it. > > > > Here is a collaborative documentation also > > http://gluster.org/docs/index.php/GlusterFS_oVirt_Setup_Guide which > could be > > referred just in case. > > > > Temporarily to build ovirt-node-image you could get glusterfs from " > > http://ftp.gluster.com/pub/gluster/glusterfs/ovirt/10/x86_64/< > http://ftp.gluster.com/pub/gluster/glusterfs/ovirt/>" > > which was released just to make sure to handle this inconsistency with > > rawhide/upstream releases. > > > > PS: Also while providing the input for export path don't provide the > backend > > directory path, in glusterfs case we need to provide the > > export-name can be anything of your choice this exports each client side > > configuration served from server configuration file. in the > > attached server configuration file is "vmimages" eg. > > volume-filename.vmimages /etc/glusterfs/glusterfs-client.vol > > > > Let me know how it goes :) > > > > Regards > > -- > > Harshavardhana > > Gluster - http://www.gluster.com > > Harshavardhana, does this mean if we pull latest glusterfs from Rawhide > everything should work? > > Thanks, > --Hugh > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From mmorsi at redhat.com Thu Jul 30 20:05:27 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 30 Jul 2009 16:05:27 -0400 Subject: [Ovirt-devel] [PATCH server] fixes to the multiple vm/nets component Message-ID: <1248984327-23504-1-git-send-email-mmorsi@redhat.com> - changes to the form/style cleaning it up greatly - changes to the controller fixing allowing nics/ networks to actually be saved (credit to Michel Loiseleur for helping with this) - very small test and indentation fixes --- src/app/controllers/vm_controller.rb | 41 +++- src/app/views/vm/_form.rhtml | 365 ++++++++++++++--------------- src/public/stylesheets/components.css | 32 ++- src/test/functional/vm_controller_test.rb | 1 - 4 files changed, 231 insertions(+), 208 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index 54c39cf..eda5798 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -177,9 +177,10 @@ class VmController < ApplicationController nnic = Nic.new(:mac => nic.mac, :vm_id => @vm.id, :network => nic.network) - if(nic.network.boot_type.proto == 'static') - nnic.ip_addresses << IpAddress.new(:address => nic.ip_address) - end + + if(nic.network.boot_type.proto == 'static') + nnic.ip_addresses << IpAddress.new(:address => nic.ip_address) + end @nics.push nnic net_conditions += (net_conditions == "" ? "" : " AND ") + @@ -191,9 +192,9 @@ class VmController < ApplicationController networks.each{ |net| nnic = Nic.new(:mac => Nic::gen_mac, :network => net) - if(net.boot_type.proto == 'static') - nnic.ip_addresses << IpAddress.new(:address => '127.0.0.1') # FIXME - end + if(net.boot_type.proto == 'static') + nnic.ip_addresses << IpAddress.new(:address => '127.0.0.1') # FIXME + end @nics.push nnic } @@ -201,14 +202,32 @@ class VmController < ApplicationController # merges vm / network parameters as submitted on the vm form def _parse_network_params(params) + # 'params' subarrays 'networks', 'macs', and 'ip_addresses' all + # correspond to each other such that networks[i] corresponds + # to macs[i] and networks.static_networks_subset[j] corresponds + # to ip_addresses[j] + ip_counter = 0 params[:nics] = [] + unless params[:networks].nil? - params[:networks].each { |network, id| - params[:nics].push({ :mac => params[("nic_"+network.to_s).intern], - :network_id => network, - :bandwidth => 0}) + (0...params[:networks].length).each { |i| + + network_id = params[:networks][i] + unless network_id.nil? || network_id == "" + nic = { :mac => params[:macs][i], + :network_id => network_id, :bandwidth => 0 } + + if(Network.find(network_id).boot_type.proto == 'static') + # FIXME make this able to be v4 or v6 address + nic[:ip_addresses] = [IpV4Address.new({ :address => params[:ip_addresses][ip_counter] })] + ip_counter += 1 + end + + params[:nics].push(nic) + end + } - end + end end end diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 2373678..5f519fc 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -50,28 +50,60 @@
      Network