From dlutter at redhat.com Fri Aug 1 02:21:52 2008 From: dlutter at redhat.com (David Lutterkort) Date: Fri, 01 Aug 2008 02:21:52 +0000 Subject: [Ovirt-devel] [PATCH] Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <1217539532-4741-1-git-send-email-dpierce@redhat.com> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> Message-ID: <1217557312.25288.193.camel@localhost.localdomain> On Thu, 2008-07-31 at 17:25 -0400, Darryl L. Pierce wrote: > The interface is stored in a module named Cobbler. > Examples for using the interface are stored in ./examples. Neat > Still looking for some feedback from anybody with some experience with Ruby. > I'm now working on the save functionality. I'm playing with having each child > class declare a saving script and passing it to the cobbler_save_method code > generator. I'd like some input from anybody on that path. Why not just decalre a save method straight up in each class ? I don't see what the metaprogramming there buys you. > ruby/rubygem-cobbler/Rakefile | 30 ++++ > ruby/rubygem-cobbler/cobbler.gemspec | 37 +++++ Generally, people put hte gemspec right in the Rakefile; for an example see ruby-libvirt's Rakefile > new file mode 100755 > index 0000000..ead6609 > --- /dev/null > +++ b/ruby/rubygem-cobbler/examples/create_system.rb > @@ -0,0 +1,52 @@ > +#!/usr/bin/ruby -W0 What do you set the warning level ? That flag shouldn't be needed. > --- /dev/null > +++ b/ruby/rubygem-cobbler/examples/has_profile.rb > + at hostname = nil > + at profile = nil Don't use instance variables here .. just simple 'hostname' and 'profile' are better in a simple script. Same comment for the other examples. > diff --git a/ruby/rubygem-cobbler/lib/cobbler.rb b/ruby/rubygem-cobbler/lib/cobbler.rb > new file mode 100644 > index 0000000..df78813 > --- /dev/null > +++ b/ruby/rubygem-cobbler/lib/cobbler.rb > @@ -0,0 +1,25 @@ > +# cobbler.rb - Cobbler module declaration. > +# > +# 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. > + > +require 'cobbler/system' Commonly, the toplevel file in a ruby library requires all the files in subdirectories, so that you can say "require 'cobbler'" and get systems, profiles, distros etc. > +module Cobbler > + > +end Nuke that ... it has no effect. > diff --git a/ruby/rubygem-cobbler/lib/cobbler/base.rb b/ruby/rubygem-cobbler/lib/cobbler/base.rb > new file mode 100644 > index 0000000..fdd696d > --- /dev/null > +++ b/ruby/rubygem-cobbler/lib/cobbler/base.rb > @@ -0,0 +1,150 @@ > +# base.rb > +# > +# 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. > + > +require 'xmlrpc/client' > +require 'pp' > + > +module Cobbler > + include XMLRPC > + > + # +Base+ represents a remote Cobbler server. > + # > + # Child classes can define fields that will be retrieved from Cobbler by > + # using the +cobbler_field+ method. For example: > + # > + # class Farkle < Base > + # cobbler_field :name, findable => 'get_farkle' > + # cobbler_field :owner > + # end > + # > + # declares a class named Farkle that contains two fields. The first, "name", > + # will be one that is searchable on Cobbler; i.e., a method named "find_by_name" > + # will be generated and will use the "get_farkle" remote method to retrieve > + # that instance from Cobbler. > + # > + # The second field, "owner", will simply be a field named Farkle.owner that > + # returns a character string. > + # > + # +Base+ provides some common functionality for all child classes: > + # > + # > + class Base > + > + @@connection = nil > + @@hostname = nil > + > + @attrs > + > + def attributes(name) > + return @attrs ? @attrs[name] : nil > + end Attributes is kinda an overloaded name in Ruby - how about fields or similar ? Also, should @attrs really be an instance variable rather than a class variable ? > + # Makes a remote call to the Cobbler server. > + # > + def self.make_call(*args) > + conn = connection(false) > + > + conn.call(*args) > + end > + > + def self.connection=(connection) > + @@connection = connection > + end > + > + def self.connection(writable = false) > + result = @@connection > + > + unless result > + result = XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") > + end > + > + return result > + end Don't you want to cache the connection here, i.e. more something like def self.connection(writable = false) unless @@connection @@connection = XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") end return @@connection end It would be cleaner if you passed the connection into the constructor (possibly wrapped in some convenience class); having it as a class variable is really strange - especially since it limits you to talking to one cobbler server for the lifetime of the Ruby interpreter. > + class << self My head is spinning: you should stick to one idiom or the other (i.e., either 'def self.foo' or 'class << self') Nice metaprogramming though. > diff --git a/ruby/rubygem-cobbler/nbproject/private/private.properties b/ruby/rubygem-cobbler/nbproject/private/private.properties That should not be in the patch. > diff --git a/ruby/rubygem-cobbler/test/test_profile.rb b/ruby/rubygem-cobbler/test/test_profile.rb > new file mode 100644 > index 0000000..01a8184 > --- /dev/null > +++ b/ruby/rubygem-cobbler/test/test_profile.rb > @@ -0,0 +1,83 @@ > +# test_profile.rb - Tests the Profile class. > +# > +# 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. > + > + > +$:.unshift File.join(File.dirname(__FILE__),'..','lib') > + > +require 'test/unit' > +require 'flexmock/test_unit' > +require 'cobbler/base' > +require 'cobbler/profile' > + > +module Cobbler > + class TestProfile < Test::Unit::TestCase > + def setup > + @connection = flexmock('connection') > + Base.connection = @connection > + Base.hostname = "localhost" > + > + @profiles = Array.new > + @profiles[0] = Hash.new > + @profiles[0]['profile'] = 'Fedora-9-i386' > + @profiles[0]['distro'] = 'Fedora-9-i386' > + @profiles[0]['dhcp tag'] = 'default' > + @profiles[0]['kernel options'] = {} > + @profiles[0]['kickstart'] = '/etc/cobbler/sample_end.ks' > + @profiles[0]['ks metadata'] = {} > + @profiles[0]['owners'] = ['admin'] > + @profiles[0]['repos'] = [] > + @profiles[0]['server'] = '<>' > + @profiles[0]['virt bridge'] = 'xenbr0' > + @profiles[0]['virt cpus'] = '1' > + @profiles[0]['virt file size'] = '5' > + @profiles[0]['virt path'] = '' > + @profiles[0]['virt ram'] = '512' > + @profiles[0]['virt type'] = 'xenpv' This can be written more compactly as @profiles << { 'profile' => 'Fedora-9-i386', 'distro' => ... } David From apevec at redhat.com Fri Aug 1 09:32:46 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 1 Aug 2008 11:32:46 +0200 Subject: [Ovirt-devel] [PATCH] intitial standalone Managed Node suport Message-ID: <1217583166-6268-1-git-send-email-apevec@redhat.com> skip services which are not available in DNS SRV records Signed-off-by: Alan Pevec --- ovirt-managed-node/src/scripts/ovirt | 44 +++++++++++++++++----------- ovirt-managed-node/src/scripts/ovirt-early | 2 +- ovirt-managed-node/src/scripts/ovirt-post | 6 +++- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/ovirt-managed-node/src/scripts/ovirt b/ovirt-managed-node/src/scripts/ovirt index 157f678..114ee1f 100755 --- a/ovirt-managed-node/src/scripts/ovirt +++ b/ovirt-managed-node/src/scripts/ovirt @@ -16,28 +16,38 @@ start() { iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT find_srv ipa tcp - krb5_conf=/etc/krb5.conf - if [ ! -s $krb5_conf ]; then - rm -f $krb5_conf - # FIXME this is IPA specific - wget -q \ - http://$SRV_HOST:$SRV_PORT/ipa/config/krb5.ini -O $krb5_conf \ - || (echo "Failed to get $krb5_conf" && return 1) + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then + krb5_conf=/etc/krb5.conf + if [ ! -s $krb5_conf ]; then + rm -f $krb5_conf + # FIXME this is IPA specific + wget -q \ + http://$SRV_HOST:$SRV_PORT/ipa/config/krb5.ini -O $krb5_conf \ + || (echo "Failed to get $krb5_conf" && return 1) + fi + else + echo "skipping Kerberos configuration, IPA service not available" fi - IPA_HOST=$SRV_HOST - IPA_PORT=$SRV_PORT find_srv identify tcp - krb5_tab=/etc/libvirt/krb5.tab - ovirt-awake start $krb5_tab $SRV_HOST $SRV_PORT + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then + krb5_tab=/etc/libvirt/krb5.tab + ovirt-awake start $krb5_tab $SRV_HOST $SRV_PORT + else + echo "skipping ovirt-awke, oVirt registration service not available" + fi find_srv collectd tcp - collectd_conf=/etc/collectd.conf - if [ -f $collectd_conf.in -a $SRV_HOST -a $SRV_PORT ]; then - sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ - -e "s/@COLLECTD_PORT@/$SRV_PORT/" $collectd_conf.in \ - > $collectd_conf \ - || (echo "Failed to write $collectd_conf" && return 1) + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then + if [ -f $collectd_conf.in ]; then + collectd_conf=/etc/collectd.conf + sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ + -e "s/@COLLECTD_PORT@/$SRV_PORT/" $collectd_conf.in \ + > $collectd_conf \ + || (echo "Failed to write $collectd_conf" && return 1) + fi + else + echo "skipping collectd configuration, collectd service not available" fi } diff --git a/ovirt-managed-node/src/scripts/ovirt-early b/ovirt-managed-node/src/scripts/ovirt-early index 6c7aaa3..aa0a49c 100755 --- a/ovirt-managed-node/src/scripts/ovirt-early +++ b/ovirt-managed-node/src/scripts/ovirt-early @@ -25,8 +25,8 @@ configure_from_network() { hostname $HOSTNAME # retrieve remote config find_srv ovirt tcp - printf . if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then + printf . wget -q -O - "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" \ | augtool > /dev/null 2>&1 if [ $? -eq 0 ]; then diff --git a/ovirt-managed-node/src/scripts/ovirt-post b/ovirt-managed-node/src/scripts/ovirt-post index 310a41c..ecc6ff4 100755 --- a/ovirt-managed-node/src/scripts/ovirt-post +++ b/ovirt-managed-node/src/scripts/ovirt-post @@ -13,7 +13,11 @@ start() { find_srv identify tcp - ovirt-identify-node -s $SRV_HOST -p $SRV_PORT + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then + ovirt-identify-node -s $SRV_HOST -p $SRV_PORT + else + echo "skipping ovirt-identify-node, oVirt registration service not available" + fi } case "$1" in -- 1.5.4.1 From john at brightbox.co.uk Fri Aug 1 11:06:55 2008 From: john at brightbox.co.uk (John Leach) Date: Fri, 01 Aug 2008 12:06:55 +0100 Subject: [Ovirt-devel] [PATCH] Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <1217557312.25288.193.camel@localhost.localdomain> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> Message-ID: <1217588815.11354.42.camel@dogen.thepride.> On Fri, 2008-08-01 at 02:21 +0000, David Lutterkort wrote: > Don't you want to cache the connection here, i.e. more something like > > def self.connection(writable = false) > unless @@connection > @@connection = XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") > end > > return @@connection > end Not to turn this into a Ruby style thread but you can do this, which is shorter, pretty common and actually faster (return is slow): def self.connection(writable = false) @@connections ||= XMLRPC::?Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") end John. -- http://www.brightbox.co.uk - UK Ruby on Rails hosting From dpierce at redhat.com Fri Aug 1 12:41:04 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 1 Aug 2008 08:41:04 -0400 Subject: [Ovirt-devel] Re: Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <1217557312.25288.193.camel@localhost.localdomain> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> Message-ID: <20080801124104.GA6313@redhat.com> +++ David Lutterkort [01/08/08 02:21 +0000]: >> Still looking for some feedback from anybody with some experience with Ruby. >> I'm now working on the save functionality. I'm playing with having each child >> class declare a saving script and passing it to the cobbler_save_method code >> generator. I'd like some input from anybody on that path. > >Why not just decalre a save method straight up in each class ? I don't >see what the metaprogramming there buys you. I guess I was trying to be a little *too* slick with the programming. For now I'll take your advice and just do a simple save per class. >> + @attrs >> + >> + def attributes(name) >> + return @attrs ? @attrs[name] : nil >> + end > >Attributes is kinda an overloaded name in Ruby - how about fields or >similar ? Also, should @attrs really be an instance variable rather than >a class variable ? Yeah. The attrs (I'll rename it to something clearer, maybe "remote_definition"?) represent the values for that particular system/distro/profile in the Cobbler server. >> + unless result >> + result = XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") >> + end >> + >> + return result >> + end > >Don't you want to cache the connection here, i.e. more something like Not really. Depending on the call, it's either got to go through the read-only interface or the read-write interface. Thanks for the input. That's the kind of stuff I need to get this codebase working cleanly. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From rjones at redhat.com Fri Aug 1 12:57:27 2008 From: rjones at redhat.com (Richard W.M. Jones) Date: Fri, 1 Aug 2008 13:57:27 +0100 Subject: [Ovirt-devel] oVirt console Message-ID: <20080801125727.GA17963@amd.home.annexia.org> As some background, Hugh Brock asked me to look at how much work it would be to add console access to guests from the oVirt WUI, properly authenticated and encrypted and -- this is the key -- supporting Windows users. I previously wrote a VNC browser plugin based around the open source gtk-vnc[i] and virt-viewer[ii] projects. This hasn't worked out well because, for reasons which are elusive, the browser plugin makes the whole browser somewhat unstable. And in any case it looks like the plugin would be limited to Firefox on Linux. My feeling is that instead of embedding the console in the browser, with all the potential complexity and unstableness involved in that, instead we should have a very small browser plugin which launches a separate virt-viewer process. This way, the browser plugin should be very small and short-lived (so won't affect the stability of the browser), should be easy to port, and all the open consoles will be running as separate virt-viewer processes so again there is a benefit of separation & stability. To achieve this, we need the following steps: (1) SASL support the VNC protocol: (a) Register one or two new VNC protocol extensions for, respectively, authentication & encryption using SASL. (b) Implement server-side protocols in upstream QEMU. This will eventually lead to it being supported in projects that synch with QEMU, ie. KVM. (c) Implement client-side protocols in gtk-vnc. (2) virt-viewer changed to register a callback to collect the required auth information (3) Build the following chain of SASL dependencies on Windows, using the MinGW cross-compiler[iii]: cyrus-sasl-*[iv] krb5-libs[v] keyutils-libs[vi] These may require changes upstream. (4) Build gtk-vnc and a virt-viewer binary for Windows. (It will already be built for Linux eg. in Fedora). (5) Write plugin(s) for Firefox on Linux / Windows using the NPAPI[vii]. (6) Write plugin for Windows / IE. (7) Build packages. Rich. Links ----- [i] http://gtk-vnc.sourceforge.net/ [ii] http://freshmeat.net/projects/virtviewer/ [iii] http://fedoraproject.org/wiki/SIGs/MinGW [iv] http://asg.web.cmu.edu/sasl/ [v] http://web.mit.edu/kerberos/www/ [vi] http://people.redhat.com/~dhowells/keyutils/ [vii] http://en.wikipedia.org/wiki/NPAPI -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ From rjones at redhat.com Fri Aug 1 13:07:15 2008 From: rjones at redhat.com (Richard W.M. Jones) Date: Fri, 1 Aug 2008 14:07:15 +0100 Subject: [Ovirt-devel] oVirt console In-Reply-To: <20080801125727.GA17963@amd.home.annexia.org> References: <20080801125727.GA17963@amd.home.annexia.org> Message-ID: <20080801130715.GA18054@amd.home.annexia.org> In case it wasn't clear, the console traffic will still travel over the VNC port (eg. 5900-) between the client's machine and the managed node. Is this expected to be a problem? Rich. -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v From berrange at redhat.com Fri Aug 1 13:15:00 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Fri, 1 Aug 2008 14:15:00 +0100 Subject: [Ovirt-devel] oVirt console In-Reply-To: <20080801125727.GA17963@amd.home.annexia.org> References: <20080801125727.GA17963@amd.home.annexia.org> Message-ID: <20080801131500.GV23993@redhat.com> On Fri, Aug 01, 2008 at 01:57:27PM +0100, Richard W.M. Jones wrote: > As some background, Hugh Brock asked me to look at how much work it > would be to add console access to guests from the oVirt WUI, properly > authenticated and encrypted and -- this is the key -- supporting > Windows users. > > I previously wrote a VNC browser plugin based around the open source > gtk-vnc[i] and virt-viewer[ii] projects. This hasn't worked out well > because, for reasons which are elusive, the browser plugin makes the > whole browser somewhat unstable. And in any case it looks like the > plugin would be limited to Firefox on Linux. > > My feeling is that instead of embedding the console in the browser, > with all the potential complexity and unstableness involved in that, > instead we should have a very small browser plugin which launches a > separate virt-viewer process. This way, the browser plugin should be > very small and short-lived (so won't affect the stability of the > browser), should be easy to port, and all the open consoles will be > running as separate virt-viewer processes so again there is a benefit > of separation & stability. > > To achieve this, we need the following steps: > > (1) SASL support the VNC protocol: > > (a) Register one or two new VNC protocol extensions for, respectively, > authentication & encryption using SASL. Only one extension is required. You negotiate encryption as part of the SASL authentication handshake. Basically if we're using SASL on the TLS encrypted channel then you set a min&max SSF of 0 (SSF = Security Strength Factor), and if not using TLS then set a min SSF of 56 (good enough to require Kerberos) and max of 100000 (or a suitably large number). This ensures that SASL encryption will be negotiated if the underlying channel isn't already using strong encryption. > (b) Implement server-side protocols in upstream QEMU. This will > eventually lead to it being supported in projects that synch > with QEMU, ie. KVM. The bulk of the server side stuff for libvirt is in the qemud/remote.c file, remoteDispatchAuthSasl(Init,Start,Step). This handles all the auth side of things. If you negotiate a SSF layer, then you also need to wire in calls to sasl_encode, sasl_decode in the part where you read/write data on the wire. > (c) Implement client-side protocols in gtk-vnc. Similarly the client side stuff in libvirt is in src/remote_internal.c in the remoteAuthSASL() method which takes care of all client auth. Again, we wire in sasl_encode/decode where needed for SSF. Gathering client credentials is a hairy bit - the virConnectCredential, virConnectAuth and virConnectAuthCallbackPtr structs/function define the public contract to expose to apps. If we use a similar contract for credentials in GTK-VNC, then it'll make the virt-viewer integration easier because it already has to deal this for libvirt auth credentials. Getting it working with Kerberos first is actually eaiser than getting it working with username/password, because with Kerberos you don't have to debug the callback stuff - it just uses the keytab. 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 jguiditt at redhat.com Fri Aug 1 15:18:15 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 01 Aug 2008 11:18:15 -0400 Subject: [Ovirt-devel] [PATCH] Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <1217588815.11354.42.camel@dogen.thepride.> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> <1217588815.11354.42.camel@dogen.thepride.> Message-ID: <1217603895.3561.3.camel@localhost.localdomain> On Fri, 2008-08-01 at 12:06 +0100, John Leach wrote: > On Fri, 2008-08-01 at 02:21 +0000, David Lutterkort wrote: > > Don't you want to cache the connection here, i.e. more something like > > > > def self.connection(writable = false) > > unless @@connection > > @@connection = XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") > > end > > > > return @@connection > > end > > Not to turn this into a Ruby style thread but you can do this, which is > shorter, pretty common and actually faster (return is slow): > > def self.connection(writable = false) > @@connections ||= XMLRPC::?Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") > end > > John. +1, not to mention that 'return' is just unneeded, since that is what happens by default, so at the very least it makes the code cleaner From jguiditt at redhat.com Fri Aug 1 18:02:23 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 01 Aug 2008 14:02:23 -0400 Subject: [Ovirt-devel] [PATCH] includes basic search functionality. In-Reply-To: <1217457392-30830-1-git-send-email-sseago@redhat.com> References: <1217457392-30830-1-git-send-email-sseago@redhat.com> Message-ID: <1217613743.3227.17.camel@localhost.localdomain> Ok, couple bits of feedback inline, but works well overall for me. On Wed, 2008-07-30 at 18:36 -0400, Scott Seago wrote: > + > + def results_json > + results_internal > + > + > + json_hash = {} > + json_hash[:page] = @page > + json_hash[:total] = @results.matches_estimated > + json_hash[:rows] = @results.results.collect do |result| > + item_hash = {} > + item = result[:model] > + item_hash[:id] = item.class.name+"_"+item.id.to_s > + item_hash[:cell] = ["display_name", "display_class"].collect do |attr| > + if attr.is_a? Array > + value = item > + attr.each { |attr_item| value = value.send(attr_item)} > + value > + else > + item.send(attr) > + end > + end > + item_hash[:cell] << result[:percent] > + item_hash > + end > + render :json => json_hash.to_json > + > + end > +end We are starting to have a lot of these *_json methods. I wonder if we should instead drop the '_json' part so if we want to change the return type we could just have a param passed in (maybe json is the default)? Just feels like we are being a bit implementation-specific. If everyone agrees with this idea, maybe rename this one, and we can start cleaning up the others as we go. > diff --git a/wui/src/app/models/host.rb b/wui/src/app/models/host.rb > index 80acaeb..33a5fa1 100644 > --- a/wui/src/app/models/host.rb > +++ b/wui/src/app/models/host.rb > @@ -40,6 +40,12 @@ class Host < ActiveRecord::Base > end > end > > + acts_as_xapian :texts => [ :hostname, :uuid ], > + :values => [ [ :created_at, 0, "created_at", :date ], > + [ :updated_at, 1, "updated_at", :date ] ], > + :terms => [ [ :hostname, 'H', "hostname" ] ] > + > + I know they are all 'QEMU' now, but if we are going to have different hypervisor types, perhaps that would be a useful thing to search on as well. I could kind of see the 'arch' field also, though that is arguably less useful. > KVM_HYPERVISOR_TYPE = "KVM" > HYPERVISOR_TYPES = [KVM_HYPERVISOR_TYPE] > STATE_UNAVAILABLE = "unavailable" > @@ -75,4 +81,11 @@ class Host < ActiveRecord::Base > def cpu_speed > "FIX ME!" > end > + > + def display_name > + hostname > + end > + def display_class > + "Host" > + end > end > diff --git a/wui/src/app/models/storage_pool.rb b/wui/src/app/models/storage_pool.rb > index a135047..39b6a08 100644 > --- a/wui/src/app/models/storage_pool.rb > +++ b/wui/src/app/models/storage_pool.rb > @@ -32,6 +32,7 @@ class StoragePool < ActiveRecord::Base > > validates_presence_of :ip_addr, :hardware_pool_id > > + acts_as_xapian :texts => [ :ip_addr, :target, :export_path ] Would 'type' perhaps be useful? > ISCSI = "iSCSI" > NFS = "NFS" > STORAGE_TYPES = { ISCSI => "Iscsi", > @@ -55,4 +56,8 @@ class StoragePool < ActiveRecord::Base > def get_type_label > STORAGE_TYPES.invert[self.class.name.gsub("StoragePool", "")] > end > + def display_class > + "Storage Pool" > + end > + > end diff --git a/wui/src/app/views/search/_grid.rhtml b/wui/src/app/views/search/_grid.rhtml > new file mode 100644 > index 0000000..6de9074 > --- /dev/null > +++ b/wui/src/app/views/search/_grid.rhtml > @@ -0,0 +1,40 @@ > +<% per_page = 40 %> > +
> +<%= '
' if checkboxes %> > + > +<%= '
' if checkboxes %> > +
> + diff --git a/wui/src/db/migrate/012_denormalize_permissions.rb b/wui/src/db/migrate/012_denormalize_permissions.rb new file mode 100644 index 0000000..f68f555 --- /dev/null +++ b/wui/src/db/migrate/012_denormalize_permissions.rb @@ -0,0 +1,32 @@ +class DenormalizePermissions < ActiveRecord::Migration + def self.up + add_column :permissions, :inherited_from_id, :integer + execute "alter table permissions add constraint fk_perm_parent + foreign key (inherited_from_id) references permissions(id)" + + Permission.transaction do + Permission.find(:all, + :conditions => "inherited_from_id is null" + ).each do |permission| + permission.pool.all_children.each do |subpool| + new_permission = Permission.new({:pool_id => subpool.id, + :uid => permission.uid, + :user_role => permission.user_role, + :inherited_from_id => permission.id}) + new_permission.save! + end + end + end + end + + def self.down + Permission.transaction do + Permission.find(:all, + :conditions => "inherited_from_id is not null" + ).each do |permission| + permission.destroy + end + end + remove_column :permissions, :inherited_from_id + end +end -- 1.5.5.1 From sseago at redhat.com Mon Aug 4 19:14:18 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 4 Aug 2008 15:14:18 -0400 Subject: [Ovirt-devel] [PATCH] permission denormalization. Message-ID: <1217877258-10144-1-git-send-email-sseago@redhat.com> Permission records are now stored for all pools a user has access to, even implied/inherited permissions. This means that if an admin is granted access to the 'default' pool, there will be one permission record for this pool, and an additional "inherited" record for each pool below 'default' in the hierarchy -- for the rest of these records, inherited_from_id will point to the master permission record. Any time a user is granted permission on a pool, the inherited records are automatically created. When a new pool is created, inherited records are created here too. You need to run 'rake db:migrate' to get the updated permissions table -- the migration script will take care of creating inherited records for existing permissions. In the UI, both direct and inherited grants are shown, but only direct grants have the checkbox to allow modification/deletion. Signed-off-by: Scott Seago --- wui/src/app/controllers/hardware_controller.rb | 2 +- wui/src/app/controllers/permission_controller.rb | 14 +++--- wui/src/app/controllers/resources_controller.rb | 2 +- wui/src/app/models/permission.rb | 42 ++++++++++++++++- wui/src/app/models/pool.rb | 35 ++++++++++----- wui/src/app/views/user/_grid.rhtml | 51 +++++++++++--------- wui/src/db/migrate/012_denormalize_permissions.rb | 32 +++++++++++++ 7 files changed, 133 insertions(+), 45 deletions(-) create mode 100644 wui/src/db/migrate/012_denormalize_permissions.rb diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index e105f7c..5f473db 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -151,7 +151,7 @@ class HardwareController < ApplicationController def users_json json_list(@pool.permissions, - [:id, :uid, :user_role]) + [:grid_id, :uid, :user_role, :source]) end def storage_pools_json diff --git a/wui/src/app/controllers/permission_controller.rb b/wui/src/app/controllers/permission_controller.rb index 14358de..4367275 100644 --- a/wui/src/app/controllers/permission_controller.rb +++ b/wui/src/app/controllers/permission_controller.rb @@ -59,10 +59,11 @@ class PermissionController < ApplicationController flash[:notice] = 'You do not have permission to create this permission record' redirect_to_parent else - if @permission.save + begin + @permission.save_with_new_children render :json => "created User Permissions for #{@permission.uid}".to_json - else - # FIXME: need to handle proper error messages w/ ajax + rescue + # FIXME: need to handle proper error messages w/ ajax render :action => 'new' end end @@ -79,13 +80,12 @@ class PermissionController < ApplicationController Permission.transaction do permissions = Permission.find(:all, :conditions => "id in (#{permission_ids.join(', ')})") permissions.each do |permission| - permission.user_role = role - permission.save! + permission.update_role(role) if permission.is_primary? end end render :json => { :object => "permission", :success => true, :alert => "User roles were successfully updated." } - rescue + rescue render :json => { :object => "permission", :success => false, :alert => "Error updating user roles: #{$!}" } end @@ -101,7 +101,7 @@ class PermissionController < ApplicationController Permission.transaction do permissions = Permission.find(:all, :conditions => "id in (#{permission_ids.join(', ')})") permissions.each do |permission| - permission.destroy + permission.destroy if permission.is_primary? end end render :json => { :object => "permission", :success => true, diff --git a/wui/src/app/controllers/resources_controller.rb b/wui/src/app/controllers/resources_controller.rb index dbf9ad7..20defca 100644 --- a/wui/src/app/controllers/resources_controller.rb +++ b/wui/src/app/controllers/resources_controller.rb @@ -97,7 +97,7 @@ class ResourcesController < ApplicationController def users_json json_list(@vm_resource_pool.permissions, - [:id, :uid, :user_role]) + [:grid_id, :uid, :user_role, :source]) end def new diff --git a/wui/src/app/models/permission.rb b/wui/src/app/models/permission.rb index 74c6c26..ece3da5 100644 --- a/wui/src/app/models/permission.rb +++ b/wui/src/app/models/permission.rb @@ -19,8 +19,13 @@ class Permission < ActiveRecord::Base belongs_to :pool + belongs_to :parent_permission, :class_name => "Permission", + :foreign_key => "inherited_from_id" + has_many :child_permissions, :dependent => :destroy, + :class_name => "Permission", :foreign_key => "inherited_from_id" - validates_uniqueness_of :uid, :scope => "pool_id" + + validates_uniqueness_of :uid, :scope => [:pool_id, :inherited_from_id] ROLE_SUPER_ADMIN = "Super Admin" ROLE_ADMIN = "Administrator" @@ -68,5 +73,38 @@ class Permission < ActiveRecord::Base PRIVILEGES[privilege] end - + def is_primary? + inherited_from_id.nil? + end + def is_inherited? + !is_primary? + end + def source + is_primary? ? "Direct" : "Inherited" + end + def grid_id + id.to_s + "_" + (is_primary? ? "1" : "0") + end + def update_role(new_role) + self.transaction do + self.user_role = new_role + self.save! + child_permissions.each do |permission| + permission.user_role = new_role + permission.save! + end + end + end + def save_with_new_children + self.transaction do + self.save! + pool.all_children.each do |subpool| + new_permission = Permission.new({:pool_id => subpool.id, + :uid => uid, + :user_role => user_role, + :inherited_from_id => id}) + new_permission.save! + end + end + end end diff --git a/wui/src/app/models/pool.rb b/wui/src/app/models/pool.rb index 4aa240b..74fe698 100644 --- a/wui/src/app/models/pool.rb +++ b/wui/src/app/models/pool.rb @@ -71,18 +71,33 @@ class Pool < ActiveRecord::Base transaction do save! move_to_child_of(parent) + parent.permissions.each do |permission| + new_permission = Permission.new({:pool_id => id, + :uid => permission.uid, + :user_role => permission.user_role, + :inherited_from_id => + permission.inherited_from_id.nil? ? + permission.id : + permission.inherited_from_id}) + new_permission.save! + end yield other_actions if other_actions end end acts_as_xapian :texts => [ :name ] - # this method lists pools with direct permission grants, but does not - # include implied permissions (i.e. subtrees) - def self.list_for_user(user, privilege) - pools = find(:all, :include => "permissions", - :conditions => "permissions.uid='#{user}' and - permissions.user_role in + # this method lists pools with direct permission grants, but by default does + # not include implied permissions (i.e. subtrees) + def self.list_for_user(user, privilege, include_indirect = false) + if include_indirect + inherited_clause = "" + else + inherited_clause = "and permissions.inherited_from_id is null" + end + pools = find(:all, :include => "permissions", + :conditions => "permissions.uid='#{user}' #{inherited_clause} and + permissions.user_role in ('#{Permission.roles_for_privilege(privilege).join("', '")}')") end @@ -126,12 +141,10 @@ class Pool < ActiveRecord::Base end def has_privilege(user, privilege) - traverse_parents do |pool| - pool.permissions.find(:first, - :conditions => "permissions.uid='#{user}' and - permissions.user_role in + pool.permissions.find(:first, + :conditions => "permissions.uid='#{user}' and + permissions.user_role in ('#{Permission.roles_for_privilege(privilege).join("', '")}')") - end end def total_resources diff --git a/wui/src/app/views/user/_grid.rhtml b/wui/src/app/views/user/_grid.rhtml index d9ad795..cabf2af 100644 --- a/wui/src/app/views/user/_grid.rhtml +++ b/wui/src/app/views/user/_grid.rhtml @@ -5,27 +5,32 @@ diff --git a/wui/src/db/migrate/012_denormalize_permissions.rb b/wui/src/db/migrate/012_denormalize_permissions.rb new file mode 100644 index 0000000..f68f555 --- /dev/null +++ b/wui/src/db/migrate/012_denormalize_permissions.rb @@ -0,0 +1,32 @@ +class DenormalizePermissions < ActiveRecord::Migration + def self.up + add_column :permissions, :inherited_from_id, :integer + execute "alter table permissions add constraint fk_perm_parent + foreign key (inherited_from_id) references permissions(id)" + + Permission.transaction do + Permission.find(:all, + :conditions => "inherited_from_id is null" + ).each do |permission| + permission.pool.all_children.each do |subpool| + new_permission = Permission.new({:pool_id => subpool.id, + :uid => permission.uid, + :user_role => permission.user_role, + :inherited_from_id => permission.id}) + new_permission.save! + end + end + end + end + + def self.down + Permission.transaction do + Permission.find(:all, + :conditions => "inherited_from_id is not null" + ).each do |permission| + permission.destroy + end + end + remove_column :permissions, :inherited_from_id + end +end -- 1.5.5.1 From sseago at redhat.com Mon Aug 4 19:16:47 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 4 Aug 2008 15:16:47 -0400 Subject: [Ovirt-devel] [PATCH] permission denormalization. (revised) Message-ID: <1217877407-10226-1-git-send-email-sseago@redhat.com> Permission records are now stored for all pools a user has access to, even implied/inherited permissions. This means that if an admin is granted access to the 'default' pool, there will be one permission record for this pool, and an additional "inherited" record for each pool below 'default' in the hierarchy -- for the rest of these records, inherited_from_id will point to the master permission record. Any time a user is granted permission on a pool, the inherited records are automatically created. When a new pool is created, inherited records are created here too. You need to run 'rake db:migrate' to get the updated permissions table -- the migration script will take care of creating inherited records for existing permissions. In the UI, both direct and inherited grants are shown, but only direct grants have the checkbox to allow modification/deletion. Signed-off-by: Scott Seago --- wui/src/app/controllers/hardware_controller.rb | 2 +- wui/src/app/controllers/permission_controller.rb | 14 +++--- wui/src/app/controllers/resources_controller.rb | 2 +- wui/src/app/models/permission.rb | 42 ++++++++++++++++- wui/src/app/models/pool.rb | 35 ++++++++++----- wui/src/app/views/user/_grid.rhtml | 51 +++++++++++--------- wui/src/db/migrate/012_denormalize_permissions.rb | 32 +++++++++++++ 7 files changed, 133 insertions(+), 45 deletions(-) create mode 100644 wui/src/db/migrate/012_denormalize_permissions.rb diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index e105f7c..5f473db 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -151,7 +151,7 @@ class HardwareController < ApplicationController def users_json json_list(@pool.permissions, - [:id, :uid, :user_role]) + [:grid_id, :uid, :user_role, :source]) end def storage_pools_json diff --git a/wui/src/app/controllers/permission_controller.rb b/wui/src/app/controllers/permission_controller.rb index 14358de..4367275 100644 --- a/wui/src/app/controllers/permission_controller.rb +++ b/wui/src/app/controllers/permission_controller.rb @@ -59,10 +59,11 @@ class PermissionController < ApplicationController flash[:notice] = 'You do not have permission to create this permission record' redirect_to_parent else - if @permission.save + begin + @permission.save_with_new_children render :json => "created User Permissions for #{@permission.uid}".to_json - else - # FIXME: need to handle proper error messages w/ ajax + rescue + # FIXME: need to handle proper error messages w/ ajax render :action => 'new' end end @@ -79,13 +80,12 @@ class PermissionController < ApplicationController Permission.transaction do permissions = Permission.find(:all, :conditions => "id in (#{permission_ids.join(', ')})") permissions.each do |permission| - permission.user_role = role - permission.save! + permission.update_role(role) if permission.is_primary? end end render :json => { :object => "permission", :success => true, :alert => "User roles were successfully updated." } - rescue + rescue render :json => { :object => "permission", :success => false, :alert => "Error updating user roles: #{$!}" } end @@ -101,7 +101,7 @@ class PermissionController < ApplicationController Permission.transaction do permissions = Permission.find(:all, :conditions => "id in (#{permission_ids.join(', ')})") permissions.each do |permission| - permission.destroy + permission.destroy if permission.is_primary? end end render :json => { :object => "permission", :success => true, diff --git a/wui/src/app/controllers/resources_controller.rb b/wui/src/app/controllers/resources_controller.rb index dbf9ad7..20defca 100644 --- a/wui/src/app/controllers/resources_controller.rb +++ b/wui/src/app/controllers/resources_controller.rb @@ -97,7 +97,7 @@ class ResourcesController < ApplicationController def users_json json_list(@vm_resource_pool.permissions, - [:id, :uid, :user_role]) + [:grid_id, :uid, :user_role, :source]) end def new diff --git a/wui/src/app/models/permission.rb b/wui/src/app/models/permission.rb index 74c6c26..ece3da5 100644 --- a/wui/src/app/models/permission.rb +++ b/wui/src/app/models/permission.rb @@ -19,8 +19,13 @@ class Permission < ActiveRecord::Base belongs_to :pool + belongs_to :parent_permission, :class_name => "Permission", + :foreign_key => "inherited_from_id" + has_many :child_permissions, :dependent => :destroy, + :class_name => "Permission", :foreign_key => "inherited_from_id" - validates_uniqueness_of :uid, :scope => "pool_id" + + validates_uniqueness_of :uid, :scope => [:pool_id, :inherited_from_id] ROLE_SUPER_ADMIN = "Super Admin" ROLE_ADMIN = "Administrator" @@ -68,5 +73,38 @@ class Permission < ActiveRecord::Base PRIVILEGES[privilege] end - + def is_primary? + inherited_from_id.nil? + end + def is_inherited? + !is_primary? + end + def source + is_primary? ? "Direct" : "Inherited" + end + def grid_id + id.to_s + "_" + (is_primary? ? "1" : "0") + end + def update_role(new_role) + self.transaction do + self.user_role = new_role + self.save! + child_permissions.each do |permission| + permission.user_role = new_role + permission.save! + end + end + end + def save_with_new_children + self.transaction do + self.save! + pool.all_children.each do |subpool| + new_permission = Permission.new({:pool_id => subpool.id, + :uid => uid, + :user_role => user_role, + :inherited_from_id => id}) + new_permission.save! + end + end + end end diff --git a/wui/src/app/models/pool.rb b/wui/src/app/models/pool.rb index 4aa240b..1870027 100644 --- a/wui/src/app/models/pool.rb +++ b/wui/src/app/models/pool.rb @@ -71,18 +71,33 @@ class Pool < ActiveRecord::Base transaction do save! move_to_child_of(parent) + parent.permissions.each do |permission| + new_permission = Permission.new({:pool_id => id, + :uid => permission.uid, + :user_role => permission.user_role, + :inherited_from_id => + permission.inherited_from_id.nil? ? + permission.id : + permission.inherited_from_id}) + new_permission.save! + end yield other_actions if other_actions end end acts_as_xapian :texts => [ :name ] - # this method lists pools with direct permission grants, but does not - # include implied permissions (i.e. subtrees) - def self.list_for_user(user, privilege) - pools = find(:all, :include => "permissions", - :conditions => "permissions.uid='#{user}' and - permissions.user_role in + # this method lists pools with direct permission grants, but by default does + # not include implied permissions (i.e. subtrees) + def self.list_for_user(user, privilege, include_indirect = false) + if include_indirect + inherited_clause = "" + else + inherited_clause = "and permissions.inherited_from_id is null" + end + pools = find(:all, :include => "permissions", + :conditions => "permissions.uid='#{user}' #{inherited_clause} and + permissions.user_role in ('#{Permission.roles_for_privilege(privilege).join("', '")}')") end @@ -126,12 +141,10 @@ class Pool < ActiveRecord::Base end def has_privilege(user, privilege) - traverse_parents do |pool| - pool.permissions.find(:first, - :conditions => "permissions.uid='#{user}' and - permissions.user_role in + permissions.find(:first, + :conditions => "permissions.uid='#{user}' and + permissions.user_role in ('#{Permission.roles_for_privilege(privilege).join("', '")}')") - end end def total_resources diff --git a/wui/src/app/views/user/_grid.rhtml b/wui/src/app/views/user/_grid.rhtml index d9ad795..cabf2af 100644 --- a/wui/src/app/views/user/_grid.rhtml +++ b/wui/src/app/views/user/_grid.rhtml @@ -5,27 +5,32 @@ diff --git a/wui/src/db/migrate/012_denormalize_permissions.rb b/wui/src/db/migrate/012_denormalize_permissions.rb new file mode 100644 index 0000000..f68f555 --- /dev/null +++ b/wui/src/db/migrate/012_denormalize_permissions.rb @@ -0,0 +1,32 @@ +class DenormalizePermissions < ActiveRecord::Migration + def self.up + add_column :permissions, :inherited_from_id, :integer + execute "alter table permissions add constraint fk_perm_parent + foreign key (inherited_from_id) references permissions(id)" + + Permission.transaction do + Permission.find(:all, + :conditions => "inherited_from_id is null" + ).each do |permission| + permission.pool.all_children.each do |subpool| + new_permission = Permission.new({:pool_id => subpool.id, + :uid => permission.uid, + :user_role => permission.user_role, + :inherited_from_id => permission.id}) + new_permission.save! + end + end + end + end + + def self.down + Permission.transaction do + Permission.find(:all, + :conditions => "inherited_from_id is not null" + ).each do |permission| + permission.destroy + end + end + remove_column :permissions, :inherited_from_id + end +end -- 1.5.5.1 From jim at meyering.net Mon Aug 4 20:29:47 2008 From: jim at meyering.net (Jim Meyering) Date: Mon, 04 Aug 2008 22:29:47 +0200 Subject: [Ovirt-devel] coming soon: prohibition on pushing bad-whitespace-adding changes In-Reply-To: <87ljzkejmx.fsf@rho.meyering.net> (Jim Meyering's message of "Tue, 29 Jul 2008 18:32:22 +0200") References: <87ljzkejmx.fsf@rho.meyering.net> Message-ID: <87hca0sev8.fsf@rho.meyering.net> Jim Meyering wrote: > In the next couple of days, I expect to enable a git hook on the server > that will deny any attempt to push a change that adds "bad" whitespace. > In the mean time, you who will be pushing or posting changes should be > sure to enable commit-time checks for white space: > > Run "chmod a+x .git/hooks/update" (if you already have the file, > .git/hooks/update) or "cd .git/hooks && cp -a update.sample update" > if you have one named update.sample. > > That will help you by making your local commits fail whenever > a change adds bad (e.g. trailing) whitespace. If that happens, > it tells you which file/line-number are affected, and you just > edit those lines and retry the commit. > > If you forget and commit (locally) changes that add trailing blanks, > you'll have to edit those change sets e.g., via "git rebase -i next", > before you can push them. I enabled this new hook a few hours ago. From yunguol at cn.ibm.com Tue Aug 5 09:15:41 2008 From: yunguol at cn.ibm.com (Guo Lian Yun) Date: Tue, 5 Aug 2008 17:15:41 +0800 Subject: [Ovirt-devel] blocked on virt-viewer in Ovirt install preparation Message-ID: Hi, I try to install Ovirt on Fedora 9, but block on virt-viewer installation. The other dependency packages are installed successfully. Here is the version of them: Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 kvm-65-1.fc9.x86_64 libvirt-0.4.3 (installed manually in /usr dir) virt-manager-0.5.3 (installed manually in /usr dir) However, it report error when I configure virt-viewer of the following: checking for LIBVIRT... configure: error: Package requirements (libvirt >= 0.2.0) were not met: Actually, the libvirt is already installed in /usr, who knows why this fails? Thanks! Best, Regards Daisy (???) VSM Team, China Systems & Technology Labs (CSTL) E-mail: yunguol at cn.ibm.com TEL: (86)-21-60922403 Building 10, 399 Ke Yuan Rd, Pudong Shanghai, 201203 -------------- next part -------------- An HTML attachment was scrubbed... URL: From yunguol at cn.ibm.com Tue Aug 5 09:46:06 2008 From: yunguol at cn.ibm.com (Guo Lian Yun) Date: Tue, 5 Aug 2008 17:46:06 +0800 Subject: [Ovirt-devel] blocked on virt-viewer in Ovirt install preparation Message-ID: Hi, I try to install Ovirt on Fedora 9, but block on virt-viewer installation. The other dependency packages are installed successfully. Here is the version of them: Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 kvm-65-1.fc9.x86_64 libvirt-0.4.3 (installed manually in /usr dir) virt-manager-0.5.3 (installed manually in /usr dir) However, it report error when I configure virt-viewer of the following: checking for LIBVIRT... configure: error: Package requirements (libvirt >= 0.2.0) were not met: No package 'libvirt' found Actually, the libvirt is already installed in /usr, who knows why this fails? I try to install libvirt as default dir of /usr/local, but still fails for me. Thanks! Best, Regards Daisy (???) VSM Team, China Systems & Technology Labs (CSTL) E-mail: yunguol at cn.ibm.com TEL: (86)-21-60922403 Building 10, 399 Ke Yuan Rd, Pudong Shanghai, 201203 -------------- next part -------------- An HTML attachment was scrubbed... URL: From clalance at redhat.com Tue Aug 5 10:32:33 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 05 Aug 2008 12:32:33 +0200 Subject: [Ovirt-devel] blocked on virt-viewer in Ovirt install preparation In-Reply-To: References: Message-ID: <48982C41.1020908@redhat.com> Guo Lian Yun wrote: > However, it report error when I configure virt-viewer of the following: > > checking for LIBVIRT... configure: error: Package requirements (libvirt >>= 0.2.0) were not met: > No package 'libvirt' found > > Actually, the libvirt is already installed in /usr, who knows why this > fails? > I try to install libvirt as default dir of /usr/local, but still fails > for me. I'm pretty sure you'll need the libvirt-devel package installed to compile this. I guess my question is: why are you doing all of this by hand? There are packages in Fedora for *most* of the software we need in ovirt, and for those pieces that we either need updated packages or packages not in Fedora, we have an ovirt repository on http://ovirt.org with those packages. Even better, we have pre-generated appliances and ways to create your own appliances that already have ovirt embedded on them. As a starting place, I would strongly recommend going down this road. Once you've seen what it can do and how it works, then you can start compiling your own stuff from source. Chris Lalancette From veillard at redhat.com Tue Aug 5 10:38:57 2008 From: veillard at redhat.com (Daniel Veillard) Date: Tue, 5 Aug 2008 06:38:57 -0400 Subject: [Ovirt-devel] blocked on virt-viewer in Ovirt install preparation In-Reply-To: References: Message-ID: <20080805103857.GA11796@redhat.com> On Tue, Aug 05, 2008 at 05:15:41PM +0800, Guo Lian Yun wrote: > Hi, > > I try to install Ovirt on Fedora 9, but block on virt-viewer installation. > > The other dependency packages are installed successfully. Here is the > version of them: > > Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > > kvm-65-1.fc9.x86_64 > > libvirt-0.4.3 (installed manually in /usr dir) > > virt-manager-0.5.3 (installed manually in /usr dir) > > > However, it report error when I configure virt-viewer of the following: > > checking for LIBVIRT... configure: error: Package requirements (libvirt >= > 0.2.0) were not met: > > Actually, the libvirt is already installed in /usr, who knows why this > fails? first you should never install manually in /usr for an RPM based system, you're breaking all dependancies. second install rpms for libvirt and libvirt-devel preferably 0.4.4 they are available as updates for F-9 Daniel -- Red Hat Virtualization group http://redhat.com/virtualization/ Daniel Veillard | virtualization library http://libvirt.org/ veillard at redhat.com | libxml GNOME XML XSLT toolkit http://xmlsoft.org/ http://veillard.com/ | Rpmfind RPM search engine http://rpmfind.net/ From sseago at redhat.com Tue Aug 5 14:36:20 2008 From: sseago at redhat.com (Scott Seago) Date: Tue, 5 Aug 2008 10:36:20 -0400 Subject: [Ovirt-devel] [PATCH] xapian migration fix Message-ID: <1217946980-14192-1-git-send-email-sseago@redhat.com> moved default pool creation to migration 11, as it fails in migration 1 without the xapian tables. Signed-off-by: Scott Seago --- wui/src/db/migrate/001_create_pools.rb | 1 - wui/src/db/migrate/011_create_acts_as_xapian.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletions(-) diff --git a/wui/src/db/migrate/001_create_pools.rb b/wui/src/db/migrate/001_create_pools.rb index 34c01d3..113a7a9 100644 --- a/wui/src/db/migrate/001_create_pools.rb +++ b/wui/src/db/migrate/001_create_pools.rb @@ -31,7 +31,6 @@ class CreatePools < ActiveRecord::Migration execute "alter table pools add constraint fk_pool_parent foreign key (parent_id) references pools(id)" - mp = HardwarePool.create( :name=>'default') end def self.down diff --git a/wui/src/db/migrate/011_create_acts_as_xapian.rb b/wui/src/db/migrate/011_create_acts_as_xapian.rb index 84a9dd7..3655f8e 100644 --- a/wui/src/db/migrate/011_create_acts_as_xapian.rb +++ b/wui/src/db/migrate/011_create_acts_as_xapian.rb @@ -6,9 +6,17 @@ class CreateActsAsXapian < ActiveRecord::Migration t.column :action, :string, :null => false end add_index :acts_as_xapian_jobs, [:model, :model_id], :unique => true + + begin + root_pool = HardwarePool.get_default_pool + new_root = HardwarePool.create( :name=>'default') unless root_pool + rescue + puts "Could not create default pool..." + end end def self.down drop_table :acts_as_xapian_jobs end + end -- 1.5.5.1 From berrange at redhat.com Tue Aug 5 16:47:24 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Tue, 5 Aug 2008 17:47:24 +0100 Subject: [Ovirt-devel] PostgreSQL supports GSSAPI auth.. Message-ID: <20080805164724.GX6570@redhat.com> I notice that the WUI appliance creates a random password for the postgresql server in its setup. PostgreSQL has long had Kerberos support authenticating users against their kerberos password, instead of tracking it in the PG user database, but more compelling is that it also recently gained GSSAPI support for single-signon If your PG client (ie oVirt WUI/taskomatic) has a client principle, then it can login to PG without needing a password. ALl that is needed is to create a PG user with matching username to your client principle username http://developer.postgresql.org/pgdocs/postgres/auth-methods.html#GSSAPI-AUTH http://developer.postgresql.org/pgdocs/postgres/auth-methods.html#KERBEROS-AUTH oVirt of course already has a client principle since it uses that to talk to libvirt, so it strikes me that it ought to be possible to just use that for PG too, and do away with generating a random password for PG 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 yunguol at cn.ibm.com Tue Aug 5 09:36:09 2008 From: yunguol at cn.ibm.com (Guo Lian Yun) Date: Tue, 5 Aug 2008 17:36:09 +0800 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: Message-ID: Guo Lian Yun/China/IBM wrote on 2008-08-05 17:15:41: > Hi, > > I try to install Ovirt on Fedora 9, but block on virt-viewer installation. > > The other dependency packages are installed successfully. Here is > the version of them: > > Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > > kvm-65-1.fc9.x86_64 > > libvirt-0.4.3 (installed manually in /usr dir) I try to install it as default dir of /usr/local, but still fails for me. Is the environment variables LIBVIRT_CFLAGS and LIBVIRT_LIBS have to set? > > virt-manager-0.5.3 (installed manually in /usr dir) > > However, it report error when I configure virt-viewer of the following: > > checking for LIBVIRT... configure: error: Package requirements > (libvirt >= 0.2.0) were not met: > > Actually, the libvirt is already installed in /usr, who knows why this fails? > > Thanks! > > Best, > Regards > > Daisy (???) > VSM Team, China Systems & Technology Labs (CSTL) > E-mail: yunguol at cn.ibm.com > TEL: (86)-21-60922403 > Building 10, 399 Ke Yuan Rd, Pudong Shanghai, 201203 -------------- next part -------------- An HTML attachment was scrubbed... URL: From pmyers at redhat.com Tue Aug 5 17:22:53 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Tue, 05 Aug 2008 13:22:53 -0400 Subject: [Ovirt-devel] PostgreSQL supports GSSAPI auth.. In-Reply-To: <20080805164724.GX6570@redhat.com> References: <20080805164724.GX6570@redhat.com> Message-ID: <48988C6D.2040009@redhat.com> Daniel P. Berrange wrote: > I notice that the WUI appliance creates a random password for the postgresql > server in its setup. > > PostgreSQL has long had Kerberos support authenticating users against their > kerberos password, instead of tracking it in the PG user database, but more > compelling is that it also recently gained GSSAPI support for single-signon > > If your PG client (ie oVirt WUI/taskomatic) has a client principle, then > it can login to PG without needing a password. ALl that is needed is to > create a PG user with matching username to your client principle username > > http://developer.postgresql.org/pgdocs/postgres/auth-methods.html#GSSAPI-AUTH > http://developer.postgresql.org/pgdocs/postgres/auth-methods.html#KERBEROS-AUTH > > oVirt of course already has a client principle since it uses that to talk > to libvirt, so it strikes me that it ought to be possible to just use that > for PG too, and do away with generating a random password for PG Didn't know that... We do use a service principal on the ovirt server to talk between the various local services (taskomatic, host browser, etc). I see no reason that we couldn't extend this to postgresql. Someone want to work on that and submit a patch? :) Perry From pmyers at redhat.com Tue Aug 5 17:25:41 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Tue, 05 Aug 2008 13:25:41 -0400 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: References: Message-ID: <48988D15.3060103@redhat.com> Guo Lian Yun wrote: > > Guo Lian Yun/China/IBM wrote on 2008-08-05 17:15:41: > > > Hi, > > > > I try to install Ovirt on Fedora 9, but block on virt-viewer > installation. > > > > The other dependency packages are installed successfully. Here is > > the version of them: > > > > Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > > > > kvm-65-1.fc9.x86_64 > > > > libvirt-0.4.3 (installed manually in /usr dir) > > I try to install it as default dir of /usr/local, but still fails for me. > Is the environment variables LIBVIRT_CFLAGS and LIBVIRT_LIBS have to set? You need to use libvirt from the RPM packages provided by Fedora. If you don't install libvirt as an RPM then you will break dependencies for all other packages that are dependent on libvirt. If you do: yum install libvirt libvirt-devel You'll get the latest version of libvirt installed and then you can run: yum install virt-viewer And things should work fine. We don't support using oVirt in the way you're using it as all of our scripts assume that yum/rpm is being used to manage the various packages. Perry From apevec at redhat.com Tue Aug 5 21:50:52 2008 From: apevec at redhat.com (Alan Pevec) Date: Tue, 5 Aug 2008 23:50:52 +0200 Subject: [Ovirt-devel] [PATCH] set RAILS_ENV for all services in common /etc/sysconfig/ovirt-rails Message-ID: <1217973052-20639-1-git-send-email-apevec@redhat.com> Signed-off-by: Alan Pevec --- wui/conf/ovirt-host-browser | 4 ++++ wui/conf/ovirt-host-collect | 4 ++++ wui/conf/ovirt-host-status | 4 ++++ wui/conf/ovirt-mongrel-rails | 8 +++++--- wui/conf/ovirt-mongrel-rails.sysconf | 4 ---- wui/conf/ovirt-rails.sysconf | 3 +++ wui/conf/ovirt-taskomatic | 4 ++++ wui/ovirt-wui.spec | 2 ++ 8 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 wui/conf/ovirt-rails.sysconf diff --git a/wui/conf/ovirt-host-browser b/wui/conf/ovirt-host-browser index 5e7fdae..892b155 100755 --- a/wui/conf/ovirt-host-browser +++ b/wui/conf/ovirt-host-browser @@ -8,6 +8,10 @@ # ovirt VM manager. # +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + +export RAILS_ENV="${RAILS_ENV:-production}" + DAEMON=/usr/share/ovirt-wui/host-browser/host-browser.rb . /etc/init.d/functions diff --git a/wui/conf/ovirt-host-collect b/wui/conf/ovirt-host-collect index 2d8ffcd..63e667a 100755 --- a/wui/conf/ovirt-host-collect +++ b/wui/conf/ovirt-host-collect @@ -8,6 +8,10 @@ # ovirt VM manager. # +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + +export RAILS_ENV="${RAILS_ENV:-production}" + DAEMON=/usr/share/ovirt-wui/host-collect/host-collect.rb . /etc/init.d/functions diff --git a/wui/conf/ovirt-host-status b/wui/conf/ovirt-host-status index 7804163..1757295 100755 --- a/wui/conf/ovirt-host-status +++ b/wui/conf/ovirt-host-status @@ -8,6 +8,10 @@ # ovirt VM manager. # +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + +export RAILS_ENV="${RAILS_ENV:-production}" + DAEMON=/usr/share/ovirt-wui/host-status/host-status.rb . /etc/init.d/functions diff --git a/wui/conf/ovirt-mongrel-rails b/wui/conf/ovirt-mongrel-rails index 7cfaf2d..498e2bf 100755 --- a/wui/conf/ovirt-mongrel-rails +++ b/wui/conf/ovirt-mongrel-rails @@ -8,13 +8,15 @@ # ovirt VM manager. # +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + [ -r /etc/sysconfig/ovirt-mongrel-rails ] && . /etc/sysconfig/ovirt-mongrel-rails +RAILS_ENV="${RAILS_ENV:-production}" OVIRT_DIR="${OVIRT_DIR:-/usr/share/ovirt-wui}" MONGREL_LOG="${MONGREL_LOG:-/var/log/ovirt-wui/mongrel.log}" MONGREL_PID="${MONGREL_PID:-/var/run/ovirt-wui/mongrel.pid}" MONGREL_LOCKFILE="${MONGREL_LOCKFILE:-/var/lock/subsys/ovirt-wui}" -RAILS_ENVIRONMENT="${RAILS_ENVIRONMENT:-production}" USER="${USER:-ovirt}" GROUP="${GROUP:-ovirt}" PREFIX="${PREFIX:-/ovirt}" @@ -29,9 +31,9 @@ RETVAL=0 start() { echo -n "Starting ovirt-mongrel-rails: " - RAILS_ENV=$RAILS_ENVIRONMENT $REINDEX_PROG + RAILS_ENV=$RAILS_EN $REINDEX_PROG $MONGREL_PROG start -c $OVIRT_DIR -l $MONGREL_LOG -P $MONGREL_PID \ - -a $ADDR -e $RAILS_ENVIRONMENT --user $USER --group $GROUP \ + -a $ADDR -e $RAILS_ENV --user $USER --group $GROUP \ -d --prefix=$PREFIX RETVAL=$? if [ $RETVAL -eq 0 ] && touch $MONGREL_LOCKFILE ; then diff --git a/wui/conf/ovirt-mongrel-rails.sysconf b/wui/conf/ovirt-mongrel-rails.sysconf index 80ac0e6..ce43383 100644 --- a/wui/conf/ovirt-mongrel-rails.sysconf +++ b/wui/conf/ovirt-mongrel-rails.sysconf @@ -10,10 +10,6 @@ # location of the file containing running process ID #MONGREL_PID=/var/run/ovirt-wui/mongrel.pid -# sets ruby on Rails environment / mode of operation -# http://wiki.rubyonrails.org/rails/pages/Environments -#RAILS_ENVIRONMENT=production - # user and group under which Rails application runs #USER=ovirt #GROUP=ovirt diff --git a/wui/conf/ovirt-rails.sysconf b/wui/conf/ovirt-rails.sysconf new file mode 100644 index 0000000..bc9e237 --- /dev/null +++ b/wui/conf/ovirt-rails.sysconf @@ -0,0 +1,3 @@ +# sets ruby on Rails environment / mode of operation +# http://wiki.rubyonrails.org/rails/pages/Environments +#RAILS_ENV=production diff --git a/wui/conf/ovirt-taskomatic b/wui/conf/ovirt-taskomatic index 2f9b255..2e548b4 100755 --- a/wui/conf/ovirt-taskomatic +++ b/wui/conf/ovirt-taskomatic @@ -8,6 +8,10 @@ # ovirt VM manager. # +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails + +export RAILS_ENV="${RAILS_ENV:-production}" + DAEMON=/usr/share/ovirt-wui/task-omatic/taskomatic.rb . /etc/init.d/functions diff --git a/wui/ovirt-wui.spec b/wui/ovirt-wui.spec index 9dda52a..7746430 100644 --- a/wui/ovirt-wui.spec +++ b/wui/ovirt-wui.spec @@ -79,6 +79,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/host-status.log %{__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-taskomatic %{buildroot}%{_initrddir} # copy over all of the src directory... @@ -171,6 +172,7 @@ fi %{_initrddir}/ovirt-mongrel-rails %{_initrddir}/ovirt-taskomatic %config(noreplace) %{_sysconfdir}/sysconfig/ovirt-mongrel-rails +%config(noreplace) %{_sysconfdir}/sysconfig/ovirt-rails %config(noreplace) %{_sysconfdir}/httpd/conf.d/%{name}.conf %doc %attr(-, ovirt, ovirt) %{_localstatedir}/lib/%{name} -- 1.5.4.1 From mmorsi at redhat.com Tue Aug 5 23:06:44 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 05 Aug 2008 19:06:44 -0400 Subject: [Ovirt-devel] [PATCH] oVirt / RRD Test Data Message-ID: <4898DD04.8070705@redhat.com> Attached is my now functional ruby script to generate test / demo rrd data and the oVirt patch required to get it working (a few small code tweaks / fixes and a few fixture modifications). 1. To generate the data data simply run `ruby demo-rrd-data.rb`. There are a few variables at the top of the script which you can tweak to configure things like nodes generated, cpu's per node, interval in seconds between data points, etc. 2. To see the test data, you need to be running rails in the testing environment and have a populated ovirt_test db. This can be accomplished by running the tests once (eg 'rake test') 3. Alot of data needs to be generated, thus the script is slow (at least on my system), and it isn't yet set to generate the number of data points in a time period that collectd does / oVirt is expecting (change the $interval variable at the top of the script from 60 to 10 to do this, but this will be all the more slower). Since the graphs require more data points than it is currently getting, they appear a bit discrete. Perhaps we can correct this and run it on a faster machine and just send out and use the data set generated. Alternatively, I could disable some of the algorithms used to generated data and simply reuse data points for some of the graphs if series repetition is not a problem. -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: demo-rrd-data.rb Type: application/x-ruby Size: 10852 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-rrd-test-data.patch Type: text/x-patch Size: 4660 bytes Desc: not available URL: From slinabery at redhat.com Tue Aug 5 23:21:07 2008 From: slinabery at redhat.com (Steve Linabery) Date: Tue, 5 Aug 2008 18:21:07 -0500 Subject: [Ovirt-devel] [PATCH] xapian migration fix In-Reply-To: <1217946980-14192-1-git-send-email-sseago@redhat.com> References: <1217946980-14192-1-git-send-email-sseago@redhat.com> Message-ID: <20080805232106.GD2798@redhat.com> On Tue, Aug 05, 2008 at 10:36:20AM -0400, Scott Seago wrote: > moved default pool creation to migration 11, as it fails in migration 1 without the xapian tables. > > Signed-off-by: Scott Seago > --- > wui/src/db/migrate/001_create_pools.rb | 1 - > wui/src/db/migrate/011_create_acts_as_xapian.rb | 8 ++++++++ > 2 files changed, 8 insertions(+), 1 deletions(-) > > diff --git a/wui/src/db/migrate/001_create_pools.rb b/wui/src/db/migrate/001_create_pools.rb > index 34c01d3..113a7a9 100644 > --- a/wui/src/db/migrate/001_create_pools.rb > +++ b/wui/src/db/migrate/001_create_pools.rb > @@ -31,7 +31,6 @@ class CreatePools < ActiveRecord::Migration > > execute "alter table pools add constraint fk_pool_parent > foreign key (parent_id) references pools(id)" > - mp = HardwarePool.create( :name=>'default') > end > > def self.down > diff --git a/wui/src/db/migrate/011_create_acts_as_xapian.rb b/wui/src/db/migrate/011_create_acts_as_xapian.rb > index 84a9dd7..3655f8e 100644 > --- a/wui/src/db/migrate/011_create_acts_as_xapian.rb > +++ b/wui/src/db/migrate/011_create_acts_as_xapian.rb > @@ -6,9 +6,17 @@ class CreateActsAsXapian < ActiveRecord::Migration > t.column :action, :string, :null => false > end > add_index :acts_as_xapian_jobs, [:model, :model_id], :unique => true > + > + begin > + root_pool = HardwarePool.get_default_pool > + new_root = HardwarePool.create( :name=>'default') unless root_pool > + rescue > + puts "Could not create default pool..." > + end > end > def self.down > drop_table :acts_as_xapian_jobs > end > + > end > > -- > 1.5.5.1 > Works for me. ACK. > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From mwagner at redhat.com Wed Aug 6 03:55:22 2008 From: mwagner at redhat.com (mark wagner) Date: Tue, 05 Aug 2008 23:55:22 -0400 Subject: [Ovirt-devel] Re: [PATCH] oVirt / RRD Test Data In-Reply-To: <4898DD04.8070705@redhat.com> References: <4898DD04.8070705@redhat.com> Message-ID: <489920AA.9090208@redhat.com> Mohammed Morsi wrote: > Attached is my now functional ruby script to generate test / demo rrd > data and the oVirt patch required to get it working (a few small code > tweaks / fixes and a few fixture modifications). > > 1. To generate the data data simply run `ruby demo-rrd-data.rb`. There > are a few variables at the top of the script which you can tweak to > configure things like nodes generated, cpu's per node, interval in > seconds between data points, etc. > 2. To see the test data, you need to be running rails in the testing > environment and have a populated ovirt_test db. This can be accomplished > by running the tests once (eg 'rake test') > 3. Alot of data needs to be generated, thus the script is slow (at least > on my system), and it isn't yet set to generate the number of data > points in a time period that collectd does / oVirt is expecting (change > the $interval variable at the top of the script from 60 to 10 to do > this, but this will be all the more slower). Since the graphs require > more data points than it is currently getting, they appear a bit > discrete. Perhaps we can correct this and run it on a faster machine and > just send out and use the data set generated. Alternatively, I could > disable some of the algorithms used to generated data and simply reuse > data points for some of the graphs if series repetition is not a problem. > > -Mo > Mo Looks like the script ran for a while. It generated the device dirs but it did not generate the .rrd files. So I have /var/lib/collectd/rrd-test/node0/cpu-0 but it is an empty directory. Debugging now... -mark From yunguol at cn.ibm.com Wed Aug 6 11:21:49 2008 From: yunguol at cn.ibm.com (Guo Lian Yun) Date: Wed, 6 Aug 2008 19:21:49 +0800 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: <48988D15.3060103@redhat.com> Message-ID: "Perry N. Myers" wrote on 2008-08-06 01:25:41: > Guo Lian Yun wrote: > > > > Guo Lian Yun/China/IBM wrote on 2008-08-05 17:15:41: > > > > > Hi, > > > > > > I try to install Ovirt on Fedora 9, but block on virt-viewer > > installation. > > > > > > The other dependency packages are installed successfully. Here is > > > the version of them: > > > > > > Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > > > > > > kvm-65-1.fc9.x86_64 > > > > > > libvirt-0.4.3 (installed manually in /usr dir) > > > > I try to install it as default dir of /usr/local, but still fails for me. > > Is the environment variables LIBVIRT_CFLAGS and LIBVIRT_LIBS have to set? > > You need to use libvirt from the RPM packages provided by Fedora. If you > don't install libvirt as an RPM then you will break dependencies for all > other packages that are dependent on libvirt. If you do: > > yum install libvirt libvirt-devel > > You'll get the latest version of libvirt installed and then you can run: > > yum install virt-viewer > > And things should work fine. > > We don't support using oVirt in the way you're using it as all of our > scripts assume that yum/rpm is being used to manage the various packages. Thanks for your great help! I installed successfully all dependency RPM by yum. But it report error when I'm installing Ovirt. 1) It fails for me by running create-wui-appliance.sh to create virtual server. #bash create-wui-appliance.sh -t http://download.fedora.redhat.com/pub/fedora/linux/releases/9/Fedora/x86_64/os/ -k http://ovirt.org/download/wui-rel-x86_64.ks -v Domain node3 defined from /tmp/tmp.b3q8JWVRoM Domain node4 defined from /tmp/tmp.jtalRXJK3k Domain node5 defined from /tmp/tmp.B8zlcSB1Cj Network dummybridge destroyed Network dummybridge has been undefined Network dummybridge defined from /tmp/tmp.s8cSkpypob Network dummybridge started Network dummybridge marked as autostarted Formatting '/var/lib/libvirt/images/developer.img', fmt=qcow2, size=6144000 kB Wed, 06 Aug 2008 03:40:05 ERROR virConnectOpen() failed Traceback (most recent call last): File "/usr/sbin/virt-install", line 496, in main() File "/usr/sbin/virt-install", line 345, in main conn = cli.getConnection(options.connect) File "/usr/lib/python2.5/site-packages/virtinst/cli.py", line 92, in getConnection return libvirt.open(connect) File "/usr/lib64/python2.5/site-packages/libvirt.py", line 135, in open if ret is None:raise libvirtError('virConnectOpen() failed') libvirtError: virConnectOpen() failed Who knows why it is? Then I try to run below command and pass for me. #bash create-wui-appliance.sh -d "`pwd`" -v Also, I can start 'developer' successfully. 2) I tried to invoke the main Virt page, but it fails for me. #ssh -Y root at 192.168.50.2 firefox (or ssh -fY root at 192.168.50.2 firefox -no-remote) ssh: connect to host 192.168.50.2 port 22: No route to host What's the ip refer to? Is it refer to the ip address of my own machine? It still fails for me by trying conncect to myself. Here is the dummybridge output for my own machine. dummybridge Link encap:Ethernet HWaddr 00:FF:99:97:1A:8F inet addr:192.168.50.1 Bcast:192.168.50.255 Mask:255.255.255.0 inet6 addr: fe80::3cb0:b6ff:feed:cab/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:26 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 b) TX bytes:4427 (4.3 KiB) #ssh -fY root at 192.168.50.1 firefox -no-remote Error: no display specified Does this fail because of putty? The set up machine isn't on my hand, I connect to it by putty. Do I have to install it on my local machine? Here is the info of Dependency RPM version on my machine: kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 kvm-65-1.fc9.x86_64 libvirt-0.4.1-4.fc9.x86_64 virt-manager-0.5.4-2.fc9.x86_64 virt-viewer-0.0.3-1.fc9.x86_64 Thanks! > > Perry -------------- next part -------------- An HTML attachment was scrubbed... URL: From yunguol at cn.ibm.com Wed Aug 6 11:27:22 2008 From: yunguol at cn.ibm.com (Guo Lian Yun) Date: Wed, 6 Aug 2008 19:27:22 +0800 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: Message-ID: ovirt-devel-bounces at redhat.com wrote on 2008-08-06 19:21:49: > > "Perry N. Myers" wrote on 2008-08-06 01:25:41: > > > Guo Lian Yun wrote: > > > > > > Guo Lian Yun/China/IBM wrote on 2008-08-05 17:15:41: > > > > > > > Hi, > > > > > > > > I try to install Ovirt on Fedora 9, but block on virt-viewer > > > installation. > > > > > > > > The other dependency packages are installed successfully. Here is > > > > the version of them: > > > > > > > > Kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > > > > > > > > kvm-65-1.fc9.x86_64 > > > > > > > > libvirt-0.4.3 (installed manually in /usr dir) > > > > > > I try to install it as default dir of /usr/local, but still > fails for me. > > > Is the environment variables LIBVIRT_CFLAGS and LIBVIRT_LIBS > have to set? > > > > You need to use libvirt from the RPM packages provided by Fedora. If you > > don't install libvirt as an RPM then you will break dependencies for all > > other packages that are dependent on libvirt. If you do: > > > > yum install libvirt libvirt-devel > > > > You'll get the latest version of libvirt installed and then you can run: > > > > yum install virt-viewer > > > > And things should work fine. > > > > We don't support using oVirt in the way you're using it as all of our > > scripts assume that yum/rpm is being used to manage the various packages. > > Thanks for your great help! > > I installed successfully all dependency RPM by yum. But it report > error when > I'm installing Ovirt. > > 1) It fails for me by running create-wui-appliance.sh to create > virtual server. > > #bash create-wui-appliance.sh -t http://download.fedora.redhat. > com/pub/fedora/linux/releases/9/Fedora/x86_64/os/ -k http://ovirt. > org/download/wui-rel-x86_64.ks -v > Domain node3 defined from /tmp/tmp.b3q8JWVRoM > > Domain node4 defined from /tmp/tmp.jtalRXJK3k > > Domain node5 defined from /tmp/tmp.B8zlcSB1Cj > > Network dummybridge destroyed > > Network dummybridge has been undefined > > Network dummybridge defined from /tmp/tmp.s8cSkpypob > > Network dummybridge started > > Network dummybridge marked as autostarted > > Formatting '/var/lib/libvirt/images/developer.img', fmt=qcow2, size=6144000 kB > Wed, 06 Aug 2008 03:40:05 ERROR virConnectOpen() failed > Traceback (most recent call last): > File "/usr/sbin/virt-install", line 496, in > main() > File "/usr/sbin/virt-install", line 345, in main > conn = cli.getConnection(options.connect) > File "/usr/lib/python2.5/site-packages/virtinst/cli.py", line 92, > in getConnection > return libvirt.open(connect) > File "/usr/lib64/python2.5/site-packages/libvirt.py", line 135, in open > if ret is None:raise libvirtError('virConnectOpen() failed') > libvirtError: virConnectOpen() failed > > Who knows why it is? > > Then I try to run below command and pass for me. > > #bash create-wui-appliance.sh -d "`pwd`" -v > > Also, I can start 'developer' successfully. > But below command fails for me: # virt-viewer developer (virt-viewer:13982): Gtk-WARNING **: cannot open display: > > 2) I tried to invoke the main Virt page, but it fails for me. > > #ssh -Y root at 192.168.50.2 firefox (or ssh -fY root at 192.168.50.2 > firefox -no-remote) > ssh: connect to host 192.168.50.2 port 22: No route to host > > What's the ip refer to? Is it refer to the ip address of my own machine? > It still fails for me by trying conncect to myself. > > Here is the dummybridge output for my own machine. > > dummybridge Link encap:Ethernet HWaddr 00:FF:99:97:1A:8F > inet addr:192.168.50.1 Bcast:192.168.50.255 Mask:255.255.255.0 > inet6 addr: fe80::3cb0:b6ff:feed:cab/64 Scope:Link > UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 > RX packets:0 errors:0 dropped:0 overruns:0 frame:0 > TX packets:26 errors:0 dropped:0 overruns:0 carrier:0 > collisions:0 txqueuelen:0 > RX bytes:0 (0.0 b) TX bytes:4427 (4.3 KiB) > > #ssh -fY root at 192.168.50.1 firefox -no-remote > Error: no display specified > > Does this fail because of putty? The set up machine isn't on my > hand, I connect to it > by putty. Do I have to install it on my local machine? > > Here is the info of Dependency RPM version on my machine: > > kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > kvm-65-1.fc9.x86_64 > libvirt-0.4.1-4.fc9.x86_64 > virt-manager-0.5.4-2.fc9.x86_64 > virt-viewer-0.0.3-1.fc9.x86_64 > > Thanks! > > > > Perry_______________________________________________ > 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 pmyers at redhat.com Wed Aug 6 13:34:35 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 09:34:35 -0400 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: References: Message-ID: <4899A86B.3000307@redhat.com> Guo Lian Yun wrote: > Thanks for your great help! You're welcome :) > I installed successfully all dependency RPM by yum. But it report > error when > I'm installing Ovirt. > > 1) It fails for me by running create-wui-appliance.sh to create virtual > server. > > #bash create-wui-appliance.sh -t > http://download.fedora.redhat.com/pub/fedora/linux/releases/9/Fedora/x86_64/os/ > -k http://ovirt.org/download/wui-rel-x86_64.ks -v > Domain node3 defined from /tmp/tmp.b3q8JWVRoM > > Domain node4 defined from /tmp/tmp.jtalRXJK3k > > Domain node5 defined from /tmp/tmp.B8zlcSB1Cj > > Network dummybridge destroyed > > Network dummybridge has been undefined > > Network dummybridge defined from /tmp/tmp.s8cSkpypob > > Network dummybridge started > > Network dummybridge marked as autostarted > > Formatting '/var/lib/libvirt/images/developer.img', fmt=qcow2, > size=6144000 kB > Wed, 06 Aug 2008 03:40:05 ERROR virConnectOpen() failed > Traceback (most recent call last): > File "/usr/sbin/virt-install", line 496, in > main() > File "/usr/sbin/virt-install", line 345, in main > conn = cli.getConnection(options.connect) > File "/usr/lib/python2.5/site-packages/virtinst/cli.py", line 92, in > getConnection > return libvirt.open(connect) > File "/usr/lib64/python2.5/site-packages/libvirt.py", line 135, in open > if ret is None:raise libvirtError('virConnectOpen() failed') > libvirtError: virConnectOpen() failed > > Who knows why it is? Hmm. The command above should work to create a completely new appliance image in the current directory. Some logs to look at would be: /root/.virtinst/virt-install.log /var/log/libvirt/qemu/*.log Attach these logs to a mail message and I'll take a look at them. They might reveal something useful. The command you're using above is only necessary if you want to create the appliance from scratch. If you have downloaded the appliance tar.gz file from ovirt.org, then you can simply just use it. No need to recreate it from the RPM repositories. > Then I try to run below command and pass for me. > > #bash create-wui-appliance.sh -d "`pwd`" -v > > Also, I can start 'developer' successfully. Good. This is the command that just reuses the existing .img file that is downloaded from ovirt.org. > > 2) I tried to invoke the main Virt page, but it fails for me. > > #ssh -Y root at 192.168.50.2 firefox (or ssh -fY root at 192.168.50.2 firefox > -no-remote) > ssh: connect to host 192.168.50.2 port 22: No route to host > > What's the ip refer to? Is it refer to the ip address of my own machine? > It still fails for me by trying conncect to myself. This is sshing to the appliance which has an IP address of 192.168.50.2 on the dummybridge network. If you just started the appliance when you ran that command, the networking stack might not have been up yet. Wait a few minutes and try again... Also, give me the output of: virsh list --all virsh dumpxml developer > Here is the dummybridge output for my own machine. > > dummybridge Link encap:Ethernet HWaddr 00:FF:99:97:1A:8F > inet addr:192.168.50.1 Bcast:192.168.50.255 Mask:255.255.255.0 > inet6 addr: fe80::3cb0:b6ff:feed:cab/64 Scope:Link > UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 > RX packets:0 errors:0 dropped:0 overruns:0 frame:0 > TX packets:26 errors:0 dropped:0 overruns:0 carrier:0 > collisions:0 txqueuelen:0 > RX bytes:0 (0.0 b) TX bytes:4427 (4.3 KiB) > > #ssh -fY root at 192.168.50.1 firefox -no-remote > Error: no display specified This is sshing to your physical host running Fedora 9, so this is not going to work. > Does this fail because of putty? The set up machine isn't on my hand, I > connect to it > by putty. Do I have to install it on my local machine? The command is trying to run firefox on a remote display using X forwarding. If you are running these commands in Putty on a Windows box none of this will work since Windows can't natively display X applications. The easiest way to do this is to log into the local console of the host that is running F9 and the developer appliance. From the local console open up an xterm and run the ssh commands from there. Perry From pmyers at redhat.com Wed Aug 6 13:36:19 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 09:36:19 -0400 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: References: Message-ID: <4899A8D3.4060507@redhat.com> Guo Lian Yun wrote: > But below command fails for me: > # virt-viewer developer > (virt-viewer:13982): Gtk-WARNING **: cannot open display: In the previous email you said you were using Putty. You need to be on a Linux host running X Windows in order for the virt-viewer application to display. Let me know if that resolves your problem. Perry From sseago at redhat.com Wed Aug 6 13:56:27 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 06 Aug 2008 09:56:27 -0400 Subject: [Ovirt-devel] [PATCH] set RAILS_ENV for all services in common /etc/sysconfig/ovirt-rails In-Reply-To: <1217973052-20639-1-git-send-email-apevec@redhat.com> References: <1217973052-20639-1-git-send-email-apevec@redhat.com> Message-ID: <4899AD8B.9030809@redhat.com> Alan Pevec wrote: > diff --git a/wui/conf/ovirt-mongrel-rails b/wui/conf/ovirt-mongrel-rails > index 7cfaf2d..498e2bf 100755 > --- a/wui/conf/ovirt-mongrel-rails > +++ b/wui/conf/ovirt-mongrel-rails > @@ -29,9 +31,9 @@ RETVAL=0 > start() { > echo -n "Starting ovirt-mongrel-rails: " > > - RAILS_ENV=$RAILS_ENVIRONMENT $REINDEX_PROG > + RAILS_ENV=$RAILS_EN $REINDEX_PROG > Typo here? $RAILS_EN should be $RAILS_ENV > $MONGREL_PROG start -c $OVIRT_DIR -l $MONGREL_LOG -P $MONGREL_PID \ > - -a $ADDR -e $RAILS_ENVIRONMENT --user $USER --group $GROUP \ > + -a $ADDR -e $RAILS_ENV --user $USER --group $GROUP \ > -d --prefix=$PREFIX > RETVAL=$? > if [ $RETVAL -eq 0 ] && touch $MONGREL_LOCKFILE ; then > Scott From apevec at redhat.com Wed Aug 6 16:07:31 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 06 Aug 2008 18:07:31 +0200 Subject: [Ovirt-devel] [PATCH] set RAILS_ENV for all services in common /etc/sysconfig/ovirt-rails In-Reply-To: <4899AD8B.9030809@redhat.com> References: <1217973052-20639-1-git-send-email-apevec@redhat.com> <4899AD8B.9030809@redhat.com> Message-ID: <4899CC43.6040309@redhat.com> Scott Seago wrote: > Alan Pevec wrote: >> diff --git a/wui/conf/ovirt-mongrel-rails b/wui/conf/ovirt-mongrel-rails >> index 7cfaf2d..498e2bf 100755 >> --- a/wui/conf/ovirt-mongrel-rails >> +++ b/wui/conf/ovirt-mongrel-rails >> @@ -29,9 +31,9 @@ RETVAL=0 >> start() { >> echo -n "Starting ovirt-mongrel-rails: " >> >> - RAILS_ENV=$RAILS_ENVIRONMENT $REINDEX_PROG >> + RAILS_ENV=$RAILS_EN $REINDEX_PROG >> > Typo here? $RAILS_EN should be $RAILS_ENV yes, just to see if you're paying attention :) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-set-RAILS_ENV-for-all-oVirt-services-in-etc-sysconf.patch Type: text/x-patch Size: 5629 bytes Desc: not available URL: From mmorsi at redhat.com Wed Aug 6 16:34:01 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 06 Aug 2008 12:34:01 -0400 Subject: [Ovirt-devel] Re: [PATCH] oVirt / RRD Test Data In-Reply-To: <489920AA.9090208@redhat.com> References: <4898DD04.8070705@redhat.com> <489920AA.9090208@redhat.com> Message-ID: <4899D279.6030204@redhat.com> mark wagner wrote: > > > Mohammed Morsi wrote: >> Attached is my now functional ruby script to generate test / demo rrd >> data and the oVirt patch required to get it working (a few small code >> tweaks / fixes and a few fixture modifications). >> >> 1. To generate the data data simply run `ruby demo-rrd-data.rb`. >> There are a few variables at the top of the script which you can >> tweak to configure things like nodes generated, cpu's per node, >> interval in seconds between data points, etc. >> 2. To see the test data, you need to be running rails in the testing >> environment and have a populated ovirt_test db. This can be >> accomplished by running the tests once (eg 'rake test') >> 3. Alot of data needs to be generated, thus the script is slow (at >> least on my system), and it isn't yet set to generate the number of >> data points in a time period that collectd does / oVirt is expecting >> (change the $interval variable at the top of the script from 60 to 10 >> to do this, but this will be all the more slower). Since the graphs >> require more data points than it is currently getting, they appear a >> bit discrete. Perhaps we can correct this and run it on a faster >> machine and just send out and use the data set generated. >> Alternatively, I could disable some of the algorithms used to >> generated data and simply reuse data points for some of the graphs if >> series repetition is not a problem. >> >> -Mo >> > > Mo > > Looks like the script ran for a while. It generated the device dirs but > it did not generate the .rrd files. So I have > /var/lib/collectd/rrd-test/node0/cpu-0 but it is an empty directory. > > Debugging now... > > -mark Hmm, weird, I haven't run into this issue. The script doesn't depend on any external modules so theoretically you should be good if you have sufficient permissions to write to the dirs and ruby / rrdtool installed. Probably the easiest thing to do to help debug would to just put a 'print command + "\n"' on line 289 right before each command is executed to see whats being done. -Mo From slinabery at redhat.com Wed Aug 6 16:50:19 2008 From: slinabery at redhat.com (Steve Linabery) Date: Wed, 6 Aug 2008 11:50:19 -0500 Subject: [Ovirt-devel] [PATCH] set RAILS_ENV for all services in common /etc/sysconfig/ovirt-rails In-Reply-To: <1217973052-20639-1-git-send-email-apevec@redhat.com> References: <1217973052-20639-1-git-send-email-apevec@redhat.com> Message-ID: <20080806165019.GA23308@redhat.com> On Tue, Aug 05, 2008 at 11:50:52PM +0200, Alan Pevec wrote: > Signed-off-by: Alan Pevec > --- > wui/conf/ovirt-host-browser | 4 ++++ > wui/conf/ovirt-host-collect | 4 ++++ > wui/conf/ovirt-host-status | 4 ++++ > wui/conf/ovirt-mongrel-rails | 8 +++++--- > wui/conf/ovirt-mongrel-rails.sysconf | 4 ---- > wui/conf/ovirt-rails.sysconf | 3 +++ > wui/conf/ovirt-taskomatic | 4 ++++ > wui/ovirt-wui.spec | 2 ++ > 8 files changed, 26 insertions(+), 7 deletions(-) > create mode 100644 wui/conf/ovirt-rails.sysconf > > diff --git a/wui/conf/ovirt-host-browser b/wui/conf/ovirt-host-browser > index 5e7fdae..892b155 100755 > --- a/wui/conf/ovirt-host-browser > +++ b/wui/conf/ovirt-host-browser > @@ -8,6 +8,10 @@ > # ovirt VM manager. > # > > +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails > + > +export RAILS_ENV="${RAILS_ENV:-production}" > + > DAEMON=/usr/share/ovirt-wui/host-browser/host-browser.rb > > . /etc/init.d/functions > diff --git a/wui/conf/ovirt-host-collect b/wui/conf/ovirt-host-collect > index 2d8ffcd..63e667a 100755 > --- a/wui/conf/ovirt-host-collect > +++ b/wui/conf/ovirt-host-collect > @@ -8,6 +8,10 @@ > # ovirt VM manager. > # > > +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails > + > +export RAILS_ENV="${RAILS_ENV:-production}" > + > DAEMON=/usr/share/ovirt-wui/host-collect/host-collect.rb > > . /etc/init.d/functions > diff --git a/wui/conf/ovirt-host-status b/wui/conf/ovirt-host-status > index 7804163..1757295 100755 > --- a/wui/conf/ovirt-host-status > +++ b/wui/conf/ovirt-host-status > @@ -8,6 +8,10 @@ > # ovirt VM manager. > # > > +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails > + > +export RAILS_ENV="${RAILS_ENV:-production}" > + > DAEMON=/usr/share/ovirt-wui/host-status/host-status.rb > > . /etc/init.d/functions > diff --git a/wui/conf/ovirt-mongrel-rails b/wui/conf/ovirt-mongrel-rails > index 7cfaf2d..498e2bf 100755 > --- a/wui/conf/ovirt-mongrel-rails > +++ b/wui/conf/ovirt-mongrel-rails > @@ -8,13 +8,15 @@ > # ovirt VM manager. > # > > +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails > + > [ -r /etc/sysconfig/ovirt-mongrel-rails ] && . /etc/sysconfig/ovirt-mongrel-rails > > +RAILS_ENV="${RAILS_ENV:-production}" > OVIRT_DIR="${OVIRT_DIR:-/usr/share/ovirt-wui}" > MONGREL_LOG="${MONGREL_LOG:-/var/log/ovirt-wui/mongrel.log}" > MONGREL_PID="${MONGREL_PID:-/var/run/ovirt-wui/mongrel.pid}" > MONGREL_LOCKFILE="${MONGREL_LOCKFILE:-/var/lock/subsys/ovirt-wui}" > -RAILS_ENVIRONMENT="${RAILS_ENVIRONMENT:-production}" > USER="${USER:-ovirt}" > GROUP="${GROUP:-ovirt}" > PREFIX="${PREFIX:-/ovirt}" > @@ -29,9 +31,9 @@ RETVAL=0 > start() { > echo -n "Starting ovirt-mongrel-rails: " > > - RAILS_ENV=$RAILS_ENVIRONMENT $REINDEX_PROG > + RAILS_ENV=$RAILS_EN $REINDEX_PROG > $MONGREL_PROG start -c $OVIRT_DIR -l $MONGREL_LOG -P $MONGREL_PID \ > - -a $ADDR -e $RAILS_ENVIRONMENT --user $USER --group $GROUP \ > + -a $ADDR -e $RAILS_ENV --user $USER --group $GROUP \ > -d --prefix=$PREFIX > RETVAL=$? > if [ $RETVAL -eq 0 ] && touch $MONGREL_LOCKFILE ; then > diff --git a/wui/conf/ovirt-mongrel-rails.sysconf b/wui/conf/ovirt-mongrel-rails.sysconf > index 80ac0e6..ce43383 100644 > --- a/wui/conf/ovirt-mongrel-rails.sysconf > +++ b/wui/conf/ovirt-mongrel-rails.sysconf > @@ -10,10 +10,6 @@ > # location of the file containing running process ID > #MONGREL_PID=/var/run/ovirt-wui/mongrel.pid > > -# sets ruby on Rails environment / mode of operation > -# http://wiki.rubyonrails.org/rails/pages/Environments > -#RAILS_ENVIRONMENT=production > - > # user and group under which Rails application runs > #USER=ovirt > #GROUP=ovirt > diff --git a/wui/conf/ovirt-rails.sysconf b/wui/conf/ovirt-rails.sysconf > new file mode 100644 > index 0000000..bc9e237 > --- /dev/null > +++ b/wui/conf/ovirt-rails.sysconf > @@ -0,0 +1,3 @@ > +# sets ruby on Rails environment / mode of operation > +# http://wiki.rubyonrails.org/rails/pages/Environments > +#RAILS_ENV=production > diff --git a/wui/conf/ovirt-taskomatic b/wui/conf/ovirt-taskomatic > index 2f9b255..2e548b4 100755 > --- a/wui/conf/ovirt-taskomatic > +++ b/wui/conf/ovirt-taskomatic > @@ -8,6 +8,10 @@ > # ovirt VM manager. > # > > +[ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails > + > +export RAILS_ENV="${RAILS_ENV:-production}" > + > DAEMON=/usr/share/ovirt-wui/task-omatic/taskomatic.rb > > . /etc/init.d/functions > diff --git a/wui/ovirt-wui.spec b/wui/ovirt-wui.spec > index 9dda52a..7746430 100644 > --- a/wui/ovirt-wui.spec > +++ b/wui/ovirt-wui.spec > @@ -79,6 +79,7 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/host-status.log > %{__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-taskomatic %{buildroot}%{_initrddir} > > # copy over all of the src directory... > @@ -171,6 +172,7 @@ fi > %{_initrddir}/ovirt-mongrel-rails > %{_initrddir}/ovirt-taskomatic > %config(noreplace) %{_sysconfdir}/sysconfig/ovirt-mongrel-rails > +%config(noreplace) %{_sysconfdir}/sysconfig/ovirt-rails > %config(noreplace) %{_sysconfdir}/httpd/conf.d/%{name}.conf > %doc > %attr(-, ovirt, ovirt) %{_localstatedir}/lib/%{name} > -- > 1.5.4.1 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel "${RAILS_ENV:-production}"--I haven't seen that syntax before: ${FOO:-bar} What does it do? My main comment: is there any reason to put the appliance *partly* in test mode, *partly* in production mode? If not, could we move this to a config file which all these init scripts can source? Thanks, Steve From jeffschroed at gmail.com Wed Aug 6 16:56:46 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Wed, 6 Aug 2008 09:56:46 -0700 Subject: [Ovirt-devel] [PATCH] set RAILS_ENV for all services in common /etc/sysconfig/ovirt-rails In-Reply-To: <20080806165019.GA23308@redhat.com> References: <1217973052-20639-1-git-send-email-apevec@redhat.com> <20080806165019.GA23308@redhat.com> Message-ID: On Wed, Aug 6, 2008 at 9:50 AM, Steve Linabery wrote: > "${RAILS_ENV:-production}"--I haven't seen that syntax before: ${FOO:-bar} What does it do? ${FOO:-bar} returns bar if FOO is unset or null. http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 Shameless plug to a blog entry about just that. http://digitalprognosis.com/blog/2008/06/13/increasing-command-line-productivity-in-the-bash-shell-pt-ii/ > My main comment: is there any reason to put the appliance *partly* in test mode, *partly* in production mode? > > If not, could we move this to a config file which all these init scripts can source? Seems like a good approach -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From slinabery at redhat.com Wed Aug 6 17:06:51 2008 From: slinabery at redhat.com (Steve Linabery) Date: Wed, 6 Aug 2008 12:06:51 -0500 Subject: [Ovirt-devel] [PATCH] set RAILS_ENV for all services in common /etc/sysconfig/ovirt-rails In-Reply-To: References: <1217973052-20639-1-git-send-email-apevec@redhat.com> <20080806165019.GA23308@redhat.com> Message-ID: <20080806170651.GB23308@redhat.com> On Wed, Aug 06, 2008 at 09:56:46AM -0700, Jeff Schroeder wrote: > On Wed, Aug 6, 2008 at 9:50 AM, Steve Linabery wrote: > > "${RAILS_ENV:-production}"--I haven't seen that syntax before: ${FOO:-bar} What does it do? > > ${FOO:-bar} returns bar if FOO is unset or null. > > http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 > > Shameless plug to a blog entry about just that. > http://digitalprognosis.com/blog/2008/06/13/increasing-command-line-productivity-in-the-bash-shell-pt-ii/ > > > My main comment: is there any reason to put the appliance *partly* in test mode, *partly* in production mode? > > > > If not, could we move this to a config file which all these init scripts can source? > > Seems like a good approach > My bad; Alan pointed out to me that his patch already does this. ACK. From sseago at redhat.com Wed Aug 6 17:45:26 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 6 Aug 2008 13:45:26 -0400 Subject: [Ovirt-devel] [PATCH] add permissions checks to search results. Message-ID: <1218044726-31953-1-git-send-email-sseago@redhat.com> To do so, I've enabled term-based parameters to each of the searchable types. At the query level, appending search_users:foo limits results to items viewable by user foo. This involved: 1) added :terms parameter to acts_as_xapian declaration for the method search_users 2) added search_users method to searchable models which return an array of usernames that have 'monitor' access 3) modified the acts_as_xapian plugin to handle prefix searches for which the object provides multiple values (since we have multiple users with access to each object) -- this is a change which may be suitable for upstream inclusion 4) When performing the search, search for "(#{terms}) AND search_users:#{user}" instead of simply searching for terms. This patch is dependant on the prior search denormalization patch. Signed-off-by: Scott Seago --- wui/src/app/controllers/hardware_controller.rb | 1 + wui/src/app/controllers/search_controller.rb | 16 ++++++++++------ wui/src/app/models/host.rb | 8 +++++++- wui/src/app/models/pool.rb | 8 +++++++- wui/src/app/models/storage_pool.rb | 6 +++++- wui/src/app/models/vm.rb | 8 +++++++- .../plugins/acts_as_xapian/lib/acts_as_xapian.rb | 6 +++++- 7 files changed, 42 insertions(+), 11 deletions(-) diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index 5f473db..019fdd8 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -110,6 +110,7 @@ class HardwareController < ApplicationController unless @can_view flash[:notice] = 'You do not have permission to view this Hardware Pool: redirecting to top level' redirect_to :action => 'list' + return end render :layout => 'selection' end diff --git a/wui/src/app/controllers/search_controller.rb b/wui/src/app/controllers/search_controller.rb index ca4fbb1..cc7e527 100644 --- a/wui/src/app/controllers/search_controller.rb +++ b/wui/src/app/controllers/search_controller.rb @@ -56,6 +56,11 @@ class SearchController < ApplicationController @models ||= [@model_param] end @user = get_login_user + #filter terms on permissions + filtered_terms = "search_users:#{@user}" + if @terms and !@terms.empty? + filtered_terms = "(#{@terms}) AND #{filtered_terms}" + end @page = params[:page].to_i @page ||= 1 @@ -63,12 +68,11 @@ class SearchController < ApplicationController @per_page ||= 20 @offset = (@page-1)*@per_page @results = ActsAsXapian::Search.new(@models, - @terms, - :offset => @offset, - :limit => @per_page, - :sort_by_prefix => nil, - :collapse_by_prefix => nil) - #FIXME filter on permissions + filtered_terms, + :offset => @offset, + :limit => @per_page, + :sort_by_prefix => nil, + :collapse_by_prefix => nil) end def results diff --git a/wui/src/app/models/host.rb b/wui/src/app/models/host.rb index fc3b236..5b32653 100644 --- a/wui/src/app/models/host.rb +++ b/wui/src/app/models/host.rb @@ -43,7 +43,8 @@ class Host < ActiveRecord::Base acts_as_xapian :texts => [ :hostname, :uuid, :hypervisor_type, :arch ], :values => [ [ :created_at, 0, "created_at", :date ], [ :updated_at, 1, "updated_at", :date ] ], - :terms => [ [ :hostname, 'H', "hostname" ] ] + :terms => [ [ :hostname, 'H', "hostname" ], + [ :search_users, 'U', "search_users" ] ] KVM_HYPERVISOR_TYPE = "KVM" @@ -88,4 +89,9 @@ class Host < ActiveRecord::Base def display_class "Host" end + + def search_users + hardware_pool.search_users + end + end diff --git a/wui/src/app/models/pool.rb b/wui/src/app/models/pool.rb index 1870027..6599c72 100644 --- a/wui/src/app/models/pool.rb +++ b/wui/src/app/models/pool.rb @@ -85,7 +85,8 @@ class Pool < ActiveRecord::Base end end - acts_as_xapian :texts => [ :name ] + acts_as_xapian :texts => [ :name ], + :terms => [ [ :search_users, 'U', "search_users" ] ] # this method lists pools with direct permission grants, but by default does # not include implied permissions (i.e. subtrees) @@ -246,6 +247,11 @@ class Pool < ActiveRecord::Base def display_class get_type_label end + + def search_users + permissions.collect {|perm| perm.uid} + end + protected def traverse_parents if id diff --git a/wui/src/app/models/storage_pool.rb b/wui/src/app/models/storage_pool.rb index 3e3f58f..460b49d 100644 --- a/wui/src/app/models/storage_pool.rb +++ b/wui/src/app/models/storage_pool.rb @@ -32,7 +32,8 @@ class StoragePool < ActiveRecord::Base validates_presence_of :ip_addr, :hardware_pool_id - acts_as_xapian :texts => [ :ip_addr, :target, :export_path, :type ] + acts_as_xapian :texts => [ :ip_addr, :target, :export_path, :type ], + :terms => [ [ :search_users, 'U', "search_users" ] ] ISCSI = "iSCSI" NFS = "NFS" STORAGE_TYPES = { ISCSI => "Iscsi", @@ -60,4 +61,7 @@ class StoragePool < ActiveRecord::Base "Storage Pool" end + def search_users + hardware_pool.search_users + end end diff --git a/wui/src/app/models/vm.rb b/wui/src/app/models/vm.rb index 34d5bf4..9ac24d9 100644 --- a/wui/src/app/models/vm.rb +++ b/wui/src/app/models/vm.rb @@ -31,7 +31,8 @@ class Vm < ActiveRecord::Base validates_presence_of :uuid, :description, :num_vcpus_allocated, :memory_allocated_in_mb, :memory_allocated, :vnic_mac_addr - acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ] + acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], + :terms => [ [ :search_users, 'U', "search_users" ] ] BOOT_DEV_HD = "hd" BOOT_DEV_NETWORK = "network" @@ -193,6 +194,11 @@ class Vm < ActiveRecord::Base def display_class "VM" end + + def search_users + vm_resource_pool.search_users + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/wui/src/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb b/wui/src/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb index eda0b1b..566fe82 100644 --- a/wui/src/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb +++ b/wui/src/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb @@ -523,6 +523,8 @@ module ActsAsXapian value.utc.strftime("%Y%m%d") elsif type == :boolean value ? true : false + elsif type == :array + (value.class == Array) ? value.collect {|x| x.to_s} : value.to_s else value.to_s end @@ -549,7 +551,9 @@ module ActsAsXapian doc.add_term("I" + doc.data) if self.xapian_options[:terms] for term in self.xapian_options[:terms] - doc.add_term(term[1] + xapian_value(term[0])) + xapian_value(term[0], :array).each do |val| + doc.add_term(term[1] + val) + end end end if self.xapian_options[:values] -- 1.5.5.1 From imain at redhat.com Wed Aug 6 04:47:48 2008 From: imain at redhat.com (Ian Main) Date: Tue, 5 Aug 2008 21:47:48 -0700 Subject: [Ovirt-devel] Serial Console Support Message-ID: <20080805214748.0bc5dee1@tp.mains.net> So, I've come up with a patch to add serial console support, however there's a catch :). The best way to do it would be to make livecd-creator support the --append option to the bootloader kickstart config. I spent a fair bit of time digging around it today so it wouldn't be hard for me to do this and get it into livecd-tools. As an immediate fix, the patch below will make consoles work for pxe and static-flash images which will would catch 99.9% of users at this point I think. So I'm just wondering how to proceed.. I'm guessing we can push this now and then I'll get a patch into livecd-creator for proper support. This patch also makes guests created with a serial console. --- Subject: [PATCH] Add serial console support This patch adds serial console support to the node and to guest VMs. With this patch, nodes in developer mode now support a serial console and can be connected to with the virsh console command. This only supports console for pxe boot as livecd-creator does not support appending arbitrary strings to the kernel command line. --- ovirt-host-creator/common-install.ks | 4 ++++ ovirt-host-creator/ovirt-flash-static | 2 +- ovirt-host-creator/ovirt-pxe | 4 +++- wui/src/task-omatic/task_vm.rb | 5 +++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ovirt-host-creator/common-install.ks b/ovirt-host-creator/common-install.ks index badc847..8a7aefa 100644 --- a/ovirt-host-creator/common-install.ks +++ b/ovirt-host-creator/common-install.ks @@ -6,6 +6,10 @@ selinux --disabled firewall --disabled part / --size 550 --fstype ext2 services --enabled=ntpd,ntpdate,collectd,iptables,network +# FIXME: I think the right way to do this is to modify +# livecd-creator to honor the --append settings. For now +# this is done for pxe and flash in different ways. +#bootloader --timeout=1 --append="console=tty0 console=ttyS0,19200n8" bootloader --timeout=1 rootpw --iscrypted Xa8QeYfWrtscM diff --git a/ovirt-host-creator/ovirt-flash-static b/ovirt-host-creator/ovirt-flash-static index be9c7c9..4a37433 100755 --- a/ovirt-host-creator/ovirt-flash-static +++ b/ovirt-host-creator/ovirt-flash-static @@ -54,7 +54,7 @@ rm -f $USBTMP/isolinux.bin mv $USBTMP/isolinux.cfg $USBTMP/extlinux.conf LABEL=`echo $ISO | cut -d'.' -f1 | cut -c-16` -sed -i -e "s/ *append.*/ append initrd=initrd.img root=LABEL=$LABEL ro/" $USBTMP/extlinux.conf +sed -i -e "s/ *append.*/ append initrd=initrd.img root=LABEL=$LABEL console=tty0 console=ttyS0,19200n8 ro/" $USBTMP/extlinux.conf extlinux -i $USBTMP diff --git a/ovirt-host-creator/ovirt-pxe b/ovirt-host-creator/ovirt-pxe index facca5b..89acaee 100755 --- a/ovirt-host-creator/ovirt-pxe +++ b/ovirt-host-creator/ovirt-pxe @@ -29,6 +29,8 @@ test $( id -u ) -ne 0 && die "$ME must run as root" livecd-iso-to-pxeboot $ISO -# append BOOTIF with PXE MAC info f=tftpboot/pxelinux.cfg/default +# Setup dual consoles. +sed -i 's/APPEND.*/& console=tty0 console=ttyS0,19200n8/' $f +# append BOOTIF with PXE MAC info grep -q 'IPAPPEND 2' $f || sed -i '/KERNEL/a \\tIPAPPEND 2' $f diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index 34749e0..d7f0869 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -80,6 +80,11 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, 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"}) + serial = Element.new("serial") + serial.add_attribute("type", "pty") + serial.add_element("target", {"port" => "0"}) + doc.root.elements["devices"] << serial + return doc end -- 1.5.5.1 From mwagner at redhat.com Wed Aug 6 18:18:49 2008 From: mwagner at redhat.com (mark wagner) Date: Wed, 06 Aug 2008 14:18:49 -0400 Subject: [Ovirt-devel] Re: [PATCH] oVirt / RRD Test Data In-Reply-To: <4898DD04.8070705@redhat.com> References: <4898DD04.8070705@redhat.com> Message-ID: <4899EB09.9070206@redhat.com> Mohammed Morsi wrote: > Attached is my now functional ruby script to generate test / demo rrd > data and the oVirt patch required to get it working (a few small code > tweaks / fixes and a few fixture modifications). > > 1. To generate the data data simply run `ruby demo-rrd-data.rb`. There > are a few variables at the top of the script which you can tweak to > configure things like nodes generated, cpu's per node, interval in > seconds between data points, etc. > 2. To see the test data, you need to be running rails in the testing > environment and have a populated ovirt_test db. This can be > accomplished by running the tests once (eg 'rake test') > 3. Alot of data needs to be generated, thus the script is slow (at > least on my system), and it isn't yet set to generate the number of > data points in a time period that collectd does / oVirt is expecting > (change the $interval variable at the top of the script from 60 to 10 > to do this, but this will be all the more slower). Since the graphs > require more data points than it is currently getting, they appear a > bit discrete. Perhaps we can correct this and run it on a faster > machine and just send out and use the data set generated. > Alternatively, I could disable some of the algorithms used to > generated data and simply reuse data points for some of the graphs if > series repetition is not a problem. > > -Mo NACK for several reasons: 1) the test generator is not working for me, still debugging. Could be "cockpit error", but it seems pretty simple and standalone... 2) Changes to graph_controller.rb, Should not be changing the ints to floats, we want the values as ints, that's why it was done. 3) changes to Stats.rb - Stats is intentionally decoupled from RAILS. One of the reasons is so that stats can be run on a separate machine in order to help offload the ovirt server. While this functionality is not yet implemented, your proposed change will cause a dependency. The change also rules out low-level stats tests being run correctly outside of the RAILS env. I will look at adding a hook in Stats to be able to specify regular or test mode, defaulting to regular -mark From jguiditt at redhat.com Wed Aug 6 22:48:11 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 06 Aug 2008 18:48:11 -0400 Subject: [Ovirt-devel] [PATCH] Sprint 5 tree widget rewrite (first draft) Message-ID: <1218062891.4879.26.camel@localhost.localdomain> The tree widget we have been using was overly complex and not meeting our needs for navigation, so I rewrote it this sprint (though it is not truly a widget right now, have to put off that step for a bit). This ended up being much more complex than I expected for numerour reasons, not the least of which was trying to figure out a cleaner way to generate the html for the nav tree. I ended up settling, at least for now, on the jTemplate plugin for jQuery. Also fixed in this patch is the dual ajax call that was happening with the old widget, so it should now _only_ hit /tree/fetch_json. Bumped the time up to every 10 seconds for refresh as well. There may be some tweaking needed in the css, but that is much simplified. I did not have time to apply this to popup trees yet, but it should be relatively straightforward. Once that is done, we can remove a couple javascript files from the old treeview. -j -------------- next part -------------- A non-text attachment was scrubbed... Name: s5-tree.patch Type: application/mbox Size: 56996 bytes Desc: not available URL: From dlutter at redhat.com Thu Aug 7 00:00:14 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 6 Aug 2008 17:00:14 -0700 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API Message-ID: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Here are a few patches that implement the beginnings of an API for OVirt. They cover handling hosts and storage and hardware pools so far. This patch is mostly meant for discussion, since there's still a few things that need to be tweaked. If you're only interested in seeing how the API is used, jump straight to patch 5/5; it contains all the client side (example) code. To see what gets sent across the wire, you can request stuff like /ovirt/hosts or /ovirt/hardware_pools?path=/default/foo/bar in your browser and admire the XML. In doing this, it became apparent that there are various things in the existing OVirt server code that need to be adapted to better accomodate the API: * Enable simple HTTP authentication It should be possible for people to run scripts without hooking the client into the krb5 infrastructure; instead, scripts should be able to use simple username/password authentication. * Fold the Rest:: controllers into the main controllers Once we have non-krb5 authn, the controllers I wrote to support the API should be folded into the controllers that drive the WUI. There's some annoying duplication of code right now, and having just one controller for each aspect of the API/WUI would eliminate that. I don't think there's a need to have 'base' controllers with separate subclasses for WUI and API - for the most part, WUI and API just differ in how the results are rendered, and should be able to have exactly the same logic to load/manipulate model objects. But as with anything, the devil will be in the details. One sideeffect of this would be that the API is accessible under /ovirt, and not /ovirt/rest as is the case with these patches. * Rename some of the WUI controllers The REST support (ActionController::Resources) is pretty specific about the names of controllers; for example, hardware pools in the API are served by a HardwarePoolsController, not a HardwareController as in the WUI. It's possible to work around that, but ultimately easiest if we just follow the preferred naming conventions of Rails. * Reconcile some of the differences in parameter naming With REST, the names of attributes in objects maps directly to parameter names in certain requests, and the naming in the WUI sometimes differs from that. For example, when creating a hardware pool, the WUI sends parameters for that in params[:pool], whereas the API sends it in params[:hardware_pool] Again, this can be overcome on a case-by-case basis, but it's easiest if the WUI uses the naming conventions preferred by the Rails' REST support. * Too many attributes are dumped into the XML I haven't tried to prune the attributes that are sent across the wire for each object to a reasonable subset. For example, right now attributes of very questionable use to an API user, like updated_at, lock_version, lft, rgt etc. are all sent. * Where's the CLI ? I intended to accompany this with a nice clean CLI client that you can use for lots of things. Even with the current subset of the API, there are lots of interesting queries and actions one could perform, even with the current subset of the API. I'd like to avoid offering a million options from the CLI though. If anybody has pressing needs for CLI functionality, let me know, otherwise, I'll start making stuff up. Especially for querying, it's questionable if CLI access to query functionality is useful at all - if you want to see all the hosts in hardware pool 7, you can just wget http://management.priv.ovirt.org:3000/ovirt/rest/hardware_pools/7/hosts and get a nice XML document. Is there a need for a CLI client for interactive use that lets you avoid using a browser ? Not sure how important that is. From dlutter at redhat.com Thu Aug 7 00:00:15 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 6 Aug 2008 17:00:15 -0700 Subject: [Ovirt-devel] [PATCH 1/5] API access to hosts In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <1218067219-20169-2-git-send-email-dlutter@redhat.com> - List hosts and filter the list by state, arch, hostname, uuid and hardware_pool_id - Retrieve an individual host The routing maps this to /ovirt/rest/hosts and /ovirt/rest/hosts/#{id} --- wui/src/app/controllers/rest/hosts_controller.rb | 26 ++++++++++++++++++++ wui/src/config/routes.rb | 5 ++++ .../test/functional/rest/hosts_controller_test.rb | 8 ++++++ 3 files changed, 39 insertions(+), 0 deletions(-) create mode 100644 wui/src/app/controllers/rest/hosts_controller.rb create mode 100644 wui/src/test/functional/rest/hosts_controller_test.rb diff --git a/wui/src/app/controllers/rest/hosts_controller.rb b/wui/src/app/controllers/rest/hosts_controller.rb new file mode 100644 index 0000000..2e85f65 --- /dev/null +++ b/wui/src/app/controllers/rest/hosts_controller.rb @@ -0,0 +1,26 @@ +class Rest::HostsController < ApplicationController + + EQ_ATTRIBUTES = [ :state, :arch, :hostname, :uuid, + :hardware_pool_id ] + def index + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "#{attr} = :#{attr}" + end + end + @hosts = Host.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") + respond_to do |format| + format.xml { render :xml => @hosts.to_xml } + end + end + + def show + @host = Host.find(params[:id]) + respond_to do |format| + format.xml { render :xml => @host.to_xml } + end + end +end diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb index a93ba4b..e7aa0be 100644 --- a/wui/src/config/routes.rb +++ b/wui/src/config/routes.rb @@ -19,6 +19,11 @@ ActionController::Routing::Routes.draw do |map| + map.namespace(:rest) do |rest| + rest.resources :hosts + end + + # The priority is based upon order of creation: first created -> highest priority. # Sample of regular route: diff --git a/wui/src/test/functional/rest/hosts_controller_test.rb b/wui/src/test/functional/rest/hosts_controller_test.rb new file mode 100644 index 0000000..32e7931 --- /dev/null +++ b/wui/src/test/functional/rest/hosts_controller_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../../test_helper' + +class Rest::HostsControllerTest < ActionController::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 7 00:00:16 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 6 Aug 2008 17:00:16 -0700 Subject: [Ovirt-devel] [PATCH 2/5] API for storage pools In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <1218067219-20169-3-git-send-email-dlutter@redhat.com> - List and filter storage pools by ipaddr, path, target and hardware_pool_id - Show an individual storage pool - Create a new storage pool - Destroy a storage pool --- .../controllers/rest/storage_pools_controller.rb | 60 ++++++++++++++++++++ wui/src/config/routes.rb | 1 + 2 files changed, 61 insertions(+), 0 deletions(-) create mode 100644 wui/src/app/controllers/rest/storage_pools_controller.rb diff --git a/wui/src/app/controllers/rest/storage_pools_controller.rb b/wui/src/app/controllers/rest/storage_pools_controller.rb new file mode 100644 index 0000000..99eb158 --- /dev/null +++ b/wui/src/app/controllers/rest/storage_pools_controller.rb @@ -0,0 +1,60 @@ +class Rest::StoragePoolsController < ApplicationController + + EQ_ATTRIBUTES = [ :ip_addr, :export_path, :target, + :hardware_pool_id ] + + def index + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "#{attr} = :#{attr}" + end + end + + @storage = StoragePool.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") + + respond_to do |format| + format.xml { render :xml => @storage.to_xml } + end + end + + def show + @storage = StoragePool.find(params[:id]) + respond_to do |format| + format.xml { render :xml => @storage.to_xml } + end + end + + def create + # Somehow the attribute 'type' never makes it through + # Maybe because there is a (deprecated) Object.type ? + pool = params[:storage_pool] + type = pool[:storage_type] + pool.delete(:storage_type) + @storage_pool = StoragePool.factory(type, pool) + respond_to do |format| + if @storage_pool + if @storage_pool.save + format.xml { render :xml => @storage_pool, + :status => :created, + :location => rest_storage_pool_url(@storage_pool) } + else + format.xml { render :xml => @storage_pool.errors, + :status => :unprocessable_entity } + end + else + format.xml { render :xml => "Illegal storage type #{params[:storage_type]}", :status => :unprocessable_entity } + end + end + end + + def destroy + @storage_pool = StoragePool.find(params[:id]) + @storage_pool.destroy + respond_to do |format| + format.xml { head :ok } + end + end +end diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb index e7aa0be..993a885 100644 --- a/wui/src/config/routes.rb +++ b/wui/src/config/routes.rb @@ -21,6 +21,7 @@ ActionController::Routing::Routes.draw do |map| map.namespace(:rest) do |rest| rest.resources :hosts + rest.resources :storage_pools end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 7 00:00:17 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 6 Aug 2008 17:00:17 -0700 Subject: [Ovirt-devel] [PATCH 3/5] API for Hardware Pools In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <1218067219-20169-4-git-send-email-dlutter@redhat.com> - List and filter HW pools by name - CRUD of individual HW pool - Hosts and storgae pools for a HW pool are accessible as nested resources, e.g. /ovirt/hardware_pools/1/hosts?hostname=myhost.example.com --- .../controllers/rest/hardware_pools_controller.rb | 85 ++++++++++++++++++++ wui/src/config/routes.rb | 4 + 2 files changed, 89 insertions(+), 0 deletions(-) create mode 100644 wui/src/app/controllers/rest/hardware_pools_controller.rb diff --git a/wui/src/app/controllers/rest/hardware_pools_controller.rb b/wui/src/app/controllers/rest/hardware_pools_controller.rb new file mode 100644 index 0000000..49a8fcc --- /dev/null +++ b/wui/src/app/controllers/rest/hardware_pools_controller.rb @@ -0,0 +1,85 @@ +class Rest::HardwarePoolsController < ApplicationController + OPTS = { + :include => [ :storage_pools, :hosts, :quota ] + } + + EQ_ATTRIBUTES = [ :name ] + + def index + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "#{attr} = :#{attr}" + end + end + + @pools = HardwarePool.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") + + respond_to do |format| + format.xml { render :xml => @pools.to_xml(OPTS) } + end + end + + def show + @pool = HardwarePool.find(params[:id]) + respond_to do |format| + format.xml { render :xml => @pool.to_xml(OPTS) } + end + end + + def create + # FIXME: Why can't I just use HardwarePool.create here ? + # Shouldn't that find the parent_id param ? + @parent = Pool.find(params[:hardware_pool][:parent_id]) + @pool = HardwarePool.new(params[:hardware_pool]) + # FIXME: How do we check for errors here ? + @pool.create_with_parent(@parent) + respond_to do |format| + format.xml { render :xml => @pool.to_xml(OPTS), + :status => :created, + :location => rest_hardware_pool_url(@pool) } + end + end + + def update + # We allow updating direct attributes of the HW pool and moving + # hosts/storage into it here. + @pool = HardwarePool.find(params[:id]) + [:hosts, :storage_pools].each do |k| + objs = params[:hardware_pool].delete(k) + ids = objs.reject{ |obj| obj[:hardware_pool_id] == @pool.id}. + collect{ |obj| obj[:id] } + if ids.size > 0 + if k == :hosts + # FIXME: Why does move_hosts need an explicit pool_id ? + @pool.move_hosts(ids, @pool.id) + else + @pool.move_storage(ids, @pool.id) + end + end + end + + @pool.update_attributes(params[:hardware_pool]) + respond_to do |format| + if @pool.save + format.xml { render :xml => @pool.to_xml(OPTS), + :status => :created, + :location => rest_hardware_pool_url(@pool) } + else + format.xml { render :xml => @pool.errors, + :status => :unprocessable_entity } + end + end + end + + def destroy + @pool = HardwarePool.find(params[:id]) + @pool.destroy + respond_to do |format| + format.xml { head :ok } + end + end + +end diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb index 993a885..322d6f9 100644 --- a/wui/src/config/routes.rb +++ b/wui/src/config/routes.rb @@ -22,6 +22,10 @@ ActionController::Routing::Routes.draw do |map| map.namespace(:rest) do |rest| rest.resources :hosts rest.resources :storage_pools + rest.resources :hardware_pools do |pools| + pools.resources :hosts + pools.resources :storage_pools + end end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 7 00:00:18 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 6 Aug 2008 17:00:18 -0700 Subject: [Ovirt-devel] [PATCH 4/5] Find hardware_pool by path; search by parent_id In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <1218067219-20169-5-git-send-email-dlutter@redhat.com> Get individual hardware pool with a path, e.g. with a GET to /ovirt/hardware_pools?path=/default/foo/bar Get children of a hardware pool from /ovirt/hardware_pools?parent_id=1 Get specific child with /ovirt/hardware_pools?parent_id=1&name=foo --- .../controllers/rest/hardware_pools_controller.rb | 25 ++++++++++++------- wui/src/app/models/hardware_pool.rb | 12 +++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/wui/src/app/controllers/rest/hardware_pools_controller.rb b/wui/src/app/controllers/rest/hardware_pools_controller.rb index 49a8fcc..836d5f2 100644 --- a/wui/src/app/controllers/rest/hardware_pools_controller.rb +++ b/wui/src/app/controllers/rest/hardware_pools_controller.rb @@ -3,19 +3,26 @@ class Rest::HardwarePoolsController < ApplicationController :include => [ :storage_pools, :hosts, :quota ] } - EQ_ATTRIBUTES = [ :name ] + EQ_ATTRIBUTES = [ :name, :parent_id ] def index - conditions = [] - EQ_ATTRIBUTES.each do |attr| - if params[attr] - conditions << "#{attr} = :#{attr}" + if params[:path] + @pools = [] + pool = HardwarePool.find_by_path(params[:path]) + @pools << pool if pool + logger.info "POOLS: #{@pools.inspect}" + else + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "#{attr} = :#{attr}" + end end - end - @pools = HardwarePool.find(:all, - :conditions => [conditions.join(" and "), params], - :order => "id") + @pools = HardwarePool.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") + end respond_to do |format| format.xml { render :xml => @pools.to_xml(OPTS) } diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb index 276779f..249d744 100644 --- a/wui/src/app/models/hardware_pool.rb +++ b/wui/src/app/models/hardware_pool.rb @@ -97,4 +97,16 @@ class HardwarePool < Pool return {:total => total, :labels => labels} end + def self.find_by_path(path) + segs = path.split("/") + unless segs.shift.empty? + raise "Path must be absolute, but is #{path}" + end + if segs.shift == "default" + segs.inject(get_default_pool) do |pool, seg| + pool.sub_hardware_pools.find { |p| p.name == seg } if pool + end + end + end + end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 7 00:00:19 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 6 Aug 2008 17:00:19 -0700 Subject: [Ovirt-devel] [PATCH 5/5] Sample code that shows how to use the API In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <1218067219-20169-6-git-send-email-dlutter@redhat.com> --- wui/client/bin/ovirt-cli | 104 ++++++++++++++++++++++++++++++++++++++++++++++ wui/client/lib/ovirt.rb | 63 ++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 0 deletions(-) create mode 100755 wui/client/bin/ovirt-cli create mode 100644 wui/client/lib/ovirt.rb diff --git a/wui/client/bin/ovirt-cli b/wui/client/bin/ovirt-cli new file mode 100755 index 0000000..cfb4697 --- /dev/null +++ b/wui/client/bin/ovirt-cli @@ -0,0 +1,104 @@ +#! /usr/bin/ruby + +# This is not really a CLI, it's just an example how +# the API can be used for a variety of tasks +# Eventually, this needs to be replaced by the real CLI + +# Run this on any machine that has activeresource installed. Since there is +# no support for authentication yet, you need to connect to Mongrel +# directly (and therefore expose port 3000 on the OVirt Server to the wider +# network) + +require 'pp' +require 'rubygems' +require 'activeresource' +require 'optparse' + +require 'ovirt' + +def move_random_host(hosts, pool) + host = hosts[rand(hosts.size)] + puts "Move #{host.hostname} to #{pool.name}" + pool.hosts << host + pool.save +end + +def element_path(obj) + "[#{obj.class.element_path(obj.id)}]" +end + +def print_pool(pool) + puts "\n\nPool #{pool.name}: #{pool.hosts.size} hosts, #{pool.storage_pools.size} storage pools #{element_path(pool)} " + puts "=" * 75 + pool.hosts.each do |h| + printf "%-36s %s\n", h.hostname, element_path(h) + end + pool.storage_pools.each do |sp| + type = sp.nfs? ? "NFS" : "iSCSI" + printf "%-5s %-30s %s\n", type, sp.label, element_path(sp) + end + puts "-" * 75 +end + +# Plumbing so we can find the OVirt server +# "http://ovirt.watzmann.net:3000/ovirt/rest" +OVirt::Base.site = ENV["OVIRT_SERVER"] +opts = OptionParser.new("ovirt-cli GLOBAL_OPTS") +opts.separator(" Run some things against an OVirt server") +opts.separator("") +opts.separator "Global options:" +opts.on("-s", "--server=URL", "The OVirt server. Since there is no auth\n" + + "#{" "*37}yet, must be the mongrel server port.\n" + + "#{" "*37}Overrides env var OVIRT_SERVER") do |val| + OVirt::Base.site = val +end + +opts.order(ARGV) + +unless OVirt::Base.site + $stderr.puts < defpool.id, + :name => "mypool" } ) +end + +# Move some hosts around +puts +if defpool.hosts.size > 1 + move_random_host(defpool.hosts, mypool) +elsif mypool.hosts.size > 0 + move_random_host(mypool.hosts, defpool) +end + +# Delete all storage pools for mypool and add a new one +mypool.storage_pools.each do |sp| + puts "Delete storage pool #{sp.id}" + sp.destroy +end + +storage_pool = OVirt::StoragePool.create( { :storage_type => "NFS", + :hardware_pool_id => mypool.id, + :ip_addr => "192.168.122.50", + :export_path => "/exports/pool1" } ) +puts "Created storage pool #{storage_pool.id}" + +# For some reason, mypool.reload doesn't work here +mypool = OVirt::HardwarePool.find_by_path("/default/mypool") +print_pool(mypool) diff --git a/wui/client/lib/ovirt.rb b/wui/client/lib/ovirt.rb new file mode 100644 index 0000000..48739f4 --- /dev/null +++ b/wui/client/lib/ovirt.rb @@ -0,0 +1,63 @@ +require 'pp' +require 'rubygems' +require 'activeresource' + +module OVirt + class Base < ActiveResource::Base ; end + + class HardwarePool < Base + def self.find_by_path(path) + find(:first, :params => { :path => path }) + end + + def self.default_pool + find(:first, :params => { :path => "/default" }) + end + end + + class StoragePool < Base + def iscsi? + attributes["type"] == "IscsiStoragePool" + end + + def nfs? + attributes["type"] == "NfsStoragePool" + end + + def label + if iscsi? + "#{ip_addr}:#{port}:#{target}" + elsif nfs? + "#{ip_addr}:#{export_path}" + else + raise "Unknown type #{attributes["type"]}" + end + end + end + + class IscsiStoragePool < StoragePool + def initialize(attributes = {}) + super(attributes.update( "type" => "IscsiStoragePool" )) + end + end + + class NfsStoragePool < StoragePool + def initialize(attributes = {}) + super(attributes.update( "type" => "NfsStoragePool" )) + end + end + + class Host < Base + def self.find_by_uuid(uuid) + find(:first, :params => { :uuid => uuid }) + end + + def self.find_by_hostname(hostname) + find(:first, :params => { :hostname => hostname }) + end + + def hardware_pool + HardwarePool.find(hardware_pool_id) + end + end +end -- 1.5.5.1 From apevec at redhat.com Thu Aug 7 00:55:35 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 7 Aug 2008 02:55:35 +0200 Subject: [Ovirt-devel] [PATCH] keep scp in Node image Message-ID: <1218070535-19835-1-git-send-email-apevec@redhat.com> - scp in Node image is helpful for debugging to add missing tools - reorganized Node image blacklist a bit Signed-off-by: Alan Pevec --- ovirt-host-creator/common-post.ks | 30 ++++++++++++++++++------------ 1 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index b21f52c..92255a0 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -53,7 +53,7 @@ $RPM perl perl-libs perl-Module-Pluggable perl-version \ # Remove additional RPMs forcefully $RPM gamin pm-utils kbd libuser passwd usermode \ - openssh-clients vbetool ConsoleKit hdparm \ + vbetool ConsoleKit hdparm \ efibootmgr krb5-workstation linux-atm-libs fedora-release-notes \ slang psmisc gdbm cryptsetup-luks pciutils mtools syslinux db4 \ wireless-tools radeontool cracklib-dicts cracklib @@ -115,23 +115,29 @@ blacklist="/boot /etc/alsa /etc/pki /usr/share/hwdata/MonitorsDB \ /usr/share/hwdata/videodrivers /usr/share/fedora-release \ /usr/share/tabset /usr/share/libvirt /usr/share/augeas/lenses/tests \ /usr/share/tc /usr/share/emacs /usr/share/info /usr/kerberos \ - /usr/src /usr/etc /usr/games /usr/include /usr/local /usr/lib{,64}/python2.5 \ - /usr/{,lib64}/tc /usr/lib{,64}/tls /usr/lib{,64}/sse2 /usr/lib{,64}/pkgconfig \ - /usr/lib{,64}/nss /usr/lib{,64}/X11 /usr/lib{,64}/games /usr/lib{,64}/alsa-lib \ - /usr/lib{,64}/fs/reiserfs /usr/lib{,64}/krb5 /usr/lib{,64}/hal /usr/lib{,64}/gio \ - /usr/bin/hal-device /usr/bin/hal-disable-polling \ + /usr/src /usr/etc /usr/games /usr/include /usr/local \ + /usr/sbin/dell*" +blacklist_lib="/usr/lib{,64}/python2.5 /usr/lib{,64}/gconv \ + /usr/{,lib64}/tc /usr/lib{,64}/tls /usr/lib{,64}/sse2 \ + /usr/lib{,64}/pkgconfig /usr/lib{,64}/nss /usr/lib{,64}/X11 \ + /usr/lib{,64}/games /usr/lib{,64}/alsa-lib /usr/lib{,64}/fs/reiserfs \ + /usr/lib{,64}/krb5 /usr/lib{,64}/hal /usr/lib{,64}/gio \ + /lib/terminfo/d /lib/terminfo/v /lib/terminfo/a \ + /lib/firmware /usr/lib/locale /usr/lib/syslinux" +blacklist_pango="/usr/lib{,64}/pango /usr/lib{,64}/libpango* \ + /etc/pango /usr/bin/pango*" +blacklist_hal="/usr/bin/hal-device /usr/bin/hal-disable-polling \ /usr/bin/hal-find-by-capability /usr/bin/hal-find-by-property \ /usr/bin/hal-is-caller-locked-out /usr/bin/hal-is-caller-privileged \ - /usr/bin/hal-lock /usr/bin/hal-set-property /usr/bin/hal-setup-keymap \ - /usr/sbin/dell* /lib/terminfo/d /lib/terminfo/v /lib/terminfo/a \ - /lib/firmware /usr/lib/locale /usr/lib/syslinux /usr/lib{,64}/gconv \ - /usr/lib{,64}/pango /usr/lib{,64}/libpango* /etc/pango /usr/bin/pango*" - + /usr/bin/hal-lock /usr/bin/hal-set-property /usr/bin/hal-setup-keymap" +blacklist_ssh="/usr/bin/sftp /usr/bin/slogin /usr/bin/ssh /usr/bin/ssh-add \ + /usr/bin/ssh-agent /usr/bin/ssh-copy-id /usr/bin/ssh-keyscan" docs_blacklist="/usr/share/omf /usr/share/gnome /usr/share/doc \ /usr/share/locale /usr/share/libthai /usr/share/man /usr/share/terminfo \ /usr/share/X11 /usr/share/i18n" -$RM $blacklist $docs_blacklist +$RM $blacklist $blacklist_lib $blacklist_pango $blacklist_hal $blacklist_ssh \ + $docs_blacklist echo "Cleanup empty directory structures in /usr/share" find /usr/share -type d -exec rmdir {} \; > /dev/null 2>&1 -- 1.5.4.1 From pmyers at redhat.com Thu Aug 7 01:11:51 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 21:11:51 -0400 Subject: [Ovirt-devel] [PATCH] intitial standalone Managed Node suport In-Reply-To: <1217583166-6268-1-git-send-email-apevec@redhat.com> References: <1217583166-6268-1-git-send-email-apevec@redhat.com> Message-ID: <489A4BD7.2010209@redhat.com> Alan Pevec wrote: > skip services which are not available in DNS SRV records This looks good as an initial cut, ACK. Perry From pmyers at redhat.com Thu Aug 7 01:12:29 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 21:12:29 -0400 Subject: [Ovirt-devel] [PATCH] keep scp in Node image In-Reply-To: <1218070535-19835-1-git-send-email-apevec@redhat.com> References: <1218070535-19835-1-git-send-email-apevec@redhat.com> Message-ID: <489A4BFD.20608@redhat.com> Alan Pevec wrote: > - scp in Node image is helpful for debugging to add missing tools > - reorganized Node image blacklist a bit Agree that having scp is useful in the node and the rest of NFC, so ACK. Perry From jeffschroed at gmail.com Thu Aug 7 01:15:38 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Wed, 6 Aug 2008 18:15:38 -0700 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: On Wed, Aug 6, 2008 at 5:00 PM, David Lutterkort wrote: ...snip... > > Is there a need for a CLI client for interactive use that lets you > avoid using a browser ? Not sure how important that is. It would be really nice. Not everyone wants to write everything in ruby. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From pmyers at redhat.com Thu Aug 7 01:20:36 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 21:20:36 -0400 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <489A4DE4.9060206@redhat.com> Jeff Schroeder wrote: > On Wed, Aug 6, 2008 at 5:00 PM, David Lutterkort wrote: > ...snip... >> Is there a need for a CLI client for interactive use that lets you >> avoid using a browser ? Not sure how important that is. > > It would be really nice. Not everyone wants to write everything in ruby. People could just write additional bindings for the API, say in Python or whatever. The only reason for an interactive CLI that avoids the browser would be for scripting in bash, right? Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From pmyers at redhat.com Thu Aug 7 01:20:53 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 21:20:53 -0400 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <489A4DF5.8090101@redhat.com> David Lutterkort wrote: > Here are a few patches that implement the beginnings of an API for > OVirt. They cover handling hosts and storage and hardware pools so > far. This patch is mostly meant for discussion, since there's still a few > things that need to be tweaked. > > If you're only interested in seeing how the API is used, jump straight to > patch 5/5; it contains all the client side (example) code. To see what gets > sent across the wire, you can request stuff like /ovirt/hosts or > /ovirt/hardware_pools?path=/default/foo/bar in your browser and admire the > XML. > > In doing this, it became apparent that there are various things in the > existing OVirt server code that need to be adapted to better accomodate the > API: > > * Enable simple HTTP authentication > > It should be possible for people to run scripts without hooking the > client into the krb5 infrastructure; instead, scripts should be able to > use simple username/password authentication. Jay is working on this. Once this is in the next branch you can modify your stuff to use the user/pass auth. [snip] > > * Where's the CLI ? > > I intended to accompany this with a nice clean CLI client that you can > use for lots of things. Even with the current subset of the API, there > are lots of interesting queries and actions one could perform, even > with the current subset of the API. I'd like to avoid offering a > million options from the CLI though. If anybody has pressing needs for > CLI functionality, let me know, otherwise, I'll start making stuff up. Start making stuff up for now :) Perry From jeffschroed at gmail.com Thu Aug 7 01:24:05 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Wed, 6 Aug 2008 18:24:05 -0700 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: <489A4DE4.9060206@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <489A4DE4.9060206@redhat.com> Message-ID: On Wed, Aug 6, 2008 at 6:20 PM, Perry N. Myers wrote: > Jeff Schroeder wrote: >> >> On Wed, Aug 6, 2008 at 5:00 PM, David Lutterkort >> wrote: >> ...snip... >>> >>> Is there a need for a CLI client for interactive use that lets you >>> avoid using a browser ? Not sure how important that is. >> >> It would be really nice. Not everyone wants to write everything in ruby. > > People could just write additional bindings for the API, say in Python or > whatever. The only reason for an interactive CLI that avoids the browser > would be for scripting in bash, right? yessir -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From mwagner at redhat.com Thu Aug 7 01:27:37 2008 From: mwagner at redhat.com (mark wagner) Date: Wed, 06 Aug 2008 21:27:37 -0400 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: <1218067219-20169-1-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <489A4F89.4000802@redhat.com> David Lutterkort wrote: > Here are a few patches that implement the beginnings of an API for > OVirt. They cover handling hosts and storage and hardware pools so > far. This patch is mostly meant for discussion, since there's still a few > things that need to be tweaked. > > * Where's the CLI ? > > I intended to accompany this with a nice clean CLI client that you can > use for lots of things. Even with the current subset of the API, there > are lots of interesting queries and actions one could perform, even > with the current subset of the API. I'd like to avoid offering a > million options from the CLI though. If anybody has pressing needs for > CLI functionality, let me know, otherwise, I'll start making stuff up. > > Especially for querying, it's questionable if CLI access to query > functionality is useful at all - if you want to see all the hosts in > hardware pool 7, you can just > > wget http://management.priv.ovirt.org:3000/ovirt/rest/hardware_pools/7/hosts > > and get a nice XML document. > > Is there a need for a CLI client for interactive use that lets you > avoid using a browser ? Not sure how important that is. > I believe that a lot of admin work tends to be performed via scripts. bash and perl are two that I use. I would think that adding new equipment to pools or moving stuff between pools would be done via scripts quicker than via the wui, especially when done in bulk. In addition, I can envision admins writing scripts to respond to events / alerts. Resources on hostX are running out, migrate vm-Y to hostA -mark > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From pmyers at redhat.com Thu Aug 7 01:29:12 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 06 Aug 2008 21:29:12 -0400 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <489A4DE4.9060206@redhat.com> Message-ID: <489A4FE8.1040400@redhat.com> Jeff Schroeder wrote: > On Wed, Aug 6, 2008 at 6:20 PM, Perry N. Myers wrote: >> Jeff Schroeder wrote: >>> On Wed, Aug 6, 2008 at 5:00 PM, David Lutterkort >>> wrote: >>> ...snip... >>>> Is there a need for a CLI client for interactive use that lets you >>>> avoid using a browser ? Not sure how important that is. >>> It would be really nice. Not everyone wants to write everything in ruby. >> People could just write additional bindings for the API, say in Python or >> whatever. The only reason for an interactive CLI that avoids the browser >> would be for scripting in bash, right? > > yessir I suppose that some folks will want to script things in bash, but I truly hope that in real production environments that's not what gets used. In any case it might be useful to have a true CLI that can be scripted similar to how virsh works. We can add it to the list of things to do, and of course if someone out there wants to try their hand and doing this, we'd love to see some patches :) Perry From dlutter at redhat.com Thu Aug 7 02:51:16 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 07 Aug 2008 02:51:16 +0000 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> Message-ID: <1218077476.19789.97.camel@localhost.localdomain> On Wed, 2008-08-06 at 18:15 -0700, Jeff Schroeder wrote: > On Wed, Aug 6, 2008 at 5:00 PM, David Lutterkort wrote: > ...snip... > > > > Is there a need for a CLI client for interactive use that lets you > > avoid using a browser ? Not sure how important that is. > > It would be really nice. Not everyone wants to write everything in ruby. Understood .. and the intent is that the API should also be accessible from Python etc. It would be nice if somebody tried and wrote Python support for the API (similar to ovirt.rb in patch 5/5) But a CLI client would only be needed (a) for shell scripting and (b) for interactive command line use. I don't think you'll get much mileage out of shell scripting with OVirt; there's a good number of data structures and relationships that you can't really replicate in shell. So, for scripting, I think you'll always need something just a little more powerful than shell, like Python. David From dlutter at redhat.com Thu Aug 7 02:53:02 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 07 Aug 2008 02:53:02 +0000 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: <489A4F89.4000802@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <489A4F89.4000802@redhat.com> Message-ID: <1218077582.19789.99.camel@localhost.localdomain> On Wed, 2008-08-06 at 21:27 -0400, mark wagner wrote: > I believe that a lot of admin work tends to be performed via scripts. bash > and perl are two that I use. I would think that adding new equipment to pools > or moving stuff between pools would be done via scripts quicker than via the wui, > especially when done in bulk. Absolutely; but it would probably have to be done in a language a little more advanced than bash, like Python. > In addition, I can envision admins writing scripts to respond to events / alerts. > Resources on hostX are running out, migrate vm-Y to hostA Yeah, but again, I would think that those would be Ruby or Python or Perl (i.e. !bash) scripts David From yunguol at cn.ibm.com Thu Aug 7 05:40:17 2008 From: yunguol at cn.ibm.com (Guo Lian Yun) Date: Thu, 7 Aug 2008 13:40:17 +0800 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: <4899A86B.3000307@redhat.com> Message-ID: "Perry N. Myers" wrote on 2008-08-06 21:34:35: > Guo Lian Yun wrote: > > Thanks for your great help! > > You're welcome :) > > > I installed successfully all dependency RPM by yum. But it report > > error when > > I'm installing Ovirt. > > > > 1) It fails for me by running create-wui-appliance.sh to create virtual > > server. > > > > #bash create-wui-appliance.sh -t > > http://download.fedora.redhat. > com/pub/fedora/linux/releases/9/Fedora/x86_64/os/ > > -k http://ovirt.org/download/wui-rel-x86_64.ks -v > > Domain node3 defined from /tmp/tmp.b3q8JWVRoM > > > > Domain node4 defined from /tmp/tmp.jtalRXJK3k > > > > Domain node5 defined from /tmp/tmp.B8zlcSB1Cj > > > > Network dummybridge destroyed > > > > Network dummybridge has been undefined > > > > Network dummybridge defined from /tmp/tmp.s8cSkpypob > > > > Network dummybridge started > > > > Network dummybridge marked as autostarted > > > > Formatting '/var/lib/libvirt/images/developer.img', fmt=qcow2, > > size=6144000 kB > > Wed, 06 Aug 2008 03:40:05 ERROR virConnectOpen() failed > > Traceback (most recent call last): > > File "/usr/sbin/virt-install", line 496, in > > main() > > File "/usr/sbin/virt-install", line 345, in main > > conn = cli.getConnection(options.connect) > > File "/usr/lib/python2.5/site-packages/virtinst/cli.py", line 92, in > > getConnection > > return libvirt.open(connect) > > File "/usr/lib64/python2.5/site-packages/libvirt.py", line 135, in open > > if ret is None:raise libvirtError('virConnectOpen() failed') > > libvirtError: virConnectOpen() failed > > > > Who knows why it is? > > Hmm. The command above should work to create a completely new appliance > image in the current directory. Some logs to look at would be: > /root/.virtinst/virt-install.log > /var/log/libvirt/qemu/*.log > > Attach these logs to a mail message and I'll take a look at them. They > might reveal something useful. The command you're using above is only > necessary if you want to create the appliance from scratch. If you have > downloaded the appliance tar.gz file from ovirt.org, then you can simply > just use it. No need to recreate it from the RPM repositories. > > > Then I try to run below command and pass for me. > > > > #bash create-wui-appliance.sh -d "`pwd`" -v > > > > Also, I can start 'developer' successfully. > > Good. This is the command that just reuses the existing .img file that is > downloaded from ovirt.org. > > > > > 2) I tried to invoke the main Virt page, but it fails for me. > > > > #ssh -Y root at 192.168.50.2 firefox (or ssh -fY root at 192.168.50.2 firefox > > -no-remote) > > ssh: connect to host 192.168.50.2 port 22: No route to host > > > > What's the ip refer to? Is it refer to the ip address of my own machine? > > It still fails for me by trying conncect to myself. > > This is sshing to the appliance which has an IP address of 192.168.50.2 on > the dummybridge network. If you just started the appliance when you ran > that command, the networking stack might not have been up yet. Wait a few > minutes and try again... I tried several times but still fails for me. Here is the steps of my installation. 1) #bash create-wui-appliance.sh -d "`pwd`" -v Domain node3 defined from /tmp/tmp.uf02Scb6Jq Domain node4 defined from /tmp/tmp.oYYPU1RmEJ Domain node5 defined from /tmp/tmp.sfDi0RKEwA Network dummybridge destroyed Network dummybridge has been undefined Network dummybridge defined from /tmp/tmp.K0RCG16KS2 Network dummybridge started Network dummybridge marked as autostarted Domain developer defined from /tmp/tmp.LWLYom3gGz Application defined using disk located at /var/lib/libvirt/images/developer.img. Run virsh start developer to start the appliance 2) #virsh start developer Domain developer started 3?#virt-viewer developer libvir: Remote error : unsupported authentication type 2 unable to connect to libvirt xen The command of 'virt-viewer developer' connect to the guest running under xen, we have to connect to the guest developer running under QEMU. (Maybe it's better to do some explanation of this command in Ovirt install instruction) I try to connect to developer as follows, still fail for me. [root at test]# virt-viewer -c qemu:///system developer libvir: Remote error : unsupported authentication type 2 unable to connect to libvirt qemu:///system [root at test]# virt-viewer -c qemu:///system 6 libvir: Remote error : unsupported authentication type 2 unable to connect to libvirt qemu:///system [root at test]# virt-viewer --connect qemu:///system 6 libvir: Remote error : unsupported authentication type 2 unable to connect to libvirt qemu:///system Actually, the developer domain run normally. virsh # list --all Id Name State ---------------------------------- 6 developer running - node3 shut off - node4 shut off - node5 shut off virsh # dumpxml developer developer bae8bbf9-2b42-4e3d-326d-db6869874ee5 786432 786432 1 hvm destroy restart destroy /usr/bin/qemu-kvm I tried to connect developer again and again, but still fail for me. [root at test]# virt-viewer -c qemu:///system developer libvir: Remote error : unsupported authentication type 2 unable to connect to libvirt qemu:///system [root at test]# virt-viewer -c qemu+tcp://localhost/system developer libvir: Remote error : Connection refused unable to connect to libvirt qemu+tcp://localhost/system [root at test]# virt-viewer -c qemu+tcp:///system developer libvir: Remote error : Connection refused unable to connect to libvirt qemu+tcp:///system [root at test]# virt-viewer -c qemu+tcp://root at localhost/system developer libvir: Remote error : Connection refused unable to connect to libvirt qemu+tcp://root at localhost/system However, I can connect to it by virsh successfully. [root at test]# virsh -c qemu:///system Welcome to virsh, the virtualization interactive terminal. Type: 'help' for help with commands 'quit' to quit > Also, give me the output of: > virsh list --all > virsh dumpxml developer > > > Here is the dummybridge output for my own machine. > > > > dummybridge Link encap:Ethernet HWaddr 00:FF:99:97:1A:8F > > inet addr:192.168.50.1 Bcast:192.168.50.255 Mask:255.255.255.0 > > inet6 addr: fe80::3cb0:b6ff:feed:cab/64 Scope:Link > > UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 > > RX packets:0 errors:0 dropped:0 overruns:0 frame:0 > > TX packets:26 errors:0 dropped:0 overruns:0 carrier:0 > > collisions:0 txqueuelen:0 > > RX bytes:0 (0.0 b) TX bytes:4427 (4.3 KiB) > > > > #ssh -fY root at 192.168.50.1 firefox -no-remote > > Error: no display specified > > This is sshing to your physical host running Fedora 9, so this is not > going to work. > > > Does this fail because of putty? The set up machine isn't on my hand, I > > connect to it > > by putty. Do I have to install it on my local machine? > > The command is trying to run firefox on a remote display using X > forwarding. If you are running these commands in Putty on a Windows box > none of this will work since Windows can't natively display X > applications. The easiest way to do this is to log into the local console > of the host that is running F9 and the developer appliance. From the > local console open up an xterm and run the ssh commands from there. Thanks for your advise and I do again. I can't ssh to it yet. [root at test]# ssh -fY root at 192.168.50.2 firefox -no-remote ssh: connect to host 192.168.50.2 port 22: No route to host Then I try to ssh to my physical host(ssh -fY root at 192.168.50.1 firefox -no-remote), and I can bring up the main firefox screen on the guest, which verify the Xterm and display X works for me. Thanks! > > Perry -------------- next part -------------- An HTML attachment was scrubbed... URL: From slinabery at redhat.com Thu Aug 7 07:06:46 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 7 Aug 2008 02:06:46 -0500 Subject: [Ovirt-devel] [PATCH] Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. Message-ID: <20080807070645.GC26662@redhat.com> Sorry for the attachment; haven't set up my esmtp yet. I didn't clean up a great deal of the existing code in graph_controller because I suspect most of it will be removed. I did edit that loop on total_memory so that it wouldn't make mongrel time out. Some whitespace cleanup in graph_controller. Main thing for review is the new method I wrote in graph_controller to retrieve stats in either xml or json (the generation of which is yet to be implemented) for a single host or vm. I want to avoid these big long lists of stats requests to Stats.rb, because a) there's no performance hit AFAICT in breaking them up into smaller requests, and b) the graph (or whatever walks the tree and assembles the data lists for the graph) needs to make more precise requests for data. Comments & suggestions welcome. Good day, Steve -------------- next part -------------- >From 0947ea5f141a8ec30d6f09693dee6db5c0854e93 Mon Sep 17 00:00:00 2001 From: Steve Linabery Date: Thu, 7 Aug 2008 01:56:11 -0500 Subject: [PATCH] Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. --- wui/src/app/controllers/graph_controller.rb | 194 ++++++++++++++++++++------- wui/src/app/util/stats/Stats.rb | 77 ++++++++++-- wui/src/app/util/stats/StatsDataList.rb | 58 ++++++--- wui/src/app/util/stats/statsTest.rb | 54 +++++--- 4 files changed, 283 insertions(+), 100 deletions(-) diff --git a/wui/src/app/controllers/graph_controller.rb b/wui/src/app/controllers/graph_controller.rb index dbe2afc..87fc52d 100644 --- a/wui/src/app/controllers/graph_controller.rb +++ b/wui/src/app/controllers/graph_controller.rb @@ -3,6 +3,65 @@ require 'util/stats/Stats' class GraphController < ApplicationController layout nil + # returns data for one pool/host/vm, one target + def one_resource_graph_data + + # the primary key for the resource + id = params[:id] + + # times are in milliseconds since the epoch + startTime = params[:start] + endTime = params[:end] + + # what statistic to retrieve data for? (cpu, memory, net i/o, etc) + target = params[:target] + + # the desired resolution (may or may not be resolution of results) + resolutionIn = params[:resolution] + + # host or vm + poolType = params[:poolType] + + #TODO: add authorization check (is user allowed to view this data?) + + devClass = DEV_KEY_CLASSES[target] + counter = DEV_KEY_COUNTERS[target] + duration = endTime - startTime + resolution = _valid_resolution(resolutionIn) + #TODO: adjust number of cpus properly + cpus = 0 + + resourceName = "" + if poolType == "host" + resourceName = Host.find(id).hostname + elsif poolType == "vm" + #FIXME: Will Stats allow querying by Vm UUID? + resourceName = Vm.find(id).uuid + end + + requestList = [ ] + #TODO: need new method in Stats to average results for >1 cpu + requestList.push(StatsRequest.new(resourceName, devClass, cpus, counter, + startTime, duration, resolution, + DataFunction::Average), + StatsRequest.new(resourceName, devClass, cpus, counter, + startTime, duration, resolution, + DataFunction::Peak), + StatsRequest.new(resourceName, devClass, cpus, counter, + startTime, duration, resolution, + DataFunction::RollingPeak), + StatsRequest.new(resourceName, devClass, cpus, counter, + startTime, duration, resolution, + DataFunction::RollingAverage)) + + @statsList = getStatsData?(requestList) + respond_to do |format| + format.xml + format.json #is this supported? + end + end + + # generate layout for avaialability bar graphs def availability_graph @id = params[:id] @@ -41,7 +100,7 @@ class GraphController < ApplicationController unlimited = false total=0 used= pools.inject(0) { |sum, pool| sum+pool.allocated_resources[:current][resource_key] } - pools.each do |pool| + pools.each do |pool| resource = pool.total_resources[resource_key] if resource total +=resource @@ -74,9 +133,9 @@ class GraphController < ApplicationController devclass = DEV_KEY_CLASSES[target] counter = DEV_KEY_COUNTERS[target] @pool = Pool.find(@id) - + hosts = @pool.hosts - # temporary workaround for vm resource history + # temporary workaround for vm resource history # graph until we have a more reqs / long term solution if poolType == "vm" hosts = [] @@ -89,18 +148,18 @@ class GraphController < ApplicationController startTime = 0 duration, resolution = _get_snapshot_time_params(myDays.to_i) - + requestList = [ ] @pool.hosts.each { |host| if target == "cpu" 0.upto(host.num_cpus - 1){ |x| - requestList.push( StatsRequest.new(host.hostname, devclass, x, counter, startTime, duration, resolution, DataFunction::Average), + requestList.push( StatsRequest.new(host.hostname, devclass, x, counter, startTime, duration, resolution, DataFunction::Average), StatsRequest.new(host.hostname, devclass, x, counter, startTime, duration, resolution, DataFunction::Peak), StatsRequest.new(host.hostname, devclass, x, counter, startTime, duration, resolution, DataFunction::RollingPeak), StatsRequest.new(host.hostname, devclass, x, counter, startTime, duration, resolution, DataFunction::RollingAverage)) } else - requestList.push( StatsRequest.new(host.hostname, devclass, 0, counter, startTime, duration, resolution, DataFunction::Average), + requestList.push( StatsRequest.new(host.hostname, devclass, 0, counter, startTime, duration, resolution, DataFunction::Average), StatsRequest.new(host.hostname, devclass, 0, counter, startTime, duration, resolution, DataFunction::Peak), StatsRequest.new(host.hostname, devclass, 0, counter, startTime, duration, resolution, DataFunction::RollingPeak), StatsRequest.new(host.hostname, devclass, 0, counter, startTime, duration, resolution, DataFunction::RollingAverage)) @@ -120,9 +179,11 @@ class GraphController < ApplicationController valueindex = (data.get_timestamp?.to_i - dat[0].get_timestamp?.to_i) / resolution times.size.upto(valueindex) { |x| time = Time.at(dat[0].get_timestamp?.to_i + valueindex * resolution) - ts = Date::ABBR_MONTHNAMES[time.month] + ' ' + time.day.to_s - ts += ' ' + time.hour.to_s + ':' + time.min.to_s if myDays.to_i == 1 - times.push ts + if myDays.to_i == 1 + times.push _time_long_format(time) + else + times.push _time_short_format(time) + end } [@avg_history, @peak_history, @roll_avg_history, @roll_peak_history].each { |valuearray| valuearray[:values].size.upto(valueindex) { |x| @@ -157,10 +218,8 @@ class GraphController < ApplicationController } end - total_peak = 0 - total_roll_peak = 0 - 0.upto(@peak_history[:values].size - 1){ |x| total_peak = @peak_history[:values][x] if @peak_history[:values][x] > total_peak } - 0.upto(@roll_peak_history[:values].size - 1){ |x| total_roll_peak = @roll_peak_history[:values][x] if @roll_peak_history[:values][x] > total_roll_peak } + total_peak = @peak_history.get_max_value? + total_roll_peak = $roll_peak_history.get_max_value? scale = [] if target == "cpu" @@ -168,12 +227,23 @@ class GraphController < ApplicationController scale.push x.to_s } elsif target == "memory" - #increments = @pool.hosts.total_memory / 512 - 0.upto(@pool.hosts.total_memory) { |x| - if x % 1024 == 0 - scale.push((x / 1024).to_s) # divide by 1024 to convert to MB - end - } + megabyte = 1024 + totalMemory = @pool.hosts.total_memory + tick = megabyte + if totalMemory >= 10 * megabyte && totalMemory < 100 * megabyte + tick = 10 * megabyte + elsif totalMemory >= 100 * megabyte && totalMemory < 1024 * megabyte + tick = 100 * megabyte + else + tick = 1024 * megabyte + end + + counter = 0 + while counter * tick < totalMemory do + counter += 1 #this gives us one tick mark beyond totalMemory + scale.push((counter * tick / 1024).to_s) # divide by 1024 to convert to MB + end + elsif target == "load" 0.upto(total_peak){|x| scale.push x.to_s if x % 5 == 0 @@ -186,7 +256,7 @@ class GraphController < ApplicationController graph_object = { :timepoints => times, :scale => scale, - :dataset => + :dataset => [ { :name => target + "roll_peak", @@ -196,7 +266,7 @@ class GraphController < ApplicationController }, { :name => target + "roll_average", - :values => @roll_avg_history[:values], + :values => @roll_avg_history[:values], :stroke => @roll_avg_history[:color], :strokeWidth => 2 }, @@ -208,7 +278,7 @@ class GraphController < ApplicationController }, { :name => target + "average", - :values => @avg_history[:values], + :values => @avg_history[:values], :stroke => @avg_history[:color], :strokeWidth => 1 } @@ -237,13 +307,13 @@ class GraphController < ApplicationController if load_value.nil? load_value = 0 elsif load_value > 10 # hack to cap it as we have nothing to compare against - load_value = 10 + load_value = 10 end load_remaining = 10 - load_value - + graph_object = { :timepoints => [], - :dataset => + :dataset => [ { :name => target, @@ -264,7 +334,7 @@ class GraphController < ApplicationController render :json => graph_object end - + # generate layout for snapshot graphs def snapshot_graph @id = params[:id] @@ -275,7 +345,7 @@ class GraphController < ApplicationController :scale => { 'load' => 10, 'cpu' => 100, 'memory' => 0, 'netin' => 1000, 'netout' => 1000}, # values which to scale graphs against :peak => { 'load' => 0, 'cpu' => 0, 'netin' => 0, 'netout' => 0, 'memory' => 0 }} @data_points = { :avg => { 'load' => 0, 'cpu' => 0, 'netin' => 0, 'netout' => 0, 'memory' => 0 }, - :scale => { 'load' => 10, 'cpu' => 100, 'memory' => 0, 'netin' => 1000, 'netout' => 1000}, + :scale => { 'load' => 10, 'cpu' => 100, 'memory' => 0, 'netin' => 1000, 'netout' => 1000}, :peak => { 'load' => 0, 'cpu' => 0, 'netin' => 0, 'netout' => 0, 'memory' => 0 }} duration = 600 @@ -291,7 +361,7 @@ class GraphController < ApplicationController host.nics.each{ |nic| @snapshots[:scale]['netin'] += 1000 @snapshots[:scale]['netout'] += 1000 - # @snapshots[:scale]['netin'] += nic.bandwidth + # @snapshots[:scale]['netin'] += nic.bandwidth # @snapshots[:scale]['netout'] += nic.bandwidth } elsif @poolType == 'vm' @@ -319,7 +389,7 @@ class GraphController < ApplicationController } } end - + statsList = getStatsData?( requestList ) statsList.each { |stat| if stat.get_status? == StatsStatus::SUCCESS @@ -395,32 +465,32 @@ class GraphController < ApplicationController DEV_CLASS_KEYS = DEV_KEY_CLASSES.invert # TODO this needs fixing / completing (cpu: more than user time? disk: ?, load: correct?, nics: correct?) - DEV_KEY_COUNTERS = { 'cpu' => CpuCounter::CalcUsed, 'memory' => MemCounter::Used, 'disk' => DiskCounter::Ops_read, + DEV_KEY_COUNTERS = { 'cpu' => CpuCounter::CalcUsed, 'memory' => MemCounter::Used, 'disk' => DiskCounter::Ops_read, 'load' => LoadCounter::Load_1min, 'netin' => NicCounter::Octets_rx, 'netout' => NicCounter::Octets_tx } DEV_COUNTER_KEYS = DEV_KEY_COUNTERS.invert def _create_host_snapshot_requests(hostname, duration, resolution) requestList = [] requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['memory'], 0, DEV_KEY_COUNTERS['memory'], - 0, duration, resolution, DataFunction::Average) - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['memory'], 0, DEV_KEY_COUNTERS['memory'], - 0, duration, resolution, DataFunction::Peak ) + 0, duration, resolution, DataFunction::Average) + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['memory'], 0, DEV_KEY_COUNTERS['memory'], + 0, duration, resolution, DataFunction::Peak ) requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['load'], 0, DEV_KEY_COUNTERS['load'], 0, duration, resolution, DataFunction::Average) - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['load'], 0, DEV_KEY_COUNTERS['load'], + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['load'], 0, DEV_KEY_COUNTERS['load'], 0, duration, resolution, DataFunction::Peak ) - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['cpu'], 0, DEV_KEY_COUNTERS['cpu'], + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['cpu'], 0, DEV_KEY_COUNTERS['cpu'], 0, duration, resolution, DataFunction::Average) # TODO more than 1 cpu - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['cpu'], 0, DEV_KEY_COUNTERS['cpu'], + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['cpu'], 0, DEV_KEY_COUNTERS['cpu'], 0, duration, resolution, DataFunction::Peak ) # TODO more than 1 cpu - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netout'], 0, DEV_KEY_COUNTERS['netout'], - 0, duration, resolution, DataFunction::Average) - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netout'], 0, DEV_KEY_COUNTERS['netout'], - 0, duration, resolution, DataFunction::Peak ) + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netout'], 0, DEV_KEY_COUNTERS['netout'], + 0, duration, resolution, DataFunction::Average) + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netout'], 0, DEV_KEY_COUNTERS['netout'], + 0, duration, resolution, DataFunction::Peak ) requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netin'], 0, DEV_KEY_COUNTERS['netin'], - 0, duration, resolution, DataFunction::Average) - requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netin'], 0, DEV_KEY_COUNTERS['netin'], - 0, duration, resolution, DataFunction::Peak ) + 0, duration, resolution, DataFunction::Average) + requestList << StatsRequest.new(hostname, DEV_KEY_CLASSES['netin'], 0, DEV_KEY_COUNTERS['netin'], + 0, duration, resolution, DataFunction::Peak ) return requestList end @@ -438,11 +508,11 @@ class GraphController < ApplicationController end def _get_snapshot_value(value, devClass, function) - if ( ( devClass != DEV_KEY_CLASSES["cpu"]) && + if ( ( devClass != DEV_KEY_CLASSES["cpu"]) && ( function != DataFunction::RollingAverage) && ( function != DataFunction::RollingPeak) && - ( value.nan?) ) - return 0 + ( value.nan?) ) + return 0 end # massage some of the data: @@ -451,7 +521,7 @@ class GraphController < ApplicationController elsif devClass == DEV_KEY_CLASSES["netout"] && counter == DEV_KEY_COUNTER["netout"] return (value.to_i * 8 / 1024 / 1024).to_i #mbits elsif devClass == DEV_KEY_CLASSES["netin"] && counter == DEV_KEY_COUNTER["netin"] - return (value.to_i * 8 / 1024 / 1024).to_i # mbits + return (value.to_i * 8 / 1024 / 1024).to_i # mbits elsif devClass == DEV_KEY_CLASSES["memory"] return (value.to_i / 1000000).to_i end @@ -462,16 +532,38 @@ class GraphController < ApplicationController now = Time.now if myDays.to_i == 1 0.upto(152){|x| - time = now - 568 * x # 568 = 24 * 60 * 60 / 152 = secs / interval - times.push Date::ABBR_MONTHNAMES[time.month] + ' ' + time.day.to_s + ' ' + time.hour.to_s + ':' + time.min.to_s + # 568 = 24 * 60 * 60 / 152 = secs / interval + time = now - 568 * x + times.push _time_long_format(time) } elsif 1.upto(myDays.to_i * 3){|x| - time = now - x * 28800 # 24 * 60 * 60 / ~2 - times.push Date::ABBR_MONTHNAMES[time.month] + ' ' + time.day.to_s + # 24 * 60 * 60 / ~2 + time = now - x * 28800 + times.push _time_short_format(time) } end times.reverse! end + def _time_short_format(time) + time.strftime("%b %d") + end + + def _time_long_format(time) + time.strftime("%b %d %H:%M") + end + + def _validate_resolution(resolution) + if resolution <= RRDResolution::Short + RRDResolution::Short + elsif resolution <= RRDResolution::Medium + RRDResolution::Medium + elsif resolution <= RRDResolution::Long + RRDResolution::Long + else + RRDResolution::Default + end + end + end diff --git a/wui/src/app/util/stats/Stats.rb b/wui/src/app/util/stats/Stats.rb index f6ced4b..2741e4a 100644 --- a/wui/src/app/util/stats/Stats.rb +++ b/wui/src/app/util/stats/Stats.rb @@ -29,6 +29,8 @@ require 'util/stats/StatsRequest' def fetchRollingAve?(rrdPath, start, endTime, interval, myFunction, lIndex, returnList, aveLen=7) final = 0 + my_min = 0 + my_max = 0 # OK, first thing we need to do is to move the start time back in order to # have data to average. @@ -55,7 +57,6 @@ def fetchRollingAve?(rrdPath, start, endTime, interval, myFunction, lIndex, retu value = 0 value = vdata[lIndex] value = 0 if value.nan? - roll.push(value) if ( i >= aveLen) @@ -65,19 +66,34 @@ def fetchRollingAve?(rrdPath, start, endTime, interval, myFunction, lIndex, retu final += rdata end final = (final / aveLen ) + + # Determine min / max to help with autoscale. + if ( final > my_max ) + my_max = final + end + if ( final < my_min ) + my_min = final + end returnList.append_data( StatsData.new(fstart + interval * ( i - indexOffset), final )) # Now shift the head off the array roll.shift end end - + + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) + return returnList end def fetchRollingCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIndex, returnList, aveLen=7) + my_min = 0 + my_max = 0 + # OK, first thing we need to do is to move the start time back in order to have data to average. indexOffset = ( aveLen / 2 ).to_i @@ -120,12 +136,24 @@ def fetchRollingCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIn final += rdata end final = (final / aveLen) + + # Determine min / max to help with autoscale. + if ( final > my_max ) + my_max = final + end + if ( final < my_min ) + my_min = final + end returnList.append_data( StatsData.new(fstart + interval * ( i - indexOffset), final )) # Now shift the head off the array roll.shift end end + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) + return returnList end @@ -137,6 +165,9 @@ def fetchCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIndex, re # We also need to handle NaN differently # Finally, we need to switch Min and Max + my_min = 0 + my_max = 0 + lFunc = "AVERAGE" case myFunction when "MAX" @@ -155,13 +186,26 @@ def fetchCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIndex, re data.each do |vdata| i += 1 value = vdata[lIndex] - value = 100 if value.nan? - if ( value > 100 ) - value = 100 - end - value = 100 - value + value = 100 if value.nan? + if ( value > 100 ) + value = 100 + end + value = 100 - value + + # Determine min / max to help with autoscale. + if ( value > my_max ) + my_max = value + end + if ( value < my_min ) + my_min = value + end + returnList.append_data( StatsData.new(fstart + interval * i, value )) end + + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) return returnList end @@ -169,6 +213,9 @@ end def fetchRegData?(rrdPath, start, endTime, interval, myFunction, lIndex, returnList) + my_min = 0 + my_max = 0 + (fstart, fend, names, data, interval) = RRD.fetch(rrdPath, "--start", start.to_s, "--end", \ endTime.to_s, myFunction, "-r", interval.to_s) i = 0 @@ -177,9 +224,21 @@ def fetchRegData?(rrdPath, start, endTime, interval, myFunction, lIndex, returnL # Now, lets walk the returned data and create the ojects, and put them in a list. data.each do |vdata| + value = vdata[lIndex] i += 1 - returnList.append_data( StatsData.new(fstart + interval * i, vdata[lIndex] )) + if ( value > my_max ) + my_max = value + end + if ( value < my_min ) + my_min = value + end + + returnList.append_data( StatsData.new(fstart + interval * i, value )) end + + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) return returnList end @@ -294,7 +353,7 @@ def getStatsData?(statRequestList) counter = request.get_counter? tmpList =fetchData?(request.get_node?, request.get_devClass?,request.get_instance?, request.get_counter?, \ request.get_starttime?, request.get_duration?,request.get_precision?, request.get_function?) - + # Now copy the array returned into the main array myList << tmpList end diff --git a/wui/src/app/util/stats/StatsDataList.rb b/wui/src/app/util/stats/StatsDataList.rb index d6de29c..9f20a12 100644 --- a/wui/src/app/util/stats/StatsDataList.rb +++ b/wui/src/app/util/stats/StatsDataList.rb @@ -21,7 +21,7 @@ #define class StatsData List class StatsDataList def initialize(node,devClass,instance, counter, status, function) - # Instance variables + # Instance variables @node = node @devClass = devClass @instance = instance @@ -29,41 +29,63 @@ class StatsDataList @data=[] @status = status @function = function - end + @min_value = 0 + @max_value = 0 + end - def get_node?() + def get_node?() return @node - end + end - def get_devClass?() + def get_node?() + return @node + end + + def get_devClass?() return @devClass - end + end - def get_instance?() + def get_instance?() return @instance - end + end - def get_counter?() + def get_counter?() return @counter - end + end - def get_data?() + def get_data?() return @data - end + end - def get_status?() + def get_status?() return @status - end + end - def get_function?() + def get_function?() return @function - end + end - def append_data(incoming) + def append_data(incoming) @data << incoming - end + end def length() return @data.length end + + def set_min_value(min) + @min_value = min + end + + def set_max_value(max) + @max_value = max + end + + def get_min_value?() + return @min_value + end + + def get_max_value?() + return @max_value + end end diff --git a/wui/src/app/util/stats/statsTest.rb b/wui/src/app/util/stats/statsTest.rb index baedbc0..1005b32 100644 --- a/wui/src/app/util/stats/statsTest.rb +++ b/wui/src/app/util/stats/statsTest.rb @@ -33,11 +33,20 @@ require 'util/stats/Stats' # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::Load, 0, LoadCounter::Load_15min, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node7.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_rx, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::NIC, 1, NicCounter::Octets_rx, 0, 0, RRDResolution::Long ) - requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Medium ) +# requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Long, DataFunction::Average ) +# requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Long, DataFunction::Peak ) +# requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Long) # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::Disk, 0, DiskCounter::Octets_read, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::Disk, 0, DiskCounter::Octets_write, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node3.priv.ovirt.org", "cpu", 0, "idle", 1211688000, 3600, 10 ) -# requestList << StatsRequest.new("node4.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 3600, RRDResolution::Short ) + + requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::CalcUsed, 0, 300, RRDResolution::Default, DataFunction::Average ) + requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_rx, 0, 0, RRDResolution::Default ) +# requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 300, RRDResolution::Default, DataFunction::RollingAverage ) +# requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 300, RRDResolution::Default, DataFunction::Average ) + requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::CalcUsed, 0, 300, RRDResolution::Default, DataFunction::RollingAverage ) +# requestList << StatsRequest.new("node4.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 3600, RRDResolution::Short, DataFunction::Average ) +# requestList << StatsRequest.new("node4.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::CalcUsed, 0, 3600, RRDResolution::Short, DataFunction::Min ) # requestList << StatsRequest.new("node5.priv.ovirt.org", "cpu", 0, "idle", 1211688000, 3600, 500 ) # requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::Memory, 0, MemCounter::Used, 0, 3600, 10 ) @@ -52,27 +61,28 @@ require 'util/stats/Stats' # puts statsListBig.length statsListBig.each do |statsList| - myNodeName = statsList.get_node?() - myDevClass = statsList.get_devClass?() - myInstance = statsList.get_instance?() - myCounter = statsList.get_counter?() - myStatus = statsList.get_status?() - - case myStatus - when StatsStatus::E_NOSUCHNODE - puts "Can't find data for node " + myNodeName - when StatsStatus::E_UNKNOWN - puts "Can't find data for requested file path" - end - if tmp != myNodeName then - puts + myNodeName = statsList.get_node?() + myDevClass = statsList.get_devClass?() + myInstance = statsList.get_instance?() + myCounter = statsList.get_counter?() + myStatus = statsList.get_status?() + + case myStatus + when StatsStatus::E_NOSUCHNODE + puts "Can't find data for node " + myNodeName + when StatsStatus::E_UNKNOWN + puts "Can't find data for requested file path" end - list = statsList.get_data?() - list.each do |d| - print("\t", myNodeName, "\t", myDevClass, "\t", myInstance, "\t", myCounter, "\t",d.get_value?, "\t",d.get_timestamp?) + if tmp != myNodeName then + puts + end + list = statsList.get_data?() + list.each do |d| + print("\t", myNodeName, "\t", myDevClass, "\t", myInstance, "\t", myCounter, "\t",d.get_value?, "\t",d.get_timestamp?) + puts + end puts - end tmp = myNodeName + print("\tmin_value is: ", statsList.get_min_value?(), "\tmax_value is: ", statsList.get_max_value?()) + puts end - - -- 1.5.5.2 From apevec at redhat.com Thu Aug 7 07:35:41 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 7 Aug 2008 09:35:41 +0200 Subject: [Ovirt-devel] [PATCH] oVirt Node local installation Message-ID: <1218094541-23280-1-git-send-email-apevec@redhat.com> oVirt partition can be initialized on a local flash or disk drive. oVirt partition is a local primary ext2 partition, labeled OVIRT, which contains: /config - local oVirt configuration (ktab, local admin password...) /boot - boot loader, kernel and initramfs /LiveOS - oVirt Node compressed livecd image oVirt partition is initialized using kernel boot parameters: ovirt_init=usb|scsi[:serial#] local installation target disk usb|scsi - select disk type, as reported by udev ID_BUS serial# - select exact disk using serial number, as reported by udev ID_SERIAL [1] e.g. ovirt_init=usb:Generic_STORAGE_DEVICE_0000145418-0:0 ovirt_local_boot install/update oVirt Node image on the local installation target disk without this parameter oVirt partition is used for storing oVirt configuration only [1] one-liner to list serial#s for all disks in the system: for d in /dev/sd?; do eval $(udevinfo --query env --name $d); echo $d $ID_SERIAL;done Signed-off-by: Alan Pevec --- ovirt-host-creator/common-pkgs.ks | 1 - ovirt-managed-node/src/scripts/ovirt | 64 +++++-- ovirt-managed-node/src/scripts/ovirt-awake | 10 +- ovirt-managed-node/src/scripts/ovirt-early | 248 +++++++++++++++++++++++- ovirt-managed-node/src/scripts/ovirt-functions | 3 + ovirt-managed-node/src/scripts/ovirt-post | 2 +- 6 files changed, 298 insertions(+), 30 deletions(-) diff --git a/ovirt-host-creator/common-pkgs.ks b/ovirt-host-creator/common-pkgs.ks index 2d1ad6e..bc08358 100644 --- a/ovirt-host-creator/common-pkgs.ks +++ b/ovirt-host-creator/common-pkgs.ks @@ -48,7 +48,6 @@ ovirt-managed-node -rhpl -kbd -usermode --grub -fedora-logos -kpartx -dmraid diff --git a/ovirt-managed-node/src/scripts/ovirt b/ovirt-managed-node/src/scripts/ovirt index 92ed330..b6dcbc6 100755 --- a/ovirt-managed-node/src/scripts/ovirt +++ b/ovirt-managed-node/src/scripts/ovirt @@ -15,36 +15,70 @@ start() { # and it needs to be at the front of the forward chain iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT + krb5_conf=/etc/krb5.conf + krb5_tab=/etc/libvirt/krb5.tab + # retrieve config from local oVirt partition if available + # krb5.conf krb5.tab + # TODO local admin password, ssh server key - what else? + ovirt=$(mktemp -d) + cfg=$ovirt/config + mount -r /dev/disk/by-label/$OVIRT_LABEL $ovirt + if [ -e $cfg/krb5.conf ]; then + cp -a $cfg/krb5.conf $krb5_conf + fi + if [ -e $cfg/krb5.tab ]; then + cp -a $cfg/krb5.tab $krb5_tab + fi + if [ -s $krb5_tab ]; then + krb5_tab= + fi + find_srv ipa tcp - if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then - krb5_conf=/etc/krb5.conf - if [ ! -s $krb5_conf ]; then - rm -f $krb5_conf - # FIXME this is IPA specific - wget -q \ - http://$SRV_HOST:$SRV_PORT/ipa/config/krb5.ini -O $krb5_conf \ - || (echo "Failed to get $krb5_conf" && return 1) + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" -a ! -s $krb5_conf ]; then + rm -f $krb5_conf + # FIXME this is IPA specific + wget -q \ + http://$SRV_HOST:$SRV_PORT/ipa/config/krb5.ini -O $krb5_conf + if [ $? -ne 0 ]; then + echo "Failed to get $krb5_conf"; return 1 + fi + # store config in oVirt partition + if [ -e $cfg ]; then + mount -o remount,rw $ovirt + cp -a $krb5_conf $cfg/krb5.conf fi else - echo "skipping Kerberos configuration, IPA service not available" + echo "skipping Kerberos configuration" fi find_srv identify tcp if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then - krb5_tab=/etc/libvirt/krb5.tab - ovirt-awake start $krb5_tab $SRV_HOST $SRV_PORT + ovirt-awake start $SRV_HOST $SRV_PORT $krb5_tab + if [ $? -ne 0 ]; then + echo "ovirt-awake failed"; return 1 + fi + # store config in oVirt partition + if [ -n "$krb_tab" -a -e $cfg ]; then + mount -o remount,rw $ovirt + cp -a $krb5_tab $cfg/krb5.tab + fi else - echo "skipping ovirt-awake, oVirt registration service not available" + echo "skipping ovirt-awake, oVirt identify service not available" fi + # cleanup + umount $ovirt && rmdir $ovirt + find_srv collectd tcp if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then if [ -f $collectd_conf.in ]; then collectd_conf=/etc/collectd.conf sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ -e "s/@COLLECTD_PORT@/$SRV_PORT/" $collectd_conf.in \ - > $collectd_conf \ - || (echo "Failed to write $collectd_conf" && return 1) + > $collectd_conf + if [ $? -ne 0 ]; then + echo "Failed to write $collectd_conf"; return 1 + fi fi else echo "skipping collectd configuration, collectd service not available" @@ -53,7 +87,7 @@ start() { case "$1" in start) - echo -n $"Starting ovirt: " + printf "Starting ovirt: " { start diff --git a/ovirt-managed-node/src/scripts/ovirt-awake b/ovirt-managed-node/src/scripts/ovirt-awake index 38d405e..fc03c9a 100755 --- a/ovirt-managed-node/src/scripts/ovirt-awake +++ b/ovirt-managed-node/src/scripts/ovirt-awake @@ -63,10 +63,10 @@ start () { KEYTAB=`echo $REPLY | awk '{ print $2 }'` - if [ -n $KEYTAB ]; then + if [ -n "$KEYTAB" -a -n "$KEYTAB_FILE" ]; then echo "Retrieving keytab: '$KEYTAB'" - wget -q $KEYTAB --output-document=$KEYTAB_FILE + wget -q "$KEYTAB" --output-document="$KEYTAB_FILE" else echo "No keytab to retrieve" fi @@ -84,9 +84,9 @@ start () { case "$1" in start) - KEYTAB_FILE=$2 - SRV_HOST=$3 - SRV_PORT=$4 + SRV_HOST=$2 + SRV_PORT=$3 + KEYTAB_FILE=$4 start RETVAL=$? ;; diff --git a/ovirt-managed-node/src/scripts/ovirt-early b/ovirt-managed-node/src/scripts/ovirt-early index aa0a49c..dada566 100755 --- a/ovirt-managed-node/src/scripts/ovirt-early +++ b/ovirt-managed-node/src/scripts/ovirt-early @@ -10,6 +10,9 @@ . /etc/init.d/functions . /etc/init.d/ovirt-functions +# size of the oVirt partition in megabytes +OVIRT_SIZE=64 + configure_from_network() { DEVICE=$1 if [ -n "$DEVICE" ]; then @@ -51,21 +54,250 @@ configure_from_network() { printf "default config applied." } +# find_disk $bus $serial +find_disk() { + local bus=$1 + local serial=$2 + local live=$(readlink -e /dev/live) + live=${live%[1-4]} + for d in /dev/sd?; do + eval $(udevinfo --query env --name $d) + if [ "$ID_BUS" = "$bus" ]; then + if [ -z "$serial" -o "$ID_SERIAL" = "$serial" ]; then + # cannot install LiveOS over itself + if [ -n "$live" -a "$d" = "$live" ]; then + return 2 + else + echo $d + return 0 + fi + fi + fi + done + return 1 +} + +# local_install $local_os $target +# local_os - 1=install LiveOS and boot loader +# 0=initialize oVirt partition only +# target - target disk to hold the oVirt partition +# usb|scsi[:serial#] +# +# oVirt partition is a local primary ext2 partition, labeled OVIRT, +# which contains: +# /config - local oVirt configuration (ktab, local admin password) +# /boot - boot loader, kernel and initramfs +# /LiveOS - oVirt Node compressed livecd image + +local_install() { + local local_os=$1 + local target=$2 + local disk + local part + local live_dev=/dev/live + if [ ! -e $live_dev ]; then + # PXE boot + live_dev=/dev/loop0 + fi + local ovirt_part=$(readlink -e /dev/disk/by-label/$OVIRT_LABEL) + local ovirt_disk=${ovirt_part%[1-4]} + if [ "$ovirt_disk" = "$ovirt_part" ]; then + ovirt_disk= + fi + if [ -z "$target" ]; then + live_part=$(readlink -e $live_dev) + if [ -z "$ovirt_disk" -o "$ovirt_part" = "$live_part" ]; then + return 1 + fi + mode=update + disk=$ovirt_disk + part=$ovirt_part + else + case "$target" in + scsi*) + bus=scsi + serial=${target#scsi:} + ;; + usb*) + bus=usb + serial=${target#usb:} + ;; + *) + return 1 + ;; + esac + if [ "$serial" = "$bus" ]; then + serial= + fi + disk=$(find_disk $bus $serial) + rc=$? + if [ $rc -eq 2 ]; then + echo "target '$target' is the boot disk, aborting install" + return 1 + elif [ $rc -ne 0 ]; then + echo "target disk '$target' not found" + return 1 + fi + mode=install + if [ -n "$ovirt_disk" ]; then + if [ "$disk" = "$ovirt_disk" ]; then + # target disk contains oVirt partition, select it for update + # TODO force install option + mode=update + part=$ovirt_part + else + # remove label from oVirt partition, there can be only one + e2label $ovirt_part "" + fi + fi + if [ "$mode" = "install" ]; then + printf "installing $disk..." | tee /dev/console + dd if=/dev/zero bs=4096 count=1 of=$disk \ + && parted -s $disk mklabel msdos \ + && parted -s $disk mkpart primary ext2 0.0 $OVIRT_SIZE \ + && partprobe -s $disk + if [ $? -ne 0 ]; then + echo "$disk partition creation failed"; return 1 + fi + part=${disk}1 + udevsettle + mkfs.ext2 -L $OVIRT_LABEL $part \ + && tune2fs -c0 -i0 $part + if [ $? -ne 0 ]; then + echo "$disk ext2 creation failed"; return 1 + fi + # update by-label link manually, mkfs won't trigger udev +ls -l /dev/disk + mkdir -p /dev/disk/by-label +ls -l /dev/disk/by-label + ln -sf $part /dev/disk/by-label/$OVIRT_LABEL + fi + fi + + if [ "$mode" = "update" ]; then + printf "updating $ovirt_disk..." | tee /dev/console + fi + ovirt=$(mktemp -d) + live=$(mktemp -d) + mount -r $live_dev $live + if [ $? -ne 0 ]; then + echo "source image mount failed"; return 1 + fi + mount $part $ovirt + if [ $? -ne 0 ]; then + echo "target mount failed"; return 1 + fi + mkdir -p $ovirt/config + # update local config using embedded in livecd image + # TODO admin tool for adding /config into livecd image + if [ -e $live/config/krb5.conf ]; then + cp -a $live/config/krb5.conf $ovirt/config \ + || echo "krb5.conf copy failed" + fi + if [ -e $live/config/krb5.tab ]; then + cp -a $live/config/krb5.tab $ovirt/config \ + || echo "krb5.tab copy failed" + fi + + if [ $local_os = 0 ]; then + # config update only, cleanup and continue booting + umount $ovirt && rmdir $ovirt + umount $live && rmdir $live + else + # install oVirt Node image for local boot + if [ -e "$live/syslinux" ]; then + syslinux=syslinux + elif [ -e "$live/isolinux" ]; then + syslinux=isolinux + else + syslinux= + fi + rm -rf $ovirt/boot + rm -rf $ovirt/LiveOS + mkdir -p $ovirt/boot/grub + mkdir -p $ovirt/LiveOS + cp -a $live/LiveOS/squashfs.img $ovirt/LiveOS \ + && cp -a $live/$syslinux/initrd0.img $ovirt/boot \ + && cp -a $live/$syslinux/vmlinuz0 $ovirt/boot + if [ $? -ne 0 ]; then + echo "image copy failed"; return 1 + fi + part_num=$(( ${part#$disk} - 1 )) + cat > $ovirt/boot/grub/grub.conf <&2 + if [ $? -ne 0 ]; then + echo "boot loader install failed"; return 1 + fi + # remove 1.5 stages we don't need + find $ovirt/boot/grub -name '*_stage1_5' ! -name e2fs_stage1_5 \ + -exec rm {} \; + # FIXME avoid reboot loops + # - cobbler: pxe-only-once, needs xmlrpc to cobbler server + # - plain dnsmasq with tftp: ? + # temp. workaround: + printf "oVirt local installation finished, press Enter to reboot." \ + > /dev/console + read key + if [ "$key" = "debug" ]; then + sh > /dev/console 2>&1 + fi + reboot + fi +} + start() { - # find boot interface from cmdline - # IPAPPEND 2 in pxelinux.cfg appends e.g. BOOTIF=01-00-16-3e-12-34-57 - BOOTIF= + # oVirt boot parameters + # BOOTIF= (appended by pxelinux) + # ovirt_init=usb|scsi[:serial#] + # ovirt_local_boot + + # BOOTIF= (appended by pxelinux) + # network boot interface is assumed to be on management network where + # oVirt Server is reachable + # IPAPPEND 2 in pxelinux.cfg appends MAC address of the booting node + # e.g. BOOTIF=01-00-16-3e-12-34-57 + bootif= + + # ovirt_init=usb|scsi[:serial#] + # local installation target disk + # usb|scsi - select disk type, as reported by udev ID_BUS + # serial# - select exact disk using serial number, as reported by + # udev ID_SERIAL + # e.g. ovirt_init=usb:Generic_STORAGE_DEVICE_0000145418-0:0 + target= + + # ovirt_local_boot + # install/update oVirt Node image on the local installation target disk + local_os=0 + for i in $(cat /proc/cmdline); do case $i in BOOTIF=??-??-??-??-??-??-??) - i=${i/#BOOTIF=??-/} - BOOTMAC=${i//-/:} - BOOTIF=$(grep -l $BOOTMAC /sys/class/net/eth*/address|rev|cut -d/ -f2|rev) + i=${i#BOOTIF=??-} + bootif=$(grep -il $(echo $i|sed 's/-/:/g') /sys/class/net/eth*/address|rev|cut -d/ -f2|rev) + ;; + ovirt_init=*) + target=${i#ovirt_init=} + ;; + ovirt_local_boot) + local_os=1 ;; esac done - configure_from_network $BOOTIF + # TODO pass extra kernel params from /proc/cmdline e.g. console=... + set -x + local_install $local_os $target + set +x + configure_from_network $bootif # find all of the partitions on the system @@ -96,7 +328,7 @@ start() { case "$1" in start) - echo -n $"Starting ovirt-early: " + printf "Starting ovirt-early: " { start diff --git a/ovirt-managed-node/src/scripts/ovirt-functions b/ovirt-managed-node/src/scripts/ovirt-functions index 5b530f7..083e13d 100644 --- a/ovirt-managed-node/src/scripts/ovirt-functions +++ b/ovirt-managed-node/src/scripts/ovirt-functions @@ -2,6 +2,9 @@ OVIRT_LOGFILE=/var/log/ovirt.log +# label of the oVirt partition +OVIRT_LABEL=OVIRT + find_srv() { local dnsreply diff --git a/ovirt-managed-node/src/scripts/ovirt-post b/ovirt-managed-node/src/scripts/ovirt-post index ecc6ff4..1267f40 100755 --- a/ovirt-managed-node/src/scripts/ovirt-post +++ b/ovirt-managed-node/src/scripts/ovirt-post @@ -22,7 +22,7 @@ start() { case "$1" in start) - echo -n $"Starting ovirt-post: " + printf "Starting ovirt-post: " { start -- 1.5.5.1 From apevec at redhat.com Thu Aug 7 08:12:58 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 07 Aug 2008 10:12:58 +0200 Subject: [Ovirt-devel] Re: [PATCH] oVirt Node local installation In-Reply-To: <1218094541-23280-1-git-send-email-apevec@redhat.com> References: <1218094541-23280-1-git-send-email-apevec@redhat.com> Message-ID: <489AAE8A.8090603@redhat.com> NB this version still has some debug hooks which I'd like to keep for the time being, until this feature is stabilized. It was found to break in various interesting ways and I expect to see more of it as different hardware configurations get tested, so detailed debug log will be needed. Local config stores for now krb5.tab and krb5.conf, additional configs can be added where needed. Important missing FIXME is to integrate w/ Cobbler's pxe-only-once feature to avoid reboot loop: oVirt machines PXE-boot by default, when local boot is desired, pxelinux config for selected machine(s) is temporarily added ovirt_* parameters and after successful installation default pxelinux entry is set to local disk boot. Workaround for developer setup is to wait for a key press after installation, giving you a chance to change boot order. To test in developer setup, add to nodeX doman definition (easiest using virt-manager) one virtual disk, size 100MB and add ovirt_init=scsi ovirt_local_boot to APPEND in /var/lib/tftpboot/pxelinux.cfg/default From pmyers at redhat.com Thu Aug 7 12:49:11 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 07 Aug 2008 08:49:11 -0400 Subject: [Ovirt-devel] Re: blocked on virt-viewer in Ovirt install preparation In-Reply-To: References: Message-ID: <489AEF47.5050108@redhat.com> Before we get started on debugging this... I noticed in your earlier emails that you're using the following versions of software: > kernel-2.6.25-0.218.rc8.git7.fc9.x86_64 > kvm-65-1.fc9.x86_64 > libvirt-0.4.1-4.fc9.x86_64 > virt-manager-0.5.4-2.fc9.x86_64 > virt-viewer-0.0.3-1.fc9.x86_64 I'm using: kernel-2.6.25.11-97.fc9.x86_64 kvm-65-7.fc9.x86_64 libvirt-0.4.4-2.fc9.x86_64 virt-manager-0.5.4-4.fc9.x86_64 virt-viewer-0.0.3-1.fc9.x86_64 The versions of the kernel and kvm shouldn't be causing any problems, but the version of libvirt is very old. Please run a yum update on your system and try your tests again with the latest version of the RPMs available for F9. We probably need to add a check to the create-wui-appliance script to check for a specific minimum version of libvirt. The assumption for right now is that oVirt should be used on a Fedora 9 box with the latest updates installed. Also... Make sure to restart libvirt after the upgrade, otherwise the daemon won't be the latest version. Try things out after that and let me know if you're seeing the same problems. Perry > > > I tried several times but still fails for me. > Here is the steps of my installation. > > 1) #bash create-wui-appliance.sh -d "`pwd`" -v > > > Domain node3 defined from /tmp/tmp.uf02Scb6Jq > > Domain node4 defined from /tmp/tmp.oYYPU1RmEJ > > Domain node5 defined from /tmp/tmp.sfDi0RKEwA > > Network dummybridge destroyed > > Network dummybridge has been undefined > > Network dummybridge defined from /tmp/tmp.K0RCG16KS2 > > Network dummybridge started > > Network dummybridge marked as autostarted > > Domain developer defined from /tmp/tmp.LWLYom3gGz > > Application defined using disk located at > /var/lib/libvirt/images/developer.img. > Run virsh start developer to start the appliance > > 2) #virsh start developer > > Domain developer started > > 3??#virt-viewer developer > libvir: Remote error : unsupported authentication type 2 > unable to connect to libvirt xen > > The command of 'virt-viewer developer' connect to the guest running > under xen, we have to connect to the guest developer running under QEMU. > (Maybe it's better to do some explanation of this command in Ovirt > install instruction) > > I try to connect to developer as follows, still fail for me. > > [root at test]# virt-viewer -c qemu:///system developer > libvir: Remote error : unsupported authentication type 2 > unable to connect to libvirt qemu:///system > > [root at test]# virt-viewer -c qemu:///system 6 > libvir: Remote error : unsupported authentication type 2 > unable to connect to libvirt qemu:///system > > [root at test]# virt-viewer --connect qemu:///system 6 > libvir: Remote error : unsupported authentication type 2 > unable to connect to libvirt qemu:///system > > Actually, the developer domain run normally. > > virsh # list --all > Id Name State > ---------------------------------- > 6 developer running > - node3 shut off > - node4 shut off > - node5 shut off > > > virsh # dumpxml developer > > developer > bae8bbf9-2b42-4e3d-326d-db6869874ee5 > 786432 > 786432 > 1 > > hvm > > > > destroy > restart > destroy > > /usr/bin/qemu-kvm > > > > > > > > > > > > > > > > > > > > I tried to connect developer again and again, but still fail for me. > > [root at test]# virt-viewer -c qemu:///system developer > libvir: Remote error : unsupported authentication type 2 > unable to connect to libvirt qemu:///system > > [root at test]# virt-viewer -c qemu+tcp://localhost/system developer > libvir: Remote error : Connection refused > unable to connect to libvirt qemu+tcp://localhost/system > > [root at test]# virt-viewer -c qemu+tcp:///system developer > libvir: Remote error : Connection refused > unable to connect to libvirt qemu+tcp:///system > > [root at test]# virt-viewer -c qemu+tcp://root at localhost/system developer > libvir: Remote error : Connection refused > unable to connect to libvirt qemu+tcp://root at localhost/system > > However, I can connect to it by virsh successfully. > > [root at test]# virsh -c qemu:///system > Welcome to virsh, the virtualization interactive terminal. > > Type: 'help' for help with commands > 'quit' to quit > > > > > Also, give me the output of: > > virsh list --all > > virsh dumpxml developer > > > > > Here is the dummybridge output for my own machine. > > > > > > dummybridge Link encap:Ethernet HWaddr 00:FF:99:97:1A:8F > > > inet addr:192.168.50.1 Bcast:192.168.50.255 > Mask:255.255.255.0 > > > inet6 addr: fe80::3cb0:b6ff:feed:cab/64 Scope:Link > > > UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 > > > RX packets:0 errors:0 dropped:0 overruns:0 frame:0 > > > TX packets:26 errors:0 dropped:0 overruns:0 carrier:0 > > > collisions:0 txqueuelen:0 > > > RX bytes:0 (0.0 b) TX bytes:4427 (4.3 KiB) > > > > > > #ssh -fY root at 192.168.50.1 firefox -no-remote > > > Error: no display specified > > > > This is sshing to your physical host running Fedora 9, so this is not > > going to work. > > > > > Does this fail because of putty? The set up machine isn't on my > hand, I > > > connect to it > > > by putty. Do I have to install it on my local machine? > > > > The command is trying to run firefox on a remote display using X > > forwarding. If you are running these commands in Putty on a Windows box > > none of this will work since Windows can't natively display X > > applications. The easiest way to do this is to log into the local > console > > of the host that is running F9 and the developer appliance. From the > > local console open up an xterm and run the ssh commands from there. > > Thanks for your advise and I do again. I can't ssh to it yet. > > [root at test]# ssh -fY root at 192.168.50.2 firefox -no-remote > ssh: connect to host 192.168.50.2 port 22: No route to host > > Then I try to ssh to my physical host(ssh -fY root at 192.168.50.1 > firefox -no-remote), and I can bring up > the main firefox screen on the guest, which verify the Xterm and > display X works for me. > > Thanks! > > > > > Perry > > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From hbrock at redhat.com Thu Aug 7 13:49:55 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Thu, 7 Aug 2008 09:49:55 -0400 Subject: [Ovirt-devel] [RFC] Beginnings of a OVirt API In-Reply-To: <489A4DF5.8090101@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <489A4DF5.8090101@redhat.com> Message-ID: <20080807134955.GR32280@redhat.com> On Wed, Aug 06, 2008 at 09:20:53PM -0400, Perry N. Myers wrote: > David Lutterkort wrote: >> Here are a few patches that implement the beginnings of an API for >> OVirt. They cover handling hosts and storage and hardware pools so >> far. This patch is mostly meant for discussion, since there's still a few >> things that need to be tweaked. >> >> If you're only interested in seeing how the API is used, jump straight to >> patch 5/5; it contains all the client side (example) code. To see what gets >> sent across the wire, you can request stuff like /ovirt/hosts or >> /ovirt/hardware_pools?path=/default/foo/bar in your browser and admire the >> XML. >> >> In doing this, it became apparent that there are various things in the >> existing OVirt server code that need to be adapted to better accomodate the >> API: >> >> * Enable simple HTTP authentication >> >> It should be possible for people to run scripts without hooking the >> client into the krb5 infrastructure; instead, scripts should be able to >> use simple username/password authentication. > > Jay is working on this. Once this is in the next branch you can modify > your stuff to use the user/pass auth. Also, for what it's worth, curl supports kerberos auth negotiation (this does not negate the need for basic http authn, but it provides another option). > > [snip] >> >> * Where's the CLI ? >> >> I intended to accompany this with a nice clean CLI client that you can >> use for lots of things. Even with the current subset of the API, there >> are lots of interesting queries and actions one could perform, even >> with the current subset of the API. I'd like to avoid offering a >> million options from the CLI though. If anybody has pressing needs for >> CLI functionality, let me know, otherwise, I'll start making stuff up. > > Start making stuff up for now :) I agree, we should start with the obvious ones and see where we need to go from there. This is one of those things that's hard to discover until you actually try to use it. hmm... once we have a CLI then we can have a Man Page, how exciting... --Hugh From clalance at redhat.com Thu Aug 7 15:11:01 2008 From: clalance at redhat.com (Chris Lalancette) Date: Thu, 07 Aug 2008 17:11:01 +0200 Subject: [Ovirt-devel] FYI, new libvirt/kvm packages in the repo Message-ID: <489B1085.1090009@redhat.com> All, I've uploaded new kvm and libvirt modules into the ovirt-specific repo. Using these RPMS, I am able to get live migration successfully working. The new libvirt package has the most recent live-migration patches from Rich Jones, plus some bug fixes. The kvm package is updated to -72, along with a bug fix and a migration patch. Please give these packages a test, especially the KVM one, since it is so new. Thanks, Chris Lalancette From meyering at redhat.com Thu Aug 7 15:31:42 2008 From: meyering at redhat.com (Jim Meyering) Date: Thu, 07 Aug 2008 17:31:42 +0200 Subject: [Ovirt-devel] [PATCH] Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. In-Reply-To: <20080807070645.GC26662@redhat.com> (Steve Linabery's message of "Thu, 7 Aug 2008 02:06:46 -0500") References: <20080807070645.GC26662@redhat.com> Message-ID: <87wsiset9d.fsf@rho.meyering.net> Steve Linabery wrote: > Sorry for the attachment; haven't set up my esmtp yet. > > I didn't clean up a great deal of the existing code in graph_controller because I suspect most of it will be removed. I did edit that loop on total_memory so that it wouldn't make mongrel time out. > > Some whitespace cleanup in graph_controller. > > Main thing for review is the new method I wrote in graph_controller to retrieve stats in either xml or json (the generation of which is yet to be implemented) for a single host or vm. I want to avoid these big long lists of stats requests to Stats.rb, because a) there's no performance hit AFAICT in breaking them up into smaller requests, and b) the graph (or whatever walks the tree and assembles the data lists for the graph) needs to make more precise requests for data. Hi Steve, A fine net change, overall. It's great to do whitespace clean-up, but please keep that sort of change separate from anything substantial. Otherwise, it's more work for the reviewer to separate the trivially-ignorable whitespace changes from the ones that are significant. If you run this, it'll make git colorize diff output: (it modifies settings in ~/.gitconfig) git config --global color.ui auto Then do "git log -p -1" (but don't pipe it into anything) and you'll see the most recent commit. If it's this one, you should see dark red rectangles marking the offending white spaces on lines added by that commit. When I applied that change set via "git am FILE", I saw these warnings: $ g am r Applying: Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. /home/meyering/work/co/ovirt/.git/rebase-apply/patch:173: space before tab in indent. scale.push((counter * tick / 1024).to_s) # divide by 1024 to convert to MB /home/meyering/work/co/ovirt/.git/rebase-apply/patch:338: space before tab in indent. times.push _time_long_format(time) /home/meyering/work/co/ovirt/.git/rebase-apply/patch:345: space before tab in indent. time = now - x * 28800 /home/meyering/work/co/ovirt/.git/rebase-apply/patch:346: space before tab in indent. times.push _time_short_format(time) /home/meyering/work/co/ovirt/.git/rebase-apply/patch:535: trailing whitespace. warning: squelched 8 whitespace errors warning: 13 lines add whitespace errors. ----------------------- Another nit-picky, but important detail: Note your long subject line. It seems rude of git to concatenate things like that, but that's the way it works. You can accommodate it by changing the log (git commit --amend) to have a single empty line after the first "summary" line: -------- Add max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. ---------------- [with this next bit, we're getting really minor, but comments/logs matter, too] Also, note that "Add ..." is recommended over "Adds ..." in all types of documentation (active voice vs passive, direct vs indirect). Another example: I'd change this comment: - # returns data for one pool/host/vm, one target + # return data for one pool/host/vm, one target ==================================================== On to the more substantial: In Stats.rb, there are four blocks nearly identical to this one: if ( final > my_max ) my_max = final end if ( final < my_min ) my_min = final end This is more concise and equivalent: my_min = [my_min, final].min my_max = [my_max, final].max Also, I noticed that the pre-existing style is to initialize variables at the top of a block or function. It's better to avoid that style, and instead to place any initialization as near as possible to the first use. For example, if you move the initializations of my_max and my_min down so they're nearer their first uses, readers don't have to worry about whether they're used uninitialized (now the initialization is just before the loop), or if the values are modified between initialization and whatever use the reader is looking at. =================== I like your peak_history and _time_short_format changes. Much more readable that way. I.e., when the resulting lines fit in 80-columns, side-by-side diffs are more useful. =================== In graph_controller.rb, + megabyte = 1024 + totalMemory = @pool.hosts.total_memory + tick = megabyte + if totalMemory >= 10 * megabyte && totalMemory < 100 * megabyte + tick = 10 * megabyte + elsif totalMemory >= 100 * megabyte && totalMemory < 1024 * megabyte + tick = 100 * megabyte + else + tick = 1024 * megabyte + end + + counter = 0 + while counter * tick < totalMemory do + counter += 1 #this gives us one tick mark beyond totalMemory + scale.push((counter * tick / 1024).to_s) # divide by 1024 to convert to MB + end Maybe that while loop test should be "<=", in case totalMemory is exactly 100*1024 or 1024*1024? Otherwise, it looks like there will be no ticks for those two edge cases. From mwagner at redhat.com Thu Aug 7 16:09:23 2008 From: mwagner at redhat.com (mark wagner) Date: Thu, 07 Aug 2008 12:09:23 -0400 Subject: [Ovirt-devel] [PATCH] Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. In-Reply-To: <87wsiset9d.fsf@rho.meyering.net> References: <20080807070645.GC26662@redhat.com> <87wsiset9d.fsf@rho.meyering.net> Message-ID: <489B1E33.4030201@redhat.com> Jim The Stats changes are actually mine, not Steve's. Since he alone needed to integrate with my changes and effectively tested them for me, I asked him to just submit them. More comments in line Jim Meyering wrote: > Steve Linabery wrote: > >> Sorry for the attachment; haven't set up my esmtp yet. >> >> I didn't clean up a great deal of the existing code in graph_controller because I suspect most of it will be removed. I did edit that loop on total_memory so that it wouldn't make mongrel time out. >> >> Some whitespace cleanup in graph_controller. >> >> Main thing for review is the new method I wrote in graph_controller to retrieve stats in either xml or json (the generation of which is yet to be implemented) for a single host or vm. I want to avoid these big long lists of stats requests to Stats.rb, because a) there's no performance hit AFAICT in breaking them up into smaller requests, and b) the graph (or whatever walks the tree and assembles the data lists for the graph) needs to make more precise requests for data. >> > > Hi Steve, > > A fine net change, overall. > > It's great to do whitespace clean-up, but please keep that sort of change > separate from anything substantial. Otherwise, it's more work for the > reviewer to separate the trivially-ignorable whitespace changes from > the ones that are significant. > > The whitespace changes are mine. I thought you wanted them out and had put such an edict in place. Sorry for not understanding > If you run this, it'll make git colorize diff output: > (it modifies settings in ~/.gitconfig) > > git config --global color.ui auto > > Then do "git log -p -1" (but don't pipe it into anything) and you'll > see the most recent commit. If it's this one, you should see dark > red rectangles marking the offending white spaces on lines added by > that commit. > > When I applied that change set via "git am FILE", I saw these warnings: > > $ g am r > Applying: Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. > /home/meyering/work/co/ovirt/.git/rebase-apply/patch:173: space before tab in indent. > scale.push((counter * tick / 1024).to_s) # divide by 1024 to convert to MB /home/meyering/work/co/ovirt/.git/rebase-apply/patch:338: space before tab in indent. > times.push _time_long_format(time) > /home/meyering/work/co/ovirt/.git/rebase-apply/patch:345: space before tab in indent. > time = now - x * 28800 > /home/meyering/work/co/ovirt/.git/rebase-apply/patch:346: space before tab in indent. > times.push _time_short_format(time) > /home/meyering/work/co/ovirt/.git/rebase-apply/patch:535: trailing whitespace. > > warning: squelched 8 whitespace errors > warning: 13 lines add whitespace errors. > > ----------------------- > Another nit-picky, but important detail: > Note your long subject line. It seems rude of git to concatenate things > like that, but that's the way it works. You can accommodate it by > changing the log (git commit --amend) to have a single empty > line after the first "summary" line: > > -------- > Add max/min methods to StatsDataList. > > Limited cleanup in graph_controller.rb. > First stab at stats data retrieval for new graphing approach. > ---------------- > > [with this next bit, we're getting really minor, but comments/logs matter, too] > Also, note that "Add ..." is recommended over "Adds ..." > in all types of documentation (active voice vs passive, direct vs > indirect). > > Another example: I'd change this comment: > > - # returns data for one pool/host/vm, one target > + # return data for one pool/host/vm, one target > > ==================================================== > On to the more substantial: > In Stats.rb, there are four blocks nearly identical to this one: > > if ( final > my_max ) > my_max = final > end > if ( final < my_min ) > my_min = final > end > > This is more concise and equivalent: > > my_min = [my_min, final].min > my_max = [my_max, final].max > > My bad, I'm a Ruby newbie, been learning enough to get this work done. I'm try to learn more Ruby to be more efficient with my code. > Also, I noticed that the pre-existing style is to initialize variables at > the top of a block or function. It's better to avoid that style, and > instead to place any initialization as near as possible to the first use. > For example, if you move the initializations of my_max and my_min down > so they're nearer their first uses, readers don't have to worry about > whether they're used uninitialized (now the initialization is just before > the loop), or if the values are modified between initialization and > whatever use the reader is looking at. > > I was actually taught just the opposite, init at the top so its easy to find the inits. In my more performance sensitive c++ apps, I tend to init just before the variable is first used (or just outside of the loop as needed) if there is a chance of getting out of the function before the variable would be used. Thus avoiding unneeded initializations. I appreciate the style suggestions, coming from a long C/C++ background, I tend do things the way I've been doing them for the last 20+ years. I try to be fairly flexible in how I write or change code. For instance I matched the style in graph_controller.rb for previous changes that I've made. However, since the ovirt program doesn't have a predefined style guide and I am the one writing and maintaining the Stats code, I will continue to write it in a manner that is easiest for me to support. I'm sorry if this upsets your style preferences, but functionality is more important to me over style. I also tend to worry when people focus on the style w/o specifying a style guide for the program. Don't you think oVirt has bigger issues to deal with other than trailing whitespace and where I init my variables? -mark > =================== > > I like your peak_history and _time_short_format changes. > Much more readable that way. I.e., when the resulting lines > fit in 80-columns, side-by-side diffs are more useful. > > =================== > > In graph_controller.rb, > > + megabyte = 1024 > + totalMemory = @pool.hosts.total_memory > + tick = megabyte > + if totalMemory >= 10 * megabyte && totalMemory < 100 * megabyte > + tick = 10 * megabyte > + elsif totalMemory >= 100 * megabyte && totalMemory < 1024 * megabyte > + tick = 100 * megabyte > + else > + tick = 1024 * megabyte > + end > + > + counter = 0 > + while counter * tick < totalMemory do > + counter += 1 #this gives us one tick mark beyond totalMemory > + scale.push((counter * tick / 1024).to_s) # divide by 1024 to convert to MB > + end > > Maybe that while loop test should be "<=", in case > totalMemory is exactly 100*1024 or 1024*1024? > Otherwise, it looks like there will be no ticks > for those two edge cases. > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From jguiditt at redhat.com Thu Aug 7 17:04:35 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 07 Aug 2008 13:04:35 -0400 Subject: [Ovirt-devel] [PATCH] permission denormalization. (revised) In-Reply-To: <1217877407-10226-1-git-send-email-sseago@redhat.com> References: <1217877407-10226-1-git-send-email-sseago@redhat.com> Message-ID: <1218128675.4120.17.camel@localhost.localdomain> This partially works for me, but noticed a couple of things. * when viewing the users grid, the column with 'inherited/direct' has no header. This may be ok, but when you mouse over it, you get a clickable arrow that drops down a box that can't be fully seen (attaching screenshot, hard to describe) * Not sure where this breaks exactly, but when I log in as a user with no permissions, I still get all nodes returned in the tree (so fetch_json must not be filtering right). However, when I click on one of them, I see rails attempting to reroute me to /login. Similarly, get the full tree for a valid user with a small set of permissions, and can go only to approve pools (though I still see them in the tree). * Search results return items my user does not have permission for. Again, I cannot click on them, so that part of security is there. (I think this is covered in the next patch, which I have not tested yet). * I have not been able to test granting a new permission as my developer appliance has not yet successfully reinstalled. I kept getting the error: 'ERROR:root:Unable to create appliance : Failed to find package 'curl' : No package(s) available to install' Trying a full reinstall but am pretty much done for the day/week (it has been running a 1/2 hour is is maybe halfway through). I'll check in on it later this after and give feedback if it is successful. -j -------------- next part -------------- A non-text attachment was scrubbed... Name: user_access.png Type: image/png Size: 106672 bytes Desc: not available URL: From jguiditt at redhat.com Thu Aug 7 17:20:18 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 07 Aug 2008 13:20:18 -0400 Subject: [Ovirt-devel] [PATCH] permission denormalization. (revised) In-Reply-To: <1217877407-10226-1-git-send-email-sseago@redhat.com> References: <1217877407-10226-1-git-send-email-sseago@redhat.com> Message-ID: <1218129618.4120.19.camel@localhost.localdomain> Sent a reply with an attachment which seems to have not gone through, so trying again without: This partially works for me, but noticed a couple of things. * when viewing the users grid, the column with 'inherited/direct' has no header. This may be ok, but when you mouse over it, you get a clickable arrow that drops down a box that can't be fully seen (attaching screenshot, hard to describe) * Not sure where this breaks exactly, but when I log in as a user with no permissions, I still get all nodes returned in the tree (so fetch_json must not be filtering right). However, when I click on one of them, I see rails attempting to reroute me to /login. Similarly, get the full tree for a valid user with a small set of permissions, and can go only to approve pools (though I still see them in the tree). * Search results return items my user does not have permission for. Again, I cannot click on them, so that part of security is there. (I think this is covered in the next patch, which I have not tested yet). * I have not been able to test granting a new permission as my developer appliance has not yet successfully reinstalled. I kept getting the error: 'ERROR:root:Unable to create appliance : Failed to find package 'curl' : No package(s) available to install' Trying a full reinstall but am pretty much done for the day/week (it has been running a 1/2 hour is is maybe halfway through). I'll check in on it later this after and give feedback if it is successful. -j From sseago at redhat.com Thu Aug 7 17:22:43 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 07 Aug 2008 13:22:43 -0400 Subject: [Ovirt-devel] [PATCH 2/5] API for storage pools In-Reply-To: <1218067219-20169-3-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-3-git-send-email-dlutter@redhat.com> Message-ID: <489B2F63.2030508@redhat.com> David Lutterkort wrote: > - List and filter storage pools by ipaddr, path, target and hardware_pool_id > - Show an individual storage pool > - Create a new storage pool > - Destroy a storage pool > --- > I'll mention the obvious comment that we've got to filter all this stuff by permissions (applies to all patches). A few additional comments inline: > .../controllers/rest/storage_pools_controller.rb | 60 ++++++++++++++++++++ > wui/src/config/routes.rb | 1 + > 2 files changed, 61 insertions(+), 0 deletions(-) > create mode 100644 wui/src/app/controllers/rest/storage_pools_controller.rb > > diff --git a/wui/src/app/controllers/rest/storage_pools_controller.rb b/wui/src/app/controllers/rest/storage_pools_controller.rb > new file mode 100644 > index 0000000..99eb158 > --- /dev/null > +++ b/wui/src/app/controllers/rest/storage_pools_controller.rb > @@ -0,0 +1,60 @@ > +class Rest::StoragePoolsController < ApplicationController > + > + EQ_ATTRIBUTES = [ :ip_addr, :export_path, :target, > + :hardware_pool_id ] > + > + def index > + conditions = [] > + EQ_ATTRIBUTES.each do |attr| > + if params[attr] > + conditions << "#{attr} = :#{attr}" > + end > + end > + > + @storage = StoragePool.find(:all, > + :conditions => [conditions.join(" and "), params], > + :order => "id") > + > + respond_to do |format| > + format.xml { render :xml => @storage.to_xml } > + end > + end > + > + def show > + @storage = StoragePool.find(params[:id]) > + respond_to do |format| > + format.xml { render :xml => @storage.to_xml } > + end > + end > + > + def create > + # Somehow the attribute 'type' never makes it through > + # Maybe because there is a (deprecated) Object.type ? > Yes, ActiveRecord has docs that address this. You have to say foo[:type] rather than foo.type ! We've run into the same problem with target. foo[:target] rather than foo.target I'm not sure how this affects the API though -- other than making sure we set/retrieve type and target attributes correctly. > + pool = params[:storage_pool] > + type = pool[:storage_type] > + pool.delete(:storage_type) > + @storage_pool = StoragePool.factory(type, pool) > + respond_to do |format| > + if @storage_pool > + if @storage_pool.save > We've tried to be consistent in using save! and dealing with errors in rescue blocks, as it's too easy to let error returns fall through this way. In addition, for multi-object transactions, this lets us deal with errors in one place for the whole transaction. > + format.xml { render :xml => @storage_pool, > + :status => :created, > + :location => rest_storage_pool_url(@storage_pool) } > + else > + format.xml { render :xml => @storage_pool.errors, > + :status => :unprocessable_entity } > When we intergrate with the WUI controllers, we should standardize our status/message processing between the REST calls and the AJAX/json calls. > + end > + else > + format.xml { render :xml => "Illegal storage type #{params[:storage_type]}", :status => :unprocessable_entity } > + end > + end > + end > + > + def destroy > + @storage_pool = StoragePool.find(params[:id]) > + @storage_pool.destroy > + respond_to do |format| > + format.xml { head :ok } > + end > + end > +end > Scott From imain at redhat.com Thu Aug 7 19:13:43 2008 From: imain at redhat.com (Ian Main) Date: Thu, 7 Aug 2008 12:13:43 -0700 Subject: [Ovirt-devel] [PATCH] keep scp in Node image In-Reply-To: <489A4BFD.20608@redhat.com> References: <1218070535-19835-1-git-send-email-apevec@redhat.com> <489A4BFD.20608@redhat.com> Message-ID: <20080807121343.0af05b42@tp.mains.net> On Wed, 06 Aug 2008 21:12:29 -0400 "Perry N. Myers" wrote: > Alan Pevec wrote: > > - scp in Node image is helpful for debugging to add missing tools > > - reorganized Node image blacklist a bit > > Agree that having scp is useful in the node and the rest of NFC, so ACK. Yes, thank you. I ran into this the other day and had to use tar + ssh to get stuff moved over. This is much easier. Ian From sseago at redhat.com Thu Aug 7 20:02:08 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 07 Aug 2008 16:02:08 -0400 Subject: [Ovirt-devel] [PATCH 3/5] API for Hardware Pools In-Reply-To: <1218067219-20169-4-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-4-git-send-email-dlutter@redhat.com> Message-ID: <489B54C0.6010302@redhat.com> David Lutterkort wrote: > - List and filter HW pools by name > - CRUD of individual HW pool > - Hosts and storgae pools for a HW pool are accessible as nested resources, e.g. > /ovirt/hardware_pools/1/hosts?hostname=myhost.example.com > --- > .../controllers/rest/hardware_pools_controller.rb | 85 ++++++++++++++++++++ > wui/src/config/routes.rb | 4 + > 2 files changed, 89 insertions(+), 0 deletions(-) > create mode 100644 wui/src/app/controllers/rest/hardware_pools_controller.rb > > diff --git a/wui/src/app/controllers/rest/hardware_pools_controller.rb b/wui/src/app/controllers/rest/hardware_pools_controller.rb > new file mode 100644 > index 0000000..49a8fcc > --- /dev/null > +++ b/wui/src/app/controllers/rest/hardware_pools_controller.rb > @@ -0,0 +1,85 @@ > +class Rest::HardwarePoolsController < ApplicationController > + OPTS = { > + :include => [ :storage_pools, :hosts, :quota ] > + } > + > + EQ_ATTRIBUTES = [ :name ] > + > + def index > + conditions = [] > + EQ_ATTRIBUTES.each do |attr| > + if params[attr] > + conditions << "#{attr} = :#{attr}" > + end > + end > + > + @pools = HardwarePool.find(:all, > + :conditions => [conditions.join(" and "), params], > + :order => "id") > + > + respond_to do |format| > + format.xml { render :xml => @pools.to_xml(OPTS) } > + end > + end > + > + def show > + @pool = HardwarePool.find(params[:id]) > + respond_to do |format| > + format.xml { render :xml => @pool.to_xml(OPTS) } > + end > + end > + > + def create > + # FIXME: Why can't I just use HardwarePool.create here ? > + # Shouldn't that find the parent_id param ? > parent is not a normal association. It's a method as part of the betternestedset API. Since the API automatically readjusts the lft/rgt parameters when a new pool is added to the hierarchy, you can't just set parent_id. You've got to create the pool (and save it) without a parent, and then call set_as_parent_of (or whatever the method is called) which sets the parent_id. Since reparenting a pool requires all of the lft/rgt parameters of the pools in the hierarchy to be reset, this must be done by the API call. > + @parent = Pool.find(params[:hardware_pool][:parent_id]) > + @pool = HardwarePool.new(params[:hardware_pool]) > + # FIXME: How do we check for errors here ? > + @pool.create_with_parent(@parent) > + respond_to do |format| > + format.xml { render :xml => @pool.to_xml(OPTS), > + :status => :created, > + :location => rest_hardware_pool_url(@pool) } > + end > + end > + > + def update > + # We allow updating direct attributes of the HW pool and moving > + # hosts/storage into it here. > + @pool = HardwarePool.find(params[:id]) > + [:hosts, :storage_pools].each do |k| > + objs = params[:hardware_pool].delete(k) > + ids = objs.reject{ |obj| obj[:hardware_pool_id] == @pool.id}. > + collect{ |obj| obj[:id] } > + if ids.size > 0 > + if k == :hosts > + # FIXME: Why does move_hosts need an explicit pool_id ? > The pool_id specifies what pool we're moving the hosts to, although it probably makes sense to default this to the current ID. i.e. move_hosts without ID moves them _here_ -- move_hosts _with_ ID moves them _there_. Scott From sseago at redhat.com Thu Aug 7 20:51:55 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 07 Aug 2008 16:51:55 -0400 Subject: [Ovirt-devel] [PATCH 5/5] Sample code that shows how to use the API In-Reply-To: <1218067219-20169-6-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-6-git-send-email-dlutter@redhat.com> Message-ID: <489B606B.5080601@redhat.com> David Lutterkort wrote: > --- > wui/client/bin/ovirt-cli | 104 ++++++++++++++++++++++++++++++++++++++++++++++ > wui/client/lib/ovirt.rb | 63 ++++++++++++++++++++++++++++ > 2 files changed, 167 insertions(+), 0 deletions(-) > create mode 100755 wui/client/bin/ovirt-cli > create mode 100644 wui/client/lib/ovirt.rb > > diff --git a/wui/client/bin/ovirt-cli b/wui/client/bin/ovirt-cli > new file mode 100755 > index 0000000..cfb4697 > --- /dev/null > +++ b/wui/client/bin/ovirt-cli > @@ -0,0 +1,104 @@ > +#! /usr/bin/ruby > + > +# This is not really a CLI, it's just an example how > +# the API can be used for a variety of tasks > +# Eventually, this needs to be replaced by the real CLI > + > +# Run this on any machine that has activeresource installed. Since there is > +# no support for authentication yet, you need to connect to Mongrel > +# directly (and therefore expose port 3000 on the OVirt Server to the wider > +# network) > + > +require 'pp' > +require 'rubygems' > +require 'activeresource' > +require 'optparse' > + > +require 'ovirt' > + > +def move_random_host(hosts, pool) > + host = hosts[rand(hosts.size)] > + puts "Move #{host.hostname} to #{pool.name}" > + pool.hosts << host > + pool.save > +end > + > +def element_path(obj) > + "[#{obj.class.element_path(obj.id)}]" > +end > + > +def print_pool(pool) > + puts "\n\nPool #{pool.name}: #{pool.hosts.size} hosts, #{pool.storage_pools.size} storage pools #{element_path(pool)} " > + puts "=" * 75 > + pool.hosts.each do |h| > + printf "%-36s %s\n", h.hostname, element_path(h) > + end > + pool.storage_pools.each do |sp| > + type = sp.nfs? ? "NFS" : "iSCSI" > + printf "%-5s %-30s %s\n", type, sp.label, element_path(sp) > + end > + puts "-" * 75 > +end > + > +# Plumbing so we can find the OVirt server > +# "http://ovirt.watzmann.net:3000/ovirt/rest" > +OVirt::Base.site = ENV["OVIRT_SERVER"] > +opts = OptionParser.new("ovirt-cli GLOBAL_OPTS") > +opts.separator(" Run some things against an OVirt server") > +opts.separator("") > +opts.separator "Global options:" > +opts.on("-s", "--server=URL", "The OVirt server. Since there is no auth\n" + > + "#{" "*37}yet, must be the mongrel server port.\n" + > + "#{" "*37}Overrides env var OVIRT_SERVER") do |val| > + OVirt::Base.site = val > +end > + > +opts.order(ARGV) > + > +unless OVirt::Base.site > + $stderr.puts < +You must specify the OVirt server to connect to, either with the > +--server option or through the OVIRT_SERVER environment variable > +EOF > + exit 1 > +end > + > +# Get a single host by name > +host = OVirt::Host.find_by_hostname("node3.priv.ovirt.org") > +puts "#{host.uuid} has id #{host.id}" > + > +# What's in the default pool > +defpool = OVirt::HardwarePool.default_pool > +print_pool(defpool) > + > +# Create a new hardware pool > +mypool = OVirt::HardwarePool.find_by_path("/default/mypool") > +unless mypool > + puts "Create mypool" > + mypool = OVirt::HardwarePool.create( { :parent_id => defpool.id, > + :name => "mypool" } ) > +end > + > +# Move some hosts around > +puts > +if defpool.hosts.size > 1 > + move_random_host(defpool.hosts, mypool) > +elsif mypool.hosts.size > 0 > + move_random_host(mypool.hosts, defpool) > +end > + > +# Delete all storage pools for mypool and add a new one > +mypool.storage_pools.each do |sp| > + puts "Delete storage pool #{sp.id}" > + sp.destroy > +end > + > +storage_pool = OVirt::StoragePool.create( { :storage_type => "NFS", > + :hardware_pool_id => mypool.id, > + :ip_addr => "192.168.122.50", > + :export_path => "/exports/pool1" } ) > +puts "Created storage pool #{storage_pool.id}" > + > +# For some reason, mypool.reload doesn't work here > +mypool = OVirt::HardwarePool.find_by_path("/default/mypool") > +print_pool(mypool) > diff --git a/wui/client/lib/ovirt.rb b/wui/client/lib/ovirt.rb > new file mode 100644 > index 0000000..48739f4 > --- /dev/null > +++ b/wui/client/lib/ovirt.rb > @@ -0,0 +1,63 @@ > +require 'pp' > +require 'rubygems' > +require 'activeresource' > + > +module OVirt > + class Base < ActiveResource::Base ; end > + > + class HardwarePool < Base > + def self.find_by_path(path) > + find(:first, :params => { :path => path }) > + end > + > + def self.default_pool > + find(:first, :params => { :path => "/default" }) > + end > + end > + > We shouldn't rely on the fact that the default pool is named 'default'. This is probably an issue with Pool.find_by_path as well. Although an admin can't delete the default pool, he can rename it. Pool.get_default_pool will return the right pool regardless of its name though. From mmorsi at redhat.com Fri Aug 8 01:46:43 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 07 Aug 2008 21:46:43 -0400 Subject: [Ovirt-devel] [PATCH] oVirt / RRD Test Data In-Reply-To: <4898DD04.8070705@redhat.com> References: <4898DD04.8070705@redhat.com> Message-ID: <489BA583.5010909@redhat.com> Mohammed Morsi wrote: > Attached is my now functional ruby script to generate test / demo rrd > data and the oVirt patch required to get it working (a few small code > tweaks / fixes and a few fixture modifications). > Updated the script to circumvent the max string command problem. Now after intervals_per_command intervals, the scripts runs a rrdupdate command and resets the data list. The script now seems to work, even with an interval of 10 seconds, which is what collectd would normally generate. -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: demo-rrd-data.rb Type: application/x-ruby Size: 11788 bytes Desc: not available URL: From clalance at redhat.com Fri Aug 8 13:47:33 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 08 Aug 2008 15:47:33 +0200 Subject: [Ovirt-devel] [PATCH]: Add /lib/modules/*/lib and /lib/modules/*/crypto into managed node Message-ID: <489C4E75.80603@redhat.com> All, In order for iSCSI to work properly, we actually need a few of the kernel modules that we've blown away inside the managed node. Currently, if you try to attach to an iSCSI target, you get this in /var/log/messages in the managed node: > Aug 8 13:35:03 node201 kernel: session7: couldn't create a new > connection.<6>scsi11 : iSCSI Initiator over TCP/IP Aug 8 13:35:03 node201 > modprobe: WARNING: Could not open > '/lib/modules/2.6.25.11-97.fc9.x86_64/kernel/lib/libcrc32c.ko': No such file > or directory Aug 8 13:35:03 node201 modprobe: FATAL: Could not open > '/lib/modules/2.6.25.11-97.fc9.x86_64/kernel/crypto/crc32c.ko': No such file > or directory Aug 8 13:35:03 node201 kernel: connection8:0: Could not create > connection due to crc32c loading error. Make sure the crc32c module is built > as a module or into the kernel This patch just adds those two directories back in; if we really need the space savings, we can probably get rid of some other modules, but we definitely need the crc ones listed above. Signed-off-by: Chris Lalancette -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-managed-node-crypto-lib.patch Type: text/x-patch Size: 636 bytes Desc: not available URL: From clalance at redhat.com Fri Aug 8 13:49:29 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 08 Aug 2008 15:49:29 +0200 Subject: [Ovirt-devel] [PATCH]: Set forward delay to 0 in "bundled" mode Message-ID: <489C4EE9.4080401@redhat.com> When you are managing machines on a physical network, we build a bridge and then plug a real eth device into that bridge. However, by default there is a 15 second forwarding delay when we first add the device. To get around this, add "bridge setfd ovirtbr 0" right before adding the eth device to the bridge. This gets rid of the delay. Signed-off-by: Chris Lalancette -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-set-bridge-delay-0.patch Type: text/x-patch Size: 469 bytes Desc: not available URL: From clalance at redhat.com Fri Aug 8 14:09:21 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 08 Aug 2008 16:09:21 +0200 Subject: [Ovirt-devel] Lighter-weight "developer" setup Message-ID: <489C5391.6050602@redhat.com> The basic premise here is that we want to make it easier for people with limited amounts of hardware to get up and running with oVirt, and hopefully build a bigger community around oVirt. We've come a long way with the developer appliance + the fake managed nodes, but that only gets you so far. In particular, you can't really start guests (you can, but they are extremely slow), and the appliance is quite big to download (especially in far-away places, or with slower internet connections). To make the initial experience with oVirt more lightweight, we should drop the "fake managed nodes" and instead have the oVirt stuff be able to manage the underlying physical hardware it is running on. Not only does it give you the ability to run *real* KVM guests (assuming appropriate hardware, of course), it also gets us closer to being able to manage something other than dedicated oVirt nodes. There are two ways we can approach this; both have their pros and cons: 1. Still have an oVirt appliance that has everything installed inside of it. Then, this appliance "manages" the host that it is actually running on, and can install additional guests alongside the appliance. You need to protect the oVirt appliance a little bit so you don't accidentally destroy itself, but otherwise you can treat the underlying hardware just like any other node. Pros: Continued isolation of the oVirt configuration from the rest of the system. Similar to what we have today, so not a huge amount of configuration churn. Probably pretty quick to implement. Cons: Heavyweight solution, particularly for those with slow connections for downloading the appliance image, or without huge amounts of memory (if you only have a 2GB box, you end up sucking down almost half of your memory just for the appliance). 2. Get rid of the oVirt appliance completely, and just provide instructions/better scripts for installing all of the oVirt software directly on the host. Then the host runs the WUI, and you don't need to protect any "special" guests. Pros: Much more lightweight solution; you really only need to download and install a handful of RPMS, and configure everything correctly. Gets us much closer to the "production-style" install that we will eventually need. Cons: Much harder to integrate well with the rest of the system. Kerberos will be a problem in particular. Needs a *lot* of documentation. Is fairly different from what we are doing now in terms of scripting, so will probably take longer to get done. After thinking about it a little bit, I actually think we should implement both solutions. In the short term, we should definitely do solution 1, since I think we can get it done fairly easily and it's very compelling. In the slightly longer term, I think we should do solution 2, since we will have to for production anyway. There are two problems I can think of with either solution; both need to be addressed: a. DNS for kerberos. In solution 1, we can actually just continue to run dnsmasq on a secondary interface and make sure all guests get hooked up to that secondary interface. In solution 2, it becomes harder because we no longer have control of DNS. For home users, in particular, this may be an issue, since they may not be running *any* DNS server that they have control over. Perry has suggested that we could run the equivalent of a caching DNS server on the host, that would resolve internal names for us and forward everything else to an upstream DNS. This solution would probably work, we just have to work out the details. b. Announcing the host machine to the WUI. Right now, we expect the managed nodes to announce themselves to the WUI via a somewhat complicated dance. We can't reboot the host machine here, so we need some alternative mechanism to do this announcement, and pull the keytab from the WUI. Needs a little bit of thought, but we can probably run "ovirt-identify-node" (maybe slightly modified) to send information to the server and grab a keytab. Thoughts about this? Would this make oVirt easier for people to setup and use? Chris Lalancette From pmyers at redhat.com Fri Aug 8 14:15:11 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Fri, 08 Aug 2008 10:15:11 -0400 Subject: [Ovirt-devel] [PATCH] oVirt Node local installation In-Reply-To: <1218094541-23280-1-git-send-email-apevec@redhat.com> References: <1218094541-23280-1-git-send-email-apevec@redhat.com> Message-ID: <489C54EF.80905@redhat.com> Alan Pevec wrote: > oVirt partition can be initialized on a local flash or disk drive. > oVirt partition is a local primary ext2 partition, labeled OVIRT, > which contains: > /config - local oVirt configuration (ktab, local admin password...) > /boot - boot loader, kernel and initramfs > /LiveOS - oVirt Node compressed livecd image Is the initramfs repeated twice then? Once inside of the LiveCD and once in /boot? Or am I just reading this wrong... > oVirt partition is initialized using kernel boot parameters: > ovirt_init=usb|scsi[:serial#] > local installation target disk > usb|scsi - select disk type, as reported by udev ID_BUS > serial# - select exact disk using serial number, as reported by > udev ID_SERIAL [1] > e.g. ovirt_init=usb:Generic_STORAGE_DEVICE_0000145418-0:0 So if you leave off serial#, how does it choose the disk? Does it just take the first device (i.e. /dev/sda) and start using it? > ovirt_local_boot > install/update oVirt Node image on the local installation target disk > without this parameter oVirt partition is used for storing > oVirt configuration only > > [1] one-liner to list serial#s for all disks in the system: > for d in /dev/sd?; do eval $(udevinfo --query env --name $d); echo $d $ID_SERIAL;done Problem with this is it assumes someone has booted linux on the box before and has access to the above command. I think for the most part people will not have the serial #s to work with. It's fine to leave this functionality in, I just wonder how much it would be used. Other question is... Should we have a sane default in the init scripts? i.e. even if you don't pass in ovirt_init the init script does the following: 1. Looks to see if there is an existing OVIRT partition and if so mounts it 2. If one does not exist looks for the first hard disk with free space on it and creates the OVIRT partition there. 3. If no hard drive space is available then it looks for the first flash disk with free space on it and creates the OVIRT partition there. These partitions are for config only, unless the ovirt_local_boot was passed on the cmdline, in which case the local installation is done. This way the admin is not forced to always use the ovirt_init parameter on every machine in the datacenter. The node will do the right thing if the OVIRT partition is not already there. Also, steps 2 and 3 can be used to set up local swap if it doesn't already exist. Also, if ovirt_local_boot is passed on the command line, the OVIRT partition should never be placed on the same flash device as the one that was booted since that device is transient. Minor comments below... > Signed-off-by: Alan Pevec > --- > ovirt-host-creator/common-pkgs.ks | 1 - > ovirt-managed-node/src/scripts/ovirt | 64 +++++-- > ovirt-managed-node/src/scripts/ovirt-awake | 10 +- > ovirt-managed-node/src/scripts/ovirt-early | 248 +++++++++++++++++++++++- > ovirt-managed-node/src/scripts/ovirt-functions | 3 + > ovirt-managed-node/src/scripts/ovirt-post | 2 +- > 6 files changed, 298 insertions(+), 30 deletions(-) > > diff --git a/ovirt-host-creator/common-pkgs.ks b/ovirt-host-creator/common-pkgs.ks > index 2d1ad6e..bc08358 100644 > --- a/ovirt-host-creator/common-pkgs.ks > +++ b/ovirt-host-creator/common-pkgs.ks > @@ -48,7 +48,6 @@ ovirt-managed-node > -rhpl > -kbd > -usermode > --grub > -fedora-logos > -kpartx > -dmraid > diff --git a/ovirt-managed-node/src/scripts/ovirt b/ovirt-managed-node/src/scripts/ovirt > index 92ed330..b6dcbc6 100755 > --- a/ovirt-managed-node/src/scripts/ovirt > +++ b/ovirt-managed-node/src/scripts/ovirt > @@ -15,36 +15,70 @@ start() { > # and it needs to be at the front of the forward chain > iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT > > + krb5_conf=/etc/krb5.conf > + krb5_tab=/etc/libvirt/krb5.tab > + # retrieve config from local oVirt partition if available > + # krb5.conf krb5.tab > + # TODO local admin password, ssh server key - what else? > + ovirt=$(mktemp -d) > + cfg=$ovirt/config > + mount -r /dev/disk/by-label/$OVIRT_LABEL $ovirt > + if [ -e $cfg/krb5.conf ]; then > + cp -a $cfg/krb5.conf $krb5_conf > + fi > + if [ -e $cfg/krb5.tab ]; then > + cp -a $cfg/krb5.tab $krb5_tab > + fi > + if [ -s $krb5_tab ]; then > + krb5_tab= > + fi > + > find_srv ipa tcp > - if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then > - krb5_conf=/etc/krb5.conf > - if [ ! -s $krb5_conf ]; then > - rm -f $krb5_conf > - # FIXME this is IPA specific > - wget -q \ > - http://$SRV_HOST:$SRV_PORT/ipa/config/krb5.ini -O $krb5_conf \ > - || (echo "Failed to get $krb5_conf" && return 1) > + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" -a ! -s $krb5_conf ]; then > + rm -f $krb5_conf > + # FIXME this is IPA specific > + wget -q \ > + http://$SRV_HOST:$SRV_PORT/ipa/config/krb5.ini -O $krb5_conf > + if [ $? -ne 0 ]; then > + echo "Failed to get $krb5_conf"; return 1 > + fi > + # store config in oVirt partition > + if [ -e $cfg ]; then > + mount -o remount,rw $ovirt > + cp -a $krb5_conf $cfg/krb5.conf > fi > else > - echo "skipping Kerberos configuration, IPA service not available" > + echo "skipping Kerberos configuration" > fi > > find_srv identify tcp > if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then > - krb5_tab=/etc/libvirt/krb5.tab > - ovirt-awake start $krb5_tab $SRV_HOST $SRV_PORT > + ovirt-awake start $SRV_HOST $SRV_PORT $krb5_tab > + if [ $? -ne 0 ]; then > + echo "ovirt-awake failed"; return 1 > + fi > + # store config in oVirt partition > + if [ -n "$krb_tab" -a -e $cfg ]; then > + mount -o remount,rw $ovirt > + cp -a $krb5_tab $cfg/krb5.tab > + fi > else > - echo "skipping ovirt-awake, oVirt registration service not available" > + echo "skipping ovirt-awake, oVirt identify service not available" > fi > > + # cleanup > + umount $ovirt && rmdir $ovirt > + > find_srv collectd tcp > if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then > if [ -f $collectd_conf.in ]; then > collectd_conf=/etc/collectd.conf > sed -e "s/@COLLECTD_SERVER@/$SRV_HOST/" \ > -e "s/@COLLECTD_PORT@/$SRV_PORT/" $collectd_conf.in \ > - > $collectd_conf \ > - || (echo "Failed to write $collectd_conf" && return 1) > + > $collectd_conf > + if [ $? -ne 0 ]; then > + echo "Failed to write $collectd_conf"; return 1 > + fi > fi > else > echo "skipping collectd configuration, collectd service not available" > @@ -53,7 +87,7 @@ start() { > > case "$1" in > start) > - echo -n $"Starting ovirt: " > + printf "Starting ovirt: " > > { > start > diff --git a/ovirt-managed-node/src/scripts/ovirt-awake b/ovirt-managed-node/src/scripts/ovirt-awake > index 38d405e..fc03c9a 100755 > --- a/ovirt-managed-node/src/scripts/ovirt-awake > +++ b/ovirt-managed-node/src/scripts/ovirt-awake > @@ -63,10 +63,10 @@ start () { > > KEYTAB=`echo $REPLY | awk '{ print $2 }'` > > - if [ -n $KEYTAB ]; then > + if [ -n "$KEYTAB" -a -n "$KEYTAB_FILE" ]; then > echo "Retrieving keytab: '$KEYTAB'" > > - wget -q $KEYTAB --output-document=$KEYTAB_FILE > + wget -q "$KEYTAB" --output-document="$KEYTAB_FILE" > else > echo "No keytab to retrieve" > fi > @@ -84,9 +84,9 @@ start () { > > case "$1" in > start) > - KEYTAB_FILE=$2 > - SRV_HOST=$3 > - SRV_PORT=$4 > + SRV_HOST=$2 > + SRV_PORT=$3 > + KEYTAB_FILE=$4 > start > RETVAL=$? > ;; > diff --git a/ovirt-managed-node/src/scripts/ovirt-early b/ovirt-managed-node/src/scripts/ovirt-early > index aa0a49c..dada566 100755 > --- a/ovirt-managed-node/src/scripts/ovirt-early > +++ b/ovirt-managed-node/src/scripts/ovirt-early > @@ -10,6 +10,9 @@ > . /etc/init.d/functions > . /etc/init.d/ovirt-functions > > +# size of the oVirt partition in megabytes > +OVIRT_SIZE=64 > + > configure_from_network() { > DEVICE=$1 > if [ -n "$DEVICE" ]; then > @@ -51,21 +54,250 @@ configure_from_network() { > printf "default config applied." > } > > +# find_disk $bus $serial > +find_disk() { > + local bus=$1 > + local serial=$2 > + local live=$(readlink -e /dev/live) > + live=${live%[1-4]} > + for d in /dev/sd?; do > + eval $(udevinfo --query env --name $d) > + if [ "$ID_BUS" = "$bus" ]; then > + if [ -z "$serial" -o "$ID_SERIAL" = "$serial" ]; then > + # cannot install LiveOS over itself > + if [ -n "$live" -a "$d" = "$live" ]; then > + return 2 > + else > + echo $d > + return 0 > + fi > + fi > + fi > + done > + return 1 > +} > + > +# local_install $local_os $target > +# local_os - 1=install LiveOS and boot loader > +# 0=initialize oVirt partition only > +# target - target disk to hold the oVirt partition > +# usb|scsi[:serial#] > +# > +# oVirt partition is a local primary ext2 partition, labeled OVIRT, > +# which contains: > +# /config - local oVirt configuration (ktab, local admin password) > +# /boot - boot loader, kernel and initramfs > +# /LiveOS - oVirt Node compressed livecd image > + > +local_install() { > + local local_os=$1 > + local target=$2 > + local disk > + local part > + local live_dev=/dev/live > + if [ ! -e $live_dev ]; then > + # PXE boot > + live_dev=/dev/loop0 > + fi > + local ovirt_part=$(readlink -e /dev/disk/by-label/$OVIRT_LABEL) > + local ovirt_disk=${ovirt_part%[1-4]} > + if [ "$ovirt_disk" = "$ovirt_part" ]; then > + ovirt_disk= > + fi > + if [ -z "$target" ]; then > + live_part=$(readlink -e $live_dev) > + if [ -z "$ovirt_disk" -o "$ovirt_part" = "$live_part" ]; then > + return 1 > + fi > + mode=update > + disk=$ovirt_disk > + part=$ovirt_part > + else > + case "$target" in > + scsi*) > + bus=scsi > + serial=${target#scsi:} > + ;; > + usb*) > + bus=usb > + serial=${target#usb:} > + ;; > + *) > + return 1 > + ;; > + esac > + if [ "$serial" = "$bus" ]; then > + serial= > + fi > + disk=$(find_disk $bus $serial) > + rc=$? > + if [ $rc -eq 2 ]; then > + echo "target '$target' is the boot disk, aborting install" > + return 1 > + elif [ $rc -ne 0 ]; then > + echo "target disk '$target' not found" > + return 1 > + fi > + mode=install > + if [ -n "$ovirt_disk" ]; then > + if [ "$disk" = "$ovirt_disk" ]; then > + # target disk contains oVirt partition, select it for update > + # TODO force install option > + mode=update > + part=$ovirt_part > + else > + # remove label from oVirt partition, there can be only one > + e2label $ovirt_part "" > + fi > + fi > + if [ "$mode" = "install" ]; then > + printf "installing $disk..." | tee /dev/console > + dd if=/dev/zero bs=4096 count=1 of=$disk \ > + && parted -s $disk mklabel msdos \ > + && parted -s $disk mkpart primary ext2 0.0 $OVIRT_SIZE \ > + && partprobe -s $disk > + if [ $? -ne 0 ]; then > + echo "$disk partition creation failed"; return 1 > + fi > + part=${disk}1 > + udevsettle > + mkfs.ext2 -L $OVIRT_LABEL $part \ > + && tune2fs -c0 -i0 $part > + if [ $? -ne 0 ]; then > + echo "$disk ext2 creation failed"; return 1 > + fi > + # update by-label link manually, mkfs won't trigger udev > +ls -l /dev/disk > + mkdir -p /dev/disk/by-label > +ls -l /dev/disk/by-label Are the two ls lines above missing indentation? > + ln -sf $part /dev/disk/by-label/$OVIRT_LABEL > + fi > + fi > + > + if [ "$mode" = "update" ]; then > + printf "updating $ovirt_disk..." | tee /dev/console > + fi > + ovirt=$(mktemp -d) > + live=$(mktemp -d) > + mount -r $live_dev $live > + if [ $? -ne 0 ]; then > + echo "source image mount failed"; return 1 > + fi > + mount $part $ovirt > + if [ $? -ne 0 ]; then > + echo "target mount failed"; return 1 > + fi > + mkdir -p $ovirt/config > + # update local config using embedded in livecd image > + # TODO admin tool for adding /config into livecd image > + if [ -e $live/config/krb5.conf ]; then > + cp -a $live/config/krb5.conf $ovirt/config \ > + || echo "krb5.conf copy failed" > + fi > + if [ -e $live/config/krb5.tab ]; then > + cp -a $live/config/krb5.tab $ovirt/config \ > + || echo "krb5.tab copy failed" > + fi Nice. I hadn't considered this as a feature. We might also want to think about using this to distribute a site-wide SSH key for root user as well as /etc/passwd and /etc/shadow files. > + if [ $local_os = 0 ]; then > + # config update only, cleanup and continue booting > + umount $ovirt && rmdir $ovirt > + umount $live && rmdir $live > + else > + # install oVirt Node image for local boot > + if [ -e "$live/syslinux" ]; then > + syslinux=syslinux > + elif [ -e "$live/isolinux" ]; then > + syslinux=isolinux > + else > + syslinux= > + fi > + rm -rf $ovirt/boot > + rm -rf $ovirt/LiveOS > + mkdir -p $ovirt/boot/grub > + mkdir -p $ovirt/LiveOS > + cp -a $live/LiveOS/squashfs.img $ovirt/LiveOS \ > + && cp -a $live/$syslinux/initrd0.img $ovirt/boot \ > + && cp -a $live/$syslinux/vmlinuz0 $ovirt/boot > + if [ $? -ne 0 ]; then > + echo "image copy failed"; return 1 > + fi > + part_num=$(( ${part#$disk} - 1 )) > + cat > $ovirt/boot/grub/grub.conf < +default=0 > +timeout=2 > +hiddenmenu > +title oVirt Managed Node > + root (hd0,$part_num) > + kernel /boot/vmlinuz0 ro root=LABEL=OVIRT roottypefs=ext2 liveimg > + initrd /boot/initrd0.img > +EOF Ian will need to add to this patch later for some serial console stuff probably. > + grub-install --root-directory=$ovirt $disk >&2 > + if [ $? -ne 0 ]; then > + echo "boot loader install failed"; return 1 > + fi > + # remove 1.5 stages we don't need > + find $ovirt/boot/grub -name '*_stage1_5' ! -name e2fs_stage1_5 \ > + -exec rm {} \; > + # FIXME avoid reboot loops > + # - cobbler: pxe-only-once, needs xmlrpc to cobbler server > + # - plain dnsmasq with tftp: ? > + # temp. workaround: > + printf "oVirt local installation finished, press Enter to reboot." \ > + > /dev/console > + read key > + if [ "$key" = "debug" ]; then > + sh > /dev/console 2>&1 > + fi > + reboot > + fi > +} > + > start() { > - # find boot interface from cmdline > - # IPAPPEND 2 in pxelinux.cfg appends e.g. BOOTIF=01-00-16-3e-12-34-57 > - BOOTIF= > + # oVirt boot parameters > + # BOOTIF= (appended by pxelinux) > + # ovirt_init=usb|scsi[:serial#] > + # ovirt_local_boot > + > + # BOOTIF= (appended by pxelinux) The BOOTIF line is in there twice. > + # network boot interface is assumed to be on management network where > + # oVirt Server is reachable > + # IPAPPEND 2 in pxelinux.cfg appends MAC address of the booting node > + # e.g. BOOTIF=01-00-16-3e-12-34-57 > + bootif= > + > + # ovirt_init=usb|scsi[:serial#] > + # local installation target disk > + # usb|scsi - select disk type, as reported by udev ID_BUS > + # serial# - select exact disk using serial number, as reported by > + # udev ID_SERIAL > + # e.g. ovirt_init=usb:Generic_STORAGE_DEVICE_0000145418-0:0 > + target= > + > + # ovirt_local_boot > + # install/update oVirt Node image on the local installation target disk > + local_os=0 > + > for i in $(cat /proc/cmdline); do > case $i in > BOOTIF=??-??-??-??-??-??-??) > - i=${i/#BOOTIF=??-/} > - BOOTMAC=${i//-/:} > - BOOTIF=$(grep -l $BOOTMAC /sys/class/net/eth*/address|rev|cut -d/ -f2|rev) > + i=${i#BOOTIF=??-} > + bootif=$(grep -il $(echo $i|sed 's/-/:/g') /sys/class/net/eth*/address|rev|cut -d/ -f2|rev) > + ;; > + ovirt_init=*) > + target=${i#ovirt_init=} > + ;; > + ovirt_local_boot) > + local_os=1 > ;; > esac > done > > - configure_from_network $BOOTIF > + # TODO pass extra kernel params from /proc/cmdline e.g. console=... > + set -x > + local_install $local_os $target > + set +x > + configure_from_network $bootif > > # find all of the partitions on the system > > @@ -96,7 +328,7 @@ start() { > > case "$1" in > start) > - echo -n $"Starting ovirt-early: " > + printf "Starting ovirt-early: " > > { > start > diff --git a/ovirt-managed-node/src/scripts/ovirt-functions b/ovirt-managed-node/src/scripts/ovirt-functions > index 5b530f7..083e13d 100644 > --- a/ovirt-managed-node/src/scripts/ovirt-functions > +++ b/ovirt-managed-node/src/scripts/ovirt-functions > @@ -2,6 +2,9 @@ > > OVIRT_LOGFILE=/var/log/ovirt.log > > +# label of the oVirt partition > +OVIRT_LABEL=OVIRT > + > find_srv() > { > local dnsreply > diff --git a/ovirt-managed-node/src/scripts/ovirt-post b/ovirt-managed-node/src/scripts/ovirt-post > index ecc6ff4..1267f40 100755 > --- a/ovirt-managed-node/src/scripts/ovirt-post > +++ b/ovirt-managed-node/src/scripts/ovirt-post > @@ -22,7 +22,7 @@ start() { > > case "$1" in > start) > - echo -n $"Starting ovirt-post: " > + printf "Starting ovirt-post: " > > { > start -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From pmyers at redhat.com Fri Aug 8 14:17:43 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Fri, 08 Aug 2008 10:17:43 -0400 Subject: [Ovirt-devel] [PATCH]: Add /lib/modules/*/lib and /lib/modules/*/crypto into managed node In-Reply-To: <489C4E75.80603@redhat.com> References: <489C4E75.80603@redhat.com> Message-ID: <489C5587.9020700@redhat.com> Chris Lalancette wrote: > All, > In order for iSCSI to work properly, we actually need a few of the kernel > modules that we've blown away inside the managed node. Currently, if you try to > attach to an iSCSI target, you get this in /var/log/messages in the managed node: > >> Aug 8 13:35:03 node201 kernel: session7: couldn't create a new >> connection.<6>scsi11 : iSCSI Initiator over TCP/IP Aug 8 13:35:03 node201 >> modprobe: WARNING: Could not open >> '/lib/modules/2.6.25.11-97.fc9.x86_64/kernel/lib/libcrc32c.ko': No such file >> or directory Aug 8 13:35:03 node201 modprobe: FATAL: Could not open >> '/lib/modules/2.6.25.11-97.fc9.x86_64/kernel/crypto/crc32c.ko': No such file >> or directory Aug 8 13:35:03 node201 kernel: connection8:0: Could not create >> connection due to crc32c loading error. Make sure the crc32c module is built >> as a module or into the kernel > > This patch just adds those two directories back in; if we really need the space > savings, we can probably get rid of some other modules, but we definitely need > the crc ones listed above. > > Signed-off-by: Chris Lalancette Ick, this is my fault. I was a little over-zealous with image minimization. ACK Perry From pmyers at redhat.com Fri Aug 8 14:18:05 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Fri, 08 Aug 2008 10:18:05 -0400 Subject: [Ovirt-devel] [PATCH]: Set forward delay to 0 in "bundled" mode In-Reply-To: <489C4EE9.4080401@redhat.com> References: <489C4EE9.4080401@redhat.com> Message-ID: <489C559D.80603@redhat.com> Chris Lalancette wrote: > When you are managing machines on a physical network, we build a bridge and then > plug a real eth device into that bridge. However, by default there is a 15 > second forwarding delay when we first add the device. To get around this, add > "bridge setfd ovirtbr 0" right before adding the eth device to the bridge. This > gets rid of the delay. > > Signed-off-by: Chris Lalancette ACK Perry From clalance at redhat.com Fri Aug 8 14:27:13 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 08 Aug 2008 16:27:13 +0200 Subject: [Ovirt-devel] [PATCH]: Add /lib/modules/*/lib and /lib/modules/*/crypto into managed node In-Reply-To: <489C4E75.80603@redhat.com> References: <489C4E75.80603@redhat.com> Message-ID: <489C57C1.5070303@redhat.com> Chris Lalancette wrote: > All, > In order for iSCSI to work properly, we actually need a few of the kernel > modules that we've blown away inside the managed node. Currently, if you try to > attach to an iSCSI target, you get this in /var/log/messages in the managed node: > >> Aug 8 13:35:03 node201 kernel: session7: couldn't create a new >> connection.<6>scsi11 : iSCSI Initiator over TCP/IP Aug 8 13:35:03 node201 >> modprobe: WARNING: Could not open >> '/lib/modules/2.6.25.11-97.fc9.x86_64/kernel/lib/libcrc32c.ko': No such file >> or directory Aug 8 13:35:03 node201 modprobe: FATAL: Could not open >> '/lib/modules/2.6.25.11-97.fc9.x86_64/kernel/crypto/crc32c.ko': No such file >> or directory Aug 8 13:35:03 node201 kernel: connection8:0: Could not create >> connection due to crc32c loading error. Make sure the crc32c module is built >> as a module or into the kernel > > This patch just adds those two directories back in; if we really need the space > savings, we can probably get rid of some other modules, but we definitely need > the crc ones listed above. Committed. Chris Lalancette From clalance at redhat.com Fri Aug 8 14:27:27 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 08 Aug 2008 16:27:27 +0200 Subject: [Ovirt-devel] [PATCH]: Set forward delay to 0 in "bundled" mode In-Reply-To: <489C4EE9.4080401@redhat.com> References: <489C4EE9.4080401@redhat.com> Message-ID: <489C57CF.7000305@redhat.com> Chris Lalancette wrote: > When you are managing machines on a physical network, we build a bridge and then > plug a real eth device into that bridge. However, by default there is a 15 > second forwarding delay when we first add the device. To get around this, add > "bridge setfd ovirtbr 0" right before adding the eth device to the bridge. This > gets rid of the delay. Committed. Chris Lalancette From dlutter at redhat.com Fri Aug 8 23:16:52 2008 From: dlutter at redhat.com (David Lutterkort) Date: Fri, 08 Aug 2008 16:16:52 -0700 Subject: [Ovirt-devel] [PATCH 2/5] API for storage pools In-Reply-To: <489B2F63.2030508@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-3-git-send-email-dlutter@redhat.com> <489B2F63.2030508@redhat.com> Message-ID: <1218237412.23699.28.camel@localhost.localdomain> On Thu, 2008-08-07 at 13:22 -0400, Scott Seago wrote: > David Lutterkort wrote: > > - List and filter storage pools by ipaddr, path, target and hardware_pool_id > > - Show an individual storage pool > > - Create a new storage pool > > - Destroy a storage pool > > --- > > > I'll mention the obvious comment that we've got to filter all this stuff > by permissions (applies to all patches). Yes, that's one of the reasons why I would like to serve WUI and API out of the same controllers. > > + def create > > + # Somehow the attribute 'type' never makes it through > > + # Maybe because there is a (deprecated) Object.type ? > > > Yes, ActiveRecord has docs that address this. You have to say foo[:type] > rather than foo.type ! The issue was that 'type' never showed up on the other side, i.e. ActiveResource did not even send it as part of the request. > We've run into the same problem with target. foo[:target] rather than > foo.target > > I'm not sure how this affects the API though -- other than making sure > we set/retrieve type and target attributes correctly. > > + pool = params[:storage_pool] > > + type = pool[:storage_type] > > + pool.delete(:storage_type) > > + @storage_pool = StoragePool.factory(type, pool) > > + respond_to do |format| > > + if @storage_pool > > + if @storage_pool.save > > > We've tried to be consistent in using save! and dealing with errors in > rescue blocks, as it's too easy to let error returns fall through this > way. In addition, for multi-object transactions, this lets us deal with > errors in one place for the whole transaction. Ok .. makes sense David From dlutter at redhat.com Fri Aug 8 23:20:41 2008 From: dlutter at redhat.com (David Lutterkort) Date: Fri, 08 Aug 2008 16:20:41 -0700 Subject: [Ovirt-devel] [PATCH 3/5] API for Hardware Pools In-Reply-To: <489B54C0.6010302@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-4-git-send-email-dlutter@redhat.com> <489B54C0.6010302@redhat.com> Message-ID: <1218237641.23699.33.camel@localhost.localdomain> On Thu, 2008-08-07 at 16:02 -0400, Scott Seago wrote: > David Lutterkort wrote: > > + # FIXME: Why does move_hosts need an explicit pool_id ? > > > The pool_id specifies what pool we're moving the hosts to, although it > probably makes sense to default this to the current ID. i.e. move_hosts > without ID moves them _here_ -- move_hosts _with_ ID moves them _there_. What I was getting at with this comment is that HardwarePool.move_hosts is an instance method, but does not depend on the instance it is called on. It should either become a class method or use the id of the pool it's called on (in which case 'add_hosts' is probably a better name) David From dlutter at redhat.com Fri Aug 8 23:23:13 2008 From: dlutter at redhat.com (David Lutterkort) Date: Fri, 08 Aug 2008 16:23:13 -0700 Subject: [Ovirt-devel] [PATCH 5/5] Sample code that shows how to use the API In-Reply-To: <489B606B.5080601@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-6-git-send-email-dlutter@redhat.com> <489B606B.5080601@redhat.com> Message-ID: <1218237793.23699.36.camel@localhost.localdomain> On Thu, 2008-08-07 at 16:51 -0400, Scott Seago wrote: > David Lutterkort wrote: > > diff --git a/wui/client/lib/ovirt.rb b/wui/client/lib/ovirt.rb > > new file mode 100644 > > index 0000000..48739f4 > > --- /dev/null > > +++ b/wui/client/lib/ovirt.rb > > @@ -0,0 +1,63 @@ > > +require 'pp' > > +require 'rubygems' > > +require 'activeresource' > > + > > +module OVirt > > + class Base < ActiveResource::Base ; end > > + > > + class HardwarePool < Base > > + def self.find_by_path(path) > > + find(:first, :params => { :path => path }) > > + end > > + > > + def self.default_pool > > + find(:first, :params => { :path => "/default" }) > > + end > > + end > > + > > > We shouldn't rely on the fact that the default pool is named 'default'. > This is probably an issue with Pool.find_by_path as well. Although an > admin can't delete the default pool, he can rename it. > Pool.get_default_pool will return the right pool regardless of its name > though. Agreed ... would looking for the pool with id 1 be better ? Or should the fact that you want the root pool be communicated entirely differently ? David From jim at meyering.net Mon Aug 11 09:16:59 2008 From: jim at meyering.net (Jim Meyering) Date: Mon, 11 Aug 2008 11:16:59 +0200 Subject: [Ovirt-devel] broken windows [Re: [PATCH] Adds max/min methods ... In-Reply-To: <489B1E33.4030201@redhat.com> (mark wagner's message of "Thu, 07 Aug 2008 12:09:23 -0400") References: <20080807070645.GC26662@redhat.com> <87wsiset9d.fsf@rho.meyering.net> <489B1E33.4030201@redhat.com> Message-ID: <87sktb6hdg.fsf_-_@rho.meyering.net> mark wagner wrote: > The Stats changes are actually mine, not Steve's. Since he alone > needed to integrate > with my changes and effectively tested them for me, I asked him to > just submit > them. Bear in mind that my review was addressed to Steve, since nothing suggested the changes were by anyone else. ... >> Hi Steve, >> >> A fine net change, overall. >> >> It's great to do whitespace clean-up, but please keep that sort of change >> separate from anything substantial. Otherwise, it's more work for the >> reviewer to separate the trivially-ignorable whitespace changes from >> the ones that are significant. >> > The whitespace changes are mine. I thought you wanted them out and had > put such an edict in place. Sorry for not understanding Eliminating all offending white space at once would be disruptive in the short term. The current policy is gentler: add no _new_ instances. ... >> Also, I noticed that the pre-existing style is to initialize variables at >> the top of a block or function. It's better to avoid that style, and >> instead to place any initialization as near as possible to the first use. >> For example, if you move the initializations of my_max and my_min down >> so they're nearer their first uses, readers don't have to worry about >> whether they're used uninitialized (now the initialization is just before >> the loop), or if the values are modified between initialization and >> whatever use the reader is looking at. >> > I was actually taught just the opposite, init at the top so its easy to find > the inits. In my more performance sensitive c++ apps, I tend to init just > before the variable is first used (or just outside of the loop as needed) > if there is a chance of getting out of the function before the variable > would be used. Thus avoiding unneeded initializations. > > I appreciate the style suggestions, coming from a long C/C++ background, > I tend do things the way I've been doing them for the last 20+ years. I > try to be fairly flexible in how I write or change code. For instance I > matched the style in graph_controller.rb for previous changes that I've > made. However, since the ovirt program doesn't have a predefined style > guide and I am the one writing and maintaining the Stats code, I will > continue to write it in a manner that is easiest for me to support. > > I'm sorry if this upsets your style preferences, but functionality is > more important to me over style. I also tend to worry when people focus > on the style w/o specifying a style guide for the program. I make the suggestion to move declarations down whenever possible because not only does that make the code more readable, but it also makes it more robust in the face of continued development and maintenance, regardless of the language. Reread the paragraph above, and imagine what can happen when the distance between initialization and first use increases. It's only a suggestion (directed to someone else, even) after all, so you're free to ignore it. The bad white space policy is also important, and likewise, is not prompted by some whim or idle preference. I have seen so many cases in which bad white space has led to bugs (resulting from mis-resolved merge conflicts that should never have arisen) that it is clearly worthwhile to avoid the root cause. Bad white space can even be a direct source of bugs in Makefiles and in languages like C and C++ (in strings and CPP directives), not to mention Python. > Don't you > think oVirt has bigger issues to deal with other than trailing whitespace > and where I init my variables? Maybe you haven't heard about the broken windows theory? http://tinyurl.com/5dy4fw aka http://www.davecheong.com/2006/06/30/broken-windows-theory-in-software-and-your-personal-life/ From apevec at redhat.com Mon Aug 11 11:52:46 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 11 Aug 2008 13:52:46 +0200 Subject: [Ovirt-devel] [PATCH] Adds max/min methods to StatsDataList. Limited cleanup in graph_controller.rb. First stab at stats data retrieval for new graphing approach. In-Reply-To: <489B1E33.4030201@redhat.com> References: <20080807070645.GC26662@redhat.com> <87wsiset9d.fsf@rho.meyering.net> <489B1E33.4030201@redhat.com> Message-ID: <48A0280E.6030207@redhat.com> mark wagner wrote: ... > I'm sorry if this upsets your style preferences, but functionality is > more important to me over style. I also tend to worry when people focus > on the style w/o specifying a style guide for the program. Don't you > think oVirt has bigger issues to deal with other than trailing whitespace > and where I init my variables? I'd like to think of whitespace/formatting as hygiene - might be a style issue but it helps towards healthy life :) From clalance at redhat.com Mon Aug 11 13:15:54 2008 From: clalance at redhat.com (Chris Lalancette) Date: Mon, 11 Aug 2008 15:15:54 +0200 Subject: [Ovirt-devel] [PATCH]: Open up port 49152 on the managed node Message-ID: <1218460554-22006-1-git-send-email-clalance@redhat.com> Make sure to open up the 49152 port on the managed nodes. This is to support live migration through libvirt. The basic situation is that libvirtd is currently single-threaded, and the migrate command we are using is synchronous, so it is never the case that we can have more than 1 live migration happening at a time. In the future, it might be possible that libvirtd will become multi-threaded, at which time we will have to address this differently. However, also in the future, for secure live migration, we are going to want to proxy the migration stuff via the libvirt channel. Either way we are going to need to change in the future; this is good enough for now. Signed-off-by: Chris Lalancette diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index 4daf264..37e2f43 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -29,6 +29,7 @@ cat > /etc/sysconfig/iptables << \EOF -A INPUT -i lo -j ACCEPT -A INPUT -p tcp --dport 16509 -j ACCEPT -A INPUT -p tcp --dport 22 -j ACCEPT +-A INPUT -p tcp --dport 49152 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT From clalance at redhat.com Mon Aug 11 13:16:29 2008 From: clalance at redhat.com (Chris Lalancette) Date: Mon, 11 Aug 2008 15:16:29 +0200 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database Message-ID: <1218460589-22051-1-git-send-email-clalance@redhat.com> When inserting memory values into the database, ovirt-identify-node is sending over values in kilobytes. The database is also in kilobytes. Don't run a non-sensical "mb_to_kb" conversion on the memory value before sticking it into the database. Signed-off-by: Chris Lalancette diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index a1bda3d..881b2ae 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -219,7 +219,7 @@ class HostBrowser "hostname" => host_info['HOSTNAME'], "hypervisor_type" => host_info['HYPERVISOR_TYPE'], "arch" => host_info['ARCH'], - "memory_in_mb" => host_info['MEMSIZE'], + "memory" => host_info['MEMSIZE'], "is_disabled" => 0, "hardware_pool" => HardwarePool.get_default_pool, # Let host-status mark it available when it @@ -232,7 +232,7 @@ class HostBrowser host.uuid = host_info['UUID'] host.hostname = host_info['HOSTNAME'] host.arch = host_info['ARCH'] - host.memory_in_mb = host_info['MEMSIZE'] + host.memory = host_info['MEMSIZE'] end # delete an existing CPUs and create new ones based on the data From clalance at redhat.com Mon Aug 11 13:17:00 2008 From: clalance at redhat.com (Chris Lalancette) Date: Mon, 11 Aug 2008 15:17:00 +0200 Subject: [Ovirt-devel] [PATCH]: Don't reject FORWARD chain on the managed node Message-ID: <1218460620-22092-1-git-send-email-clalance@redhat.com> Duh. We can't reject everything on the FORWARD chain, since we are basically forwarding all packets through from the guests. Remove the rule from the chain completely; we might be able to do better later, but at least things work this way. Signed-off-by: Chris Lalancette diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks index 37e2f43..a91a0c1 100644 --- a/ovirt-host-creator/common-post.ks +++ b/ovirt-host-creator/common-post.ks @@ -31,7 +31,6 @@ cat > /etc/sysconfig/iptables << \EOF -A INPUT -p tcp --dport 22 -j ACCEPT -A INPUT -p tcp --dport 49152 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited --A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT EOF From slinabery at redhat.com Mon Aug 11 14:29:52 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 11 Aug 2008 09:29:52 -0500 Subject: [Ovirt-devel] [PATCH]: Open up port 49152 on the managed node In-Reply-To: <1218460554-22006-1-git-send-email-clalance@redhat.com> References: <1218460554-22006-1-git-send-email-clalance@redhat.com> Message-ID: <20080811142952.GA2830@redhat.com> On Mon, Aug 11, 2008 at 03:15:54PM +0200, Chris Lalancette wrote: > Make sure to open up the 49152 port on the managed nodes. This is to support > live migration through libvirt. The basic situation is that libvirtd is > currently single-threaded, and the migrate command we are using is > synchronous, so it is never the case that we can have more than 1 live > migration happening at a time. In the future, it might be possible that > libvirtd will become multi-threaded, at which time we will have to address > this differently. However, also in the future, for secure live migration, we > are going to want to proxy the migration stuff via the libvirt channel. Either > way we are going to need to change in the future; this is good enough for now. > > Signed-off-by: Chris Lalancette > > diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks > index 4daf264..37e2f43 100644 > --- a/ovirt-host-creator/common-post.ks > +++ b/ovirt-host-creator/common-post.ks > @@ -29,6 +29,7 @@ cat > /etc/sysconfig/iptables << \EOF > -A INPUT -i lo -j ACCEPT > -A INPUT -p tcp --dport 16509 -j ACCEPT > -A INPUT -p tcp --dport 22 -j ACCEPT > +-A INPUT -p tcp --dport 49152 -j ACCEPT > -A INPUT -j REJECT --reject-with icmp-host-prohibited > -A FORWARD -j REJECT --reject-with icmp-host-prohibited > COMMIT > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel Although this change is trivial and probably does not require an ACK, I say ACK. From slinabery at redhat.com Mon Aug 11 14:45:00 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 11 Aug 2008 09:45:00 -0500 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <1218460589-22051-1-git-send-email-clalance@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> Message-ID: <20080811144500.GB2830@redhat.com> On Mon, Aug 11, 2008 at 03:16:29PM +0200, Chris Lalancette wrote: > When inserting memory values into the database, ovirt-identify-node is > sending over values in kilobytes. The database is also in kilobytes. > Don't run a non-sensical "mb_to_kb" conversion on the memory value > before sticking it into the database. > > Signed-off-by: Chris Lalancette > > diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb > index a1bda3d..881b2ae 100755 > --- a/wui/src/host-browser/host-browser.rb > +++ b/wui/src/host-browser/host-browser.rb > @@ -219,7 +219,7 @@ class HostBrowser > "hostname" => host_info['HOSTNAME'], > "hypervisor_type" => host_info['HYPERVISOR_TYPE'], > "arch" => host_info['ARCH'], > - "memory_in_mb" => host_info['MEMSIZE'], > + "memory" => host_info['MEMSIZE'], > "is_disabled" => 0, > "hardware_pool" => HardwarePool.get_default_pool, > # Let host-status mark it available when it > @@ -232,7 +232,7 @@ class HostBrowser > host.uuid = host_info['UUID'] > host.hostname = host_info['HOSTNAME'] > host.arch = host_info['ARCH'] > - host.memory_in_mb = host_info['MEMSIZE'] > + host.memory = host_info['MEMSIZE'] > end > > # delete an existing CPUs and create new ones based on the data > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel I don't think this is necessary. The migration file for CreateHosts (currently wui/src/db/migrate/002_create_hosts.rb) doesn't define memory_in_mb, so this attribute is never being stuck into the database. I'm not sure I understand what effect this: "memory_in_mb" => host_info['MEMSIZE'], was intended to have in host-browser.rb in the first place (creating an instance of Host using a non-existent attribute? Do I just not know ruby well enough to know what's going on here?) Thanks, Steve From slinabery at redhat.com Mon Aug 11 14:47:03 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 11 Aug 2008 09:47:03 -0500 Subject: [Ovirt-devel] [PATCH]: Don't reject FORWARD chain on the managed node In-Reply-To: <1218460620-22092-1-git-send-email-clalance@redhat.com> References: <1218460620-22092-1-git-send-email-clalance@redhat.com> Message-ID: <20080811144702.GC2830@redhat.com> On Mon, Aug 11, 2008 at 03:17:00PM +0200, Chris Lalancette wrote: > Duh. We can't reject everything on the FORWARD chain, since we are basically > forwarding all packets through from the guests. Remove the rule from the > chain completely; we might be able to do better later, but at least things > work this way. > > Signed-off-by: Chris Lalancette > > diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks > index 37e2f43..a91a0c1 100644 > --- a/ovirt-host-creator/common-post.ks > +++ b/ovirt-host-creator/common-post.ks > @@ -31,7 +31,6 @@ cat > /etc/sysconfig/iptables << \EOF > -A INPUT -p tcp --dport 22 -j ACCEPT > -A INPUT -p tcp --dport 49152 -j ACCEPT > -A INPUT -j REJECT --reject-with icmp-host-prohibited > --A FORWARD -j REJECT --reject-with icmp-host-prohibited > COMMIT > EOF > > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK. Not sure what the better solution is, but I agree that we need to let the guests' packets through :) From markmc at redhat.com Mon Aug 11 14:47:24 2008 From: markmc at redhat.com (Mark McLoughlin) Date: Mon, 11 Aug 2008 15:47:24 +0100 Subject: [Ovirt-devel] [PATCH]: Don't reject FORWARD chain on the managed node In-Reply-To: <1218460620-22092-1-git-send-email-clalance@redhat.com> References: <1218460620-22092-1-git-send-email-clalance@redhat.com> Message-ID: <1218466044.9936.1.camel@muff> On Mon, 2008-08-11 at 15:17 +0200, Chris Lalancette wrote: > Duh. We can't reject everything on the FORWARD chain, since we are basically > forwarding all packets through from the guests. Remove the rule from the > chain completely; we might be able to do better later, but at least things > work this way. > > Signed-off-by: Chris Lalancette > > diff --git a/ovirt-host-creator/common-post.ks b/ovirt-host-creator/common-post.ks > index 37e2f43..a91a0c1 100644 > --- a/ovirt-host-creator/common-post.ks > +++ b/ovirt-host-creator/common-post.ks > @@ -31,7 +31,6 @@ cat > /etc/sysconfig/iptables << \EOF > -A INPUT -p tcp --dport 22 -j ACCEPT > -A INPUT -p tcp --dport 49152 -j ACCEPT > -A INPUT -j REJECT --reject-with icmp-host-prohibited > --A FORWARD -j REJECT --reject-with icmp-host-prohibited I'd like the default rule in Fedora to be: -A FORWARD -m physdev ! --physdev-is-bridged -j REJECT --reject-with icmp-admin-prohibited see: https://bugzilla.redhat.com/221828 That should work here too. Cheers, Mark. From sseago at redhat.com Mon Aug 11 14:54:19 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 11 Aug 2008 10:54:19 -0400 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <20080811144500.GB2830@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> <20080811144500.GB2830@redhat.com> Message-ID: <48A0529B.6060805@redhat.com> Steve Linabery wrote: > On Mon, Aug 11, 2008 at 03:16:29PM +0200, Chris Lalancette wrote: > >> When inserting memory values into the database, ovirt-identify-node is >> sending over values in kilobytes. The database is also in kilobytes. >> Don't run a non-sensical "mb_to_kb" conversion on the memory value >> before sticking it into the database. >> >> Signed-off-by: Chris Lalancette >> >> diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb >> index a1bda3d..881b2ae 100755 >> --- a/wui/src/host-browser/host-browser.rb >> +++ b/wui/src/host-browser/host-browser.rb >> @@ -219,7 +219,7 @@ class HostBrowser >> "hostname" => host_info['HOSTNAME'], >> "hypervisor_type" => host_info['HYPERVISOR_TYPE'], >> "arch" => host_info['ARCH'], >> - "memory_in_mb" => host_info['MEMSIZE'], >> + "memory" => host_info['MEMSIZE'], >> "is_disabled" => 0, >> "hardware_pool" => HardwarePool.get_default_pool, >> # Let host-status mark it available when it >> @@ -232,7 +232,7 @@ class HostBrowser >> host.uuid = host_info['UUID'] >> host.hostname = host_info['HOSTNAME'] >> host.arch = host_info['ARCH'] >> - host.memory_in_mb = host_info['MEMSIZE'] >> + host.memory = host_info['MEMSIZE'] >> end >> >> # delete an existing CPUs and create new ones based on the data >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> > > I don't think this is necessary. The migration file for CreateHosts (currently wui/src/db/migrate/002_create_hosts.rb) doesn't define memory_in_mb, so this attribute is never being stuck into the database. > > memory_in_mb is a method defined on Host which converts from MB to kb to stuff in the db. The idea is when you have a number already in kb, you just set host.memory, but if your value is in mb (as it would be if you're soliciting user input via the WUI), you set host.memory_in_mb, which converts to kb and stores in the db. > I'm not sure I understand what effect this: > "memory_in_mb" => host_info['MEMSIZE'], > > was intended to have in host-browser.rb in the first place (creating an instance of Host using a non-existent attribute? Do I just not know ruby well enough to know what's going on here?) > > Well the attribute doesn't exist, but as mentioned above, there's a method with this name that sets the proper memory value (assuming what you have is in megabytes). On this particular change, I seem to recall a bug a while back where we had to change the host browser code to use the _in_mb methods to fix an earlier problem. Could it be that the semantics of host_browser has changed and that, at one point, we were passing memory in MB? In a larger sense (i.e. beyond this particular bugfix, etc.) we've talked about how we need a more robust way of dealing with storage/memory sizing and conversion/display. As we see here, it's currently a bit fragile. Scott > Thanks, > Steve > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From slinabery at redhat.com Mon Aug 11 15:08:17 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 11 Aug 2008 10:08:17 -0500 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <48A0529B.6060805@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> <20080811144500.GB2830@redhat.com> <48A0529B.6060805@redhat.com> Message-ID: <20080811150817.GD2830@redhat.com> On Mon, Aug 11, 2008 at 10:54:19AM -0400, Scott Seago wrote: > Steve Linabery wrote: >> On Mon, Aug 11, 2008 at 03:16:29PM +0200, Chris Lalancette wrote: >> >>> When inserting memory values into the database, ovirt-identify-node is >>> sending over values in kilobytes. The database is also in kilobytes. >>> Don't run a non-sensical "mb_to_kb" conversion on the memory value >>> before sticking it into the database. >>> Signed-off-by: Chris Lalancette >>> >>> diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb >>> index a1bda3d..881b2ae 100755 >>> --- a/wui/src/host-browser/host-browser.rb >>> +++ b/wui/src/host-browser/host-browser.rb >>> @@ -219,7 +219,7 @@ class HostBrowser >>> "hostname" => host_info['HOSTNAME'], >>> "hypervisor_type" => host_info['HYPERVISOR_TYPE'], >>> "arch" => host_info['ARCH'], >>> - "memory_in_mb" => host_info['MEMSIZE'], >>> + "memory" => host_info['MEMSIZE'], >>> "is_disabled" => 0, >>> "hardware_pool" => HardwarePool.get_default_pool, >>> # Let host-status mark it available when it >>> @@ -232,7 +232,7 @@ class HostBrowser >>> host.uuid = host_info['UUID'] >>> host.hostname = host_info['HOSTNAME'] >>> host.arch = host_info['ARCH'] >>> - host.memory_in_mb = host_info['MEMSIZE'] >>> + host.memory = host_info['MEMSIZE'] >>> end >>> # delete an existing CPUs and create new ones based on the >>> data >>> >>> _______________________________________________ >>> Ovirt-devel mailing list >>> Ovirt-devel at redhat.com >>> https://www.redhat.com/mailman/listinfo/ovirt-devel >>> >> >> I don't think this is necessary. The migration file for CreateHosts (currently wui/src/db/migrate/002_create_hosts.rb) doesn't define memory_in_mb, so this attribute is never being stuck into the database. >> >> > memory_in_mb is a method defined on Host which converts from MB to kb to > stuff in the db. The idea is when you have a number already in kb, you > just set host.memory, but if your value is in mb (as it would be if > you're soliciting user input via the WUI), you set host.memory_in_mb, > which converts to kb and stores in the db. > >> I'm not sure I understand what effect this: >> "memory_in_mb" => host_info['MEMSIZE'], >> >> was intended to have in host-browser.rb in the first place (creating an instance of Host using a non-existent attribute? Do I just not know ruby well enough to know what's going on here?) >> >> > Well the attribute doesn't exist, but as mentioned above, there's a > method with this name that sets the proper memory value (assuming what > you have is in megabytes). For the record, there is only memory_in_mb= that does what you describe here (but which doesn't work for initializing a new instance). > > On this particular change, I seem to recall a bug a while back where we > had to change the host browser code to use the _in_mb methods to fix an > earlier problem. Could it be that the semantics of host_browser has > changed and that, at one point, we were passing memory in MB? > Seems likely enough. > In a larger sense (i.e. beyond this particular bugfix, etc.) we've > talked about how we need a more robust way of dealing with > storage/memory sizing and conversion/display. As we see here, it's > currently a bit fragile. > Agreed. To Chris's original post I say ACK, but not for the original reason stated. ACK because if we don't set "memory" then the newly created object has no info on the memory at all. From clalance at redhat.com Mon Aug 11 15:09:54 2008 From: clalance at redhat.com (Chris Lalancette) Date: Mon, 11 Aug 2008 17:09:54 +0200 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <48A0529B.6060805@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> <20080811144500.GB2830@redhat.com> <48A0529B.6060805@redhat.com> Message-ID: <48A05642.9020808@redhat.com> Scott Seago wrote: >> I don't think this is necessary. The migration file for CreateHosts >> (currently wui/src/db/migrate/002_create_hosts.rb) doesn't define >> memory_in_mb, so this attribute is never being stuck into the database. >> >> > memory_in_mb is a method defined on Host which converts from MB to kb to > stuff in the db. The idea is when you have a number already in kb, you just > set host.memory, but if your value is in mb (as it would be if you're > soliciting user input via the WUI), you set host.memory_in_mb, which converts > to kb and stores in the db. Right, that's exactly it. > On this particular change, I seem to recall a bug a while back where we had > to change the host browser code to use the _in_mb methods to fix an earlier > problem. Could it be that the semantics of host_browser has changed and that, > at one point, we were passing memory in MB? This I'm not sure about, and I can't remember the bug you are talking about anymore. Looking at earlier code (like what's in master, for instance), we were passing the value in MEMSIZE straight into "memory", so this looks like recent breakage. > > In a larger sense (i.e. beyond this particular bugfix, etc.) we've talked > about how we need a more robust way of dealing with storage/memory sizing and > conversion/display. As we see here, it's currently a bit fragile. Yeah, agreed. It's not always clear what values belong where, although luckily it seems we were smart enough to always have things in kb in the database; it's just getting things in and out of the DB that's a little hairy. I'm not sure how to make it more robust, but we definitely need this bug fix. Chris Lalancette From sseago at redhat.com Mon Aug 11 15:30:01 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 11 Aug 2008 11:30:01 -0400 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <20080811150817.GD2830@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> <20080811144500.GB2830@redhat.com> <48A0529B.6060805@redhat.com> <20080811150817.GD2830@redhat.com> Message-ID: <48A05AF9.4030004@redhat.com> Steve Linabery wrote: > On Mon, Aug 11, 2008 at 10:54:19AM -0400, Scott Seago wrote: > >> Well the attribute doesn't exist, but as mentioned above, there's a >> method with this name that sets the proper memory value (assuming what >> you have is in megabytes). >> > > For the record, there is only memory_in_mb= that does what you describe here (but which doesn't work for initializing a new instance). > > It actually works fine for initializing an instance: irb(main):045:0> foo = Host.new(:hostname=>"foo", :memory_in_mb=>256) => # irb(main):046:0> foo.memory_in_mb => 256 irb(main):047:0> foo.memory => 262144 But again, in this instance we don't want the conversion happening. Scott From slinabery at redhat.com Mon Aug 11 15:42:29 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 11 Aug 2008 10:42:29 -0500 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <48A05AF9.4030004@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> <20080811144500.GB2830@redhat.com> <48A0529B.6060805@redhat.com> <20080811150817.GD2830@redhat.com> <48A05AF9.4030004@redhat.com> Message-ID: <20080811154229.GE2830@redhat.com> On Mon, Aug 11, 2008 at 11:30:01AM -0400, Scott Seago wrote: > Steve Linabery wrote: >> On Mon, Aug 11, 2008 at 10:54:19AM -0400, Scott Seago wrote: >>> Well the attribute doesn't exist, but as mentioned above, there's a >>> method with this name that sets the proper memory value (assuming >>> what you have is in megabytes). >>> >> >> For the record, there is only memory_in_mb= that does what you describe here (but which doesn't work for initializing a new instance). >> >> > It actually works fine for initializing an instance: > > irb(main):045:0> foo = Host.new(:hostname=>"foo", :memory_in_mb=>256) > => # arch: nil, memory: 262144, is_disabled: nil, hardware_pool_id: nil, > state: nil, load_average: nil, created_at: nil, updated_at: nil> > irb(main):046:0> foo.memory_in_mb > => 256 > irb(main):047:0> foo.memory > => 262144 Thanks for the edu-muh-catin'! > > But again, in this instance we don't want the conversion happening. > > Scott > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From mniranjan at redhat.com Mon Aug 11 16:37:46 2008 From: mniranjan at redhat.com ( Niranjan M.R) Date: Mon, 11 Aug 2008 22:07:46 +0530 Subject: [Ovirt-devel] How to make a Ovirt Cloud Message-ID: <48A06ADA.5030306@redhat.com> Hi all, I am newbie to this list and to the ovirt technology . I would like to know like i have a system with 4GB RAM, 160 GBHDD, and AMD Athlon(tm) 64 X2 Dual Core Processor 4000 system. With F9 installed , i use KVM for creating VM's. I have a similar system with the same configuration and in that system also i have F9 installed and use KVM to run certain VM's under it What i would like to know, Is there a way i can combine both these system's in to one system (not physically) but through Network or something like that and install Ovirt . So that Ovirt thinks that i have total 8 GB RAM, 320GB HDD and 4 Processors. And i can create VM's using this pool of resources Does oVirt alone would be enough or Do i need some kind of Grid kind of software to combine systems. Any ideas on the above would be helpful Regards Niranjan From sghosh at redhat.com Mon Aug 11 19:01:57 2008 From: sghosh at redhat.com (Subhendu Ghosh) Date: Mon, 11 Aug 2008 15:01:57 -0400 Subject: [Ovirt-devel] How to make a Ovirt Cloud In-Reply-To: <48A06ADA.5030306@redhat.com> References: <48A06ADA.5030306@redhat.com> Message-ID: <48A08CA5.4010406@redhat.com> Niranjan M.R wrote: > Hi all, > > I am newbie to this list and to the ovirt technology . I would like to > know like > > i have a system with 4GB RAM, 160 GBHDD, and AMD Athlon(tm) 64 X2 Dual > Core Processor 4000 system. With F9 installed , i use KVM for creating > VM's. > I have a similar system with the same configuration and in that system > also i have F9 installed and use KVM to run certain VM's under it > > What i would like to know, Is there a way i can combine both these > system's in to one system (not physically) but through Network or > something like that > and install Ovirt . So that Ovirt thinks that i have total 8 GB RAM, > 320GB HDD and 4 Processors. And i can create VM's using this pool of > resources > > Does oVirt alone would be enough or Do i need some kind of Grid kind of > software to combine systems. > > Any ideas on the above would be helpful > What you are describing is a single-system-image. oVirt does not do that. oVirt is designed to take the hardware systems, install a minimal kernel+hypervisor+tools layers, and then create a virtual pool of resources on which VMs can be deployed. The VMs are still going to be constrained by underlying physical resources. -- -regards Subhendu Ghosh From clalance at redhat.com Tue Aug 12 07:05:12 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 12 Aug 2008 09:05:12 +0200 Subject: [Ovirt-devel] [PATCH]: Don't reject FORWARD chain on the managed node In-Reply-To: <1218466044.9936.1.camel@muff> References: <1218460620-22092-1-git-send-email-clalance@redhat.com> <1218466044.9936.1.camel@muff> Message-ID: <48A13628.9020705@redhat.com> Mark McLoughlin wrote: > I'd like the default rule in Fedora to be: > > -A FORWARD -m physdev ! --physdev-is-bridged -j REJECT --reject-with icmp-admin-prohibited Ah, that worked out well, and is what I ended up committing. Thanks! Chris Lalancette From clalance at redhat.com Tue Aug 12 07:05:30 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 12 Aug 2008 09:05:30 +0200 Subject: [Ovirt-devel] [PATCH]: Open up port 49152 on the managed node In-Reply-To: <1218460554-22006-1-git-send-email-clalance@redhat.com> References: <1218460554-22006-1-git-send-email-clalance@redhat.com> Message-ID: <48A1363A.8070008@redhat.com> Chris Lalancette wrote: > Make sure to open up the 49152 port on the managed nodes. This is to support > live migration through libvirt. The basic situation is that libvirtd is > currently single-threaded, and the migrate command we are using is > synchronous, so it is never the case that we can have more than 1 live > migration happening at a time. In the future, it might be possible that > libvirtd will become multi-threaded, at which time we will have to address > this differently. However, also in the future, for secure live migration, we > are going to want to proxy the migration stuff via the libvirt channel. Either > way we are going to need to change in the future; this is good enough for now. > > Signed-off-by: Chris Lalancette Committed. Chris Lalancette From clalance at redhat.com Tue Aug 12 07:06:15 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 12 Aug 2008 09:06:15 +0200 Subject: [Ovirt-devel] [PATCH]: Add host memory in kb to the database In-Reply-To: <1218460589-22051-1-git-send-email-clalance@redhat.com> References: <1218460589-22051-1-git-send-email-clalance@redhat.com> Message-ID: <48A13667.9040807@redhat.com> Chris Lalancette wrote: > When inserting memory values into the database, ovirt-identify-node is > sending over values in kilobytes. The database is also in kilobytes. > Don't run a non-sensical "mb_to_kb" conversion on the memory value > before sticking it into the database. > > Signed-off-by: Chris Lalancette Committed (we'll have to address the larger issue of sizes later on). Chris Lalancette From clalance at redhat.com Tue Aug 12 08:41:42 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 12 Aug 2008 10:41:42 +0200 Subject: [Ovirt-devel] [PATCH]: Tested, working implementation of migration Message-ID: <1218530502-11852-1-git-send-email-clalance@redhat.com> An actually working, tested cut of the migration code for taskomatic. It supports 3 modes: a) ClearHost - this clears all of the VMs off of a particular host, the idea being that this host will probably be taken down for maintenance. b) Migrate VM to a particular host - this migrates a particular VM from where it currently resides to a destination specified by the admin. The idea here is that the admin might want to override the automatic placement decision of taskomatic c) Migrate VM anywhere else - this migrates a particular VM from where it currently resides to anywhere else. The idea here is for a step-by-step clearing of a host (similar to ClearHost), or to reduce load on a particular host by moving a VM somewhere else. Signed-off-by: Chris Lalancette diff --git a/wui/src/task-omatic/task_host.rb b/wui/src/task-omatic/task_host.rb new file mode 100644 index 0000000..bc60876 --- /dev/null +++ b/wui/src/task-omatic/task_host.rb @@ -0,0 +1,33 @@ +# 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. + +require 'utils' + +# FIXME: a little ugly to be including all of task_vm here, but +# utils really isn't the right place for the migrate() method +require 'task_vm' + +def clear_vms_host(task) + puts "clear_vms_host" + + src_host = findHost(task.host_id) + + src_host.vms.each do |vm| + migrate(vm) + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index 34749e0..0e20da0 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -123,17 +123,6 @@ def findVM(task, fail_on_nil_host_id = true) return vm end -def findHost(task, host_id) - host = Host.find(:first, :conditions => [ "id = ?", host_id]) - - if host == nil - # Hm, we didn't find the host_id. Seems odd. Return a failure - raise "Could not find host_id " + host_id - end - - return host -end - def setVmShutdown(vm) vm.host_id = nil vm.memory_used = nil @@ -184,7 +173,7 @@ def shutdown_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -195,10 +184,21 @@ def shutdown_vm(task) # of problems. Needs more thought #dom.shutdown dom.destroy - dom.undefine + + begin + dom.undefine + rescue + # undefine can fail, for instance, if we live migrated from A -> B, and + # then we are shutting down the VM on B (because it only has "transient" + # XML). Therefore, just ignore undefine errors so we do the rest + # FIXME: we really should have a marker in the database somehow so that + # we can tell if this domain was migrated; that way, we can tell the + # difference between a real undefine failure and one because of migration + end + + teardown_storage_pools(conn) + conn.close - # FIXME: hm. We probably want to undefine the storage pool that this host - # was using if and only if it's not in use by another VM. rescue => ex setVmState(vm, vm_orig_state) raise ex @@ -224,7 +224,6 @@ def start_vm(task) end # FIXME: Validate that the VM is still within quota - #vm.validate vm_orig_state = vm.state setVmState(vm, Vm::STATE_STARTING) @@ -241,93 +240,17 @@ def start_vm(task) # OK, now that we found the VM, go looking in the hardware_pool # hosts to see if there is a host that will fit these constraints - host = nil - - vm.vm_resource_pool.get_hardware_pool.hosts.each do |host| - if host.num_cpus >= vm.num_vcpus_allocated \ - and host.memory >= vm.memory_allocated \ - and not host.is_disabled - host = curr - break - end - end - - if host == nil - # we couldn't find a host that matches this description; report ERROR - raise "No host matching VM parameters could be found" - end + host = findHostSLA(vm) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") - # here, build up a list of already defined pools. We'll use it - # later to see if we need to define new pools for the storage or just - # keep using existing ones - - defined_pools = [] - all_storage_pools(conn).each do |remote_pool_name| - tmppool = conn.lookup_storage_pool_by_name(remote_pool_name) - defined_pools << tmppool - end - - storagedevs = [] - vm.storage_volumes.each do |volume| - # here, we need to iterate through each volume and possibly attach it - # to the host we are going to be using - storage_pool = volume.storage_pool - - if storage_pool == nil - # Hum. Specified by the VM description, but not in the storage pool? - # continue on and hope for the best - next - end - - if storage_pool[:type] == "IscsiStoragePool" - thisstorage = Iscsi.new(storage_pool.ip_addr, storage_pool[:target]) - elsif storage_pool[:type] == "NfsStoragePool" - thisstorage = NFS.new(storage_pool.ip_addr, storage_pool.export_path) - else - # Hm, a storage type we don't understand; skip it - next - end - - thepool = nil - defined_pools.each do |pool| - doc = Document.new(pool.xml_desc(0)) - root = doc.root - - if thisstorage.xmlequal?(doc.root) - thepool = pool - break - end - end - - if thepool == nil - thepool = conn.define_storage_pool_xml(thisstorage.getxml, 0) - thepool.build(0) - thepool.create(0) - elsif thepool.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 - thepool.create(0) - end - - storagedevs << thepool.lookup_volume_by_name(volume.read_attribute(thisstorage.db_column)).path - end - - conn.close - - if storagedevs.length > 4 - raise "Too many storage volumes; maximum is 4" - end - - # OK, we found a host that will work; now let's build up the XML + storagedevs = connect_storage_pools(conn, vm) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, vm.memory_used, vm.num_vcpus_allocated, vm.boot_device, vm.vnic_mac_addr, "ovirtbr0", storagedevs) - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.define_domain_xml(xml.to_s) dom.create @@ -368,7 +291,7 @@ def save_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -411,7 +334,7 @@ def restore_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) # FIXME: we should probably go out to the host and check what it thinks # the state is @@ -453,7 +376,7 @@ def suspend_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -493,7 +416,7 @@ def resume_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -529,3 +452,74 @@ def update_state_vm(task) puts "Updated state to " + task.args end end + +def migrate(vm, dest = nil) + if vm.state == Vm::STATE_STOPPED + raise "Cannot migrate stopped domain" + elsif vm.state == Vm::STATE_SUSPENDED + raise "Cannot migrate suspended domain" + elsif vm.state == Vm::STATE_SAVED + raise "Cannot migrate saved domain" + end + + vm_orig_state = vm.state + setVmState(vm, Vm::STATE_MIGRATING) + + begin + src_host = findHost(vm.host_id) + if not dest.nil? + if dest.to_i == vm.host_id + raise "Cannot migrate from host " + src_host.hostname + " to itself!" + end + dst_host = findHost(dest.to_i) + else + dst_host = findHostSLA(vm) + end + + src_conn = Libvirt::open("qemu+tcp://" + src_host.hostname + "/system") + dst_conn = Libvirt::open("qemu+tcp://" + dst_host.hostname + "/system") + + connect_storage_pools(dst_conn, vm) + + dom = src_conn.lookup_domain_by_uuid(vm.uuid) + dom.migrate(dst_conn, Libvirt::Domain::MIGRATE_LIVE) + + # if we didn't raise an exception, then the migration was successful. We + # still have a pointer to the now-shutdown domain on the source side, so + # undefine it + begin + dom.undefine + rescue + # undefine can fail, for instance, if we live migrated from A -> B, and + # then we are shutting down the VM on B (because it only has "transient" + # XML). Therefore, just ignore undefine errors so we do the rest + # FIXME: we really should have a marker in the database somehow so that + # we can tell if this domain was migrated; that way, we can tell the + # difference between a real undefine failure and one because of migration + end + + teardown_storage_pools(src_conn) + dst_conn.close + src_conn.close + rescue => ex + # FIXME: ug. We may have open connections that we need to close; not + # sure how to handle that + setVmState(vm, vm_orig_state) + raise ex + end + + setVmState(vm, Vm::STATE_RUNNING) + vm.host_id = dst_host.id + vm.save +end + +def migrate_vm(task) + puts "migrate_vm" + + # here, we are given an id for a VM to migrate; we have to lookup which + # physical host it is running on + + vm = findVM(task) + + migrate(vm, task.args) +end diff --git a/wui/src/task-omatic/taskomatic.rb b/wui/src/task-omatic/taskomatic.rb index bb70247..7d6e950 100755 --- a/wui/src/task-omatic/taskomatic.rb +++ b/wui/src/task-omatic/taskomatic.rb @@ -63,9 +63,9 @@ end require 'task_vm' require 'task_storage' +require 'task_host' loop do - first = true tasks = Array.new begin tasks = Task.find(:all, :conditions => [ "state = ?", Task::STATE_QUEUED ]) @@ -86,11 +86,10 @@ loop do end end tasks.each do |task| - if first - # make sure we get our credentials up-front - get_credentials - first = false - end + # make sure we get our credentials up-front + get_credentials + + task.time_started = Time.now state = Task::STATE_FINISHED begin @@ -103,7 +102,9 @@ loop do when VmTask::ACTION_SAVE_VM then save_vm(task) when VmTask::ACTION_RESTORE_VM then restore_vm(task) when VmTask::ACTION_UPDATE_STATE_VM then update_state_vm(task) + when VmTask::ACTION_MIGRATE_VM then migrate_vm(task) when StorageTask::ACTION_REFRESH_POOL then refresh_pool(task) + when HostTask::ACTION_CLEAR_VMS then clear_vms_host(task) else puts "unknown task " + task.action state = Task::STATE_FAILED diff --git a/wui/src/task-omatic/utils.rb b/wui/src/task-omatic/utils.rb index e6401dc..69b3c3e 100644 --- a/wui/src/task-omatic/utils.rb +++ b/wui/src/task-omatic/utils.rb @@ -1,7 +1,39 @@ require 'rexml/document' include REXML -require 'models/task' +def findHostSLA(vm) + host = nil + + vm.vm_resource_pool.get_hardware_pool.hosts.each do |curr| + # FIXME: we probably need to add in some notion of "load" into this check + if curr.num_cpus >= vm.num_vcpus_allocated \ + and curr.memory >= vm.memory_allocated \ + and not curr.is_disabled.nil? and curr.is_disabled == 0 \ + and curr.state == Host::STATE_AVAILABLE \ + and (vm.host_id.nil? or (not vm.host_id.nil? and vm.host_id != curr.id)) + host = curr + break + end + end + + if host == nil + # we couldn't find a host that matches this criteria + raise "No host matching VM parameters could be found" + end + + return host +end + +def findHost(host_id) + host = Host.find(:first, :conditions => [ "id = ?", host_id]) + + if host == nil + # Hm, we didn't find the host_id. Seems odd. Return a failure + raise "Could not find host_id " + host_id + end + + return host +end def String.random_alphanumeric(size=16) s = "" @@ -10,12 +42,91 @@ def String.random_alphanumeric(size=16) end def all_storage_pools(conn) - all_pools = [] - all_pools.concat(conn.list_defined_storage_pools) + all_pools = conn.list_defined_storage_pools all_pools.concat(conn.list_storage_pools) return all_pools end +def teardown_storage_pools(conn) + # FIXME: this needs to get a *lot* smarter. In particular, we want to make + # sure we can tear down unused pools even when there are other guests running + if conn.list_domains.empty? + # OK, there are no running guests on this host anymore. We can teardown + # any storage pools that are there without fear + all_storage_pools(conn).each do |remote_pool_name| + begin + pool = conn.lookup_storage_pool_by_name(remote_pool_name) + pool.destroy + pool.undefine + rescue + # do nothing if any of this failed; the worst that happens is that + # we leave a pool configured + puts "Could not teardown pool " + remote_pool_name + "; skipping" + end + end + end +end + +def connect_storage_pools(conn, vm) + # here, build up a list of already defined pools. We'll use it + # later to see if we need to define new pools for the storage or just + # keep using existing ones + + defined_pools = [] + all_storage_pools(conn).each do |remote_pool_name| + defined_pools << conn.lookup_storage_pool_by_name(remote_pool_name) + end + + storagedevs = [] + vm.storage_volumes.each do |volume| + # here, we need to iterate through each volume and possibly attach it + # to the host we are going to be using + storage_pool = volume.storage_pool + + if storage_pool == nil + # Hum. Specified by the VM description, but not in the storage pool? + # continue on and hope for the best + # FIXME: probably want a print to the logs here + next + end + + if storage_pool[:type] == "IscsiStoragePool" + thisstorage = Iscsi.new(storage_pool.ip_addr, storage_pool[:target]) + elsif storage_pool[:type] == "NfsStoragePool" + thisstorage = NFS.new(storage_pool.ip_addr, storage_pool.export_path) + else + # Hm, a storage type we don't understand; skip it + puts "Storage type " + storage_pool[:type] + " is not understood; skipping" + next + end + + thepool = nil + defined_pools.each do |pool| + doc = Document.new(pool.xml_desc) + root = doc.root + + if thisstorage.xmlequal?(doc.root) + thepool = pool + break + end + end + + if thepool == nil + thepool = conn.define_storage_pool_xml(thisstorage.getxml) + thepool.build + thepool.create + elsif thepool.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 + thepool.create + end + + storagedevs << thepool.lookup_volume_by_name(volume.read_attribute(thisstorage.db_column)).path + end + + return storagedevs +end + class StorageType attr_reader :db_column From clalance at redhat.com Tue Aug 12 10:03:24 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 12 Aug 2008 12:03:24 +0200 Subject: [Ovirt-devel] [PATCH]: Tested, working implementation of migration In-Reply-To: <1218530502-11852-1-git-send-email-clalance@redhat.com> References: <1218530502-11852-1-git-send-email-clalance@redhat.com> Message-ID: <48A15FEC.7080909@redhat.com> Chris Lalancette wrote: > An actually working, tested cut of the migration code for taskomatic. It > supports 3 modes: > > a) ClearHost - this clears all of the VMs off of a particular host, the idea > being that this host will probably be taken down for maintenance. > b) Migrate VM to a particular host - this migrates a particular VM from where > it currently resides to a destination specified by the admin. The idea here > is that the admin might want to override the automatic placement decision of > taskomatic > c) Migrate VM anywhere else - this migrates a particular VM from where it > currently resides to anywhere else. The idea here is for a step-by-step > clearing of a host (similar to ClearHost), or to reduce load on a particular > host by moving a VM somewhere else. Oh, I should mention that this implementation is tested and working with the updated libvirt, ruby-libvirt, and kvm packages in the ovirt.org repository. The patches for all 3 have been submitted upstream, but I'm still waiting on feedback on all of them. In the meantime, the packages in ovirt.org will work. Chris Lalancette From mwagner at redhat.com Tue Aug 12 15:53:39 2008 From: mwagner at redhat.com (mark wagner) Date: Tue, 12 Aug 2008 11:53:39 -0400 Subject: [Ovirt-devel] Re: broken windows [Re: [PATCH] Adds max/min methods ... In-Reply-To: <87sktb6hdg.fsf_-_@rho.meyering.net> References: <20080807070645.GC26662@redhat.com> <87wsiset9d.fsf@rho.meyering.net> <489B1E33.4030201@redhat.com> <87sktb6hdg.fsf_-_@rho.meyering.net> Message-ID: <48A1B203.9070907@redhat.com> Jim Meyering wrote: > mark wagner wrote: > >> The Stats changes are actually mine, not Steve's. Since he alone >> needed to integrate >> with my changes and effectively tested them for me, I asked him to >> just submit >> them. >> > > Bear in mind that my review was addressed to Steve, since nothing > suggested the changes were by anyone else. > > I was clarifying that the code was mine so as to clear Steve's reputation. I'm guessing next time he will have me submit the code on my own... :) > ... > >>> Hi Steve, >>> >>> A fine net change, overall. >>> >>> It's great to do whitespace clean-up, but please keep that sort of change >>> separate from anything substantial. Otherwise, it's more work for the >>> reviewer to separate the trivially-ignorable whitespace changes from >>> the ones that are significant. >>> >>> >> The whitespace changes are mine. I thought you wanted them out and had >> put such an edict in place. Sorry for not understanding >> > > Eliminating all offending white space at once would be disruptive in > the short term. The current policy is gentler: add no _new_ instances. > > ... > >>> Also, I noticed that the pre-existing style is to initialize variables at >>> the top of a block or function. It's better to avoid that style, and >>> instead to place any initialization as near as possible to the first use. >>> For example, if you move the initializations of my_max and my_min down >>> so they're nearer their first uses, readers don't have to worry about >>> whether they're used uninitialized (now the initialization is just before >>> the loop), or if the values are modified between initialization and >>> whatever use the reader is looking at. >>> >>> >> I was actually taught just the opposite, init at the top so its easy to find >> the inits. In my more performance sensitive c++ apps, I tend to init just >> before the variable is first used (or just outside of the loop as needed) >> if there is a chance of getting out of the function before the variable >> would be used. Thus avoiding unneeded initializations. >> >> I appreciate the style suggestions, coming from a long C/C++ background, >> I tend do things the way I've been doing them for the last 20+ years. I >> try to be fairly flexible in how I write or change code. For instance I >> matched the style in graph_controller.rb for previous changes that I've >> made. However, since the ovirt program doesn't have a predefined style >> guide and I am the one writing and maintaining the Stats code, I will >> continue to write it in a manner that is easiest for me to support. >> >> I'm sorry if this upsets your style preferences, but functionality is >> more important to me over style. I also tend to worry when people focus >> on the style w/o specifying a style guide for the program. >> > > I make the suggestion to move declarations down whenever possible because > not only does that make the code more readable, but it also makes it more > robust in the face of continued development and maintenance, regardless > of the language. Reread the paragraph above, and imagine what can happen > when the distance between initialization and first use increases. > > From my personal experiences, I would disagree on the robustness and readability. If I know that someone always declares and initializes their variables at the top of a function I know exactly where to look for them. It may be an inconvenience for me to page up once or twice, but I know where to look and I know the scope. If they declare them just before use, as a reviewer I need to hunt to find them as they may be a page or two up based on the block scope. I also need to verify that the usage of the variable is used correctly for the scope it has been declared in. I have spent too much time in my career helping debug other peoples errors caused by improper declarations and initializations. Whether it be declaring and initializing something inside a loop or in an "if" block and trying to use it in the "else" block, etc. Putting them at the beginning of the function eliminates that issue. As I mentioned in my previous email, there are certain times where I will declare things differently, but that is mostly related to language and performance requirements. > It's only a suggestion (directed to someone else, even) after all, > so you're free to ignore it. > > Maybe directed at someone else but it was about code that I authored, so it implicitly directed at me. > The bad white space policy is also important, and likewise, is not > prompted by some whim or idle preference. I have seen so many cases in > which bad white space has led to bugs (resulting from mis-resolved merge > conflicts that should never have arisen) that it is clearly worthwhile > to avoid the root cause. Bad white space can even be a direct source > of bugs in Makefiles and in languages like C and C++ (in strings and > CPP directives), not to mention Python. > > I clearly agree that trailing whitespace can cause issues in some cases and languages. In those cases it needs to be dealt with. In Ruby I would think its fairly benign except a few isolated cases. I have yet to see that the few instances of extra whitespace at the end of line in any of the Ruby code I have written has caused any issues with the execution of the code. Well, in honesty, the only case has been where the script sent remove the bad whitespace was not adequately tested and instead removed the new lines, thus creating a big one line file. Does that count ? However, no more bad whitespace is now a rule for ovirt and I will abide by it. >> Don't you >> think oVirt has bigger issues to deal with other than trailing whitespace >> and where I init my variables? >> > > Maybe you haven't heard about the broken windows theory? > > http://tinyurl.com/5dy4fw > aka > http://www.davecheong.com/2006/06/30/broken-windows-theory-in-software-and-your-personal-life/ > I have heard about it and believe in some of its premises, they seem pretty common sense to me. However I tend to focus on the testing side of the issues, not the styling. For instance, people should test their changes before submitting them. There are still instances where crap is getting committed to ovirt that couldn't have passed a basic smoke test because stuff just doesn't work after it is submitted. To me that is the top issue facing us. Why is the build broken? Did the submitter and the reviewers test the changes. I believe that those are part of the working sets of rules we should be following. So I can apply the "Broken Windows Theory" to this and say that we need to crackdown on both the submitter and reviewer. (however see fears below)... BTW - Did you do enough research to see that the original premise has pretty much been disproven ? http://en.wikipedia.org/wiki/Fixing_Broken_Windows On a personal note the main thing I dislike about this "theory" is that the original theory was based on results achieved by massive police crackdowns. Exactly what I fear in software development. That the style police will come after me because of where I put my curly braces when the real issue is does the code work. Keep in mind that I come from a state that prints "Live Free or Die" on the front and back of my vehicle. It is a motto I try to live by. My personal belief is that quality needs to be designed into a product. It can't be tested or dictated in. However, testing is still extremely important to ensure that the quality is there. I will continue to focus on designing and implementing extensible, well thought out code. I will spend my time worrying about testing the code paths, not coding style. As with many things, these tend to be philosophical issues bordering on religion to some. Using my "Live Free or Die" mantra, feel free to focus on what you think is important, I'll continue to do the same. -mark From mmorsi at redhat.com Tue Aug 12 17:46:07 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 12 Aug 2008 13:46:07 -0400 Subject: [Ovirt-devel] oVirt / Selenium Update Message-ID: <48A1CC5F.1000207@redhat.com> Attached is a patch and other relevant files containing my recent work in integrating the Selenium test suite into oVirt. In the patch is the selenium.rb module, obtained from the Selenium web site and licensed under the Apache license, which is used to contact the selenium server. Also included is interface_test.rb which now contains a single simple test using selenium to verify the title of the oVirt wui site. More tests can be easily added by adding methods to that module. The test suite attempts to contact the selenium server running at 192.168.50.1, eg the host maching which the oVirt wui appliance is running on, and thus port 4444 must be open on that machine. Also in that patch is the ovirt httpd configuration file, ovirt-wui.conf, which was modified to comment out the rewrite rules that were preventing the kerb un / pass from being accepted; as well as enabling simple password based authentication and disabling negotiation based (obviously the disabling of the negotiation bit is unacceptable, but was the only was the short term solution to getting everything working. an easy / albeit hackish work around would be to use sed in the autobuild.sh script to toggle those values). The selenium server is written in java and does not currently have a simple rpm to install it system wide. I whipped up and attached a small init script to assist in launching / killing the selenium server. Since we want selenium to be run in an automated fashion, eg during an autobuild cycle, I had to create and tell selenium to use a custom firefox profile which disabled various alert boxes firefox normally launches during its operation ('your session crashed unexpectidly, restore?', 'you are about to log into this site as admin, are you sure?', etc). I did not attach this profile (59M), but if anyone is interested, you merely need to add the following options to /prefs.js (where is defined in the init script): user_pref("browser.sessionstore.enabled", false); user_pref("network.http.phishy-userpass-length", 255); -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-selenium1.patch Type: text/x-patch Size: 77177 bytes Desc: not available URL: -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: selenium.init URL: From bkearney at redhat.com Tue Aug 12 18:34:11 2008 From: bkearney at redhat.com (Bryan Kearney) Date: Tue, 12 Aug 2008 14:34:11 -0400 Subject: [Ovirt-devel] Lighter-weight "developer" setup In-Reply-To: <489C5391.6050602@redhat.com> References: <489C5391.6050602@redhat.com> Message-ID: <48A1D7A3.8050007@redhat.com> Chris Lalancette wrote: > 1. Still have an oVirt appliance that has everything installed inside of it. > Then, this appliance "manages" the host that it is actually running on, and can > install additional guests alongside the appliance. You need to protect the > oVirt appliance a little bit so you don't accidentally destroy itself, but > otherwise you can treat the underlying hardware just like any other node. > > > 2. Get rid of the oVirt appliance completely, and just provide > instructions/better scripts for installing all of the oVirt software directly on > the host. Then the host runs the WUI, and you don't need to protect any > "special" guests. > We have a first cut of an appliance definition of the ovirt appliance at [1]. With this, we could look at a couple of other alternatives: 1) Make the recipe availble with instructions to build it via the appliance tools on their machines either from the public mirrors or local media. 2) Use the recipe to configure an existing bare metal machine. 3) Set up a public cobbler server to "koan up" a new appliance. Again, this would be from the public mirrors. 1 and 3 _could_ save bandwidth based on using public mirrors. 1 could save more if they have media handy. 2 would be really cool, but the current recipes do not control packages. We would need to work up a new "package" recipe, and then apply the one from [1]. -- bk [1] http://git.et.redhat.com/?p=acex.git;a=tree;f=ovirt/appliances/ovirt; From dlutter at redhat.com Tue Aug 12 21:37:43 2008 From: dlutter at redhat.com (David Lutterkort) Date: Tue, 12 Aug 2008 21:37:43 +0000 Subject: [Ovirt-devel] Lighter-weight "developer" setup In-Reply-To: <489C5391.6050602@redhat.com> References: <489C5391.6050602@redhat.com> Message-ID: <1218577063.5092.142.camel@localhost.localdomain> On Fri, 2008-08-08 at 16:09 +0200, Chris Lalancette wrote: > The basic premise here is that we want to make it easier for people with limited > amounts of hardware to get up and running with oVirt, and hopefully build a > bigger community around oVirt. We've come a long way with the developer > appliance + the fake managed nodes, but that only gets you so far. In > particular, you can't really start guests (you can, but they are extremely > slow), and the appliance is quite big to download (especially in far-away > places, or with slower internet connections). Expanding on what Bryan mentioned, the first step to this should be to express the setup of the oVirt appliance as puppet manifests, since that will give you a lot of flexibility in all the possible permutations that people might want for a setup. Besides that, there's a few more benefits: * rebasing to different OS versions becomes easier (possible ?) since we'd have to make a clear distinction between 'base appliance' (think JeOS) and 'oVirt personalization'. That's one of the big reason why our IT folks came up with genome and discarded an image-based approach to setting their infrastructure up. * developers get a much more controlled ways to keep their appliance in sync with development in git - no need to manually figure out how changes to various files need to be translated into changes on their appliance. If we had manifests for the managed node, too, it would also be very easy to pull the pertinent pieces out of it and turn those manifests into manifests that people need to run on their preinstalled host. David From imain at redhat.com Tue Aug 12 23:15:01 2008 From: imain at redhat.com (Ian Main) Date: Tue, 12 Aug 2008 16:15:01 -0700 Subject: [Ovirt-devel] [PATCH]: Tested, working implementation of migration In-Reply-To: <1218530502-11852-1-git-send-email-clalance@redhat.com> References: <1218530502-11852-1-git-send-email-clalance@redhat.com> Message-ID: <20080812161501.5528a86e@tp.mains.net> On Tue, 12 Aug 2008 10:41:42 +0200 Chris Lalancette wrote: > An actually working, tested cut of the migration code for taskomatic. It > supports 3 modes: > > a) ClearHost - this clears all of the VMs off of a particular host, the idea > being that this host will probably be taken down for maintenance. > b) Migrate VM to a particular host - this migrates a particular VM from where > it currently resides to a destination specified by the admin. The idea here > is that the admin might want to override the automatic placement decision of > taskomatic > c) Migrate VM anywhere else - this migrates a particular VM from where it > currently resides to anywhere else. The idea here is for a step-by-step > clearing of a host (similar to ClearHost), or to reduce load on a particular > host by moving a VM somewhere else. I'm thinking this must be against an older rev of the code? I can't get it to apply to either master or next.. ?? Ian From mmorsi at redhat.com Wed Aug 13 00:38:23 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 12 Aug 2008 20:38:23 -0400 Subject: [Ovirt-devel] oVirt / Selenium Update In-Reply-To: <48A1CC5F.1000207@redhat.com> References: <48A1CC5F.1000207@redhat.com> Message-ID: <48A22CFF.30507@redhat.com> Mohammed Morsi wrote: > Attached is a patch and other relevant files containing my recent work > in integrating the Selenium test suite into oVirt. > Attached is the updated patch including the selenium.rb file existance check. Also attached is the selenium server init script with a few additional tweaks. Both this script and autobuild now require a few selenium related components on the host system, under /root/ovirt (changed to whatever) namely the actual selenium server jar, the ruby selenium.rb module, and the template firefox profile as mentioned in the last email. If the selenium ruby module isn't available, autobuild will make note of it and continue, but it will never be uploaded to the wui appliance and thus the interface tests will never be run. One last thing, that I failed to mention in my last email, is that due to the headless test server issues as discussed in the previous selenium email thread, and the vncserver solution, the selenium init script instructs all graphical operations to take place on DISPLAY :5, which must be running. -Mo -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: selenium.init URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-selenium3.patch Type: text/x-patch Size: 4051 bytes Desc: not available URL: From apevec at redhat.com Wed Aug 13 00:58:04 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 13 Aug 2008 02:58:04 +0200 Subject: [Ovirt-devel] [PATCH] use YUM cache instead of pungi in build-all.sh Message-ID: <1218589084-8424-1-git-send-email-apevec@redhat.com> appliance-creator doesn't need install tree with anaconda images so we can use YUM cache to avoid re-downloading RPMs for successive image rebuilds Signed-off-by: Alan Pevec --- .gitignore | 1 + build-all.sh | 178 ++++++++++++++------------------- ovirt-host-creator/Makefile | 2 +- ovirt-host-creator/ovirt-cd | 6 +- wui-appliance/create-wui-appliance.sh | 21 +++-- 5 files changed, 94 insertions(+), 114 deletions(-) diff --git a/.gitignore b/.gitignore index 72271a2..d36c16e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ wui/src/log/* wui/src/tmp/* wui/src/db/schema.rb +tmp/ diff --git a/build-all.sh b/build-all.sh index 3d7d5ee..451c6cf 100755 --- a/build-all.sh +++ b/build-all.sh @@ -2,12 +2,11 @@ # # build all oVirt components -# -# - create local YUM repository with Fedora subset required by oVirt +# - create oVirt host image (livecd-creator) # - create local YUM repository with ovirt-wui and ovirt-host-image-pxe RPMs -# - create oVirt admin appliance +# - create oVirt admin appliance (appliance-creator) -# Requires: createrepo httpd kvm libvirt livecd-tools pungi +# Requires: createrepo kvm libvirt livecd-tools appliance-tools PATH=$PATH:/sbin:/usr/sbin @@ -20,22 +19,22 @@ cd $(dirname $0) BASE=$(pwd) F_REL=9 ARCH=$(uname -i) -HTDOCS=/var/www/html -OVIRT=$HTDOCS/ovirt -PUNGI=$HTDOCS/pungi -PUNGIKS=$PUNGI/pungi.ks -DEP_RPMS="createrepo httpd kvm libvirt livecd-tools pungi appliance-tools" +NODE=$BASE/ovirt-host-creator +WUI=$BASE/wui-appliance +BUILD=$BASE/tmp +OVIRT=$BUILD/ovirt +CACHE=$BUILD/cache +DEP_RPMS="createrepo kvm libvirt livecd-tools appliance-tools" usage() { case $# in 1) warn "$1"; try_h; exit 1;; esac cat < /dev/null 2>&1 || - service httpd start > /dev/null 2>&1 - service libvirtd status > /dev/null 2>&1 || - service libvirtd start > /dev/null 2>&1 - service libvirtd reload + rm -rf $CACHE/* fi # stop execution on any error @@ -141,65 +123,69 @@ if [ $update_wui = 1 ]; then createrepo . fi -# build Fedora subset required for oVirt -if [ $update_pungi != 0 ]; then - pungi_flags="-GC" - - fedora_mirror=http://mirrors.fedoraproject.org/mirrorlist - # use Fedora + updates - currentbadupdates='' - cat > $PUNGIKS << EOF +fedora_mirror=http://mirrors.fedoraproject.org/mirrorlist +# use Fedora + updates +currentbadupdates='' +cat > $NODE/repos.ks << EOF repo --name=f$F_REL \ --mirrorlist=$fedora_mirror?repo=fedora-$F_REL&arch=\$basearch repo --name=f$F_REL-updates \ --mirrorlist=$fedora_mirror?repo=updates-released-f$F_REL&arch=\$basearch $currentbadupdates EOF - # + ovirt.org repo for updates not yet in Fedora - # + local ovirt repo with locally rebuilt ovirt* RPMs ( options -w and -n ) - # if not available, ovirt* RPMs from ovirt.org will be used - excludepkgs= - if [[ -f $OVIRT/repodata/repomd.xml ]]; then - excludepkgs='--excludepkgs=ovirt*' - cat >> $PUNGIKS << EOF -repo --name=ovirt --baseurl=http://localhost/ovirt +# + ovirt.org repo for updates not yet in Fedora +# + local ovirt repo with locally rebuilt ovirt* RPMs ( options -w and -n ) +# if not available, ovirt* RPMs from ovirt.org will be used +excludepkgs= +if [[ -f $OVIRT/repodata/repomd.xml ]]; then + excludepkgs='--excludepkgs=ovirt*' + cat >> $NODE/repos.ks << EOF +repo --name=ovirt --baseurl=file://$OVIRT EOF - fi - cat >> $PUNGIKS << EOF +fi +cat >> $NODE/repos.ks << EOF repo --name=ovirt-org \ --baseurl=http://ovirt.org/repos/ovirt/$F_REL/\$basearch $excludepkgs EOF - if [ $include_src != 0 ]; then - cat >> $PUNGIKS << EOF -repo --name=f$F_REL-src \ - --mirrorlist=$fedora_mirror?repo=fedora-source-$F_REL&arch=\$basearch -repo --name=f$F_REL-updates-src \ - --mirrorlist=$fedora_mirror?repo=updates-released-source-f$F_REL&arch=\$basearch $currentbadupdates -repo --name=ovirt-org-src \ - --baseurl=http://ovirt.org/repos/ovirt/$F_REL/src $excludepkgs +if [ $include_src != 0 ]; then + cat > $OVIRT/sources.repo << EOF +[main] +cachedir=$CACHE +keepcache=1 +[f$F_REL-src] +name=f$F_REL-src +mirrorlist=$fedora_mirror?repo=fedora-source-$F_REL&arch=\$basearch +enabled=1 +gpgcheck=0 +[f$F_REL-updates-src] +name=f$F_REL-updates-src +mirrorlist=$fedora_mirror?repo=updates-released-source-f$F_REL&arch=\$basearch $currentbadupdates +enabled=1 +gpgcheck=0 +[ovirt-org-src] +name=ovirt-org-src +baseurl=http://ovirt.org/repos/ovirt/$F_REL/src +exclude=${excludepkgs#--excludepkgs=} +enabled=1 +gpgcheck=0 EOF - else - pungi_flags+=" --nosource" - fi - - cd $BASE - cat >> $PUNGIKS << EOF - -%packages +if [ -n "$excludepkgs" ]; then + cat >> $OVIRT/sources.repo << EOF +[ovirt-src] +name=ovirt-src +baseurl=file://$OVIRT +enabled=1 +gpgcheck=0 EOF - # merge package lists from all oVirt kickstarts - # exclude ovirt-host-image* (chicken-egg: built at the next step - # using repo created here) - egrep -hv "^-|^ovirt-host-image" \ - ovirt-host-creator/common-pkgs.ks \ - wui-appliance/common-pkgs.ks \ - | sort -u >> $PUNGIKS - cd $PUNGI - pungi --ver=$F_REL $pungi_flags -c $PUNGIKS --force - if [ $include_src != 0 ]; then - pungi --ver=$F_REL -I --sourceisos --nosplitmedia -c $PUNGIKS --force - fi - restorecon -r . fi + mkdir -p $BUILD/source + cd $BUILD/source + # FIXME yumdownloader doesn't resolve @groups + # XXX global yum repos are included + yumdownloader -c $OVIRT/sources.repo --source --resolve \ + $(egrep -hv "^-|^ovirt-host-image" \ + $NODE/common-pkgs.ks \ + $WUI/common-pkgs.ks | sort -u) + fi # build oVirt host image; note that we unconditionally rebuild the # ovirt-managed-node RPM, since it is now needed for the managed node @@ -214,15 +200,10 @@ if [ $update_node = 1 ]; then cd $OVIRT createrepo . - cd $BASE/ovirt-host-creator + cd $NODE rm -rf rpm-build - cat > repos.ks << EOF -repo --name=f$F_REL --baseurl=http://localhost/pungi/$F_REL/$ARCH/os -repo --name=ovirt --baseurl=http://localhost/ovirt - -EOF bumpver - make rpms + make rpms YUMCACHE=$CACHE rm -f $OVIRT/ovirt-host-image*rpm cp rpm-build/ovirt-host-image*rpm $OVIRT cd $OVIRT @@ -231,22 +212,9 @@ fi # build oVirt admin appliance if [ $update_app == 1 ]; then - cd $BASE/wui-appliance + cd $WUI make clean - cat > repos.ks << EOF -url --url http://localhost/pungi/$F_REL/$ARCH/os -EOF - excludepkgs= - if [[ -f $OVIRT/repodata/repomd.xml ]]; then - excludepkgs='--excludepkgs=ovirt*' - cat >> repos.ks << EOF -repo --name=ovirt --baseurl=http://localhost/ovirt -EOF - fi - cat >> repos.ks << EOF -repo --name=ovirt-org --baseurl=http://ovirt.org/repos/ovirt/$F_REL/$ARCH $excludepkgs - -EOF + cp $NODE/repos.ks $WUI/repos.ks make bridge_flag= @@ -254,7 +222,7 @@ EOF bridge_flag="-e $bridge" fi - ./create-wui-appliance.sh \ + ./create-wui-appliance.sh -y $CACHE \ -k wui-rel.ks \ $bridge_flag diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index 1f0122f..a912e84 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -14,7 +14,7 @@ repos.ks: repos.ks.in build: ovirt.ks common-install.ks common-pkgs.ks common-post.ks repos.ks ./rpm-compare.py GE 0 livecd-tools 017.1 1 - ./ovirt-cd + ./ovirt-cd $(YUMCACHE) tar: clean build mv $$(cat iso-file) ovirt.iso diff --git a/ovirt-host-creator/ovirt-cd b/ovirt-host-creator/ovirt-cd index 5678f14..9a2ba8f 100755 --- a/ovirt-host-creator/ovirt-cd +++ b/ovirt-host-creator/ovirt-cd @@ -20,8 +20,12 @@ PATH=/sbin:/bin:/usr/bin KICKSTART=ovirt.ks +CACHE= +if [ -n "$1" ]; then + CACHE=--cache=$1 +fi LABEL=ovirt-`date +%Y%m%d%H%M` -livecd-creator --skip-minimize -c $KICKSTART -f $LABEL 1>&2 && +livecd-creator $CACHE --skip-minimize -c $KICKSTART -f $LABEL 1>&2 && echo $LABEL.iso > iso-file diff --git a/wui-appliance/create-wui-appliance.sh b/wui-appliance/create-wui-appliance.sh index b15cc76..1683237 100755 --- a/wui-appliance/create-wui-appliance.sh +++ b/wui-appliance/create-wui-appliance.sh @@ -18,9 +18,10 @@ imgdir=$IMGDIR_DEFAULT usage() { case $# in 1) warn "$1"; try_h; exit 1;; esac cat < /dev/null 2>&1 if [ -n "$kickstart" ]; then mkdir -p tmp set -e - appliance-creator --config $kickstart --name $NAME --tmpdir $(pwd)/tmp + appliance-creator --config $kickstart --name $NAME \ + --tmpdir=$(pwd)/tmp $yumcache # FIXME add --compress option to appliance-creator if [ $compress -ne 0 ]; then printf "Compressing the image..." qemu-img convert -c $NAME-sda.raw -O qcow2 "$imgdir/$IMGNAME" - rm ovirt-appliance-sda.raw + rm $NAME-sda.raw else printf "Moving the image..." - mv ovirt-appliance-sda.raw "$imgdir/$IMGNAME" + mv $NAME-sda.raw "$imgdir/$IMGNAME" restorecon -v "$imgdir/$IMGNAME" fi echo done -- 1.5.5.1 From berrange at redhat.com Wed Aug 13 12:22:51 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 13 Aug 2008 13:22:51 +0100 Subject: [Ovirt-devel] [PATCH 3/5] API for Hardware Pools In-Reply-To: <1218067219-20169-4-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-4-git-send-email-dlutter@redhat.com> Message-ID: <20080813122251.GH11789@redhat.com> On Wed, Aug 06, 2008 at 05:00:17PM -0700, David Lutterkort wrote: > - List and filter HW pools by name > - CRUD of individual HW pool > - Hosts and storgae pools for a HW pool are accessible as nested resources, e.g. > /ovirt/hardware_pools/1/hosts?hostname=myhost.example.com IMHO, a parameter like 'hostname' which is always require should be encoded as part of the URL path /ovirt/hardware_pools/1/hosts/myhost.example.com Has the nice side effect of making 'wget' usage easier because you don't need to quote to get around shell metacharacter expansion problems when using '?' and '&' 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 berrange at redhat.com Wed Aug 13 12:34:42 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 13 Aug 2008 13:34:42 +0100 Subject: [Ovirt-devel] [PATCH 4/5] Find hardware_pool by path; search by parent_id In-Reply-To: <1218067219-20169-5-git-send-email-dlutter@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-5-git-send-email-dlutter@redhat.com> Message-ID: <20080813123442.GI11789@redhat.com> On Wed, Aug 06, 2008 at 05:00:18PM -0700, David Lutterkort wrote: > Get individual hardware pool with a path, e.g. with a GET to > /ovirt/hardware_pools?path=/default/foo/bar > > Get children of a hardware pool from > /ovirt/hardware_pools?parent_id=1 > > Get specific child with > /ovirt/hardware_pools?parent_id=1&name=foo Guess I have similar questions about the URI syntax here as the previous mail. Should we be encoding the name of the object as a parameter, vs part of the URI path ? I've always thought of the URI path as providing lookup for the object, and the parameters as filters/modifiers for the data returned / action performed 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 mdehaan at redhat.com Wed Aug 13 13:27:42 2008 From: mdehaan at redhat.com (Michael DeHaan) Date: Wed, 13 Aug 2008 09:27:42 -0400 Subject: [Ovirt-devel] Re: Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <20080801124104.GA6313@redhat.com> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> <20080801124104.GA6313@redhat.com> Message-ID: <48A2E14E.6050400@redhat.com> > Depending on the call, it's either got to go through the > read-only interface or the read-write interface. > Thanks for the input. That's the kind of stuff I need to get this > codebase > working cleanly. Hey Daryl, Perhaps this isn't what this question was about, but FYI: all calls can go through the read-write interface for ovirt as it extends the read-only interface. You would only want to construct something that uses the read-only interface if you needed to do something like koan. Read only access does not require login() and only has a subset of the available methods. --Michael > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From dpierce at redhat.com Wed Aug 13 13:30:39 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 13 Aug 2008 09:30:39 -0400 Subject: [Ovirt-devel] Re: Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <48A2E14E.6050400@redhat.com> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> <20080801124104.GA6313@redhat.com> <48A2E14E.6050400@redhat.com> Message-ID: <20080813133039.GD3901@redhat.com> +++ Michael DeHaan [13/08/08 09:27 -0400]: > Perhaps this isn't what this question was about, but FYI: all calls can > go through the read-write interface for ovirt as it extends the > read-only interface. > > You would only want to construct something that uses the read-only > interface if you needed to do something like koan. > > Read only access does not require login() and only has a subset of the > available methods. The way the Cobbler Ruby bindings are written, calls identify themselves as writable or not. From that, it determines whether or not to the use read or read/write interface to perform the call. The code doesn't keep a persistent connection to the Cobbler server. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From mdehaan at redhat.com Wed Aug 13 13:36:25 2008 From: mdehaan at redhat.com (Michael DeHaan) Date: Wed, 13 Aug 2008 09:36:25 -0400 Subject: [Ovirt-devel] Re: Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <20080813133039.GD3901@redhat.com> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> <20080801124104.GA6313@redhat.com> <48A2E14E.6050400@redhat.com> <20080813133039.GD3901@redhat.com> Message-ID: <48A2E359.3050907@redhat.com> Darryl L. Pierce wrote: > +++ Michael DeHaan [13/08/08 09:27 -0400]: >> Perhaps this isn't what this question was about, but FYI: all calls >> can go through the read-write interface for ovirt as it extends the >> read-only interface. >> >> You would only want to construct something that uses the read-only >> interface if you needed to do something like koan. >> >> Read only access does not require login() and only has a subset of >> the available methods. > > The way the Cobbler Ruby bindings are written, calls identify > themselves as > writable or not. From that, it determines whether or not to the use > read or > read/write interface to perform the call. The code doesn't keep a > persistent > connection to the Cobbler server. > That would be ok. Just be sure you are not making unnecessary calls to login() when you already have a token. --Michael From dpierce at redhat.com Wed Aug 13 13:44:58 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 13 Aug 2008 09:44:58 -0400 Subject: [Ovirt-devel] Re: Ruby interface for Cobbler XML-RPC APIs. In-Reply-To: <48A2E359.3050907@redhat.com> References: <1217539532-4741-1-git-send-email-dpierce@redhat.com> <1217557312.25288.193.camel@localhost.localdomain> <20080801124104.GA6313@redhat.com> <48A2E14E.6050400@redhat.com> <20080813133039.GD3901@redhat.com> <48A2E359.3050907@redhat.com> Message-ID: <20080813134458.GE3901@redhat.com> +++ Michael DeHaan [13/08/08 09:36 -0400]: > That would be ok. Just be sure you are not making unnecessary calls to > login() when you already have a token. Good point. Currently the code doesn't cache the token, so I'll put that on the backlog. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 redhat.com Wed Aug 13 14:55:13 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 13 Aug 2008 16:55:13 +0200 Subject: [Ovirt-devel] oVirt / Selenium Update In-Reply-To: <48A22CFF.30507@redhat.com> References: <48A1CC5F.1000207@redhat.com> <48A22CFF.30507@redhat.com> Message-ID: <48A2F5D1.5070504@redhat.com> Mohammed Morsi wrote: > check. Also attached is the selenium server init script with a few > additional tweaks. Both this script and autobuild now require a few > selenium related components on the host system, under /root/ovirt ok for now, but selenium Fedora RPM is now our TODO So don't check-in that selenium init script in ovirt repo, assume it's on autobuild machine. ACK for ovirt-selenium3.patch with a remark: > +if [ -f /root/ovirt/selenium.rb ]; then > + $scp_cmd /root/ovirt/selenium.rb $remote_target:/usr/share/ovirt-wui/test/ put /root/ovirt/selenium.rb in a var e.g. +SELENIUM_RB=/root/ovirt/selenium.rb +if [ -f $SELENIUM_RB ]; then + $scp_cmd $SELENIUM_RB $remote_target:/usr/share/ovirt-wui/test/ +else + echo "$SELENIUM_RB not found, will not run interface tests" +fi From jguiditt at redhat.com Wed Aug 13 16:37:57 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 13 Aug 2008 12:37:57 -0400 Subject: [Ovirt-devel] [PATCH] add permissions checks to search results. In-Reply-To: <1218044726-31953-1-git-send-email-sseago@redhat.com> References: <1218044726-31953-1-git-send-email-sseago@redhat.com> Message-ID: <1218645478.3043.5.camel@localhost.localdomain> On Wed, 2008-08-06 at 13:45 -0400, Scott Seago wrote: > To do so, I've enabled term-based parameters to each of the searchable types. At the query level, appending search_users:foo limits results to items viewable by user foo. This involved: > > 1) added :terms parameter to acts_as_xapian declaration for the method search_users > 2) added search_users method to searchable models which return an array of usernames that have 'monitor' access > 3) modified the acts_as_xapian plugin to handle prefix searches for which the object provides multiple values (since we have multiple users with access to each object) -- this is a change which may be suitable for upstream inclusion > 4) When performing the search, search for "(#{terms}) AND search_users:#{user}" instead of simply searching for terms. > > This patch is dependant on the prior search denormalization patch. > > Signed-off-by: Scott Seago After much trouble of my own making, finally got this tested properly. Search works as desired, so ACK with one caveat. I noticed when I try to add a user (under any 'user access'), clicking 'create user permission' gives me a 401, and subsequent attempts give me a 401 followed by a 500. I looked at the log in /var/log/ovirt-wui/mongrel.log, but did not see anything go by, not even an attempt at running the requested action. Perhaps this is related to the freeipa perm issues I saw being discussed in irc, but I thought it should be mentioned. This may not be at all related to this patch, and if not, then definitely ACK. -j From sseago at redhat.com Wed Aug 13 20:46:44 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 13 Aug 2008 20:46:44 +0000 Subject: [Ovirt-devel] [PATCH] Fixed bug in adding user permissions. Code wasn't sending a properly formatted json response, and the error response was redirecting rather than sending json. Message-ID: <1218660404-17218-1-git-send-email-sseago@redhat.com> Signed-off-by: Scott Seago --- wui/src/app/controllers/permission_controller.rb | 7 ++++--- 1 files changed, 4 insertions(+), 3 deletions(-) diff --git a/wui/src/app/controllers/permission_controller.rb b/wui/src/app/controllers/permission_controller.rb index 4367275..813d9d9 100644 --- a/wui/src/app/controllers/permission_controller.rb +++ b/wui/src/app/controllers/permission_controller.rb @@ -61,10 +61,11 @@ class PermissionController < ApplicationController else begin @permission.save_with_new_children - render :json => "created User Permissions for #{@permission.uid}".to_json + render :json => { :object => "permission", :success => true, + :alert => "created User Permissions for #{@permission.uid}." } rescue - # FIXME: need to handle proper error messages w/ ajax - render :action => 'new' + render :json => { :object => "permission", :success => false, + :alert => "Error adding user: #{$!}" } end end end -- 1.5.5.1 From dlutter at redhat.com Wed Aug 13 21:17:38 2008 From: dlutter at redhat.com (David Lutterkort) Date: Wed, 13 Aug 2008 21:17:38 +0000 Subject: [Ovirt-devel] [PATCH 4/5] Find hardware_pool by path; search by parent_id In-Reply-To: <20080813123442.GI11789@redhat.com> References: <1218067219-20169-1-git-send-email-dlutter@redhat.com> <1218067219-20169-5-git-send-email-dlutter@redhat.com> <20080813123442.GI11789@redhat.com> Message-ID: <1218662258.5092.217.camel@localhost.localdomain> On Wed, 2008-08-13 at 13:34 +0100, Daniel P. Berrange wrote: > On Wed, Aug 06, 2008 at 05:00:18PM -0700, David Lutterkort wrote: > > Get individual hardware pool with a path, e.g. with a GET to > > /ovirt/hardware_pools?path=/default/foo/bar > > > > Get children of a hardware pool from > > /ovirt/hardware_pools?parent_id=1 > > > > Get specific child with > > /ovirt/hardware_pools?parent_id=1&name=foo > > Guess I have similar questions about the URI syntax here as the previous > mail. Should we be encoding the name of the object as a parameter, vs > part of the URI path ? I've always thought of the URI path as providing > lookup for the object, and the parameters as filters/modifiers for the > data returned / action performed I assume this is about the path parameter: on one hand it would be nicer, on the other it would keep us from addressing nested objects, e.g. the hosts in a hardware pool as /ovirt/hardware_pools/1/hosts - is that the hosts for the hw pool with id 1 or is it the hw pool 'hosts' inside the pool '1' ? In any event, the exact structure of the URL's is an implementation detail that isn't exposed to somebody scripting against the API. (at least not in Ruby) David From mmorsi at redhat.com Wed Aug 13 22:12:01 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 13 Aug 2008 18:12:01 -0400 Subject: [Ovirt-devel] oVirt / Selenium Update In-Reply-To: <48A2F5D1.5070504@redhat.com> References: <48A1CC5F.1000207@redhat.com> <48A22CFF.30507@redhat.com> <48A2F5D1.5070504@redhat.com> Message-ID: <48A35C31.5030104@redhat.com> Alan Pevec wrote: > Mohammed Morsi wrote: >> check. Also attached is the selenium server init script with a few >> additional tweaks. Both this script and autobuild now require a few >> selenium related components on the host system, under /root/ovirt > > ok for now, but selenium Fedora RPM is now our TODO > So don't check-in that selenium init script in ovirt repo, assume it's > on autobuild machine. > > ACK for ovirt-selenium3.patch > Patch committed. Attached is the updated selenium init script, including the DISPLAY check / failure if not found. -Mo -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: selenium.init URL: From mmorsi at redhat.com Wed Aug 13 22:15:04 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 13 Aug 2008 18:15:04 -0400 Subject: [Ovirt-devel] [patch] oVirt Test Fixes Message-ID: <48A35CE8.1020606@redhat.com> Attached is a patch fixing a few things with the build system. Namely changes in autobuild.sh so that it works with the new changes made for the selenium integration, and changes to the vm controller, vm controller test, and permission fixture due to problems caused by recent changes. -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-test-fixes.patch Type: text/x-patch Size: 4896 bytes Desc: not available URL: From imain at redhat.com Wed Aug 13 22:44:05 2008 From: imain at redhat.com (Ian Main) Date: Wed, 13 Aug 2008 15:44:05 -0700 Subject: [Ovirt-devel] [PATCH] Add serial console support Message-ID: <1218667445-11150-1-git-send-email-imain@redhat.com> This patch adds serial console support to the ovirt node. With this patch, nodes in developer mode now support a serial console and can be connected to with the virsh console command. The speed is set to 115200 by default but is configurable via the kickstart option. This requires the new kvm and livecd-creator from the ovirt repo in order to work properly! Signed-off-by: Ian Main --- ovirt-host-creator/common-install.ks | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/ovirt-host-creator/common-install.ks b/ovirt-host-creator/common-install.ks index badc847..053c03b 100644 --- a/ovirt-host-creator/common-install.ks +++ b/ovirt-host-creator/common-install.ks @@ -6,6 +6,7 @@ selinux --disabled firewall --disabled part / --size 550 --fstype ext2 services --enabled=ntpd,ntpdate,collectd,iptables,network -bootloader --timeout=1 +# This requires a new fixed version of livecd-creator to honor the --append settings. +bootloader --timeout=1 --append="console=tty0 console=ttyS0,115200n8" rootpw --iscrypted Xa8QeYfWrtscM -- 1.5.5.1 From imain at redhat.com Wed Aug 13 22:45:43 2008 From: imain at redhat.com (Ian Main) Date: Wed, 13 Aug 2008 15:45:43 -0700 Subject: [Ovirt-devel] [PATCH] Add serial consoles to guests Message-ID: <1218667543-11264-1-git-send-email-imain@redhat.com> This patch adds a serial console to the guest VMs as created by taskomatic. Again this requires the latest kvm from the ovirt repo, but that should be pulled in automatically. Signed-off-by: Ian Main --- wui/src/task-omatic/task_vm.rb | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index 34749e0..d7f0869 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -80,6 +80,11 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, 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"}) + serial = Element.new("serial") + serial.add_attribute("type", "pty") + serial.add_element("target", {"port" => "0"}) + doc.root.elements["devices"] << serial + return doc end -- 1.5.5.1 From jguiditt at redhat.com Thu Aug 14 02:46:50 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 13 Aug 2008 22:46:50 -0400 Subject: [Ovirt-devel] [PATCH] use YUM cache instead of pungi in build-all.sh In-Reply-To: <1218589084-8424-1-git-send-email-apevec@redhat.com> References: <1218589084-8424-1-git-send-email-apevec@redhat.com> Message-ID: <1218682010.7568.0.camel@localhost.localdomain> On Wed, 2008-08-13 at 02:58 +0200, Alan Pevec wrote: > appliance-creator doesn't need install tree with anaconda images so we can > use YUM cache to avoid re-downloading RPMs for successive image rebuilds > > Signed-off-by: Alan Pevec ACK. Tried this, on second install of appliance went from taking almost an hour down to about 10 minutes. -j From jguiditt at redhat.com Thu Aug 14 03:12:11 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 13 Aug 2008 23:12:11 -0400 Subject: [Ovirt-devel] [PATCH] Fix broken js cause by svg widget. Message-ID: <1218683531-9412-1-git-send-email-jguiditt@redhat.com> This just disables the calls to svg widget within any flexigrids, which is the cause of checkboxes not appearing, details not showing, and other such unpleasantness. Next step would be to replace these bits with css bar graphs, which will hopefully make it in shortly, but as this breakage is annoying, thought I would do a quick fix. Also, slightly changed mmorsi's check to bypass apache for dev so it is still ised with production settings, but not in test or development. --- wui/src/app/controllers/application.rb | 2 +- wui/src/app/views/graph/_load_graph.rhtml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index eacf6f3..5ad209f 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -34,7 +34,7 @@ class ApplicationController < ActionController::Base before_filter :authorize_admin, :only => [:new, :create, :edit, :update, :destroy] def get_login_user - if ENV["RAILS_ENV"] != 'test' + if ENV["RAILS_ENV"] == 'production' user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) else 'ovirtadmin' diff --git a/wui/src/app/views/graph/_load_graph.rhtml b/wui/src/app/views/graph/_load_graph.rhtml index f6827b2..6678e90 100644 --- a/wui/src/app/views/graph/_load_graph.rhtml +++ b/wui/src/app/views/graph/_load_graph.rhtml @@ -5,10 +5,10 @@ function load_widget(div, target){ var id = $(div).html(); $(div).html(''); $(div).addClass("load_graph"); - $(div).svg(); - var svg = svgManager.getSVGFor(div); + //$(div).svg(); + //var svg = svgManager.getSVGFor(div); var params = { id:1, type:"json", timeframe:"7 days", isJSON:true}; - $.getJSON("<%= url_for :controller => 'graph', :action => 'load_graph_data' %>/" + id + "?target=" + target, params, + /*$.getJSON("<%= url_for :controller => 'graph', :action => 'load_graph_data' %>/" + id + "?target=" + target, params, function(response){ svg.graph.noDraw(); svg.graph.chartFormat('white', 'white'). @@ -25,10 +25,10 @@ function load_widget(div, target){ svg.graph.legend.show(false); svg.graph.redraw(); } - ); + );*/ // $(div).children().filter("svg").attr('height', 25).attr('width', 200); - $(div).children().filter("svg").attr("height").baseVal.value = 25; - $(div).children().filter("svg").attr("width").baseVal.value = 200; + //$(div).children().filter("svg").attr("height").baseVal.value = 25; + //$(div).children().filter("svg").attr("width").baseVal.value = 200; }; // invoked when a row containing a load widgit is selected @@ -38,11 +38,11 @@ function toggle_load_widget(div, state){ color = '#D5EFFC'; } - var graph = svgManager.getSVGFor(div).graph; + /*var graph = svgManager.getSVGFor(div).graph; graph.noDraw().chartFormat(color, color); graph.xAxis.line(color, 0); graph.yAxis.line(color, 0); - graph.redraw(); + graph.redraw();*/ }; function load_widget_select(selected_row) -- 1.5.5.1 From slinabery at redhat.com Thu Aug 14 07:45:12 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 14 Aug 2008 02:45:12 -0500 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts Message-ID: <20080814074512.GA1778@redhat.com> Once again, apologies for the attachment. Also, apologies for the comments in wui-devel.ks which wrap past 80 chars. Please help me test this. Build a new appliance with this patch, ssh to the appliance, set a new ipa password for ovirtadmin, and then kdestroy. If you launch browser after that, you should get authorization requested dialog from firefox, and (following entry of correct username/password) get redirected back to dashboard. Goodnight! Steve -------------- next part -------------- >From 49410330dd46413b30c1ed29ec86cc73c6cf2f41 Mon Sep 17 00:00:00 2001 From: Steve Linabery Date: Thu, 14 Aug 2008 02:41:17 -0500 Subject: [PATCH] Add username/password authentication for browsing from non-kerberized hosts Add cookie-based session support and migration file. New login controller. --- wui-appliance/wui-devel.ks | 7 +++++ wui/conf/ovirt-wui.conf | 6 ++-- wui/src/app/controllers/application.rb | 15 +++++----- wui/src/app/controllers/login_controller.rb | 39 +++++++++++++++++++++++++++ wui/src/config/environment.rb | 2 +- wui/src/db/migrate/013_create_sessions.rb | 35 ++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 wui/src/app/controllers/login_controller.rb create mode 100644 wui/src/db/migrate/013_create_sessions.rb diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index e36f3a7..5c334d1 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -152,6 +152,13 @@ start() { ipa-server-install -r PRIV.OVIRT.ORG -p @password@ -P @password@ -a @password@ \ --hostname management.priv.ovirt.org -u dirsrv -U + # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459061 + # note: this has to happen after ipa-server-install or the templating feature + # in ipa-server-install chokes on the characters in the regexp we add here. + sed -i -e 's###' /etc/httpd/conf.d/ipa.conf + sed -i -e 's###' /etc/httpd/conf.d/ipa.conf + sed -i -e 's/^/#/' /etc/httpd/conf.d/ipa-rewrite.conf + /usr/sbin/apachectl restart # now create the ovirtadmin user echo @password@|kinit admin # change max username length policy diff --git a/wui/conf/ovirt-wui.conf b/wui/conf/ovirt-wui.conf index f56ce81..63e1dc4 100644 --- a/wui/conf/ovirt-wui.conf +++ b/wui/conf/ovirt-wui.conf @@ -2,11 +2,11 @@ NameVirtualHost *:80 ProxyRequests Off - + AuthType Kerberos AuthName "Kerberos Login" KrbMethodNegotiate on - KrbMethodK5Passwd off + KrbMethodK5Passwd on KrbServiceName HTTP Krb5KeyTab /etc/httpd/conf/ipa.keytab KrbSaveCredentials on @@ -26,7 +26,7 @@ ProxyRequests Off RequestHeader set X-Forwarded-Keytab %{KRB5CCNAME}e # RequestHeader unset Authorization - + Alias /ovirt/stylesheets "/usr/share/ovirt-wui/public/stylesheets" Alias /ovirt/images "/usr/share/ovirt-wui/public/images" diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index eacf6f3..53d0aa6 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -32,17 +32,16 @@ class ApplicationController < ActionController::Base before_filter :pre_show, :only => [:show, :show_vms, :show_users, :show_hosts, :show_storage] before_filter :authorize_admin, :only => [:new, :create, :edit, :update, :destroy] + before_filter :is_logged_in - def get_login_user - if ENV["RAILS_ENV"] != 'test' - user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) - else - 'ovirtadmin' + def is_logged_in + if session[:user] == nil + redirect_to :controller => "login", :action => "login" end end - - def user_from_principal(principal) - principal.split('@')[0] + + def get_login_user + session[:user] end def set_perms(hwpool) diff --git a/wui/src/app/controllers/login_controller.rb b/wui/src/app/controllers/login_controller.rb new file mode 100644 index 0000000..5babb43 --- /dev/null +++ b/wui/src/app/controllers/login_controller.rb @@ -0,0 +1,39 @@ +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Steve Linabery +# +# 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 LoginController < ActionController::Base + + before_filter :is_logged_in, :except => :login + def login + myUser = "ovirtadmin" + if ENV["RAILS_ENV"] != "test" + myUser = user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) + end + session[:user] = myUser + redirect_to :controller => "dashboard" + end + + def user_from_principal(principal) + principal.split('@')[0] + end + +end diff --git a/wui/src/config/environment.rb b/wui/src/config/environment.rb index 379dcf4..d14899a 100644 --- a/wui/src/config/environment.rb +++ b/wui/src/config/environment.rb @@ -44,7 +44,7 @@ Rails::Initializer.run do |config| # Use the database for sessions instead of the file system # (create the session table with 'rake db:sessions:create') - # config.action_controller.session_store = :active_record_store + config.action_controller.session_store = :active_record_store config.action_controller.session = { :session_key => "_ovirt_session_id", :secret => "a covert ovirt phrase or some such" diff --git a/wui/src/db/migrate/013_create_sessions.rb b/wui/src/db/migrate/013_create_sessions.rb new file mode 100644 index 0000000..9eca543 --- /dev/null +++ b/wui/src/db/migrate/013_create_sessions.rb @@ -0,0 +1,35 @@ +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Steve Linabery +# +# 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 CreateSessions < ActiveRecord::Migration + def self.up + create_table :sessions do |t| + t.string :session_id, :null => false + t.text :data + t.timestamps + end + + add_index :sessions, :session_id + add_index :sessions, :updated_at + end + + def self.down + drop_table :sessions + end +end -- 1.5.5.2 From dpierce at redhat.com Thu Aug 14 14:19:07 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 14 Aug 2008 10:19:07 -0400 Subject: [Ovirt-devel] [PATCH] The identify node phase now passes the interface name when identifying Message-ID: <1218723547-18257-1-git-send-email-dpierce@redhat.com> You will need to do a rake db:migrate for this patch. Signed-off-by: Darryl L. Pierce --- ovirt-managed-node/src/gather.c | 2 + ovirt-managed-node/src/ovirt-identify-node.h | 1 + ovirt-managed-node/src/protocol.c | 3 +- wui/src/db/migrate/013_add_iface_name_to_nics.rb | 9 + wui/src/host-browser/host-browser.rb | 582 ++++++++++---------- wui/src/host-browser/test-host-browser-identify.rb | 490 +++++++++-------- 6 files changed, 555 insertions(+), 532 deletions(-) create mode 100644 wui/src/db/migrate/013_add_iface_name_to_nics.rb diff --git a/ovirt-managed-node/src/gather.c b/ovirt-managed-node/src/gather.c index 39be6fd..7fa0992 100644 --- a/ovirt-managed-node/src/gather.c +++ b/ovirt-managed-node/src/gather.c @@ -205,6 +205,8 @@ get_nic_data(char *nic, nic_info_ptr nic_info) interface = libhal_device_get_property_string(hal_ctx, nic, "net.interface", &dbus_error); + snprintf(nic_info->interface_name, BUFFER_LENGTH, "%s", interface); + bzero(&ifr, sizeof(struct ifreq)); sockfd = socket(AF_INET, SOCK_DGRAM, 0); diff --git a/ovirt-managed-node/src/ovirt-identify-node.h b/ovirt-managed-node/src/ovirt-identify-node.h index c595891..b2814fa 100644 --- a/ovirt-managed-node/src/ovirt-identify-node.h +++ b/ovirt-managed-node/src/ovirt-identify-node.h @@ -67,6 +67,7 @@ typedef struct _nic_info { char mac_address[BUFFER_LENGTH]; char bandwidth[BUFFER_LENGTH]; char ip_address[BUFFER_LENGTH]; + char interface_name[BUFFER_LENGTH]; struct _nic_info* next; } t_nic_info; diff --git a/ovirt-managed-node/src/protocol.c b/ovirt-managed-node/src/protocol.c index 131bb38..d5c5fac 100644 --- a/ovirt-managed-node/src/protocol.c +++ b/ovirt-managed-node/src/protocol.c @@ -181,7 +181,8 @@ send_nic_details(void) if (!(get_text("NICINFO?")) && (!send_value("MAC", current->mac_address)) && - (!send_value("BANDWIDTH", current->bandwidth))) { + (!send_value("BANDWIDTH", current->bandwidth)) && + (!send_value("IFACE_NAME", current->interface_name))) { send_text("ENDNIC"); result = get_text("ACK NIC"); } diff --git a/wui/src/db/migrate/013_add_iface_name_to_nics.rb b/wui/src/db/migrate/013_add_iface_name_to_nics.rb new file mode 100644 index 0000000..c829c20 --- /dev/null +++ b/wui/src/db/migrate/013_add_iface_name_to_nics.rb @@ -0,0 +1,9 @@ +class AddIntfNameToNics < ActiveRecord::Migration + def self.up + add_column :nics, :iface_name, :string, :limit => 10 + end + + def self.down + remove_column :nics, :iface_name + end +end diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 881b2ae..26981fd 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -39,361 +39,369 @@ $logfile = '/var/log/ovirt-wui/host-browser.log' # about the node and then updates the list of active nodes for the WUI. # class HostBrowser - attr_accessor :logfile - attr_accessor :keytab_dir - attr_accessor :keytab_filename - - def initialize(session) - @session = session - @log_prefix = "[#{session.peeraddr[3]}] " - @keytab_dir = '/usr/share/ipa/html/' - end - - # Ensures the conversation starts properly. - # - def begin_conversation - puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) - @session.write("HELLO?\n") - - response = @session.readline.chomp - raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" - end - - # Retrieves the mode request from the remote system. - # - def get_mode - puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) - @session.write("MODE?\n") - response = @session.readline.chomp - puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + attr_accessor :logfile + attr_accessor :keytab_dir + attr_accessor :keytab_filename + + def initialize(connection) + @connection = connection + @log_prefix = "[#{connection.peeraddr[3]}] " + @keytab_dir = '/usr/share/ipa/html/' + end + + # Ensures the conversation starts properly. + # + def begin_conversation + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) + @connection.write("HELLO?\n") + + response = @connection.readline.chomp + raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" + end + + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @connection.write("MODE?\n") + response = @connection.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + + # Requests node information from the remote system. + # + def get_remote_info + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) + result = Hash.new + result['HOSTNAME'] = @connection.peeraddr[2] + result['IPADDR'] = @connection.peeraddr[3] + + @connection.write("INFO?\n") + + loop do + info = @connection.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 - response - end + cpu_info << cpu + + when "NIC" + nic = get_nic_info + nic_info = result['NICINFO'] - # Requests node information from the remote system. - # - def get_remote_info - puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) - result = Hash.new - result['HOSTNAME'] = @session.peeraddr[2] - result['IPADDR'] = @session.peeraddr[3] + if(nic_info == nil) + nic_info = Array.new + result['NICINFO'] = nic_info + end - @session.write("INFO?\n") + nic_info << nic + + else + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - loop do - info = @session.readline.chomp + key, value = info.split("=") - puts "Received info='#{info}'" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - break if info == "ENDINFO" + @connection.write("ACK #{key}\n") + end + end - case info - when "CPU" - cpu = get_cpu_info - cpu_info = result['CPUINFO'] + return result + end - if(cpu_info == nil) - cpu_info = Array.new - result['CPUINFO'] = cpu_info - end + # Extracts CPU details from the managed node. + # + def get_cpu_info + puts "Begin receiving CPU details" - cpu_info << cpu - when "NIC" - nic = get_nic_info - nic_info = result['NICINFO'] + result = Hash.new - if(nic_info == nil) - nic_info = Array.new - result['NICINFO'] = nic_info - end + @connection.write("CPUINFO?\n") - nic_info << nic - else + loop do + info = @connection.readline.chomp - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + break if info == "ENDCPU" - key, value = info.split("=") + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + key, value = info.split("=") - @session.write("ACK #{key}\n") - end - end + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts CPU details from the managed node. - # - def get_cpu_info - puts "Begin receiving CPU details" + @connection.write("ACK CPU\n"); - result = Hash.new + return result + end - @session.write("CPUINFO?\n") + # Extracts NIC details from the managed node. + # + def get_nic_info + puts "Begin receiving NIC details" - loop do - info = @session.readline.chomp + result = Hash.new - break if info == "ENDCPU" + @connection.write("NICINFO?\n") - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + loop do + info = @connection.readline.chomp - key, value = info.split("=") + break if info == "ENDNIC" - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - @session.write("ACK #{key}\n") - end + key, value = info.split("=") - @session.write("ACK CPU\n"); + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts NIC details from the managed node. - # - def get_nic_info - puts "Begin receiving NIC details" - - result = Hash.new - - @session.write("NICINFO?\n") + @connection.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 + + nic_info.each do |nic| + ensure_present(nic, 'MAC') + ensure_present(nic, 'BANDWIDTH') + ensure_present(nic, 'IFACE_NAME') + end - loop do - info = @session.readline.chomp + 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 - break if info == "ENDNIC" + # 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.collect do |cpu| + detail = Cpu.new( + "cpu_number" => cpu['CPUNUM'], + "core_number" => cpu['CORENUM]'], + "number_of_cores" => cpu['NUMCORES'], + "vendor" => cpu['VENDOR'], + "model" => cpu['MODEL'], + "family" => cpu['FAMILY'], + "cpuid_level" => cpu['CPUIDLVL'], + "speed" => cpu['SPEED'], + "cache" => cpu['CACHE'], + "flags" => cpu['FLAGS']) + + host.cpus << detail + end - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + # 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 - key, value = info.split("=") + puts "Updating NIC records for the node" + nics = Array.new - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + host.nics.collect do |nic| + found = false - @session.write("ACK #{key}\n") + nic_info.collect do |detail| + # if we have a match, then update the database and remove + # the received data to avoid creating a dupe later + if detail['MAC'] == nic.mac + nic_info.delete(detail) end + end - @session.write("ACK NIC\n"); - - return result + # if the record wasn't found, then remove it from the database + unless found + host.nics.delete(nic) + nic.destroy + end 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 + # iterate over any nics left and create new records for them. - 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 + nic_info.collect do |nic| + puts "Creating a new nic..." + detail = Nic.new( + 'mac' => nic['MAC'], + 'bandwidth' => nic['BANDWIDTH'], + 'iface_name' => nic['IFACE_NAME'], + 'usage_type' => 1) - # 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.collect do |cpu| - detail = Cpu.new( - "cpu_number" => cpu['CPUNUM'], - "core_number" => cpu['CORENUM]'], - "number_of_cores" => cpu['NUMCORES'], - "vendor" => cpu['VENDOR'], - "model" => cpu['MODEL'], - "family" => cpu['FAMILY'], - "cpuid_level" => cpu['CPUIDLVL'], - "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 - - host.nics.collect do |nic| - found = false - - nic_info.collect do |detail| - # if we have a match, then update the database and remove - # the received data to avoid creating a dupe later - if detail['MAC'] == nic.mac - nic_info.delete(detail) - end - end - - # if the record wasn't found, then remove it from the database - unless found - host.nics.delete(nic) - nic.destroy - end - end - - # iterate over any nics left and create new records for them. - - nic_info.collect do |nic| - puts "Creating a new nic..." - detail = Nic.new( - 'mac' => nic['MAC'], - 'bandwidth' => nic['BANDWIDTH'], - 'usage_type' => 1) + host.nics << detail + end - host.nics << detail - end + host.save! - host.save! + return host + end - return host - end + # Creates a keytab if one is needed, returning the filename. + # + def create_keytab(hostname, ipaddress, krb5_arg = nil) + krb5 = krb5_arg || Krb5.new - # Creates a keytab if one is needed, returning the filename. - # - def create_keytab(hostname, ipaddress, krb5_arg = nil) - krb5 = krb5_arg || Krb5.new + default_realm = krb5.get_default_realm + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' + @keytab_filename = @keytab_dir + outfile - default_realm = krb5.get_default_realm - libvirt_princ = 'libvirt/' + hostname + '@' + default_realm - outfile = ipaddress + '-libvirt.tab' - @keytab_filename = @keytab_dir + outfile + # TODO need a way to test this portion + unless (defined? TESTING) || File.exists?(@keytab_filename) + # TODO replace with Kr5Auth when it supports admin actions + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) + kadmin_local('addprinc -randkey ' + libvirt_princ) + kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) - # TODO need a way to test this portion - unless (defined? TESTING) || File.exists?(@keytab_filename) - # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) - kadmin_local('addprinc -randkey ' + libvirt_princ) - kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) + File.chmod(0644, at keytab_filename) + end - File.chmod(0644, at keytab_filename) - end + hostname = `hostname -f`.chomp - hostname = `hostname -f`.chomp + @connection.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") - @session.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") + response = @connection.readline.chomp - response = @session.readline.chomp + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end - raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" - end + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation - puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + @connection.write("BYE\n"); + end - @session.write("BYE\n"); - end + private - 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 - # 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) - system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") - end + # Executes an external program to support the keytab function. + # + def kadmin_local(command) + system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") + end end def entry_point(server) - while(session = server.accept) - child = fork do - remote = session.peeraddr[2] - - 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 + while(session = server.accept) + child = fork do + remote = session.peeraddr[2] - begin - browser = HostBrowser.new(session) + puts "Connected to #{remote}" unless defined?(TESTING) - 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 + # This is needed because we just forked a new process + # which now needs its own connection to the database. + database_connect - browser.end_conversation - rescue Exception => error - session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" unless defined?(TESTING) - end + begin + browser = HostBrowser.new(session) - puts "Disconnected from #{remote}" unless defined?(TESTING) + 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 - Process.detach(child) + browser.end_conversation + rescue Exception => error + session.write("ERROR #{error.message}\n") + puts "ERROR #{error.message}" unless defined?(TESTING) + end + + puts "Disconnected from #{remote}" unless defined?(TESTING) end + + Process.detach(child) + end end unless defined?(TESTING) - # The main entry point. - # - unless ARGV[0] == "-n" - daemonize - # redirect output to the log - STDOUT.reopen $logfile, 'a' - STDERR.reopen STDOUT - end - - server = TCPServer.new("",12120) - entry_point(server) + # The main entry point. + # + unless ARGV[0] == "-n" + daemonize + # redirect output to the log + STDOUT.reopen $logfile, 'a' + STDERR.reopen STDOUT + end + + server = TCPServer.new("",12120) + entry_point(server) end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb index 7e672ce..e25497f 100755 --- a/wui/src/host-browser/test-host-browser-identify.rb +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -27,257 +27,259 @@ TESTING=true require 'host-browser' class TestHostBrowser < Test::Unit::TestCase - def setup - @session = flexmock('session') - @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @browser = HostBrowser.new(@session) - @browser.logfile = './unit-test.log' - - # default host info - @host_info = {} - @host_info['UUID'] = 'node1' - @host_info['IPADDR'] = '192.168.2.2' - @host_info['HOSTNAME'] = '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 \ + 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 \ + @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'][0] = {} - @host_info['NICINFO'][0]['MAC'] = '00:11:22:33:44:55' - @host_info['NICINFO'][0]['BANDWIDTH'] = '100' - - @host_info['NICINFO'][1] = {} - @host_info['NICINFO'][1]['MAC'] = '00:77:11:77:19:65' - @host_info['NICINFO'][1]['BANDWIDTH'] = '100' - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "IDENTIFY\n" } - - result = @browser.get_mode() - - assert_equal "IDENTIFY", result, "method did not return the right value" - end - - # Ensures that, if an info field is missing a key, the server raises - # an exception. - # - def test_get_info_with_missing_key - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "=value1\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if an info field is missing a value, the server raises - # an exception. - # - def test_get_info_with_missing_value - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if the server gets a poorly formed ending statement, it - # raises an exception. - # - def test_get_info_with_invalid_end - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDIFNO\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that a well-formed transaction works as expected. - # - def test_get_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?("IPADDR") - assert info.include?("HOSTNAME") - 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 the browser can properly parse the CPU details. - # - def test_parse_cpu_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - - # CPU 0 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - # CPU 1 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key3=value3\n" } - @session.should_receive(:write).with("ACK key3\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key4=value4\n" } - @session.should_receive(:write).with("ACK key4\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "NIC\n" } - @session.should_receive(:write).with("NICINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDNIC\n" } - @session.should_receive(:write).with("ACK NIC\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain four keys" - assert info.include?("NICINFO") - end + @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 4,info.keys.size, "Should contain four keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + 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 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 3,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 3,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.5.5.1 From apevec at redhat.com Thu Aug 14 14:26:35 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 14 Aug 2008 16:26:35 +0200 Subject: [Ovirt-devel] [PATCH] Fix broken js cause by svg widget. In-Reply-To: <1218683531-9412-1-git-send-email-jguiditt@redhat.com> References: <1218683531-9412-1-git-send-email-jguiditt@redhat.com> Message-ID: <48A4409B.2060103@redhat.com> Jason Guiditta wrote: > This just disables the calls to svg widget within any flexigrids, which is the cause of checkboxes not appearing, details not showing, and other such unpleasantness. Next step would be to replace these bits with css bar graphs, which will hopefully make it in shortly, but as this breakage is annoying, thought I would do a quick fix. > Also, slightly changed mmorsi's check to bypass apache for dev so it is still ised with production settings, but not in test or development. ACK w/ comments: > --- a/wui/src/app/controllers/application.rb > +++ b/wui/src/app/controllers/application.rb > def get_login_user > - if ENV["RAILS_ENV"] != 'test' > + if ENV["RAILS_ENV"] == 'production' after session auth patch, this check is now in login_controller.rb: --- a/wui/src/app/controllers/login_controller.rb +++ b/wui/src/app/controllers/login_controller.rb @@ -25,7 +25,7 @@ class LoginController < ActionController::Base before_filter :is_logged_in, :except => :login def login myUser = "ovirtadmin" - if ENV["RAILS_ENV"] != "test" + if ENV["RAILS_ENV"] == "production" myUser = user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) end session[:user] = myUser > diff --git a/wui/src/app/views/graph/_load_graph.rhtml b/wui/src/app/views/graph/_load_graph.rhtml > - $(div).svg(); > - var svg = svgManager.getSVGFor(div); > + //$(div).svg(); > + //var svg = svgManager.getSVGFor(div); just delete lines, don't comment out, we have git to keep history same for the rest of the file From apevec at redhat.com Thu Aug 14 14:35:45 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 14 Aug 2008 16:35:45 +0200 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts In-Reply-To: <20080814074512.GA1778@redhat.com> References: <20080814074512.GA1778@redhat.com> Message-ID: <48A442C1.5050901@redhat.com> Steve Linabery wrote: > Also, apologies for the comments in wui-devel.ks which wrap past 80 chars. no apologies, use \ :) > Please help me test this. Build a new appliance with this patch, ssh to the appliance, set a new ipa password for ovirtadmin, and then kdestroy. If you launch browser after that, you should get authorization requested dialog from firefox, and (following entry of correct username/password) get redirected back to dashboard. works as advertised, so ACK Comments: - when to expire the session? - and related: do we need explicit logout action? > diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks > + /usr/sbin/apachectl restart why not service httpd reload ? From apevec at redhat.com Thu Aug 14 14:40:19 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 14 Aug 2008 16:40:19 +0200 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts In-Reply-To: <48A442C1.5050901@redhat.com> References: <20080814074512.GA1778@redhat.com> <48A442C1.5050901@redhat.com> Message-ID: <48A443D3.1020000@redhat.com> and trailing space... > +++ b/wui/src/app/controllers/login_controller.rb > @@ -0,0 +1,39 @@ > +# ...here From slinabery at redhat.com Thu Aug 14 15:04:43 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 14 Aug 2008 10:04:43 -0500 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts In-Reply-To: <48A443D3.1020000@redhat.com> References: <20080814074512.GA1778@redhat.com> <48A442C1.5050901@redhat.com> <48A443D3.1020000@redhat.com> Message-ID: <20080814150443.GA5066@redhat.com> On Thu, Aug 14, 2008 at 04:40:19PM +0200, Alan Pevec wrote: > and trailing space... > > > +++ b/wui/src/app/controllers/login_controller.rb > > @@ -0,0 +1,39 @@ > > +# > ...here > > Odd, I got no warnings from git. From slinabery at redhat.com Thu Aug 14 15:13:01 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 14 Aug 2008 10:13:01 -0500 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts In-Reply-To: <48A442C1.5050901@redhat.com> References: <20080814074512.GA1778@redhat.com> <48A442C1.5050901@redhat.com> Message-ID: <20080814151300.GB5066@redhat.com> On Thu, Aug 14, 2008 at 04:35:45PM +0200, Alan Pevec wrote: > Steve Linabery wrote: >> Also, apologies for the comments in wui-devel.ks which wrap past 80 chars. > > no apologies, use \ :) > Yeah, I realized how silly this was as I laid my head on my pillow at 3 AM... >> Please help me test this. Build a new appliance with this patch, ssh to the appliance, set a new ipa password for ovirtadmin, and then kdestroy. If you launch browser after that, you should get authorization requested dialog from firefox, and (following entry of correct username/password) get redirected back to dashboard. > > works as advertised, so ACK > > Comments: > - when to expire the session? > - and related: do we need explicit logout action? these will be addressed in a later patch. > >> diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks >> + /usr/sbin/apachectl restart > > why not service httpd reload ? > I'll change this, add a notation about the additional bugzilla for ipa-rewrite, possibly add session expires, and re-post this patch later today. Thanks for testing it! Steve From sseago at redhat.com Thu Aug 14 15:50:43 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 14 Aug 2008 11:50:43 -0400 Subject: [Ovirt-devel] Re: [patch] oVirt Test Fixes In-Reply-To: <48A35CE8.1020606@redhat.com> References: <48A35CE8.1020606@redhat.com> Message-ID: <48A45453.6090809@redhat.com> Mohammed Morsi wrote: > Attached is a patch fixing a few things with the build system. Namely > changes in autobuild.sh so that it works with the new changes made for > the selenium integration, and changes to the vm controller, vm > controller test, and permission fixture due to problems caused by > recent changes. > > -Mo > From 73b27d4e104b0b23eccefbe50c710c7e0975407c Mon Sep 17 00:00:00 2001 > From: Mohammed Morsi > Date: Wed, 13 Aug 2008 18:08:01 -0400 > Subject: [PATCH] fixes to autobuild and tests due to recent changes > > --- > autobuild.sh | 5 +- > wui/src/app/controllers/vm_controller.rb | 3 +- > wui/src/test/fixtures/permissions.yml | 100 > ++++++++++++++++++++++++- > wui/src/test/functional/vm_controller_test.rb | 4 +- > 4 files changed, 105 insertions(+), 7 deletions(-) > Ack for everything but the autobuild.sh bits -- slinabery can look at this. Scott From jguiditt at redhat.com Thu Aug 14 16:34:41 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 14 Aug 2008 12:34:41 -0400 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts In-Reply-To: <20080814074512.GA1778@redhat.com> References: <20080814074512.GA1778@redhat.com> Message-ID: <1218731681.3789.15.camel@localhost.localdomain> Overall, ACK -works for me. Couple notes/tweaks below. On Thu, 2008-08-14 at 02:45 -0500, Steve Linabery wrote: > Once again, apologies for the attachment. > > Also, apologies for the comments in wui-devel.ks which wrap past 80 chars. > > Please help me test this. Build a new appliance with this patch, ssh to the appliance, set a new ipa password for ovirtadmin, and then kdestroy. If you launch browser after that, you should get authorization requested dialog from firefox, and (following entry of correct username/password) get redirected back to dashboard. > > Goodnight! > Steve > diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index eacf6f3..53d0aa6 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -32,17 +32,16 @@ class ApplicationController < ActionController::Base before_filter :pre_show, :only => [:show, :show_vms, :show_users, :show_hosts, :show_storage] before_filter :authorize_admin, :only => [:new, :create, :edit, :update, :destroy] + before_filter :is_logged_in - def get_login_user - if ENV["RAILS_ENV"] != 'test' - user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) - else - 'ovirtadmin' + def is_logged_in + if session[:user] == nil + redirect_to :controller => "login", :action => "login" we may want to change this next rev, since we don't really want a redirect while the user is logged in and just reauthing diff --git a/wui/src/app/controllers/login_controller.rb b/wui/src/app/controllers/login_controller.rb + before_filter :is_logged_in, :except => :login + def login + myUser = "ovirtadmin" + if ENV["RAILS_ENV"] != "test" + myUser = user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) + end + session[:user] = myUser To combine this with what I did in my earlier patch (which I will regenerate w/o the env test) and to make it more succinct, I would suggest changing the above block to: session[:user] = (ENV["RAILS_ENV"] == "production") ? user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) : "ovirtadmin" + redirect_to :controller => "dashboard" Same comment as in application.rb for the redirect -j > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From jguiditt at redhat.com Thu Aug 14 17:00:08 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 14 Aug 2008 13:00:08 -0400 Subject: [Ovirt-devel] [PATCH] Fix broken js cause by svg widget. (revised) Message-ID: <1218733208-26808-1-git-send-email-jguiditt@redhat.com> This just disables the calls to svg widget within any flexigrids, which is the cause of checkboxes not appearing, details not showing, and other such unpleasantness. Next step would be to replace these bits with css bar graphs, which will hopefully make it in shortly, but as this breakage is annoying, thought I would do a quick fix. Left it as commented out since the very next step is to replace with css and I want to easily see what I am replacing. --- wui/src/app/views/graph/_load_graph.rhtml | 16 ++++++++-------- 1 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wui/src/app/views/graph/_load_graph.rhtml b/wui/src/app/views/graph/_load_graph.rhtml index f6827b2..6678e90 100644 --- a/wui/src/app/views/graph/_load_graph.rhtml +++ b/wui/src/app/views/graph/_load_graph.rhtml @@ -5,10 +5,10 @@ function load_widget(div, target){ var id = $(div).html(); $(div).html(''); $(div).addClass("load_graph"); - $(div).svg(); - var svg = svgManager.getSVGFor(div); + //$(div).svg(); + //var svg = svgManager.getSVGFor(div); var params = { id:1, type:"json", timeframe:"7 days", isJSON:true}; - $.getJSON("<%= url_for :controller => 'graph', :action => 'load_graph_data' %>/" + id + "?target=" + target, params, + /*$.getJSON("<%= url_for :controller => 'graph', :action => 'load_graph_data' %>/" + id + "?target=" + target, params, function(response){ svg.graph.noDraw(); svg.graph.chartFormat('white', 'white'). @@ -25,10 +25,10 @@ function load_widget(div, target){ svg.graph.legend.show(false); svg.graph.redraw(); } - ); + );*/ // $(div).children().filter("svg").attr('height', 25).attr('width', 200); - $(div).children().filter("svg").attr("height").baseVal.value = 25; - $(div).children().filter("svg").attr("width").baseVal.value = 200; + //$(div).children().filter("svg").attr("height").baseVal.value = 25; + //$(div).children().filter("svg").attr("width").baseVal.value = 200; }; // invoked when a row containing a load widgit is selected @@ -38,11 +38,11 @@ function toggle_load_widget(div, state){ color = '#D5EFFC'; } - var graph = svgManager.getSVGFor(div).graph; + /*var graph = svgManager.getSVGFor(div).graph; graph.noDraw().chartFormat(color, color); graph.xAxis.line(color, 0); graph.yAxis.line(color, 0); - graph.redraw(); + graph.redraw();*/ }; function load_widget_select(selected_row) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 17:10:06 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 17:10:06 +0000 Subject: [Ovirt-devel] [PATCH] Fix broken js cause by svg widget. (revised) In-Reply-To: <1218733208-26808-1-git-send-email-jguiditt@redhat.com> References: <1218733208-26808-1-git-send-email-jguiditt@redhat.com> Message-ID: <1218733806.5092.236.camel@localhost.localdomain> On Thu, 2008-08-14 at 13:00 -0400, Jason Guiditta wrote: > This just disables the calls to svg widget within any flexigrids, > which is the cause of checkboxes not appearing, details not showing, > and other such unpleasantness. Next step would be to replace these > bits with css bar graphs, which will hopefully make it in shortly, but > as this breakage is annoying, thought I would do a quick fix. Left it > as commented out since the very next step is to replace with css and I > want to easily see what I am replacing. If the goal is to simply make these things work again, this patch works for me: it simply bypasses the check for SVG support. The way graphs are turned off is what ultimately leads to disappearing checkboxes etc. because of cascading JS errors. Running with this patch has worked fine for me with FF 3 and galeon. And you still get to look at the graphs :) commit a9054e690d18e915abbc30efb0d2e6902f0fe1ae Author: David Lutterkort Date: Wed Jul 30 17:56:58 2008 -0700 Make jquery/SVG slightly less busted (ugly hack) diff --git a/wui/src/app/views/layouts/redux.rhtml b/wui/src/app/views/layouts/redux.rhtml index 924f300..bcabe91 100644 --- a/wui/src/app/views/layouts/redux.rhtml +++ b/wui/src/app/views/layouts/redux.rhtml @@ -23,7 +23,7 @@ <%= javascript_include_tag "flexigrid.js" -%> <%= javascript_include_tag "facebox.js" -%> <%= javascript_include_tag "jquery.timers.js" -%> - <%= javascript_include_tag "jquery-svg/jquery.svg.pack.js" -%> + <%= javascript_include_tag "jquery-svg/jquery.svg.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svggraph.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svggraph.js" -%> - <%= select_tag_with_label "Operating System:", 'cobbler_profile', {"Fedora 9" => "fedora9"}, :style=>"width:250px;" %> + <%= select_with_label "Operating System:", 'vm', 'cobbler_profile', @cobbler_profiles, :style=>"width:250px;" if create %>
Resources
diff --git a/wui/src/app/views/vm/show.rhtml b/wui/src/app/views/vm/show.rhtml index 674a66a..03395f7 100644 --- a/wui/src/app/views/vm/show.rhtml +++ b/wui/src/app/views/vm/show.rhtml @@ -87,24 +87,26 @@
- Uuid:
- Num vcpus allocated:
- Num vcpus used:
- Memory allocated:
- Memory used:
- vNIC MAC address:
- Boot device:
- State:
- Pending State:
+ Uuid:
+ Num vcpus allocated:
+ Num vcpus used:
+ Memory allocated:
+ Memory used:
+ vNIC MAC address:
+ Boot device:
+ Cobbler profile:
+ State:
+ Pending State:
<%=h @vm.uuid %>
<%=h @vm.num_vcpus_allocated %>
- <%=h @vm.num_vcpus_used %>
+ <%=h @vm.num_vcpus_used %>
<%=h @vm.memory_allocated_in_mb %> MB
<%=h @vm.memory_used_in_mb %> MB
<%=h @vm.vnic_mac_addr %>
<%=h @vm.boot_device %>
+ <%=h @vm.cobbler_profile %>
<%=h @vm.state %> <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) diff --git a/wui/src/config/cobbler.yml b/wui/src/config/cobbler.yml new file mode 100644 index 0000000..0f78d20 --- /dev/null +++ b/wui/src/config/cobbler.yml @@ -0,0 +1,5 @@ +# Cobbler connection details + +hostname: localhost +username: ovirt +password: ovirt diff --git a/wui/src/db/migrate/013_add_cobbler_to_vms.rb b/wui/src/db/migrate/013_add_cobbler_to_vms.rb new file mode 100644 index 0000000..a8f8250 --- /dev/null +++ b/wui/src/db/migrate/013_add_cobbler_to_vms.rb @@ -0,0 +1,28 @@ +# +# 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. + +class AddCobblerToVms < ActiveRecord::Migration + def self.up + add_column :vms, :cobbler_profile, :string + end + + def self.down + remove_column :vms, :cobbler_profile + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index 34749e0..c9ea2cd 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -21,6 +21,9 @@ include REXML require 'utils' +gem 'cobbler' +require 'cobbler' + def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, macAddr, bridge, diskDevices) doc = Document.new @@ -152,13 +155,25 @@ def create_vm(task) if vm.state != Vm::STATE_PENDING raise "VM not pending" end - setVmState(vm, Vm::STATE_CREATING) - # FIXME: in here, we would do any long running creating tasks (allocating - # disk, etc.) + # create cobbler system profile + begin + if !vm.cobbler_profile or vm.cobbler_profile.empty? + raise "Cobbler profile not specified" + end - setVmState(vm, Vm::STATE_STOPPED) + system = Cobbler::System.new('name' => vm.uuid, + 'profile' => vm.cobbler_profile) + system.interfaces=[Cobbler::NetworkInterface.new( + ["intf",{'mac_address' => vm.vnic_mac_addr}] + )] + system.save + setVmState(vm, Vm::STATE_STOPPED) + rescue Exception => error + setVmState(vm, Vm::STATE_CREATE_FAILED) + raise "Unable to create system: #{error.message}" + end end def shutdown_vm(task) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:36:38 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:38 -0700 Subject: [Ovirt-devel] [PATCH 0/6] OVirt API for hosts and hardware/storage pools Message-ID: <1218757004-29604-1-git-send-email-dlutter@redhat.com> This is a revamp of the API I posted previously. The most important change is that the REST specific controllers have been folded into the existing controllers, so that the WUI and the API share the same business logic, and in particular perform the same permission checks. Once Steve's patches to allow HTTP authentication have been committed, the API can be accessed through the URL http://USER:PASSWORD at SERVER/ovirt. Until then, you either have to hack the config of your OVirt server to allow unauthenticated access and assume that's user 'ovirtadmin', or direct API requests directly at the Mongrel port at SERVER:3000 Patch 6/6 contains a little supporting client code and the example script examples/script.rb that shows what you can do with the API (and how) [1] https://www.redhat.com/archives/ovirt-devel/2008-August/msg00045.html From dlutter at redhat.com Thu Aug 14 23:36:39 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:39 -0700 Subject: [Ovirt-devel] [PATCH 1/6] Select a host in the grid using GET instead of POST In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757004-29604-2-git-send-email-dlutter@redhat.com> The url_for call must set 'id' to nil to keep the controller from appending the ID of the current hardware pool, since we want a URL of the form /hosts/show/ID where ID is dynamically determined in JavaScript. Signed-off-by: David Lutterkort wui/src/app/views/hardware/show_hosts.rhtml | 3 +-- 1 files changed, 1 insertions(+), 2 deletions(-) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:36:40 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:40 -0700 Subject: [Ovirt-devel] [PATCH 2/6] API access to hosts In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757004-29604-3-git-send-email-dlutter@redhat.com> - List hosts and filter the list by state, arch, hostname, uuid and hardware_pool_id - Retrieve an individual host The routing maps this to /ovirt/hosts and /ovirt/hosts/#{id} Signed-off-by: David Lutterkort wui/src/app/controllers/host_controller.rb | 28 ++++++++++++++++++++++++---- wui/src/config/routes.rb | 7 +++++++ 2 files changed, 31 insertions(+), 4 deletions(-) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:36:41 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:41 -0700 Subject: [Ovirt-devel] [PATCH 3/6] API for storage pools In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757004-29604-4-git-send-email-dlutter@redhat.com> - List and filter storage pools by ipaddr, path, target and hardware_pool_id - Show an individual storage pool - Create a new storage pool - Destroy a storage pool Signed-off-by: David Lutterkort wui/src/app/controllers/storage_controller.rb | 64 ++++++++++++++++++++---- wui/src/config/routes.rb | 1 + 2 files changed, 54 insertions(+), 11 deletions(-) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:36:42 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:42 -0700 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757004-29604-5-git-send-email-dlutter@redhat.com> Signed-off-by: David Lutterkort wui/src/app/models/hardware_pool.rb | 12 ++++++++++++ 1 files changed, 12 insertions(+), 0 deletions(-) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:36:43 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:43 -0700 Subject: [Ovirt-devel] [PATCH 5/6] API for Hardware Pools In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757004-29604-6-git-send-email-dlutter@redhat.com> - List and filter HW pools by name - CRUD of individual HW pool - Hosts and storage pools for a HW pool are accessible as nested resources, e.g. /ovirt/hardware_pools/1/hosts?hostname=myhost.example.com Signed-off-by: David Lutterkort wui/src/app/controllers/hardware_controller.rb | 145 ++++++++++++++++++++---- wui/src/config/routes.rb | 4 + 2 files changed, 128 insertions(+), 21 deletions(-) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:36:44 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:36:44 -0700 Subject: [Ovirt-devel] [PATCH 6/6] Sample client code that shows how to use the API In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757004-29604-7-git-send-email-dlutter@redhat.com> Signed-off-by: David Lutterkort wui/client/README | 14 ++++++ wui/client/examples/script.rb | 99 +++++++++++++++++++++++++++++++++++++++++ wui/client/lib/ovirt.rb | 63 ++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 0 deletions(-) -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:38:45 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:38:45 -0700 Subject: [Ovirt-devel] [PATCH 0/6] OVirt API for hosts and hardware/storage pools In-Reply-To: <1218757004-29604-1-git-send-email-dlutter@redhat.com> References: <1218757004-29604-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757125.5092.247.camel@localhost.localdomain> On Thu, 2008-08-14 at 16:36 -0700, David Lutterkort wrote: > This is a revamp of the API I posted previously. Crud .. somehow git-format-patch didn't include the actual patches. Will resend in a minute. David From dlutter at redhat.com Thu Aug 14 23:43:46 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:46 -0700 Subject: [Ovirt-devel] OVirt API for hosts and hardware/storage pools Message-ID: <1218757432-30330-1-git-send-email-dlutter@redhat.com> [ Resent. The previous postings didn't have the actual patches in them ] This is a revamp of the API I posted previously. The most important change is that the REST specific controllers have been folded into the existing controllers, so that the WUI and the API share the same business logic, and in particular perform the same permission checks. Once Steve's patches to allow HTTP authentication have been committed, the API can be accessed through the URL http://USER:PASSWORD at SERVER/ovirt. Until then, you either have to hack the config of your OVirt server to allow unauthenticated access and assume that's user 'ovirtadmin', or direct API requests directly at the Mongrel port at SERVER:3000 Patch 6/6 contains a little supporting client code and the example script examples/script.rb that shows what you can do with the API (and how) [1] https://www.redhat.com/archives/ovirt-devel/2008-August/msg00045.html From dlutter at redhat.com Thu Aug 14 23:43:47 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:47 -0700 Subject: [Ovirt-devel] [PATCH 1/6] Select a host in the grid using GET instead of POST In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757432-30330-2-git-send-email-dlutter@redhat.com> The url_for call must set 'id' to nil to keep the controller from appending the ID of the current hardware pool, since we want a URL of the form /hosts/show/ID where ID is dynamically determined in JavaScript. Signed-off-by: David Lutterkort --- wui/src/app/views/hardware/show_hosts.rhtml | 3 +-- 1 files changed, 1 insertions(+), 2 deletions(-) diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml index f2962cb..38f942d 100644 --- a/wui/src/app/views/hardware/show_hosts.rhtml +++ b/wui/src/app/views/hardware/show_hosts.rhtml @@ -49,8 +49,7 @@ } if (selected_ids.length == 1) { - $('#hosts_selection').load('<%= url_for :controller => "host", :action => "show" %>', - { id: parseInt(selected_ids[0].substring(3))}) + $('#hosts_selection').load('<%= url_for :controller => "host", :action => "show", :id => nil %>/' + parseInt(selected_ids[0].substring(3))) } } -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:43:48 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:48 -0700 Subject: [Ovirt-devel] [PATCH 2/6] API access to hosts In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757432-30330-3-git-send-email-dlutter@redhat.com> - List hosts and filter the list by state, arch, hostname, uuid and hardware_pool_id - Retrieve an individual host The routing maps this to /ovirt/hosts and /ovirt/hosts/#{id} Signed-off-by: David Lutterkort --- wui/src/app/controllers/host_controller.rb | 28 ++++++++++++++++++++++++---- wui/src/config/routes.rb | 7 +++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/wui/src/app/controllers/host_controller.rb b/wui/src/app/controllers/host_controller.rb index 4e4375a..5174c88 100644 --- a/wui/src/app/controllers/host_controller.rb +++ b/wui/src/app/controllers/host_controller.rb @@ -18,19 +18,36 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class HostController < ApplicationController + + EQ_ATTRIBUTES = [ :state, :arch, :hostname, :uuid, + :hardware_pool_id ] + def index list - render :action => 'list' + respond_to do |format| + format.html { render :action => 'list' } + format.xml { render :xml => @hosts.to_xml } + end end before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms] # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) - verify :method => :post, :only => [ :destroy, :create, :update ], + verify :method => [:post, :put], :only => [ :create, :update ], + :redirect_to => { :action => :list } + verify :method => [:post, :delete], :only => :destroy, :redirect_to => { :action => :list } def list - @hosts = Host.find(:all) if @hosts.nil? + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "#{attr} = :#{attr}" + end + end + @hosts = Host.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") end @@ -41,7 +58,10 @@ class HostController < ApplicationController #perm errors for ajax should be done differently redirect_to :controller => 'dashboard', :action => 'list' end - render :layout => 'selection' + respond_to do |format| + format.html { render :layout => 'selection' } + format.xml { render :xml => @host.to_xml } + end end def quick_summary diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb index a93ba4b..241cac6 100644 --- a/wui/src/config/routes.rb +++ b/wui/src/config/routes.rb @@ -43,4 +43,11 @@ ActionController::Routing::Routes.draw do |map| # Install the default route as the lowest priority. map.connect ':controller/:action/:id.:format' map.connect ':controller/:action/:id' + + # We put routes for the REST API _after_ the default routes so that we + # don't disturb existing routes for the WUI + # FIXME: Eventually, we want to rename the controllers in a way that makes + # REST work out of the box, and use these as the default routes + map.resources :hosts, :controller => 'host' + end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:43:49 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:49 -0700 Subject: [Ovirt-devel] [PATCH 3/6] API for storage pools In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757432-30330-4-git-send-email-dlutter@redhat.com> - List and filter storage pools by ipaddr, path, target and hardware_pool_id - Show an individual storage pool - Create a new storage pool - Destroy a storage pool Signed-off-by: David Lutterkort --- wui/src/app/controllers/storage_controller.rb | 64 ++++++++++++++++++++---- wui/src/config/routes.rb | 1 + 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/wui/src/app/controllers/storage_controller.rb b/wui/src/app/controllers/storage_controller.rb index ffa42bd..7e97764 100644 --- a/wui/src/app/controllers/storage_controller.rb +++ b/wui/src/app/controllers/storage_controller.rb @@ -19,17 +19,25 @@ class StorageController < ApplicationController + EQ_ATTRIBUTES = [ :ip_addr, :export_path, :target, + :hardware_pool_id ] + before_filter :pre_pool_admin, :only => [:refresh] before_filter :pre_new2, :only => [:new2] before_filter :pre_json, :only => [:storage_volumes_json] def index list - render :action => 'list' + respond_to do |format| + format.html { render :action => 'list' } + format.xml { render :xml => @storage_pools.to_xml } + end end # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) - verify :method => :post, :only => [ :destroy, :create, :update ], + verify :method => [:post, :put], :only => [ :create, :update ], + :redirect_to => { :action => :list } + verify :method => [:post, :delete], :only => :destroy, :redirect_to => { :action => :list } def list @@ -47,7 +55,14 @@ class StorageController < ApplicationController end else #no permissions here yet -- do we disable raw volume list - @storage_pools = StoragePool.find(:all) + conditions = [] + EQ_ATTRIBUTES.each { |attr| + conditions << "#{attr} = :#{attr}" if params[attr] + } + + @storage_pools = StoragePool.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") end end @@ -56,9 +71,16 @@ class StorageController < ApplicationController set_perms(@storage_pool.hardware_pool) unless @can_view flash[:notice] = 'You do not have permission to view this storage pool: redirecting to top level' - redirect_to :controller => 'dashboard' + respond_to do |format| + format.html { redirect_to :controller => 'dashboard' } + format.xml { head :forbidden } + end + else + respond_to do |format| + format.html { render :layout => 'selection' } + format.xml { render :xml => @storage_pool.to_xml } + end end - render :layout => 'selection' end def storage_volumes_json @@ -113,12 +135,24 @@ class StorageController < ApplicationController @storage_pool.save! insert_refresh_task end - render :json => { :object => "storage_pool", :success => true, - :alert => "Storage Pool was successfully created." } + respond_to do |format| + format.json { render :json => { :object => "storage_pool", + :success => true, + :alert => "Storage Pool was successfully created." } } + format.xml { render :xml => @storage_pool, + :status => :created, + :location => storage_pool_url(@storage_pool) + } + end rescue # FIXME: need to distinguish pool vs. task save errors (but should mostly be pool) - render :json => { :object => "storage_pool", :success => false, - :errors => @storage_pool.errors.localize_error_messages.to_a } + respond_to do |format| + format.json { + render :json => { :object => "storage_pool", :success => false, + :errors => @storage_pool.errors.localize_error_messages.to_a } } + format.xml { render :xml => @storage_pool.errors, + :status => :unprocessable_entity } + end end end @@ -213,7 +247,11 @@ class StorageController < ApplicationController alert="Failed to delete storage pool." success=false end - render :json => { :object => "storage_pool", :success => success, :alert => alert } + respond_to do |format| + format.json { render :json => { :object => "storage_pool", + :success => success, :alert => alert } } + format.xml { head (success ? :ok : :method_not_allowed) } + end end def pre_new @@ -233,7 +271,11 @@ class StorageController < ApplicationController authorize_admin end def pre_create - @storage_pool = StoragePool.factory(params[:storage_type], params[:storage_pool]) + pool = params[:storage_pool] + unless type = params[:storage_type] + type = pool.delete(:storage_type) + end + @storage_pool = StoragePool.factory(type, pool) @perm_obj = @storage_pool.hardware_pool @redir_controller = @storage_pool.hardware_pool.get_controller end diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb index 241cac6..c5e7f90 100644 --- a/wui/src/config/routes.rb +++ b/wui/src/config/routes.rb @@ -49,5 +49,6 @@ ActionController::Routing::Routes.draw do |map| # FIXME: Eventually, we want to rename the controllers in a way that makes # REST work out of the box, and use these as the default routes map.resources :hosts, :controller => 'host' + map.resources :storage_pools, :controller => 'storage' end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:43:50 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:50 -0700 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757432-30330-5-git-send-email-dlutter@redhat.com> Signed-off-by: David Lutterkort --- wui/src/app/models/hardware_pool.rb | 12 ++++++++++++ 1 files changed, 12 insertions(+), 0 deletions(-) diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb index 276779f..249d744 100644 --- a/wui/src/app/models/hardware_pool.rb +++ b/wui/src/app/models/hardware_pool.rb @@ -97,4 +97,16 @@ class HardwarePool < Pool return {:total => total, :labels => labels} end + def self.find_by_path(path) + segs = path.split("/") + unless segs.shift.empty? + raise "Path must be absolute, but is #{path}" + end + if segs.shift == "default" + segs.inject(get_default_pool) do |pool, seg| + pool.sub_hardware_pools.find { |p| p.name == seg } if pool + end + end + end + end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:43:52 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:52 -0700 Subject: [Ovirt-devel] [PATCH 6/6] Sample client code that shows how to use the API In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757432-30330-7-git-send-email-dlutter@redhat.com> Signed-off-by: David Lutterkort --- wui/client/README | 14 ++++++ wui/client/examples/script.rb | 99 +++++++++++++++++++++++++++++++++++++++++ wui/client/lib/ovirt.rb | 63 ++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 0 deletions(-) create mode 100644 wui/client/README create mode 100755 wui/client/examples/script.rb create mode 100644 wui/client/lib/ovirt.rb diff --git a/wui/client/README b/wui/client/README new file mode 100644 index 0000000..4bdce27 --- /dev/null +++ b/wui/client/README @@ -0,0 +1,14 @@ +This is a very simple client library for accessing the OVirt API from Ruby. + +The file examples/script.rb contains a script that shows how this is done +in some detail. + +You must have ActiveResource installed, e.g. with 'yum install +rubygem-activeresource' + +The server is specified with a URL of the form + http://USER:PASSWORD at HOST/ovirt + +This requires that the server is configured to allow HTTP authentication, +since there are no mechanisms in the API to forward krb5 tickets. You can +also try and connect to Mongrel running on HOST:3000 directly. diff --git a/wui/client/examples/script.rb b/wui/client/examples/script.rb new file mode 100755 index 0000000..2103ad7 --- /dev/null +++ b/wui/client/examples/script.rb @@ -0,0 +1,99 @@ +#! /usr/bin/ruby + +# Sample script that shows how to use the OVirt API + +require 'pp' +require 'rubygems' +require 'activeresource' +require 'optparse' + +require 'ovirt' + +def move_random_host(hosts, pool) + host = hosts[rand(hosts.size)] + puts "Move #{host.hostname} to #{pool.name}" + pool.hosts << host + pool.save +end + +def element_path(obj) + "[#{obj.class.element_path(obj.id)}]" +end + +def print_pool(pool) + puts "\n\nPool #{pool.name}: #{pool.hosts.size} hosts, #{pool.storage_pools.size} storage pools #{element_path(pool)} " + puts "=" * 75 + pool.hosts.each do |h| + printf "%-36s %s\n", h.hostname, element_path(h) + end + pool.storage_pools.each do |sp| + type = sp.nfs? ? "NFS" : "iSCSI" + printf "%-5s %-30s %s\n", type, sp.label, element_path(sp) + end + puts "-" * 75 +end + +# Plumbing so we can find the OVirt server +# "http://ovirt.watzmann.net:3000/ovirt/rest" +PROGNAME=File::basename($0) +OVirt::Base.site = ENV["OVIRT_SERVER"] +opts = OptionParser.new("#{PROGNAME} GLOBAL_OPTS") +opts.separator(" Run some things against an OVirt server. The server is specified with") +opts.separator(" the -s option as a URL of the form http://USER:PASSWORD at SERVER/ovirt") +opts.separator("") +opts.separator "Global options:" +opts.on("-s", "--server=URL", "The OVirt server. Since there is no auth\n" + + "#{" "*37}yet, must be the mongrel server port.\n" + + "#{" "*37}Overrides env var OVIRT_SERVER") do |val| + OVirt::Base.site = val +end + +opts.order(ARGV) + +unless OVirt::Base.site + $stderr.puts < defpool.id, + :name => "mypool" } ) +end + +# Move some hosts around +puts +if defpool.hosts.size > 1 + move_random_host(defpool.hosts, mypool) +elsif mypool.hosts.size > 0 + move_random_host(mypool.hosts, defpool) +end + +# Delete all storage pools for mypool and add a new one +mypool.storage_pools.each do |sp| + puts "Delete storage pool #{sp.id}" + sp.destroy +end + +storage_pool = OVirt::StoragePool.create( { :storage_type => "NFS", + :hardware_pool_id => mypool.id, + :ip_addr => "192.168.122.50", + :export_path => "/exports/pool1" } ) +puts "Created storage pool #{storage_pool.id}" + +# For some reason, mypool.reload doesn't work here +mypool = OVirt::HardwarePool.find_by_path("/default/mypool") +print_pool(mypool) diff --git a/wui/client/lib/ovirt.rb b/wui/client/lib/ovirt.rb new file mode 100644 index 0000000..48739f4 --- /dev/null +++ b/wui/client/lib/ovirt.rb @@ -0,0 +1,63 @@ +require 'pp' +require 'rubygems' +require 'activeresource' + +module OVirt + class Base < ActiveResource::Base ; end + + class HardwarePool < Base + def self.find_by_path(path) + find(:first, :params => { :path => path }) + end + + def self.default_pool + find(:first, :params => { :path => "/default" }) + end + end + + class StoragePool < Base + def iscsi? + attributes["type"] == "IscsiStoragePool" + end + + def nfs? + attributes["type"] == "NfsStoragePool" + end + + def label + if iscsi? + "#{ip_addr}:#{port}:#{target}" + elsif nfs? + "#{ip_addr}:#{export_path}" + else + raise "Unknown type #{attributes["type"]}" + end + end + end + + class IscsiStoragePool < StoragePool + def initialize(attributes = {}) + super(attributes.update( "type" => "IscsiStoragePool" )) + end + end + + class NfsStoragePool < StoragePool + def initialize(attributes = {}) + super(attributes.update( "type" => "NfsStoragePool" )) + end + end + + class Host < Base + def self.find_by_uuid(uuid) + find(:first, :params => { :uuid => uuid }) + end + + def self.find_by_hostname(hostname) + find(:first, :params => { :hostname => hostname }) + end + + def hardware_pool + HardwarePool.find(hardware_pool_id) + end + end +end -- 1.5.5.1 From dlutter at redhat.com Thu Aug 14 23:43:51 2008 From: dlutter at redhat.com (David Lutterkort) Date: Thu, 14 Aug 2008 16:43:51 -0700 Subject: [Ovirt-devel] [PATCH 5/6] API for Hardware Pools In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1218757432-30330-6-git-send-email-dlutter@redhat.com> - List and filter HW pools by name - CRUD of individual HW pool - Hosts and storage pools for a HW pool are accessible as nested resources, e.g. /ovirt/hardware_pools/1/hosts?hostname=myhost.example.com Signed-off-by: David Lutterkort --- wui/src/app/controllers/hardware_controller.rb | 145 ++++++++++++++++++++---- wui/src/config/routes.rb | 4 + 2 files changed, 128 insertions(+), 21 deletions(-) diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index 019fdd8..ad12d34 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -20,7 +20,15 @@ class HardwareController < ApplicationController - verify :method => :post, :only => [ :destroy, :create, :update ], + XML_OPTS = { + :include => [ :storage_pools, :hosts, :quota ] + } + + EQ_ATTRIBUTES = [ :name, :parent_id ] + + verify :method => [:post, :put], :only => [ :create, :update ], + :redirect_to => { :action => :list } + verify :method => [:post, :delete], :only => :destroy, :redirect_to => { :action => :list } before_filter :pre_json, :only => [:vm_pools_json, :users_json, @@ -29,19 +37,47 @@ class HardwareController < ApplicationController :add_storage, :move_storage, :create_storage, :delete_storage] + def index + if params[:path] + @pools = [] + pool = HardwarePool.find_by_path(params[:path]) + @pools << pool if pool + else + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "#{attr} = :#{attr}" + end + end + + @pools = HardwarePool.find(:all, + :conditions => [conditions.join(" and "), params], + :order => "id") + end + + respond_to do |format| + format.xml { render :xml => @pools.to_xml(XML_OPTS) } + end + end def show set_perms(@perm_obj) unless @can_view flash[:notice] = 'You do not have permission to view this hardware pool: redirecting to top level' - redirect_to :controller => "dashboard" + respond_to do |format| + format.html { redirect_to :controller => "dashboard" } + format.xml { head :forbidden } + end return end - if params[:ajax] - render :layout => 'tabs-and-content' - end - if params[:nolayout] - render :layout => false + respond_to do |format| + format.html { + render :layout => 'tabs-and-content' if params[:ajax] + render :layout => false if params[:nolayout] + } + format.xml { + render :xml => @pool.to_xml(XML_OPTS) + } end end @@ -199,15 +235,29 @@ class HardwareController < ApplicationController resource_ids = resource_ids_str.split(",").collect {|x| x.to_i} if resource_ids_str begin @pool.create_with_resources(@parent, resource_type, resource_ids) - reply = { :object => "pool", :success => true, - :alert => "Hardware Pool was successfully created." } - reply[:resource_type] = resource_type if resource_type - render :json => reply + respond_to do |format| + format.html { + reply = { :object => "pool", :success => true, + :alert => "Hardware Pool was successfully created." } + reply[:resource_type] = resource_type if resource_type + render :json => reply + } + format.xml { + render :xml => @pool.to_xml(XML_OPTS), + :status => :created, + :location => hardware_pool_url(@pool) + } + end rescue - render :json => { :object => "pool", :success => false, - :errors => @pool.errors.localize_error_messages.to_a } + respond_to do |format| + format.json { + render :json => { :object => "pool", :success => false, + :errors => @pool.errors.localize_error_messages.to_a } + } + format.xml { render :xml => @pool.errors, + :status => :unprocessable_entity } + end end - end def edit @@ -215,13 +265,51 @@ class HardwareController < ApplicationController end def update + if params[:hardware_pool] + # FIXME: For the REST API, we allow moving hosts/storage through + # update. It makes that operation convenient for clients, though makes + # the implementation here somewhat ugly. + [:hosts, :storage_pools].each do |k| + objs = params[:hardware_pool].delete(k) + ids = objs.reject{ |obj| obj[:hardware_pool_id] == @pool.id}. + collect{ |obj| obj[:id] } + if ids.size > 0 + # FIXME: use self.move_hosts/self.move_storage + if k == :hosts + @pool.move_hosts(ids, @pool.id) + else + @pool.move_storage(ids, @pool.id) + end + end + end + # FIXME: HTML views should use :hardware_pool + params[:pool] = params.delete(:hardware_pool) + end + begin @pool.update_attributes!(params[:pool]) - render :json => { :object => "pool", :success => true, - :alert => "Hardware Pool was successfully modified." } + respond_to do |format| + format.json { + render :json => { :object => "pool", :success => true, + :alert => "Hardware Pool was successfully modified." } + } + format.xml { + render :xml => @pool.to_xml(XML_OPTS), + :status => :created, + :location => hardware_pool_url(@pool) + } + end rescue - render :json => { :object => "pool", :success => false, - :errors => @pool.errors.localize_error_messages.to_a} + respond_to do |format| + format.json { + render :json => { :object => "pool", :success => false, + :errors => @pool.errors.localize_error_messages.to_a} + } + format.xml { + render :xml => @pool.errors, + :status => :unprocessable_entity + } + end end end @@ -313,19 +401,27 @@ class HardwareController < ApplicationController if not(parent) alert="You can't delete the top level Hardware pool." success=false + status=:method_not_allowed elsif not(@pool.children.empty?) alert = "You can't delete a Pool without first deleting its children." success=false + status=:conflict else if @pool.move_contents_and_destroy alert="Hardware Pool was successfully deleted." success=true + status=:ok else alert="Failed to delete hardware pool." success=false + status=:internal_server_error end end - render :json => { :object => "pool", :success => success, :alert => alert } + respond_to do |format| + format.json { render :json => { :object => "pool", :success => success, + :alert => alert } } + format.xml { head status } + end end private @@ -337,8 +433,15 @@ class HardwareController < ApplicationController @current_pool_id=@parent.id end def pre_create - @pool = HardwarePool.new(params[:pool]) - @parent = Pool.find(params[:parent_id]) + # FIXME: REST and browsers send params differently. Should be fixed + # in the views + if params[:pool] + @pool = HardwarePool.new(params[:pool]) + @parent = Pool.find(params[:parent_id]) + else + @pool = HardwarePool.new(params[:hardware_pool]) + @parent = Pool.find(params[:hardware_pool][:parent_id]) + end @perm_obj = @parent @current_pool_id=@parent.id end diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb index c5e7f90..8fd64a9 100644 --- a/wui/src/config/routes.rb +++ b/wui/src/config/routes.rb @@ -50,5 +50,9 @@ ActionController::Routing::Routes.draw do |map| # REST work out of the box, and use these as the default routes map.resources :hosts, :controller => 'host' map.resources :storage_pools, :controller => 'storage' + map.resources :hardware_pools, :controller => 'hardware' do |hardware_pools| + hardware_pools.resources :hosts, :controller => 'host' + hardware_pools.resources :storage_pools, :controller => 'storage' + end end -- 1.5.5.1 From apevec at redhat.com Thu Aug 14 23:49:04 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 15 Aug 2008 01:49:04 +0200 Subject: [Ovirt-devel] [PATCH] Add serial console support In-Reply-To: <1218667445-11150-1-git-send-email-imain@redhat.com> References: <1218667445-11150-1-git-send-email-imain@redhat.com> Message-ID: <48A4C470.8020509@redhat.com> Ian Main wrote: > This patch adds serial console support to the ovirt node. ACK NB: you need livecd-tools-017.1-2ovirt1.fc9 from ovirt.org repo http://ovirt.org/repos/ovirt/9/ From apevec at redhat.com Thu Aug 14 23:56:14 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 15 Aug 2008 01:56:14 +0200 Subject: [Ovirt-devel] [PATCH] Add serial consoles to guests In-Reply-To: <1218667543-11264-1-git-send-email-imain@redhat.com> References: <1218667543-11264-1-git-send-email-imain@redhat.com> Message-ID: <48A4C61E.1010907@redhat.com> Ian Main wrote: > This patch adds a serial console to the guest VMs as created by taskomatic. ACK From mwagner at redhat.com Fri Aug 15 00:33:36 2008 From: mwagner at redhat.com (mark wagner) Date: Thu, 14 Aug 2008 20:33:36 -0400 Subject: [Ovirt-devel] Managed node NIC management In-Reply-To: <8E039F7B-66A7-4E65-96E7-933CD39C9A49@redhat.com> References: <8E039F7B-66A7-4E65-96E7-933CD39C9A49@redhat.com> Message-ID: <48A4CEE0.6030204@redhat.com> Darryl Not really involved in this discussion previously so may be a naive question(s) but can you key off of the MAC ID of each of the interfaces ? I realize that this may change if its not integrated on the mobo (and yes even the mobo macid can be changed), but it would seem to handle the overwhelming majority of the cases. If the mac needs to change, treat it as a new card. btw - is there an existing method for dealing with new interfaces ? Also (potentially showing more ignorance) I will assume that there may be multiple paths between the managed node and the ovirt server. Are we setting arp_filter=1 in /etc/sysctl.conf ? (we should) Also, is the PXE service tied to a particular interface or just the server machine, For instance if the ovirt server and the manage node each have two nics, one on each subnet, do we need to make sure that the PXE request only goes on a certain subnet or is that handled for us? -mark Darryl Pierce wrote: > During a discussion today, we discussed managing NIC configuration for > managed nodes from within the server suite. > > My understanding of how that would is: > > 1. managed node runs the awake script, notifying the server suite that > it is awake. > 2. managed node identifies all of its hardware > 3. server suite generates a network configuration file based on what is > in the nics table for this managed node > 4. managed node pulls down this configuration > 5. managed node configures its network interfaces based on the configuration > > The main point brought up is that there is no guarantee that eth0 on one > boot will be eth0 on the next boot. If a card should get moved, or a > kernel upgrade should change the process order, then the interface name > can change. As such, we have a challenge of deciding how to response > when the interface name for a card changes between boots. > > The responses that come to mind for me are: > > 1. server suite ignores the interface name in the db and uses what is > sent up during identification; it sends an alert to the admin that the > network has changed > 2. server suite returns an error condition to the managed node, alerts > the admin and shuts the managed node down > 3. server suite returns no configuration file and alerts the admin that > the managed node's network was not configured > > #2 is pretty heavy handed. #1 seems a reasonable response on first > blush, since it's assumed that the only thing different is the > interface's name. > > So, I'd like some input from everybody on what we ought to do. One > suggestion that came up during our discussion was passing in some > network configuration parameters with the boot parameters. I'll be > honest, I'm not sure how we would go about that. But, if someone can > help explain it to me, I'd be glad to explore that path. > > Thoughts? > > -- > Darryl L. Pierce, Sr. Software Engineer > Red Hat, Inc. - http://www.redhat.com/ > oVirt - Virtual Machine Management - http://www.ovirt.org/ > "What do you care what other people think, Mr. Feynman?" > > > > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From imain at redhat.com Fri Aug 15 02:11:30 2008 From: imain at redhat.com (Ian Main) Date: Thu, 14 Aug 2008 19:11:30 -0700 Subject: [Ovirt-devel] [PATCH] Add serial console support In-Reply-To: <48A4C470.8020509@redhat.com> References: <1218667445-11150-1-git-send-email-imain@redhat.com> <48A4C470.8020509@redhat.com> Message-ID: <20080814191130.56b93d9d@tp.mains.net> On Fri, 15 Aug 2008 01:49:04 +0200 Alan Pevec wrote: > Ian Main wrote: > > This patch adds serial console support to the ovirt node. > ACK Thanks apevec :) pushed. Ian From imain at redhat.com Fri Aug 15 02:11:40 2008 From: imain at redhat.com (Ian Main) Date: Thu, 14 Aug 2008 19:11:40 -0700 Subject: [Ovirt-devel] [PATCH] Add serial consoles to guests In-Reply-To: <48A4C61E.1010907@redhat.com> References: <1218667543-11264-1-git-send-email-imain@redhat.com> <48A4C61E.1010907@redhat.com> Message-ID: <20080814191140.6d19d026@tp.mains.net> On Fri, 15 Aug 2008 01:56:14 +0200 Alan Pevec wrote: > Ian Main wrote: > > This patch adds a serial console to the guest VMs as created by taskomatic. > > ACK Pushed From mwagner at redhat.com Fri Aug 15 02:58:52 2008 From: mwagner at redhat.com (mark wagner) Date: Thu, 14 Aug 2008 22:58:52 -0400 Subject: [Ovirt-devel] [PATCH] oVirt / RRD Test Data In-Reply-To: <489BA583.5010909@redhat.com> References: <4898DD04.8070705@redhat.com> <489BA583.5010909@redhat.com> Message-ID: <48A4F0EC.1080102@redhat.com> Mohammed Morsi wrote: > Mohammed Morsi wrote: >> Attached is my now functional ruby script to generate test / demo rrd >> data and the oVirt patch required to get it working (a few small code >> tweaks / fixes and a few fixture modifications). >> > Updated the script to circumvent the max string command problem. Now > after intervals_per_command intervals, the scripts runs a rrdupdate > command and resets the data list. The script now seems to work, even > with an interval of 10 seconds, which is what collectd would normally > generate. > > -Mo I'm still debugging some issues with this. Mo, I'll catch up with you in the morning and hopefully we can get this wrapped up. -mark From slinabery at redhat.com Fri Aug 15 03:08:09 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 14 Aug 2008 22:08:09 -0500 Subject: [Ovirt-devel] [PATCH] Add username/password authentication for browsing from non-kerberized hosts In-Reply-To: <1218731681.3789.15.camel@localhost.localdomain> References: <20080814074512.GA1778@redhat.com> <1218731681.3789.15.camel@localhost.localdomain> Message-ID: <20080815030809.GA5675@redhat.com> On Thu, Aug 14, 2008 at 12:34:41PM -0400, Jason Guiditta wrote: > Overall, ACK -works for me. Couple notes/tweaks below. Hi all, Here's a revised patch which addresses most if not all of the concerns with the first version. I await your review! Thanks, Steve -------------- next part -------------- >From fd1e3e9793c110fe6a4be77def0ac8c8ae7fbff0 Mon Sep 17 00:00:00 2001 From: Steve Linabery Date: Thu, 14 Aug 2008 20:16:12 -0500 Subject: [PATCH] Add basic auth for browsing from non-kerberized hosts. This will require rake db:migrate or rebuild of appliance due to new session table in db. --- wui-appliance/wui-devel.ks | 10 +++++++ wui/conf/ovirt-wui.conf | 6 ++-- wui/src/app/controllers/application.rb | 15 ++++------ wui/src/app/controllers/login_controller.rb | 37 +++++++++++++++++++++++++++ wui/src/config/environment.rb | 2 +- wui/src/db/migrate/013_create_sessions.rb | 35 +++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 wui/src/app/controllers/login_controller.rb create mode 100644 wui/src/db/migrate/013_create_sessions.rb diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 3793026..5729b60 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -152,6 +152,16 @@ start() { ipa-server-install -r PRIV.OVIRT.ORG -p @password@ -P @password@ -a @password@ \ --hostname management.priv.ovirt.org -u dirsrv -U + # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459061 + # note: this has to happen after ipa-server-install or the templating + # feature in ipa-server-install chokes on the characters in the regexp + # we add here. + sed -i -e 's###' \ + /etc/httpd/conf.d/ipa.conf + sed -i -e 's###' /etc/httpd/conf.d/ipa.conf + # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459209 + sed -i -e 's/^/#/' /etc/httpd/conf.d/ipa-rewrite.conf + service httpd restart # now create the ovirtadmin user echo @password@|kinit admin # change max username length policy diff --git a/wui/conf/ovirt-wui.conf b/wui/conf/ovirt-wui.conf index 280d541..56ad1f8 100644 --- a/wui/conf/ovirt-wui.conf +++ b/wui/conf/ovirt-wui.conf @@ -2,11 +2,11 @@ NameVirtualHost *:80 ProxyRequests Off - + AuthType Kerberos AuthName "Kerberos Login" KrbMethodNegotiate on - KrbMethodK5Passwd off + KrbMethodK5Passwd on KrbServiceName HTTP Krb5KeyTab /etc/httpd/conf/ipa.keytab KrbSaveCredentials on @@ -26,7 +26,7 @@ ProxyRequests Off RequestHeader set X-Forwarded-Keytab %{KRB5CCNAME}e # RequestHeader unset Authorization - + Alias /ovirt/stylesheets "/usr/share/ovirt-wui/public/stylesheets" Alias /ovirt/images "/usr/share/ovirt-wui/public/images" diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index eacf6f3..f779131 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -32,19 +32,16 @@ class ApplicationController < ActionController::Base before_filter :pre_show, :only => [:show, :show_vms, :show_users, :show_hosts, :show_storage] before_filter :authorize_admin, :only => [:new, :create, :edit, :update, :destroy] + before_filter :is_logged_in + + def is_logged_in + redirect_to (:controller => "login", :action => "login") unless session[:user] != nil + end def get_login_user - if ENV["RAILS_ENV"] != 'test' - user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) - else - 'ovirtadmin' - end + (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" end - def user_from_principal(principal) - principal.split('@')[0] - end - def set_perms(hwpool) @user = get_login_user @can_view = hwpool.can_view(@user) diff --git a/wui/src/app/controllers/login_controller.rb b/wui/src/app/controllers/login_controller.rb new file mode 100644 index 0000000..65d18e7 --- /dev/null +++ b/wui/src/app/controllers/login_controller.rb @@ -0,0 +1,37 @@ +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Steve Linabery +# +# 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 LoginController < ActionController::Base + + before_filter :is_logged_in, :except => :login + def login + session[:user] = (ENV["RAILS_ENV"] == "production") ? + user_from_principal(request.env["HTTP_X_FORWARDED_USER"]) : + "ovirtadmin" + redirect_to :controller => "dashboard" + end + + def user_from_principal(principal) + principal.split('@')[0] + end + +end diff --git a/wui/src/config/environment.rb b/wui/src/config/environment.rb index 379dcf4..d14899a 100644 --- a/wui/src/config/environment.rb +++ b/wui/src/config/environment.rb @@ -44,7 +44,7 @@ Rails::Initializer.run do |config| # Use the database for sessions instead of the file system # (create the session table with 'rake db:sessions:create') - # config.action_controller.session_store = :active_record_store + config.action_controller.session_store = :active_record_store config.action_controller.session = { :session_key => "_ovirt_session_id", :secret => "a covert ovirt phrase or some such" diff --git a/wui/src/db/migrate/013_create_sessions.rb b/wui/src/db/migrate/013_create_sessions.rb new file mode 100644 index 0000000..9eca543 --- /dev/null +++ b/wui/src/db/migrate/013_create_sessions.rb @@ -0,0 +1,35 @@ +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Steve Linabery +# +# 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 CreateSessions < ActiveRecord::Migration + def self.up + create_table :sessions do |t| + t.string :session_id, :null => false + t.text :data + t.timestamps + end + + add_index :sessions, :session_id + add_index :sessions, :updated_at + end + + def self.down + drop_table :sessions + end +end -- 1.5.5.2 From imain at redhat.com Fri Aug 15 05:06:16 2008 From: imain at redhat.com (Ian Main) Date: Thu, 14 Aug 2008 22:06:16 -0700 Subject: [Ovirt-devel] [PATCH]: Tested, working implementation of migration In-Reply-To: <1218530502-11852-1-git-send-email-clalance@redhat.com> References: <1218530502-11852-1-git-send-email-clalance@redhat.com> Message-ID: <20080814220616.22d3fac8@tp.mains.net> On Tue, 12 Aug 2008 10:41:42 +0200 Chris Lalancette wrote: > An actually working, tested cut of the migration code for taskomatic. It > supports 3 modes: I tried to get this to work but I'm having a number of issues. I fixed one bug with migrating to an unselected host, but the migrate itself is still failing. It's getting late so I'm going to bed :) Here's the main error I was getting: libvir: QEMU error : operation failed: migrate failed: migrate "tcp://node13 Task action processing failed: Libvirt::Error: Call to function virDomainMigrate failed ./task_vm.rb:487:in `migrate' I tried to get libvirt debug output but never saw anything interesting. I probably just needed to redo it and look harder. There are numerous other bugs around migration mostly in the UI from what I can see. Selecting hosts to migrate to doesn't work sometimes, sometimes I got errors that migrage_vm wasn't a valid action etc. I updated the patch to fix one buglet with migration and errors and got rid of whitespace. I'll post it shortly. I'll keep poking at it in the morning. Next step I think is to try a straight migration just using libvirt and make sure that works before testing the taskomatic changes. Ian From imain at redhat.com Fri Aug 15 05:08:20 2008 From: imain at redhat.com (Ian Main) Date: Thu, 14 Aug 2008 22:08:20 -0700 Subject: [Ovirt-devel] [PATCH] Tested, working implementation of migration Message-ID: <1218776900-7158-1-git-send-email-imain@redhat.com> From: Chris Lalancette I updated this patch to fix a few bugs that I found and get rid of whitespace so I'm reposting - Ian. An actually working, tested cut of the migration code for taskomatic. It supports 3 modes: a) ClearHost - this clears all of the VMs off of a particular host, the idea being that this host will probably be taken down for maintenance. b) Migrate VM to a particular host - this migrates a particular VM from where it currently resides to a destination specified by the admin. The idea here is that the admin might want to override the automatic placement decision of taskomatic c) Migrate VM anywhere else - this migrates a particular VM from where it currently resides to anywhere else. The idea here is for a step-by-step clearing of a host (similar to ClearHost), or to reduce load on a particular host by moving a VM somewhere else. Signed-off-by: Chris Lalancette --- wui/src/task-omatic/task_host.rb | 33 +++++++ wui/src/task-omatic/task_vm.rb | 190 ++++++++++++++++++------------------- wui/src/task-omatic/taskomatic.rb | 13 ++- wui/src/task-omatic/utils.rb | 117 ++++++++++++++++++++++- 4 files changed, 246 insertions(+), 107 deletions(-) create mode 100644 wui/src/task-omatic/task_host.rb diff --git a/wui/src/task-omatic/task_host.rb b/wui/src/task-omatic/task_host.rb new file mode 100644 index 0000000..5f42da3 --- /dev/null +++ b/wui/src/task-omatic/task_host.rb @@ -0,0 +1,33 @@ +# 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. + +require 'utils' + +# FIXME: a little ugly to be including all of task_vm here, but +# utils really isn't the right place for the migrate() method +require 'task_vm' + +def clear_vms_host(task) + puts "clear_vms_host" + + src_host = findHost(task.host_id) + + src_host.vms.each do |vm| + migrate(vm) + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index d7f0869..ed4d366 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -128,17 +128,6 @@ def findVM(task, fail_on_nil_host_id = true) return vm end -def findHost(task, host_id) - host = Host.find(:first, :conditions => [ "id = ?", host_id]) - - if host == nil - # Hm, we didn't find the host_id. Seems odd. Return a failure - raise "Could not find host_id " + host_id - end - - return host -end - def setVmShutdown(vm) vm.host_id = nil vm.memory_used = nil @@ -189,7 +178,7 @@ def shutdown_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -200,10 +189,21 @@ def shutdown_vm(task) # of problems. Needs more thought #dom.shutdown dom.destroy - dom.undefine + + begin + dom.undefine + rescue + # undefine can fail, for instance, if we live migrated from A -> B, and + # then we are shutting down the VM on B (because it only has "transient" + # XML). Therefore, just ignore undefine errors so we do the rest + # FIXME: we really should have a marker in the database somehow so that + # we can tell if this domain was migrated; that way, we can tell the + # difference between a real undefine failure and one because of migration + end + + teardown_storage_pools(conn) + conn.close - # FIXME: hm. We probably want to undefine the storage pool that this host - # was using if and only if it's not in use by another VM. rescue => ex setVmState(vm, vm_orig_state) raise ex @@ -229,7 +229,6 @@ def start_vm(task) end # FIXME: Validate that the VM is still within quota - #vm.validate vm_orig_state = vm.state setVmState(vm, Vm::STATE_STARTING) @@ -246,93 +245,17 @@ def start_vm(task) # OK, now that we found the VM, go looking in the hardware_pool # hosts to see if there is a host that will fit these constraints - host = nil - - vm.vm_resource_pool.get_hardware_pool.hosts.each do |host| - if host.num_cpus >= vm.num_vcpus_allocated \ - and host.memory >= vm.memory_allocated \ - and not host.is_disabled - host = curr - break - end - end - - if host == nil - # we couldn't find a host that matches this description; report ERROR - raise "No host matching VM parameters could be found" - end + host = findHostSLA(vm) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") - # here, build up a list of already defined pools. We'll use it - # later to see if we need to define new pools for the storage or just - # keep using existing ones - - defined_pools = [] - all_storage_pools(conn).each do |remote_pool_name| - tmppool = conn.lookup_storage_pool_by_name(remote_pool_name) - defined_pools << tmppool - end - - storagedevs = [] - vm.storage_volumes.each do |volume| - # here, we need to iterate through each volume and possibly attach it - # to the host we are going to be using - storage_pool = volume.storage_pool - - if storage_pool == nil - # Hum. Specified by the VM description, but not in the storage pool? - # continue on and hope for the best - next - end - - if storage_pool[:type] == "IscsiStoragePool" - thisstorage = Iscsi.new(storage_pool.ip_addr, storage_pool[:target]) - elsif storage_pool[:type] == "NfsStoragePool" - thisstorage = NFS.new(storage_pool.ip_addr, storage_pool.export_path) - else - # Hm, a storage type we don't understand; skip it - next - end - - thepool = nil - defined_pools.each do |pool| - doc = Document.new(pool.xml_desc(0)) - root = doc.root - - if thisstorage.xmlequal?(doc.root) - thepool = pool - break - end - end - - if thepool == nil - thepool = conn.define_storage_pool_xml(thisstorage.getxml, 0) - thepool.build(0) - thepool.create(0) - elsif thepool.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 - thepool.create(0) - end - - storagedevs << thepool.lookup_volume_by_name(volume.read_attribute(thisstorage.db_column)).path - end - - conn.close - - if storagedevs.length > 4 - raise "Too many storage volumes; maximum is 4" - end - - # OK, we found a host that will work; now let's build up the XML + storagedevs = connect_storage_pools(conn, vm) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, vm.memory_used, vm.num_vcpus_allocated, vm.boot_device, vm.vnic_mac_addr, "ovirtbr0", storagedevs) - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.define_domain_xml(xml.to_s) dom.create @@ -373,7 +296,7 @@ def save_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -416,7 +339,7 @@ def restore_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) # FIXME: we should probably go out to the host and check what it thinks # the state is @@ -458,7 +381,7 @@ def suspend_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -498,7 +421,7 @@ def resume_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -534,3 +457,74 @@ def update_state_vm(task) puts "Updated state to " + task.args end end + +def migrate(vm, dest = nil) + if vm.state == Vm::STATE_STOPPED + raise "Cannot migrate stopped domain" + elsif vm.state == Vm::STATE_SUSPENDED + raise "Cannot migrate suspended domain" + elsif vm.state == Vm::STATE_SAVED + raise "Cannot migrate saved domain" + end + + vm_orig_state = vm.state + setVmState(vm, Vm::STATE_MIGRATING) + + begin + src_host = findHost(vm.host_id) + if not dest.empty? + if dest.to_i == vm.host_id + raise "Cannot migrate from host " + src_host.hostname + " to itself!" + end + dst_host = findHost(dest.to_i) + else + dst_host = findHostSLA(vm) + end + + src_conn = Libvirt::open("qemu+tcp://" + src_host.hostname + "/system") + dst_conn = Libvirt::open("qemu+tcp://" + dst_host.hostname + "/system") + + connect_storage_pools(dst_conn, vm) + + dom = src_conn.lookup_domain_by_uuid(vm.uuid) + dom.migrate(dst_conn, Libvirt::Domain::MIGRATE_LIVE) + + # if we didn't raise an exception, then the migration was successful. We + # still have a pointer to the now-shutdown domain on the source side, so + # undefine it + begin + dom.undefine + rescue + # undefine can fail, for instance, if we live migrated from A -> B, and + # then we are shutting down the VM on B (because it only has "transient" + # XML). Therefore, just ignore undefine errors so we do the rest + # FIXME: we really should have a marker in the database somehow so that + # we can tell if this domain was migrated; that way, we can tell the + # difference between a real undefine failure and one because of migration + end + + teardown_storage_pools(src_conn) + dst_conn.close + src_conn.close + rescue => ex + # FIXME: ug. We may have open connections that we need to close; not + # sure how to handle that + setVmState(vm, vm_orig_state) + raise ex + end + + setVmState(vm, Vm::STATE_RUNNING) + vm.host_id = dst_host.id + vm.save +end + +def migrate_vm(task) + puts "migrate_vm" + + # here, we are given an id for a VM to migrate; we have to lookup which + # physical host it is running on + + vm = findVM(task) + + migrate(vm, task.args) +end diff --git a/wui/src/task-omatic/taskomatic.rb b/wui/src/task-omatic/taskomatic.rb index bb70247..7d6e950 100755 --- a/wui/src/task-omatic/taskomatic.rb +++ b/wui/src/task-omatic/taskomatic.rb @@ -63,9 +63,9 @@ end require 'task_vm' require 'task_storage' +require 'task_host' loop do - first = true tasks = Array.new begin tasks = Task.find(:all, :conditions => [ "state = ?", Task::STATE_QUEUED ]) @@ -86,11 +86,10 @@ loop do end end tasks.each do |task| - if first - # make sure we get our credentials up-front - get_credentials - first = false - end + # make sure we get our credentials up-front + get_credentials + + task.time_started = Time.now state = Task::STATE_FINISHED begin @@ -103,7 +102,9 @@ loop do when VmTask::ACTION_SAVE_VM then save_vm(task) when VmTask::ACTION_RESTORE_VM then restore_vm(task) when VmTask::ACTION_UPDATE_STATE_VM then update_state_vm(task) + when VmTask::ACTION_MIGRATE_VM then migrate_vm(task) when StorageTask::ACTION_REFRESH_POOL then refresh_pool(task) + when HostTask::ACTION_CLEAR_VMS then clear_vms_host(task) else puts "unknown task " + task.action state = Task::STATE_FAILED diff --git a/wui/src/task-omatic/utils.rb b/wui/src/task-omatic/utils.rb index e6401dc..9e60122 100644 --- a/wui/src/task-omatic/utils.rb +++ b/wui/src/task-omatic/utils.rb @@ -1,7 +1,39 @@ require 'rexml/document' include REXML -require 'models/task' +def findHostSLA(vm) + host = nil + + vm.vm_resource_pool.get_hardware_pool.hosts.each do |curr| + # FIXME: we probably need to add in some notion of "load" into this check + if curr.num_cpus >= vm.num_vcpus_allocated \ + and curr.memory >= vm.memory_allocated \ + and not curr.is_disabled.nil? and curr.is_disabled == 0 \ + and curr.state == Host::STATE_AVAILABLE \ + and (vm.host_id.nil? or (not vm.host_id.nil? and vm.host_id != curr.id)) + host = curr + break + end + end + + if host == nil + # we couldn't find a host that matches this criteria + raise "No host matching VM parameters could be found" + end + + return host +end + +def findHost(host_id) + host = Host.find(:first, :conditions => [ "id = ?", host_id]) + + if host == nil + # Hm, we didn't find the host_id. Seems odd. Return a failure + raise "Could not find host_id " + host_id.to_s + end + + return host +end def String.random_alphanumeric(size=16) s = "" @@ -10,12 +42,91 @@ def String.random_alphanumeric(size=16) end def all_storage_pools(conn) - all_pools = [] - all_pools.concat(conn.list_defined_storage_pools) + all_pools = conn.list_defined_storage_pools all_pools.concat(conn.list_storage_pools) return all_pools end +def teardown_storage_pools(conn) + # FIXME: this needs to get a *lot* smarter. In particular, we want to make + # sure we can tear down unused pools even when there are other guests running + if conn.list_domains.empty? + # OK, there are no running guests on this host anymore. We can teardown + # any storage pools that are there without fear + all_storage_pools(conn).each do |remote_pool_name| + begin + pool = conn.lookup_storage_pool_by_name(remote_pool_name) + pool.destroy + pool.undefine + rescue + # do nothing if any of this failed; the worst that happens is that + # we leave a pool configured + puts "Could not teardown pool " + remote_pool_name + "; skipping" + end + end + end +end + +def connect_storage_pools(conn, vm) + # here, build up a list of already defined pools. We'll use it + # later to see if we need to define new pools for the storage or just + # keep using existing ones + + defined_pools = [] + all_storage_pools(conn).each do |remote_pool_name| + defined_pools << conn.lookup_storage_pool_by_name(remote_pool_name) + end + + storagedevs = [] + vm.storage_volumes.each do |volume| + # here, we need to iterate through each volume and possibly attach it + # to the host we are going to be using + storage_pool = volume.storage_pool + + if storage_pool == nil + # Hum. Specified by the VM description, but not in the storage pool? + # continue on and hope for the best + # FIXME: probably want a print to the logs here + next + end + + if storage_pool[:type] == "IscsiStoragePool" + thisstorage = Iscsi.new(storage_pool.ip_addr, storage_pool[:target]) + elsif storage_pool[:type] == "NfsStoragePool" + thisstorage = NFS.new(storage_pool.ip_addr, storage_pool.export_path) + else + # Hm, a storage type we don't understand; skip it + puts "Storage type " + storage_pool[:type] + " is not understood; skipping" + next + end + + thepool = nil + defined_pools.each do |pool| + doc = Document.new(pool.xml_desc) + root = doc.root + + if thisstorage.xmlequal?(doc.root) + thepool = pool + break + end + end + + if thepool == nil + thepool = conn.define_storage_pool_xml(thisstorage.getxml) + thepool.build + thepool.create + elsif thepool.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 + thepool.create + end + + storagedevs << thepool.lookup_volume_by_name(volume.read_attribute(thisstorage.db_column)).path + end + + return storagedevs +end + class StorageType attr_reader :db_column -- 1.5.5.1 From slinabery at gmail.com Fri Aug 15 17:04:46 2008 From: slinabery at gmail.com (steve linabery) Date: Fri, 15 Aug 2008 12:04:46 -0500 Subject: [Ovirt-devel] [PATCH] Add basic auth to wui for non-kerberized browser use. Message-ID: <769584de0808151004t29610798na3e80f5e41851e93@mail.gmail.com> Some whitespace cleanup (git pre-commit hook enabled now, thank you all) and fix for is_logged_in in non-production mode. -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: authpatch3.txt URL: From jguiditt at redhat.com Fri Aug 15 17:20:59 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 15 Aug 2008 13:20:59 -0400 Subject: [Ovirt-devel] [PATCH] Add basic auth to wui for non-kerberized browser use. In-Reply-To: <769584de0808151004t29610798na3e80f5e41851e93@mail.gmail.com> References: <769584de0808151004t29610798na3e80f5e41851e93@mail.gmail.com> Message-ID: <1218820859.3229.0.camel@localhost.localdomain> On Fri, 2008-08-15 at 12:04 -0500, steve linabery wrote: > Some whitespace cleanup (git pre-commit hook enabled now, thank you > all) and fix for is_logged_in in non-production mode. > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ACK, works for me. From mmorsi at syr.edu Fri Aug 15 19:19:05 2008 From: mmorsi at syr.edu (Mohammed Morsi) Date: Fri, 15 Aug 2008 15:19:05 -0400 Subject: [Ovirt-devel] [PATCH] test updates incorporating new auth changes Message-ID: <48A5D6A9.50502@syr.edu> The included patch switches the appliance into the test environment before running the wui tests, and removed the now-unnecessary url based credentials. -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-test-auth.patch Type: text/x-patch Size: 2200 bytes Desc: not available URL: From jguiditt at redhat.com Fri Aug 15 19:37:22 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 15 Aug 2008 15:37:22 -0400 Subject: [Ovirt-devel] [PATCH] Reposition facebox on subtab select. Message-ID: <1218829042-8464-1-git-send-email-jguiditt@redhat.com> For example 'add storage pool'. Box was positioned based on initially displayed content. This positioning needed to be called again whenever the content changes. Signed-off-by: Jason Guiditta --- wui/src/app/views/layouts/redux.rhtml | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/wui/src/app/views/layouts/redux.rhtml b/wui/src/app/views/layouts/redux.rhtml index 924f300..d6cfe24 100644 --- a/wui/src/app/views/layouts/redux.rhtml +++ b/wui/src/app/views/layouts/redux.rhtml @@ -84,6 +84,7 @@ var wrapped_data = $(data).find('div').filter('[id=dialog-content-area]'); var my_parent = $(this).parent(); $('#dialog-content-area').html($(data)); + $('#facebox').css('left', $(window).width() / 2 - ($('#facebox table').width() / 2)); }, error: function(xhr) {$.jGrowl(xhr.status + ' ' + xhr.statusText);} }); -- 1.5.5.1 From lutter at redhat.com Fri Aug 15 19:56:26 2008 From: lutter at redhat.com (David Lutterkort) Date: Fri, 15 Aug 2008 12:56:26 -0700 Subject: [Ovirt-devel] [PATCH] Add basic auth to wui for non-kerberized browser use. In-Reply-To: <769584de0808151004t29610798na3e80f5e41851e93@mail.gmail.com> References: <769584de0808151004t29610798na3e80f5e41851e93@mail.gmail.com> Message-ID: <1218830186.30055.13.camel@localhost.localdomain> On Fri, 2008-08-15 at 12:04 -0500, steve linabery wrote: > Some whitespace cleanup (git pre-commit hook enabled now, thank you > all) and fix for is_logged_in in non-production mode. IMHO, it's a very bad idea to make the default login 'ovirtadmin' unless you're running in production mode ... I am pretty sure we'll find lots of bugs only then, and not when people are running in development mode. David From imain at redhat.com Fri Aug 15 20:19:12 2008 From: imain at redhat.com (Ian Main) Date: Fri, 15 Aug 2008 13:19:12 -0700 Subject: [Ovirt-devel] [PATCH] Tested, working implementation of migration Message-ID: <1218831552-26076-1-git-send-email-imain@redhat.com> From: Chris Lalancette I updated this patch to fix a few bugs that I found and get rid of whitespace so I'm reposting. There is still a bit of an issue with taskomatic and host-status arguing over vm state but I'll fix that in another patch. - Ian An actually working, tested cut of the migration code for taskomatic. It supports 3 modes: a) ClearHost - this clears all of the VMs off of a particular host, the idea being that this host will probably be taken down for maintenance. b) Migrate VM to a particular host - this migrates a particular VM from where it currently resides to a destination specified by the admin. The idea here is that the admin might want to override the automatic placement decision of taskomatic c) Migrate VM anywhere else - this migrates a particular VM from where it currently resides to anywhere else. The idea here is for a step-by-step clearing of a host (similar to ClearHost), or to reduce load on a particular host by moving a VM somewhere else. Signed-off-by: Chris Lalancette --- wui/src/host-status/host-status.rb | 17 ++-- wui/src/task-omatic/task_host.rb | 33 ++++++ wui/src/task-omatic/task_vm.rb | 203 ++++++++++++++++++----------------- wui/src/task-omatic/taskomatic.rb | 13 ++- wui/src/task-omatic/utils.rb | 117 ++++++++++++++++++++- 5 files changed, 268 insertions(+), 115 deletions(-) create mode 100644 wui/src/task-omatic/task_host.rb diff --git a/wui/src/host-status/host-status.rb b/wui/src/host-status/host-status.rb index d28f46f..80efb73 100755 --- a/wui/src/host-status/host-status.rb +++ b/wui/src/host-status/host-status.rb @@ -58,7 +58,9 @@ end # connects to the db in here require 'dutils' -def check_state(vm, dom_info) +def check_state(vm, dom_info, host) + puts 'checking state of vm', vm.description + case dom_info.state when Libvirt::Domain::NOSTATE, Libvirt::Domain::SHUTDOWN, @@ -66,17 +68,17 @@ def check_state(vm, dom_info) if Vm::RUNNING_STATES.include?(vm.state) # OK, the host thinks this VM is off, while the database thinks it # is running; we have to kick taskomatic - kick_taskomatic(Vm::STATE_STOPPED, vm) + kick_taskomatic(Vm::STATE_STOPPED, vm, host.id) end when Libvirt::Domain::RUNNING, Libvirt::Domain::BLOCKED then if not Vm::RUNNING_STATES.include?(vm.state) # OK, the host thinks this VM is running, but it's not marked as running # in the database; kick taskomatic - kick_taskomatic(Vm::STATE_RUNNING, vm) + kick_taskomatic(Vm::STATE_RUNNING, vm, host.id) end when Libvirt::Domain::PAUSED then if vm.state != Vm::STATE_SUSPENDING and vm.state != Vm::STATE_SUSPENDED - kick_taskomatic(Vm::STATE_SUSPENDED, vm) + kick_taskomatic(Vm::STATE_SUSPENDED, vm, host.id) end else puts "Unknown vm state...skipping" @@ -84,13 +86,14 @@ def check_state(vm, dom_info) end -def kick_taskomatic(msg, vm) +def kick_taskomatic(msg, vm, host_id = nil) print "Kicking taskomatic, state is %s\n" % msg task = VmTask.new task.user = "host-status" task.action = VmTask::ACTION_UPDATE_STATE_VM task.state = Task::STATE_QUEUED task.args = msg + task.host_id = host_id task.created_at = Time.now task.time_started = Time.now task.vm_id = vm.id @@ -172,7 +175,7 @@ def check_status(host) next end - check_state(vm, info) + check_state(vm, info, host) end # Now we get a list of all vms that should be on this system and see if @@ -189,7 +192,7 @@ def check_status(host) next end info = dom.info - check_state(vm, info) + check_state(vm, info, host) conn.close diff --git a/wui/src/task-omatic/task_host.rb b/wui/src/task-omatic/task_host.rb new file mode 100644 index 0000000..5f42da3 --- /dev/null +++ b/wui/src/task-omatic/task_host.rb @@ -0,0 +1,33 @@ +# 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. + +require 'utils' + +# FIXME: a little ugly to be including all of task_vm here, but +# utils really isn't the right place for the migrate() method +require 'task_vm' + +def clear_vms_host(task) + puts "clear_vms_host" + + src_host = findHost(task.host_id) + + src_host.vms.each do |vm| + migrate(vm) + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index d7f0869..508a744 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -128,17 +128,6 @@ def findVM(task, fail_on_nil_host_id = true) return vm end -def findHost(task, host_id) - host = Host.find(:first, :conditions => [ "id = ?", host_id]) - - if host == nil - # Hm, we didn't find the host_id. Seems odd. Return a failure - raise "Could not find host_id " + host_id - end - - return host -end - def setVmShutdown(vm) vm.host_id = nil vm.memory_used = nil @@ -189,7 +178,7 @@ def shutdown_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -200,10 +189,21 @@ def shutdown_vm(task) # of problems. Needs more thought #dom.shutdown dom.destroy - dom.undefine + + begin + dom.undefine + rescue + # undefine can fail, for instance, if we live migrated from A -> B, and + # then we are shutting down the VM on B (because it only has "transient" + # XML). Therefore, just ignore undefine errors so we do the rest + # FIXME: we really should have a marker in the database somehow so that + # we can tell if this domain was migrated; that way, we can tell the + # difference between a real undefine failure and one because of migration + end + + teardown_storage_pools(conn) + conn.close - # FIXME: hm. We probably want to undefine the storage pool that this host - # was using if and only if it's not in use by another VM. rescue => ex setVmState(vm, vm_orig_state) raise ex @@ -229,7 +229,6 @@ def start_vm(task) end # FIXME: Validate that the VM is still within quota - #vm.validate vm_orig_state = vm.state setVmState(vm, Vm::STATE_STARTING) @@ -246,93 +245,17 @@ def start_vm(task) # OK, now that we found the VM, go looking in the hardware_pool # hosts to see if there is a host that will fit these constraints - host = nil - - vm.vm_resource_pool.get_hardware_pool.hosts.each do |host| - if host.num_cpus >= vm.num_vcpus_allocated \ - and host.memory >= vm.memory_allocated \ - and not host.is_disabled - host = curr - break - end - end - - if host == nil - # we couldn't find a host that matches this description; report ERROR - raise "No host matching VM parameters could be found" - end + host = findHostSLA(vm) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") - # here, build up a list of already defined pools. We'll use it - # later to see if we need to define new pools for the storage or just - # keep using existing ones - - defined_pools = [] - all_storage_pools(conn).each do |remote_pool_name| - tmppool = conn.lookup_storage_pool_by_name(remote_pool_name) - defined_pools << tmppool - end - - storagedevs = [] - vm.storage_volumes.each do |volume| - # here, we need to iterate through each volume and possibly attach it - # to the host we are going to be using - storage_pool = volume.storage_pool - - if storage_pool == nil - # Hum. Specified by the VM description, but not in the storage pool? - # continue on and hope for the best - next - end - - if storage_pool[:type] == "IscsiStoragePool" - thisstorage = Iscsi.new(storage_pool.ip_addr, storage_pool[:target]) - elsif storage_pool[:type] == "NfsStoragePool" - thisstorage = NFS.new(storage_pool.ip_addr, storage_pool.export_path) - else - # Hm, a storage type we don't understand; skip it - next - end - - thepool = nil - defined_pools.each do |pool| - doc = Document.new(pool.xml_desc(0)) - root = doc.root - - if thisstorage.xmlequal?(doc.root) - thepool = pool - break - end - end - - if thepool == nil - thepool = conn.define_storage_pool_xml(thisstorage.getxml, 0) - thepool.build(0) - thepool.create(0) - elsif thepool.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 - thepool.create(0) - end - - storagedevs << thepool.lookup_volume_by_name(volume.read_attribute(thisstorage.db_column)).path - end - - conn.close - - if storagedevs.length > 4 - raise "Too many storage volumes; maximum is 4" - end - - # OK, we found a host that will work; now let's build up the XML + storagedevs = connect_storage_pools(conn, vm) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, vm.memory_used, vm.num_vcpus_allocated, vm.boot_device, vm.vnic_mac_addr, "ovirtbr0", storagedevs) - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.define_domain_xml(xml.to_s) dom.create @@ -373,7 +296,7 @@ def save_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -416,7 +339,7 @@ def restore_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) # FIXME: we should probably go out to the host and check what it thinks # the state is @@ -458,7 +381,7 @@ def suspend_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -498,7 +421,7 @@ def resume_vm(task) begin # OK, now that we found the VM, go looking in the hosts table - host = findHost(task, vm.host_id) + host = findHost(vm.host_id) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -519,7 +442,18 @@ def update_state_vm(task) # in. So if a vm that we thought was stopped is running, this returns nil # and we don't update any information about it. The tricky part # is that we're still not sure what to do in this case :). - Ian - vm = findVM(task) + # + # Actually for migration it is necessary that it be able to update + # the host and state of the VM once it is migrated. + vm = findVM(task, fail_on_nil_host_id = false) + if vm == nil + raise "VM id " + task.vm_id + "not found" + end + + if vm.host_id == nil + vm.host_id = task.host_id + end + vm_effective_state = Vm::EFFECTIVE_STATE[vm.state] task_effective_state = Vm::EFFECTIVE_STATE[task.args] @@ -534,3 +468,74 @@ def update_state_vm(task) puts "Updated state to " + task.args end end + +def migrate(vm, dest = nil) + if vm.state == Vm::STATE_STOPPED + raise "Cannot migrate stopped domain" + elsif vm.state == Vm::STATE_SUSPENDED + raise "Cannot migrate suspended domain" + elsif vm.state == Vm::STATE_SAVED + raise "Cannot migrate saved domain" + end + + vm_orig_state = vm.state + setVmState(vm, Vm::STATE_MIGRATING) + + begin + src_host = findHost(vm.host_id) + unless dest.nil? or dest.empty? + if dest.to_i == vm.host_id + raise "Cannot migrate from host " + src_host.hostname + " to itself!" + end + dst_host = findHost(dest.to_i) + else + dst_host = findHostSLA(vm) + end + + src_conn = Libvirt::open("qemu+tcp://" + src_host.hostname + "/system") + dst_conn = Libvirt::open("qemu+tcp://" + dst_host.hostname + "/system") + + connect_storage_pools(dst_conn, vm) + + dom = src_conn.lookup_domain_by_uuid(vm.uuid) + dom.migrate(dst_conn, Libvirt::Domain::MIGRATE_LIVE) + + # if we didn't raise an exception, then the migration was successful. We + # still have a pointer to the now-shutdown domain on the source side, so + # undefine it + begin + dom.undefine + rescue + # undefine can fail, for instance, if we live migrated from A -> B, and + # then we are shutting down the VM on B (because it only has "transient" + # XML). Therefore, just ignore undefine errors so we do the rest + # FIXME: we really should have a marker in the database somehow so that + # we can tell if this domain was migrated; that way, we can tell the + # difference between a real undefine failure and one because of migration + end + + teardown_storage_pools(src_conn) + dst_conn.close + src_conn.close + rescue => ex + # FIXME: ug. We may have open connections that we need to close; not + # sure how to handle that + setVmState(vm, vm_orig_state) + raise ex + end + + setVmState(vm, Vm::STATE_RUNNING) + vm.host_id = dst_host.id + vm.save +end + +def migrate_vm(task) + puts "migrate_vm" + + # here, we are given an id for a VM to migrate; we have to lookup which + # physical host it is running on + + vm = findVM(task) + + migrate(vm, task.args) +end diff --git a/wui/src/task-omatic/taskomatic.rb b/wui/src/task-omatic/taskomatic.rb index bb70247..7d6e950 100755 --- a/wui/src/task-omatic/taskomatic.rb +++ b/wui/src/task-omatic/taskomatic.rb @@ -63,9 +63,9 @@ end require 'task_vm' require 'task_storage' +require 'task_host' loop do - first = true tasks = Array.new begin tasks = Task.find(:all, :conditions => [ "state = ?", Task::STATE_QUEUED ]) @@ -86,11 +86,10 @@ loop do end end tasks.each do |task| - if first - # make sure we get our credentials up-front - get_credentials - first = false - end + # make sure we get our credentials up-front + get_credentials + + task.time_started = Time.now state = Task::STATE_FINISHED begin @@ -103,7 +102,9 @@ loop do when VmTask::ACTION_SAVE_VM then save_vm(task) when VmTask::ACTION_RESTORE_VM then restore_vm(task) when VmTask::ACTION_UPDATE_STATE_VM then update_state_vm(task) + when VmTask::ACTION_MIGRATE_VM then migrate_vm(task) when StorageTask::ACTION_REFRESH_POOL then refresh_pool(task) + when HostTask::ACTION_CLEAR_VMS then clear_vms_host(task) else puts "unknown task " + task.action state = Task::STATE_FAILED diff --git a/wui/src/task-omatic/utils.rb b/wui/src/task-omatic/utils.rb index e6401dc..9e60122 100644 --- a/wui/src/task-omatic/utils.rb +++ b/wui/src/task-omatic/utils.rb @@ -1,7 +1,39 @@ require 'rexml/document' include REXML -require 'models/task' +def findHostSLA(vm) + host = nil + + vm.vm_resource_pool.get_hardware_pool.hosts.each do |curr| + # FIXME: we probably need to add in some notion of "load" into this check + if curr.num_cpus >= vm.num_vcpus_allocated \ + and curr.memory >= vm.memory_allocated \ + and not curr.is_disabled.nil? and curr.is_disabled == 0 \ + and curr.state == Host::STATE_AVAILABLE \ + and (vm.host_id.nil? or (not vm.host_id.nil? and vm.host_id != curr.id)) + host = curr + break + end + end + + if host == nil + # we couldn't find a host that matches this criteria + raise "No host matching VM parameters could be found" + end + + return host +end + +def findHost(host_id) + host = Host.find(:first, :conditions => [ "id = ?", host_id]) + + if host == nil + # Hm, we didn't find the host_id. Seems odd. Return a failure + raise "Could not find host_id " + host_id.to_s + end + + return host +end def String.random_alphanumeric(size=16) s = "" @@ -10,12 +42,91 @@ def String.random_alphanumeric(size=16) end def all_storage_pools(conn) - all_pools = [] - all_pools.concat(conn.list_defined_storage_pools) + all_pools = conn.list_defined_storage_pools all_pools.concat(conn.list_storage_pools) return all_pools end +def teardown_storage_pools(conn) + # FIXME: this needs to get a *lot* smarter. In particular, we want to make + # sure we can tear down unused pools even when there are other guests running + if conn.list_domains.empty? + # OK, there are no running guests on this host anymore. We can teardown + # any storage pools that are there without fear + all_storage_pools(conn).each do |remote_pool_name| + begin + pool = conn.lookup_storage_pool_by_name(remote_pool_name) + pool.destroy + pool.undefine + rescue + # do nothing if any of this failed; the worst that happens is that + # we leave a pool configured + puts "Could not teardown pool " + remote_pool_name + "; skipping" + end + end + end +end + +def connect_storage_pools(conn, vm) + # here, build up a list of already defined pools. We'll use it + # later to see if we need to define new pools for the storage or just + # keep using existing ones + + defined_pools = [] + all_storage_pools(conn).each do |remote_pool_name| + defined_pools << conn.lookup_storage_pool_by_name(remote_pool_name) + end + + storagedevs = [] + vm.storage_volumes.each do |volume| + # here, we need to iterate through each volume and possibly attach it + # to the host we are going to be using + storage_pool = volume.storage_pool + + if storage_pool == nil + # Hum. Specified by the VM description, but not in the storage pool? + # continue on and hope for the best + # FIXME: probably want a print to the logs here + next + end + + if storage_pool[:type] == "IscsiStoragePool" + thisstorage = Iscsi.new(storage_pool.ip_addr, storage_pool[:target]) + elsif storage_pool[:type] == "NfsStoragePool" + thisstorage = NFS.new(storage_pool.ip_addr, storage_pool.export_path) + else + # Hm, a storage type we don't understand; skip it + puts "Storage type " + storage_pool[:type] + " is not understood; skipping" + next + end + + thepool = nil + defined_pools.each do |pool| + doc = Document.new(pool.xml_desc) + root = doc.root + + if thisstorage.xmlequal?(doc.root) + thepool = pool + break + end + end + + if thepool == nil + thepool = conn.define_storage_pool_xml(thisstorage.getxml) + thepool.build + thepool.create + elsif thepool.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 + thepool.create + end + + storagedevs << thepool.lookup_volume_by_name(volume.read_attribute(thisstorage.db_column)).path + end + + return storagedevs +end + class StorageType attr_reader :db_column -- 1.5.5.1 From sseago at redhat.com Fri Aug 15 22:18:45 2008 From: sseago at redhat.com (Scott Seago) Date: Fri, 15 Aug 2008 22:18:45 +0000 Subject: [Ovirt-devel] [PATCH] adds cobbler provisioning to ovirt WUI Message-ID: <1218838725-6162-1-git-send-email-sseago@redhat.com> First, there are a few things either missing or not part of this patch: 1) current IPA/ovirt httpd conf breaks cobbler WUI. slinabery has fixes for this, but I'm not sure if those are pushed 2) since we're still using username/passwd for cobbler, you've got to set up cobbler with password access, and make sure username/password matches config/cobbler.yml This bit need to be built into the appliance setup 3) for this to work you've got to have cobbler profiles created. 4) vm creation w/ cobbler provisioning has not yet been tested, as we're having host image networking troubles right now. Signed-off-by: Scott Seago --- wui-appliance/common-pkgs.ks | 1 + wui/conf/ovirt-rails.sysconf | 3 ++ wui/conf/ovirt-taskomatic | 1 + wui/ovirt-wui.spec | 1 + wui/src/app/controllers/vm_controller.rb | 1 + wui/src/app/views/vm/_form.rhtml | 3 +- wui/src/app/views/vm/show.rhtml | 22 +++++++++++--------- wui/src/config/cobbler.yml | 5 ++++ wui/src/config/environment.rb | 2 + wui/src/db/migrate/014_add_cobbler_to_vms.rb | 28 ++++++++++++++++++++++++++ wui/src/task-omatic/task_vm.rb | 25 ++++++++++++++++++---- 11 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 wui/src/config/cobbler.yml create mode 100644 wui/src/db/migrate/014_add_cobbler_to_vms.rb diff --git a/wui-appliance/common-pkgs.ks b/wui-appliance/common-pkgs.ks index 5688308..62f688f 100644 --- a/wui-appliance/common-pkgs.ks +++ b/wui-appliance/common-pkgs.ks @@ -22,6 +22,7 @@ xorg-x11-xauth virt-viewer rhpl cobbler +rubygem-cobbler bind-utils augeas /usr/sbin/lokkit diff --git a/wui/conf/ovirt-rails.sysconf b/wui/conf/ovirt-rails.sysconf index bc9e237..abec394 100644 --- a/wui/conf/ovirt-rails.sysconf +++ b/wui/conf/ovirt-rails.sysconf @@ -1,3 +1,6 @@ # sets ruby on Rails environment / mode of operation # http://wiki.rubyonrails.org/rails/pages/Environments #RAILS_ENV=production + +#sets the path of the cobbler.yml file for cobbler access +#COBBLER_YML=/usr/share/ovirt-wui/config/cobbler.yml diff --git a/wui/conf/ovirt-taskomatic b/wui/conf/ovirt-taskomatic index 2e548b4..f5c0c51 100755 --- a/wui/conf/ovirt-taskomatic +++ b/wui/conf/ovirt-taskomatic @@ -11,6 +11,7 @@ [ -r /etc/sysconfig/ovirt-rails ] && . /etc/sysconfig/ovirt-rails export RAILS_ENV="${RAILS_ENV:-production}" +export COBBLER_YML="${COBBLER_YML:-/usr/share/ovirt-wui/config/cobbler.yml}" DAEMON=/usr/share/ovirt-wui/task-omatic/taskomatic.rb diff --git a/wui/ovirt-wui.spec b/wui/ovirt-wui.spec index 7746430..8e5a3da 100644 --- a/wui/ovirt-wui.spec +++ b/wui/ovirt-wui.spec @@ -18,6 +18,7 @@ Requires: rubygem(activeldap) >= 0.10.0 Requires: rubygem(rails) >= 2.0.1 Requires: rubygem(mongrel) >= 1.0.1 Requires: rubygem(krb5-auth) >= 0.6 +Requires: rubygem(cobbler) >= 0.0.1 Requires: ruby-gettext-package Requires: postgresql-server Requires: ruby-postgres diff --git a/wui/src/app/controllers/vm_controller.rb b/wui/src/app/controllers/vm_controller.rb index b077e88..81c3d3f 100644 --- a/wui/src/app/controllers/vm_controller.rb +++ b/wui/src/app/controllers/vm_controller.rb @@ -223,6 +223,7 @@ class VmController < ApplicationController @perm_obj = @vm.vm_resource_pool @redir_controller = 'resources' @current_pool_id=@perm_obj.id + @cobbler_profiles = Cobbler::Profile.find.collect {|profile| profile.name } end def pre_create params[:vm][:state] = Vm::STATE_PENDING diff --git a/wui/src/app/views/vm/_form.rhtml b/wui/src/app/views/vm/_form.rhtml index 023f04b..308ad71 100644 --- a/wui/src/app/views/vm/_form.rhtml +++ b/wui/src/app/views/vm/_form.rhtml @@ -8,8 +8,7 @@ <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id if @hardware_pool %> <%= text_field_with_label "Name:", "vm", "description", {:style=>"width:250px;"} %> - - <%= select_tag_with_label "Operating System:", 'cobbler_profile', {"Fedora 9" => "fedora9"}, :style=>"width:250px;" %> + <%= select_with_label "Operating System:", 'vm', 'cobbler_profile', @cobbler_profiles, :style=>"width:250px;" if create %>
Resources
diff --git a/wui/src/app/views/vm/show.rhtml b/wui/src/app/views/vm/show.rhtml index 674a66a..03395f7 100644 --- a/wui/src/app/views/vm/show.rhtml +++ b/wui/src/app/views/vm/show.rhtml @@ -87,24 +87,26 @@
- Uuid:
- Num vcpus allocated:
- Num vcpus used:
- Memory allocated:
- Memory used:
- vNIC MAC address:
- Boot device:
- State:
- Pending State:
+ Uuid:
+ Num vcpus allocated:
+ Num vcpus used:
+ Memory allocated:
+ Memory used:
+ vNIC MAC address:
+ Boot device:
+ Cobbler profile:
+ State:
+ Pending State:
<%=h @vm.uuid %>
<%=h @vm.num_vcpus_allocated %>
- <%=h @vm.num_vcpus_used %>
+ <%=h @vm.num_vcpus_used %>
<%=h @vm.memory_allocated_in_mb %> MB
<%=h @vm.memory_used_in_mb %> MB
<%=h @vm.vnic_mac_addr %>
<%=h @vm.boot_device %>
+ <%=h @vm.cobbler_profile %>
<%=h @vm.state %> <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) diff --git a/wui/src/config/cobbler.yml b/wui/src/config/cobbler.yml new file mode 100644 index 0000000..0f78d20 --- /dev/null +++ b/wui/src/config/cobbler.yml @@ -0,0 +1,5 @@ +# Cobbler connection details + +hostname: localhost +username: ovirt +password: ovirt diff --git a/wui/src/config/environment.rb b/wui/src/config/environment.rb index d14899a..ff6f6e8 100644 --- a/wui/src/config/environment.rb +++ b/wui/src/config/environment.rb @@ -78,3 +78,5 @@ end # Include your application configuration below require 'gettext/rails' +gem 'cobbler' +require 'cobbler' diff --git a/wui/src/db/migrate/014_add_cobbler_to_vms.rb b/wui/src/db/migrate/014_add_cobbler_to_vms.rb new file mode 100644 index 0000000..a8f8250 --- /dev/null +++ b/wui/src/db/migrate/014_add_cobbler_to_vms.rb @@ -0,0 +1,28 @@ +# +# 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. + +class AddCobblerToVms < ActiveRecord::Migration + def self.up + add_column :vms, :cobbler_profile, :string + end + + def self.down + remove_column :vms, :cobbler_profile + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index d7f0869..dfab4ee 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -21,6 +21,9 @@ include REXML require 'utils' +gem 'cobbler' +require 'cobbler' + def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, macAddr, bridge, diskDevices) doc = Document.new @@ -157,13 +160,25 @@ def create_vm(task) if vm.state != Vm::STATE_PENDING raise "VM not pending" end - setVmState(vm, Vm::STATE_CREATING) - # FIXME: in here, we would do any long running creating tasks (allocating - # disk, etc.) + # create cobbler system profile + begin + if !vm.cobbler_profile or vm.cobbler_profile.empty? + raise "Cobbler profile not specified" + end - setVmState(vm, Vm::STATE_STOPPED) + system = Cobbler::System.new('name' => vm.uuid, + 'profile' => vm.cobbler_profile) + system.interfaces=[Cobbler::NetworkInterface.new( + ["intf",{'mac_address' => vm.vnic_mac_addr}] + )] + system.save + setVmState(vm, Vm::STATE_STOPPED) + rescue Exception => error + setVmState(vm, Vm::STATE_CREATE_FAILED) + raise "Unable to create system: #{error.message}" + end end def shutdown_vm(task) -- 1.5.5.1 From lutter at redhat.com Fri Aug 15 23:53:19 2008 From: lutter at redhat.com (David Lutterkort) Date: Fri, 15 Aug 2008 16:53:19 -0700 Subject: [Ovirt-devel] [PATCH] Add a method to log into the OVirt server Message-ID: <1218844399-31765-1-git-send-email-lutter@redhat.com> Before we can do anything else, we need to go to the login controller and get a session cookie. Unfortunatly, this code requires some monkey-patching of ActiveResource::Connection to store the session cookie in the connection's headers. This patch should be applied after the series of patches I sent yesterday. Signed-off-by: David Lutterkort --- wui/client/README | 7 ++++- wui/client/examples/script.rb | 2 + wui/client/lib/ovirt.rb | 47 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/wui/client/README b/wui/client/README index 4bdce27..ee1db15 100644 --- a/wui/client/README +++ b/wui/client/README @@ -10,5 +10,8 @@ The server is specified with a URL of the form http://USER:PASSWORD at HOST/ovirt This requires that the server is configured to allow HTTP authentication, -since there are no mechanisms in the API to forward krb5 tickets. You can -also try and connect to Mongrel running on HOST:3000 directly. +since there are no mechanisms in the API to forward krb5 tickets. + +Before calling any other method on the API, you need to call + OVirt::Base::site = "http://USER:PASSWORD at HOST/ovirt" + OVirt::Base::login diff --git a/wui/client/examples/script.rb b/wui/client/examples/script.rb index 2103ad7..1485535 100755 --- a/wui/client/examples/script.rb +++ b/wui/client/examples/script.rb @@ -58,6 +58,8 @@ EOF exit 1 end +OVirt::Base.login + # Get a single host by name host = OVirt::Host.find_by_hostname("node3.priv.ovirt.org") puts "#{host.uuid} has id #{host.id}" diff --git a/wui/client/lib/ovirt.rb b/wui/client/lib/ovirt.rb index 48739f4..ce8c310 100644 --- a/wui/client/lib/ovirt.rb +++ b/wui/client/lib/ovirt.rb @@ -2,8 +2,53 @@ require 'pp' require 'rubygems' require 'activeresource' +class ActiveResource::Connection + attr_accessor :session + + alias_method :old_default_header, :default_header + + def default_header + old_default_header + @default_header ||= {} + if session + @default_header["Cookie"] = session + end + @default_header + end +end + module OVirt - class Base < ActiveResource::Base ; end + class Base < ActiveResource::Base + def self.login + # Do the double-redirect handshake. We don't go directly + # to the login page, since we don't want to hardcode + # its location + response = get_redirected(prefix) + uri = URI::parse(response["Location"]) + response = get_redirected(uri.path) + unless connection.session = session_cookie(response) + raise "Authentication failed" + end + end + + private + def self.session_cookie(response) + if cookies = response.get_fields("Set-Cookie") + cookies.find { |cookie| + cookie.split(";")[0].split("=")[0] == "_ovirt_session_id" + } + end + end + + def self.get_redirected(path) + begin + # We can't use HEAD since it's not in Rails 2.0.2 + connection.get(path) + rescue ActiveResource::Redirection => e + return e.response + end + end + end class HardwarePool < Base def self.find_by_path(path) -- 1.5.5.1 From apevec at redhat.com Sun Aug 17 14:25:23 2008 From: apevec at redhat.com (Alan Pevec) Date: Sun, 17 Aug 2008 16:25:23 +0200 Subject: [Ovirt-devel] [PATCH] build process improvements Message-ID: <1218983123-5679-1-git-send-email-apevec@redhat.com> - move node and appliance image creation to Makefiles - build-all.sh -u - upload new oVirt RPMs to the running appliance - build-all.sh -m URL - specify Fedora mirror instead of using mirrorlist Signed-off-by: Alan Pevec --- build-all.sh | 179 +++++++++++++++++++++++--------- ovirt-host-creator/.gitignore | 4 +- ovirt-host-creator/Makefile | 15 ++-- ovirt-host-creator/iso-file | 1 + ovirt-host-creator/ovirt-cd | 31 ------ ovirt-host-creator/ovirt.ks | 4 +- ovirt-host-creator/repos.ks.in | 6 +- wui-appliance/.gitignore | 1 - wui-appliance/Makefile | 30 +++--- wui-appliance/create-wui-appliance.sh | 69 ++++--------- wui-appliance/repos.ks.in | 9 +- 11 files changed, 184 insertions(+), 165 deletions(-) create mode 100644 ovirt-host-creator/iso-file delete mode 100755 ovirt-host-creator/ovirt-cd diff --git a/build-all.sh b/build-all.sh index 6c0f252..d2d2c7e 100755 --- a/build-all.sh +++ b/build-all.sh @@ -2,9 +2,9 @@ # # build all oVirt components -# - create oVirt host image (livecd-creator) +# - create oVirt Node image (livecd-creator) # - create local YUM repository with ovirt-wui and ovirt-host-image-pxe RPMs -# - create oVirt admin appliance (appliance-creator) +# - create oVirt Server Suite appliance (appliance-creator) # Requires: createrepo kvm libvirt livecd-tools appliance-tools @@ -25,17 +25,22 @@ BUILD=$BASE/tmp COMMON=$BASE/common OVIRT=$BUILD/ovirt CACHE=$BUILD/cache +NAME=ovirt-appliance +IMGDIR=/var/lib/libvirt/images DEP_RPMS="createrepo kvm libvirt livecd-tools appliance-tools" usage() { case $# in 1) warn "$1"; try_h; exit 1;; esac cat < ... +# e.g. setup_repos $NODE/repos.ks ruby ruby-libs gtk-vnc\* +setup_repos() { + local ks_include=$1 + + # use Fedora + updates - those marked bad + currentbadupdates='' + for p in $@; do + currentbadupdates="$currentbadupdates --excludepkgs=$p" + done + fedora_mirror=http://mirrors.fedoraproject.org/mirrorlist + printf "repo --name=f$F_REL" > $ks_include + if [ -n "$fedora_url" ]; then + cat >> $ks_include << EOF + --baseurl=$fedora_url/releases/$F_REL/Everything/\$basearch/os +EOF + else + cat >> $ks_include << EOF + --mirrorlist=$fedora_mirror?repo=fedora-$F_REL&arch=\$basearch +EOF + fi + printf "repo --name=f$F_REL-updates" >> $ks_include + if [ -n "$fedora_url" ]; then + cat >> $ks_include << EOF + --baseurl=$fedora_url/updates/$F_REL/\$basearch \ + $currentbadupdates +EOF + else + cat >> $ks_include << EOF + --mirrorlist=$fedora_mirror?repo=updates-released-f$F_REL&arch=\$basearch \ + $currentbadupdates +EOF + fi + # + ovirt.org repo for updates not yet in Fedora + # + local ovirt repo with locally rebuilt ovirt* RPMs ( options -w and -n ) + # if not available, ovirt* RPMs from ovirt.org will be used + excludepkgs= + if [[ -f $OVIRT/repodata/repomd.xml ]]; then + excludepkgs='--excludepkgs=ovirt*' + cat >> $ks_include << EOF +repo --name=ovirt --baseurl=file://$OVIRT +EOF + fi + cat >> $ks_include << EOF +repo --name=ovirt-org \ + --baseurl=http://ovirt.org/repos/ovirt/$F_REL/\$basearch $excludepkgs +EOF +} + +update_running_appliance() { + local rpmfile=$1 + + local services_rpm=ovirt-wui + local services_list='ovirt-mongrel-rails ovirt-host-* ovirt-taskomatic' + + if [ $upload_rpms = 1 ]; then + pkg=$(basename $rpmfile) + cat $rpmfile | ssh root at 192.168.50.2 \ + "cat > $pkg; yum -y --nogpgcheck localupdate $pkg; + if [ $pkg != ${pkg#$services_rpm} ]; then + cd /etc/init.d + for s in $services_list; do + if service \$s status > /dev/null; then + service \$s restart + fi + done + fi" + fi +} + +update_wui=0 +update_node=0 update_app=0 +upload_rpms=0 include_src=0 +fedora_url= cleanup=0 version_type=git bridge= err=0 help=0 -while getopts wnsacv:e:h c; do +while getopts wnsacum:v:e:h c; do case $c in w) update_wui=1;; n) update_node=1;; s) include_src=1;; a) update_wui=1; update_node=1; update_app=1;; c) cleanup=1;; + u) upload_rpms=1;; + m) fedora_url=$OPTARG;; v) version_type=$OPTARG;; e) bridge=$OPTARG;; h) help=1;; @@ -81,11 +162,14 @@ test "$version_type" != "git" -a "$version_type" != "release" \ && usage "version type must be git, release or none" if [ $update_node = 1 -o $update_app = 1 ]; then - test $( id -u ) -ne 0 && die "Node or Application Update must run as root" + test $( id -u ) -ne 0 && die "Node or Appliance update must run as root" fi +test $upload_rpms = 1 && "$(virsh domstate $NAME 2> /dev/null)" != "running" \ + && die "oVirt appliance is not running" + # now make sure the packages we need are installed -rpm -q $DEP_RPMS >& /dev/null +rpm -q $DEP_RPMS > /dev/null 2>&1 if [ $? -ne 0 ]; then # one of the previous packages wasn't installed; bail out die "Must have $DEP_RPMS installed" @@ -132,52 +216,23 @@ if [ $cleanup = 1 ]; then rm -rf $CACHE/* fi - # stop execution on any error set -e # build ovirt-wui RPM if [ $update_wui = 1 ]; then - cd $BASE/wui rm -rf rpm-build bumpver make rpms rm -f $OVIRT/ovirt-wui*rpm cp rpm-build/ovirt-wui*rpm $OVIRT - cd $OVIRT createrepo . + update_running_appliance $OVIRT/ovirt-wui*.noarch.rpm fi -fedora_mirror=http://mirrors.fedoraproject.org/mirrorlist -# use Fedora + updates -currentbadupdates='' -cat > $NODE/repos.ks << EOF -repo --name=f$F_REL \ - --mirrorlist=$fedora_mirror?repo=fedora-$F_REL&arch=\$basearch -repo --name=f$F_REL-updates \ - --mirrorlist=$fedora_mirror?repo=updates-released-f$F_REL&arch=\$basearch \ - $currentbadupdates -EOF -# + ovirt.org repo for updates not yet in Fedora -# + local ovirt repo with locally rebuilt ovirt* RPMs ( options -w and -n ) -# if not available, ovirt* RPMs from ovirt.org will be used -excludepkgs= -if [[ -f $OVIRT/repodata/repomd.xml ]]; then - excludepkgs='--excludepkgs=ovirt*' - cat >> $NODE/repos.ks << EOF -repo --name=ovirt --baseurl=file://$OVIRT -EOF -fi -cat >> $NODE/repos.ks << EOF -repo --name=ovirt-org \ - --baseurl=http://ovirt.org/repos/ovirt/$F_REL/\$basearch $excludepkgs -EOF - -# build oVirt host image; note that we unconditionally rebuild the -# ovirt-managed-node RPM, since it is now needed for the managed node -# NOTE: livecd-tools must run as root +# build oVirt Node image if [ $update_node = 1 ]; then cd $BASE/ovirt-managed-node rm -rf rpm-build @@ -191,21 +246,42 @@ if [ $update_node = 1 ]; then cd $NODE rm -rf rpm-build bumpver + make distclean + setup_repos $NODE/repos.ks make rpms YUMCACHE=$CACHE rm -f $OVIRT/ovirt-host-image*rpm cp rpm-build/ovirt-host-image*rpm $OVIRT cd $OVIRT createrepo . + update_running_appliance $OVIRT/ovirt-host-image-pxe*.$ARCH.rpm fi # build sources tarball if [ $include_src != 0 ]; then - cat $NODE/repos.ks - > $BUILD/src.ks << EOF -repo --name=f$F_REL-src \ + setup_repos $BUILD/src.ks + printf "repo --name=f$F_REL-src" >> $BUILD/src.ks + if [ -n "$fedora_url" ]; then + cat >> $BUILD/src.ks << EOF + --baseurl=$fedora_url/releases/$F_REL/Everything/source/SRPMS +EOF + else + cat >> $BUILD/src.ks << EOF --mirrorlist=$fedora_mirror?repo=fedora-source-$F_REL&arch=src -repo --name=f$F_REL-updates-src \ +EOF + fi + printf "repo --name=f$F_REL-updates-src" >> $BUILD/src.ks + if [ -n "$fedora_url" ]; then + cat >> $BUILD/src.ks << EOF + --baseurl=$fedora_url/updates/$F_REL/SRPMS + $currentbadupdates +EOF + else + cat >> $BUILD/src.ks << EOF --mirrorlist=$fedora_mirror?repo=updates-released-source-f$F_REL&arch=src \ $currentbadupdates +EOF + fi + cat >> $BUILD/src.ks << EOF repo --name=ovirt-org-src \ --baseurl=http://ovirt.org/repos/ovirt/$F_REL/src $excludepkgs @@ -221,20 +297,21 @@ EOF tar cf ovirt-source.tar SRPMS fi -# build oVirt admin appliance +# build oVirt Server Suite appliance if [ $update_app == 1 ]; then cd $WUI - make clean - cp $NODE/repos.ks $WUI/repos.ks - make + make distclean + setup_repos $WUI/repos.ks + make appliance YUMCACHE=$CACHE NAME=$NAME + printf "Moving the image..." + mv $NAME-sda.raw $IMGDIR + restorecon -v $IMGDIR/$NAME.img bridge_flag= if [ -n "$bridge" ]; then bridge_flag="-e $bridge" fi - ./create-wui-appliance.sh -y $CACHE \ - -k wui-rel.ks \ - $bridge_flag + ./create-wui-appliance.sh -d $IMGDIR -n $NAME $bridge_flag fi diff --git a/ovirt-host-creator/.gitignore b/ovirt-host-creator/.gitignore index dee1a01..77f0929 100644 --- a/ovirt-host-creator/.gitignore +++ b/ovirt-host-creator/.gitignore @@ -1,6 +1,4 @@ -iso-file -*.iso +ovirt.iso tftpboot rpm-build -ovirt-cd.log repos.ks diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index b4c94d6..3a2d74a 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -1,3 +1,5 @@ +YUMCACHE=$$(pwd)/../tmp/cache + pkg_name = ovirt-host-image all: rpms @@ -7,17 +9,16 @@ clean: rm -rf ovirt-host-image-* ovirt-cd.log distclean: clean - rm -rf *.iso repos.ks rpm-build iso-file + rm -rf *.iso repos.ks rpm-build repos.ks: repos.ks.in - sed "s/@@ARCH@@/$(ARCH)/" repos.ks.in > repos.ks + cp repos.ks.in repos.ks -build: ovirt.ks common-install.ks common-pkgs.ks common-post.ks repos.ks +ovirt.iso: ovirt.ks common-install.ks common-pkgs.ks common-post.ks repos.ks ../common/rpm-compare.py GE 0 livecd-tools 017.1 1 - ./ovirt-cd $(YUMCACHE) + livecd-creator --cache=$(YUMCACHE) --skip-minimize -c ovirt.ks -f ovirt -tar: clean build - mv $$(cat iso-file) ovirt.iso +tar: clean ovirt.iso mkdir -p $(NV) cp -a ovirt-host-image.spec ovirt-pxe ovirt-flash ovirt-flash-static ovirt.iso $(NV) mkdir -p rpm-build @@ -25,4 +26,4 @@ tar: clean build cp version rpm-build/ rm -rf $(NV) -.PHONY: all clean build tar distclean +.PHONY: all clean tar distclean diff --git a/ovirt-host-creator/iso-file b/ovirt-host-creator/iso-file new file mode 100644 index 0000000..28b3530 --- /dev/null +++ b/ovirt-host-creator/iso-file @@ -0,0 +1 @@ +ovirt-200808152256.iso diff --git a/ovirt-host-creator/ovirt-cd b/ovirt-host-creator/ovirt-cd deleted file mode 100755 index 9a2ba8f..0000000 --- a/ovirt-host-creator/ovirt-cd +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# -# Create an Ovirt Host LiveCD -# Copyright 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 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. - -PATH=/sbin:/bin:/usr/bin - -KICKSTART=ovirt.ks -CACHE= -if [ -n "$1" ]; then - CACHE=--cache=$1 -fi - -LABEL=ovirt-`date +%Y%m%d%H%M` -livecd-creator $CACHE --skip-minimize -c $KICKSTART -f $LABEL 1>&2 && - -echo $LABEL.iso > iso-file diff --git a/ovirt-host-creator/ovirt.ks b/ovirt-host-creator/ovirt.ks index e58176a..afa4d68 100644 --- a/ovirt-host-creator/ovirt.ks +++ b/ovirt-host-creator/ovirt.ks @@ -2,9 +2,11 @@ %include repos.ks -%packages --excludedocs +%packages --excludedocs --nobase %include common-pkgs.ks +%end + %post %include common-post.ks diff --git a/ovirt-host-creator/repos.ks.in b/ovirt-host-creator/repos.ks.in index 0a5cb52..79a8cef 100644 --- a/ovirt-host-creator/repos.ks.in +++ b/ovirt-host-creator/repos.ks.in @@ -1,3 +1,3 @@ -repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=@@ARCH@@ -repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=@@ARCH@@ -repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/@@ARCH@@ +repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch +repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch +repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch diff --git a/wui-appliance/.gitignore b/wui-appliance/.gitignore index 13842ee..4f7b1f4 100644 --- a/wui-appliance/.gitignore +++ b/wui-appliance/.gitignore @@ -1,3 +1,2 @@ -wui-rel.ks repos.ks ovirt-appliance.xml diff --git a/wui-appliance/Makefile b/wui-appliance/Makefile index b023622..d912105 100644 --- a/wui-appliance/Makefile +++ b/wui-appliance/Makefile @@ -1,21 +1,25 @@ -all: ks +NAME=ovirt-appliance +YUMCACHE=$$(pwd)/../tmp/cache -ARCH := $(shell uname -i) +appliance: $(NAME)-sda.raw -ks: wui-rel.ks +appliance-compressed: $(NAME)-sda.qcow -define ks-flatten - rm -f $@ $@-t - ksflatten $< > $@-t - chmod a=r $@-t - mv $@-t $@ -endef +$(NAME)-sda.raw: wui-devel.ks common-install.ks common-pkgs.ks common-post.ks repos.ks + appliance-creator --config wui-devel.ks --name $(NAME) \ + --tmpdir=$$(pwd)/tmp --cache=$(YUMCACHE) -wui-rel.ks: wui-devel.ks repos.ks - $(ks-flatten) +$(NAME)-sda.qcow: $(NAME)-sda.raw + # FIXME add --compress option to appliance-creator + qemu-img convert -c $(NAME)-sda.raw -O qcow2 $(NAME)-sda.qcow repos.ks: repos.ks.in - sed "s/@@ARCH@@/$(ARCH)/" repos.ks.in > repos.ks + cp repos.ks.in repos.ks clean: - rm -f repos.ks wui-rel.ks *~ + rm -f repos.ks + +distclean: clean + rm -rf $(NAME)-sda.raw $(NAME)-sda.qcow + +.PHONY: appliance appliance-compressed clean distclean diff --git a/wui-appliance/create-wui-appliance.sh b/wui-appliance/create-wui-appliance.sh index 1683237..2527675 100755 --- a/wui-appliance/create-wui-appliance.sh +++ b/wui-appliance/create-wui-appliance.sh @@ -6,54 +6,44 @@ try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } die() { warn "$@"; try_h; exit 1; } RAM=768 -IMGSIZE=6000M IMGDIR_DEFAULT=/var/lib/libvirt/images +NAME_DEFAULT=ovirt-appliance NET_SCRIPTS=/etc/sysconfig/network-scripts -NAME=ovirt-appliance BRIDGENAME=ovirtbr imgdir=$IMGDIR_DEFAULT +name=$NAME_DEFAULT usage() { case $# in 1) warn "$1"; try_h; exit 1;; esac cat < $name @@ -64,8 +54,8 @@ EOF } gen_fake_managed_node() { - num=$1 - last_mac=$(( 54 + $num )) + local num=$1 + local last_mac=$(( 54 + $num )) cat < @@ -102,12 +92,13 @@ EOF } gen_app() { - local disk=$1 - local ram=$2 + local name=$1 + local disk=$2 + local ram=$3 cat< - $NAME + $name $(( $ram * 1024 )) $(( $ram * 1024 )) 1 @@ -273,36 +264,16 @@ fi virsh undefine bundled } > /dev/null 2>&1 -IMGNAME=$NAME.img mkdir -p $imgdir -virsh destroy $NAME > /dev/null 2>&1 -virsh undefine $NAME > /dev/null 2>&1 - -if [ -n "$kickstart" ]; then - mkdir -p tmp - set -e - appliance-creator --config $kickstart --name $NAME \ - --tmpdir=$(pwd)/tmp $yumcache - # FIXME add --compress option to appliance-creator - if [ $compress -ne 0 ]; then - printf "Compressing the image..." - qemu-img convert -c $NAME-sda.raw -O qcow2 "$imgdir/$IMGNAME" - rm $NAME-sda.raw - else - printf "Moving the image..." - mv $NAME-sda.raw "$imgdir/$IMGNAME" - restorecon -v "$imgdir/$IMGNAME" - fi - echo done - set +e -fi - -test ! -r $imgdir/$IMGNAME && die "Disk image not found at $imgdir/$IMGNAME" +imgname=$name.img +test ! -r $imgdir/$imgname && die "Disk image not found at $imgdir/$imgname" +virsh destroy $name > /dev/null 2>&1 +virsh undefine $name > /dev/null 2>&1 TMPXML=$(mktemp) || exit 1 # FIXME virt-image to define the appliance instance -gen_app $imgdir/$IMGNAME $RAM > $TMPXML +gen_app $name $imgdir/$imgname $RAM > $TMPXML virsh define $TMPXML rm $TMPXML -echo "Application defined using disk located at $imgdir/$IMGNAME." -echo "Run virsh start $NAME to start the appliance" +echo "Application defined using disk located at $imgdir/$imgname." +echo "Run virsh start $name to start the appliance" diff --git a/wui-appliance/repos.ks.in b/wui-appliance/repos.ks.in index 29fbbc6..79a8cef 100644 --- a/wui-appliance/repos.ks.in +++ b/wui-appliance/repos.ks.in @@ -1,6 +1,3 @@ -url --url http://download.fedora.redhat.com/pub/fedora/linux/releases/9/Fedora/@@ARCH@@/os/ - -repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=@@ARCH@@ -repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=@@ARCH@@ -repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/@@ARCH@@ - +repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch +repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch +repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch -- 1.5.5.1 From apevec at redhat.com Mon Aug 18 00:58:25 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 18 Aug 2008 02:58:25 +0200 Subject: [Ovirt-devel] [PATCH] replace gtk-vnc with virt-viewer plugin Message-ID: <1219021105-7800-1-git-send-email-apevec@redhat.com> open issue: - console.rhtml opens as new window, it was displaying gtk-vnc plugin. virt-viewer plugin doesn't display anything, it just launches an external virt-viewer program. We could embedd the plugin in show.rhtml, if it were possible to control the plugin when to launch virt-viewer. --- wui-appliance/common-pkgs.ks | 3 +-- wui/src/app/views/vm/console.rhtml | 11 +++++++---- wui/src/app/views/vm/show.rhtml | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/wui-appliance/common-pkgs.ks b/wui-appliance/common-pkgs.ks index 62f688f..15d3178 100644 --- a/wui-appliance/common-pkgs.ks +++ b/wui-appliance/common-pkgs.ks @@ -17,9 +17,8 @@ iscsi-initiator-utils ovirt-wui.noarch ovirt-host-image-pxe firefox -gtk-vnc-plugin xorg-x11-xauth -virt-viewer +virt-viewer-plugin rhpl cobbler rubygem-cobbler diff --git a/wui/src/app/views/vm/console.rhtml b/wui/src/app/views/vm/console.rhtml index 06e2068..5a853de 100644 --- a/wui/src/app/views/vm/console.rhtml +++ b/wui/src/app/views/vm/console.rhtml @@ -26,10 +26,13 @@
<%else -%> - + +virt-viewer started
+hypervisor qemu+tcp://<%= @vm.host.hostname%>/system
+virtual machine <%= @vm.uuid%>
<%end -%> diff --git a/wui/src/app/views/vm/show.rhtml b/wui/src/app/views/vm/show.rhtml index 03395f7..4e9eab1 100644 --- a/wui/src/app/views/vm/show.rhtml +++ b/wui/src/app/views/vm/show.rhtml @@ -9,7 +9,7 @@ :id=>"vnc_console_link" %> <% end -%> -- 1.5.5.1 From jeffschroed at gmail.com Mon Aug 18 01:09:04 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Sun, 17 Aug 2008 18:09:04 -0700 Subject: [Ovirt-devel] [PATCH] replace gtk-vnc with virt-viewer plugin In-Reply-To: <1219021105-7800-1-git-send-email-apevec@redhat.com> References: <1219021105-7800-1-git-send-email-apevec@redhat.com> Message-ID: On Sun, Aug 17, 2008 at 5:58 PM, Alan Pevec wrote: > open issue: > - console.rhtml opens as new window, it was displaying gtk-vnc plugin. > virt-viewer plugin doesn't display anything, it just launches an external > virt-viewer program. We could embedd the plugin in show.rhtml, if it were > possible to control the plugin when to launch virt-viewer. Just a question... Is this available on windows? Will it ever be available on windows? For most it won't matter, but for some this would be a killer feature. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From apevec at redhat.com Mon Aug 18 01:22:57 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 18 Aug 2008 03:22:57 +0200 Subject: [Ovirt-devel] [PATCH] replace gtk-vnc with virt-viewer plugin In-Reply-To: References: <1219021105-7800-1-git-send-email-apevec@redhat.com> Message-ID: <48A8CEF1.4030701@redhat.com> Jeff Schroeder wrote: > On Sun, Aug 17, 2008 at 5:58 PM, Alan Pevec wrote: >> open issue: >> - console.rhtml opens as new window, it was displaying gtk-vnc plugin. >> virt-viewer plugin doesn't display anything, it just launches an external >> virt-viewer program. We could embedd the plugin in show.rhtml, if it were >> possible to control the plugin when to launch virt-viewer. > > Just a question... Is this available on windows? Will it ever be > available on windows? > For most it won't matter, but for some this would be a killer feature. yes, the plan is to have virt-viewer on windows, rjones is working on that From jeffschroed at gmail.com Mon Aug 18 01:30:07 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Sun, 17 Aug 2008 18:30:07 -0700 Subject: [Ovirt-devel] [PATCH] replace gtk-vnc with virt-viewer plugin In-Reply-To: <48A8CEF1.4030701@redhat.com> References: <1219021105-7800-1-git-send-email-apevec@redhat.com> <48A8CEF1.4030701@redhat.com> Message-ID: On Sun, Aug 17, 2008 at 6:22 PM, Alan Pevec wrote: > Jeff Schroeder wrote: >> >> On Sun, Aug 17, 2008 at 5:58 PM, Alan Pevec wrote: >>> >>> open issue: >>> - console.rhtml opens as new window, it was displaying gtk-vnc plugin. >>> virt-viewer plugin doesn't display anything, it just launches an >>> external >>> virt-viewer program. We could embedd the plugin in show.rhtml, if it >>> were >>> possible to control the plugin when to launch virt-viewer. >> >> Just a question... Is this available on windows? Will it ever be >> available on windows? >> For most it won't matter, but for some this would be a killer feature. > > yes, the plan is to have virt-viewer on windows, rjones is working on that Great! Is there any timeframe when the developer's appliance will support managing vms on localhost? This would bring down the testing barrier *substantially* now that cobbler integration is mostly in place. My goal is to be able to "apt-get install ovirt" in 1 or 2 ubuntu / debian releases from now to install the wui and bundled management server. So far freeipa has a zillion deps that I'm slowly working out. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From clalance at redhat.com Mon Aug 18 06:48:03 2008 From: clalance at redhat.com (Chris Lalancette) Date: Mon, 18 Aug 2008 08:48:03 +0200 Subject: [Ovirt-devel] [PATCH]: Tested, working implementation of migration In-Reply-To: <20080814220616.22d3fac8@tp.mains.net> References: <1218530502-11852-1-git-send-email-clalance@redhat.com> <20080814220616.22d3fac8@tp.mains.net> Message-ID: <48A91B23.7040000@redhat.com> Ian Main wrote: > On Tue, 12 Aug 2008 10:41:42 +0200 > Chris Lalancette wrote: > >> An actually working, tested cut of the migration code for taskomatic. It >> supports 3 modes: > > I tried to get this to work but I'm having a number of issues. I fixed > one bug with migrating to an unselected host, but the migrate itself is > still failing. It's getting late so I'm going to bed :) > > Here's the main error I was getting: > > libvir: QEMU error : operation failed: migrate failed: migrate > "tcp://node13 Task action processing failed: Libvirt::Error: Call to > function virDomainMigrate failed ./task_vm.rb:487:in `migrate' OK. Yeah, unfortunately, the error reporting still isn't very good, so it's hard to figure out what went on. One of the main reasons I was getting the above type of error was when the storage pool didn't get defined on the remote end for whatever reason. You'll get better information if you log into both the src and dst hosts, shutdown libvirtd, and run: # LIBVIRT_DEBUG=1 /usr/sbin/libvirtd --listen --verbose That will show you better error information. Chris Lalancette From apevec at redhat.com Mon Aug 18 10:15:13 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 18 Aug 2008 12:15:13 +0200 Subject: [Ovirt-devel] [PATCH] remove ovirtadmin keytab Message-ID: <1219054513-5535-1-git-send-email-apevec@redhat.com> ipa-getkeytab randomizes the password, so it wasn't possible to login as ovirtadmin using browser basic auth --- wui-appliance/wui-devel.ks | 5 +---- 1 files changed, 1 insertions(+), 4 deletions(-) diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 5729b60..66927be 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -45,7 +45,6 @@ principal=ovirtadmin realm=PRIV.OVIRT.ORG password=ovirt cron_file=/etc/cron.hourly/ovirtadmin.cron -ktab_file=/usr/share/ovirt-wui/ovirtadmin.tab # automatically refresh the kerberos ticket every hour (we'll create the # principal on first-boot) @@ -53,7 +52,7 @@ cat > $cron_file << EOF #!/bin/bash export PATH=/usr/kerberos/bin:$PATH kdestroy -kinit -k -t $ktab_file $principal@$realm +echo $password | kinit $principal@$realm EOF chmod 755 $cron_file @@ -128,7 +127,6 @@ sed -e "s, at cron_file@,$cron_file," \ -e "s, at principal@,$principal," \ -e "s, at realm@,$realm," \ -e "s, at password@,$password,g" \ - -e "s, at ktab_file@,$ktab_file," \ > $first_run_file << \EOF #!/bin/bash # @@ -175,7 +173,6 @@ LDAP # make ovitadmin also an IPA admin ipa-modgroup -a ovirtadmin admins ipa-moduser --setattr krbPasswordExpiration=19700101000000Z @principal@ - ipa-getkeytab -s management.priv.ovirt.org -p @principal@ -k @ktab_file@ @cron_file@ ) > /var/log/ovirt-wui-dev-first-run.log 2>&1 -- 1.5.4.1 From sseago at redhat.com Mon Aug 18 13:25:21 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 18 Aug 2008 09:25:21 -0400 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <1218757432-30330-5-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> Message-ID: <48A97841.4090906@redhat.com> David Lutterkort wrote: > Signed-off-by: David Lutterkort > --- > wui/src/app/models/hardware_pool.rb | 12 ++++++++++++ > 1 files changed, 12 insertions(+), 0 deletions(-) > > diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb > index 276779f..249d744 100644 > --- a/wui/src/app/models/hardware_pool.rb > +++ b/wui/src/app/models/hardware_pool.rb > @@ -97,4 +97,16 @@ class HardwarePool < Pool > return {:total => total, :labels => labels} > end > > + def self.find_by_path(path) > + segs = path.split("/") > + unless segs.shift.empty? > + raise "Path must be absolute, but is #{path}" > + end > + if segs.shift == "default" > + segs.inject(get_default_pool) do |pool, seg| > + pool.sub_hardware_pools.find { |p| p.name == seg } if pool > + end > + end > + end > + > end > We shouldn't assume that the default pool happens to be named 'default'. I'm not sure the best way to do this, but one way is to treat the default pool as having path "/". So Pool.find_by_path("/") would return the default pool, and Pool.find_by_path("/engineering/QA") would start with the default pool, find a subpool named "engineering" and a subpool of engineering called "QA". Scott From hbrock at redhat.com Mon Aug 18 14:17:19 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Mon, 18 Aug 2008 10:17:19 -0400 Subject: [Ovirt-devel] [PATCH] replace gtk-vnc with virt-viewer plugin In-Reply-To: References: <1219021105-7800-1-git-send-email-apevec@redhat.com> <48A8CEF1.4030701@redhat.com> Message-ID: <20080818141719.GB11219@redhat.com> On Sun, Aug 17, 2008 at 06:30:07PM -0700, Jeff Schroeder wrote: > On Sun, Aug 17, 2008 at 6:22 PM, Alan Pevec wrote: > > Jeff Schroeder wrote: > >> > >> On Sun, Aug 17, 2008 at 5:58 PM, Alan Pevec wrote: > >>> > >>> open issue: > >>> - console.rhtml opens as new window, it was displaying gtk-vnc plugin. > >>> virt-viewer plugin doesn't display anything, it just launches an > >>> external > >>> virt-viewer program. We could embedd the plugin in show.rhtml, if it > >>> were > >>> possible to control the plugin when to launch virt-viewer. > >> > >> Just a question... Is this available on windows? Will it ever be > >> available on windows? > >> For most it won't matter, but for some this would be a killer feature. > > > > yes, the plan is to have virt-viewer on windows, rjones is working on that > > Great! Is there any timeframe when the developer's appliance will support > managing vms on localhost? This would bring down the testing barrier > *substantially* now that cobbler integration is mostly in place. > > My goal is to be able to "apt-get install ovirt" in 1 or 2 ubuntu / > debian releases > from now to install the wui and bundled management server. So far freeipa has > a zillion deps that I'm slowly working out. > Hey Jeff. Yeah we're working on that one too (managing VMs on localhost) . It won't be in the release we're about to push but it will certainly be in the next one. Great news on the debian release, let us know if there's anything we can do to help. Take care, --Hugh From hbrock at harpcolumn.com Mon Aug 18 14:18:35 2008 From: hbrock at harpcolumn.com (Hugh Brock) Date: Mon, 18 Aug 2008 10:18:35 -0400 Subject: [Ovirt-devel] [PATCH] Reposition facebox on subtab select. In-Reply-To: <1218829042-8464-1-git-send-email-jguiditt@redhat.com> References: <1218829042-8464-1-git-send-email-jguiditt@redhat.com> Message-ID: <20080818141743.GD11219@redhat.com> On Fri, Aug 15, 2008 at 03:37:22PM -0400, Jason Guiditta wrote: > For example 'add storage pool'. Box was positioned based > on initially displayed content. This positioning needed to > be called again whenever the content changes. > > Signed-off-by: Jason Guiditta > --- > wui/src/app/views/layouts/redux.rhtml | 1 + > 1 files changed, 1 insertions(+), 0 deletions(-) > > diff --git a/wui/src/app/views/layouts/redux.rhtml b/wui/src/app/views/layouts/redux.rhtml > index 924f300..d6cfe24 100644 > --- a/wui/src/app/views/layouts/redux.rhtml > +++ b/wui/src/app/views/layouts/redux.rhtml > @@ -84,6 +84,7 @@ > var wrapped_data = $(data).find('div').filter('[id=dialog-content-area]'); > var my_parent = $(this).parent(); > $('#dialog-content-area').html($(data)); > + $('#facebox').css('left', $(window).width() / 2 - ($('#facebox table').width() / 2)); > }, > error: function(xhr) {$.jGrowl(xhr.status + ' ' + xhr.statusText);} > }); ACK, even though this does not solve the original problem, but rather a new problem jay discovered... --Hugh From hbrock at redhat.com Mon Aug 18 14:18:46 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Mon, 18 Aug 2008 10:18:46 -0400 Subject: [Ovirt-devel] [PATCH] test updates incorporating new auth changes In-Reply-To: <48A5D6A9.50502@syr.edu> References: <48A5D6A9.50502@syr.edu> Message-ID: <20080818141846.GE11219@redhat.com> On Fri, Aug 15, 2008 at 03:19:05PM -0400, Mohammed Morsi wrote: > The included patch switches the appliance into the test environment > before running the wui tests, and removed the now-unnecessary url based > credentials. > > -Mo > >From 1c34a258e617615f8211a12312057daa2cfc14dd Mon Sep 17 00:00:00 2001 > From: root > Date: Fri, 15 Aug 2008 15:06:28 -0400 > Subject: [PATCH] changes to autobuild / interface test incorporating new auth changes > > --- > autobuild.sh | 11 ++++------- > wui/src/test/functional/interface_test.rb | 5 +++-- > 2 files changed, 7 insertions(+), 9 deletions(-) > > diff --git a/autobuild.sh b/autobuild.sh > index b565404..64b48a7 100755 > --- a/autobuild.sh > +++ b/autobuild.sh > @@ -74,13 +74,10 @@ else > echo "$SELENIUM_RB not found, will not run interface tests" > fi > > -$ssh_cmd \ > - "sed -i -e \"s/KrbMethodNegotiate on/KrbMethodNegotiate off/g\" \ > - -e \"s/KrbMethodK5Passwd off/KrbMethodK5Passwd on/g\" \ > - /etc/httpd/conf.d/ovirt-wui.conf" > - > echo "Running the wui tests" > $ssh_cmd \ > - "curl -i --negotiate -u : management.priv.ovirt.org/ovirt/ | \ > + "sed -i \"s/#RAILS_ENV=production/RAILS_ENV=test/g\" /etc/sysconfig/ovirt-rails && \ > + service ovirt-mongrel-rails restart && service httpd restart && \ > + curl -i http://management.priv.ovirt.org/ovirt/ | \ > grep 'HTTP/1.1 200 OK' && \ > - cd /usr/share/ovirt-wui && rake test" > + cd /usr/share/ovirt-wui && rake test" > diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb > index d37a716..6563b44 100644 > --- a/wui/src/test/functional/interface_test.rb > +++ b/wui/src/test/functional/interface_test.rb > @@ -25,13 +25,14 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' > def setup > @browser = Selenium::SeleniumDriver.new("192.168.50.1", 4444, > "*firefox /usr/lib64/firefox-3.0.1/firefox", > - "http://admin:ovirt at 192.168.50.2/ovirt/", 15000) > + "http://192.168.50.2/ovirt/", 15000) > @browser.start > end > > def test_ovirt > - @browser.open("http://admin:ovirt at 192.168.50.2/ovirt/") > + @browser.open("http://192.168.50.2/ovirt/") > assert_equal("Dashboard", @browser.get_title()) > + @browser.close > end > > def teardown ACK --Hugh From bkearney at redhat.com Mon Aug 18 14:53:53 2008 From: bkearney at redhat.com (Bryan Kearney) Date: Mon, 18 Aug 2008 10:53:53 -0400 Subject: [Ovirt-devel] Lighter-weight "developer" setup In-Reply-To: <48A1D7A3.8050007@redhat.com> References: <489C5391.6050602@redhat.com> <48A1D7A3.8050007@redhat.com> Message-ID: <48A98D01.7020001@redhat.com> Bryan Kearney wrote: > Chris Lalancette wrote: > >> 1. Still have an oVirt appliance that has everything installed inside >> of it. >> Then, this appliance "manages" the host that it is actually running >> on, and can >> install additional guests alongside the appliance. You need to >> protect the >> oVirt appliance a little bit so you don't accidentally destroy itself, >> but >> otherwise you can treat the underlying hardware just like any other node. >> > >> >> 2. Get rid of the oVirt appliance completely, and just provide >> instructions/better scripts for installing all of the oVirt software >> directly on >> the host. Then the host runs the WUI, and you don't need to protect any >> "special" guests. > > > > We have a first cut of an appliance definition of the ovirt appliance at > [1]. With this, we could look at a couple of other alternatives: > > 1) Make the recipe availble with instructions to build it via the > appliance tools on their machines either from the public mirrors or > local media. > 2) Use the recipe to configure an existing bare metal machine. > 3) Set up a public cobbler server to "koan up" a new appliance. Again, > this would be from the public mirrors. > > 1 and 3 _could_ save bandwidth based on using public mirrors. 1 could > save more if they have media handy. > > 2 would be really cool, but the current recipes do not control packages. > We would need to work up a new "package" recipe, and then apply the one > from [1]. > > -- bk > > [1] http://git.et.redhat.com/?p=acex.git;a=tree;f=ovirt/appliances/ovirt; Just following up. Would this approach help out? If so, how can I help getting folks to use the new puppet based recipe? -- bk From jeffschroed at gmail.com Mon Aug 18 15:26:35 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Mon, 18 Aug 2008 08:26:35 -0700 Subject: [Ovirt-devel] Lighter-weight "developer" setup In-Reply-To: <48A98D01.7020001@redhat.com> References: <489C5391.6050602@redhat.com> <48A1D7A3.8050007@redhat.com> <48A98D01.7020001@redhat.com> Message-ID: On Mon, Aug 18, 2008 at 7:53 AM, Bryan Kearney wrote: > Bryan Kearney wrote: >> >> Chris Lalancette wrote: >> >>> >>> 1. Still have an oVirt appliance that has everything installed inside of >>> it. >>> Then, this appliance "manages" the host that it is actually running on, >>> and can >>> install additional guests alongside the appliance. You need to protect >>> the >>> oVirt appliance a little bit so you don't accidentally destroy itself, >>> but >>> otherwise you can treat the underlying hardware just like any other node. >>> >> >>> >>> 2. Get rid of the oVirt appliance completely, and just provide >>> instructions/better scripts for installing all of the oVirt software >>> directly on >>> the host. Then the host runs the WUI, and you don't need to protect any >>> "special" guests. >> >> >> >> >> We have a first cut of an appliance definition of the ovirt appliance at >> [1]. With this, we could look at a couple of other alternatives: >> >> 1) Make the recipe availble with instructions to build it via the >> appliance tools on their machines either from the public mirrors or local >> media. >> 2) Use the recipe to configure an existing bare metal machine. >> 3) Set up a public cobbler server to "koan up" a new appliance. Again, >> this would be from the public mirrors. >> >> 1 and 3 _could_ save bandwidth based on using public mirrors. 1 could save >> more if they have media handy. >> >> 2 would be really cool, but the current recipes do not control packages. >> We would need to work up a new "package" recipe, and then apply the one >> from [1]. >> >> -- bk >> >> [1] http://git.et.redhat.com/?p=acex.git;a=tree;f=ovirt/appliances/ovirt; > > > Just following up. Would this approach help out? If so, how can I help > getting folks to use the new puppet based recipe? Please no. Some people aren't big fans of puppet and prefer something with the flexibility of "everything is a text file". If you mainly manage similar Linux hosts, puppet is annoying to setup and maintain. Treat everything like a text file and it is vastly less complex: http://code.google.com/p/spine-mgmt/ I used this as an admin on the backend of ticketmaster.com. It works like a champ. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From apevec at redhat.com Mon Aug 18 15:38:37 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 18 Aug 2008 17:38:37 +0200 Subject: [Ovirt-devel] Lighter-weight "developer" setup In-Reply-To: <48A98D01.7020001@redhat.com> References: <489C5391.6050602@redhat.com> <48A1D7A3.8050007@redhat.com> <48A98D01.7020001@redhat.com> Message-ID: <48A9977D.4030507@redhat.com> Bryan Kearney wrote: > Bryan Kearney wrote: >> Chris Lalancette wrote: >> >>> 1. Still have an oVirt appliance that has everything installed >>> inside of it. >>> Then, this appliance "manages" the host that it is actually running >>> on, and can >>> install additional guests alongside the appliance. You need to >>> protect the >>> oVirt appliance a little bit so you don't accidentally destroy >>> itself, but >>> otherwise you can treat the underlying hardware just like any other >>> node. >>> >> >>> >>> 2. Get rid of the oVirt appliance completely, and just provide >>> instructions/better scripts for installing all of the oVirt software >>> directly on >>> the host. Then the host runs the WUI, and you don't need to protect any >>> "special" guests. >> >> >> >> We have a first cut of an appliance definition of the ovirt appliance >> at [1]. With this, we could look at a couple of other alternatives: >> >> 1) Make the recipe availble with instructions to build it via the >> appliance tools on their machines either from the public mirrors or >> local media. >> 2) Use the recipe to configure an existing bare metal machine. >> 3) Set up a public cobbler server to "koan up" a new appliance. Again, >> this would be from the public mirrors. >> >> 1 and 3 _could_ save bandwidth based on using public mirrors. 1 could >> save more if they have media handy. >> >> 2 would be really cool, but the current recipes do not control >> packages. We would need to work up a new "package" recipe, and then >> apply the one from [1]. >> >> -- bk >> >> [1] http://git.et.redhat.com/?p=acex.git;a=tree;f=ovirt/appliances/ovirt; > > > Just following up. Would this approach help out? If so, how can I help > getting folks to use the new puppet based recipe? Yeah, I plan to look into that asap From slinabery at redhat.com Mon Aug 18 16:00:54 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 18 Aug 2008 11:00:54 -0500 Subject: [Ovirt-devel] [PATCH] remove ovirtadmin keytab In-Reply-To: <1219054513-5535-1-git-send-email-apevec@redhat.com> References: <1219054513-5535-1-git-send-email-apevec@redhat.com> Message-ID: <20080818160054.GB2951@redhat.com> On Mon, Aug 18, 2008 at 12:15:13PM +0200, Alan Pevec wrote: > ipa-getkeytab randomizes the password, so it wasn't possible to > login as ovirtadmin using browser basic auth > --- > wui-appliance/wui-devel.ks | 5 +---- > 1 files changed, 1 insertions(+), 4 deletions(-) Works for me, ACK. From apevec at redhat.com Mon Aug 18 20:15:28 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 18 Aug 2008 22:15:28 +0200 Subject: [Ovirt-devel] [PATCH] remove ovirtadmin keytab Message-ID: <1219090528-7510-1-git-send-email-apevec@redhat.com> ipa-getkeytab randomizes the password, so it wasn't possible to login as ovirtadmin using browser basic auth --- wui-appliance/wui-devel.ks | 5 +---- wui/scripts/ovirt-wui-install | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 5729b60..66927be 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -45,7 +45,6 @@ principal=ovirtadmin realm=PRIV.OVIRT.ORG password=ovirt cron_file=/etc/cron.hourly/ovirtadmin.cron -ktab_file=/usr/share/ovirt-wui/ovirtadmin.tab # automatically refresh the kerberos ticket every hour (we'll create the # principal on first-boot) @@ -53,7 +52,7 @@ cat > $cron_file << EOF #!/bin/bash export PATH=/usr/kerberos/bin:$PATH kdestroy -kinit -k -t $ktab_file $principal@$realm +echo $password | kinit $principal@$realm EOF chmod 755 $cron_file @@ -128,7 +127,6 @@ sed -e "s, at cron_file@,$cron_file," \ -e "s, at principal@,$principal," \ -e "s, at realm@,$realm," \ -e "s, at password@,$password,g" \ - -e "s, at ktab_file@,$ktab_file," \ > $first_run_file << \EOF #!/bin/bash # @@ -175,7 +173,6 @@ LDAP # make ovitadmin also an IPA admin ipa-modgroup -a ovirtadmin admins ipa-moduser --setattr krbPasswordExpiration=19700101000000Z @principal@ - ipa-getkeytab -s management.priv.ovirt.org -p @principal@ -k @ktab_file@ @cron_file@ ) > /var/log/ovirt-wui-dev-first-run.log 2>&1 diff --git a/wui/scripts/ovirt-wui-install b/wui/scripts/ovirt-wui-install index c39364c..8580134 100755 --- a/wui/scripts/ovirt-wui-install +++ b/wui/scripts/ovirt-wui-install @@ -189,10 +189,8 @@ mkdir -p log rake db:migrate cd - -if [ -f ${OVIRT_DIR}/ovirtadmin.tab ]; then - ${OVIRT_DIR}/script/grant_admin_privileges ovirtadmin - [ $? != 0 ] && echo "Failed to grant ovirtadmin privileges" && exit 1 -fi +${OVIRT_DIR}/script/grant_admin_privileges ovirtadmin +[ $? != 0 ] && echo "Failed to grant ovirtadmin privileges" && exit 1 ovirt-add-host $(hostname) ${OVIRT_DIR}/ovirt.keytab -- 1.5.4.1 From jim at meyering.net Mon Aug 18 20:17:24 2008 From: jim at meyering.net (Jim Meyering) Date: Mon, 18 Aug 2008 22:17:24 +0200 Subject: [Ovirt-devel] [PATCH] protect against shell meta-characters in working directory name Message-ID: <87ej4m2i3v.fsf@rho.meyering.net> I doubt anyone using these tools will be clueless enough to do so from a polluted (e.g., space-containing) working directory, but better safe than sorry. And if someone ever does, this way we won't get a bug report about it. Untested. >From dcb8024406297dc33c95c2b924a6f61d7ec27792 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 18 Aug 2008 22:13:06 +0200 Subject: [PATCH] protect against shell meta-characters in working directory name --- ovirt-host-creator/Makefile | 2 +- wui-appliance/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index eba1f15..bd30f49 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -18,7 +18,7 @@ ovirt.iso: ovirt.ks common-install.ks common-pkgs.ks common-post.ks repos.ks ../common/rpm-compare.py GE 0 livecd-tools 017.1 1 mkdir -p tmp livecd-creator --skip-minimize -c ovirt.ks -f ovirt \ - --tmpdir=$$(pwd)/tmp --cache=$(YUMCACHE) + --tmpdir="$$(pwd)/tmp" --cache="$(YUMCACHE)" tar: clean ovirt.iso mkdir -p $(NV) diff --git a/wui-appliance/Makefile b/wui-appliance/Makefile index 760b6ff..ba57aea 100644 --- a/wui-appliance/Makefile +++ b/wui-appliance/Makefile @@ -8,7 +8,7 @@ appliance-compressed: $(NAME)-sda.qcow $(NAME)-sda.raw: wui-devel.ks common-install.ks common-pkgs.ks common-post.ks repos.ks mkdir -p tmp appliance-creator --config wui-devel.ks --name $(NAME) \ - --tmpdir=$$(pwd)/tmp --cache=$(YUMCACHE) + --tmpdir="$$(pwd)/tmp" --cache="$(YUMCACHE)" $(NAME)-sda.qcow: $(NAME)-sda.raw # FIXME add --compress option to appliance-creator -- 1.6.0.4.g750768 From slinabery at redhat.com Mon Aug 18 20:38:28 2008 From: slinabery at redhat.com (Steve Linabery) Date: Mon, 18 Aug 2008 15:38:28 -0500 Subject: [Ovirt-devel] [PATCH] remove ovirtadmin keytab In-Reply-To: <1219090528-7510-1-git-send-email-apevec@redhat.com> References: <1219090528-7510-1-git-send-email-apevec@redhat.com> Message-ID: <20080818203828.GB24240@redhat.com> On Mon, Aug 18, 2008 at 10:15:28PM +0200, Alan Pevec wrote: > ipa-getkeytab randomizes the password, so it wasn't possible to > login as ovirtadmin using browser basic auth > --- > wui-appliance/wui-devel.ks | 5 +---- > wui/scripts/ovirt-wui-install | 6 ++---- > 2 files changed, 3 insertions(+), 8 deletions(-) > > diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks > index 5729b60..66927be 100644 > --- a/wui-appliance/wui-devel.ks > +++ b/wui-appliance/wui-devel.ks > @@ -45,7 +45,6 @@ principal=ovirtadmin > realm=PRIV.OVIRT.ORG > password=ovirt > cron_file=/etc/cron.hourly/ovirtadmin.cron > -ktab_file=/usr/share/ovirt-wui/ovirtadmin.tab > > # automatically refresh the kerberos ticket every hour (we'll create the > # principal on first-boot) > @@ -53,7 +52,7 @@ cat > $cron_file << EOF > #!/bin/bash > export PATH=/usr/kerberos/bin:$PATH > kdestroy > -kinit -k -t $ktab_file $principal@$realm > +echo $password | kinit $principal@$realm > EOF > chmod 755 $cron_file > > @@ -128,7 +127,6 @@ sed -e "s, at cron_file@,$cron_file," \ > -e "s, at principal@,$principal," \ > -e "s, at realm@,$realm," \ > -e "s, at password@,$password,g" \ > - -e "s, at ktab_file@,$ktab_file," \ > > $first_run_file << \EOF > #!/bin/bash > # > @@ -175,7 +173,6 @@ LDAP > # make ovitadmin also an IPA admin > ipa-modgroup -a ovirtadmin admins > ipa-moduser --setattr krbPasswordExpiration=19700101000000Z @principal@ > - ipa-getkeytab -s management.priv.ovirt.org -p @principal@ -k @ktab_file@ > @cron_file@ > > ) > /var/log/ovirt-wui-dev-first-run.log 2>&1 > diff --git a/wui/scripts/ovirt-wui-install b/wui/scripts/ovirt-wui-install > index c39364c..8580134 100755 > --- a/wui/scripts/ovirt-wui-install > +++ b/wui/scripts/ovirt-wui-install > @@ -189,10 +189,8 @@ mkdir -p log > rake db:migrate > cd - > > -if [ -f ${OVIRT_DIR}/ovirtadmin.tab ]; then > - ${OVIRT_DIR}/script/grant_admin_privileges ovirtadmin > - [ $? != 0 ] && echo "Failed to grant ovirtadmin privileges" && exit 1 > -fi > +${OVIRT_DIR}/script/grant_admin_privileges ovirtadmin > +[ $? != 0 ] && echo "Failed to grant ovirtadmin privileges" && exit 1 > > ovirt-add-host $(hostname) ${OVIRT_DIR}/ovirt.keytab > > -- > 1.5.4.1 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel OK, this one *really* works for me. ACK. I see no other instance of the string "ovirtadmin.tab" in the source tree. From dpierce at redhat.com Mon Aug 18 20:48:42 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 18 Aug 2008 16:48:42 -0400 Subject: [Ovirt-devel] [PATCH] Enables the generation of a configuration file for a managed node. Message-ID: <1219092522-10167-1-git-send-email-dpierce@redhat.com> The configuration is generated from the contents of the nics table. The managed node then downloads that configuration after completing the identification and applies those changes. What this needs is integration with the WUI itself, to generate the file when the admin modifies the NIC for the host. Signed-off-by: Darryl L. Pierce --- ovirt-managed-node/src/gather.c | 2 + ovirt-managed-node/src/ovirt-identify-node.h | 1 + ovirt-managed-node/src/protocol.c | 3 +- wui/src/host-browser/host-browser.rb | 582 ++++++++++---------- wui/src/host-browser/test-host-browser-identify.rb | 490 +++++++++-------- wui/src/lib/managed_node_configuration.rb | 58 ++ .../test/unit/managed_node_configuration_test.rb | 88 +++ 7 files changed, 692 insertions(+), 532 deletions(-) create mode 100644 wui/src/lib/managed_node_configuration.rb create mode 100644 wui/src/test/unit/managed_node_configuration_test.rb diff --git a/ovirt-managed-node/src/gather.c b/ovirt-managed-node/src/gather.c index 39be6fd..7fa0992 100644 --- a/ovirt-managed-node/src/gather.c +++ b/ovirt-managed-node/src/gather.c @@ -205,6 +205,8 @@ get_nic_data(char *nic, nic_info_ptr nic_info) interface = libhal_device_get_property_string(hal_ctx, nic, "net.interface", &dbus_error); + snprintf(nic_info->interface_name, BUFFER_LENGTH, "%s", interface); + bzero(&ifr, sizeof(struct ifreq)); sockfd = socket(AF_INET, SOCK_DGRAM, 0); diff --git a/ovirt-managed-node/src/ovirt-identify-node.h b/ovirt-managed-node/src/ovirt-identify-node.h index c595891..b2814fa 100644 --- a/ovirt-managed-node/src/ovirt-identify-node.h +++ b/ovirt-managed-node/src/ovirt-identify-node.h @@ -67,6 +67,7 @@ typedef struct _nic_info { char mac_address[BUFFER_LENGTH]; char bandwidth[BUFFER_LENGTH]; char ip_address[BUFFER_LENGTH]; + char interface_name[BUFFER_LENGTH]; struct _nic_info* next; } t_nic_info; diff --git a/ovirt-managed-node/src/protocol.c b/ovirt-managed-node/src/protocol.c index 131bb38..d5c5fac 100644 --- a/ovirt-managed-node/src/protocol.c +++ b/ovirt-managed-node/src/protocol.c @@ -181,7 +181,8 @@ send_nic_details(void) if (!(get_text("NICINFO?")) && (!send_value("MAC", current->mac_address)) && - (!send_value("BANDWIDTH", current->bandwidth))) { + (!send_value("BANDWIDTH", current->bandwidth)) && + (!send_value("IFACE_NAME", current->interface_name))) { send_text("ENDNIC"); result = get_text("ACK NIC"); } diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 881b2ae..26981fd 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -39,361 +39,369 @@ $logfile = '/var/log/ovirt-wui/host-browser.log' # about the node and then updates the list of active nodes for the WUI. # class HostBrowser - attr_accessor :logfile - attr_accessor :keytab_dir - attr_accessor :keytab_filename - - def initialize(session) - @session = session - @log_prefix = "[#{session.peeraddr[3]}] " - @keytab_dir = '/usr/share/ipa/html/' - end - - # Ensures the conversation starts properly. - # - def begin_conversation - puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) - @session.write("HELLO?\n") - - response = @session.readline.chomp - raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" - end - - # Retrieves the mode request from the remote system. - # - def get_mode - puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) - @session.write("MODE?\n") - response = @session.readline.chomp - puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + attr_accessor :logfile + attr_accessor :keytab_dir + attr_accessor :keytab_filename + + def initialize(connection) + @connection = connection + @log_prefix = "[#{connection.peeraddr[3]}] " + @keytab_dir = '/usr/share/ipa/html/' + end + + # Ensures the conversation starts properly. + # + def begin_conversation + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) + @connection.write("HELLO?\n") + + response = @connection.readline.chomp + raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" + end + + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @connection.write("MODE?\n") + response = @connection.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + + # Requests node information from the remote system. + # + def get_remote_info + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) + result = Hash.new + result['HOSTNAME'] = @connection.peeraddr[2] + result['IPADDR'] = @connection.peeraddr[3] + + @connection.write("INFO?\n") + + loop do + info = @connection.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 - response - end + cpu_info << cpu + + when "NIC" + nic = get_nic_info + nic_info = result['NICINFO'] - # Requests node information from the remote system. - # - def get_remote_info - puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) - result = Hash.new - result['HOSTNAME'] = @session.peeraddr[2] - result['IPADDR'] = @session.peeraddr[3] + if(nic_info == nil) + nic_info = Array.new + result['NICINFO'] = nic_info + end - @session.write("INFO?\n") + nic_info << nic + + else + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - loop do - info = @session.readline.chomp + key, value = info.split("=") - puts "Received info='#{info}'" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - break if info == "ENDINFO" + @connection.write("ACK #{key}\n") + end + end - case info - when "CPU" - cpu = get_cpu_info - cpu_info = result['CPUINFO'] + return result + end - if(cpu_info == nil) - cpu_info = Array.new - result['CPUINFO'] = cpu_info - end + # Extracts CPU details from the managed node. + # + def get_cpu_info + puts "Begin receiving CPU details" - cpu_info << cpu - when "NIC" - nic = get_nic_info - nic_info = result['NICINFO'] + result = Hash.new - if(nic_info == nil) - nic_info = Array.new - result['NICINFO'] = nic_info - end + @connection.write("CPUINFO?\n") - nic_info << nic - else + loop do + info = @connection.readline.chomp - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + break if info == "ENDCPU" - key, value = info.split("=") + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + key, value = info.split("=") - @session.write("ACK #{key}\n") - end - end + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts CPU details from the managed node. - # - def get_cpu_info - puts "Begin receiving CPU details" + @connection.write("ACK CPU\n"); - result = Hash.new + return result + end - @session.write("CPUINFO?\n") + # Extracts NIC details from the managed node. + # + def get_nic_info + puts "Begin receiving NIC details" - loop do - info = @session.readline.chomp + result = Hash.new - break if info == "ENDCPU" + @connection.write("NICINFO?\n") - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + loop do + info = @connection.readline.chomp - key, value = info.split("=") + break if info == "ENDNIC" - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - @session.write("ACK #{key}\n") - end + key, value = info.split("=") - @session.write("ACK CPU\n"); + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts NIC details from the managed node. - # - def get_nic_info - puts "Begin receiving NIC details" - - result = Hash.new - - @session.write("NICINFO?\n") + @connection.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 + + nic_info.each do |nic| + ensure_present(nic, 'MAC') + ensure_present(nic, 'BANDWIDTH') + ensure_present(nic, 'IFACE_NAME') + end - loop do - info = @session.readline.chomp + 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 - break if info == "ENDNIC" + # 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.collect do |cpu| + detail = Cpu.new( + "cpu_number" => cpu['CPUNUM'], + "core_number" => cpu['CORENUM]'], + "number_of_cores" => cpu['NUMCORES'], + "vendor" => cpu['VENDOR'], + "model" => cpu['MODEL'], + "family" => cpu['FAMILY'], + "cpuid_level" => cpu['CPUIDLVL'], + "speed" => cpu['SPEED'], + "cache" => cpu['CACHE'], + "flags" => cpu['FLAGS']) + + host.cpus << detail + end - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + # 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 - key, value = info.split("=") + puts "Updating NIC records for the node" + nics = Array.new - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + host.nics.collect do |nic| + found = false - @session.write("ACK #{key}\n") + nic_info.collect do |detail| + # if we have a match, then update the database and remove + # the received data to avoid creating a dupe later + if detail['MAC'] == nic.mac + nic_info.delete(detail) end + end - @session.write("ACK NIC\n"); - - return result + # if the record wasn't found, then remove it from the database + unless found + host.nics.delete(nic) + nic.destroy + end 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 + # iterate over any nics left and create new records for them. - 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 + nic_info.collect do |nic| + puts "Creating a new nic..." + detail = Nic.new( + 'mac' => nic['MAC'], + 'bandwidth' => nic['BANDWIDTH'], + 'iface_name' => nic['IFACE_NAME'], + 'usage_type' => 1) - # 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.collect do |cpu| - detail = Cpu.new( - "cpu_number" => cpu['CPUNUM'], - "core_number" => cpu['CORENUM]'], - "number_of_cores" => cpu['NUMCORES'], - "vendor" => cpu['VENDOR'], - "model" => cpu['MODEL'], - "family" => cpu['FAMILY'], - "cpuid_level" => cpu['CPUIDLVL'], - "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 - - host.nics.collect do |nic| - found = false - - nic_info.collect do |detail| - # if we have a match, then update the database and remove - # the received data to avoid creating a dupe later - if detail['MAC'] == nic.mac - nic_info.delete(detail) - end - end - - # if the record wasn't found, then remove it from the database - unless found - host.nics.delete(nic) - nic.destroy - end - end - - # iterate over any nics left and create new records for them. - - nic_info.collect do |nic| - puts "Creating a new nic..." - detail = Nic.new( - 'mac' => nic['MAC'], - 'bandwidth' => nic['BANDWIDTH'], - 'usage_type' => 1) + host.nics << detail + end - host.nics << detail - end + host.save! - host.save! + return host + end - return host - end + # Creates a keytab if one is needed, returning the filename. + # + def create_keytab(hostname, ipaddress, krb5_arg = nil) + krb5 = krb5_arg || Krb5.new - # Creates a keytab if one is needed, returning the filename. - # - def create_keytab(hostname, ipaddress, krb5_arg = nil) - krb5 = krb5_arg || Krb5.new + default_realm = krb5.get_default_realm + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' + @keytab_filename = @keytab_dir + outfile - default_realm = krb5.get_default_realm - libvirt_princ = 'libvirt/' + hostname + '@' + default_realm - outfile = ipaddress + '-libvirt.tab' - @keytab_filename = @keytab_dir + outfile + # TODO need a way to test this portion + unless (defined? TESTING) || File.exists?(@keytab_filename) + # TODO replace with Kr5Auth when it supports admin actions + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) + kadmin_local('addprinc -randkey ' + libvirt_princ) + kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) - # TODO need a way to test this portion - unless (defined? TESTING) || File.exists?(@keytab_filename) - # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) - kadmin_local('addprinc -randkey ' + libvirt_princ) - kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) + File.chmod(0644, at keytab_filename) + end - File.chmod(0644, at keytab_filename) - end + hostname = `hostname -f`.chomp - hostname = `hostname -f`.chomp + @connection.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") - @session.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") + response = @connection.readline.chomp - response = @session.readline.chomp + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end - raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" - end + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation - puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + @connection.write("BYE\n"); + end - @session.write("BYE\n"); - end + private - 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 - # 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) - system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") - end + # Executes an external program to support the keytab function. + # + def kadmin_local(command) + system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") + end end def entry_point(server) - while(session = server.accept) - child = fork do - remote = session.peeraddr[2] - - 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 + while(session = server.accept) + child = fork do + remote = session.peeraddr[2] - begin - browser = HostBrowser.new(session) + puts "Connected to #{remote}" unless defined?(TESTING) - 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 + # This is needed because we just forked a new process + # which now needs its own connection to the database. + database_connect - browser.end_conversation - rescue Exception => error - session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" unless defined?(TESTING) - end + begin + browser = HostBrowser.new(session) - puts "Disconnected from #{remote}" unless defined?(TESTING) + 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 - Process.detach(child) + browser.end_conversation + rescue Exception => error + session.write("ERROR #{error.message}\n") + puts "ERROR #{error.message}" unless defined?(TESTING) + end + + puts "Disconnected from #{remote}" unless defined?(TESTING) end + + Process.detach(child) + end end unless defined?(TESTING) - # The main entry point. - # - unless ARGV[0] == "-n" - daemonize - # redirect output to the log - STDOUT.reopen $logfile, 'a' - STDERR.reopen STDOUT - end - - server = TCPServer.new("",12120) - entry_point(server) + # The main entry point. + # + unless ARGV[0] == "-n" + daemonize + # redirect output to the log + STDOUT.reopen $logfile, 'a' + STDERR.reopen STDOUT + end + + server = TCPServer.new("",12120) + entry_point(server) end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb index 7e672ce..e25497f 100755 --- a/wui/src/host-browser/test-host-browser-identify.rb +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -27,257 +27,259 @@ TESTING=true require 'host-browser' class TestHostBrowser < Test::Unit::TestCase - def setup - @session = flexmock('session') - @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @browser = HostBrowser.new(@session) - @browser.logfile = './unit-test.log' - - # default host info - @host_info = {} - @host_info['UUID'] = 'node1' - @host_info['IPADDR'] = '192.168.2.2' - @host_info['HOSTNAME'] = '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 \ + 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 \ + @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'][0] = {} - @host_info['NICINFO'][0]['MAC'] = '00:11:22:33:44:55' - @host_info['NICINFO'][0]['BANDWIDTH'] = '100' - - @host_info['NICINFO'][1] = {} - @host_info['NICINFO'][1]['MAC'] = '00:77:11:77:19:65' - @host_info['NICINFO'][1]['BANDWIDTH'] = '100' - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "IDENTIFY\n" } - - result = @browser.get_mode() - - assert_equal "IDENTIFY", result, "method did not return the right value" - end - - # Ensures that, if an info field is missing a key, the server raises - # an exception. - # - def test_get_info_with_missing_key - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "=value1\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if an info field is missing a value, the server raises - # an exception. - # - def test_get_info_with_missing_value - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if the server gets a poorly formed ending statement, it - # raises an exception. - # - def test_get_info_with_invalid_end - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDIFNO\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that a well-formed transaction works as expected. - # - def test_get_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?("IPADDR") - assert info.include?("HOSTNAME") - 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 the browser can properly parse the CPU details. - # - def test_parse_cpu_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - - # CPU 0 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - # CPU 1 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key3=value3\n" } - @session.should_receive(:write).with("ACK key3\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key4=value4\n" } - @session.should_receive(:write).with("ACK key4\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "NIC\n" } - @session.should_receive(:write).with("NICINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDNIC\n" } - @session.should_receive(:write).with("ACK NIC\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain four keys" - assert info.include?("NICINFO") - end + @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 4,info.keys.size, "Should contain four keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + 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 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 3,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 3,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 diff --git a/wui/src/lib/managed_node_configuration.rb b/wui/src/lib/managed_node_configuration.rb new file mode 100644 index 0000000..0bb34f0 --- /dev/null +++ b/wui/src/lib/managed_node_configuration.rb @@ -0,0 +1,58 @@ +# +# 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. + +# +ManagedNodeConfiguration+ takes in the description for a managed node and, +# from that, generates the configuration file that is consumed the next time +# the managed node starts up. +# + +require 'stringio' + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), "../") + +class ManagedNodeConfiguration + @network_interfaces + @file = nil + + attr_accessor :hostname + + def initialize(host) + @host = host + end + + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' + + def generate + result = StringIO.new + + @host.nics.each do |nic| + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/DEVICE #{nic.iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/BRIDGE #{nic.bridge}" if nic.bridge + end + + result.puts "save" + + result.string + end +end + diff --git a/wui/src/test/unit/managed_node_configuration_test.rb b/wui/src/test/unit/managed_node_configuration_test.rb new file mode 100644 index 0000000..bfbfdac --- /dev/null +++ b/wui/src/test/unit/managed_node_configuration_test.rb @@ -0,0 +1,88 @@ +# +# 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. + +$:.unshift File.join(File.dirname(__FILE__),'..','lib') + +# require File.dirname(__FILE__) + '/../test/test_helper' +require 'test/unit' +require 'managed_node_configuration' +require 'dutils' + +# Performs unit tests on the +ManagedNodeConfiguration+ class. +# +class ManagedNodeConfigurationTest < Test::Unit::TestCase + def setup + @host = Host.new + @config = ManagedNodeConfiguration.new(@host) + end + + + # Ensures that network interfaces uses DHCP when no IP address is specified. + # + def test_generate_with_no_ip_address + @host.nics << Nic.new(:iface_name => 'eth0') + + expected = < 'eth0', :ip_addr => '192.168.2.1') + + expected = < 'eth0', :bridge => 'ovirtbr0') + + expected = < References: <87ej4m2i3v.fsf@rho.meyering.net> Message-ID: <48AAA515.4070005@redhat.com> Jim Meyering wrote: > I doubt anyone using these tools will be clueless enough to do > so from a polluted (e.g., space-containing) working directory, > but better safe than sorry. And if someone ever does, > this way we won't get a bug report about it. > > Untested. Thanks for the watchful eye :) tested, ACK >>From dcb8024406297dc33c95c2b924a6f61d7ec27792 Mon Sep 17 00:00:00 2001 why >From ? Does mailman still have problem with git patches? From jim at meyering.net Tue Aug 19 11:40:44 2008 From: jim at meyering.net (Jim Meyering) Date: Tue, 19 Aug 2008 13:40:44 +0200 Subject: [Ovirt-devel] [PATCH] protect against shell meta-characters in working directory name In-Reply-To: <48AAA515.4070005@redhat.com> (Alan Pevec's message of "Tue, 19 Aug 2008 12:48:53 +0200") References: <87ej4m2i3v.fsf@rho.meyering.net> <48AAA515.4070005@redhat.com> Message-ID: <877iad1bcz.fsf@rho.meyering.net> Alan Pevec wrote: > Jim Meyering wrote: >> I doubt anyone using these tools will be clueless enough to do >> so from a polluted (e.g., space-containing) working directory, >> but better safe than sorry. And if someone ever does, >> this way we won't get a bug report about it. >> >> Untested. > > Thanks for the watchful eye :) > > tested, ACK Thanks! Pushed. >>>From dcb8024406297dc33c95c2b924a6f61d7ec27792 Mon Sep 17 00:00:00 2001 > > why >From ? > Does mailman still have problem with git patches? No. That's done by mail clients to comply with the mail spec that treats "From " at the beginning of a line specially. From dpierce at redhat.com Tue Aug 19 15:26:36 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 19 Aug 2008 11:26:36 -0400 Subject: [Ovirt-devel] [PATCH] Enables the generation of a configuration file for a managed node. Message-ID: <1219159596-15153-1-git-send-email-dpierce@redhat.com> The configuration is generated from the contents of the nics table. The managed node then downloads that configuration after completing the identification and applies those changes. Signed-off-by: Darryl L. Pierce --- ovirt-managed-node/src/gather.c | 2 + ovirt-managed-node/src/ovirt-identify-node.h | 1 + ovirt-managed-node/src/protocol.c | 3 +- wui/src/app/controllers/application.rb | 2 +- wui/src/app/controllers/nic_controller.rb | 13 +- wui/src/config/environment.rb | 2 + wui/src/db/migrate/015_add_iface_name_to_nics.rb | 9 + wui/src/host-browser/host-browser.rb | 582 ++++++++++---------- wui/src/host-browser/test-host-browser-identify.rb | 490 +++++++++-------- wui/src/lib/managed_node_configuration.rb | 49 ++ .../test/unit/managed_node_configuration_test.rb | 87 +++ 11 files changed, 706 insertions(+), 534 deletions(-) create mode 100644 wui/src/db/migrate/015_add_iface_name_to_nics.rb create mode 100644 wui/src/lib/managed_node_configuration.rb create mode 100644 wui/src/test/unit/managed_node_configuration_test.rb diff --git a/ovirt-managed-node/src/gather.c b/ovirt-managed-node/src/gather.c index 39be6fd..7fa0992 100644 --- a/ovirt-managed-node/src/gather.c +++ b/ovirt-managed-node/src/gather.c @@ -205,6 +205,8 @@ get_nic_data(char *nic, nic_info_ptr nic_info) interface = libhal_device_get_property_string(hal_ctx, nic, "net.interface", &dbus_error); + snprintf(nic_info->interface_name, BUFFER_LENGTH, "%s", interface); + bzero(&ifr, sizeof(struct ifreq)); sockfd = socket(AF_INET, SOCK_DGRAM, 0); diff --git a/ovirt-managed-node/src/ovirt-identify-node.h b/ovirt-managed-node/src/ovirt-identify-node.h index c595891..b2814fa 100644 --- a/ovirt-managed-node/src/ovirt-identify-node.h +++ b/ovirt-managed-node/src/ovirt-identify-node.h @@ -67,6 +67,7 @@ typedef struct _nic_info { char mac_address[BUFFER_LENGTH]; char bandwidth[BUFFER_LENGTH]; char ip_address[BUFFER_LENGTH]; + char interface_name[BUFFER_LENGTH]; struct _nic_info* next; } t_nic_info; diff --git a/ovirt-managed-node/src/protocol.c b/ovirt-managed-node/src/protocol.c index 131bb38..d5c5fac 100644 --- a/ovirt-managed-node/src/protocol.c +++ b/ovirt-managed-node/src/protocol.c @@ -181,7 +181,8 @@ send_nic_details(void) if (!(get_text("NICINFO?")) && (!send_value("MAC", current->mac_address)) && - (!send_value("BANDWIDTH", current->bandwidth))) { + (!send_value("BANDWIDTH", current->bandwidth)) && + (!send_value("IFACE_NAME", current->interface_name))) { send_text("ENDNIC"); result = get_text("ACK NIC"); } diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index d653171..b27ddbe 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base before_filter :is_logged_in def is_logged_in - redirect_to (:controller => "login", :action => "login") unless get_login_user + redirect_to(:controller => "login", :action => "login") unless get_login_user end def get_login_user diff --git a/wui/src/app/controllers/nic_controller.rb b/wui/src/app/controllers/nic_controller.rb index 85d4315..fef7c5b 100644 --- a/wui/src/app/controllers/nic_controller.rb +++ b/wui/src/app/controllers/nic_controller.rb @@ -18,9 +18,10 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class NicController < ApplicationController + after_filter :generate_configuration, :only => [:create, :edit, :update, :destroy] # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], - :redirect_to => { :controller => 'dashboard' } + :redirect_to => { :controller => 'dashboard' } def show set_perms(@perm_obj) @@ -44,8 +45,18 @@ class NicController < ApplicationController def destroy end + + def generate_configuration + host = Host.find_by_id(params[:host_id]) + + File.open("#{MANAGED_NODE_CONFIGURATION_DIR}/#{host.hostname}", "w") do |file| + # write the nic configuration + file.puts ManagedNodeConfiguration.generate(host) + end + end private + #filter methods def pre_new flash[:notice] = 'Network Interfaces may not be edited via the web UI' diff --git a/wui/src/config/environment.rb b/wui/src/config/environment.rb index ff6f6e8..57fd461 100644 --- a/wui/src/config/environment.rb +++ b/wui/src/config/environment.rb @@ -80,3 +80,5 @@ end require 'gettext/rails' gem 'cobbler' require 'cobbler' + +MANAGED_NODE_CONFIGURATION_DIR = '/var/www/html/ovirt-cfgdb' \ No newline at end of file diff --git a/wui/src/db/migrate/015_add_iface_name_to_nics.rb b/wui/src/db/migrate/015_add_iface_name_to_nics.rb new file mode 100644 index 0000000..f1fb28d --- /dev/null +++ b/wui/src/db/migrate/015_add_iface_name_to_nics.rb @@ -0,0 +1,9 @@ +class AddIfaceNameToNics < ActiveRecord::Migration + def self.up + add_column :nics, :iface_name, :string, :limit => 10 + end + + def self.down + remove_column :nics, :iface_name + end +end diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 881b2ae..26981fd 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -39,361 +39,369 @@ $logfile = '/var/log/ovirt-wui/host-browser.log' # about the node and then updates the list of active nodes for the WUI. # class HostBrowser - attr_accessor :logfile - attr_accessor :keytab_dir - attr_accessor :keytab_filename - - def initialize(session) - @session = session - @log_prefix = "[#{session.peeraddr[3]}] " - @keytab_dir = '/usr/share/ipa/html/' - end - - # Ensures the conversation starts properly. - # - def begin_conversation - puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) - @session.write("HELLO?\n") - - response = @session.readline.chomp - raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" - end - - # Retrieves the mode request from the remote system. - # - def get_mode - puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) - @session.write("MODE?\n") - response = @session.readline.chomp - puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + attr_accessor :logfile + attr_accessor :keytab_dir + attr_accessor :keytab_filename + + def initialize(connection) + @connection = connection + @log_prefix = "[#{connection.peeraddr[3]}] " + @keytab_dir = '/usr/share/ipa/html/' + end + + # Ensures the conversation starts properly. + # + def begin_conversation + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) + @connection.write("HELLO?\n") + + response = @connection.readline.chomp + raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" + end + + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @connection.write("MODE?\n") + response = @connection.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + + # Requests node information from the remote system. + # + def get_remote_info + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) + result = Hash.new + result['HOSTNAME'] = @connection.peeraddr[2] + result['IPADDR'] = @connection.peeraddr[3] + + @connection.write("INFO?\n") + + loop do + info = @connection.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 - response - end + cpu_info << cpu + + when "NIC" + nic = get_nic_info + nic_info = result['NICINFO'] - # Requests node information from the remote system. - # - def get_remote_info - puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) - result = Hash.new - result['HOSTNAME'] = @session.peeraddr[2] - result['IPADDR'] = @session.peeraddr[3] + if(nic_info == nil) + nic_info = Array.new + result['NICINFO'] = nic_info + end - @session.write("INFO?\n") + nic_info << nic + + else + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - loop do - info = @session.readline.chomp + key, value = info.split("=") - puts "Received info='#{info}'" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - break if info == "ENDINFO" + @connection.write("ACK #{key}\n") + end + end - case info - when "CPU" - cpu = get_cpu_info - cpu_info = result['CPUINFO'] + return result + end - if(cpu_info == nil) - cpu_info = Array.new - result['CPUINFO'] = cpu_info - end + # Extracts CPU details from the managed node. + # + def get_cpu_info + puts "Begin receiving CPU details" - cpu_info << cpu - when "NIC" - nic = get_nic_info - nic_info = result['NICINFO'] + result = Hash.new - if(nic_info == nil) - nic_info = Array.new - result['NICINFO'] = nic_info - end + @connection.write("CPUINFO?\n") - nic_info << nic - else + loop do + info = @connection.readline.chomp - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + break if info == "ENDCPU" - key, value = info.split("=") + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + key, value = info.split("=") - @session.write("ACK #{key}\n") - end - end + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts CPU details from the managed node. - # - def get_cpu_info - puts "Begin receiving CPU details" + @connection.write("ACK CPU\n"); - result = Hash.new + return result + end - @session.write("CPUINFO?\n") + # Extracts NIC details from the managed node. + # + def get_nic_info + puts "Begin receiving NIC details" - loop do - info = @session.readline.chomp + result = Hash.new - break if info == "ENDCPU" + @connection.write("NICINFO?\n") - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + loop do + info = @connection.readline.chomp - key, value = info.split("=") + break if info == "ENDNIC" - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - @session.write("ACK #{key}\n") - end + key, value = info.split("=") - @session.write("ACK CPU\n"); + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts NIC details from the managed node. - # - def get_nic_info - puts "Begin receiving NIC details" - - result = Hash.new - - @session.write("NICINFO?\n") + @connection.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 + + nic_info.each do |nic| + ensure_present(nic, 'MAC') + ensure_present(nic, 'BANDWIDTH') + ensure_present(nic, 'IFACE_NAME') + end - loop do - info = @session.readline.chomp + 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 - break if info == "ENDNIC" + # 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.collect do |cpu| + detail = Cpu.new( + "cpu_number" => cpu['CPUNUM'], + "core_number" => cpu['CORENUM]'], + "number_of_cores" => cpu['NUMCORES'], + "vendor" => cpu['VENDOR'], + "model" => cpu['MODEL'], + "family" => cpu['FAMILY'], + "cpuid_level" => cpu['CPUIDLVL'], + "speed" => cpu['SPEED'], + "cache" => cpu['CACHE'], + "flags" => cpu['FLAGS']) + + host.cpus << detail + end - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + # 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 - key, value = info.split("=") + puts "Updating NIC records for the node" + nics = Array.new - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + host.nics.collect do |nic| + found = false - @session.write("ACK #{key}\n") + nic_info.collect do |detail| + # if we have a match, then update the database and remove + # the received data to avoid creating a dupe later + if detail['MAC'] == nic.mac + nic_info.delete(detail) end + end - @session.write("ACK NIC\n"); - - return result + # if the record wasn't found, then remove it from the database + unless found + host.nics.delete(nic) + nic.destroy + end 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 + # iterate over any nics left and create new records for them. - 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 + nic_info.collect do |nic| + puts "Creating a new nic..." + detail = Nic.new( + 'mac' => nic['MAC'], + 'bandwidth' => nic['BANDWIDTH'], + 'iface_name' => nic['IFACE_NAME'], + 'usage_type' => 1) - # 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.collect do |cpu| - detail = Cpu.new( - "cpu_number" => cpu['CPUNUM'], - "core_number" => cpu['CORENUM]'], - "number_of_cores" => cpu['NUMCORES'], - "vendor" => cpu['VENDOR'], - "model" => cpu['MODEL'], - "family" => cpu['FAMILY'], - "cpuid_level" => cpu['CPUIDLVL'], - "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 - - host.nics.collect do |nic| - found = false - - nic_info.collect do |detail| - # if we have a match, then update the database and remove - # the received data to avoid creating a dupe later - if detail['MAC'] == nic.mac - nic_info.delete(detail) - end - end - - # if the record wasn't found, then remove it from the database - unless found - host.nics.delete(nic) - nic.destroy - end - end - - # iterate over any nics left and create new records for them. - - nic_info.collect do |nic| - puts "Creating a new nic..." - detail = Nic.new( - 'mac' => nic['MAC'], - 'bandwidth' => nic['BANDWIDTH'], - 'usage_type' => 1) + host.nics << detail + end - host.nics << detail - end + host.save! - host.save! + return host + end - return host - end + # Creates a keytab if one is needed, returning the filename. + # + def create_keytab(hostname, ipaddress, krb5_arg = nil) + krb5 = krb5_arg || Krb5.new - # Creates a keytab if one is needed, returning the filename. - # - def create_keytab(hostname, ipaddress, krb5_arg = nil) - krb5 = krb5_arg || Krb5.new + default_realm = krb5.get_default_realm + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' + @keytab_filename = @keytab_dir + outfile - default_realm = krb5.get_default_realm - libvirt_princ = 'libvirt/' + hostname + '@' + default_realm - outfile = ipaddress + '-libvirt.tab' - @keytab_filename = @keytab_dir + outfile + # TODO need a way to test this portion + unless (defined? TESTING) || File.exists?(@keytab_filename) + # TODO replace with Kr5Auth when it supports admin actions + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) + kadmin_local('addprinc -randkey ' + libvirt_princ) + kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) - # TODO need a way to test this portion - unless (defined? TESTING) || File.exists?(@keytab_filename) - # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) - kadmin_local('addprinc -randkey ' + libvirt_princ) - kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) + File.chmod(0644, at keytab_filename) + end - File.chmod(0644, at keytab_filename) - end + hostname = `hostname -f`.chomp - hostname = `hostname -f`.chomp + @connection.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") - @session.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") + response = @connection.readline.chomp - response = @session.readline.chomp + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end - raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" - end + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation - puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + @connection.write("BYE\n"); + end - @session.write("BYE\n"); - end + private - 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 - # 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) - system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") - end + # Executes an external program to support the keytab function. + # + def kadmin_local(command) + system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") + end end def entry_point(server) - while(session = server.accept) - child = fork do - remote = session.peeraddr[2] - - 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 + while(session = server.accept) + child = fork do + remote = session.peeraddr[2] - begin - browser = HostBrowser.new(session) + puts "Connected to #{remote}" unless defined?(TESTING) - 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 + # This is needed because we just forked a new process + # which now needs its own connection to the database. + database_connect - browser.end_conversation - rescue Exception => error - session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" unless defined?(TESTING) - end + begin + browser = HostBrowser.new(session) - puts "Disconnected from #{remote}" unless defined?(TESTING) + 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 - Process.detach(child) + browser.end_conversation + rescue Exception => error + session.write("ERROR #{error.message}\n") + puts "ERROR #{error.message}" unless defined?(TESTING) + end + + puts "Disconnected from #{remote}" unless defined?(TESTING) end + + Process.detach(child) + end end unless defined?(TESTING) - # The main entry point. - # - unless ARGV[0] == "-n" - daemonize - # redirect output to the log - STDOUT.reopen $logfile, 'a' - STDERR.reopen STDOUT - end - - server = TCPServer.new("",12120) - entry_point(server) + # The main entry point. + # + unless ARGV[0] == "-n" + daemonize + # redirect output to the log + STDOUT.reopen $logfile, 'a' + STDERR.reopen STDOUT + end + + server = TCPServer.new("",12120) + entry_point(server) end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb index 7e672ce..e25497f 100755 --- a/wui/src/host-browser/test-host-browser-identify.rb +++ b/wui/src/host-browser/test-host-browser-identify.rb @@ -27,257 +27,259 @@ TESTING=true require 'host-browser' class TestHostBrowser < Test::Unit::TestCase - def setup - @session = flexmock('session') - @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @browser = HostBrowser.new(@session) - @browser.logfile = './unit-test.log' - - # default host info - @host_info = {} - @host_info['UUID'] = 'node1' - @host_info['IPADDR'] = '192.168.2.2' - @host_info['HOSTNAME'] = '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 \ + 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 \ + @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'][0] = {} - @host_info['NICINFO'][0]['MAC'] = '00:11:22:33:44:55' - @host_info['NICINFO'][0]['BANDWIDTH'] = '100' - - @host_info['NICINFO'][1] = {} - @host_info['NICINFO'][1]['MAC'] = '00:77:11:77:19:65' - @host_info['NICINFO'][1]['BANDWIDTH'] = '100' - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "IDENTIFY\n" } - - result = @browser.get_mode() - - assert_equal "IDENTIFY", result, "method did not return the right value" - end - - # Ensures that, if an info field is missing a key, the server raises - # an exception. - # - def test_get_info_with_missing_key - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "=value1\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if an info field is missing a value, the server raises - # an exception. - # - def test_get_info_with_missing_value - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if the server gets a poorly formed ending statement, it - # raises an exception. - # - def test_get_info_with_invalid_end - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDIFNO\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that a well-formed transaction works as expected. - # - def test_get_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?("IPADDR") - assert info.include?("HOSTNAME") - 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 the browser can properly parse the CPU details. - # - def test_parse_cpu_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - - # CPU 0 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - # CPU 1 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key3=value3\n" } - @session.should_receive(:write).with("ACK key3\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key4=value4\n" } - @session.should_receive(:write).with("ACK key4\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "NIC\n" } - @session.should_receive(:write).with("NICINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDNIC\n" } - @session.should_receive(:write).with("ACK NIC\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain four keys" - assert info.include?("NICINFO") - end + @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 4,info.keys.size, "Should contain four keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + 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 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 3,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 3,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 diff --git a/wui/src/lib/managed_node_configuration.rb b/wui/src/lib/managed_node_configuration.rb new file mode 100644 index 0000000..aa8c711 --- /dev/null +++ b/wui/src/lib/managed_node_configuration.rb @@ -0,0 +1,49 @@ +# +# 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. + +# +ManagedNodeConfiguration+ takes in the description for a managed node and, +# from that, generates the configuration file that is consumed the next time +# the managed node starts up. +# + +require 'stringio' + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), "../") + +class ManagedNodeConfiguration + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' + + def self.generate(host) + result = StringIO.new + + host.nics.each do |nic| + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/DEVICE #{nic.iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/BRIDGE #{nic.bridge}" if nic.bridge + end + + result.puts "save" + + result.string + end +end + diff --git a/wui/src/test/unit/managed_node_configuration_test.rb b/wui/src/test/unit/managed_node_configuration_test.rb new file mode 100644 index 0000000..01b3fe4 --- /dev/null +++ b/wui/src/test/unit/managed_node_configuration_test.rb @@ -0,0 +1,87 @@ +# +# 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. + +$:.unshift File.join(File.dirname(__FILE__),'..','lib') + +# require File.dirname(__FILE__) + '/../test/test_helper' +require 'test/unit' +require 'managed_node_configuration' +require 'dutils' + +# Performs unit tests on the +ManagedNodeConfiguration+ class. +# +class ManagedNodeConfigurationTest < Test::Unit::TestCase + def setup + @host = Host.new + end + + + # Ensures that network interfaces uses DHCP when no IP address is specified. + # + def test_generate_with_no_ip_address + @host.nics << Nic.new(:iface_name => 'eth0') + + expected = < 'eth0', :ip_addr => '192.168.2.1') + + expected = < 'eth0', :bridge => 'ovirtbr0') + + expected = < Attached in this patch is an interface test used to run through and verify the vm creation process, as well as updates to the test fixtures to support it. Also included is a small fix to storage_volume.rb for a bug which I discovered, where upon retrieval of the storage pool list when creating a new vm, the include_vm variable (created / passed in via the vm controller only when the vm id specified by the interface is not null, as it is when we're creating (eg. not updating) a vm) is null and thus an exception is being thrown. I've been attempting to figure out how to proceed with the start vm test, but I am still a bit confused about how to satisfy the storage requirements for a vm I want to start. With this patch, all the resources / quotas requirements are satisfied, and the new vm is created, but this is perpretually in the 'pending' state, and I'm not sure how to start / instruct taskomatic to change this. Furthermore I don't see a newly created vm on node 3, and am still unsure about the correct storage pool / volume parameters to set in the test fixtures in order to set the vm's storage to something it can access. Any help / pointers would be appreciated. The selenium test that is included should also serve as a good guide for anyone else wanting to write interface tests of your own. A tutorial on how to do this is still on my list of things to do, but this guide shows all the basics, eg clicking on links, filling in input, waiting for elements to load, verifying page contents, etc. The complete selenium ruby API is available here http://release.openqa.org/selenium-remote-control/0.9.2/doc/ruby/ and the selenium reference here http://selenium-core.openqa.org/reference.html -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: vm-creation-test.patch Type: text/x-patch Size: 6301 bytes Desc: not available URL: From jim at meyering.net Wed Aug 20 08:11:03 2008 From: jim at meyering.net (Jim Meyering) Date: Wed, 20 Aug 2008 10:11:03 +0200 Subject: [Ovirt-devel] [PATCH] Enables the generation of a configuration file for a managed node. In-Reply-To: <1219092522-10167-1-git-send-email-dpierce@redhat.com> (Darryl L. Pierce's message of "Mon, 18 Aug 2008 16:48:42 -0400") References: <1219092522-10167-1-git-send-email-dpierce@redhat.com> Message-ID: <87zln8t8bs.fsf@rho.meyering.net> "Darryl L. Pierce" wrote: > The configuration is generated from the contents of the nics table. The > managed node then downloads that configuration after completing the > identification and applies those changes. > > What this needs is integration with the WUI itself, to generate the file > when the admin modifies the NIC for the host. Hi Darryl, I was just glancing through this and stumbled up on what looks like a bug: ... > diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb ... > + # 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 > > - key, value = info.split("=") > + puts "Updating NIC records for the node" > + nics = Array.new > > - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) > - result[key] = value > + host.nics.collect do |nic| > + found = false > > - @session.write("ACK #{key}\n") > + nic_info.collect do |detail| > + # if we have a match, then update the database and remove > + # the received data to avoid creating a dupe later > + if detail['MAC'] == nic.mac > + nic_info.delete(detail) I think you want to insert "found = true" here. > end > + end > > - @session.write("ACK NIC\n"); > - > - return result > + # if the record wasn't found, then remove it from the database > + unless found > + host.nics.delete(nic) > + nic.destroy > + end > end From dpierce at redhat.com Wed Aug 20 11:49:17 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 20 Aug 2008 07:49:17 -0400 Subject: [Ovirt-devel] Re: Enables the generation of a configuration file for a managed node. In-Reply-To: <87zln8t8bs.fsf@rho.meyering.net> References: <1219092522-10167-1-git-send-email-dpierce@redhat.com> <87zln8t8bs.fsf@rho.meyering.net> Message-ID: <20080820114917.GA3587@redhat.com> +++ Jim Meyering [20/08/08 10:11 +0200]: >I was just glancing through this and >stumbled up on what looks like a bug: Good catch. My test cases didn't hit a problem, so that hid the problem. :) -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 redhat.com Wed Aug 20 12:06:10 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 20 Aug 2008 14:06:10 +0200 Subject: [Ovirt-devel] [PATCH] move repos.ks.in to common Message-ID: <1219233971-13916-1-git-send-email-apevec@redhat.com> --- common/repos.ks.in | 3 +++ ovirt-host-creator/Makefile | 4 ++-- ovirt-host-creator/repos.ks.in | 3 --- wui-appliance/Makefile | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 common/repos.ks.in delete mode 100644 ovirt-host-creator/repos.ks.in diff --git a/common/repos.ks.in b/common/repos.ks.in new file mode 100644 index 0000000..79a8cef --- /dev/null +++ b/common/repos.ks.in @@ -0,0 +1,3 @@ +repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch +repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch +repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile index bd30f49..ae212ae 100644 --- a/ovirt-host-creator/Makefile +++ b/ovirt-host-creator/Makefile @@ -11,8 +11,8 @@ clean: distclean: clean rm -rf *.iso repos.ks rpm-build -repos.ks: repos.ks.in - cp repos.ks.in repos.ks +repos.ks: ../common/repos.ks.in + cp ../common/repos.ks.in repos.ks ovirt.iso: ovirt.ks common-install.ks common-pkgs.ks common-post.ks repos.ks ../common/rpm-compare.py GE 0 livecd-tools 017.1 1 diff --git a/ovirt-host-creator/repos.ks.in b/ovirt-host-creator/repos.ks.in deleted file mode 100644 index 79a8cef..0000000 --- a/ovirt-host-creator/repos.ks.in +++ /dev/null @@ -1,3 +0,0 @@ -repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch -repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch -repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch diff --git a/wui-appliance/Makefile b/wui-appliance/Makefile index ba57aea..ea0bb61 100644 --- a/wui-appliance/Makefile +++ b/wui-appliance/Makefile @@ -14,8 +14,8 @@ $(NAME)-sda.qcow: $(NAME)-sda.raw # FIXME add --compress option to appliance-creator qemu-img convert -c $(NAME)-sda.raw -O qcow2 $(NAME)-sda.qcow -repos.ks: repos.ks.in - cp repos.ks.in repos.ks +repos.ks: ../common/repos.ks.in + cp ../common/repos.ks.in repos.ks clean: rm -f repos.ks -- 1.5.4.1 From apevec at redhat.com Wed Aug 20 12:06:11 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 20 Aug 2008 14:06:11 +0200 Subject: [Ovirt-devel] [PATCH] change remote ovirt YUM repo name to avoid collision with locally built YUM repo In-Reply-To: <1219233971-13916-1-git-send-email-apevec@redhat.com> References: <1219233971-13916-1-git-send-email-apevec@redhat.com> Message-ID: <1219233971-13916-2-git-send-email-apevec@redhat.com> --- common/repos.ks.in | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/common/repos.ks.in b/common/repos.ks.in index 79a8cef..ba5ee20 100644 --- a/common/repos.ks.in +++ b/common/repos.ks.in @@ -1,3 +1,3 @@ repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch -repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch +repo --name=ovirt-org --baseurl=http://ovirt.org/repos/ovirt/9/$basearch -- 1.5.4.1 From pmyers at redhat.com Wed Aug 20 12:41:33 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 20 Aug 2008 08:41:33 -0400 Subject: [Ovirt-devel] [PATCH] change remote ovirt YUM repo name to avoid collision with locally built YUM repo In-Reply-To: <1219233971-13916-2-git-send-email-apevec@redhat.com> References: <1219233971-13916-1-git-send-email-apevec@redhat.com> <1219233971-13916-2-git-send-email-apevec@redhat.com> Message-ID: <48AC10FD.9000506@redhat.com> Alan Pevec wrote: > --- > common/repos.ks.in | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/common/repos.ks.in b/common/repos.ks.in > index 79a8cef..ba5ee20 100644 > --- a/common/repos.ks.in > +++ b/common/repos.ks.in > @@ -1,3 +1,3 @@ > repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch > repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch > -repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch > +repo --name=ovirt-org --baseurl=http://ovirt.org/repos/ovirt/9/$basearch ACK -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From pmyers at redhat.com Wed Aug 20 12:43:20 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 20 Aug 2008 08:43:20 -0400 Subject: [Ovirt-devel] [PATCH] move repos.ks.in to common In-Reply-To: <1219233971-13916-1-git-send-email-apevec@redhat.com> References: <1219233971-13916-1-git-send-email-apevec@redhat.com> Message-ID: <48AC1168.7010809@redhat.com> Alan Pevec wrote: > --- > common/repos.ks.in | 3 +++ > ovirt-host-creator/Makefile | 4 ++-- > ovirt-host-creator/repos.ks.in | 3 --- > wui-appliance/Makefile | 4 ++-- > 4 files changed, 7 insertions(+), 7 deletions(-) > create mode 100644 common/repos.ks.in > delete mode 100644 ovirt-host-creator/repos.ks.in > > diff --git a/common/repos.ks.in b/common/repos.ks.in > new file mode 100644 > index 0000000..79a8cef > --- /dev/null > +++ b/common/repos.ks.in > @@ -0,0 +1,3 @@ > +repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch > +repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch > +repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch > diff --git a/ovirt-host-creator/Makefile b/ovirt-host-creator/Makefile > index bd30f49..ae212ae 100644 > --- a/ovirt-host-creator/Makefile > +++ b/ovirt-host-creator/Makefile > @@ -11,8 +11,8 @@ clean: > distclean: clean > rm -rf *.iso repos.ks rpm-build > > -repos.ks: repos.ks.in > - cp repos.ks.in repos.ks > +repos.ks: ../common/repos.ks.in > + cp ../common/repos.ks.in repos.ks > > ovirt.iso: ovirt.ks common-install.ks common-pkgs.ks common-post.ks repos.ks > ../common/rpm-compare.py GE 0 livecd-tools 017.1 1 > diff --git a/ovirt-host-creator/repos.ks.in b/ovirt-host-creator/repos.ks.in > deleted file mode 100644 > index 79a8cef..0000000 > --- a/ovirt-host-creator/repos.ks.in > +++ /dev/null > @@ -1,3 +0,0 @@ > -repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch > -repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch > -repo --name=ovirt --baseurl=http://ovirt.org/repos/ovirt/9/$basearch > diff --git a/wui-appliance/Makefile b/wui-appliance/Makefile > index ba57aea..ea0bb61 100644 > --- a/wui-appliance/Makefile > +++ b/wui-appliance/Makefile > @@ -14,8 +14,8 @@ $(NAME)-sda.qcow: $(NAME)-sda.raw > # FIXME add --compress option to appliance-creator > qemu-img convert -c $(NAME)-sda.raw -O qcow2 $(NAME)-sda.qcow > > -repos.ks: repos.ks.in > - cp repos.ks.in repos.ks > +repos.ks: ../common/repos.ks.in > + cp ../common/repos.ks.in repos.ks > > clean: > rm -f repos.ks ACK -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From dpierce at redhat.com Wed Aug 20 13:10:43 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 20 Aug 2008 09:10:43 -0400 Subject: [Ovirt-devel] [PATCH] Enables the generation of a configuration file for a managed node. Message-ID: <1219237843-9748-1-git-send-email-dpierce@redhat.com> The configuration is generated from the contents of the nics table. The managed node then downloads that configuration after completing the identification and applies those changes. Signed-off-by: Darryl L. Pierce --- ovirt-managed-node/src/gather.c | 2 + ovirt-managed-node/src/ovirt-identify-node.h | 1 + ovirt-managed-node/src/protocol.c | 3 +- wui/src/app/controllers/application.rb | 2 +- wui/src/app/controllers/nic_controller.rb | 13 +- wui/src/config/environment.rb | 2 + wui/src/db/migrate/015_add_iface_name_to_nics.rb | 9 + wui/src/host-browser/host-browser.rb | 583 ++++++++++---------- wui/src/host-browser/test-host-browser-awake.rb | 94 ---- wui/src/host-browser/test-host-browser-identify.rb | 283 ---------- wui/src/lib/managed_node_configuration.rb | 49 ++ wui/src/test/fixtures/nics.yml | 3 + wui/src/test/unit/host_browser_awaken_test.rb | 96 ++++ wui/src/test/unit/host_browser_identify_test.rb | 304 ++++++++++ .../test/unit/managed_node_configuration_test.rb | 87 +++ 15 files changed, 864 insertions(+), 667 deletions(-) create mode 100644 wui/src/db/migrate/015_add_iface_name_to_nics.rb delete mode 100755 wui/src/host-browser/test-host-browser-awake.rb delete mode 100755 wui/src/host-browser/test-host-browser-identify.rb create mode 100644 wui/src/lib/managed_node_configuration.rb create mode 100755 wui/src/test/unit/host_browser_awaken_test.rb create mode 100755 wui/src/test/unit/host_browser_identify_test.rb create mode 100644 wui/src/test/unit/managed_node_configuration_test.rb diff --git a/ovirt-managed-node/src/gather.c b/ovirt-managed-node/src/gather.c index 39be6fd..7fa0992 100644 --- a/ovirt-managed-node/src/gather.c +++ b/ovirt-managed-node/src/gather.c @@ -205,6 +205,8 @@ get_nic_data(char *nic, nic_info_ptr nic_info) interface = libhal_device_get_property_string(hal_ctx, nic, "net.interface", &dbus_error); + snprintf(nic_info->interface_name, BUFFER_LENGTH, "%s", interface); + bzero(&ifr, sizeof(struct ifreq)); sockfd = socket(AF_INET, SOCK_DGRAM, 0); diff --git a/ovirt-managed-node/src/ovirt-identify-node.h b/ovirt-managed-node/src/ovirt-identify-node.h index c595891..b2814fa 100644 --- a/ovirt-managed-node/src/ovirt-identify-node.h +++ b/ovirt-managed-node/src/ovirt-identify-node.h @@ -67,6 +67,7 @@ typedef struct _nic_info { char mac_address[BUFFER_LENGTH]; char bandwidth[BUFFER_LENGTH]; char ip_address[BUFFER_LENGTH]; + char interface_name[BUFFER_LENGTH]; struct _nic_info* next; } t_nic_info; diff --git a/ovirt-managed-node/src/protocol.c b/ovirt-managed-node/src/protocol.c index 131bb38..d5c5fac 100644 --- a/ovirt-managed-node/src/protocol.c +++ b/ovirt-managed-node/src/protocol.c @@ -181,7 +181,8 @@ send_nic_details(void) if (!(get_text("NICINFO?")) && (!send_value("MAC", current->mac_address)) && - (!send_value("BANDWIDTH", current->bandwidth))) { + (!send_value("BANDWIDTH", current->bandwidth)) && + (!send_value("IFACE_NAME", current->interface_name))) { send_text("ENDNIC"); result = get_text("ACK NIC"); } diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index d653171..b27ddbe 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base before_filter :is_logged_in def is_logged_in - redirect_to (:controller => "login", :action => "login") unless get_login_user + redirect_to(:controller => "login", :action => "login") unless get_login_user end def get_login_user diff --git a/wui/src/app/controllers/nic_controller.rb b/wui/src/app/controllers/nic_controller.rb index 85d4315..fef7c5b 100644 --- a/wui/src/app/controllers/nic_controller.rb +++ b/wui/src/app/controllers/nic_controller.rb @@ -18,9 +18,10 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class NicController < ApplicationController + after_filter :generate_configuration, :only => [:create, :edit, :update, :destroy] # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], - :redirect_to => { :controller => 'dashboard' } + :redirect_to => { :controller => 'dashboard' } def show set_perms(@perm_obj) @@ -44,8 +45,18 @@ class NicController < ApplicationController def destroy end + + def generate_configuration + host = Host.find_by_id(params[:host_id]) + + File.open("#{MANAGED_NODE_CONFIGURATION_DIR}/#{host.hostname}", "w") do |file| + # write the nic configuration + file.puts ManagedNodeConfiguration.generate(host) + end + end private + #filter methods def pre_new flash[:notice] = 'Network Interfaces may not be edited via the web UI' diff --git a/wui/src/config/environment.rb b/wui/src/config/environment.rb index ff6f6e8..57fd461 100644 --- a/wui/src/config/environment.rb +++ b/wui/src/config/environment.rb @@ -80,3 +80,5 @@ end require 'gettext/rails' gem 'cobbler' require 'cobbler' + +MANAGED_NODE_CONFIGURATION_DIR = '/var/www/html/ovirt-cfgdb' \ No newline at end of file diff --git a/wui/src/db/migrate/015_add_iface_name_to_nics.rb b/wui/src/db/migrate/015_add_iface_name_to_nics.rb new file mode 100644 index 0000000..f1fb28d --- /dev/null +++ b/wui/src/db/migrate/015_add_iface_name_to_nics.rb @@ -0,0 +1,9 @@ +class AddIfaceNameToNics < ActiveRecord::Migration + def self.up + add_column :nics, :iface_name, :string, :limit => 10 + end + + def self.down + remove_column :nics, :iface_name + end +end diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 881b2ae..d3098ce 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -39,361 +39,370 @@ $logfile = '/var/log/ovirt-wui/host-browser.log' # about the node and then updates the list of active nodes for the WUI. # class HostBrowser - attr_accessor :logfile - attr_accessor :keytab_dir - attr_accessor :keytab_filename - - def initialize(session) - @session = session - @log_prefix = "[#{session.peeraddr[3]}] " - @keytab_dir = '/usr/share/ipa/html/' - end - - # Ensures the conversation starts properly. - # - def begin_conversation - puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) - @session.write("HELLO?\n") - - response = @session.readline.chomp - raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" - end - - # Retrieves the mode request from the remote system. - # - def get_mode - puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) - @session.write("MODE?\n") - response = @session.readline.chomp - puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + attr_accessor :logfile + attr_accessor :keytab_dir + attr_accessor :keytab_filename + + def initialize(connection) + @connection = connection + @log_prefix = "[#{connection.peeraddr[3]}] " + @keytab_dir = '/usr/share/ipa/html/' + end + + # Ensures the conversation starts properly. + # + def begin_conversation + puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) + @connection.write("HELLO?\n") + + response = @connection.readline.chomp + raise Exception.new("received #{response}, expected HELLO!") unless response == "HELLO!" + end + + # Retrieves the mode request from the remote system. + # + def get_mode + puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + @connection.write("MODE?\n") + response = @connection.readline.chomp + puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + + response + end + + # Requests node information from the remote system. + # + def get_remote_info + puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) + result = Hash.new + result['HOSTNAME'] = @connection.peeraddr[2] + result['IPADDR'] = @connection.peeraddr[3] + + @connection.write("INFO?\n") + + loop do + info = @connection.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 - response - end + cpu_info << cpu + + when "NIC" + nic = get_nic_info + nic_info = result['NICINFO'] - # Requests node information from the remote system. - # - def get_remote_info - puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) - result = Hash.new - result['HOSTNAME'] = @session.peeraddr[2] - result['IPADDR'] = @session.peeraddr[3] + if(nic_info == nil) + nic_info = Array.new + result['NICINFO'] = nic_info + end - @session.write("INFO?\n") + nic_info << nic + + else + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - loop do - info = @session.readline.chomp + key, value = info.split("=") - puts "Received info='#{info}'" + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - break if info == "ENDINFO" + @connection.write("ACK #{key}\n") + end + end - case info - when "CPU" - cpu = get_cpu_info - cpu_info = result['CPUINFO'] + return result + end - if(cpu_info == nil) - cpu_info = Array.new - result['CPUINFO'] = cpu_info - end + # Extracts CPU details from the managed node. + # + def get_cpu_info + puts "Begin receiving CPU details" - cpu_info << cpu - when "NIC" - nic = get_nic_info - nic_info = result['NICINFO'] + result = Hash.new - if(nic_info == nil) - nic_info = Array.new - result['NICINFO'] = nic_info - end + @connection.write("CPUINFO?\n") - nic_info << nic - else + loop do + info = @connection.readline.chomp - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + break if info == "ENDCPU" - key, value = info.split("=") + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + key, value = info.split("=") - @session.write("ACK #{key}\n") - end - end + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts CPU details from the managed node. - # - def get_cpu_info - puts "Begin receiving CPU details" + @connection.write("ACK CPU\n"); - result = Hash.new + return result + end - @session.write("CPUINFO?\n") + # Extracts NIC details from the managed node. + # + def get_nic_info + puts "Begin receiving NIC details" - loop do - info = @session.readline.chomp + result = Hash.new - break if info == "ENDCPU" + @connection.write("NICINFO?\n") - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + loop do + info = @connection.readline.chomp - key, value = info.split("=") + break if info == "ENDNIC" - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ - @session.write("ACK #{key}\n") - end + key, value = info.split("=") - @session.write("ACK CPU\n"); + puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + result[key] = value - return result + @connection.write("ACK #{key}\n") end - # Extracts NIC details from the managed node. - # - def get_nic_info - puts "Begin receiving NIC details" - - result = Hash.new - - @session.write("NICINFO?\n") + @connection.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 + + nic_info.each do |nic| + ensure_present(nic, 'MAC') + ensure_present(nic, 'BANDWIDTH') + ensure_present(nic, 'IFACE_NAME') + end - loop do - info = @session.readline.chomp + 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 - break if info == "ENDNIC" + # 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.collect do |cpu| + detail = Cpu.new( + "cpu_number" => cpu['CPUNUM'], + "core_number" => cpu['CORENUM]'], + "number_of_cores" => cpu['NUMCORES'], + "vendor" => cpu['VENDOR'], + "model" => cpu['MODEL'], + "family" => cpu['FAMILY'], + "cpuid_level" => cpu['CPUIDLVL'], + "speed" => cpu['SPEED'], + "cache" => cpu['CACHE'], + "flags" => cpu['FLAGS']) + + host.cpus << detail + end - raise Exception.new("ERRINFO! Excepted key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]/ + # 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 - key, value = info.split("=") + puts "Updating NIC records for the node" + nics = Array.new - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) - result[key] = value + host.nics.collect do |nic| + found = false - @session.write("ACK #{key}\n") + nic_info.collect do |detail| + # if we have a match, then update the database and remove + # the received data to avoid creating a dupe later + if detail['MAC'] == nic.mac + nic_info.delete(detail) + found = true end + end - @session.write("ACK NIC\n"); - - return result + # if the record wasn't found, then remove it from the database + unless found + host.nics.delete(nic) + nic.destroy + end 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 + # iterate over any nics left and create new records for them. - 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 + nic_info.collect do |nic| + puts "Creating a new nic..." + detail = Nic.new( + 'mac' => nic['MAC'], + 'bandwidth' => nic['BANDWIDTH'], + 'iface_name' => nic['IFACE_NAME'], + 'usage_type' => 1) - # 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.collect do |cpu| - detail = Cpu.new( - "cpu_number" => cpu['CPUNUM'], - "core_number" => cpu['CORENUM]'], - "number_of_cores" => cpu['NUMCORES'], - "vendor" => cpu['VENDOR'], - "model" => cpu['MODEL'], - "family" => cpu['FAMILY'], - "cpuid_level" => cpu['CPUIDLVL'], - "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 - - host.nics.collect do |nic| - found = false - - nic_info.collect do |detail| - # if we have a match, then update the database and remove - # the received data to avoid creating a dupe later - if detail['MAC'] == nic.mac - nic_info.delete(detail) - end - end - - # if the record wasn't found, then remove it from the database - unless found - host.nics.delete(nic) - nic.destroy - end - end - - # iterate over any nics left and create new records for them. - - nic_info.collect do |nic| - puts "Creating a new nic..." - detail = Nic.new( - 'mac' => nic['MAC'], - 'bandwidth' => nic['BANDWIDTH'], - 'usage_type' => 1) + host.nics << detail + end - host.nics << detail - end + host.save! - host.save! + return host + end - return host - end + # Creates a keytab if one is needed, returning the filename. + # + def create_keytab(hostname, ipaddress, krb5_arg = nil) + krb5 = krb5_arg || Krb5.new - # Creates a keytab if one is needed, returning the filename. - # - def create_keytab(hostname, ipaddress, krb5_arg = nil) - krb5 = krb5_arg || Krb5.new + default_realm = krb5.get_default_realm + libvirt_princ = 'libvirt/' + hostname + '@' + default_realm + outfile = ipaddress + '-libvirt.tab' + @keytab_filename = @keytab_dir + outfile - default_realm = krb5.get_default_realm - libvirt_princ = 'libvirt/' + hostname + '@' + default_realm - outfile = ipaddress + '-libvirt.tab' - @keytab_filename = @keytab_dir + outfile + # TODO need a way to test this portion + unless (defined? TESTING) || File.exists?(@keytab_filename) + # TODO replace with Kr5Auth when it supports admin actions + puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) + kadmin_local('addprinc -randkey ' + libvirt_princ) + kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) - # TODO need a way to test this portion - unless (defined? TESTING) || File.exists?(@keytab_filename) - # TODO replace with Kr5Auth when it supports admin actions - puts "Writing keytab file: #{@keytab_filename}" unless defined?(TESTING) - kadmin_local('addprinc -randkey ' + libvirt_princ) - kadmin_local('ktadd -k ' + @keytab_filename + ' ' + libvirt_princ) + File.chmod(0644, at keytab_filename) + end - File.chmod(0644, at keytab_filename) - end + hostname = `hostname -f`.chomp - hostname = `hostname -f`.chomp + @connection.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") - @session.write("KTAB http://#{hostname}/ipa/config/#{outfile}\n") + response = @connection.readline.chomp - response = @session.readline.chomp + raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" + end - raise Exception.new("ERRINFO! No keytab acknowledgement") unless response == "ACK" - end + # Ends the conversation, notifying the user of the key version number. + # + def end_conversation + puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) - # Ends the conversation, notifying the user of the key version number. - # - def end_conversation - puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + @connection.write("BYE\n"); + end - @session.write("BYE\n"); - end + private - 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 - # 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) - system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") - end + # Executes an external program to support the keytab function. + # + def kadmin_local(command) + system("/usr/kerberos/sbin/kadmin.local -q '" + command + "'") + end end def entry_point(server) - while(session = server.accept) - child = fork do - remote = session.peeraddr[2] - - 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 + while(session = server.accept) + child = fork do + remote = session.peeraddr[2] - begin - browser = HostBrowser.new(session) + puts "Connected to #{remote}" unless defined?(TESTING) - 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 + # This is needed because we just forked a new process + # which now needs its own connection to the database. + database_connect - browser.end_conversation - rescue Exception => error - session.write("ERROR #{error.message}\n") - puts "ERROR #{error.message}" unless defined?(TESTING) - end + begin + browser = HostBrowser.new(session) - puts "Disconnected from #{remote}" unless defined?(TESTING) + 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 - Process.detach(child) + browser.end_conversation + rescue Exception => error + session.write("ERROR #{error.message}\n") + puts "ERROR #{error.message}" unless defined?(TESTING) + end + + puts "Disconnected from #{remote}" unless defined?(TESTING) end + + Process.detach(child) + end end unless defined?(TESTING) - # The main entry point. - # - unless ARGV[0] == "-n" - daemonize - # redirect output to the log - STDOUT.reopen $logfile, 'a' - STDERR.reopen STDOUT - end - - server = TCPServer.new("",12120) - entry_point(server) + # The main entry point. + # + unless ARGV[0] == "-n" + daemonize + # redirect output to the log + STDOUT.reopen $logfile, 'a' + STDERR.reopen STDOUT + end + + server = TCPServer.new("",12120) + entry_point(server) end diff --git a/wui/src/host-browser/test-host-browser-awake.rb b/wui/src/host-browser/test-host-browser-awake.rb deleted file mode 100755 index 02e9146..0000000 --- a/wui/src/host-browser/test-host-browser-awake.rb +++ /dev/null @@ -1,94 +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. - -require File.dirname(__FILE__) + '/../test/test_helper' -require 'test/unit' -require 'flexmock/test_unit' - -TESTING=true - -require 'host-browser' - -# +TestHostBrowserAwaken+ -class TestHostBrowserAwaken < Test::Unit::TestCase - - def setup - @session = flexmock('session') - @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @krb5 = flexmock('krb5') - - @browser = HostBrowser.new(@session) - @browser.logfile = './unit-test.log' - @browser.keytab_dir = '/var/temp/' - end - - # Ensures that the server raises an exception when it receives an - # improper handshake response. - # - def test_begin_conversation_with_improper_response_to_greeting - @session.should_receive(:write).with("HELLO?\n").once().returns { |greeting| greeting.length } - @session.should_receive(:readline).once().returns { "SUP?" } - - assert_raise(Exception) { @browser.begin_conversation } - end - - # Ensures the server accepts a proper response from the remote system. - # - def test_begin_conversation - @session.should_receive(:write).with("HELLO?\n").once().returns { |greeting| greeting.length } - @session.should_receive(:readline).once().returns { "HELLO!\n" } - - assert_nothing_raised(Exception) { @browser.begin_conversation } - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "AWAKEN\n" } - - result = @browser.get_mode() - - assert_equal "AWAKEN", result, "method did not return the right value" - end - - # Ensures the host browser generates a keytab as expected. - # - def test_create_keytab - @krb5.should_receive(:get_default_realm).once().returns { "ovirt-test-realm" } - servername = `hostname -f`.chomp - @session.should_receive(:write).with("KTAB http://#{servername}/ipa/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ACK\n" } - - assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } - end - - # Ensures that, if a keytab is present and a key version number available, - # the server ends the conversation by returning the key version number. - # - def test_end_conversation - @session.should_receive(:write).with("BYE\n").once().returns { |request| request.length } - - assert_nothing_raised(Exception) { @browser.end_conversation } - end - -end diff --git a/wui/src/host-browser/test-host-browser-identify.rb b/wui/src/host-browser/test-host-browser-identify.rb deleted file mode 100755 index 7e672ce..0000000 --- a/wui/src/host-browser/test-host-browser-identify.rb +++ /dev/null @@ -1,283 +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. - -require File.dirname(__FILE__) + '/../test/test_helper' -require 'test/unit' -require 'flexmock/test_unit' - -TESTING=true - -require 'host-browser' - -class TestHostBrowser < Test::Unit::TestCase - def setup - @session = flexmock('session') - @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } - - @browser = HostBrowser.new(@session) - @browser.logfile = './unit-test.log' - - # default host info - @host_info = {} - @host_info['UUID'] = 'node1' - @host_info['IPADDR'] = '192.168.2.2' - @host_info['HOSTNAME'] = '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'][0] = {} - @host_info['NICINFO'][0]['MAC'] = '00:11:22:33:44:55' - @host_info['NICINFO'][0]['BANDWIDTH'] = '100' - - @host_info['NICINFO'][1] = {} - @host_info['NICINFO'][1]['MAC'] = '00:77:11:77:19:65' - @host_info['NICINFO'][1]['BANDWIDTH'] = '100' - end - - # Ensures that the server is satisfied if the remote system is - # making a wakeup call. - # - def test_get_mode_with_awaken_request - @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "IDENTIFY\n" } - - result = @browser.get_mode() - - assert_equal "IDENTIFY", result, "method did not return the right value" - end - - # Ensures that, if an info field is missing a key, the server raises - # an exception. - # - def test_get_info_with_missing_key - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "=value1\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if an info field is missing a value, the server raises - # an exception. - # - def test_get_info_with_missing_value - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that, if the server gets a poorly formed ending statement, it - # raises an exception. - # - def test_get_info_with_invalid_end - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDIFNO\n" } - - assert_raise(Exception) { @browser.get_remote_info } - end - - # Ensures that a well-formed transaction works as expected. - # - def test_get_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 4,info.keys.size, "Should contain four keys" - assert info.include?("IPADDR") - assert info.include?("HOSTNAME") - 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 the browser can properly parse the CPU details. - # - def test_parse_cpu_info - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - - # CPU 0 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - # CPU 1 - @session.should_receive(:readline).once().returns { "CPU\n" } - @session.should_receive(:write).with("CPUINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key3=value3\n" } - @session.should_receive(:write).with("ACK key3\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key4=value4\n" } - @session.should_receive(:write).with("ACK key4\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDCPU\n" } - @session.should_receive(:write).with("ACK CPU\n").once().returns { |request| request.length } - - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain 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 - @session.should_receive(:write).with("INFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "NIC\n" } - @session.should_receive(:write).with("NICINFO?\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key1=value1\n" } - @session.should_receive(:write).with("ACK key1\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "key2=value2\n" } - @session.should_receive(:write).with("ACK key2\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDNIC\n" } - @session.should_receive(:write).with("ACK NIC\n").once().returns { |request| request.length } - @session.should_receive(:readline).once().returns { "ENDINFO\n" } - - info = @browser.get_remote_info - - assert_equal 3,info.keys.size, "Should contain four keys" - assert info.include?("NICINFO") - end - -end diff --git a/wui/src/lib/managed_node_configuration.rb b/wui/src/lib/managed_node_configuration.rb new file mode 100644 index 0000000..aa8c711 --- /dev/null +++ b/wui/src/lib/managed_node_configuration.rb @@ -0,0 +1,49 @@ +# +# 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. + +# +ManagedNodeConfiguration+ takes in the description for a managed node and, +# from that, generates the configuration file that is consumed the next time +# the managed node starts up. +# + +require 'stringio' + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), "../") + +class ManagedNodeConfiguration + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' + + def self.generate(host) + result = StringIO.new + + host.nics.each do |nic| + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/DEVICE #{nic.iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{nic.iface_name}/BRIDGE #{nic.bridge}" if nic.bridge + end + + result.puts "save" + + result.string + end +end + diff --git a/wui/src/test/fixtures/nics.yml b/wui/src/test/fixtures/nics.yml index 008cfb7..c31714a 100644 --- a/wui/src/test/fixtures/nics.yml +++ b/wui/src/test/fixtures/nics.yml @@ -6,6 +6,7 @@ one: usage_type: '1' bandwidth: 100 host_id: 1 + iface_name: eth0 two: id: 2 mac: 'AA:BB:CC:DD:EE:FF' @@ -13,6 +14,7 @@ two: usage_type: '2' bandwidth: 1000 host_id: 1 + iface_name: eth1 three: id: 3 mac: '00:FF:11:EE:22:DD' @@ -20,3 +22,4 @@ three: usage_type: '1' bandwidth: 10 host_id: 2 + iface_name: eth0 diff --git a/wui/src/test/unit/host_browser_awaken_test.rb b/wui/src/test/unit/host_browser_awaken_test.rb new file mode 100755 index 0000000..5340e01 --- /dev/null +++ b/wui/src/test/unit/host_browser_awaken_test.rb @@ -0,0 +1,96 @@ +#!/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. + +require File.dirname(__FILE__) + '/../test_helper' +require 'test/unit' +require 'flexmock/test_unit' + +TESTING=true + +require 'host-browser' + +# +HostBrowserAwakenTest+ ensures that the host-browser daemon works correctly +# during the identify phase of operation. +# +class HostBrowserAwakenTest < Test::Unit::TestCase + + def setup + @session = flexmock('session') + @session.should_receive(:peeraddr).at_least.once.returns { [nil,nil,nil,"192.168.2.255"] } + + @krb5 = flexmock('krb5') + + @browser = HostBrowser.new(@session) + @browser.logfile = './unit-test.log' + @browser.keytab_dir = '/var/temp/' + end + + # Ensures that the server raises an exception when it receives an + # improper handshake response. + # + def test_begin_conversation_with_improper_response_to_greeting + @session.should_receive(:write).with("HELLO?\n").once().returns { |greeting| greeting.length } + @session.should_receive(:readline).once().returns { "SUP?" } + + assert_raise(Exception) { @browser.begin_conversation } + end + + # Ensures the server accepts a proper response from the remote system. + # + def test_begin_conversation + @session.should_receive(:write).with("HELLO?\n").once().returns { |greeting| greeting.length } + @session.should_receive(:readline).once().returns { "HELLO!\n" } + + assert_nothing_raised(Exception) { @browser.begin_conversation } + end + + # Ensures that the server is satisfied if the remote system is + # making a wakeup call. + # + def test_get_mode_with_awaken_request + @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "AWAKEN\n" } + + result = @browser.get_mode() + + assert_equal "AWAKEN", result, "method did not return the right value" + end + + # Ensures the host browser generates a keytab as expected. + # + def test_create_keytab + @krb5.should_receive(:get_default_realm).once().returns { "ovirt-test-realm" } + servername = `hostname -f`.chomp + @session.should_receive(:write).with("KTAB http://#{servername}/ipa/config/127.0.0.1-libvirt.tab\n").once().returns { |request| request.length } + @session.should_receive(:readline).once().returns { "ACK\n" } + + assert_nothing_raised(Exception) { @browser.create_keytab('localhost','127.0.0.1', at krb5) } + end + + # Ensures that, if a keytab is present and a key version number available, + # the server ends the conversation by returning the key version number. + # + def test_end_conversation + @session.should_receive(:write).with("BYE\n").once().returns { |request| request.length } + + assert_nothing_raised(Exception) { @browser.end_conversation } + end + +end diff --git a/wui/src/test/unit/host_browser_identify_test.rb b/wui/src/test/unit/host_browser_identify_test.rb new file mode 100755 index 0000000..e43507c --- /dev/null +++ b/wui/src/test/unit/host_browser_identify_test.rb @@ -0,0 +1,304 @@ +#!/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. + +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 + 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 4,info.keys.size, "Should contain four keys" + assert info.include?("IPADDR") + assert info.include?("HOSTNAME") + 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 3,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 3,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 diff --git a/wui/src/test/unit/managed_node_configuration_test.rb b/wui/src/test/unit/managed_node_configuration_test.rb new file mode 100644 index 0000000..01b3fe4 --- /dev/null +++ b/wui/src/test/unit/managed_node_configuration_test.rb @@ -0,0 +1,87 @@ +# +# 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. + +$:.unshift File.join(File.dirname(__FILE__),'..','lib') + +# require File.dirname(__FILE__) + '/../test/test_helper' +require 'test/unit' +require 'managed_node_configuration' +require 'dutils' + +# Performs unit tests on the +ManagedNodeConfiguration+ class. +# +class ManagedNodeConfigurationTest < Test::Unit::TestCase + def setup + @host = Host.new + end + + + # Ensures that network interfaces uses DHCP when no IP address is specified. + # + def test_generate_with_no_ip_address + @host.nics << Nic.new(:iface_name => 'eth0') + + expected = < 'eth0', :ip_addr => '192.168.2.1') + + expected = < 'eth0', :bridge => 'ovirtbr0') + + expected = < From: Bryan kearney This patch modifies the appliance building to use the thincrust configuraion engine. There is probably a related commit which pulls the contents of http://git.et.redhat.com/?p=acex.git;a=tree;f=ovirt; into this repo. --- common/repos.ks.in | 1 + ovirt-splash.xpm.gz | Bin 0 -> 51388 bytes wui-appliance/wui-devel.ks | 317 ++++++-------------------------------------- 3 files changed, 40 insertions(+), 278 deletions(-) create mode 100644 ovirt-splash.xpm.gz diff --git a/common/repos.ks.in b/common/repos.ks.in index ba5ee20..8605d6e 100644 --- a/common/repos.ks.in +++ b/common/repos.ks.in @@ -1,3 +1,4 @@ repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch repo --name=ovirt-org --baseurl=http://ovirt.org/repos/ovirt/9/$basearch +repo --name=thincrust --baseurl=http://www.thincrust.net/repo/noarch diff --git a/ovirt-splash.xpm.gz b/ovirt-splash.xpm.gz new file mode 100644 index 0000000000000000000000000000000000000000..8cd25b89b37a8660067bdc5de4f84614d40880ec GIT binary patch literal 51388 zcmV)ZK&!tWiwFoYW0Ob#18;U|a&#?oaBN|7XfAkgZ2;Z4{FyT6GU*`YrU;UT=`k(&m|N5W*@}K{g|MY+SW&X?m_W%B0 z|BwIrKmV8i{r~Zw{>%UJKmULJ_y6br_5b#l^)LV5fAz2b@$dikFMt2<{`N0_{fEE& z^^{<>%aN at IDUToH~;ka|NgK4`1x`A{P_2O{fB at 2>%af} z`1t(z4}bd)fA?C0`cMDx&wu at opC8xbU;pu+{`xGSuu zpNngZ*U#Va`2FfN_|I?GI2`XM*LTOD1FfL#+sDU82H^os{vV!0?m%3DTlvZ%7+>o5 zSC~_*;ZMFizFC&HgR+Y at fGn=1{Mxq*LD&G}9Irdl;xGNIv;1oVbNg$6Yo2xp&hv3! zA#eZvMBpciCi~@2jW&Z?xe^FJ|L=Ab>VNSAF&M!5M%v4t3l`JMfxs>Z-~0;@?&wma zb$-oJhOsK^h9Of|U%;v0NN2=X6$ z)%db1FTF38H?SEUEo{O%YO$OQok?DX>|0zZhp%R2mF&^tgE1^Gkx5Ty4Z6ext) z0^LF?EcEwYwBEjh2lHPGi)-*9;CG-cUQmP^xLdRYgAwNyTo?qXm6%tvGu*k5h?@&N zIuDLoi-iZZ8EnE(;O8)uG936DhZ>1LtQPAX+CA3V)Fc`Sc9BT*f%ZO#y7D0q_Yr{m zXhTmyxbT)=u`6@{_84$wAzrO2XBY|+yDgv=)JJ>F_Q{lsDNCXH32H6P!yCD3owfMvSSbhVF at kcCHD*0Q5K9*$t6y at 1viGcIX zzl^*^J}vr=t`Lk!YCOAGhM_=I5=$jsycR1UXK>z%+8YQgh1dH(r9TBBCCRB60s5$f zqa;s-07->`z9}_*@zJ>NP$57dO7Pg|1&8f>^Pv90miZtU3o{aGfSi75HJE_w{sakv z2G2Oq_;1Pb*wimd^kTI-qYq7ALZXNepie-ztQm!-K??SQ2Io1xf>%=kabhc2e%Kb4 zm)QXr9pP~g?2!wjO&q4ALndhgM}eXdV#R_2$nVXARKRijk+{(6?}5wPCZVD*-V}cX zVuSpeP+WjO at Sy6Dg>Tv^UJxO=KjA)9Uwd!W;dzP1FZG0>M4}Qa4R$O-U=gsZ8bN`d zlAjf16UWm*f`j&hN_ at u)U?`ktcMSx7M;;{o5r8I6Xaq*{_pPf*#1XW+cLJmPG=jLk zVH4n!+ADSU&8iFlp5t##cdKyYQWxS at Q@Dd}v8K at AS37}g<4#DBFGY+rDlxc^wU`Jr zzMBT=0f(u`kewO_Da!y~4Z}(ae`gG4DH3Hk%i~&@q>|iZMKpg^@Arl|BU;#`aiaz& zA;MSmgf4!C|7tzF3IkFS+c0Di0;E at 6p>Vc8A;g(C`o$>=N;O7NW<7Q$lva>djF{7f z3xC7gV?sOl+bim?*wKR!Vk*UeU$Y(j4m?=*e^QAFL}?6 at 48AJK@AL_%BoTUNs3N%o zMiV4%Qi~p`k=QE1@@e&-brm at NR&AHhnwuFgga|*+0zUVpF&^omPksQu+8Z2|3J*H8 zG8|m^dG|?o2T(`U-zgYxdJMTPCmy69*yF+NyVYVtVR=oyU?A_C at m>pobf$Cn_m&bY zo^%B`lP9ezjH2u4vq{ON9(M4N7)_6u_-OQlYy??^(GXIBh%&BW2qy;aG+#S&Vi2Km z9_*M$LEU36-wgPNcsbBn7*MzmW0CAf_fYsfFjzn*E=-<|d;}|o5z{Tc-U{+Xp?DN# z)?N9~Da17uF08 at Xoqn4bj<>1`6Mno6)Ge*D+cO5|{9#sOX3oEY#Vm at 85C#$heseQN|_iN+&9u_%0C{ zps+=RNs)a13^FD at pg6Gm+fgX!JFtfrg_w$;v7#9Y>EkehX%bIEAf5o zs-)!%UK at jwQGn!a8q)gxty;WdK(cBRsB)xDfl-8YNFu`#jOb=f&9EXjd%+S_FjYKR zISvj~MZ(5#$KL$NF7<>$e8}Tn5KkQ%eAvK5aL5w<9c!^r-#5`B at MWqW0Pku>EJln9 z5OD;Fz>iB7G%6GzY7qwD8Zns$Ia(by7}B7w?ZCyLrd^=AF{{qx)^!!o^}N9emY{NB z&y``}En1`eO~3Vin&El`@E#K^nNTLvVFx}`i42q`f;FqdhQRH3`C~jtUz*ieK8gz? z^I`L38rU;S(5vwKgPP={(jqq+L1_YE2Ff%f^Iso$x%Ngl3k0}8jBrHKrNLR=XDP-d zo3L6d800v0`$55<3`wd&{E5HcIB&oC;7=yF0;RGTqXapo(3#U18mM7m`lK^j3 z7nr<3ufbqKl;|D{QW#f1?;yag9eBcpMuA$3mGk;lI1S~>kBE_6gg?Qi4sp)LKGQoc`72!05IQ$Y1MkVGbqyUbc;rj;uRFH5g7sA+#>Y%`-Cc?smticjs zMemmfCB$Niu7U?=p}%@ef_ at NcID~P~-Ly-QswLcsFyu%Ki(L*sMo}W7PFj6nNa8=t zgYLMv6_Yh_!QTx<+=%L{^YUTSklS92yjIS%UotQ5RbwT=@(rLS&a}|sEJUaiVwsGH zD=iSDJ5$f+7*K^P6UaMX9}#y(J?W{UL9f6EC5{-|xkz!01{LpUj42GW9`cN8lTLx at +8-{rMXFujhghm`p-z{z!Xke8f#b75uRrU;XJKS!i-J({zxk{I32FwC<{&0#bc$0 zcTOD89hOgzkwl1b$uOWXysE*83x5X&KL9`hB615KToj=sv60;g8%7;Y1z2fNz3H!t z5^V;>hLOJT?QD1s#8s-WhZFcop$9>XYI}T6fx$ePOa9<$AdCsgG9CANv?p9hgysYO2DRU~PRMUbp7grPb_n`HBCtxwBx{GlzVIriV3GvW5qi>?+|8{{5HmWdz1j?l79kNbFf;Yj`#0P{#1qV) zIdWQuLbf{mm`shRLK%Eitmw-r>eDI7Z~fr!os;+mv1(y_P;2JW610z{Ajj at -lEmP_ zuax3yN at o;kA|i*U z&AbeR at EqM?$YE&*4?l1Y(5WnbSRoz~2nPz{3m|S`4Gv%L2VIUxTtP;6AI^U&h|e+# z^6 at R^OqL`vFaAAKIvJF3N}`hFMjc)Se=Nvr)+WugjJAU_5Qxf*uNpQ@)ewprNjPk$ zR_#Mc35qRf+ni-+r_CSPa1cgxQ|1zUO*sCpTFiNHbcSgtVgRhInI;hBB#)2SqpQ}H zKE7OleT7LP`{o5+s$tt1$+CEjlANr;7W`~BqCD85J=ihSC3==ZV9~+}O%y(68Zk!| z{`_JmyU(xo8wcPHr_vBULL0Z`a14wK2i{P`TX5m;&?F*2NPU4jo0D|oS>F_QH4K#k zKVokrphy_rM4~KoE>@1;hclz9_<-XbAg-w~1QRkoIBR|o0NiCt_5n3I$Uab7!&HGC zdn$-EH|-#tl?!orLXjbetwM|h6o)x_!@;`2mMyar;X?{?2ZS5j!7vDa->j~H?n7>J zQ2*PlZqUZVAJ!SZqZrc}6gvExB0-88#!SPDxpD;)>XJ3MEX12QgH>=)`#>qdo=dQ- zj8Yev*vN->(jF(Ry^mg6br!{WFP{`Qzs9hP%Gh`(H#H at qLc;P07} zcM~!MstT)^tCvMi+5~B1w*$2boZP=|`}kY!esE#iVY)3lZ2a#ZV+) z;EM(jHwO*Q>Q!4U zCJh=8XT3{GxJKVfciK!D#8>k!r!gUB#0J2izRBPxGhwZ~CPt9oAFnd|YRYVpn)xyh zYF4X?PFp(|Huc>JiB5%mHuBLz;1`$>-e6i|s`p2(E|6YRl&tdM^s4G`_GQ=-3INyW z1Sf-E+?|ezL4ZMvEXwK+9{7tMPW{-G{Yb8Yy3nV7Fj$eBGk1dI;?@omgR1*GZNU&F zUW{izPe3>^MUB*!4 at dGMU0E#|k2dj9q%bk)6&cbvT*LkU&r}AJ%UBFCZG8eOv1`t1VNN4krRE)oLvFdUpm!`wln{*x*6iK|SB1obij44S* zA=oh1ncCf;Nn$5J(IGOZi_?FbHCPJqEco*ReAs~Vn6IeAjda40!Iw307lwf1_H|A2 zEjNCufvHL=Rb1il?f2{jy)SeGzO@!#hh>xoWkw7dl<@76b*>ip27&um_P^4P9c`8&G(E`+d^((6M+8!1O7vv&${$u!luCOJPk&j8eQd z_t{fJNEw2Jbp9a>MHed(g8Tf8BUIj=B!NN08L>2iiT?%)WXj`dH14BOI5-!c15oa^ zuVD~4ETGeTOlChr;WYr?4!w_hcjTktO5c?wJ{jRcyADQf at F2H}D-v9EuQQ!7ShnXf z0YVq2;YfmGP at qyH{b^Zg4^dLpVhd>n3Xes)XawhDl`b^!6p3j)eVS#MOsX9n)HIfC)a&?-8?^SyQ;FSUX- z^(`pIb#z}`>9x&+hPhQ~@DpbeoG$&}amTn;8k39-EKa&sj9NUEk%*pdGzj+0nUV4f z0rJJ90vf{G{$REQYb9J;)WL(@xJ<^MCuRhO^k!~eDutB*Ax6?3-30^2OPdDQbzK~Y zJ%3PE>E>ioCj^QV4j8VQQ;5+BI>FOZJ$TxnDPGvq0mDy2W)2Yy21IDpB?-O|F4T{8 za(Vy-ZrT+7hQj+i`0bmcE^I>MK(z at k;P)0T4D at wV(`pdjgP*m5noVFV;dDONu|&p3VNi19BbtT+R~%WNFa-;izjm7e%9rrtzhNA zcwn_MWA4aeg7GW5_`}KY<6EN`H_`VkBk%1WiN0<}_$3x(K!Ny!3^rM^(5gN|pY(w` z6_I=R(J>xw65&iDWOzEQkF1&h+ at apVw-)5B%OP16J2 at 694o#q=)Vh z3m!3>Lkrli$3cT&!7YGzPm}0AJAo*HRiZ5=WG*^F|HMgMs>E+{;oWUcgCf~a2E}*C z&$9{(vA0C|r8Nx-T-G`H(=r-yFR=VecBe^$>%6l50)?^7K3QC=0lyO4S5hoh7>7)E zP&h=BI39Z=M`~eWsYSs}p7bb~?6aWr;H_Hxp_udG2)!Ay9}`;_*E`c-d1(rgFKA*J z5Q{OWIR!1vmn^8Fq%fg|w#a)Wsu162ELuIb)uqauHenp-<6Xq~yK${D5TP$9gAth* zO93|Qu?8QvDMNky(_>7Kyojx49u#?eSCbHqsrVuqW7bSueIz!wM^bj*UFqBCdm};Z zM{M!k)kp`3wm-7Ui)IvHQy(w$(x5?KpUXU`x&wqv{65q`9w2K0Zi-Nfv+eCNO~6gI{Bl&Vn*lkkuZ% zLxPI{Fg}U6Z45GMo7Wjz(Armg*iq|5X!Ll?3}kGjRSxf z)$L!;j}{8U5F`#okGKdn0QQRU#tc8-Z}H%V-2#2zyaB9w!!`)thB|_erMaPtzd9#Y zYg%iujOlJ6F#17@@S7|V1g>orRldiA20b>1EXYZYT(*Mmbn`au`!EE4#hcdL^c$sj z!8=>P3joIQ(ke7qSK^{iq#^7&gOV9k7+PC6^L at wWB#}Y9GB2YUnFatoI9>ecBehVr zi#@dp%aBhe-nB8oH!`t5fc$7!-%M+HKX}WV$#zWFpoSc2_Ju7NT3EHIiG7LWCJ<#z z=iZ>((sxI6ORB)R8Y})}Qs-+Uyq(z+8Vy!3Y+lttmnUU-KUpSPLCeXM&_Y`eKW52I zQ=OzL%<1uE5f=JWUIYs7_=7@#o)P?(iZE^JD<*u)8%l7dK~6~C_sX#?(eYztQZI=U zy8x`ZKnJ4&Fx~7plnoNYqZooz#c3>(8bPH%w1vQ6!=4_Gufv|H%;_m9Vi(0I!aLTc zhX7G{Po^U>8Ob=5lLk?JZD4Zh4}P#0Y-CmqagICBEVL0wLF}C&Z9%#D5(_HmJgSO0 zvCv=>g{vb`{pl=-Nsn^`w<lUJxwg>>4qm54EPm2vgD6; zuFTRG!wnz??rKG%Rd|W0OcmZwy6)e#4v ze^?mRN at L=4QKB at MT|pHmkR^?cb`v9-l1Yl#OK>NykV#PgaK&(#l&I5 at QF=j)jpf3A zaj+c@;FptEhfz={y5u^kV4*?1J+H-eA@=N6;k%vu0KRW$P<)#piGDC at Z(~$<6NWY( z&U7f#I;X(X=rkBmyF%S$M?szo8#)In0 at u_DQVvT$*iFi0SEkxw0tV+vO0Fr9W2=r) z8Fqwt^2n|~T%W`q$$nQ(R-hKC#&c~TVwp3yg4>OB`6&WU0=lEafOR7{I~M5=%Ips1SDCOzBE3NYU_UP?M98YlxI%@( z(k~XLdQi at j#dfu806z}tPErbXxCh{;9pRNGNqd|YmI)U8;H>x(ep!@=8WDf+tw+qf zZ|ahiRA_pF*EJaY=e~4-oG>A;odD0w=|*mUR#xO&(IcfE$1L=x zfrT*!B?k&WC&+YUhU00RP$MHzDDs1XSWEEOL7{ApC=n8V=b9uInX)JCctj~ypxwY> zs=}l}b!YC2Vm;;!P_#*%&q}USgAPNXJ;Y{c&vYgvv%XZNI7PMGA3PR`#gIWScs`&j z6&dw-mYz_iBgcTa;Zqkrn2&y#LKJ%$u4zzw znK3tn(=^P}ND7p#PN6q6=z0I zRq*_ at wSr>`ZD~%%4pYG01HB+DnPX7w&R_trHfK&)(3P+ogQX%jG5En(g`s3+U`7WH zl at l)r(C5QV7BqwK5{Ib}i0Q&<>L2FAeM zz0^!D!ZxI9_%UBu=Cs`ELMDVIcx_JSz*Z@~nF1+`8?>jAxNx4KQqu`x%ygWYvN)WL z>7E`bub)mKY-+Uz6 at 7dLQ^DLP`7tR|(V#bh$(0!s{u(@34)^eae8PhtQITDe*ss9v z+Z78Y60DoScYeD!95G{>Oh|3qvSPYGf at fPmrLs>9m-trFM5uUpd= zqpgoTre~Ff3`*VXER(gPW@(C-K#dHyck at kV##4P68J6 zQk-3B<-Uu^*4thxbNa*hkN5ZRXPl_*pa_y{UvQE2B at x9d#>1hAF`qjE3!IOHELqH# zQEPCg!SH2dI2RWg(T>%cnlz{=#QGc|D7GNuTGPJFZhat*n;vyMTx$_g#aE4>g%zR_ zD+~(-_U6Un!Z-*uJXk)y#9nQl#c7crw zyod@>8e|7pc+ko(co1nBd;Un;T#{LF4Zg>$uHgRN(FaOwe-h+9${LCDAgw@{g$&P7 zm)tXFC`*PR(_jddc0ZVABhd-MkI`zZ$g5EJ&UYG^sk8eY-oF=C$XqCkbhb`+XOC$W zZ>H)6r96WL6AA@{aR7RJ7YV`D2E`=6<9Bk0T28&}wSp7$$28Yn;7*A1P{m}1gCq9` za)t<*;xUNjC5~4h=MH9<>gstI4Qid>0p;ey{ z+l=H3W%zA+#5IU|G|Q6TnpJq|5MUz0Lz2Lk$&gmUL~$gbI@lI^ldyE*JclA+xT-IhwF#W+g1;#< zVK2CP at Lr7Vu+GlAF)b6*oIYB at N$JmPGA|V4j7QDxTVhM6Bj(H0*)1p$VMOl$rR0W8 zIc33CDfaOQS at 0`~X|KXYg?cM>_!NcO6zaU#oN493r81`%L*gnZqzIOvf|OwZFO%TJ z4E5Bl!|(+s3S!LX#yD8vA!`Tivq6fi0dD>ZU8qiZCmMG$LK*eJhy0p5%7xlFmGmz?b-I9X&&@ofG0gU$E-j4%0cPb_&1q-Bo0qnOCPubqZsr3o6##9M({_pP zh+ at a0iV?gx3*I-XTPG?=8`#mq`HQr_RCj?3_eZokw~(kRL`TL_b5|um7&DU%izj4d zBskrKVOb-V0z5 at -`heav#M#p+ECYG$#xV%eHgpHm^qZ}6eKgcgZc<3Z)WZ#4)p zAj#Y8_e!wRUqCT?({EKJw`ed$WzvWadiX{m&XbW`g`r7&i!+08VQLFTj8$ZsGAk;u z(I9$39}@e~3TruAYdZ4#$AB1$c+xRl3d{%x9*LRHFCO6zh9jt6wHPvkzBNF&DGUif z3CaL^sK`|GL+ zt^<&JODM{yz~I6m at gPl^w7q0j408*&v~eQ9RE}efPQ96%z2JggFw%lH7S>vPg2UBe zsBqM_kmF)GSfN#kBf1(>5e`F=HK_UNY0i%(p3w<cJMkntr3Mm;J0~V!D_M3JLi#a+!W)hZy1}AGG%h&tw7328yR3;OlxH2kB6nG^$afqmumwwfMu at LE$|BQh4y2SL9pe*i}jT z`6Bx at Sg`g5FXkfcR%gNj{r^QNn z)3HRS1PC6)yXpv9ebLYxhT;SCanlm47p zgO6F at H)=P?;9w;|hNCMC;+qoojvA~**oMR>$w7&z`g`gGr6bhL0#WCtbaJOvh}cU9 zbYc&hEUvu4CKgZO*MZ|31sLC*s$v2#7h>6BGOxNPN^)c?M2Wc5oRO$IGf6ARmE!nX zOL2x_e8!-7jMShRoPqC7TiDngDLo)Zb5?;bmzWHG>JF}gLegNn4_qUz6e|yfMWIhfG7b8uqyt2W$1D^h z%7Rvkn?qtQwR<(b7!^-57wIM>7LM`oqzov<_+Y^)N^^l7#j!~f$Kp8~)+ihlhE|6m zQ_`H|gJ`U9`0=$CFcYG{4mONRk*_&3ioKQ|K>@xTk|g>QQ7oo(@&^W&6G%K*n zfUQf8SD+=>qd=*}>0lYXMM>7(r4Yjb(?Q!n_hcS4WIEV;2>xt^ZfF)`mf_?N5`G^_ zb85(Z`w%^HZOpt;gQfke{lPD8knqTT;xK{nS`Rx9t|m-e7!!pp0-V>#ss;c|8p zT_Vp4M at eky0W%josEO4{ed%e6qC4(P=>6cqf%Ai02-lCbm;@+tq#s>P1G)i74E)u7 zAP_hR20btxRoM{u!TqIc7JNgF5QK44JA)qvyfYBiuC#)V9pNJHSN`~&ZChurSrTNOlq-XT#=Mku#lq(VvWgU;8nG^jYZ-w zCUcz7X&Dx8y3K>?2&OSfLg6iWA|%*ApO)qO?)1`V1;v_{j&Qji6o(g+3$c6DQjT{V zYBoWeLDj|PNV6VKGOSC{X_?PSOIX{2MuxIL;s6}MgcBP=&rApIv;}-rd6 at wjnsXwg z4QQd&JRmXfwfezkT0xANgt#68-I4id=@F~KVN`$RLN6%IU?9-w at VpAAyr8s!BtYp0 zmssNz{P{GPt_;wIn1Z&1`^h z@!)hQqq%-H)*39%G;ydcp at z1M2b0oOONL(-bNX5l6NVq`1zR+z5$aoH7&c5sVo+c; zDLF%8$|#sG!@?Z+TMEY^LpT$L_yWCQ7&0uv>Is(2;IxVvs9m8wjOl!dNw(T0FepX1 z%E%x*IE-V-${;vs8zj~do^#+ZI0Ygqt)fI*K$RpZN1ZCN*wRf!d;^AgPS@%N at 43@e zFSxwzXA$7_o50ixw#;dxz{Zrs%dn}Cz}(sh?VD8{jHoQgBFx#`>ESYESQnf(Awezq z!TKryoCX$T$?vlUTX%SFQhPcfYBP5^5B8IS>@Wlmx+SBlBOgM5XC=4-;9C$_#}y9z zeKn=f0%Muik@(&>U|0|7tOxX<%<(d%mH8=B-uxM%|G|AVj zL3V;4G^CRlv46OOzoq`+ at 1i_-qKxwji2yLQgrvcgT$RNU*=U!c2ztf99qMwSKwaTP zfO@(p#4#74D3$?nn$S&Sc>;`nH!8%y2_N^UmJTQy4wycN=_?xg5yOixY^r*Ke-cMF!|Ew zJZL{0kbJljx#|hCGb7bmsV_iyRgxEdQ%^ciO5Q?-XGw(sW3 at Zt-5|Z0S#*b*GBdxU zMF=@U?6tD{T7#?5ptyrAHr&Z?jx#+-e+M8R^H417-!w9Fs}h!u9XgK<=CbTMxa-fT zJ&k~Zn1STMp at JVVXv<8FzZ#5pE{*O(9xD&h5By?E;axGH)Z>Zq>Q1w#;9Jw9%a$(%n9b{x4uQcl#8W-tJF zmDx(IAoxx|Y2S6d8R}wh1{Ha at zmeVt3|reHZ{Y81P&7@(;$a)9R$94uE&>7^;q;>5NMHAH)zfRgRo;* zki7&;6<)OyTojM%amW$!{8Ed7L((73nJJfI8IzRc^hFhBPq^n|_P$UYnj}aj?E}X+ zj2|`k_+$f-QLFHc0STrf(f~UB6`!VAiF-U~H0a}!n-M}a2CFKp==*YFP}vapt31NX z*eYqTDT=in>_`&&fjSQ%4O&hbz)R~R?#eJS)4j-odvsN^TM2Vi#8a^ZV>TkM(hHWy zGFkl%81}LT=QeuBd zYD87B36p*+mjzTmeO2>ZrTG9R0sVDNu9 at dPC_6Z7g>jv9%(dpoe>$D3S-h!<0m`GLe_rKkhf>p~c3D z)V~5kA`vwBU~a36MXquprp163i|{ZQ4f<8_px+2F7WiFhW7(Q}8vq4=9#puiMTGS# zms$tK*8ps7vzs%W#7K8sHwz&a=pFDAM>^bSHLOv9SH)c(kDR9lnNk?>xzM9Egt14$ z$sCr74S{A5>R0CtVQAmM`@t%>ssZX6i+0X}w0$Z=uC=gFM?TO73Met9Q|R5T!{osV ze#M#LbvwVGK!|#5eAvEbL0_UlccrZtTt$7PM)1fe%qJuW_^BceEFM{z?mn at -J;a`| zO6>SM8;?kQA55tBU`rNvXfSxtvx0S8<|Bs&+laKVmcUPE!yk6{rOUm+ckH$Y{oOKU z#Ec1rY>uEggIzLo9<;hEBf0AHw2CxwAQm)M+0_;b1B`OA%p_TvYDPEjpxe)xrZmQ> z(}@KsKQy~~0!En?7g8kUd3yi4A7lyEqDz}5t}Y?M6ig|FoSy!ABe-ZeRyne>o-pDK zbD9OYF&fb+$=YN>m%%}Zlp%DiMUDjUN>8XKDo7W2QHLWhT at D!BPgvlYmt!5*YGns? zR$T4S2?`aQO&C%M3l5o=35N!N?4a-2=m$R}oKVl`E=7qVFm;A`KKJp(YPDrSvLe!9qbc3 at 6ShXJvWFEs2qro&TKBYkq%J`ga949E^{p0Z| zdccWq8kwn<*;$NFhr at W=Dio3iU6XXZ!KlSy2R7VknU-9l(d-xt&uB*&^!KH1kcY17 z-SQS+=GG#ohWCX8YhApoYaDK5$>{(gORfM at i7$XhLadrW#8#&eB)z1JVlm|Kv`9#e=c0lzeIz#21+v84_RZaw-&W zppOg)8q|#7jijL9Fzpgog;-=r`{?r$TxEE5ra_(ciJ-0$BNE%Uq)d|tAAvu#hR2US zh>q`-B{-rO3k_C+_Y?k2Ol#WOw7j9`*e43tq0*BDW4IHK^-3b)g1!_??_N!d! zFBD@)VbLLM3*Xn37~>IEUt20xIqX;U1c=!-m~1$VTjpeH;2r2&C#}Tu1qMlfNNL3* z>4NsA(GHtF{%tts#CwBy0B~O=b`XO0NP}mk*z@`Wll&Wim?p*jIxIZ+;rZ1hg|9n7 z%gSU+`a@|@#PJROlNM0zX%2}~89twqz!Y4(9M|qlA;SZ4&Veg;IU!HTfHY`kXT-V{ zw6LJ16q-xzlG8de)CG*(BH6`pJcwx+B}hHkJB>jzWX6gHPo?md at K;4I-gsLzC_mV>-y;gu03bJ+);>Tn&lV{WW{R z6KlG%X4)qz at Wlpo2(A`Ye0bN7svk5FoM{lF;alCH!ccrbYYz>15}Fw at 07VyzEUv2Y zo#^1(6bZoShr88{;RPzIr4;tDM37qDZ0Ti%xTSTk^3k at Mg?K+31y*kP=@8=mx_$BmlvJ zjLLAExJ`noCmd at yj`kvLU^OclkK~}hF*`Eh)fDux8aGh5r4I+#toy@!#C at 4I6rZ1! zcuRYA=1bp1WjQC28R+FoQ(yw1k;Ndj0T4>&ey71Lsch@>tQzZ0Y$ybU7$u- zTL|2!i%k at _bjdXpz6Ff2zG-uj`0gG87k{ZIbc@`a*W%_egOv(t3`nvf4)`7KbOwPs z3k`n0PIP6C35hL{gd)xZk~t$#IM1|kdnR0^sL>6MW4M4qh$4cAjudV(qUBY$I=p`( zM%WELoActz9=roTy?Hl=#R9+E6qsWQMtUMhF4aijujx^%D|lEojzeHiit?f>B!a_^ z4%XcCDp}RJ9P7dC;Q4jvk|KonMKvG35xBN3;slmAMAtl5bpuNL=B(4-5bb53Y#_)s1eF zUjvDl53Bwq4{By#3$mVasq0IU9c*5YS(#86 at xUKLTgQJ0-*T*U1to^k;xj^V=~v)^x{G|S7i5R z0)e(fjkPXjjDqdiP> zI(4K+Bf|cWgr*?>gc0dSw;jT#ycZC5dGMYZHYS8GO+V225L|d0ic5vV=mnD&=_1j^ zNTVH;0SOYT%PLProT-wLlv#m0j7L`1;@OahM-)eF)uwb_gdK5)y~XT2c6CxZB7iB4en3 at U^!4j%Nt%ry?9-%}x6jp^QR zLhq&|W at y?=u{DC1Qlxp#pqF7as{?^^%1!S$oaxJORHDwH)?)DotK(LA*5y>X!p?{; zZF0(ZPNFhpz4}`FmPz>seD at t&UzGM$lqf{jsj?DHtK$vtfO7X2` z at U3z&sSX}b2Na7e3Qx9Ln_f3SfwdZ=;Os>FmIBKwI3s{u*76mv)d z9<;i9R}g1GqRn7qKzHkWt;KFNM9osnh}NAwI*dsg at bmGAaN+{wrD at 5mOo%2>9KlKp z>yUUhNemKf3zDe7b7}__i9FQ<=zC0q;&6DxQ%I!rf(Du3KlfzPLNN=n2xAmsr^3bp ziRh6nc+d|zk`c-6$jnlY2r|t z!T8|apyAM%aINuo+)px~>uoaejA_n{tprblG=`}R54;;jo~JbkAvW|#6Mqcr>ld43 z__?)drc48X2o3J=dmUhWQWs|wp1XrJ2f7j_69x_P^^yd2nKN(*0%UBpPlAaGbwunP zq3Mvd!-n$S*jchMaeyzBan%yim%ctu0ppMeOYeZ&UBR+YSBVj)K~cyfM#hyTEn|}Y z?#LirAz9Fz!6Eg77c-J4$918LKlJ4m59<5dk(k_>-%o=QR^V{tYImf8yoHB)a5PdW z6Rp8;q%jnZeY$m4T+X>Df8HE=IOk+!GJ<8!6Ms5RFajTrM5T0sSq{hD(FZZ^EsSngS at kKZ2 zgSyudAk at qP2=IJRYze_=1gq^4Hi2I$!Z|)IF7<+!&>pnc3$E79ptGQ2P}Ipv zFK{L8RdbPPXZVA(q;G98X)P8SZ1jS^6NOMC>O}_)x<7qNge~w%R~QO;aTIDc2I+l* zjvfto(8zE`XgV7*JWxiwneHiDpF9_bdWbhT#`B7$`vVJ>_)HlLk7<**4zBjcb2Sm# z3Q5D2Hg~!qkP+HCl*s27ul>)wnW`Ljp{L%o<@hTEeq2^zg8J9jTNHTckMiM#Q60S*LEpJY$kcuSueJpf0^e6;xY)gTgE5|4 zv>Cjxr`rqd2Ej#q8Kc2~B8DT;8_rUDX~UHE(Cq1W6chBws>$rE#SM!g0V*;!dPCc1Xr54QY4TgE0H%l5dw;c3ou(;EeKU0A(YVpdA_V8n(3M(NNJv>7ai!TE&Mm}{Q zc!*z8AGFD6=l$XZN6VD=*rU_m>L>Mr7jlGU*{m4VB13{-jts^c9e;Ni?nr!b$K8O} z1ECM*gb`a*_ at z?@^Ztmu0*j(hE>(%IaRv at s^O0b|491!H)q at P@d0xSOxbw_v5M$EB zbL6HcY!s^~NmnEo9-f%7Vr(6uS~KhcCmf}q4s|NPr!Z`Fgj$8=re_HNjzDq0k_MlV zli8uSVN$X^nOVr?xW=J$f}Iu4OoQBNx`98L(&fms42mxoiC6Jsln+f0>!_qvg3~-W zcu;k)*58$d__OI~&I!&W)(ls8_KE`NRJ3;9JO(Dl? zo{R_wZpwr~f(4iE3l4(76L;`gv~kRfqeX1g;u9FYNr0|TTJ`vAtsslBua2al-T5W` zpbSW|A($P(W><);Adj79aDn`XcjC`>iAyuMe~`#vb*)pD#ryB)!A8P>ow!zL;Pgrc zOwC|UuAaJs6vAQ!QjWMQ*sjP=0pV6=Bt;az1cNs-g9614_HqTj2BL%%)TLgS71XiF zjeTNI$wYD*GzeF)8Qh{NBXNbW1Fa2IF}#K#uH>+U1tCZ}l2|p!5gUvQD=KnP$>Uh8 z={+K|_kPFz()2I{4v7Nm3CUG47FFDA2OWi7QT&v+jEG|HljvGqxBi+8=?LNv?*nfU zp|pWZ{=otXjG2pGu(qYsI(s&!SD>-i4q`460Q9&F09dD01Amaj6%Eb~z+8!^n2^XO z=VZrDONx3>h4MJIi5-h-OM3~<1zBwv;7BRFi0Qp~7K>@U3njinzn9^}=5qHYnB z5Z^To;<_b at buQ8%!$99Hd)l380b-t&Xk!TEu?t+pADl^sb&?*@u2n!*eVvxrv z2oB>-%^B?nMF39%K9U~~Kz|^w1y$0`%jhW{d@~xX*sy!~63JeNOGCKxU|{e--i=|g zI)oxmk~64##b3N^NU~r^B1Cv)3-^*X39mVdNUJ=B{?4=8R$R{=t5Vh!sLNrYUFM=UWY`|S49 zn~qpJC_YR%;bTYQ>+>Uc at OE}u%xTN<=MmlSV8J{shBp%>_`;%I{dKkat8%2_3@$l1 zV;-SRN>VFT zWZfts^3V#lfcR7(7Dck{*gIYhVti_D<_`XJjW{{ePA&U~IiE3owr^850fPVi^y zxNQucEYhhnt)_H?2A4GtNVElykAWE@$ zgXQUS0?!Wgk{x{++#jSBY=%~CFsHJlD#UiS3 at z#xe=d3Ooj=CA?~lxEn_})i-vQnALajw=}$DNy>bp at 0b<8Sc at -qIbjHXfDgSN4BXvKu1b90 z54tpW at XC_W9XdN+!7Ff+|Be?_-{Xac@>fzsUle!y7uCu8E{RWK3S*Gpj?;!Ulj`vN zFk`y7(GeS at 0I3aXA;TUDH at Rme)*Ne+B)vM!)O1K<2$H=7hcy z^92DtjE5sbJ^A at s<)KV+%nX>YL4*Y1CWl7F>)4ZC>Yt;ah1fLt-@^G2k`g3hTVe9?7CTp at sM<8MM^u`3A`*uVlha z(^{INhBWD2v<9Cs9DbH6Tv#yL!aF>uIfbenY~b(~A#ym9yFlSUYXwjD=+G6`7PZKc zlWPVS5|$=Ryd2NGGfQx8K=68-cywnb?vK+2wHrigb?^^7-e?@FI!riTRAHsUp+a4A zD0kTq;*i0J4aShJ^n!agc%h4JrC5m2MDhI-J;;5$!Khe-iAv)erPxngl>v#NP&g35 z)!Yx_NjT^NSAtD{oc at 45&PU|P>ApQ^>%?%QPUlMWsOkP)Brzy(<*1 zq{7-3bYr^9h&W#>Ka*~D+~slc+Q<&tkgiXFI7cD0N!*vwaO at Gw;R?gV9g^LdnWO`3 zBo+*br5;~!p;$BIKr^Vf3lwf!Jo7;#3gwf~;1=!Rc`bG&A|WlUz_{);gWRJ-P+#c} zp+OMgc_Y|aVk+=c1fCUS=ny}AP||{VI)|`K(jxOL>^FA>v5_HJFpgIZS0;HghbHC` ztDm_UMy68**3jUkB~AOK5FTo=`h&Cy``zFZ at 6X5z2IwL$b09BN at s0eTfly!Saov|r zRd}lwjtx2;65sHq*QyOftg?d!4T+21T(^U(qA-!*YU{*>2sF!6yvnSM9ifEJmZuZ9(I1_ zPA3-JZ)Td(Tx#bB>kDm<$;xnf;S(U>(wxhZP0O%UVHRWS2YX?Klp1_(9L|;W!gIF< znI3$ddoxypJAuJ8qx-_O7!OJ{zA>O%?Y_Z=krxF$W)>Ir3)PLK$XPy#b^ zbchWi^a4Cv4;TQv5gVMQ2L8%`WPh{CaKLTUoz5864b at +d2OkoT8HP3mk^_f?!d4{? z%6n>3RpukFa=^l?&|tG4d at K98DppB+2Y7898V!amo%G1PtU`+MulZ;7MY<@;`9bZH zScUSn*$4#5Wadm&h-Hz4;pq%NYXc?4U&<~XMrS_tA&diYB8aUUghY?>`|$pVQ)Lnj z-ruDYB&r>)Geel-6f$0CK<+q60;bo!6-{h(hCk!dhw%y{q8EA9dN|n5JkC^Xi+4ezh^x7R!n9| zEcm;>!}Qk7pz4vOG=g_JK_kMd_{xwZm*CU}qWY$O at N76D-5}BmsJ*sSd?txUAwmp9 zreY43lcx`BWun~gW}Tx2___Trl&c~!Ayez#6$j057i`Gn!(v^jy9vLg{R8?tpm%?Tq}3C8C2q8WZ2snBiHQ#c zBM~NdP{t$evo5(qce>MF0;?3mL5fTe4yg;ZYOQ^r*$E1_o0Ln21m)@aklbNv%P&e^Y7(u|zD|geHx2fof7~eDKC4 zD3tzhU|u1J&*-o-ArTt6ozYW0Ji;T&nORwG|YuP(U^3*scTaLRv~2G@!N zIVx$RuDs^wFs+6<)xxW+&#e^1xeDi`LQfX^0b6pb{3nMhOj>&5W+)G9Pd|{t<8W1l zxA0@?MLNQ+S|dIoLJcaE;C`e8pFxD5?{Eiu{h$DmUQBy5@|AYbn?bPP8!RYw`1YKn zKJzy$YVuzLisDTJe8q|(0m`V at e3?oSyDFCOblnKD^y-0wpuUMGPVaB>4A{@V`N4$3 zh<{;j!C?sawQ<Qj^lg}r^4EhwsGBACDxwMC9#y^E)m*cGFxnrAUXZosEfB} z;4k*-x;bhem87uj6o5 z#rt$_3PvBu-0B!_p9DByIx at hSDW6pYYgaf{8=a~~v?pv&Nz5S>H&D%KDaaR^gsa45 z91^q-Ev^$k69MXkq~sT>TI?%KZ!)3JN8Z)J3g-YkVL^*cCthrzQPR_^ zOiHpOJ|E9X9rnE?e5DH|#(cXtUP~Wnrp!EF8!`!tajpW^iH-lF5gbQ6YD7%IQGN%E z=L!}dioo88PF3*!F*L}am`jsY7wC*5 at SMqoP!0?2wGgB z6P59E25S(G1fh7+$(pt;_6cjmJK**+6%qmo!g1uWtAh%XZd9S>Km%d;fEXK}r)AWh zdE#`z(8)d@*67wNYO(8)rY%^J;jJRvAi+10h{$vj#r@#4Fkr2T69oc!Y6-%8;aSrf z+1jDDYGOu!IL)g+n)vfm$l^;5)N?)L5jc5P=ab=Dn4kkx9lXPkn)t^0CM}urRbCuP zEO?GTb*B}IPXV2e=g5Ma&g$OEdNu#4D|lRA5XCYPp)z6lHDmA&2{x9P>fhkN(-}!7 zMrr?8h^b2IO*-tdQ<8KS>A2GlCx*+z!-R(tPmkX|>H~^76wz_1yVk=KgmP^+++F~?GIgasc~ zlCw#D>I)8#AXPEms=v;q$I2i)z40OK8^wAkP}CFc=M?(_TvvgZ0Fkk#a1+lVaRRYMCmT=u5Xra7_1M4HiWlyTnm| zb0Hq^P&&R1d<*8Y-!1_%vyacJNL;M2Fkcqqg2oqfGDQPNboyD>W6e?P7Tw#}P_#%g zXufdFpz4uD>+oW_a5f(oH6a6^8Njk=X)+#)YHcZ|15LR%5OoLBc}y>W*5e<1BgDW^JdWXNCS&^uh1l1)Km_i)2`U$cXXZ`w7gLNrh8y(>>BPrzfkii_MbcZwZ2(w0W3Yd>n z;7e54r$AYy+q5{tg8}pgd}=CVKg@$l1kTJ8Cr=|Q^NG7h2$BsLe7EEXsjw2oUs5FD z$EYG^O%~MMQzOoSX*iOBxU4y>wTK?Eu%OI|sfuSx67K`!ZJL0^b|n at q90VA%5jr$= zd^KdragM@)v`JgQ+MV&?-1U3}f><<3(k3uwdbTt$h%$`xJ)#%1B8(}?Va=3F=Ol!k z*WqXYby9K*emak1Q at GHfUeFfatI1M~)t!MZ5gOEZf=3l}pDreIi32UG<Oo=h7LoK!`u`!|e(_f^)#qRUDGQi~Zq&R?wL*85un{MdD)k2 z9Twz}&a1Lc=RR*AnGpMx*!*ekw3n&zlgsQ99^7%qZtxYF7qCIAkJtC zBP0l9K8h%QaKW<-=Hy{GLVcRwY5|ZEm~ONwlH^C942z!vz=H?lj5nGxu|#)J-m^e& zr at zPRg~FV9 at Lfrg4+Y_u*wE6`N`n<4-dkf at 4%|_R*lf;2gPqeZmngv1gv3o6)aV&; z!FFa)<1+mVF+q9Pte}bF>5#xS6e)a&ClrS|J`hPHW{0{1MJd5H5{U*-!uzL^gy956 z24h&P6S^v~P`J=}Flum<22)ACjY7`l5&_HS`Em2U%!a12QLmkAQq=4kwm=?Ty__Mw+6 at Gv+L%3RJp!c$jp)tMHBFzHp#S=+vB9uEFPAsH-E% zpzg$E8d3z0$3_*-woHsjY+M`*Bv8fC|G6hqUg zHUOF}SgB!pg at j@ocNvC?Lds*C̈#vx at 1BGkuP$w7FrPWMDDhAsFM6k0#nF2-U~ z-*;z}0}XkyJaYG34=u#z%{UFVOc_ZDDjrdKo0DQK#B>Rz9b`8MWo!&sHH4g#%%$=^ z;z6H2Yfmweg*V``<`V= zr`{mvb+1k03XNG^H5su1$(Qu7Vo_)i0ak$T%%WadhZFu-h^rY%H?u`LOrUHgr#mb- zp%;{xLK}W^teZrq=@*714wG7)LizL%#HBtkjp at 3Tc+=Y|C}=rBH)aBe-S9NRgIpki zHhJkmgBsgd@(2qJ zcAkQ>L~b?FU`F3;cax~%N#D(psXD at u!9je*uxf#(j7bhTJUyl%L9troOk$X8Bx+2b zF`6oSTtm(E~r1B)dSaT#+3sY&3=Y>L2_@!-3v$hjbY1&6gWt?VajBp1avrMA`> z)2&3K^53#2250LD=P+W_mJSj$JzQQUIccyUZ=JLkOtQGoffU4yD&)()9CH6RvNc3q zelV at 7alx;q6e>}?oy!moiDgLl5+I5z15vE97=Dae)X@*dOeBqp2|ZEA_h6XAGEszW zR(x846Ct`C!}+jxgP8_rqO;(Sb$MNLC8i?raUEkavO(9g2E`MUPSC?L6C`mZh7F4> z$ZYdAx*B<6P?B>q9 at tVD#ON#`p7`OUC!5rJc!+pWAT07D#qfqiO4^rT!#W?yw zsl(ND?n_YAMlj0nTXcxcim&LfRgcvg6iRHw_|wJp^;D$8n_)#}{jC5vi#bCU>}?cB zaMe?TA$}9`%KltpL<#PX?ERDjlSSPw!V;RXFTw`Q1b9-8Wh7F;vHX-{jiMmOIa>vW z)(vv6t|dkqz`LV8A!P#|eqCUp*!{XTi!8gfT6E>~Nqz_+cdr z@{2iy at M_qE4v~&fG|8R347m`&t%n$ZQ$wFb6BvJ%8zk7sfk_iLX;7C%fWjQ%Hwrul zV6BTII2}EqJ%dDj!R=Kuq0dPKf7Jcvo*G`c}?q17q$ z;#;V1T@`w!N7hLIXiH2%gKc}-YVpB;B}hF=Jw8;%F&LSG{vKtgPSChxZfvU&A;LEP z&=Pj?g6a(#`Wm6ZjXWs$lcUaKN+*u=1`d2a9emagra9f)?r%bI$+yz_sYiV|qf3jN zUXMi}uP%u at J)i)ul;e3;It&gJS(0`~&dd_pvM3bc&798ZR^G at uu~y?MJiYsZ$5X#j0XF3;PV@?2hBX-ZnF7AmNKh=@Uka;Uz%P~HR%*50Wone-xn{x?Uxf at odA1U)xoMuK zpeuCeXRX4S*2ExU)6=0wVxz>sM;+WA(jE7Yku=yBB at a$UEGN at aBJocr)tIOKiXVgM zems~{9*fB1-5A?yT#+Aqdt%|Oba)4e6%7UqTbkq&gi?^RCJ}ouZ!y(KjOW1uc}bY) z5*^5}@E>W=bE^`WX(7-LT%F>@|mPZo54s<@H2=hS at DXF#9)flzd0Qh at YFgt^)h?^Ru4Md^86jRU<$y)V+4!-iL z&Wx|KV@{W7kmqZy!F}imXMit9rfVF+3Ov4}G3^s$c^JDZk)d>n0zSl8D^c86B`$$e zQ|N_wI8!8&-cM15LV~Z-8NLpW2E)0sQY at i`PCr;rNPJrN1s2SvOfh5LYz!}naOHb) zs?`r97~5AF)e(quM5npQ2|cWXx{e^WRxQ)0DL~_$xs%zP#E4!^nh*L-KxbRL|lag3j%o8 ztHcUCnawSo1~4b5^a$GEFiz(zH?7mamJPz(=UqU)|f8ypyw36LW73I zC8JObnj883R%DRW=kmmNNd$Tdw4#rN2Iqhx<1&n at t~Cgenz0k7dz*C93PxpSW)KAU z01V*=4yKM}MyDdUwrH*ZNF;&^tvGu%Zl+c%8hoPS78-0knwT9SoauHcCJ(wfsO)#Q z81rb4);_1aUbr%*L6#81mQ}qWNQ4WilEaTKHF)(3yf!Pbt}vk+?AwCeq2p98z$cZs z8q-Dlr|6SX{7~#c4xjdcR+Kj_#9Q at p?o6{9KNAT)w=(~TM-zu8*%*z9)M^KTau%?& zAhgMy;Rp_TtDR{L$GlJ2uckp^Je$p3^??=kiYhreK#oIP6N3fw<41%9$%f}ApW7$& zfdz+Q4NhYnn)cvonH1Z(=PHsfXi$)diJg|=fzHgufaK}Oi{CizvM{BxUz}?6kOMuq zO4s4Rc6$FyB{<%fP67UcIj9F4i7jo$par&C#W=Kx%uT3(Y0Rujf$I*tvQBIcp=`6O zAV~rK at S;E6m;o^b&1&J4t)2GJ^$~zXBdT-d%?t*^lgL&M%EW7;Ky_zY*31 at dAZ79X zNV;;Q(X&|5B|q~d4_=pH@}MWB?c;3#h9YSswu}_7Sx^SVt5TqVk%c&w<0!hGl`%_Z zt at y991Z9V$S|*Ov%nce0`r9MZcs at I;a*$&=c3*>nbYsMsmd4QV*Fa%M6xXhxc7T0r z+82n6S6y`RW||nT;Bf?|L>KZ#XRgKB9sEiwh(3@&T#OGzg`CnL&$v`;dJ*NZcqdpT z7Q~HFMwG)9!i}LU7I~bG_29`e2yFG5!O+5F!$Tf_YF=Oyh4-P&pjy*>CVT5e)X9jR z at U#Ngdz?O at V+q#abOxXvFvH;p<|F&53KF7*9C^M at k(YVU#v?%AeP_`7KyL^ukNT!F zWA2PpV+CNhFPtO8pJ-6bn0V6;@YEcflnL#!@cu|`qGU)JJSqNUJQCjzunBFeeXv at b zxv-1Fc!NDdWAK(aXs$HUF>%rs4m&a&MtC2G%3!Viq#tA%W=D973Ugo!4)vmdTvQdG z!B1c(62v at e!mbr zVLA}^U?&LDi#s0@>y)Ggz<8C2f)9$%{Fns8G#-h#3 at J|^P6w+RPoyiz>-B{6s6UMR z?r+gW8|Xx6TG;oBFKTh}W?YA)I$ZYX8pmBOLWAK9)~IuHW_1j%sSKYUuw~nH$P6AT z#C)2BNcI{lyy ziBXkrpfA^ARmErrGycxVFj~NBfn-qxwo9-q&1p<#4r^w{Hw_pf{VNhX8&R7AqBHW2>r9~#iTZv7-wpb-WGx&Nn zPnYTPV0kL>7sPNMlQ}DM3xEyqIsO!f8sML67_uWwG&qMGhB2f4VbUYK)zMrFf_mPs z?GjHTC9r$b1NuWx365i7ylhXKJU)k>FA=jOx3UU&=@01eE4w3c=x=%m9d_EK9G}!+ z<3XW89ItQ(jp>lV#c9x)@T?9?NR&Y>kT38`7pbbk-D9}VmLZ7~N9cr|JG9!-k^s&IX;oNgU zp*)n6I6S37>S9Z5eU%RD8Hsg+my!fJWXY`{(lX1H9zL9zWwlZ~OK8xA$ciL2vkF<2 z%yz(FqQs^uk;9GRMN+F66z?k7!qYr?-^au at AqEX*UDBzjUv!qnx z!x`LX#YvZlGktk&1frxBl3fiD4jGGR17&?Ah9f1tkK<)F9gz62g!atq=B(~_Xc8PQ zv*bc-L2Bah41QjK%@<5m*kD5QW_G(K4 at lB{j+5fq)|m8QBj2MtL!m%%iixp0GKVad z!pr^533fDl!WH2QROn!6)kR(;v?MiraW~4!qM2LKhngrkf7= zWhzW_VW-10B3#X(P8PLH=$3vjSd_=%3VtGN(W)CE>Szzicey4j3j&OC+NEFg1a+BC z5BHF at VJX7Fg`bGxx%*OplQC0;_s=MhuNjHin-+E4=>(xlU`^{^oOF3)7G7Madp_eK zIJYj>Bwz8Sb#=)P_Mk4g1|TJIa-|VmJ>HkZfHe|lMWfOm2vEkuwnCDHNSYFp0V@=) z1$pJ9Rr6$!+R91k0zn>eBhL4>a&ipzsJg$7^o zbYigK9USVs_$$W?E_S1xAV(wgr{iiMaU~8}kj0qE(~$#`v}4?vfh8D?p;Tj;76X87 zf7(IVV?j{gG&8kIb9!ew)x_nr)j=vTN^}h5MtXrczCWJh&ql-a2(v0yTgDgaY5;m% z3$nPR24AvDC(Yo#<>}99zk@*?(EY*mJ`UR={O;^>RxKdFS2u)B`kVcZ2M)3XTW(O$ zR}NiWDL{CzhfYWOZD2ZOka9%#=}I-`prqK((5Dkh zXVK|`#ZrH_uw~-p{in={)ebzeThAw_#;nEln7FFNgx*(oihW!{8We~61b%6Sq)yC` z0+${YvhQ1R at ts+f2|pc^tWkVd;Xa#?M7izrVDR5Ds#E6EqZDLHH%t$$!Nnp}3LNwu zvZN-&9ujkKKYvIaVO5C{RYgW&FTML|mQQ&waXIS}Y3kS0oS2KijPhWW9=ySU9U5FR z4PK}BnU`@cw0`gee#gbffQ2fV>A$R15ni1KZ9qa_5cYHni7B;uJcB8n{Cgl#&zBKy zjQC(EW3+%#r&|no$04+#v9n-Gt~Q*()Dwm;NPBu0BHOc?6>o9K(WdR-&02iz##Gd2 zaBTbt4zDM4+6FGj at T*N-iIWP6vm{m}MIEM%3`xw7Db}D2MXLCIM-!Ja{D at u7(8b8g z42`Ikh>;iiEnba=c(ZIa1o(ERDs zZDI{iBdVb3)z~N&ms;`)j}XMs5k4qMY8dv2aPtovn?jex!hCm^oMbA zoNk0Ix-Vex8hRBHTkD)aVPy_>Q7AkJS4MO&=5s=WR)IacI?v%uYmb=4$SRdrA583> zVB*2%p$bqW4~5 at x#Gvf6ry}fYbhsTLjz`j<8ZtK~bsLa36auW#C%UAKL|x?6=(HO# zp^4)}kJoYDc|P9qOBlTE4(j?+rHQjAeLABv9<*w__LkZK&LoAGph9Z}S7caeOp8NU zDzfbq%MRz&feMa8j73D3L~M%~L~4e6n^uy8^3)qt_?>}plXn4Q)+Jkd>lR}g)EoAR znH>xbvXz<~8=b+0&#(7`Uqs|!+C1zO02PA;qLL!tBoF37xq)0)`TFWvF zmyjZ4ho4O4ET at 2a+(jYcTDCtTGZGz;U^p`Db$P00B!hmY3aLo2J0uokT9=p9oE~n# zZ%ex)fMTqW at G(rWJQq~x2mgK%gK=GgP|q1maV=XUu8qMOigj2)^D+{QkruFi5ED*Y zWqw9u`vf5yLNRA#r5FX-oSELa3NjVe1(Hz1QHvpoOIJ9=r-$KhjPSG#=R_C}H&wBD zGs=T*Oq)DTAVgeMf#_r6q=#dD7zu^;+2>~%e82Ba>QYVyZ6R?e?sMNQK6H`x$Hv+M{Ox$b>pBT`^VG6oQ!Vj^*DF?lM5rbd%Wo&Kaoj4Hw^hsDo zftaTwc|ey%i51~8nd2#gGhhgh(C%8ZVm(`C7Dap!nvUS$=g&NqExKvaMljDyP at DJj z6|hT!N!_CcA+I~hnNEgZa8m{8aS2 at rVjp-kzpPK+_#mt;w*#kWw{Xb3SI z78PZ{!o?Wx{gKALA9ca*pB>pryX$j#?Yb9=MGNn0ju>s>DOyLhs6$a at F zYEIA{YW1Xp22aYbATA=*CBN$CU_YgR%<7g)@fN2Wp$mMH`yfCtnd=_6dV#^aD@<<5 zNF5f|GaO#QFm7kwtpyUo4s1vLfqww?!JRov45|*%gICoUwAoycUyAL20f&}ZxLuB= zAPesaEN{$-g$El1=z65bf~)9av18US24$OW6*#WN7PJA0f^yOkN$Z+jnAltdo5Uf7 zrxxh;53IhU$>d~D_aSI`nW0|lL at kCckzTL{q6fB03;5Jg-Kzu^D)lYw06C)rCC<>pnF;9+wiU6(wJyUl z$FW-CP74R_&fxJRx~duc1Y_(JQwdjh(hpvhU;5MAX6Q?E;;oB5i>t8Q-II_ct(a?<)k~KTA0k2k}}#h7tQcL(>E1B|(DM zb7HVzXc8Tal&_@~Jk!L{5yn2}vq)juf;iekt-&Xdn7Y9)VDOGJv&gHLC(V2@5XSR(xUH=lQN(;VEv_mGXwA$5W!G1zdY`>I%d7zxdQ2shQp4cv8TklXA>c;BsQ zdtf{Bs=seMk}-G#fHb4Yg)yb0FLQg1sX>E{2}uWrpA|VsP>b-Y_gaI^kV(&CVolc6 zYUheE3Bvp3gVy4DI!9>?@P#S{4O(*W4*416hCvE2`@u9J>4dhP#(xEjk(G`|KER_k zo8~xC;mfvkg~6M(m^2tV_;?2=UdE;gL{#GVt>z=ip>9#(YPA^6pb28F%Cn8;Bqpc~ zcw$VC#g7@#2_hRaY5m=8!NVWaJx&Jrr6Kemmf-}$HXJv>Vcz288VO5rn+czC6G+6_ z3R^kn(hrr{UVMASsQBV3B)=Pj64&xx at U6553|_F{>O9zN2#o}T2p31+EKj}(5Tfde zN1Y-v`pU}8#Eh)7pB-+LjTwfhWv3l4BOX3NBtFcNb`@Ob6dL&3TK52;jEPa0bvV*- zW`Md9Qz(ZUg&au_q>JKmyJ}%~At7nNVo+~5vptONnh0_il+-T_uLnupK9-3MtAyI~O0X#3mR3QPo zem^LibYV+B5#q;2J61?uFDb>2y+7bl- at sk!1Q#vBN`qJUtMdB}Kq}%yg;Ihs_W<%p zgB%bG6DFHFy243tD9Pz_8Vn_(o=iN6DNRNi*HBIt3{Z~#q;}4Tb5ZWpVj46Q#v0%! z>orr;1Yqh5Lzu*&zbMAK+#busLW4gDg91iXB5%rJA;3Gckz`N1KTSn^9vqa^eybZK z3Q=|&B1E0Rc}6qXs>UTJLzmhHBoo$2G8K;SWNdUi7!nPEgQ${bDW)BW8 at 7N@!drE7 zri*c^C4XKxg8x~pL8-#={(?D3E_~gLDi{VYD)I8RbAO}!dW>3}noC5b4K+4I|Dpey3JcfzF_Y7S6K at hZ!Te)sao$Ftuy(q|q2D%7Z#I;J$DM1&NNs zht%N2f-tQEcP>P#X7Fi$HZ2ZUFd9Pc&c*pgF)sey1bCANnOapdhCmc!<|?nH2TrR! zsQ9x9-5CY446`Pi6s9HZ3MAPvvlQg>2{GG2Jg9y51LHuC1CqgYMG_BKB~b+WaL)9I zS<@$jl6sRf7hs at osG*lhFrqT3!5gM at Qi^p-EaTzRXA^%94BC`VwTPABd#?1NyJ4(-0>=mInz(^9A36ysrT; z!EgZsE`;GCS($Z}ycSJB!JSNlL3|Pxv|&jFL+b at keL>V}(Nk(it(}T8z zr*+tfQ27shXwBbnQjVYA4zdPgm5%!47!^Np;^jk+J^urNg+2xWz(j*#(=2$W7`|Gi zTQUeM9`usjx}+nNtU^rdlmY7 at o%7&?_j-w>O at wlw0-3N%PwzhT>|{6 at 3d@PBQG`Vz zn=RcZL4%^{lHkECNLnj608O$F_ at cY1t7h_HPPTqZ?JLMGe|DmK|5j#Fg)b17=xCg-j7lc`cq%p9hw_{ zjKOfrzX^Aw!@er9O?I+gbc6mlM7o1G;qA*OD)cdU0>8z`&*pUo!5dXLTY|MUxH$9{ z=>=mFvc^LZS({~K*RI25P|V1T9<3!}xStn%%t3-N5;?5E5Gdu0nLR4wtL?1CQG6vk z?UKYDnO;yKrxmRGYv8bpJ_)T_f*Qp)wm7$!j7Xk*I_&Sy$=t=D(V(OhRE9YEZUHeJ z^rix(=Dy$1mxxenuyliESZv)OiO^_pA_Qk|2w93n2|omiW6+MR3=hk*FO5DZH{I&mod&$xND>Nor(vi%?0>Q7Cex7&4EEhbing zcGP8Dzp-L05$~gtN0ub|>+WE$9NVCH7zRV&kW++Fi?7DShQj9-GvYXOTDC{xgh4!( zPMGkIz_6=}g#-yj8Z-rN_wriRlz4|)1B$5~6!u$LnE4CTMP@$~GNnj4N?{cMN+q5w zGI({+o1~{lmSJ$8UH+X;>nK1}hrkp(l_G*Xai>!kXk(FXL#X9fr*siteNq^|UfLH1 z;J$||)5oB$5HlutbcIoaxw&+m5&q;Fn z85$W5wGWHG7M8idV1>teK%zLj5+$ZWk`j^butS2zffAmcwGLM^y7*Mi`mL(49WmH@ z00|pGRAI4Z_Af9kX`pZ#@SI{j?X6m$!;o0m at RSJUgYuw2kS$>kiOc zB=NK2K^%FB&CVZ!LBv(7&{oPS2o3guNc=V2!G<)(jx}ym>z7A zp9+yGDRXkB0*1s#jv5Di(3hF`g!bYM7X5pO;sI;L$DwZK3M%y#1ddGx+?ZR{V1l5J zMK>#yUOT9;lbiyU*-ePV_Q(_Gt$w$}LXQV^Hm5b%1|u$xFKRLS!Ii~WsnA)leY66+ zX~Hi0LjW(_8G~e9iKQAhmg&qF#DHYtXig6pEW?r#rQUJL=hKNLsOI2U1F(%gN21+& zjNg!W=Er~rxzQfO;tj{GmZPYj7|VG{{=QcOgNxsLX3*B?u10jZ4BJofXV$dI)JpG1 z;XOumIBg3KO`6jw50()L3vty4HVJT60O}G6g8U`A&&*7AXyQSfV-m^1gE;xEHy3Fk zFuBrOh8{`qY at f~y8K*zgGPc}AzAcS>hO6nUc4C$cQB2K zyUk!xDl)JG>{Jhf0U<`#*<`T-Q34CSm;&!d7Gg{8Rvl}5CJV2kvI)nf7s+d zvjo9|0XjsrM4WV6K_U at tU}3=+*C8t%l{v)*zu%QGD7h!YPQSSM?L8Bq+pMftr2qAfI zgHuXw9a9i*klE=ZlA8>;VUvzpWFHV8pxXm7TUlSiYg!`tK^QbJaw^BTOrpWl=wKCD zZQ^kCD!hMoKjz5G1QWUqqf-)fre$1=7x~1a2HWs6A{;d;gHO{m+JlKdkyQ$FNZ;e= z)p$1NFxn4essQ1&GL}1|K>$zvz|#!>VabFW6KR?9#qm}W3fmL*;Oj;7#ryBJrcW^F zXAFu_sCm`vu#BNF>)>UzkByMlDoR*rFussMiHc$H;EYohRDtZ^N-~7=;XvbQDfY)dpffB_Vk)B#g!o at LX)xNs z at 99t17?i|T?M??VS}E at Lfl`Z;C1V_ at yuvUe?7ZL at 7dcIKw!&V{wA`1%rnJ=TdWT_Ac*lBP-+b_`%9+caxI zRyiWaY_+cu?+j(_$MCcF^TBZ=IfJgyr^9mIbi8y&BU}56EkgXNe`{N&2|y!1Hi9_e zsMvsqLmg_^1JmD^1rradD`PQ!SH$NOSdrmc!B7fv2A>93kAlnt-VQrPF-rOqNok=O4961P$mxhHVyVW%Stpn|5AzVsX;oA>Q~*>S ze6k{oDZ{u-9C7rD)82#xBrzG0vpu>qI$feGTDTnbd!X_WnJqMh`+yju5NiryN9&VfS$dWKZkU9|eFNcU zH!T7b07hDi*7S3evlO0i2r;88!njUNe>Xa;YOzd*IiS{d+H#Y2U{ZHpt%bwYgffiJKirgP!X=+Q>s`Pwn4fkEzzfc{jP$aTF(*QPM^p? zKAe+LNuLFEBtl294Cms9D)#gReY%Gs$9c2aop8x0i;(gtVo}e@$t8b4KS&Ov;HY z%Nr~5d0JtsO_{K(aoj4)@hc*#59DP6f6wnJOxpfnZOdr)*WVp+8dPsu<|0iRY*Qhu zY0f`Zk{vb4s@&%lnJ;$k>L~}t8l6q)lmg3%TS|RXIcM-Ns6U6rkI4`?3^n+w&`b+| zOlDbP)p)RB3f8Jve<($sWmyUC;PF|G{tdu=Z#|qbD9OME{@kBACqh*twerx?1}uy2R!rvm>;X+>%M1s^OCWzsO?l%}wQq!Oxe$ha`+}c~#a{kRuXRB7wi_ z*n*~H5Zv-TlFFcI(%|;Ys(6csavo at WZclNfpSnc%<1m;!nN)^d1i$6=q!#CXkU(tc z;r1RUYwm((kd5Ce%Gja`34RdA@^iw1SmT_yIycdr4h6D*l~3N6V-)C753NEh-Qcx5 zy^#qw?F=g#>`q8zHbUry5ZQIHJdi!|f7Js5e at sW1<|LIGE^5TxnH$MfmBhk^ zp-d{|;k&uQ;Sm^A0BSclD-vfH#RZ5I!Rg%TekVAw{(ipC6Jmo^bg-mn1aqv+1B&r* z%u6bJxI-*} zvFVcqrep|KkL8 at hlUdjV&QW_6dq#e>7B)=`A#!faOgw5D+C;*Fg$&D?)59ocp`36T zY#2&J7mAY{9 at D#A59#~76nitM#km{ZuWE7;D0_l1c1SAhg+&-kb-?0}z+i1p%Yib0 z;g at Jo`$5)anan{J*H2-JWjX+oG-yQ`f&RGcgYls3FP#9e{F?+6EGxKE;=t at ya3HI& zAFnU~kUE}oS<8hnB~d1+6BJznGHBOxL16Zf^2$59V|PaIL2!u8BD^&bih028obhjD=|jPUASVkO$0JMGhTOSu^*& z8E6ydKU8IJ0~=t>nZb}GM@*WFbds0Z!S+Nomf-3SBE;wi7s4+|;#_4(fDHo;}h4;F%_-AdVLz(UeG)SRIc3uuSTr2^=#8S~qwj zG&sbK7h~oW#2!3bq_gd%7?bSdkd3JYi7sGo$yeEbf;Z#C;w4w&$|>xyAWRv9q2&eJ zDVZq50X;wZca|z_(j-c9$!e{WV$t21)1~6kQ3w$30l~ncKbW}8MO=kQS at sGnNJKk$ zHYA}IN$Ygd4T?nW7GUSXvc7)2zKSM}kMw1zL;l!_=QlB!`@jXuoVT?|X2e<%FIIxR z9w#U+thv at 3mbz@%Gjp01SXD_nWb0Is0DyaVW(a9qG%=Dh`@apjDfAJYy3^?VF0(SB zgR3uH0+-_J`^25?k*6`?v#iuHx)mvzBQ}HL`;f4VV(|M7NECw>Tn$%7YVgGZlaQb~ zgKZAX#dsm{DHr0Cs9~&($bBF&sS%#$F{cKwTZ;E-2r`4@#3Dr|&PY-r*2t>PMszes zOHj?hQx4pW^zx8IZJLuMrYbgFrgK|KDBu`{7?i{_!7MoaLs`)CgXBRe!+N&Ir6Oqr z2P>nxN^Q05!BTDgJoKo*2r9_5WKPHLm!}g?rb(vGn65a}fxH8$g zj%As+C=m=utQ0qC at XDiMR3TY`j=LKHemXO+?FnNrJ)r%G;tQMR`#c))0)w=qNrQqy zg!pvDDZPdAO3QC=k?Gm-~2r%=NC#}$Vv zzzM{pgb-|_u&+yGk8_h&P|GkwGiVAsD2%AqW+YfpXb&KXf*idd9{%v-FY#tlSGd%l zE-o98pcW at xti52f9SlW+xm@{rc1#KdT05BQu{{1p`EPEaP(+Cij9XHeM{6DYX9Hsw z!_{*+?*s)6i;BYN1_ui*bUE-zBo&ON3Sf3`h)vOR at +{ zHFmH1@{$B~atGayiCcB4XUC#EnwaRc60C!g=F{jTwspQsZ2Ro-K1xw;#bhc3k_O3w z1%8S?z3DZ811v$R6IaDR9PODl584B8o at G)Jt0RY3{e0?>2WP|d at 3{N_q0u~DScr5k-NX&n&QHm1a$m04Fo-JDCX(BR|~5|bn}W at f?RSusv~Or<7= zGuUbh1EO)lR>9j8Pbt=s+(lrphM#<+tDI&K_G!=()8!u9BL at wZu*@sE!IEFy#R~K> z{lOoHM(GDlfMm>F#a44(#{3xO_>&50&Wfci)Gq{&mRgvyEVeVJbv)9^D~!kU=rErv z&a^p$pLaNYUn}vR6r5t6eJD{H3+wHsQt?f3p!}4~S|UiLJ&% zIpgr(gA(H*BtH{_E=iVb#ayW3Rq>Y%LWcKiS-i(rWpN~zW$go5h1W!fO+$tpcyEz& zM;kWVBe@#YcS;nvKYngjRYKT}n8}Sz8syZZhdqiBt-Vr;r55)X(BSBge<+sBUU!+< zk7r}=S1G}l__!2#CxRhaA)jbkD<_`MpjLWG-xotw9r>4R~?OmGT#tKepDqH$kXWR z6NhWP at Hi%W0Lf90$|6A5(dqgG1j|b4K9kpg2Xi;5#3<}c>zCakwtt!%;#tt1{yyP zT=hBeHgB*?&{>z38$8#Buniw=II(=chM^+qa z^u;|VB{+b8pN()%jFooD$o#Y_EEqKP at lG^&?oDemnC)pagS=@sgGPnmI_MDdWmJvS z6<8nrFexAQ1c at Hy;RbbdgBgV5aCCtWsmn=&NLOf@*scZ%7d~^ELHik6vLQFz)M+~KKc&WE$eD^*JgH{ji?%#)^ye?@RwJZ zq#W1yn*hXd(x1l>AUuxTml-h^oj&Pr3i7Ls80S5U at HY)=3(9Qr83 at Gc$kyK=k!51B z2{(`+;#)77(V;6WmrUt?0v5%aG3*&1URGn>q+8p^D>UVZWd@;07GO~ZB$$gNEV>gT z{h&t`&WiCP(1<8?96CM0nVCe2xF9hSOj~p*#MW0%z{X=ta?ss=KT3qZ5t~6yAxiPXVV8XwSHl-2IWN-59utHxp?5A!5FW&QBp$|z zGAumkYjnO%_cJi)V>;0%=Onliijc$|8UzVOK4w`(c*xTqXbm2np!$Lq+d3G}BGi^y zy^0LZm=nj4u3$0jK^)^Dn{orzi$5WT=W;>#(GSv;e$kaF zyL2xZ(ban-hz$l6{y<;QF!+{}J>D%eo}n;{5rPk*_+&l at LCjrFxYUensX7J@Cl+Hl zTPxJCk>E*7=!Ysy(*AiBKB6;mo)+ov(7;R#W@(J8gFrr at U6x3)9+sR!wae7#1a*lM zTXZ;WFtS?dL^ng=PrWLCFeSLIt{9J`0SUb6z+~zJ=lT=fYT(hz at BR`Zc%2gsiY%cH z>9Qb4b7O7J1P4y(v7A8S{P67&b*r@#OzQZY|GX$W7$v%Ykr)&(g8nvGGjhIB@y|n|>S7e&d%68Jn}ga7IurwEnGQw%u8olhE3rZmU0{`+c4MX^NYMWo2eJ(W z0~OBA!w4>9TUxeB8rvf~0Hqz|bmVkAmyY&GkwN1= zwt)Mao^dHzeY0gOyU;La{1guZAYNe15P&oZJ1`^y)uE17&&3mTEvnj^Ss)?hXT>l(Z!Q$`2HopLOO^o&;UGcesq${fr$ zi4b9O!H_0%yMYBc?oP_^h7VSSO&a%egUAU=C6;yKJ*gD|E^Lu(=m0lFr++>W;my#M zxvs^QkTL!P6qgJ=i*C)~)XcdIvqU#&kb<~I<1|?*E*AAv*J9w$!-G&IQ&Pna!$N|) zOxA-HNP2D-0(ECq8R*MJm;zaW*H2F)DXJb&!>Gz_f zc#XnpNT>K~?vSLyOZ^e#r!Am at Pz{#TXF2KZRT{#lg$50MKOKnB#c*Y=&o)|Y zJZBA|;!m;H$tmE7Eg#lNZq- at v^EGo|I%w5k*cHWgvIjm(V!@8F7$YMbvTEVA{N1m}o3`G81cgDP#J4am#43!4c3sy-o z56+!cIteS}*bL!Tp3#m$iJ>sY#36@!rC3u6Q<)Xh9hC7%TyfYSPGBvSmT0rDQ()$@ z46`L<{q>|mwyHbSx0wvtpo!xtV3O2|@*C$HJ%7U1$T60PAQ_BnrERHnipJ((nqzlc7Y(&ZJBiu?3#PJN+N zVWO`oi~Ug~s)@TZy8U|rB1-QFRJtG;8mJp3qw4qaGDL>@tO#?1WHaK+;^cHfLRtI_ zvl_A0XbfLZ=BAHN5#Hx<9zW^C!4?M7`pD_EE)7Y-vTFqx$|U1&Aq~!9)&v-nJ1xB- ziP2CPinug{mzK;lPI1(rr4(X7_pnEB4zfcxA|@lwpj$H3&b7V}BTzZ40{-)Z?8ZGmY%VrREAl$(c zV8GeGFX;t?2>(n`f-{eNT6`i)EVZTMVcTFL6*fA;nMf26niWXR0vhC9(4b`neO6pU zam3B&G`M5&F-2ckP&X?SWnjz#ytAR at 1!X#y1W7Zz-y_B(fs-j+ud`ch5c=5tnCA-) z^`0Ai;_(kW`}gOk6$FEK4_c)wQy1bSO&ai1TLw>kh^8?4)jA&*J`8OFV`c&yP7+h- zXZJ^W6;W~&>Y&0ziGxQ4?J$K=x7eK`0 zVI?VpnF;mCpFoE)FAnNEQ^Zn-Ybb*Lno`7gkJ*Gp=nivX_JdlA1BVivmNU=ImpO%D z94|ACu|KjSPogubiKkJdhvQIeLY{PYYIs`4!`cXX zGswA#PKO733A~v#c-{&Q==x+j0&+xmi0Epc1)0|3jD&i)WTl^gU)f`NKFd>>B)k9T z>f-LnZW)H~W*TD>*=9mDJ~?Jp4po??J>(`&m{f$%>}k)dmWOdF2fnaXERUlT4I%;RKL>}*QD5)U zMLlkJf~Ja3Y#Bo%dcZ&&$8?BHOKxG(0<7Q at CDNk7*e3=FHcp-<3S*BB4?`^H{Ynp* z64F$}I;QJ_Zm88SlGKvl*ZD}xomOw!X%K_qdNu`6Y1A{;#OEYojOLXU% z#Ex&J3{m~jKp$Gdxc7C=bi7vZUHf#S9M?i4NMokFKiCHHZFt0_SQN5m`LiK&R*M~o zhC2};J}a(Z7&DRT0Q54edmZDs7mt{M7jv|Kk6!?PpBMbWi%PsI4jc5R{O5376o08P zB=@a~K6eJ!7!sQ#p6qCuNq6bm*Qn->~2=WA&R1`+0_FmH2Kb(o}QkPI23 zmh_sszvG}cp};U at G_Bfd2W_DkN*Hm$+{53KFeDaVk=A;~D*N;GGnp5!CTE>QxHov?o3ADPsc+a)>!16 zWmc?abz@@8w2WjF42CBoI-x&H$xMuD?H|E;`G}c8 at n;>O?bTh_GM1i(9gWYmGRBn( zF>uJi$P-E=Qv4nklw$@DE9TG5 at WAUjeQ&9g{Sai11&~;B)f=92EH6UO4$Y z>M-5uKjFZ^bVWaSeW^WN^@JIB%}MdB2W-trCJX4Q58=YB<>Y<&u{lZSa2eMm0mqS# zr3+`U$l{6k8!@O0K|CgZFwtPICU1I+woiIN`>CMUfTFL_i6jOX<+;RQKRf_}izL4W z2G7hH$K1lPuxXNGTx at abR!1nt;OZq-TtZbLF&t5gTH`WWgu8lV#2THANW35)^2GE9 zX)uJyXhRXs?IVFe~w;@^C4ZpFY at YH)t`!|5K~=pR{vtHf3-upsRp^D?r5K#v{huNQI|iri5Xe=B~WP24k at g`X*PkeYAh^R%|}#_V108dqv972yVMdK?-_l6Iu5~uO%$>ROtpAflQu~# zSha#HPTddM6C)7HWYs`FxKKs$nk%s$@6j at +MG@PXtEC}q@}HiA$*=dvv+E<7k{>7( zc-9eaD8z$tp<05&1qW{6!1CykEgt^Bb>bJ;a3dI>kj8%f8F3k0m;Rt1E+brMvY05W zGlNxNi^Jm8*6Fki&ygIQ7#WNwj!0IpyGfj)`&C*i4qDwmA3FH+T$x89Ns+<@U4c%s z1!XRGLlvVTG#7dxIzAmFoPWblLrpMq%LQC{Q31)uNyqr+ at Z5>P@)PgK|K2x zv!GCrr)pRmLeH}Goc_gPK6<*@Z{3 at QHUR@saM?X}v;$<-a1{uzT;U^6)=mh6gekPf!yv2ycqFIG!7}(QUkyioP=QCx;R`;B&L$(rMYW1OT=C{t9JyzfR^PL|!NmS0V1pv}4q>^oLfDDU2yiLWInb zpVl+p!ih2b^(66VcccE4+FO`Wqgzuzu^e?-4C;rFA53en!MIw*_(2V_1rWuV5&m<3 zumeD>FJ)3ZXprX&Mpz%eFA4AeLM(XR56Y3N(hatZ!L}F{?5QnC02&9*R*gHE1s#jb zxfWMI#7g^U&0wbjkAv}|(x70kbY>V^C^xA9_`CzZ79+SBodjlN507mY%%& zD_tEP3>{+IOeRJe3ZxlUz+^67=A02cX2%*@ZId95Ig+Sg2Y`~?ACJ`$>S2!SLX+Tu zOoLG4q{8GAbo#9JB#5tk8SXE-vnZphgbFhe!LW8fTZ?^wR*J*AB znz(cLzL(^NGdKf#dsv3^yUL;#U at 8i$hjKEyN-=T at L_hGiTZ);RL3)3{F at ujO=@N~t zdJA}7k7Y_XNINJ=uG|a`hCVrK^{FAPErNp~LY}W!Xh(7e$DRLT-QXn;u08mvHC^bh z*%KZ~twps+XFk&E1nmi6np>R>gbhW)0RI?N$f)2jjn)W$UON?vG>*sNSY)e?DgcuG z;mGE=D}P%GY`FkwM+P^gKD at +ajt>PxTn4Vp^Cjls3xNDn$0N9!*)6gk26WjO)MFJI z;In*SqZ4!#hCrF|vVIC;eI8vB#OwpB%*>vTo?H>5H7v&r;+VJUg7}?|sKPLm%);P6 z)XY`~e~o&~AEiMN!vhxUI+fVZ_AokpmcU>H6rMPBP~xh;PEvtan+I2UJ2$3vQr9_N z1_vB9rHI)v<;71~os|qHq^IX at 5tapF%8{M?<}xsuNPT*h4U&oMmuRq9gW3_^g5Q=u z+6GfKA8DcR={VHDAg3aGXyz49VImFsKHcZ_E5Wem&A1ue{LtjV-105J+X at tF-zVi5 zfx%VPu^NL?j(g}M3g;-qMSM&s!O;>+Kr$ZL{h&oVT_Mj$&d0^clG!%jW{=Oe zV2Bc2PWC+%@e at y=JIZjBW;xjs26bGLBm5Pv8iQ-Ch+)SV4eFRUM9C5 at pw?txAK|=Y zU2|9_A1_#)tdM7QT^h7466`T?t6h0dnJ}P+!YoJ(uR0gvlk#gg6iy6}5a|SqYn at 19 z(4e04wrzYiY&;Fok5+iSenC$ruC6ALHR4>3Ju}$UBEX)oAd4z(E!ISduT{iR8a8SMqhg)t#Hv!-$DWjKO? z!;>g4|T^fWToiZ~)gfwI#I2e0NuYE|T zCl3BHNd;cbelXEsrHQ>WTvx2oHCw@$+|A+6(3r88B#WH}sf5o^NGBR>czm1>NkZ}i z2kr+AmR2wh94yDZJ%aqqod)dsu3&;8)W^UW|Da$@EO?&ULMwQ<(qawT3dt+RCE`y1 zY6MuW77uC-ZWz>+Kiv;c(~Jp3QVOd%V;$hC=*!0{oTX(9jcf+X34hsB at 3>J!!OnBPKtH0cpFu2MM z6-gI>%i8J)XFcQ+%y^VubbvlMmLW-IL5CtS*w)2-D(4hO4Kl1S7A)FOSSzqb_$h;x z2}yxO-N}5;0q6DDCnQ%JB>o7TI9(=Mz at sT-SWt7U1$kt{U+ppsu0WsCAmOL@>!h`2 zzoP+IaJa6lyv^+NI{VT4RKwuGvok%Wnft_(C%4=j9P>GY599}Nu%IF at Dd3CjbRt1` zgtavt6nN&zAS#2gh at 7&8oR&QP0lnbsc>M*NK)D*VSdrK|cbclCF3JR8dK}#v-5?x7 zlfor4y(apbW>_;erMvP5n`gTZCGMtB&yElD+m&!-hRT?|eX)Y(dD9S1c5> z_8zc%YVrNGX|UxF=4CoHWrX>9NtreB`(X{DAB-Nbv%>^K2Kjwg#pkg>J7ZOVSST<= z$~emR1mEb5A-0zW{~Zm0Kw!g at v9lDYM4}#xGd(0O>~w*Cyj$!P$L)bdJO(VKW?sKG z3 at aX7mKcUCOM+mozjAA1VvdUEBgeox%vo{m$|MOg$DCN?h-Rm|ku4pIOq3u6j)TE( z%&hnDCC;%4zbZC!!h;S(p~2|?Zlty(wTe;QD;^Rn5916s<{1Y~w}!>P-bD=R_DH7= z>u9dxz;yDtgcHP^HcJNWU}%vB5P9bBe4$8)L`|;Jr>;*X-26u at 7 z&kzV9ER|TS7;&l78dC#&puiW`he?1juzPM&lKptWU(}o~m+~O+r|H#HlNp=NNE8Sz zfx at NKbI5JuQW+eBF|OOz)Va?dKet-W+WGW9xyn`5pOyp$&{$`VV#bM6v+ln1Asz(dbomg ze3+ADbEe6K0A9)qmU;2>h3LYO-9Lo!fj&L$?Oy~2S3#gcu=b}{!(;_O65_n-MF4vA9{T{`2?BpnAbKr^J1qdL=fwS<5L$%6gcT_*9nL8s9!j_+ zOTd2;UG1-s%;3WZqJp at YAJP`4`7q`pU_;n6=FOB>>jqnB#CaZILC!-OD)^)z+jAd+ z2}^XfY|-)XUyE#&b-I56iX<=?fvrS?Xa{S7+`1yHrJqTIfjOySwj4sj7In&tqfN*sG)t9Xf>z(l4UwOZ1B{Uu_Zbw!O;hr z20>P)fbC(?g#cQs4qrMmn-^jtG5*m?@`)bi3R4_27&3T^sLT>=pZJ<0 zH*IhVjO_z7m%?wnZX27!GyBRG&OXAjS%&=g=Cb-h9Puo(npQrD=!EhIjJ1`kg#4tT?$IAVgYD9(H&-7n33dQlhin4={G z$*xDF5o`fa26kxuk{9h(O>zcvZWW1nW${b%3E at wJ4|)Ci&ETtGFl{f at o-{deZNd=#bf&-+ zOU1#0E19r&`N3M^&~%89_8 at 350gp^b@|*Oat%tx;@mC73nlpFUk7aqVVhqq(a=O8R zB7{|$l*d182ujDpm-JktXFT=cSI1slJ}(w)CWRFgcC9TsfKUg*ZV1YBq;!7?jU}ES zCo4>Sb6P4f6I$ZTBxCTAT0z7J2l-1<_ at ST4f2krjsnApV#)=lzI>}DUNvmoLZX1Wh z$C(OGIsg1A!sOQ{z;acyy4GlpVq{(Ycr6Pl%=wo%6hUHC*j%D(cru-XJ at jY~*5O@$ zl*3hZnJ%t5wnaw7RmU+1PkO(ROxP^R4fG*AjTxOOl8u1uN)qn|b&oS>ka$F3`WeT0 zlu?MBI{52f=m%RQ*w7 at Y4lmRq(HsUB(uig?mI>Vy-JsQAH3m_DPa}gGmHB*m%=`O* z$QJ2uj|>j%3HP6=NRs$Uy=fbi49bPwmQVoc9pMH^A{X6iB|%xFGjlM|cYy90qv7(* zhwX()@vkq1?UGqtvZo0~4n~$kfYc&{N1g+bGnPB^e0U}NrV50!0+IaJOv>1Y3QuT|NZcQU_>4o|I3+3POshA| z>G0>Z$^EZ>!JA2rj6x7bVOx%@46fRFgE0otg6xW9t?-uy#4d{)ID7~XYHTou#giSn z&lYf?H;8Yz*zVZ7b;+2xrHS*)RZhXqmkEk`xRUm6pJF1Bi%!N at Y) zIcgPsA?G7ee^>FOSA$*_#`(ZsW>>q#cqST|o<>||r#&FhCv=q}7C}5 at aUv4=tOO6D zMv@=~@re{U-yvaCTKUg4 at rDPqKhga8jBdYv&9n?J=?B9Z%rg?P1#54xbc1*@EHYZU zIzm-a6Ce+r4p%z+(p^tR&__2$3h!k4cXCfK%$PB`f@%vU;GMfNvPtYRI6H*Kf+rDb zijg3~@(@HEHI2DQ)MGha at Yjw`+f{flsbP;QtgC+80AaaZus;X=VN*I*Vnw3uk4#>T zZ!9q#w<8(wfZs62mj?=CJo4Ln8I{C?AEXwiAGfJbBmB&0!H70nW=lDlo-BSg6bV=A z;?ADK6tV+hN&i6n)uDd{TVRhCjOEBBOZghXlKxAUI zjEhl)A9I{UJp@(k8Cf6 z-oGFOdTvI4%#T^MF-)mx5TMrKS;r%p_*%W7HH5HaPH9jV=mL8a5O~}k7GWvRkCD$p zl%`O2O2m~W@{%m65UBZ>;6YoSb8pc0+Z!ZkWXKr*D*_AU#K9NM!Ot~1o~@?=unT_BNK%(l<0K?w~?gsY$sL77!(us-6bc|D{p6iMu| zqn_A_H7!awuvSGDBsqPqkI8>lk9oL<8ily}xtRlh8>S at B64=5vaUn%iU_IaH^~1w5 zkH0Bb^Iu-C>EmO%|G$!~-8fLLy8WIBhRAZ5nwntc^-cj{td|r|#6 at 7#3 at 4wLFXt=dK15J{5?c zmmj~;=VeP_UDue4|{rkq at p}VLs$?vrIxT&m`Q$JQCtP5YkQ_D!l#2`H3p>) zKZ69TGcUs#L~QjZhGiZtB79t8uz_3QL4jY>qQ*T!T6!B+V9S=y=o1ubcwdh6=tWm& zQ6n-ej@(S4K)FB0#FY$j{c8bgO;BGBNn|b;AJvkv=)PjHmzS}#R>O$d+?eJ===R^3 z&HY3Q3^dgkJg9N6#qEZ$#hs=}Jhh7H+{tW2;#!=J$m|%zs9oVyow&-(AP<+hFsFqE znH%JJN4pG;O6+s-H{`g#2p1DeETAw}= zfh`rqKLv)ZVq8!;e|)st!ABQ^O)*kwlByT9dB{pW40X&RY?;**QTJ z1gnTv-2aS$aZQAemQ2b22TcsdR^7 at Bf0o2;CS3Up98D#t7zvvuW3R!2(-gA!Dzz$y zdyKKsKIy><2NdT*g at eEl71>I#^nJ;$DUziDM-R&kTwuy2dD9BqEyadIT7$)viKE_P ze~!bsqKo744?pv&G1x*_^`G==ac&3Ksu#>0SdpLCUrvaP3T0Y{W2faAOgXpO-Y72N zEFBY1crdoA;!kJhnAHsmK4fOH8<=Jz?F!s1#sHpjo^CK12R6aC)f>g8N}?M)9!2u` z|903VChPrp`V(XNkrAad$nPKedcuSGnRHad9VpslM8B0`o#t1a1y>a#)(Z;sdH{?Q zwj`-FKMDoTk7ULe_oY at a6w0JeCD!t6nC!z)`atS(z9PhYtBERrnzgdH{oh_5ITWAj92pqyKr0!F&q88iOb0*f-iSGI3Yh$8%+u zc%9v!o-Mq8A&D&~IE4`AtZvF|I6ssI3(_Wm+^DRlz2OP;@>g4;!y1Yw^ARwiClw+r zbELM8V~$i4ogiN$SbRbpuXHL7|R zmU-r1`-v^O<50IK!B4~Di^mQAz)RRGu;+|MK;r9%5MX?+{)aOU3IGrI*%qAyWR{ec zG3twHUD$&Fpj(5qWhwyniEyMr_#g`XD4B at _xU1rc40yDErhgkQjj~|X6Z%+K-wPdr z6ulp8u^^9>c at mK91cL+*ag66bq>;!^QN+f1<-_0PFXf+km;Srze>r$Cw}W89c5Kzk zt|(rq$<_s`J*bCHW05%TFm(#?V at B@>COoWZDazzNF$ag=0IN&In2gl?cbQlf016`l ze#sJa4($5VP{F>^Zp*~xPyhDKgO<#XKKT4$@Ofy*C*M_2OD$uU2gRDU)U;RSDmJLH zm}3&k6ISE{Z0mmPeq{@~D{}(E2}e%bkZzcw5Q7UVWjy#PgRVgKw}${xDX?3H<7N{I zlnC`un9rft8PQ1}xKW7cvq%d5n9+Kw1pXNZ?>hzqWr{%oTf$)VpZTSk1u+LJ2E?OA zEIj at B1;f)WjHL=^YbLSa*)CnBNN}1*w0`UWL4^vwI8|AME5zwWFr<0DR-AkJLX!2z7vow2e|}U|BJgqy%71pwgPz_`B{&u2 z@)$7f1EoR}iXl&e2Ni-VCv-&;`+%fn%cv_8#|%#K{2RPzYpD6bp`1fK)HE14M3;!` zMruL#iZ{?;3hi at TlCkI;oKloGc`g&Zq`HASK|c+E^GFtpRB+m(3a&Hku%J!K2P<1M*RSaG)ndDhINC;kQy#iKqC zuIfHJ0J-Img8%jEaN~R}Pw^wO at WMHR1#>@biLOCtW)T{?x)?m+!D^|qbAj(v9V-pa zvw)Evb%gr&Hi&;GY!t>DVs zKKGdJT$+>lJYefMXF5*f|Gdgpu|B2PJ3%vOvLS==E9e^&@IZO=p%e0|*<6SJ$behP zN^2PoK_Y at U~OmkZ at o?8W!l{&FTkcm}U zVQPuuQhy__MHUQ)dSYURIjuqdFc?Wu{yZ6R9=y0VyK!HJzo*S03b9n;FsGlPge5D( zDM@)&?Ca+(wRd!JFk(;seuW2VQhSCPNR*h?YyeagiXkX3$Eo{sI(J(4IDx}U9*j0n z;h zGZOeW0W}B8_>UgczRZKy*SwSBLb3}i%AAf2NO$Ha!I=dsbGoO9B{0LFmTeL9+6gf| zk8Tn#MyM~1i*0h;M4k^wd~<{%Bv~@UDGe4qEIF;CA>6r;&uQm~Bs#(BVRS$CYRpL_ zpwOkeul9pge$`8H0bnV^OAO}<{xm9sUN9BnYO$SxnH~{7X2Ig93v>WtATciNw(BV# zXaJAz9|?_Jnyny=K^u at fDTxu!irn-wf?Chl&o*al5E7t! zR5WeGx$*3!E-v?6plfJ7>r04Ce)sY6Vi0TNi`P6T4(yoO6SA&4mULmr*a!Xt3=FGQ at 8MSyF@jO3eH|02qK{Wbi1s zg$n1#r~p*TOB#4&S9lFSDZ_00)RRF>Yv?qA!h?(q0)U$yKpi9qUoa*kg1(>4hH0-Z zt_p*Xzj^#kzDxfT@>R)EH!`bWKiYyKN4ykkLn!iCj2X!b@<+1MO)j)UWj+;hd;kp| z9pS`7j!F+`foZw%Aqjye`GjUvfyRsiFKH2 at zhnŠ9EV_honHL^P7hxHuS6}}^ z9twN&3QfZFV1#8nHa+K)esv^bV>+S1&kvIVk#L-BfwPlX9reQ`95}+HK7f}NIzfLW zzvb?<%941MntaBF;t}fFNPgR5Lfp at 7DGO4P#ACr;+jDaU41fB;)lQ8q{OU*VpI%mr zJ9RkMWJ4h)#;fD61v~nI8d*J2#Nx>W?xuJwF<j2|oFd zp=Y*qV!^r$Hx5p>1B&bh&7Ywrp7SLK zO)DE_AjV^tI2{@J&Vt-z!o5E%t)ZQ%U=(Q4nUmbULT at 7#ZmB8UhD}k}Z3dkSpE at Ky z81N9rg%35WP%Oa%c|ZNAw;y-Oyts+NX!>~Es=0u(eKIxD3MwErc#&8IBn^gnPVC!r zfwOYB>hTH3?fYCL*W{}zyruh107(8z0UGp%g99(7!c}hd6|vQj!B1E+v}S(h!3sa= zDz$j60)zD$-Mp at P?COjFjY?vh6W1)rA}rp_g*P3)`|+v=5Fe!_JkndCi-&|p{m}Ul zcltE35N?dKppNIP6|B}t8rAse-5|E<%o&skv5!Z7)|mKbT*iZaE~E-}F%nE at s-#(l zWrfL-3IV_d7LUnX#Iq)pVVo*+gau1(CV at _BZ(3w}2>jd}Y at u-5gVRHeJ^m(3 at Xi8V zH6IE5;Q{U3E8fcbTmc~Ss{gH^5dC2331`P%Y5A6TAmTzi&g=@4Gt=JW%yAicY4Beh zu!WDKFk%$sg1(0Uue|fb)he_%*eUn;5h^7>z^{dClJr&}U5t-0!7>hg+8IeMQ`Rr z90wV$3dCT;0>4TPPtq68GOLrrR=pG_?8OSFjEI}?E8I6fqy;S7b3+1PnGsW+G+3|~ zm+_oJs>ef4O!tR$sKN<_$NpRym6Z4v&sO*}05^LY*ndM;#e?-4F$2>!qf-qcouKld z1QaGKz!b#Ig<^WsVaz;OflCLtFL=sBAP(3LWAb7edV~|ZhuJbGESMHKtr0xnCP9TW z7*?YZp5}32?rA5x$&~K-G!Fc4sUf5;UK9B8kggij%}C6cASNN!rC9uFi7Vvmum+<6 ze6SvWZqKPrSQ^6lQDkwC`W7&hc1XQt-Ik)(-&l1hGjUuYz^w_I;_H2k_zPk lI1-zF#))r8%IG%nk6oktnb*Jm|God!{~!0la2=kw1OTIPExrH% literal 0 HcmV?d00001 diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 66927be..6dec58c 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -8,285 +8,46 @@ network --device=eth1 --bootproto=static --ip=192.168.50.2 --netmask=255.255.255 %packages --nobase -%include common-pkgs.ks + %include common-pkgs.ks + ovirtAppliance +%end %post -exec > /root/kickstart-post.log 2>&1 - -%include common-post.ks - -# FIXME [PATCH] fix SelinuxConfig firewall side-effect -lokkit -f --nostart --disabled -# FIXME imgcreate.kickstart.NetworkConfig doesn't store nameserver into ifcfg-* -# only in resolv.conf which gets overwritten by dhclient-script -augtool <> /etc/hosts -for i in `seq 3 252` ; do - echo "192.168.50.$i node$i.priv.ovirt.org" >> /etc/hosts -done - -# Enable forwarding so this node can act as a router for the .50 network -sed -i 's/net.ipv4.ip_forward = .*/net.ipv4.ip_forward = 1/' /etc/sysctl.conf -cat > /etc/sysconfig/iptables << EOF -*nat --A POSTROUTING -o eth0 -j MASQUERADE -COMMIT -EOF - -principal=ovirtadmin -realm=PRIV.OVIRT.ORG -password=ovirt -cron_file=/etc/cron.hourly/ovirtadmin.cron - -# automatically refresh the kerberos ticket every hour (we'll create the -# principal on first-boot) -cat > $cron_file << EOF -#!/bin/bash -export PATH=/usr/kerberos/bin:$PATH -kdestroy -echo $password | kinit $principal@$realm -EOF -chmod 755 $cron_file - -ff_profile_dir=uxssq4qb.ovirtadmin - -# for firefox, we need to make some subdirs and add some preferences -mkdir -p /root/.mozilla/firefox/$ff_profile_dir -cat >> /root/.mozilla/firefox/$ff_profile_dir/prefs.js << \EOF -user_pref("network.negotiate-auth.delegation-uris", "priv.ovirt.org"); -user_pref("network.negotiate-auth.trusted-uris", "priv.ovirt.org"); -user_pref("browser.startup.homepage", "http://management.priv.ovirt.org/ovirt"); -EOF - -cat >> /root/.mozilla/firefox/profiles.ini << EOF -[General] -StartWithLastProfile=1 - -[Profile0] -Name=ovirtadmin -IsRelative=1 -Path=$ff_profile_dir -EOF - -# Create sparse files for iSCSI backing stores -mkdir -p /ovirtiscsi -for i in `seq 3 5`; do - dd if=/dev/null of=/ovirtiscsi/iSCSI$i bs=1 count=1 seek=64M -done - -# make an NFS directory with some small, fake disks and export them via NFS -# to show off the NFS part of the WUI -mkdir -p /ovirtnfs -for i in `seq 1 5`; do - dd if=/dev/zero of=/ovirtnfs/disk$i.dsk bs=1 count=1 seek=1G -done -echo "/ovirtnfs 192.168.50.0/24(rw,no_root_squash)" >> /etc/exports - -# make sure that we get a kerberos principal on every boot -echo "$cron_file" >> /etc/rc.d/rc.local - -# make collectd.conf. -cat > /etc/collectd.conf << \EOF -LoadPlugin network -LoadPlugin logfile -LoadPlugin rrdtool -LoadPlugin unixsock - - - LogLevel info - File STDOUT - - - - Listen "0.0.0.0" - - - - DataDir "/var/lib/collectd/rrd" - CacheTimeout 120 - CacheFlush 900 - - - - SocketFile "/var/lib/collectd/unixsock" - - -EOF - - -first_run_file=/etc/init.d/ovirt-wui-dev-first-run -sed -e "s, at cron_file@,$cron_file," \ - -e "s, at principal@,$principal," \ - -e "s, at realm@,$realm," \ - -e "s, at password@,$password,g" \ - > $first_run_file << \EOF -#!/bin/bash -# -# ovirt-wui-dev-first-run First run configuration for oVirt WUI Dev appliance -# -# chkconfig: 3 95 01 -# description: ovirt wui dev appliance first run configuration -# - -# Source functions library -. /etc/init.d/functions - -export PATH=/usr/kerberos/bin:$PATH - -start() { - echo -n "Starting ovirt-wui-dev-first-run: " - ( - # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=451936 - sed -i '/\[kdcdefaults\]/a \ kdc_ports = 88' /usr/share/ipa/kdc.conf.template - # set up freeipa - ipa-server-install -r PRIV.OVIRT.ORG -p @password@ -P @password@ -a @password@ \ - --hostname management.priv.ovirt.org -u dirsrv -U - - # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459061 - # note: this has to happen after ipa-server-install or the templating - # feature in ipa-server-install chokes on the characters in the regexp - # we add here. - sed -i -e 's###' \ - /etc/httpd/conf.d/ipa.conf - sed -i -e 's###' /etc/httpd/conf.d/ipa.conf - # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459209 - sed -i -e 's/^/#/' /etc/httpd/conf.d/ipa-rewrite.conf - service httpd restart - # now create the ovirtadmin user - echo @password@|kinit admin - # change max username length policy - ldapmodify -h management.priv.ovirt.org -p 389 -Y GSSAPI < /var/log/ovirt-wui-dev-first-run.log 2>&1 - RETVAL=$? - if [ $RETVAL -eq 0 ]; then - echo_success - else - echo_failure - fi - echo -} - -case "$1" in - start) - start - ;; - *) - echo "Usage: ovirt-wui-dev-first-run {start}" - exit 2 -esac - -chkconfig ovirt-wui-dev-first-run off -EOF -chmod +x $first_run_file -chkconfig ovirt-wui-dev-first-run on - -cat > /etc/init.d/ovirt-wui-dev << \EOF -#!/bin/bash -# -# ovirt-wui-dev oVirt WUI Dev appliance service -# -# chkconfig: 3 60 40 -# description: ovirt wui dev appliance service -# - -# Source functions library -. /etc/init.d/functions - -start() { - echo -n "Starting ovirt-wui-dev: " - dnsmasq -i eth1 -F 192.168.50.6,192.168.50.252 \ - -G 00:16:3e:12:34:57,192.168.50.3 -G 00:16:3e:12:34:58,192.168.50.4 \ - -G 00:16:3e:12:34:59,192.168.50.5 \ - -s priv.ovirt.org \ - -W _ovirt._tcp,management.priv.ovirt.org,80 \ - -W _ipa._tcp,management.priv.ovirt.org,80 \ - -W _ldap._tcp,management.priv.ovirt.org,389 \ - -W _collectd._tcp,management.priv.ovirt.org,25826 \ - -W _identify._tcp,management.priv.ovirt.org,12120 \ - --enable-tftp --tftp-root=/var/lib/tftpboot -M pxelinux.0 \ - -O option:router,192.168.50.2 -O option:ntp-server,192.168.50.2 \ - --dhcp-option=12 \ - -R --local /priv.ovirt.org/ --server 192.168.122.1 - - # Set up the fake iscsi target - tgtadm --lld iscsi --op new --mode target --tid 1 \ - -T ovirtpriv:storage - - # - # Now associate them to the backing stores - # - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ - --lun 1 -b /ovirtiscsi/iSCSI3 - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ - --lun 2 -b /ovirtiscsi/iSCSI4 - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ - --lun 3 -b /ovirtiscsi/iSCSI5 - - # - # Now make them available - # - tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL - - echo_success - echo -} - -stop() { - echo -n "Stopping ovirt-wui-dev: " - - # stop access to the iscsi target - tgtadm --lld iscsi --op unbind --mode target --tid 1 -I ALL - - # unbind the LUNs - tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 3 - tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 2 - tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 1 - - # shutdown the target - tgtadm --lld iscsi --op delete --mode target --tid 1 - - kill $(cat /var/run/dnsmasq.pid) - - echo_success - echo -} + exec > /root/kickstart-post.log 2>&1 + + # make sure to update the /etc/hosts with the list of all possible DHCP + # addresses we can hand out; dnsmasq uses this + sed -i -e 's/management\.priv\.ovirt\.org//' /etc/hosts + echo "192.168.50.2 management.priv.ovirt.org" >> /etc/hosts + for i in `seq 3 252` ; do + echo "192.168.50.$i node$i.priv.ovirt.org" >> /etc/hosts + done + + # Create sparse files for iSCSI backing stores + mkdir -p /ovirtiscsi + for i in `seq 3 5`; do + dd if=/dev/null of=/ovirtiscsi/iSCSI$i bs=1 count=1 seek=64M + done + + # make an NFS directory with some small, fake disks and export them via NFS + # to show off the NFS part of the WUI + mkdir -p /ovirtnfs + for i in `seq 1 5`; do + dd if=/dev/zero of=/ovirtnfs/disk$i.dsk bs=1 count=1 seek=1G + done + echo "/ovirtnfs 192.168.50.0/24(rw,no_root_squash)" >> /etc/exports + + + # The ace stuff. + /sbin/chkconfig --level 35 ace on + mkdir /etc/sysconfig/ace + echo ovirt >> /etc/sysconfig/ace/appliancename + + /sbin/chkconfig --add acpid + +%end -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - *) - echo "Usage: ovirt-wui-dev {start|stop|restart}" - exit 2 -esac -EOF -chmod +x /etc/init.d/ovirt-wui-dev -chkconfig ovirt-wui-dev on +%post --nochroot + mv ovirt-splash.xpm.gz $INSTALL_ROOT/boot/grub/splash.xpm.gz +popd -%end -- 1.5.5.1 From mmorsi at redhat.com Wed Aug 20 19:45:44 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 20 Aug 2008 15:45:44 -0400 Subject: [Ovirt-devel] [patch] oVirt autobuild rpms Message-ID: <48AC7468.6030704@redhat.com> Small patch to include the generated oVirt rpms in the final build output -Mo -------------- next part -------------- A non-text attachment was scrubbed... Name: autobuild-rpm-gen.patch Type: text/x-patch Size: 626 bytes Desc: not available URL: From berrange at redhat.com Wed Aug 20 19:50:40 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 20 Aug 2008 20:50:40 +0100 Subject: [Ovirt-devel] [patch] oVirt autobuild rpms In-Reply-To: <48AC7468.6030704@redhat.com> References: <48AC7468.6030704@redhat.com> Message-ID: <20080820195040.GM6521@redhat.com> On Wed, Aug 20, 2008 at 03:45:44PM -0400, Mohammed Morsi wrote: > Small patch to include the generated oVirt rpms in the final build output Use 'ln' instead of 'cp'. This will avoid doing any IO > diff --git a/autobuild.sh b/autobuild.sh > index 843ac5c..0823aa9 100755 > --- a/autobuild.sh > +++ b/autobuild.sh > @@ -82,3 +82,5 @@ $ssh_cmd \ > curl -i http://management.priv.ovirt.org/ovirt/ | \ > grep 'HTTP/1.1 200 OK' && \ > cd /usr/share/ovirt-wui && rake test" > + > +cp tmp/ovirt/*.rpm $AUTOBUILD_PACKAGE_ROOT/rpm 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 jguiditt at redhat.com Wed Aug 20 20:12:59 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 20 Aug 2008 16:12:59 -0400 Subject: [Ovirt-devel] [PATCH] No data returned in hosts popup Message-ID: <1219263179-27912-1-git-send-email-jguiditt@redhat.com> There was an encoding problem between what rails was doing and what the flexigrid javascript was doing. Signed-off-by: Jason Guiditta --- wui/src/app/views/host/_grid.rhtml | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml index 14204b5..ac24e0c 100644 --- a/wui/src/app/views/host/_grid.rhtml +++ b/wui/src/app/views/host/_grid.rhtml @@ -12,6 +12,7 @@ { url: '<%= url_for :controller => "hardware", :action => "hosts_json", + :escape => false, :id => (hwpool.nil? ? nil : hwpool.id), :exclude_pool => exclude_pool, :exclude_host => exclude_host, -- 1.5.5.1 From jguiditt at redhat.com Wed Aug 20 20:15:42 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 20 Aug 2008 16:15:42 -0400 Subject: [Ovirt-devel] [PATCH] Removed space that caused warning in log. Message-ID: <1219263342-28109-1-git-send-email-jguiditt@redhat.com> There are not supposed to be spaces before parens, so the mongrel log was showing a warning all the time. Signed-off-by: Jason Guiditta --- wui/src/app/controllers/application.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index d653171..b27ddbe 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base before_filter :is_logged_in def is_logged_in - redirect_to (:controller => "login", :action => "login") unless get_login_user + redirect_to(:controller => "login", :action => "login") unless get_login_user end def get_login_user -- 1.5.5.1 From jguiditt at redhat.com Wed Aug 20 20:24:48 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 20 Aug 2008 16:24:48 -0400 Subject: [Ovirt-devel] [PATCH] Fix for flexigrid popups. (resend) Message-ID: <1219263888-28585-1-git-send-email-jguiditt@redhat.com> These popups were sometimes too wide, this patch cleans that up. Also removed some columns from the hosts popup, as this was an additional issue causing things to be too wide. Signed-off-by: Jason Guiditta --- wui/src/app/views/hardware/show_hosts.rhtml | 3 ++- wui/src/app/views/hardware/show_storage.rhtml | 3 ++- wui/src/app/views/host/_grid.rhtml | 23 ++++++++++++++--------- wui/src/app/views/host/addhost.html.erb | 9 ++++++--- wui/src/app/views/storage/_grid.rhtml | 3 +++ wui/src/app/views/storage/add.rhtml | 2 +- wui/src/public/stylesheets/facebox.css | 2 +- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml index f2962cb..2981440 100644 --- a/wui/src/app/views/hardware/show_hosts.rhtml +++ b/wui/src/app/views/hardware/show_hosts.rhtml @@ -67,7 +67,8 @@ :on_select => "hosts_select", :on_deselect => "load_widget_deselect", :on_hover => "load_widget_hover", - :on_unhover => "load_widget_unhover" } %> + :on_unhover => "load_widget_unhover", + :is_popup => false} %>
diff --git a/wui/src/app/views/hardware/show_storage.rhtml b/wui/src/app/views/hardware/show_storage.rhtml index 3446280..37af1ce 100644 --- a/wui/src/app/views/hardware/show_storage.rhtml +++ b/wui/src/app/views/hardware/show_storage.rhtml @@ -67,7 +67,8 @@ <%= render :partial => "/storage/grid", :locals => { :table_id => "storage_grid", :hwpool => @pool, :exclude_pool => nil, - :on_select => "storage_select" } %> + :on_select => "storage_select", + :is_popup => false} %>
diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml index d3db182..14204b5 100644 --- a/wui/src/app/views/host/_grid.rhtml +++ b/wui/src/app/views/host/_grid.rhtml @@ -1,6 +1,6 @@ <%= render :partial => 'graph/load_graph.rhtml' %> -<% hosts_per_page = 40 %> +<% hosts_per_page.nil? ? hosts_per_page = 40: hosts_per_page = hosts_per_page %>
<%= "
" if checkboxes %> @@ -17,18 +17,23 @@ :exclude_host => exclude_host, :checkboxes => checkboxes %>', dataType: 'json', + <% if is_popup%> + width: 700, + <% end %> colModel : [ <%= "{display: '', width : 20, align: 'left', process: #{table_id}checkbox}," if checkboxes %> {display: 'Hostname', name : 'hostname', width : 60, align: 'left'}, <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_pool %> - {display: 'UUID', name : 'uuid', width : 180, align: 'left'}, - {display: 'Hypervisor', name : 'hypervisor_type', width : 60, align: 'left'}, - {display: 'CPUs', name : 'num_cpus', width : 30, align: 'left'}, - {display: 'Speed (MHz)', name : 'cpu_speed', width : 70, align: 'right'}, - {display: 'Arch', name : 'arch', width : 50, align: 'right'}, - {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, - {display: 'Status', name : 'is_disabled', width : 110, align: 'right'}, - {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } + {display: 'UUID', name : 'uuid', width : 180, align: 'left'}<% if !is_popup %>,<% end %> + <% if !is_popup %> + {display: 'Hypervisor', name : 'hypervisor_type', width : 60, align: 'left'}, + {display: 'CPUs', name : 'num_cpus', width : 30, align: 'left'}, + {display: 'Speed (MHz)', name : 'cpu_speed', width : 70, align: 'right'}, + {display: 'Arch', name : 'arch', width : 50, align: 'right'}, + {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, + {display: 'Status', name : 'is_disabled', width : 110, align: 'right'}, + {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } + <% end %> ], sortname: "hostname", sortorder: "asc", diff --git a/wui/src/app/views/host/addhost.html.erb b/wui/src/app/views/host/addhost.html.erb index 41b6213..7edd4c5 100644 --- a/wui/src/app/views/host/addhost.html.erb +++ b/wui/src/app/views/host/addhost.html.erb @@ -4,9 +4,9 @@ <%- content_for :description do -%> Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. Learn how to manage hosts <%- end -%> - +
+
-
<%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_grid", :hwpool => nil, :exclude_pool => @hardware_pool.id, @@ -15,10 +15,13 @@ :on_select => "load_widget_select", :on_deselect => "load_widget_deselect", :on_hover => "load_widget_hover", - :on_unhover => "load_widget_unhover" } %> + :on_unhover => "load_widget_unhover", + :is_popup => true, + :hosts_per_page => 10} %>
<%= popup_footer("add_hosts('#{url_for :controller => "hardware", :action => "add_hosts", :id => @hardware_pool}')", "Add Hosts") %> +
\ No newline at end of file diff --git a/wui/src/app/views/storage/_grid.rhtml b/wui/src/app/views/storage/_grid.rhtml index 3bdf407..c36f4d3 100644 --- a/wui/src/app/views/storage/_grid.rhtml +++ b/wui/src/app/views/storage/_grid.rhtml @@ -11,6 +11,9 @@ { url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => (hwpool.nil? ? nil : hwpool.id), :exclude_pool => exclude_pool %>', dataType: 'json', + <% if is_popup %> + width: 700, + <% end %> colModel : [ {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox}, {display: 'Alias', width : 180, align: 'left'}, diff --git a/wui/src/app/views/storage/add.rhtml b/wui/src/app/views/storage/add.rhtml index 11cda06..60176a0 100644 --- a/wui/src/app/views/storage/add.rhtml +++ b/wui/src/app/views/storage/add.rhtml @@ -4,7 +4,7 @@
<%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_grid", :hwpool => nil, :exclude_pool => @hardware_pool.id, - :on_select => "false" } %> + :on_select => "false", :is_popup => true } %>
<%= popup_footer("add_storage('#{url_for :controller => 'hardware', :action => 'add_storage', diff --git a/wui/src/public/stylesheets/facebox.css b/wui/src/public/stylesheets/facebox.css index 9c120f9..e8268a0 100644 --- a/wui/src/public/stylesheets/facebox.css +++ b/wui/src/public/stylesheets/facebox.css @@ -69,7 +69,7 @@ #facebox .tl, #facebox .tr, #facebox .bl, #facebox .br { height: 10px; width: 10px; - overflow: hidden; + overflow: auto; padding: 0; } -- 1.5.5.1 From jguiditt at redhat.com Wed Aug 20 20:35:17 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 20 Aug 2008 16:35:17 -0400 Subject: [Ovirt-devel] [PATCH] Stylesheet fix. Message-ID: <1219264517-29076-1-git-send-email-jguiditt@redhat.com> There was a missing ; which can cause weird issues. Signed-off-by: Jason Guiditta --- wui/src/public/stylesheets/layout.css | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/wui/src/public/stylesheets/layout.css b/wui/src/public/stylesheets/layout.css index c9ec8f4..9471cdc 100644 --- a/wui/src/public/stylesheets/layout.css +++ b/wui/src/public/stylesheets/layout.css @@ -408,7 +408,7 @@ a { color:#000000; text-decoration: none;} .dialog_form { margin: 15px; - background: #FFFFFF + background: #FFFFFF; } .dialog_tree { -- 1.5.5.1 From jguiditt at redhat.com Wed Aug 20 20:45:54 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 20 Aug 2008 16:45:54 -0400 Subject: [Ovirt-devel] [PATCH] Fix case where an id is not passed in. Message-ID: <1219265154-29600-1-git-send-email-jguiditt@redhat.com> Signed-off-by: Jason Guiditta --- wui/src/app/models/storage_volume.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/wui/src/app/models/storage_volume.rb b/wui/src/app/models/storage_volume.rb index ccc5839..ef9cd6e 100644 --- a/wui/src/app/models/storage_volume.rb +++ b/wui/src/app/models/storage_volume.rb @@ -53,7 +53,7 @@ class StorageVolume < ActiveRecord::Base def self.find_for_vm(include_vm, vm_pool) if vm_pool condition = "(vms.id is null and storage_pools.hardware_pool_id=#{vm_pool.get_hardware_pool.id})" - condition += " or vms.id=#{include_vm.id}" if (include_vm.id) + condition += " or vms.id=#{include_vm.id}" if (include_vm and include_vm.id) self.find(:all, :include => [:vms, :storage_pool], :conditions => condition) else return [] -- 1.5.5.1 From jguiditt at redhat.com Wed Aug 20 20:01:55 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 20 Aug 2008 16:01:55 -0400 Subject: [Ovirt-devel] [PATCH] Fix for flexigrid popups. Message-ID: <1219262515-27389-1-git-send-email-jguiditt@redhat.com> These popups were sometimes too wide, this patch cleans that up. Also removed some columns from the hosts popup, as this was an additional issue causing things to be too wide. Signed-off-by: Jason Guiditta --- wui/src/app/views/hardware/show_hosts.rhtml | 3 ++- wui/src/app/views/hardware/show_storage.rhtml | 3 ++- wui/src/app/views/host/_grid.rhtml | 23 ++++++++++++++--------- wui/src/app/views/host/addhost.html.erb | 9 ++++++--- wui/src/app/views/storage/_grid.rhtml | 3 +++ wui/src/app/views/storage/add.rhtml | 2 +- wui/src/public/stylesheets/facebox.css | 2 +- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml index f2962cb..2981440 100644 --- a/wui/src/app/views/hardware/show_hosts.rhtml +++ b/wui/src/app/views/hardware/show_hosts.rhtml @@ -67,7 +67,8 @@ :on_select => "hosts_select", :on_deselect => "load_widget_deselect", :on_hover => "load_widget_hover", - :on_unhover => "load_widget_unhover" } %> + :on_unhover => "load_widget_unhover", + :is_popup => false} %>
diff --git a/wui/src/app/views/hardware/show_storage.rhtml b/wui/src/app/views/hardware/show_storage.rhtml index 3446280..37af1ce 100644 --- a/wui/src/app/views/hardware/show_storage.rhtml +++ b/wui/src/app/views/hardware/show_storage.rhtml @@ -67,7 +67,8 @@ <%= render :partial => "/storage/grid", :locals => { :table_id => "storage_grid", :hwpool => @pool, :exclude_pool => nil, - :on_select => "storage_select" } %> + :on_select => "storage_select", + :is_popup => false} %>
diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml index d3db182..14204b5 100644 --- a/wui/src/app/views/host/_grid.rhtml +++ b/wui/src/app/views/host/_grid.rhtml @@ -1,6 +1,6 @@ <%= render :partial => 'graph/load_graph.rhtml' %> -<% hosts_per_page = 40 %> +<% hosts_per_page.nil? ? hosts_per_page = 40: hosts_per_page = hosts_per_page %>
<%= "" if checkboxes %> @@ -17,18 +17,23 @@ :exclude_host => exclude_host, :checkboxes => checkboxes %>', dataType: 'json', + <% if is_popup%> + width: 700, + <% end %> colModel : [ <%= "{display: '', width : 20, align: 'left', process: #{table_id}checkbox}," if checkboxes %> {display: 'Hostname', name : 'hostname', width : 60, align: 'left'}, <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_pool %> - {display: 'UUID', name : 'uuid', width : 180, align: 'left'}, - {display: 'Hypervisor', name : 'hypervisor_type', width : 60, align: 'left'}, - {display: 'CPUs', name : 'num_cpus', width : 30, align: 'left'}, - {display: 'Speed (MHz)', name : 'cpu_speed', width : 70, align: 'right'}, - {display: 'Arch', name : 'arch', width : 50, align: 'right'}, - {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, - {display: 'Status', name : 'is_disabled', width : 110, align: 'right'}, - {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } + {display: 'UUID', name : 'uuid', width : 180, align: 'left'}<% if !is_popup %>,<% end %> + <% if !is_popup %> + {display: 'Hypervisor', name : 'hypervisor_type', width : 60, align: 'left'}, + {display: 'CPUs', name : 'num_cpus', width : 30, align: 'left'}, + {display: 'Speed (MHz)', name : 'cpu_speed', width : 70, align: 'right'}, + {display: 'Arch', name : 'arch', width : 50, align: 'right'}, + {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, + {display: 'Status', name : 'is_disabled', width : 110, align: 'right'}, + {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } + <% end %> ], sortname: "hostname", sortorder: "asc", diff --git a/wui/src/app/views/host/addhost.html.erb b/wui/src/app/views/host/addhost.html.erb index 41b6213..7edd4c5 100644 --- a/wui/src/app/views/host/addhost.html.erb +++ b/wui/src/app/views/host/addhost.html.erb @@ -4,9 +4,9 @@ <%- content_for :description do -%> Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. Learn how to manage hosts <%- end -%> - +
+
-
<%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_grid", :hwpool => nil, :exclude_pool => @hardware_pool.id, @@ -15,10 +15,13 @@ :on_select => "load_widget_select", :on_deselect => "load_widget_deselect", :on_hover => "load_widget_hover", - :on_unhover => "load_widget_unhover" } %> + :on_unhover => "load_widget_unhover", + :is_popup => true, + :hosts_per_page => 10} %>
<%= popup_footer("add_hosts('#{url_for :controller => "hardware", :action => "add_hosts", :id => @hardware_pool}')", "Add Hosts") %> +
\ No newline at end of file diff --git a/wui/src/app/views/storage/_grid.rhtml b/wui/src/app/views/storage/_grid.rhtml index 3bdf407..c36f4d3 100644 --- a/wui/src/app/views/storage/_grid.rhtml +++ b/wui/src/app/views/storage/_grid.rhtml @@ -11,6 +11,9 @@ { url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => (hwpool.nil? ? nil : hwpool.id), :exclude_pool => exclude_pool %>', dataType: 'json', + <% if is_popup %> + width: 700, + <% end %> colModel : [ {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox}, {display: 'Alias', width : 180, align: 'left'}, diff --git a/wui/src/app/views/storage/add.rhtml b/wui/src/app/views/storage/add.rhtml index 11cda06..60176a0 100644 --- a/wui/src/app/views/storage/add.rhtml +++ b/wui/src/app/views/storage/add.rhtml @@ -4,7 +4,7 @@
<%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_grid", :hwpool => nil, :exclude_pool => @hardware_pool.id, - :on_select => "false" } %> + :on_select => "false", :is_popup => true } %>
<%= popup_footer("add_storage('#{url_for :controller => 'hardware', :action => 'add_storage', diff --git a/wui/src/public/stylesheets/facebox.css b/wui/src/public/stylesheets/facebox.css index 9c120f9..e8268a0 100644 --- a/wui/src/public/stylesheets/facebox.css +++ b/wui/src/public/stylesheets/facebox.css @@ -69,7 +69,7 @@ #facebox .tl, #facebox .tr, #facebox .bl, #facebox .br { height: 10px; width: 10px; - overflow: hidden; + overflow: auto; padding: 0; } -- 1.5.5.1 From sseago at redhat.com Wed Aug 20 21:43:52 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 20 Aug 2008 21:43:52 +0000 Subject: [Ovirt-devel] [PATCH] Make cobbler profile choice optional. Message-ID: <1219268632-30353-1-git-send-email-sseago@redhat.com> new VM page allows choice of any cobbler profile, or PXE (pxe boot without managing provisioning), or HD boot. renamed cobbler_profile VM field 'provisioning', and store field as "cobbler:profile:$profilename". This allows for cobbler image support, and possibly other provisioning schemes without rewriting the VM mode Signed-off-by: Scott Seago --- wui/src/app/controllers/vm_controller.rb | 11 ++++- wui/src/app/models/vm.rb | 46 ++++++++++++++++++++-- wui/src/app/views/vm/_form.rhtml | 2 +- wui/src/app/views/vm/show.rhtml | 4 +- wui/src/db/migrate/015_rename_cobbler_profile.rb | 28 +++++++++++++ wui/src/task-omatic/task_vm.rb | 22 +++++++---- 6 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 wui/src/db/migrate/015_rename_cobbler_profile.rb diff --git a/wui/src/app/controllers/vm_controller.rb b/wui/src/app/controllers/vm_controller.rb index 81c3d3f..eae8c2e 100644 --- a/wui/src/app/controllers/vm_controller.rb +++ b/wui/src/app/controllers/vm_controller.rb @@ -223,7 +223,14 @@ class VmController < ApplicationController @perm_obj = @vm.vm_resource_pool @redir_controller = 'resources' @current_pool_id=@perm_obj.id - @cobbler_profiles = Cobbler::Profile.find.collect {|profile| profile.name } + @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], + [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] + # FIXME add cobbler images too + @provisioning_options += Cobbler::Profile.find.collect do |profile| + [profile.name + Vm::COBBLER_PROFILE_SUFFIX, + Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + + Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] + end end def pre_create params[:vm][:state] = Vm::STATE_PENDING @@ -235,8 +242,6 @@ class VmController < ApplicationController vm_resource_pool.create_with_parent(hardware_pool) params[:vm][:vm_resource_pool_id] = vm_resource_pool.id end - #set boot device to network for first boot (install) - params[:vm][:boot_device] = Vm::BOOT_DEV_NETWORK unless params[:vm][:boot_device] @vm = Vm.new(params[:vm]) @perm_obj = @vm.vm_resource_pool @redir_controller = 'resources' diff --git a/wui/src/app/models/vm.rb b/wui/src/app/models/vm.rb index 9ac24d9..80c7efb 100644 --- a/wui/src/app/models/vm.rb +++ b/wui/src/app/models/vm.rb @@ -34,10 +34,22 @@ class Vm < ActiveRecord::Base acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], :terms => [ [ :search_users, 'U', "search_users" ] ] - BOOT_DEV_HD = "hd" - BOOT_DEV_NETWORK = "network" - BOOT_DEV_CDROM = "cdrom" - BOOT_DEV_FIELDS = [ BOOT_DEV_HD, BOOT_DEV_NETWORK, BOOT_DEV_CDROM ] + BOOT_DEV_HD = "hd" + BOOT_DEV_NETWORK = "network" + BOOT_DEV_CDROM = "cdrom" + BOOT_DEV_FIELDS = [ BOOT_DEV_HD, BOOT_DEV_NETWORK, BOOT_DEV_CDROM ] + + PROVISIONING_DELIMITER = ":" + COBBLER_PREFIX = "cobbler" + PROFILE_PREFIX = "profile" + IMAGE_PREFIX = "image" + COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + + PXE_OPTION_LABEL = "PXE Boot" + PXE_OPTION_VALUE = "pxe" + HD_OPTION_LABEL = "Boot from HD" + HD_OPTION_VALUE = "hd" NEEDS_RESTART_FIELDS = [:uuid, :num_vcpus_allocated, @@ -121,6 +133,32 @@ class Vm < ActiveRecord::Base self[:memory_used]=(mb_to_kb(mem)) end + def provisioning_and_boot_settings=(settings) + if settings==PXE_OPTION_VALUE + self[:boot_device]= BOOT_DEV_NETWORK + self[:provisioning]= nil + elsif settings==HD_OPTION_VALUE + self[:boot_device]= BOOT_DEV_HD + self[:provisioning]= nil + else + self[:boot_device]= BOOT_DEV_NETWORK + self[:provisioning]= settings + end + end + def provisioning_and_boot_settings + if provisioning == nil + if boot_device==BOOT_DEV_NETWORK + PXE_OPTION_VALUE + elsif boot_device==BOOT_DEV_HD + HD_OPTION_VALUE + else + PXE_OPTION_VALUE + end + else + provisioning + end + end + def get_pending_state pending_state = state pending_state = EFFECTIVE_STATE[state] if pending_state diff --git a/wui/src/app/views/vm/_form.rhtml b/wui/src/app/views/vm/_form.rhtml index 308ad71..b3a8957 100644 --- a/wui/src/app/views/vm/_form.rhtml +++ b/wui/src/app/views/vm/_form.rhtml @@ -8,7 +8,7 @@ <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id if @hardware_pool %> <%= text_field_with_label "Name:", "vm", "description", {:style=>"width:250px;"} %> - <%= select_with_label "Operating System:", 'vm', 'cobbler_profile', @cobbler_profiles, :style=>"width:250px;" if create %> + <%= select_with_label "Operating System:", 'vm', 'provisioning_and_boot_settings', @provisioning_options, :style=>"width:250px;" if create %>
Resources
diff --git a/wui/src/app/views/vm/show.rhtml b/wui/src/app/views/vm/show.rhtml index 4e9eab1..f361131 100644 --- a/wui/src/app/views/vm/show.rhtml +++ b/wui/src/app/views/vm/show.rhtml @@ -94,7 +94,7 @@ Memory used:
vNIC MAC address:
Boot device:
- Cobbler profile:
+ Provisioning source:
State:
Pending State:
@@ -106,7 +106,7 @@ <%=h @vm.memory_used_in_mb %> MB
<%=h @vm.vnic_mac_addr %>
<%=h @vm.boot_device %>
- <%=h @vm.cobbler_profile %>
+ <%=h @vm.provisioning_and_boot_settings %>
<%=h @vm.state %> <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) diff --git a/wui/src/db/migrate/015_rename_cobbler_profile.rb b/wui/src/db/migrate/015_rename_cobbler_profile.rb new file mode 100644 index 0000000..7471c6b --- /dev/null +++ b/wui/src/db/migrate/015_rename_cobbler_profile.rb @@ -0,0 +1,28 @@ +# +# 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. + +class RenameCobblerProfile < ActiveRecord::Migration + def self.up + rename_column :vms, :cobbler_profile, :provisioning + end + + def self.down + rename_column :vms, :provisioning, :cobbler_profile + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index 6276d04..fca978f 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -153,16 +153,22 @@ def create_vm(task) # create cobbler system profile begin - if !vm.cobbler_profile or vm.cobbler_profile.empty? - raise "Cobbler profile not specified" + if vm.provisioning and !vm.provisioning.empty? + provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) + if provisioning_arr[0]==Vm::COBBLER_PREFIX + if provisioning_arr[1]==Vm::PROFILE_PREFIX + system = Cobbler::System.new('name' => vm.uuid, + 'profile' => provisioning_arr[2]) + system.interfaces=[Cobbler::NetworkInterface.new( + ["intf",{'mac_address' => vm.vnic_mac_addr}] + )] + system.save + elsif provisioning_arr[1]==Vm::IMAGE_PREFIX + #FIXME handle cobbler images + end + end end - system = Cobbler::System.new('name' => vm.uuid, - 'profile' => vm.cobbler_profile) - system.interfaces=[Cobbler::NetworkInterface.new( - ["intf",{'mac_address' => vm.vnic_mac_addr}] - )] - system.save setVmState(vm, Vm::STATE_STOPPED) rescue Exception => error setVmState(vm, Vm::STATE_CREATE_FAILED) -- 1.5.5.1 From sseago at redhat.com Wed Aug 20 22:04:34 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 20 Aug 2008 22:04:34 +0000 Subject: [Ovirt-devel] [PATCH] Make cobbler profile choice optional.(revised) Message-ID: <1219269874-31541-1-git-send-email-sseago@redhat.com> new VM page allows choice of any cobbler profile, or PXE (pxe boot without managing provisioning), or HD boot. renamed cobbler_profile VM field 'provisioning', and store field as "cobbler:profile:$profilename". This allows for cobbler image support, and possibly other provisioning schemes without rewriting the VM mode wrapped new VM form cobbler call in begin/rescue so that we fall back gracefully (skip profiles) if cobbler isn't running. Signed-off-by: Scott Seago --- wui/src/app/controllers/vm_controller.rb | 15 ++++++- wui/src/app/models/vm.rb | 46 ++++++++++++++++++++-- wui/src/app/views/vm/_form.rhtml | 2 +- wui/src/app/views/vm/show.rhtml | 4 +- wui/src/db/migrate/015_rename_cobbler_profile.rb | 28 +++++++++++++ wui/src/task-omatic/task_vm.rb | 22 +++++++---- 6 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 wui/src/db/migrate/015_rename_cobbler_profile.rb diff --git a/wui/src/app/controllers/vm_controller.rb b/wui/src/app/controllers/vm_controller.rb index 81c3d3f..a9deff5 100644 --- a/wui/src/app/controllers/vm_controller.rb +++ b/wui/src/app/controllers/vm_controller.rb @@ -223,7 +223,18 @@ class VmController < ApplicationController @perm_obj = @vm.vm_resource_pool @redir_controller = 'resources' @current_pool_id=@perm_obj.id - @cobbler_profiles = Cobbler::Profile.find.collect {|profile| profile.name } + @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], + [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] + # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Profile.find.collect do |profile| + [profile.name + Vm::COBBLER_PROFILE_SUFFIX, + Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + + Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] + end + rescue + #if cobbler doesn't respond/is misconfigured/etc just don't add profiles + end end def pre_create params[:vm][:state] = Vm::STATE_PENDING @@ -235,8 +246,6 @@ class VmController < ApplicationController vm_resource_pool.create_with_parent(hardware_pool) params[:vm][:vm_resource_pool_id] = vm_resource_pool.id end - #set boot device to network for first boot (install) - params[:vm][:boot_device] = Vm::BOOT_DEV_NETWORK unless params[:vm][:boot_device] @vm = Vm.new(params[:vm]) @perm_obj = @vm.vm_resource_pool @redir_controller = 'resources' diff --git a/wui/src/app/models/vm.rb b/wui/src/app/models/vm.rb index 9ac24d9..80c7efb 100644 --- a/wui/src/app/models/vm.rb +++ b/wui/src/app/models/vm.rb @@ -34,10 +34,22 @@ class Vm < ActiveRecord::Base acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], :terms => [ [ :search_users, 'U', "search_users" ] ] - BOOT_DEV_HD = "hd" - BOOT_DEV_NETWORK = "network" - BOOT_DEV_CDROM = "cdrom" - BOOT_DEV_FIELDS = [ BOOT_DEV_HD, BOOT_DEV_NETWORK, BOOT_DEV_CDROM ] + BOOT_DEV_HD = "hd" + BOOT_DEV_NETWORK = "network" + BOOT_DEV_CDROM = "cdrom" + BOOT_DEV_FIELDS = [ BOOT_DEV_HD, BOOT_DEV_NETWORK, BOOT_DEV_CDROM ] + + PROVISIONING_DELIMITER = ":" + COBBLER_PREFIX = "cobbler" + PROFILE_PREFIX = "profile" + IMAGE_PREFIX = "image" + COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + + PXE_OPTION_LABEL = "PXE Boot" + PXE_OPTION_VALUE = "pxe" + HD_OPTION_LABEL = "Boot from HD" + HD_OPTION_VALUE = "hd" NEEDS_RESTART_FIELDS = [:uuid, :num_vcpus_allocated, @@ -121,6 +133,32 @@ class Vm < ActiveRecord::Base self[:memory_used]=(mb_to_kb(mem)) end + def provisioning_and_boot_settings=(settings) + if settings==PXE_OPTION_VALUE + self[:boot_device]= BOOT_DEV_NETWORK + self[:provisioning]= nil + elsif settings==HD_OPTION_VALUE + self[:boot_device]= BOOT_DEV_HD + self[:provisioning]= nil + else + self[:boot_device]= BOOT_DEV_NETWORK + self[:provisioning]= settings + end + end + def provisioning_and_boot_settings + if provisioning == nil + if boot_device==BOOT_DEV_NETWORK + PXE_OPTION_VALUE + elsif boot_device==BOOT_DEV_HD + HD_OPTION_VALUE + else + PXE_OPTION_VALUE + end + else + provisioning + end + end + def get_pending_state pending_state = state pending_state = EFFECTIVE_STATE[state] if pending_state diff --git a/wui/src/app/views/vm/_form.rhtml b/wui/src/app/views/vm/_form.rhtml index 308ad71..b3a8957 100644 --- a/wui/src/app/views/vm/_form.rhtml +++ b/wui/src/app/views/vm/_form.rhtml @@ -8,7 +8,7 @@ <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id if @hardware_pool %> <%= text_field_with_label "Name:", "vm", "description", {:style=>"width:250px;"} %> - <%= select_with_label "Operating System:", 'vm', 'cobbler_profile', @cobbler_profiles, :style=>"width:250px;" if create %> + <%= select_with_label "Operating System:", 'vm', 'provisioning_and_boot_settings', @provisioning_options, :style=>"width:250px;" if create %>
Resources
diff --git a/wui/src/app/views/vm/show.rhtml b/wui/src/app/views/vm/show.rhtml index 4e9eab1..f361131 100644 --- a/wui/src/app/views/vm/show.rhtml +++ b/wui/src/app/views/vm/show.rhtml @@ -94,7 +94,7 @@ Memory used:
vNIC MAC address:
Boot device:
- Cobbler profile:
+ Provisioning source:
State:
Pending State:
@@ -106,7 +106,7 @@ <%=h @vm.memory_used_in_mb %> MB
<%=h @vm.vnic_mac_addr %>
<%=h @vm.boot_device %>
- <%=h @vm.cobbler_profile %>
+ <%=h @vm.provisioning_and_boot_settings %>
<%=h @vm.state %> <%unless @vm.needs_restart.nil? or @vm.needs_restart == 0 -%> (needs restart) diff --git a/wui/src/db/migrate/015_rename_cobbler_profile.rb b/wui/src/db/migrate/015_rename_cobbler_profile.rb new file mode 100644 index 0000000..7471c6b --- /dev/null +++ b/wui/src/db/migrate/015_rename_cobbler_profile.rb @@ -0,0 +1,28 @@ +# +# 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. + +class RenameCobblerProfile < ActiveRecord::Migration + def self.up + rename_column :vms, :cobbler_profile, :provisioning + end + + def self.down + rename_column :vms, :provisioning, :cobbler_profile + end +end diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index 6276d04..fca978f 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -153,16 +153,22 @@ def create_vm(task) # create cobbler system profile begin - if !vm.cobbler_profile or vm.cobbler_profile.empty? - raise "Cobbler profile not specified" + if vm.provisioning and !vm.provisioning.empty? + provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) + if provisioning_arr[0]==Vm::COBBLER_PREFIX + if provisioning_arr[1]==Vm::PROFILE_PREFIX + system = Cobbler::System.new('name' => vm.uuid, + 'profile' => provisioning_arr[2]) + system.interfaces=[Cobbler::NetworkInterface.new( + ["intf",{'mac_address' => vm.vnic_mac_addr}] + )] + system.save + elsif provisioning_arr[1]==Vm::IMAGE_PREFIX + #FIXME handle cobbler images + end + end end - system = Cobbler::System.new('name' => vm.uuid, - 'profile' => vm.cobbler_profile) - system.interfaces=[Cobbler::NetworkInterface.new( - ["intf",{'mac_address' => vm.vnic_mac_addr}] - )] - system.save setVmState(vm, Vm::STATE_STOPPED) rescue Exception => error setVmState(vm, Vm::STATE_CREATE_FAILED) -- 1.5.5.1 From apevec at redhat.com Wed Aug 20 23:41:42 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 21 Aug 2008 01:41:42 +0200 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance Message-ID: <1219275702-16782-1-git-send-email-apevec@redhat.com> Minimal Fedora install tree is imported into the image during appliance creation, at appliance firstboot Cobbler distro and profiles are created, using non-mirrored repositories to save appliance disk space and setup time. Cobbler entries for each "fake" oVirt Node in developer setup is added if you add more oVirt Nodes, define them as Cobbler system: cobbler system add --name=NodeName --profile=oVirt-Node-x86_64 --mac=00:11:22:33:44:55 --netboot-enabled=1 otherwise they will PXE boot to the default Cobbler boot menu, where oVirt Node can be manually choosen Signed-off-by: Alan Pevec --- wui-appliance/Makefile | 9 +++++- wui-appliance/gettree.sh | 31 ++++++++++++++++++ wui-appliance/wui-devel.ks | 75 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletions(-) create mode 100755 wui-appliance/gettree.sh diff --git a/wui-appliance/Makefile b/wui-appliance/Makefile index ea0bb61..ace0b8c 100644 --- a/wui-appliance/Makefile +++ b/wui-appliance/Makefile @@ -1,11 +1,13 @@ NAME=ovirt-appliance YUMCACHE=$$(pwd)/../tmp/cache +F_REL := $(shell rpm -q --qf '%{VERSION}' fedora-release) +ARCH := $(shell uname -i) appliance: $(NAME)-sda.raw appliance-compressed: $(NAME)-sda.qcow -$(NAME)-sda.raw: wui-devel.ks common-install.ks common-pkgs.ks common-post.ks repos.ks +$(NAME)-sda.raw: *.ks tmp/tree/.treeinfo mkdir -p tmp appliance-creator --config wui-devel.ks --name $(NAME) \ --tmpdir="$$(pwd)/tmp" --cache="$(YUMCACHE)" @@ -17,6 +19,11 @@ $(NAME)-sda.qcow: $(NAME)-sda.raw repos.ks: ../common/repos.ks.in cp ../common/repos.ks.in repos.ks +tmp/tree/.treeinfo: + mkdir -p tmp/tree + cd tmp/tree; ../../gettree.sh \ + http://download.fedoraproject.org/pub/fedora/linux/releases/$(F_REL)/Fedora/$(ARCH)/os + clean: rm -f repos.ks diff --git a/wui-appliance/gettree.sh b/wui-appliance/gettree.sh new file mode 100755 index 0000000..f6b01a8 --- /dev/null +++ b/wui-appliance/gettree.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# gettree.sh +# Fedora release URL - Fedora base URL +# e.g. http://download.fedoraproject.org/pub/fedora/linux/releases/9/Fedora/x86_64/os +# download minimal Fedora tree: .treeinfo stage2 initrd and kernel + +download() { + local f=$1 + wget --progress=dot:mega --continue $1 + printf "." +} + +if [ -z "$1" ]; then + cat >&2 << EOF +Usage: $(basename "$0") +EOF + exit 1 +fi + +url=$1 +printf "Downloading minimal Fedora install tree from $url" +set -e +download $url/.treeinfo +mkdir -p images/pxeboot +cd images +download $url/images/stage2.img +cd pxeboot +download $url/images/pxeboot/initrd.img +download $url/images/pxeboot/vmlinuz +echo "done" diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 66927be..cb49b6b 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -290,3 +290,78 @@ chmod +x /etc/init.d/ovirt-wui-dev chkconfig ovirt-wui-dev on %end + +%post --nochroot + # distribution tree is ready in tmp/tree + set -e + python -c ' +from iniparse.ini import INIConfig +ini = INIConfig() +fp = open("tmp/tree/.treeinfo") +ini.readfp(fp) +fp.close() +family = ini.general.family +version = ini.general.version +arch = ini.general.arch +print "%s %s %s" % (family, version, arch)' | ( read os ver arch + dest=$INSTALL_ROOT/var/www/cobbler/ks_mirror/$os-$ver-$arch + printf "Importing $os-$ver-$arch ..." + cp -a tmp/tree $dest + cat >> $INSTALL_ROOT/etc/rc.d/rc.cobbler-import << EOF +#!/bin/sh +# Import Cobbler profiles on first boot + +exec > /root/cobbler-import.log 2>&1 + +# run only once +chmod -x \$0 +set -x +cobbler import --name=$os-$ver --arch=$arch \ + --path=/var/www/cobbler/ks_mirror/$os-$ver-$arch +cobbler repo add --name=f9-$arch --arch=$arch --mirror-locally=0 \ + --mirror=http://download.fedoraproject.org/pub/fedora/linux/releases/9/Everything/$arch/os +cobbler repo add --name=f9-$arch-updates --arch=$arch --mirror-locally=0 \ + --mirror=http://download.fedoraproject.org/pub/fedora/linux/updates/9/$arch +cobbler profile edit --name=$os-$ver-$arch --repos="f9-$arch f9-$arch-updates" + +# TODO extract Node boot params from /var/lib/tftboot/pxelinux.cfg/default +# before Cobbler overwrites it +cobbler distro add --name="oVirt-Node-$arch" --arch=$arch \ + --initrd=/var/lib/tftpboot/initrd0.img --kernel=/var/lib/tftpboot/vmlinuz0 \ + --kopts="rootflags=loop root=/ovirt.iso rootfstype=iso9660 ro console=tty0 console=ttyS0,115200n8" +cobbler profile add --name=oVirt-Node-$arch --distro=oVirt-Node-$arch +cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ + --name=node3 --mac=00:16:3e:12:34:57 +cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ + --name=node4 --mac=00:16:3e:12:34:58 +cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ + --name=node5 --mac=00:16:3e:12:34:59 +set +x +echo "Add new oVirt Nodes as Cobbler systems to make them PXE boot oVirt Node image directly." +echo "Alternatively, choose oVirt-Node-$arch in default Cobbler boot menu." +EOF + chmod +x $INSTALL_ROOT/etc/rc.d/rc.cobbler-import + echo "[ -x /etc/rc.d/rc.cobbler-import ] && /etc/rc.d/rc.cobbler-import" \ + >> $INSTALL_ROOT/etc/rc.d/rc.local + echo done + ) +%end + +# Cobbler configuration +%post + exec >> /root/kickstart-post.log 2>&1 + # ovirt/ovirt + echo ovirt:Cobbler:68db208a546dcedf34edf0b4fe0ab1f2 > /etc/cobbler/users.digest + # make cobbler check happier + mkdir -p /etc/vsftpd + touch /etc/vsftpd/vsftpd.conf + mkdir -p /usr/lib/syslinux + cp /var/lib/tftpboot/pxelinux.0 /usr/lib/syslinux/pxelinux.0 + # TODO use Augeas 0.3.0 Inifile lens + sed -i -e "s/^module = authn_denyall.*/module = authn_configfile/" \ + /etc/cobbler/modules.conf + sed -i -e "s/^server:.*/server: '192.168.50.2'/" \ + -e "s/^next_server:.*/next_server: '192.168.50.2'/" \ + /etc/cobbler/settings + sed -i -e '/kernel /a \\tIPAPPEND 2' /etc/cobbler/pxesystem.template +%end -- 1.5.5.1 From imain at redhat.com Thu Aug 21 00:26:55 2008 From: imain at redhat.com (Ian Main) Date: Wed, 20 Aug 2008 17:26:55 -0700 Subject: [Ovirt-devel] [PATCH] Make cobbler profile choice optional.(revised) In-Reply-To: <1219269874-31541-1-git-send-email-sseago@redhat.com> References: <1219269874-31541-1-git-send-email-sseago@redhat.com> Message-ID: <20080820172655.64f362e5@tp.mains.net> On Wed, 20 Aug 2008 22:04:34 +0000 Scott Seago wrote: > new VM page allows choice of any cobbler profile, or PXE (pxe boot without managing provisioning), or HD boot. > > renamed cobbler_profile VM field 'provisioning', and store field as "cobbler:profile:$profilename". > > This allows for cobbler image support, and possibly other provisioning schemes without rewriting the VM mode > > wrapped new VM form cobbler call in begin/rescue so that we fall back gracefully (skip profiles) if cobbler isn't running. > Signed-off-by: Scott Seago Only buglet I noticed was that you can create a VM that will boot from hd with no storage attached. Should alert the user and not allow creation. ACK Ian From pmyers at redhat.com Thu Aug 21 00:34:13 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 20 Aug 2008 20:34:13 -0400 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <1219275702-16782-1-git-send-email-apevec@redhat.com> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> Message-ID: <48ACB805.4040500@redhat.com> Alan Pevec wrote: > Minimal Fedora install tree is imported into the image during appliance creation, > at appliance firstboot Cobbler distro and profiles are created, > using non-mirrored repositories to save appliance disk space and setup time. > > Cobbler entries for each "fake" oVirt Node in developer setup is added > if you add more oVirt Nodes, define them as Cobbler system: > cobbler system add --name=NodeName --profile=oVirt-Node-x86_64 --mac=00:11:22:33:44:55 --netboot-enabled=1 > otherwise they will PXE boot to the default Cobbler boot menu, where > oVirt Node can be manually choosen Had a conversation with Alan on IRC about this, but wanted to document here... This patch means that the admin has to run "cobbler system" for each new Node. This adds an extra manual step to adding a new Node to a network. Here's a way around that (not for this patch, but for another subsequent patch) 1. Node initial boot goes to default PXE menu which has Node image as an option. On first boot admin selects Node image 2. Node boots and registers with the oVirt Server and during registration the Server creates a cobbler system with the MAC address of the Node so that subsequent boots of the new box always default to the Node image Only thing that is bad about this setup is that for nodes with no keyboard/mouse/display (rack mount boxes) there's no way to select Node from the PXE default menu. In this case the admin needs to either manually add the system with the MAC before booting the first time as Alan instructs above. Or the Admin needs to configure cobbler so that the default option is the Node instead of Local Disk. We'll just need to provide instructions for doing this. Perry From pmyers at redhat.com Thu Aug 21 02:52:22 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 20 Aug 2008 22:52:22 -0400 Subject: [Ovirt-devel] [PATCH] Make cobbler profile choice optional.(revised) In-Reply-To: <1219269874-31541-1-git-send-email-sseago@redhat.com> References: <1219269874-31541-1-git-send-email-sseago@redhat.com> Message-ID: <48ACD866.50307@redhat.com> Scott Seago wrote: > new VM page allows choice of any cobbler profile, or PXE (pxe boot without managing provisioning), or HD boot. > > renamed cobbler_profile VM field 'provisioning', and store field as "cobbler:profile:$profilename". > > This allows for cobbler image support, and possibly other provisioning schemes without rewriting the VM mode > > wrapped new VM form cobbler call in begin/rescue so that we fall back gracefully (skip profiles) if cobbler isn't running. Looks good, found one minor issue... Since the ovirt node is itself a cobbler profile it shows up in the list of cobbler profiles to choose from. We should filter this out somehow... We don't want people choosing the recursive node option :) Perry From sseago at redhat.com Thu Aug 21 03:12:44 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 20 Aug 2008 23:12:44 -0400 Subject: [Ovirt-devel] [PATCH] Make cobbler profile choice optional.(revised) In-Reply-To: <48ACD866.50307@redhat.com> References: <1219269874-31541-1-git-send-email-sseago@redhat.com> <48ACD866.50307@redhat.com> Message-ID: <48ACDD2C.40002@redhat.com> Perry N. Myers wrote: > Scott Seago wrote: >> new VM page allows choice of any cobbler profile, or PXE (pxe boot >> without managing provisioning), or HD boot. >> >> renamed cobbler_profile VM field 'provisioning', and store field as >> "cobbler:profile:$profilename". >> >> This allows for cobbler image support, and possibly other >> provisioning schemes without rewriting the VM mode >> >> wrapped new VM form cobbler call in begin/rescue so that we fall back >> gracefully (skip profiles) if cobbler isn't running. > > Looks good, found one minor issue... > > Since the ovirt node is itself a cobbler profile it shows up in the > list of cobbler profiles to choose from. We should filter this out > somehow... We don't want people choosing the recursive node option :) > > Perry Yes, apevec mentioned this today too. We had agreed to skip this for now, but it should be easy enough to resolve, as long as we can identify clearly what to expect the node profile to be called. Scott From imain at redhat.com Thu Aug 21 03:53:04 2008 From: imain at redhat.com (Ian Main) Date: Wed, 20 Aug 2008 20:53:04 -0700 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <1219275702-16782-1-git-send-email-apevec@redhat.com> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> Message-ID: <20080820205304.056f045f@tp.mains.net> On Thu, 21 Aug 2008 01:41:42 +0200 Alan Pevec wrote: > Minimal Fedora install tree is imported into the image during appliance creation, > at appliance firstboot Cobbler distro and profiles are created, > using non-mirrored repositories to save appliance disk space and setup time. > > Cobbler entries for each "fake" oVirt Node in developer setup is added > if you add more oVirt Nodes, define them as Cobbler system: > cobbler system add --name=NodeName --profile=oVirt-Node-x86_64 --mac=00:11:22:33:44:55 --netboot-enabled=1 > otherwise they will PXE boot to the default Cobbler boot menu, where > oVirt Node can be manually choosen Perry and I spent a lot of time messing about with this one. Turns out the config is not picked up for some bizarre reason until you restart cobbler. We tried cobbler sync too with no success. Here is what I added and it tested and worked fine: --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -336,6 +336,7 @@ cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ --name=node4 --mac=00:16:3e:12:34:58 cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ --name=node5 --mac=00:16:3e:12:34:59 +service cobblerd restart set +x echo "Add new oVirt Nodes as Cobbler systems to make them PXE boot oVirt Node image directly." echo "Alternatively, choose oVirt-Node-$arch in default Cobbler boot menu." If you add that, it all works fine on boot up. I'm installing fedora now! ACK with that change. :) Ian From apevec at redhat.com Thu Aug 21 08:13:22 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 21 Aug 2008 10:13:22 +0200 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <20080820205304.056f045f@tp.mains.net> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> <20080820205304.056f045f@tp.mains.net> Message-ID: <48AD23A2.9010006@redhat.com> Ian Main wrote: > Perry and I spent a lot of time messing about with this one. Turns out the config is not picked up for some bizarre reason until you restart cobbler. We tried cobbler sync too with no success. Here is what I added and it tested and worked fine: > +service cobblerd restart Yeah, first boot is always tricky but FWIW it worked for me w/o cobbler restart. In anycase, it can't hurt, "jeder 'boot' tut gut" :) > If you add that, it all works fine on boot up. I'm installing fedora now! ACK with that change. :) Which kickstart btw? Default ks in Cobbler is empty and when I assigned sample_end.ks template to Fedora-9-x86_64 profile, it turned out that Anaconda complains about my minimal install tree. I'll need to add at least dummy repodata, looking Anaconda src now. Other option is to specify in ks remote tree directly i.e. instead of url --url=$tree put url --url=http://download.fedoraproject.org/pub/fedora/linux/releases/9/Fedora/x86_64/os From apevec at redhat.com Thu Aug 21 13:39:10 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 21 Aug 2008 15:39:10 +0200 Subject: [Ovirt-devel] [PATCH] separate test results log Message-ID: <1219325950-5632-1-git-send-email-apevec@redhat.com> --- autobuild.sh | 5 ++++- 1 files changed, 4 insertions(+), 1 deletions(-) diff --git a/autobuild.sh b/autobuild.sh index 19ff5a4..4eee9ea 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -23,6 +23,8 @@ ME=$(basename "$0") warn() { printf "$ME: $@\n" >&2; } die() { warn "$@"; exit 1; } +test -n "$1" && RESULTS="$1" || RESULTS="results.log" + echo "Running oVirt Autobuild" SSHKEY=~/.ssh/id_autobuild @@ -81,7 +83,8 @@ $ssh_cmd \ service ovirt-mongrel-rails restart && service httpd restart && \ curl -i http://management.priv.ovirt.org/ovirt/ | \ grep 'HTTP/1.1 200 OK' && \ - cd /usr/share/ovirt-wui && rake test" + cd /usr/share/ovirt-wui && rake test" > $RESULTS 2>&1 \ + || die "wui tests failed" # make oVirt RPMs available as autobuild output # TODO fix oVirt Makefiles to honour ~/.rpmmacros (autobuild sets that up) -- 1.5.4.1 From jguiditt at redhat.com Thu Aug 21 14:09:17 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 21 Aug 2008 10:09:17 -0400 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <20080820205304.056f045f@tp.mains.net> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> <20080820205304.056f045f@tp.mains.net> Message-ID: <1219327757.3673.16.camel@localhost.localdomain> On Wed, 2008-08-20 at 20:53 -0700, Ian Main wrote: > > Turns out the config is not picked up for some bizarre reason until you restart cobbler. > We tried cobbler sync too with no success. Here is what I added and it tested and worked fine: > I will preface this by saying I know very little about cobbler, but this sounds like a bug to me, maybe it should be brought to the cobbler developers' attention? -j From pmyers at redhat.com Thu Aug 21 14:09:23 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 21 Aug 2008 10:09:23 -0400 Subject: [Ovirt-devel] Managed node NIC management In-Reply-To: <48A4CEE0.6030204@redhat.com> References: <8E039F7B-66A7-4E65-96E7-933CD39C9A49@redhat.com> <48A4CEE0.6030204@redhat.com> Message-ID: <48AD7713.3060205@redhat.com> mark wagner wrote: > Darryl > > Not really involved in this discussion previously so may be a naive > question(s) but can you key off of the MAC ID of each of the interfaces ? > > I realize that this may change if its not integrated on the mobo (and > yes even the mobo macid can be changed), but it would seem to handle the > overwhelming majority of the cases. If the mac needs to change, treat it > as a new card. btw - is there an existing method for dealing with new > interfaces ? I agree. New MAC for a card == totally new interface. New interfaces are handled on each boot, as the node always sends hw information to the server on each boot. > Also (potentially showing more ignorance) I will assume that there may > be multiple paths between the managed node and the ovirt server. Are > we setting arp_filter=1 in /etc/sysctl.conf ? (we should) There could possibly be multiple paths between the Node and the Server (we aren't going to prohibit this explicitly). But only a single interface is designated as the iface for the Management LAN and that iface will be the one configured to PXE boot. The additional interfaces brought up for use by VMs for data networks might not even have IP addresses. (In fact I don't think there is a need for them to have addresses) i.e. Node has 2 NICs. NIC 1 is configured to PXE boot and is on the Management LAN. NIC 2 is configured as a bridge for VMs to use on a VM LAN. When the node boots, NIC 1 is used to PXE the Node and gets an IP address to talk to the Server. NIC 2 is brought up as a bridge device but doesn't need an IP address. VMs are brought up and use NIC 2 as the bridge and get IPs on the LAN that NIC 2 is connected to. Now, if for some reason you -do- want to assign an IP to NIC 2, you can (is there a compelling reason to allow this?). And if there is a route to the Server Suite via NIC 2 we don't want the Node using NIC 2 for SS comms, only NIC 1. Are you saying setting arp_filter=1 would take care of this, if so we should set it as long as it doesn't have any other negative implications. > Also, is the PXE service tied to a particular interface or just the > server machine, For instance > if the ovirt server and the manage node each have two nics, one on each > subnet, do we need to make sure that the PXE request only goes on a > certain subnet or is that handled for us? Each physical node is assumed to have only a single Ethernet interface configured to PXE boot and this interface is the one that is on the Management LAN. This is something that needs to be set up when racking the new box in the datacenter. (i.e. admin boots the box enters the BIOS and picks which cards to PXE boot or not) Perry From jim at meyering.net Thu Aug 21 14:11:34 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 21 Aug 2008 16:11:34 +0200 Subject: [Ovirt-devel] [PATCH] separate test results log In-Reply-To: <1219325950-5632-1-git-send-email-apevec@redhat.com> (Alan Pevec's message of "Thu, 21 Aug 2008 15:39:10 +0200") References: <1219325950-5632-1-git-send-email-apevec@redhat.com> Message-ID: <87tzdepieh.fsf@rho.meyering.net> Hi Alan, Looks good, modulo a nit at the end. Alan Pevec wrote: > diff --git a/autobuild.sh b/autobuild.sh > index 19ff5a4..4eee9ea 100755 > --- a/autobuild.sh > +++ b/autobuild.sh > @@ -23,6 +23,8 @@ ME=$(basename "$0") > warn() { printf "$ME: $@\n" >&2; } > die() { warn "$@"; exit 1; } > > +test -n "$1" && RESULTS="$1" || RESULTS="results.log" You can drop some of those quotes (in the name of removing unnecessary syntax): test -n "$1" && RESULTS=$1 || RESULTS=results.log > echo "Running oVirt Autobuild" > > SSHKEY=~/.ssh/id_autobuild > @@ -81,7 +83,8 @@ $ssh_cmd \ > service ovirt-mongrel-rails restart && service httpd restart && \ > curl -i http://management.priv.ovirt.org/ovirt/ | \ > grep 'HTTP/1.1 200 OK' && \ > - cd /usr/share/ovirt-wui && rake test" > + cd /usr/share/ovirt-wui && rake test" > $RESULTS 2>&1 \ > + || die "wui tests failed" The above requires quotes around "$RESULTS": cd /usr/share/ovirt-wui && rake test" > "$RESULTS" 2>&1 \ The quotes are useful when the value contains e.g., a space: $ f='a b' $ echo a > $f bash: $f: ambiguous redirect with quotes, a poorly chosen name doesn't cause a malfunction: $ f='a b' $ echo a > "$f" From pmyers at redhat.com Thu Aug 21 14:15:17 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 21 Aug 2008 10:15:17 -0400 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <1219327757.3673.16.camel@localhost.localdomain> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> <20080820205304.056f045f@tp.mains.net> <1219327757.3673.16.camel@localhost.localdomain> Message-ID: <48AD7875.6050100@redhat.com> Jason Guiditta wrote: > On Wed, 2008-08-20 at 20:53 -0700, Ian Main wrote: >> Turns out the config is not picked up for some bizarre reason until you restart cobbler. >> We tried cobbler sync too with no success. Here is what I added and it tested and worked fine: >> > I will preface this by saying I know very little about cobbler, but this > sounds like a bug to me, maybe it should be brought to the cobbler > developers' attention? Agreed. Ian and I proposed this change just to get it to work for the short term. But we're going to follow up with Dehaan to see why it is the case. Perry From apevec at redhat.com Thu Aug 21 14:45:42 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 21 Aug 2008 16:45:42 +0200 Subject: [Ovirt-devel] [PATCH] separate test results log In-Reply-To: <87tzdepieh.fsf@rho.meyering.net> References: <1219325950-5632-1-git-send-email-apevec@redhat.com> <87tzdepieh.fsf@rho.meyering.net> Message-ID: <48AD7F96.6010405@redhat.com> >> +test -n "$1" && RESULTS="$1" || RESULTS="results.log" > > You can drop some of those quotes (in the name of removing > unnecessary syntax): > > test -n "$1" && RESULTS=$1 || RESULTS=results.log >> + cd /usr/share/ovirt-wui && rake test" > $RESULTS 2>&1 \ >> + || die "wui tests failed" > cd /usr/share/ovirt-wui && rake test" > "$RESULTS" 2>&1 \ ok, BTW, that was copy/paste from libvirt/autobuild.sh so you might want to suggest the same fix "upstream" :) From jim at meyering.net Thu Aug 21 15:57:47 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 21 Aug 2008 17:57:47 +0200 Subject: [Ovirt-devel] [PATCH] separate test results log In-Reply-To: <48AD7F96.6010405@redhat.com> (Alan Pevec's message of "Thu, 21 Aug 2008 16:45:42 +0200") References: <1219325950-5632-1-git-send-email-apevec@redhat.com> <87tzdepieh.fsf@rho.meyering.net> <48AD7F96.6010405@redhat.com> Message-ID: <877iaapdhg.fsf@rho.meyering.net> Alan Pevec wrote: >>> +test -n "$1" && RESULTS="$1" || RESULTS="results.log" >> >> You can drop some of those quotes (in the name of removing >> unnecessary syntax): >> >> test -n "$1" && RESULTS=$1 || RESULTS=results.log > >>> + cd /usr/share/ovirt-wui && rake test" > $RESULTS 2>&1 \ >>> + || die "wui tests failed" > >> cd /usr/share/ovirt-wui && rake test" > "$RESULTS" 2>&1 \ > > ok, BTW, that was copy/paste from libvirt/autobuild.sh so you might want to suggest the same fix "upstream" :) Thanks. I've just posted a patch there. From mdehaan at redhat.com Thu Aug 21 16:18:26 2008 From: mdehaan at redhat.com (Michael DeHaan) Date: Thu, 21 Aug 2008 12:18:26 -0400 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <1219327757.3673.16.camel@localhost.localdomain> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> <20080820205304.056f045f@tp.mains.net> <1219327757.3673.16.camel@localhost.localdomain> Message-ID: <48AD9552.4040801@redhat.com> Jason Guiditta wrote: > On Wed, 2008-08-20 at 20:53 -0700, Ian Main wrote: > >> Turns out the config is not picked up for some bizarre reason until you restart cobbler. >> We tried cobbler sync too with no success. Here is what I added and it tested and worked fine: >> >> > I will preface this by saying I know very little about cobbler, but this > sounds like a bug to me, maybe it should be brought to the cobbler > developers' attention? > > -j > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > You have it. Cobbler XMLRPC changes should show up automatically, so I would suspect you are doing something wrong if there is any need to restart cobblerd. --Michael From mdehaan at redhat.com Thu Aug 21 16:20:20 2008 From: mdehaan at redhat.com (Michael DeHaan) Date: Thu, 21 Aug 2008 12:20:20 -0400 Subject: [Ovirt-devel] [PATCH] configure Cobbler instance in oVirt appliance In-Reply-To: <20080820205304.056f045f@tp.mains.net> References: <1219275702-16782-1-git-send-email-apevec@redhat.com> <20080820205304.056f045f@tp.mains.net> Message-ID: <48AD95C4.4050703@redhat.com> Ian Main wrote: > On Thu, 21 Aug 2008 01:41:42 +0200 > Alan Pevec wrote: > > >> Minimal Fedora install tree is imported into the image during appliance creation, >> at appliance firstboot Cobbler distro and profiles are created, >> using non-mirrored repositories to save appliance disk space and setup time. >> >> Cobbler entries for each "fake" oVirt Node in developer setup is added >> if you add more oVirt Nodes, define them as Cobbler system: >> cobbler system add --name=NodeName --profile=oVirt-Node-x86_64 --mac=00:11:22:33:44:55 --netboot-enabled=1 >> otherwise they will PXE boot to the default Cobbler boot menu, where >> oVirt Node can be manually choosen >> > > Perry and I spent a lot of time messing about with this one. Turns out the config is not picked up for some bizarre reason until you restart cobbler. We tried cobbler sync too with no success. Here is what I added and it tested and worked fine: > > --- a/wui-appliance/wui-devel.ks > +++ b/wui-appliance/wui-devel.ks > @@ -336,6 +336,7 @@ cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ > --name=node4 --mac=00:16:3e:12:34:58 > cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ > --name=node5 --mac=00:16:3e:12:34:59 > +service cobblerd restart > set +x > echo "Add new oVirt Nodes as Cobbler systems to make them PXE boot oVirt Node image directly." > echo "Alternatively, choose oVirt-Node-$arch in default Cobbler boot menu." > > > If you add that, it all works fine on boot up. I'm installing fedora now! ACK with that change. :) > > Ian > > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > If you're talking about system record changes not showing up in /var/lib/tftpboot? These should just be applied automatically. Also, what cobbler version are you using? --Michael From dpierce at redhat.com Thu Aug 21 16:57:09 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 21 Aug 2008 12:57:09 -0400 Subject: [Ovirt-devel] [PATCH] Fixed a bug that left the nics table empty. Message-ID: <1219337829-23118-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- wui/src/host-browser/host-browser.rb | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 881b2ae..d331154 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -272,6 +272,7 @@ class HostBrowser # the received data to avoid creating a dupe later if detail['MAC'] == nic.mac nic_info.delete(detail) + found = true end end -- 1.5.5.1 From dpierce at redhat.com Thu Aug 21 17:12:31 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 21 Aug 2008 13:12:31 -0400 Subject: [Ovirt-devel] [PATCH] Added a configuration generation for managed nodes. It takes as input a Message-ID: <1219338751-23974-1-git-send-email-dpierce@redhat.com> --- wui/src/app/controllers/application.rb | 2 +- wui/src/lib/managed_node_configuration.rb | 53 +++++++++++ wui/src/test/fixtures/hosts.yml | 9 ++ wui/src/test/fixtures/nics.yml | 7 +- wui/src/test/fixtures/pools.yml | 4 + .../functional/managed_node_configuration_test.rb | 98 ++++++++++++++++++++ 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 wui/src/lib/managed_node_configuration.rb create mode 100644 wui/src/test/functional/managed_node_configuration_test.rb diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index d653171..b27ddbe 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base before_filter :is_logged_in def is_logged_in - redirect_to (:controller => "login", :action => "login") unless get_login_user + redirect_to(:controller => "login", :action => "login") unless get_login_user end def get_login_user diff --git a/wui/src/lib/managed_node_configuration.rb b/wui/src/lib/managed_node_configuration.rb new file mode 100644 index 0000000..6d6b7c9 --- /dev/null +++ b/wui/src/lib/managed_node_configuration.rb @@ -0,0 +1,53 @@ +# +# 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. + +# +ManagedNodeConfiguration+ takes in the description for a managed node and, +# from that, generates the configuration file that is consumed the next time +# the managed node starts up. +# + +require 'stringio' + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), "../") + +class ManagedNodeConfiguration + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' + + def self.generate(host, macs) + result = StringIO.new + + host.nics.each do |nic| + iface_name = macs[nic.mac] + + if iface_name + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DEVICE #{iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE #{nic.bridge}" if nic.bridge + result.puts "" + end + end + + result.puts "save" + + result.string + end +end diff --git a/wui/src/test/fixtures/hosts.yml b/wui/src/test/fixtures/hosts.yml index f10a756..64707da 100644 --- a/wui/src/test/fixtures/hosts.yml +++ b/wui/src/test/fixtures/hosts.yml @@ -1,3 +1,12 @@ +mailservers_managed_node: + uuid: '182a8596-961d-11dc-9387-001558c41534' + hostname: 'mail.mynetwork.com' + arch: 'i386' + memory: 16384 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + one: id: 1 uuid: '1148fdf8-961d-11dc-9387-001558c41534' diff --git a/wui/src/test/fixtures/nics.yml b/wui/src/test/fixtures/nics.yml index 008cfb7..c37f3d4 100644 --- a/wui/src/test/fixtures/nics.yml +++ b/wui/src/test/fixtures/nics.yml @@ -1,4 +1,9 @@ -# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +mailserver_nic_one: + mac: '00:11:22:33:44:55' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + one: id: 1 mac: '00:11:22:33:44:55' diff --git a/wui/src/test/fixtures/pools.yml b/wui/src/test/fixtures/pools.yml index 4cf7c97..91dd6e2 100644 --- a/wui/src/test/fixtures/pools.yml +++ b/wui/src/test/fixtures/pools.yml @@ -1,3 +1,7 @@ +prodops_pool: + name: 'Production Operations' + type: 'HardwarePool' + one: id: 1 name: 'master pool' diff --git a/wui/src/test/functional/managed_node_configuration_test.rb b/wui/src/test/functional/managed_node_configuration_test.rb new file mode 100644 index 0000000..195ec6c --- /dev/null +++ b/wui/src/test/functional/managed_node_configuration_test.rb @@ -0,0 +1,98 @@ +# +# 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. + +$:.unshift File.join(File.dirname(__FILE__),'..','lib') + +require File.dirname(__FILE__) + '/../test_helper' +require 'test/unit' +require 'managed_node_configuration' +require 'dutils' + +# Performs unit tests on the +ManagedNodeConfiguration+ class. +# +class ManagedNodeConfigurationTest < Test::Unit::TestCase + def setup + @host = Host.new + @nic = Nic.new(:mac => '00:11:22:33:44:55') + @host.nics << @nic + end + + # Ensures that network interfaces uses DHCP when no IP address is specified. + # + def test_generate_with_no_ip_address + expected = < 'eth0'} + ) + + assert_equal expected, result + end + + # Ensures that network interfaces use the IP address when it's provided. + # + def test_generate_with_ip_address + @nic.ip_addr = '192.168.2.1' + + expected = < 'eth0'} + ) + + assert_equal expected, result + end + + # Ensures the bridge is added to the configuration if one is defined. + # + def test_generate_with_bridge + @nic.bridge = 'ovirtbr0' + + expected = < 'eth0'} + ) + + assert_equal expected, result + end + +end -- 1.5.5.1 From mmorsi at redhat.com Thu Aug 21 19:17:41 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 21 Aug 2008 15:17:41 -0400 Subject: [Ovirt-devel] [PATCH] oVirt autobuild / rpm inclusion (fix) Message-ID: <48ADBF55.2010604@redhat.com> -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-autobuild-rpm-inc.patch Type: text/x-patch Size: 729 bytes Desc: not available URL: From mmorsi at redhat.com Thu Aug 21 19:18:56 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 21 Aug 2008 15:18:56 -0400 Subject: [Ovirt-devel] [PATCH] oVirt Selenium Configuration Message-ID: <48ADBFA0.3050507@redhat.com> -------------- next part -------------- A non-text attachment was scrubbed... Name: selenium-config.patch Type: text/x-patch Size: 2480 bytes Desc: not available URL: From sseago at redhat.com Thu Aug 21 19:19:02 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 21 Aug 2008 15:19:02 -0400 Subject: [Ovirt-devel] [PATCH] Stylesheet fix. In-Reply-To: <1219264517-29076-1-git-send-email-jguiditt@redhat.com> References: <1219264517-29076-1-git-send-email-jguiditt@redhat.com> Message-ID: <48ADBFA6.2090002@redhat.com> Jason Guiditta wrote: > There was a missing ; which can cause weird issues. > > Signed-off-by: Jason Guiditta > --- > wui/src/public/stylesheets/layout.css | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/wui/src/public/stylesheets/layout.css b/wui/src/public/stylesheets/layout.css > index c9ec8f4..9471cdc 100644 > --- a/wui/src/public/stylesheets/layout.css > +++ b/wui/src/public/stylesheets/layout.css > @@ -408,7 +408,7 @@ a { color:#000000; text-decoration: none;} > > .dialog_form { > margin: 15px; > - background: #FFFFFF > + background: #FFFFFF; > } > > .dialog_tree { > ACK Scott From mmorsi at redhat.com Thu Aug 21 19:20:00 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 21 Aug 2008 15:20:00 -0400 Subject: [Ovirt-devel] [PATCH] small storage_volume model bug fix Message-ID: <48ADBFE0.1080209@redhat.com> -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-bugfix1.patch Type: text/x-patch Size: 1067 bytes Desc: not available URL: From sseago at redhat.com Thu Aug 21 19:20:14 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 21 Aug 2008 15:20:14 -0400 Subject: [Ovirt-devel] [PATCH] Fix for flexigrid popups. (resend) In-Reply-To: <1219263888-28585-1-git-send-email-jguiditt@redhat.com> References: <1219263888-28585-1-git-send-email-jguiditt@redhat.com> Message-ID: <48ADBFEE.3040109@redhat.com> Jason Guiditta wrote: > These popups were sometimes too wide, this patch cleans that up. > > Also removed some columns from the hosts popup, as this was an > additional issue causing things to be too wide. > > Signed-off-by: Jason Guiditta > --- > wui/src/app/views/hardware/show_hosts.rhtml | 3 ++- > wui/src/app/views/hardware/show_storage.rhtml | 3 ++- > wui/src/app/views/host/_grid.rhtml | 23 ++++++++++++++--------- > wui/src/app/views/host/addhost.html.erb | 9 ++++++--- > wui/src/app/views/storage/_grid.rhtml | 3 +++ > wui/src/app/views/storage/add.rhtml | 2 +- > wui/src/public/stylesheets/facebox.css | 2 +- > 7 files changed, 29 insertions(+), 16 deletions(-) > > diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml > index f2962cb..2981440 100644 > --- a/wui/src/app/views/hardware/show_hosts.rhtml > +++ b/wui/src/app/views/hardware/show_hosts.rhtml > @@ -67,7 +67,8 @@ > :on_select => "hosts_select", > :on_deselect => "load_widget_deselect", > :on_hover => "load_widget_hover", > - :on_unhover => "load_widget_unhover" } %> > + :on_unhover => "load_widget_unhover", > + :is_popup => false} %> >
>
>
> diff --git a/wui/src/app/views/hardware/show_storage.rhtml b/wui/src/app/views/hardware/show_storage.rhtml > index 3446280..37af1ce 100644 > --- a/wui/src/app/views/hardware/show_storage.rhtml > +++ b/wui/src/app/views/hardware/show_storage.rhtml > @@ -67,7 +67,8 @@ > <%= render :partial => "/storage/grid", :locals => { :table_id => "storage_grid", > :hwpool => @pool, > :exclude_pool => nil, > - :on_select => "storage_select" } %> > + :on_select => "storage_select", > + :is_popup => false} %> >
> >
> diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml > index d3db182..14204b5 100644 > --- a/wui/src/app/views/host/_grid.rhtml > +++ b/wui/src/app/views/host/_grid.rhtml > @@ -1,6 +1,6 @@ > <%= render :partial => 'graph/load_graph.rhtml' %> > > -<% hosts_per_page = 40 %> > +<% hosts_per_page.nil? ? hosts_per_page = 40: hosts_per_page = hosts_per_page %> >
> <%= "" if checkboxes %> > > @@ -17,18 +17,23 @@ > :exclude_host => exclude_host, > :checkboxes => checkboxes %>', > dataType: 'json', > + <% if is_popup%> > + width: 700, > + <% end %> > colModel : [ > <%= "{display: '', width : 20, align: 'left', process: #{table_id}checkbox}," if checkboxes %> > {display: 'Hostname', name : 'hostname', width : 60, align: 'left'}, > <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_pool %> > - {display: 'UUID', name : 'uuid', width : 180, align: 'left'}, > - {display: 'Hypervisor', name : 'hypervisor_type', width : 60, align: 'left'}, > - {display: 'CPUs', name : 'num_cpus', width : 30, align: 'left'}, > - {display: 'Speed (MHz)', name : 'cpu_speed', width : 70, align: 'right'}, > - {display: 'Arch', name : 'arch', width : 50, align: 'right'}, > - {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, > - {display: 'Status', name : 'is_disabled', width : 110, align: 'right'}, > - {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } > + {display: 'UUID', name : 'uuid', width : 180, align: 'left'}<% if !is_popup %>,<% end %> > + <% if !is_popup %> > + {display: 'Hypervisor', name : 'hypervisor_type', width : 60, align: 'left'}, > + {display: 'CPUs', name : 'num_cpus', width : 30, align: 'left'}, > + {display: 'Speed (MHz)', name : 'cpu_speed', width : 70, align: 'right'}, > + {display: 'Arch', name : 'arch', width : 50, align: 'right'}, > + {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, > + {display: 'Status', name : 'is_disabled', width : 110, align: 'right'}, > + {display: 'Load', name : 'load', width: 180, sortable : false, align: 'left', process: <%= table_id %>_load_widget } > + <% end %> > ], > sortname: "hostname", > sortorder: "asc", > diff --git a/wui/src/app/views/host/addhost.html.erb b/wui/src/app/views/host/addhost.html.erb > index 41b6213..7edd4c5 100644 > --- a/wui/src/app/views/host/addhost.html.erb > +++ b/wui/src/app/views/host/addhost.html.erb > @@ -4,9 +4,9 @@ > <%- content_for :description do -%> > Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. Learn how to manage hosts > <%- end -%> > - > +
> +
>
> -
> <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_grid", > :hwpool => nil, > :exclude_pool => @hardware_pool.id, > @@ -15,10 +15,13 @@ > :on_select => "load_widget_select", > :on_deselect => "load_widget_deselect", > :on_hover => "load_widget_hover", > - :on_unhover => "load_widget_unhover" } %> > + :on_unhover => "load_widget_unhover", > + :is_popup => true, > + :hosts_per_page => 10} %> >
> > <%= popup_footer("add_hosts('#{url_for :controller => "hardware", > :action => "add_hosts", > :id => @hardware_pool}')", > "Add Hosts") %> > +
> \ No newline at end of file > diff --git a/wui/src/app/views/storage/_grid.rhtml b/wui/src/app/views/storage/_grid.rhtml > index 3bdf407..c36f4d3 100644 > --- a/wui/src/app/views/storage/_grid.rhtml > +++ b/wui/src/app/views/storage/_grid.rhtml > @@ -11,6 +11,9 @@ > { > url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => (hwpool.nil? ? nil : hwpool.id), :exclude_pool => exclude_pool %>', > dataType: 'json', > + <% if is_popup %> > + width: 700, > + <% end %> > colModel : [ > {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox}, > {display: 'Alias', width : 180, align: 'left'}, > diff --git a/wui/src/app/views/storage/add.rhtml b/wui/src/app/views/storage/add.rhtml > index 11cda06..60176a0 100644 > --- a/wui/src/app/views/storage/add.rhtml > +++ b/wui/src/app/views/storage/add.rhtml > @@ -4,7 +4,7 @@ >
> <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_grid", > :hwpool => nil, :exclude_pool => @hardware_pool.id, > - :on_select => "false" } %> > + :on_select => "false", :is_popup => true } %> >
> <%= popup_footer("add_storage('#{url_for :controller => 'hardware', > :action => 'add_storage', > diff --git a/wui/src/public/stylesheets/facebox.css b/wui/src/public/stylesheets/facebox.css > index 9c120f9..e8268a0 100644 > --- a/wui/src/public/stylesheets/facebox.css > +++ b/wui/src/public/stylesheets/facebox.css > @@ -69,7 +69,7 @@ > #facebox .tl, #facebox .tr, #facebox .bl, #facebox .br { > height: 10px; > width: 10px; > - overflow: hidden; > + overflow: auto; > padding: 0; > } > > Looks good to me. ACK Scott From mmorsi at redhat.com Thu Aug 21 19:27:40 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 21 Aug 2008 15:27:40 -0400 Subject: [Ovirt-devel] Testing oVirt with Selenium. Message-ID: <48ADC1AC.8090906@redhat.com> Everything should be setup and ready for oVirt developers to test interface functionality via Selenium. Interface tests can be added to the test/functional/interface_test.rb module and will be picked up and executed automatically when ?rake test? is run. Note if the ?selenium.rb? module is missing from the ?test/? directory, the interface tests will _not_ be loaded. Since this module cannot be added to the code base due to licensing issues, the oVirt autobuild system is configured to automatically pick it up from /var/selenium/selenium.rb (if present, else the interface tests will be skipped as mentioned before) and copy it to the oVirt test appliance. These tests contact a Selenium test server, giving it the web-accessible location of the oVirt wui. Both the Selenium Server and oVirt wui locations can now be specified via the config/selenium.rb config file (pending one of my recent patches being committed). If you are running the tests via autobuild, or on an build-all.sh generated appliance whose host is running the Selenium server (remember to open the port), you don?t need to edit any of these values, as the fixed 192.168.50.x virtual network will be employed. If you have a different test setup, you will need to modify this configuration file to specify the location of the Selenium Server and the location of the oVirt wui server from Selenium?s perspective. If your able to run ?rake test? without any errors after all this, then selenium is integrated, and you should be able to add your own tests. See the tests already there and pending ack on the list to see how to do this. Opening / closing the browser and navigating to the main page is already taken care of via the setup/teardown methods so that all that remains is to actual test the input / output of the oVirt interface. Locators for page elements can be specified via a number of means, perhaps the easiest being xpath (good primer via wikipedia http://en.wikipedia.org/wiki/Xpath ) . Though one could specify the full path to the element they are looking for eg /html/body/div[2]/... its easiest to get as close as you can via the ?descendant or self? specifier, eg ?//?, in conjunction to fixed identifiers and attributes, indexes, etc. For example to get the 3^rd list item in a list under the div identified by ?foobar? somewhere on the page, a user would use the locator ?//div[@id=?foobar?]/ul/li[3]? (note lists start at index 1). If you are unsure about your xpath locator, firebug is a great tool to assist you, see more information here http://selenium-core.openqa.org/xpath-help.html There are many other means to specify locators other than Xpath, such as by javascript, css, etc; see here for more info http://selenium-core.openqa.org/reference.html Now that you can locate elements on a page, operating on them is trivial. You may use the Selenium ruby api http://release.openqa.org/selenium-remote-control/0.9.2/doc/ruby/ (Selenium also provides bindings for many other languages) to interface with the site. Some useful commands are: * click(locator) - click on a page element * type (locator, text) ? type some text into an element * get_xpath_count(locator) ? determine the number of nodes found for a locator, useful for counting list items and what not * etc Since our site is very javascript heavy, the ?waitForCondition? method, which takes a javascript expression and a timeout and waits until the expression evaluates true or the timeout occurs (whichever happens first) is critical. Make sure to invoke this before attempting to perform any operations on page elements loaded by javascript, less they might not be present and your test fails. To wait for the existance of a div identified by ?foobar?, wait_for_condition("selenium.isElementPresent(\"//div[@id='foobar']\")", 10000) And thats about it, Selenium provides many more methods to test the application than I describe here, but this should give you a general idea of where to start. Hope this helps. -Mo -------------- next part -------------- An HTML attachment was scrubbed... URL: From berrange at redhat.com Thu Aug 21 20:01:03 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 21 Aug 2008 21:01:03 +0100 Subject: [Ovirt-devel] [PATCH] oVirt autobuild / rpm inclusion (fix) In-Reply-To: <48ADBF55.2010604@redhat.com> References: <48ADBF55.2010604@redhat.com> Message-ID: <20080821200103.GE1531@redhat.com> On Thu, Aug 21, 2008 at 03:17:41PM -0400, Mohammed Morsi wrote: > > # make oVirt RPMs available as autobuild output > # TODO fix oVirt Makefiles to honour ~/.rpmmacros (autobuild sets that up) > -ln tmp/ovirt/*.rpm $AUTOBUILD_PACKAGE_ROOT/rpm > +rm -f $AUTOBUILD_PACKAGE_ROOT/rpm/*.rpm > +ln -s `pwd`/tmp/ovirt/*.rpm $AUTOBUILD_PACKAGE_ROOT/rpm/ NACK. Hardlinking is the correct way todo this. symlinking increasing the I/O overhead, and prevents autobuild from using hardlinks for its archive and when publish to the web root. 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 apevec at redhat.com Thu Aug 21 21:30:22 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 21 Aug 2008 23:30:22 +0200 Subject: [Ovirt-devel] [PATCH] make oVirt-Node-$arch default boot option in Cobbler menu Message-ID: <1219354222-7579-1-git-send-email-apevec@redhat.com> Signed-off-by: Alan Pevec --- wui-appliance/wui-devel.ks | 7 +++++-- 1 files changed, 5 insertions(+), 2 deletions(-) diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 6202e8b..a2d42b9 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -339,12 +339,13 @@ cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ service cobblerd restart set +x echo "Add new oVirt Nodes as Cobbler systems to make them PXE boot oVirt Node image directly." -echo "Alternatively, choose oVirt-Node-$arch in default Cobbler boot menu." +echo "oVirt-Node-$arch is also default boot option in Cobbler menu" EOF chmod +x $INSTALL_ROOT/etc/rc.d/rc.cobbler-import echo "[ -x /etc/rc.d/rc.cobbler-import ] && /etc/rc.d/rc.cobbler-import" \ >> $INSTALL_ROOT/etc/rc.d/rc.local - echo done + printf "oVirt-Node-$arch" > $INSTALL_ROOT/tmp/cobbler-default + echo done ) %end @@ -365,4 +366,6 @@ EOF -e "s/^next_server:.*/next_server: '192.168.50.2'/" \ /etc/cobbler/settings sed -i -e '/kernel /a \\tIPAPPEND 2' /etc/cobbler/pxesystem.template + sed -i -e "s/^ONTIMEOUT.*/ONTIMEOUT $(cat /tmp/cobbler-default)/" \ + /etc/cobbler/pxedefault.template %end -- 1.5.4.1 From imain at redhat.com Thu Aug 21 21:33:32 2008 From: imain at redhat.com (Ian Main) Date: Thu, 21 Aug 2008 14:33:32 -0700 Subject: [Ovirt-devel] [PATCH] Fix console order Message-ID: <1219354412-28622-1-git-send-email-imain@redhat.com> This patch just changes the order of the consoles so that init messages are always sent to tty0. This allows these settings to work even on machines without a serial port. Signed-off-by: Ian Main --- ovirt-host-creator/common-install.ks | 2 +- wui-appliance/wui-devel.ks | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ovirt-host-creator/common-install.ks b/ovirt-host-creator/common-install.ks index 053c03b..9cdf976 100644 --- a/ovirt-host-creator/common-install.ks +++ b/ovirt-host-creator/common-install.ks @@ -7,6 +7,6 @@ firewall --disabled part / --size 550 --fstype ext2 services --enabled=ntpd,ntpdate,collectd,iptables,network # This requires a new fixed version of livecd-creator to honor the --append settings. -bootloader --timeout=1 --append="console=tty0 console=ttyS0,115200n8" +bootloader --timeout=1 --append="console=ttyS0,115200n8 console=tty0" rootpw --iscrypted Xa8QeYfWrtscM diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 6202e8b..c652e53 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -328,7 +328,7 @@ cobbler profile edit --name=$os-$ver-$arch --repos="f9-$arch f9-$arch-updates" # before Cobbler overwrites it cobbler distro add --name="oVirt-Node-$arch" --arch=$arch \ --initrd=/var/lib/tftpboot/initrd0.img --kernel=/var/lib/tftpboot/vmlinuz0 \ - --kopts="rootflags=loop root=/ovirt.iso rootfstype=iso9660 ro console=tty0 console=ttyS0,115200n8" + --kopts="rootflags=loop root=/ovirt.iso rootfstype=iso9660 ro console=ttyS0,115200n8 console=tty0" cobbler profile add --name=oVirt-Node-$arch --distro=oVirt-Node-$arch cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ --name=node3 --mac=00:16:3e:12:34:57 -- 1.5.5.1 From imain at redhat.com Thu Aug 21 22:14:42 2008 From: imain at redhat.com (Ian Main) Date: Thu, 21 Aug 2008 15:14:42 -0700 Subject: [Ovirt-devel] [PATCH] make oVirt-Node-$arch default boot option in Cobbler menu In-Reply-To: <1219354222-7579-1-git-send-email-apevec@redhat.com> References: <1219354222-7579-1-git-send-email-apevec@redhat.com> Message-ID: <20080821151442.6bb3c9be@tp.mains.net> On Thu, 21 Aug 2008 23:30:22 +0200 Alan Pevec wrote: > Signed-off-by: Alan Pevec > --- > wui-appliance/wui-devel.ks | 7 +++++-- > 1 files changed, 5 insertions(+), 2 deletions(-) ACK. Works nicely. We going to keep the macs for the vm nodes then? Guess it doesn't hurt.. With this plus my other patch I can boot a mccreary node right out of the box. Ian From imain at redhat.com Thu Aug 21 22:19:49 2008 From: imain at redhat.com (Ian Main) Date: Thu, 21 Aug 2008 15:19:49 -0700 Subject: [Ovirt-devel] [PATCH] make oVirt-Node-$arch default boot option in Cobbler menu In-Reply-To: <20080821151442.6bb3c9be@tp.mains.net> References: <1219354222-7579-1-git-send-email-apevec@redhat.com> <20080821151442.6bb3c9be@tp.mains.net> Message-ID: <20080821151949.1b75ac26@tp.mains.net> On Thu, 21 Aug 2008 15:14:42 -0700 Ian Main wrote: > On Thu, 21 Aug 2008 23:30:22 +0200 > Alan Pevec wrote: > > > Signed-off-by: Alan Pevec > > --- > > wui-appliance/wui-devel.ks | 7 +++++-- > > 1 files changed, 5 insertions(+), 2 deletions(-) Pushed. Ian From pmyers at redhat.com Thu Aug 21 23:20:02 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 21 Aug 2008 19:20:02 -0400 Subject: [Ovirt-devel] [PATCH] Fix console order In-Reply-To: <1219354412-28622-1-git-send-email-imain@redhat.com> References: <1219354412-28622-1-git-send-email-imain@redhat.com> Message-ID: <48ADF822.2070906@redhat.com> Ian Main wrote: > This patch just changes the order of the consoles so that init messages > are always sent to tty0. This allows these settings to work even on > machines without a serial port. Assuming you've already tested this on a box without a serial port (since I don't have one to test on right now), ACK Perry From imain at redhat.com Fri Aug 22 00:02:54 2008 From: imain at redhat.com (Ian Main) Date: Thu, 21 Aug 2008 17:02:54 -0700 Subject: [Ovirt-devel] [PATCH] Fix console order In-Reply-To: <48ADF822.2070906@redhat.com> References: <1219354412-28622-1-git-send-email-imain@redhat.com> <48ADF822.2070906@redhat.com> Message-ID: <20080821170254.28749ca7@tp.mains.net> On Thu, 21 Aug 2008 19:20:02 -0400 "Perry N. Myers" wrote: > Ian Main wrote: > > This patch just changes the order of the consoles so that init messages > > are always sent to tty0. This allows these settings to work even on > > machines without a serial port. > > Assuming you've already tested this on a box without a serial port (since > I don't have one to test on right now), ACK Yes it works well. With this and apevecs patch I can boot both my machines right out of the box. Ian From pmyers at redhat.com Fri Aug 22 02:25:00 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 21 Aug 2008 22:25:00 -0400 Subject: [Ovirt-devel] provisioning broken with rubygem-cobbler-0.0.2-1 and cobbler-1.1.1-2 Message-ID: <48AE237C.10209@redhat.com> Went through some testing tonight to make sure things were looking halfway decent and ran into some problems provisioning new VMs with the Fedora cobbler profile. Error in taskomatic (after removing the rescue block) was: > create_vm > Task action processing failed: TypeError: can't convert String into Integer > /usr/lib/ruby/gems/1.8/gems/cobbler-0.0.2/lib/cobbler/base.rb:127:in `[]' > /usr/lib/ruby/gems/1.8/gems/cobbler-0.0.2/lib/cobbler/base.rb:127:in `definition' > (eval):2:in `mac_address' > /usr/lib/ruby/gems/1.8/gems/cobbler-0.0.2/lib/cobbler/network_interface.rb:42:in `bundle_for_saving' > /usr/lib/ruby/gems/1.8/gems/cobbler-0.0.2/lib/cobbler/system.rb:71:in `save' > /usr/lib/ruby/gems/1.8/gems/cobbler-0.0.2/lib/cobbler/system.rb:69:in `each' > /usr/lib/ruby/gems/1.8/gems/cobbler-0.0.2/lib/cobbler/system.rb:69:in `save' > /usr/share/ovirt-wui/task-omatic/./task_vm.rb:169:in `create_vm' > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:97 > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:88:in `each' > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:88 > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:68:in `loop' > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:68 It looks like code in network_interface.rb is trying to convert the MAC (which is a string) into an int for some reason and an exception is being thrown. Since I don't know the details of what this is trying to do (and we're trying to get things wrapped up to bundle up a new release soon) I tried removing the 0.0.2-1 version of the bindings and replacing with the older 0.0.1-2 version. This solved the problem for now. Darryl, can you debug this and rebuild the RPM? Make sure you run a full regression test to make sure there are no problems before putting the RPM up on ovirt.org. In addition, provisioning was also broken due to addition of cobbler 1.1.1-2. Log in /root/cobbler-import.log on appliance shows: > Could not find files matching /usr/share/syslinux/memdisk Looks like we're missing syslinux package on appliance. New version of cobbler must use files from syslinux for something, because missing memdisk was preventing cobbler sync from working which caused the Fedora profile to not be created in /var/lib/tftp/images. So added syslinux to wui packages list and committed and this fixed it. These errors bring up an important point... For packages that folks on our team maintain (like a lot of the ruby gems, custom ovirt builds of libvirt/kvm, etc...) we need to treat changes to these packages the same as we treat patches to the ovirt.org git repo. i.e. before putting new RPMs into the ovirt.org repos, make sure you run regression tests for basic functionality in the areas that your RPM affect. Other packages in Fedora that we don't own can also cause problems occasionally (as we saw in cobbler here) but hopefully that will be less frequent. In that case we'll have to live with the pain of dealing with the Fedora package release cycle and fixing things as they break. Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From pmyers at redhat.com Fri Aug 22 03:43:45 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 21 Aug 2008 23:43:45 -0400 Subject: [Ovirt-devel] virt-viewer plugin integration issues Message-ID: <48AE35F1.1010106@redhat.com> Looking for some design advice from you guys. Here's the situation. We want to be able to run virt-viewer to connect to oVirt Node guests from hosts that are not part of the kerberos infrastructure. From my looking around it seems we have the following options: 1. enable digest-md5 as an auth mech and do user/pass auth and setup a simple service account just for virt-viewer (using qemu+tcp connect method) 2. use qemu+ssh to connect to libvirt on the Node 1 doesn't seem to work presently since virt-viewer won't prompt you for user/password if digest-md5 is a valid auth method (is that because virConnectOpenReadOnly is used instead of virConnectOpenAuth?) And even if it were modified to prompt for a password that would happen on a shell which may not exist if you're launching firefox from a desktop icon. We'd need a graphical prompt for the user/pass or the ability to pass the password as part of the uri perhaps. 2 is problematic since we'd have to set up ssh keys at build time and distribute them as part of the appliance. Key management that we've been trying to avoid with all of this. Either of you have any suggestions on where we should go with this. Short term we need a solution (even if it is slightly hackish) just to make the console work. Longer term we need something more secure. Dan you mentioned just falling back and using straight vnc plugin since we don't need the vnc port lookup since oVirt Server has that info. That doesn't work for when Node is in standalone mode with no server... And besides in standalone mode libvirt has to do digest-md5 since we have no kerberos infrastructure in that mode. Speaking of that... Alan, for your standalone Node patches we need to switch libvirt from gssapi to digest-md5 and create an account for people to use... that account creation should be part of the Node first-boot configuration TUI probably (along with setting the root passwd). Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From apevec at redhat.com Fri Aug 22 06:57:15 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 22 Aug 2008 08:57:15 +0200 Subject: [Ovirt-devel] [PATCH] make oVirt-Node-$arch default boot option in Cobbler menu In-Reply-To: <20080821151442.6bb3c9be@tp.mains.net> References: <1219354222-7579-1-git-send-email-apevec@redhat.com> <20080821151442.6bb3c9be@tp.mains.net> Message-ID: <48AE634B.4020704@redhat.com> Ian Main wrote: > On Thu, 21 Aug 2008 23:30:22 +0200 > Alan Pevec wrote: > >> Signed-off-by: Alan Pevec >> --- >> wui-appliance/wui-devel.ks | 7 +++++-- >> 1 files changed, 5 insertions(+), 2 deletions(-) > > ACK. Works nicely. We going to keep the macs for the vm nodes then? Guess it doesn't hurt.. Yes, you want all Nodes defined as Cobbler systems, so that they boot oVirt Node image directly. TODO is also to make oVirt Server register Nodes with Cobbler when they identify themselves, during their first boot. Default selection is fragile (breaks if someone presses a key on the Node console) and takes timeout (20 by default) second more to boot. From clalance at redhat.com Fri Aug 22 07:12:39 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 22 Aug 2008 09:12:39 +0200 Subject: [Ovirt-devel] virt-viewer plugin integration issues In-Reply-To: <48AE35F1.1010106@redhat.com> References: <48AE35F1.1010106@redhat.com> Message-ID: <48AE66E7.7070709@redhat.com> Perry N. Myers wrote: > Dan you mentioned just falling back and using straight vnc plugin since we > don't need the vnc port lookup since oVirt Server has that info. That > doesn't work for when Node is in standalone mode with no server... And > besides in standalone mode libvirt has to do digest-md5 since we have no > kerberos infrastructure in that mode. One thought. You *can* setup libvirt to do completely unencrypted tcp communication; you just need to set up libvirtd.conf properly. So maybe we can just do that in standalone mode, and bypass the digest-md5 thing altogether? Since we aren't ever leaving the local node with the traffic, we really shouldn't have a security concern. Then (I think) we would be able to use the VNC plugin. Chris Lalancette From apevec at redhat.com Fri Aug 22 07:30:46 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 22 Aug 2008 09:30:46 +0200 Subject: [Ovirt-devel] virt-viewer plugin integration issues In-Reply-To: <48AE66E7.7070709@redhat.com> References: <48AE35F1.1010106@redhat.com> <48AE66E7.7070709@redhat.com> Message-ID: <48AE6B26.80102@redhat.com> Chris Lalancette wrote: > Perry N. Myers wrote: >> Dan you mentioned just falling back and using straight vnc plugin since we >> don't need the vnc port lookup since oVirt Server has that info. That >> doesn't work for when Node is in standalone mode with no server... And >> besides in standalone mode libvirt has to do digest-md5 since we have no >> kerberos infrastructure in that mode. > > One thought. You *can* setup libvirt to do completely unencrypted tcp > communication; you just need to set up libvirtd.conf properly. So maybe we can > just do that in standalone mode, and bypass the digest-md5 thing altogether? > Since we aren't ever leaving the local node with the traffic, we really > shouldn't have a security concern. Then (I think) we would be able to use the > VNC plugin. Connection to libvirt is for virt-viewer to look up the VNC port, and oVirt Server stores port in the database (updated by host-status daemon) so you have all the info (hostname+port) to start any VNC client. In standalone you must use virt-viewer since otherwise you don't know VNC port. But you're right, in standalone mode we should just disable libvirt remoting and use local qemu:///system URL. From rjones at redhat.com Fri Aug 22 07:57:26 2008 From: rjones at redhat.com (Richard W.M. Jones) Date: Fri, 22 Aug 2008 08:57:26 +0100 Subject: [Ovirt-devel] Re: virt-viewer plugin integration issues In-Reply-To: <48AE35F1.1010106@redhat.com> References: <48AE35F1.1010106@redhat.com> Message-ID: <20080822075726.GF31279@amd.home.annexia.org> On Thu, Aug 21, 2008 at 11:43:45PM -0400, Perry N. Myers wrote: > Either of you have any suggestions on where we should go with this. > Short term we need a solution (even if it is slightly hackish) just to > make the console work. Longer term we need something more secure. virt-viewer in this case is still running from the browser plugin, or are we expecting users to launch virt-viewer themselves? If we control the launching of virt-viewer (eg. through the browser plugin) then can we obtain a one-time password or key from the QEMU server on the managed node? If we can obtain one, then we can easily pass it through to virt-viewer via an HTML parameter which gets passed to the virt-viewer command line. I think I'm not sure of the exact constraints that this needs to operate under. Rich. -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ From berrange at redhat.com Fri Aug 22 08:25:05 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Fri, 22 Aug 2008 09:25:05 +0100 Subject: [Ovirt-devel] virt-viewer plugin integration issues In-Reply-To: <48AE66E7.7070709@redhat.com> References: <48AE35F1.1010106@redhat.com> <48AE66E7.7070709@redhat.com> Message-ID: <20080822082505.GB9321@redhat.com> On Fri, Aug 22, 2008 at 09:12:39AM +0200, Chris Lalancette wrote: > Perry N. Myers wrote: > > Dan you mentioned just falling back and using straight vnc plugin since we > > don't need the vnc port lookup since oVirt Server has that info. That > > doesn't work for when Node is in standalone mode with no server... And > > besides in standalone mode libvirt has to do digest-md5 since we have no > > kerberos infrastructure in that mode. > > One thought. You *can* setup libvirt to do completely unencrypted tcp > communication; you just need to set up libvirtd.conf properly. So maybe we can > just do that in standalone mode, and bypass the digest-md5 thing altogether? > Since we aren't ever leaving the local node with the traffic, we really > shouldn't have a security concern. Then (I think) we would be able to use the > VNC plugin. If you're doing it local only, then don't use TCP at all. Connect to the UNIX socket. Use TCP in unencrypted mode is just a recipe for disaster because sooner or later someone will switch it from localhost to public forgetting you disabled encryption. Either set the user/group ownership of the UNIX socket to what you need, or let is do PolicyKit auth locally (if running a desktop app for local node). 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 berrange at redhat.com Fri Aug 22 08:26:25 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Fri, 22 Aug 2008 09:26:25 +0100 Subject: [Ovirt-devel] Re: virt-viewer plugin integration issues In-Reply-To: <20080822075726.GF31279@amd.home.annexia.org> References: <48AE35F1.1010106@redhat.com> <20080822075726.GF31279@amd.home.annexia.org> Message-ID: <20080822082625.GC9321@redhat.com> On Fri, Aug 22, 2008 at 08:57:26AM +0100, Richard W.M. Jones wrote: > On Thu, Aug 21, 2008 at 11:43:45PM -0400, Perry N. Myers wrote: > > Either of you have any suggestions on where we should go with this. > > Short term we need a solution (even if it is slightly hackish) just to > > make the console work. Longer term we need something more secure. > > virt-viewer in this case is still running from the browser plugin, or > are we expecting users to launch virt-viewer themselves? > > If we control the launching of virt-viewer (eg. through the browser > plugin) then can we obtain a one-time password or key from the QEMU > server on the managed node? If we can obtain one, then we can easily > pass it through to virt-viewer via an HTML parameter which > gets passed to the virt-viewer command line. The one time key idea doesn't easily work because it doesn't enable use of encryption - it only provides authentication - unless we find a SASL plugin that lets us do this. 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 clalance at redhat.com Fri Aug 22 08:45:31 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 22 Aug 2008 10:45:31 +0200 Subject: [Ovirt-devel] Migration testing Message-ID: <48AE7CAB.1090101@redhat.com> Hello, I did some end-to-end migration testing with all of the new stuff today. Everything seemed to work (at least, the parts that I could test). I was able to test clicking on an individual VM, and saying "migrate", which worked. I was also able to test clicking on a host, and saying "Clear Host" (that wording could be better). I was not able to test "migrate to a particular machine", because the WUI didn't show any machines in that dialog box. There's a bug in there somewhere. Chris Lalancette From berrange at redhat.com Fri Aug 22 09:04:00 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Fri, 22 Aug 2008 10:04:00 +0100 Subject: [Ovirt-devel] Re: virt-viewer plugin integration issues In-Reply-To: <48AE35F1.1010106@redhat.com> References: <48AE35F1.1010106@redhat.com> Message-ID: <20080822090400.GQ9321@redhat.com> On Thu, Aug 21, 2008 at 11:43:45PM -0400, Perry N. Myers wrote: > Looking for some design advice from you guys. Here's the situation. > > We want to be able to run virt-viewer to connect to oVirt Node guests from > hosts that are not part of the kerberos infrastructure. From my looking > around it seems we have the following options: > > 1. enable digest-md5 as an auth mech and do user/pass auth and setup a > simple service account just for virt-viewer (using qemu+tcp connect > method) Don't use a special account - the user/pass auth should simply be delegated to the FreeIPA LDAP server instead of using a local passwd DB. > 2. use qemu+ssh to connect to libvirt on the Node > > 1 doesn't seem to work presently since virt-viewer won't prompt you for > user/password if digest-md5 is a valid auth method (is that because > virConnectOpenReadOnly is used instead of virConnectOpenAuth?) And even > if it were modified to prompt for a password that would happen on a shell > which may not exist if you're launching firefox from a desktop icon. We'd > need a graphical prompt for the user/pass or the ability to pass the > password as part of the uri perhaps. virt-viewer already has ability to prompt for username/password. If it is not doing this, then I expect its not choosing digest-md5. You can force it to choose digest-md5 with a special libvirt URL qemu+tcp://hostname/system?auth=sasl.digest-md5 > 2 is problematic since we'd have to set up ssh keys at build time and > distribute them as part of the appliance. Key management that we've been > trying to avoid with all of this. Yes, don't do this. SSH key management is the devil and it destroys your audit trail because it lets one user triivally login as another without trace. > Speaking of that... Alan, for your standalone Node patches we need to > switch libvirt from gssapi to digest-md5 and create an account for people > to use... that account creation should be part of the Node first-boot > configuration TUI probably (along with setting the root passwd). You shouldn't need to switch - you can run both SASL methods at once and it should choose the 'best' one to authenticate with, or you can force the choice with the URL shown above. Setting up both is my recommendation 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 berrange at redhat.com Fri Aug 22 11:02:35 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Fri, 22 Aug 2008 12:02:35 +0100 Subject: [Ovirt-devel] Re: virt-viewer plugin integration issues In-Reply-To: <20080822090400.GQ9321@redhat.com> References: <48AE35F1.1010106@redhat.com> <20080822090400.GQ9321@redhat.com> Message-ID: <20080822110235.GT9321@redhat.com> On Fri, Aug 22, 2008 at 10:04:00AM +0100, Daniel P. Berrange wrote: > On Thu, Aug 21, 2008 at 11:43:45PM -0400, Perry N. Myers wrote: > > Looking for some design advice from you guys. Here's the situation. > > > > We want to be able to run virt-viewer to connect to oVirt Node guests from > > hosts that are not part of the kerberos infrastructure. From my looking > > around it seems we have the following options: > > > > 1. enable digest-md5 as an auth mech and do user/pass auth and setup a > > simple service account just for virt-viewer (using qemu+tcp connect > > method) > > Don't use a special account - the user/pass auth should simply be delegated > to the FreeIPA LDAP server instead of using a local passwd DB. [snip] > > Speaking of that... Alan, for your standalone Node patches we need to > > switch libvirt from gssapi to digest-md5 and create an account for people > > to use... that account creation should be part of the Node first-boot > > configuration TUI probably (along with setting the root passwd). > > You shouldn't need to switch - you can run both SASL methods at once and > it should choose the 'best' one to authenticate with, or you can force > the choice with the URL shown above. Setting up both is my recommendation Ok, here's the low-down on SASL There are many auth mechanisms, but if going over TCP we need one which provides an SSF layer (ie session encryption). This narrows down the choices to just 3, GSSAPI, DIGEST-MD5 and SRP. SRP is patented and not in Fedora/Debian so that's ruled out too. Since we're explicitly trying to provide a non-GSSAPI option, that leaves DIGEST-MD5 Digest MD5 is a shared secret mechanism. The password itself does not ever travel on the wire; the client sends a token which proves it knows the secret, this requires that the server have access to the cleartext secret to verify. The way SASL checks shared secrets also uses plugins. By default it uses the sasldb plugin, which checks against /etc/libvirt/passwd.db. Other plugins are ldapdb and sql. It looks like we ned to use one of these two plugins. This So as a starting point in /etc/sasl2/libvirt.conf you'd need to enable digest-md5 and set the desired plugin mech_list: gssapi digest-md5 auxprop_plugin: ldapdb (or sql) If using the LDAP plugin the LDAP server needs to have the user passwords available in cleartext. I'm unclear on whether FreeIPA does it this or not. If it does then we just ned a little more magic config to tell the ldap plugin how to connect to the LDAP server - this can be done with GSSAPI. If FreeIPA doesn't make passwords available, we could setup a separate table in the oVirt WUI database and maintain usernames/passwords in that instead. The main trouble with all ths stuff is that you are giving your users the permission to onnect to libvirt on the hosts. This gives them the permission todo anything with libvirt, not just the VNC port lookup. So ultimately, the entire plan to use virt-viewer is flawed and oVirt WUI should talk to the libvirtd to discover the port number and put that in the HTML page. The VNC client then is merely concerned with auth against the VNC server, and not libvirt. We'd still use a GTK-VNC baed VNC client, simply not the virt-viewer one We'd then want to write a VNC extension to use SASL, and have the VNC server SASL plugin use the SQL backend for auth. That way we can authenticate the VNC server connections against oVirt database table - and just store one time passwords in that database. NB, SASL does have an explicit OTP plugin, but this doesn't provide an SSF layer, so its not suitable. Hence why we need to use DIGEST-MD% and have its password plugin lookup a OTP in the oVirt DB 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 clalance at redhat.com Fri Aug 22 11:07:12 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 22 Aug 2008 13:07:12 +0200 Subject: [Ovirt-devel] [PATCH]: Remove bad documentation files Message-ID: <1219403232-17849-1-git-send-email-clalance@redhat.com> Remove a bunch of the documentation in the source tree that was not only wrong, but actively misleading in some cases. Signed-off-by: Chris Lalancette diff --git a/README b/README deleted file mode 100644 index 2d8a975..0000000 --- a/README +++ /dev/null @@ -1,22 +0,0 @@ -To do the setup for an Ovirt host, look in the ovirt-host-creator/ directory -and read INSTALL and README. - -To setup a management application, look in the wui/ directory. - -Temporary: we don't have a README for the wui directory yet, so I'll just put -this info here so I don't lose it. - -To make the task-omatic part of the WUI work properly, we have to create a -keytab for it and put it in the right spot. This is so that taskomatic can -properly authenticate to the freeipa server. - -On the freeipa server: -# kadmin.local - > addprinc -randkey libvirt/@ (where is the DNS - name of the machine where the - WUI is running, and - is the realm you are using) - > modprinc +requires_preauth libvirt/@ - > ktadd -k /tmp/taskomatic.keytab libvirt/@ - > quit -# scp /tmp/taskomatic.keytab wui:/usr/share/ovirt-wui/ovirt.keytab (where 'wui' is the name of the machine where the WUI is running) diff --git a/ovirt-host-creator/INSTALL b/ovirt-host-creator/INSTALL deleted file mode 100644 index 42cb1ac..0000000 --- a/ovirt-host-creator/INSTALL +++ /dev/null @@ -1,179 +0,0 @@ -Installation instructions -------------------------- - -[ CAUTION: The instructions in http://ovirt.org/install-instructions.html - are usually more up-to-date than the instructions here. ] - -This file describes an Ovirt installation using a Flash drive as the root -partition. This is the easiest configuration to work with; using either a -LiveCD or PXE is very similar, but presents a few additional steps, so it -will not be discussed here. - -In order to have an ovirt host start up, you'll need to have two machines -available: - -Machine 1 - Fedora 8 x86_64 machine - this will be the build server and the -DHCP/NFS/iSCSI server for the rest of the setup - -Machine 2 - x86_64 machine - this will be the Ovirt host at the end - -There are quite a few steps to getting this running, so let's get started. - -Machine 1/Build Server setup -1) Make sure you have the following packages installed on the build server: - -python-virtinst -libvirt -libvirt-python -libvirt-devel -livecd-tools -dhcp -nfs-utils -scsi-target-utils -gnutls-utils -git -httpd -cyrus-sasl -cyrus-sasl-lib -cyrus-sasl-gssapi -rpm-build -libxml2-devel -perl-ExtUtils-MakeMaker -lm_sensors-devel -python-devel -readline-devel -ncurses-devel -avahi-devel -qemu -cyrus-sasl-devel -gcc -autoconf -automake -libtool -SDL-devel -dev86 -iasl -compat-gcc-34 -e2fsprogs-devel -texi2html -glibc-devel -kernel-devel -createrepo -rrdtool-devel - -2) Checkout the ovirt repo: -# git clone git://et.devel.redhat.com/ovirt - -3) Compile the collectd SRPM located in the "srpms" directory. When it's -done compiling, do the following: - -# mkdir -p /var/www/html/rpms -# cp collectd-.x86_64.rpm /var/www/html/rpms -# createrepo /var/www/html/rpms - -4) Recompile the libvirt SRPM with GSSAPI/kerberos support (located in the -"srpms" directory). When it's done recompiling, do the following: - -# mkdir -p /var/www/html/rpms -# cp libvirt-.x86_64.rpm /var/www/html/rpms -# createrepo /var/www/html/rpms - -5) Recompile the kvm SRPM located in the "srpms" directory. We need this to -bring it up to version 54; note that just using the package from rawhide is -*not* enough, since it doesn't include the updated kernel modules. You need to -install the kernel-devel package to successfully compile this. Note that the -version of kernel-devel you need is the version that will be on the ovirt host, -that is, what is going to get pulled in by yum in subsequent steps; otherwise, -the modules will fail to load on the ovirt host. When it is done recompiling, -do the following: - -# mkdir -p /var/www/html/rpms -# cp kvm-modules.x86_64.rpm /var/www/html/rpms -# createrepo /var/www/html/rpms - -6) Edit ovirt.ks, and add an additional repo where it will pull the collectd -RPM (and optionally, the kerberized libvirt RPM). There is an example repo in -there already. - -7) Put a 1GB (or greater) flash drive into the machine; you'll need to find out -which SCSI device it got assigned to via dmesg (mine went to /dev/sdc, for -instance) - -8) # cd /root/ovirt ; ./ovirt-flash.sh /dev/sdc - -(replacing /dev/sdc with the device your USB stick is). Be warned that this -will totally destroy any data on the USB stick, so make sure to back up any -data you care about. This step will take quite a while, as it will download -and yum install all of the necessary packages into a chroot jail, run the -kickstart %post, and lay the resulting image down on the USB stick. - -9) Edit /etc/dhcpd.conf; you'll want to make it look like: - ------------------------------------------------------------------ -allow booting; -allow bootp; -ddns-update-style interim; -ignore client-updates; - -option libvirt-auth-method code 202 = text; - -subnet 192.168.25.0 netmask 255.255.255.0 { - option domain-name "redhat.com"; - option domain-name-servers 172.16.76.10,172.16.76.20; - next-server 192.168.25.1; - option routers 192.168.25.1; - option libvirt-auth-method "krb5:192.168.25.1"; - host perf200 { - fixed-address 192.168.25.200; - hardware ethernet 00:13:20:f5:fa:7c; - } - - filename "pxelinux.0"; -} ------------------------------------------------------------------ - -Make sure to replace the subnet with the one you want to use, the -domain-name-servers to what you want, and the next-server, routers, -and libvirt-auth-method options to be the IP address of *this* server. -Finally, make sure that you change the "host perf200" to assign a static IP -address to your ovirt host(s). The IP address and DNS name will be important -for a number of the authentication mechanisms later. - -10) Follow the steps in README.krb5 for setup. You'll need to take the -/etc/krb5.conf and keytable files that you generated for the "ovirt" -machine and place them in /var/www/html for the init scripts to fetch. -Make sure to call the keytable file "-libvirt.tab" and the krb5.conf -file "-krb5.conf", as that is the filename the scripts will look for. - -11) Setup the services you need: - -# service dhcpd start -# service iptables stop -# service httpd start - -12) Set up 1 or more iSCSI LUNs for export: - -# lvcreate -n iSCSI1 -L +10G /dev/HostGroup - -(or whatever you want; just make sure that you have either a raw partition or -an LVM volume available for guests) - -# service tgtd start -# tgtadm --lld iscsi --op new --mode target --tid 1 -T host:storage - -(the host:storage can actually be anything you want; it's mostly used as a -descriptive name later. I made mine :storage.virt) - -# tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 -b /dev/HostGroup/iSCSI1 - -(this exports /dev/HostGroup/iSCSI1 as LUN 1 on the target we made above. If -you want, repeat this for additional devices, making sure to incremente the LUN -for each) - -# tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL - -Machine 2/Ovirt Host: -1) Take the USB stick created on the build server, and make sure your BIOS -is set to boot to USB. -2) Boot the host; it should come up and login to all of the iSCSI servers -that you specified. It should also be all setup for connecting via GSSAPI. diff --git a/ovirt-host-creator/README b/ovirt-host-creator/README deleted file mode 100644 index e59bc12..0000000 --- a/ovirt-host-creator/README +++ /dev/null @@ -1,142 +0,0 @@ -OVIRT SCRIPTS README --------------------- - -[ CAUTION: The instructions in http://ovirt.org/install-instructions.html - are usually more up-to-date than the instructions here. ] - -If this is the first time you are using the Ovirt scripts, start with INSTALL. -This file is a collection of usage notes, and they assume INSTALL has already -been completed. - -This is currently a collection of scripts to build/deploy an Ovirt instance, -which is essentially stateless. Each of the commands is described below, along -with common usage. - -ovirt-cd.sh: This is just a thin wrapper around livecd-creator, mostly here for -orthogonality with the other commands. - -ovirt-flash.sh: This is a wrapper around creator.py and livecd-iso-to-disk. -Basically this creates an ISO with creator.py, and then lays down that ISO on -top of a flash drive. It is run as: - -./ovirt-flash.sh [base_iso] - -where the second argument is a base ISO to start from. Note that this command -completely destroys any USB device you point it at, so be careful. - -ovirt-flash-static.sh: This is a wrapper around creator.py that additionally -will lay down the output from the creator.py onto a flash drive. It is -"static" because when you boot from this, you are actually booting and writing -to the flash drive, not to a "LiveCD", in-memory drive. This is useful for -development. - -ovirt-pxe.sh: This is a command to create the files needed to do a PXE boot of -ovirt. It is run as: - -./ovirt-pxe.sh [base_iso] - -What this command does is to create an ISO (or use the one from base_iso), and -then create a tftpboot/ in the local directory to set it all up for PXE -booting. You can then follow README.pxe to set up the rest of the server, and -you should be able to get the machine to boot from PXE. - -Typical Script Usage --------------------- - -Here's how I've been typically using these scripts. - -1) Have a 1GB flash drive. I think you can get away with 500MB, but it might -be quite tight. Put the flash drive into the machine where you have the -scripts. -2) Run ./ovirt-flash.sh /dev/sdc. This will build an ISO from repos, and -lay it down on /dev/sdc -3) Put the flash drive in the target machine, and boot that machine from USB. -4) Oops! I need to make a change. Edit the ovirt.ks, then run -./creator.py -b iso_from_last_step -c ovirt.ks; this will just update the parts -of the ISO that is needed, which is much faster than regenerating the whole -thing. Now run ./ovirt-flash.sh /dev/sdc modified_iso, to lay down the -ISO on the flash drive. - -Ovirt Usage ------------ -After booting the Ovirt machine with one of these methods, in theory everything -should be setup for a remote install. You'll need to use the modified -ovirt-install.py in the repository; really the only part that is substantially -modified is the section that checks with storage, since virt-install is -assuming everything is locally stored. There are three caveats to using the -modified ovirt-install.py: - -1) You need to copy over the boot.iso to the Ovirt machine. Currently it has -no way to fetch this stuff remotely. In theory we can use PXE for this, -eliminating the need for the CD at all, but I haven't tested it. - -2) In case you are using the boot.iso, you *also* need to have it locally, in -the same location, on the build machine. This is because there is one part -of the install that actually checks for the existence of the boot.iso, but it -operates on the local machine. - -3) You need to know which bridge you want to bind this guest to, so you can -pass it in. They are called "ovirtbr", starting from 0; if you only have -one network card, it makes it easy. - -Assuming you have the above in place, you should be able to do: - -# ./ovirt-install.py --connect qemu+tcp://perf200.perf.redhat.com/system -n f8x86_64 -r 512 -f /dev/disk/by-id/scsi-16465616462656166313a3100000000000000000000000000 --vnc -c /root/boot.iso --bridge ovirtbr0 --accelerate -v - -If you have virt-viewer installed locally, it should automatically pop up and -connect to the console. - -iSCSI disks ------------ -When using iSCSI as the target for disks, you definitely don't want to use -the /dev/sdc device, or whatever. That could change on subsequent reboots -depending on what order devices are scanned in, how many iSCSI servers there -are, etc, etc. What you really want to use is the ID (or WWID, or UUID) of the -device. That's available through /dev/disk/by-id. For example, say I've -mapped 3 LUNs from iSCSI server laforge. If I run "# ls /dev/disk/by-path", I -see the following: - -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-1 -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-1-part1 -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-1-part2 -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-2 -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-2-part1 -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-2-part2 -ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-3 -pci-0000:00:1f.2-scsi-0:0:0:0 -pci-0000:00:1f.2-scsi-0:0:0:0-part1 -pci-0000:00:1f.2-scsi-0:0:0:0-part2 -pci-0000:00:1f.2-scsi-1:0:0:0 - -Note that there are 3 LUNs there. If you know you want to install on LUN 3, -then "# ls -l /dev/disk/by-path/ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-3" and you'll see: - -lrwxrwxrwx 1 root root 9 2007-10-24 19:14 ip-192.168.25.1:3260-iscsi-laforge:storage.virt-lun-3 -> ../../sdd - -Now run: - -# /sbin/scsi_id -g -u -s /block/sdd -16465616462656166313a3300000000000000000000000000 - -Which gives you the ID. Finally, what this means is that for your disk device, -you should use: - -/dev/disk/by-id/scsi-16465616462656166313a3300000000000000000000000000 - -which will persist across reboots, and even changes to the LUN scheme. - -Miscellaneous -------------- -We are only going to support kerberos in Ovirt, so we disable TLS completely -and open up the TCP port. - -TODO ----- -1) List of packages to later remove (but are extremely useful for debugging): - a) less - b) openssh-server - c) openssh-clients - d) net-tools - e) nfs-utils - f) wget -2) Use proper iscsid/iscsi startup scripts instead of my hacked up iscsi_scan.sh diff --git a/ovirt-host-creator/README.pxe b/ovirt-host-creator/README.pxe deleted file mode 100644 index fc8a684..0000000 --- a/ovirt-host-creator/README.pxe +++ /dev/null @@ -1,16 +0,0 @@ -This README describes how to set up the server so that you can PXE boot your -ovirt host. Note that there is not much to do, as the ovirt-setup-pxe script -sets up most of the stuff you need. - -On "server" machine: -1. Follow the instructions in INSTALL, except skip the step where you create - the flash device with ovirt-flash.sh. -2. # yum install tftp-server dhcp tftp dhclient -3. edit /etc/xinetd.d/tftp, change disable to no -4. # service xinetd restart -5. Run the "ovirt-pxe" script, which will create a ./tftpboot - directory and put in it everything needed for Ovirt. If you don't - supply your own base_iso, this script creates one for you. - # ./ovirt-pxe.sh [base_iso] -6. Copy the contents to /tftpboot: - # cp -a --remove-destination tftpboot/* /tftpboot From clalance at redhat.com Fri Aug 22 11:15:17 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 22 Aug 2008 13:15:17 +0200 Subject: [Ovirt-devel] [PATCH]: Rearrange the ovirt-managed-node subdir Message-ID: <48AE9FC5.8000702@redhat.com> All, The attached diffstat shows the rearrangement I want to make to the ovirt-managed-node subdirectory. This makes the layout a little more sane, and is the preparation work for the code that I will add later on for having a WUI appliance manage the hardware it is running on. I didn't include the full diff because it is uninteresting; it is just moving things around and fixing Makefiles and spec files to compensate. I'm mostly looking for confirmation from mcpierce that this is acceptable to him, since it will affect him the most. Signed-off-by: Chris Lalancette -------------- next part -------------- A non-text attachment was scrubbed... Name: ovirt-rearrange-managed-node.patch Type: text/x-patch Size: 3225 bytes Desc: not available URL: From apevec at redhat.com Fri Aug 22 11:50:58 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 22 Aug 2008 13:50:58 +0200 Subject: [Ovirt-devel] [PATCH]: Remove bad documentation files In-Reply-To: <1219403232-17849-1-git-send-email-clalance@redhat.com> References: <1219403232-17849-1-git-send-email-clalance@redhat.com> Message-ID: <48AEA822.1090004@redhat.com> ACK From dpierce at redhat.com Fri Aug 22 12:02:24 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 22 Aug 2008 08:02:24 -0400 Subject: [Ovirt-devel] Re: provisioning broken with rubygem-cobbler-0.0.2-1 and cobbler-1.1.1-2 In-Reply-To: <48AE237C.10209@redhat.com> References: <48AE237C.10209@redhat.com> Message-ID: <20080822120224.GB3884@redhat.com> +++ Perry N. Myers [21/08/08 22:25 -0400]: > It looks like code in network_interface.rb is trying to convert the MAC > (which is a string) into an int for some reason and an exception is being > thrown. The problem, from what I'm seeing, is that taskomatic is trying to set the System.interfaces field as an array of NetworkInterface instances. However, System.interfaces is expected to be a hash where the interface name is the key and the NetworkInterface is the value. This is to match how Cobbler is encodes and expects the values. > Darryl, can you debug this and rebuild the RPM? Make sure you run a full > regression test to make sure there are no problems before putting the RPM > up on ovirt.org. Taking care of that now. I'll have a patch sometime today after testing. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From jeffschroed at gmail.com Fri Aug 22 12:17:50 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Fri, 22 Aug 2008 05:17:50 -0700 Subject: [Ovirt-devel] Migration testing In-Reply-To: <48AE7CAB.1090101@redhat.com> References: <48AE7CAB.1090101@redhat.com> Message-ID: On Fri, Aug 22, 2008 at 1:45 AM, Chris Lalancette wrote: > Hello, > I did some end-to-end migration testing with all of the new stuff today. > Everything seemed to work (at least, the parts that I could test). I was able > to test clicking on an individual VM, and saying "migrate", which worked. I was > also able to test clicking on a host, and saying "Clear Host" (that wording > could be better). I was not able to test "migrate to a particular machine", > because the WUI didn't show any machines in that dialog box. There's a bug in > there somewhere. For the wording of "Clear Host", how about something like, "Mark for Downtime"? Perhaps that isn't much better. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From clalance at redhat.com Fri Aug 22 12:24:22 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 22 Aug 2008 14:24:22 +0200 Subject: [Ovirt-devel] Migration testing In-Reply-To: References: <48AE7CAB.1090101@redhat.com> Message-ID: <48AEAFF6.80804@redhat.com> Jeff Schroeder wrote: > On Fri, Aug 22, 2008 at 1:45 AM, Chris Lalancette wrote: >> Hello, >> I did some end-to-end migration testing with all of the new stuff today. >> Everything seemed to work (at least, the parts that I could test). I was able >> to test clicking on an individual VM, and saying "migrate", which worked. I was >> also able to test clicking on a host, and saying "Clear Host" (that wording >> could be better). I was not able to test "migrate to a particular machine", >> because the WUI didn't show any machines in that dialog box. There's a bug in >> there somewhere. > > For the wording of "Clear Host", how about something like, "Mark for Downtime"? > Perhaps that isn't much better. > Actually, that is far superior (if not perfect). "Clear Host" could mean basically anything; at least "Mark for Downtime" gives a hint of what it does. "Migrate all running VMs off of this host" is what it really means, but that may be a little wordy for a button label. Chris Lalancette From jeffschroed at gmail.com Fri Aug 22 12:58:51 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Fri, 22 Aug 2008 05:58:51 -0700 Subject: [Ovirt-devel] Migration testing In-Reply-To: <48AEAFF6.80804@redhat.com> References: <48AE7CAB.1090101@redhat.com> <48AEAFF6.80804@redhat.com> Message-ID: On Fri, Aug 22, 2008 at 5:24 AM, Chris Lalancette wrote: > Jeff Schroeder wrote: >> On Fri, Aug 22, 2008 at 1:45 AM, Chris Lalancette wrote: >>> Hello, >>> I did some end-to-end migration testing with all of the new stuff today. >>> Everything seemed to work (at least, the parts that I could test). I was able >>> to test clicking on an individual VM, and saying "migrate", which worked. I was >>> also able to test clicking on a host, and saying "Clear Host" (that wording >>> could be better). I was not able to test "migrate to a particular machine", >>> because the WUI didn't show any machines in that dialog box. There's a bug in >>> there somewhere. >> >> For the wording of "Clear Host", how about something like, "Mark for Downtime"? >> Perhaps that isn't much better. >> > > Actually, that is far superior (if not perfect). "Clear Host" could mean > basically anything; at least "Mark for Downtime" gives a hint of what it does. > "Migrate all running VMs off of this host" is what it really means, but that may > be a little wordy for a button label. That was the idea and I agree with you completely. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From sseago at redhat.com Fri Aug 22 13:55:44 2008 From: sseago at redhat.com (Scott Seago) Date: Fri, 22 Aug 2008 09:55:44 -0400 Subject: [Ovirt-devel] Migration testing In-Reply-To: <48AEAFF6.80804@redhat.com> References: <48AE7CAB.1090101@redhat.com> <48AEAFF6.80804@redhat.com> Message-ID: <48AEC560.6030407@redhat.com> Chris Lalancette wrote: > Jeff Schroeder wrote: > >> On Fri, Aug 22, 2008 at 1:45 AM, Chris Lalancette wrote: >> >>> Hello, >>> I did some end-to-end migration testing with all of the new stuff today. >>> Everything seemed to work (at least, the parts that I could test). I was able >>> to test clicking on an individual VM, and saying "migrate", which worked. I was >>> also able to test clicking on a host, and saying "Clear Host" (that wording >>> could be better). I was not able to test "migrate to a particular machine", >>> because the WUI didn't show any machines in that dialog box. There's a bug in >>> there somewhere. >>> >> For the wording of "Clear Host", how about something like, "Mark for Downtime"? >> Perhaps that isn't much better. >> >> > > Actually, that is far superior (if not perfect). "Clear Host" could mean > basically anything; at least "Mark for Downtime" gives a hint of what it does. > "Migrate all running VMs off of this host" is what it really means, but that may > be a little wordy for a button label. > > Chris Lalancette > We should update this in conjunction with the similarly-confusing "disable host" -- Currently, "disable host" leaves existing VMs alone but marks the host as unavailable for new VMs/migrations. "Clear host" starts with a "disable host" operation and _then_ migrates the VMs off. So we need one label that indicates "mark host to prevent starting new VMs" (i.e. disable) and another that says "Mark for downtime and migrate all VMs" Scott From sseago at redhat.com Fri Aug 22 13:57:34 2008 From: sseago at redhat.com (Scott Seago) Date: Fri, 22 Aug 2008 09:57:34 -0400 Subject: [Ovirt-devel] Migration testing In-Reply-To: <48AE7CAB.1090101@redhat.com> References: <48AE7CAB.1090101@redhat.com> Message-ID: <48AEC5CE.1070008@redhat.com> Chris Lalancette wrote: > I was not able to test "migrate to a particular machine", > because the WUI didn't show any machines in that dialog box. There's a bug in > there somewhere. > > Chris Lalancette > I'll bet this is the same bug Jay fixed yesterday which was preventing host lists from popping up on the 'add host' dialog. Scott From jeffschroed at gmail.com Fri Aug 22 13:58:13 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Fri, 22 Aug 2008 06:58:13 -0700 Subject: [Ovirt-devel] Migration testing In-Reply-To: <48AEC560.6030407@redhat.com> References: <48AE7CAB.1090101@redhat.com> <48AEAFF6.80804@redhat.com> <48AEC560.6030407@redhat.com> Message-ID: On Fri, Aug 22, 2008 at 6:55 AM, Scott Seago wrote: > Chris Lalancette wrote: >> >> Jeff Schroeder wrote: >> >>> >>> On Fri, Aug 22, 2008 at 1:45 AM, Chris Lalancette >>> wrote: >>> >>>> >>>> Hello, >>>> I did some end-to-end migration testing with all of the new stuff >>>> today. >>>> Everything seemed to work (at least, the parts that I could test). I >>>> was able >>>> to test clicking on an individual VM, and saying "migrate", which >>>> worked. I was >>>> also able to test clicking on a host, and saying "Clear Host" (that >>>> wording >>>> could be better). I was not able to test "migrate to a particular >>>> machine", >>>> because the WUI didn't show any machines in that dialog box. There's a >>>> bug in >>>> there somewhere. >>>> >>> >>> For the wording of "Clear Host", how about something like, "Mark for >>> Downtime"? >>> Perhaps that isn't much better. >>> >>> >> >> Actually, that is far superior (if not perfect). "Clear Host" could mean >> basically anything; at least "Mark for Downtime" gives a hint of what it >> does. >> "Migrate all running VMs off of this host" is what it really means, but >> that may >> be a little wordy for a button label. >> >> Chris Lalancette >> > > We should update this in conjunction with the similarly-confusing "disable > host" -- Currently, "disable host" leaves existing VMs alone but marks the > host as unavailable for new VMs/migrations. "Clear host" starts with a > "disable host" operation and _then_ migrates the VMs off. > > So we need one label that indicates "mark host to prevent starting new VMs" > (i.e. disable) and another that says "Mark for downtime and migrate all VMs" Ok so you have "Mark for Downtime" and "No new VMs". Something along those lines. -- Jeff Schroeder Don't drink and derive, alcohol and analysis don't mix. http://www.digitalprognosis.com From pmyers at redhat.com Fri Aug 22 14:02:09 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Fri, 22 Aug 2008 10:02:09 -0400 Subject: [Ovirt-devel] Re: provisioning broken with rubygem-cobbler-0.0.2-1 and cobbler-1.1.1-2 In-Reply-To: <20080822120224.GB3884@redhat.com> References: <48AE237C.10209@redhat.com> <20080822120224.GB3884@redhat.com> Message-ID: <48AEC6E1.2060903@redhat.com> Darryl L. Pierce wrote: > +++ Perry N. Myers [21/08/08 22:25 -0400]: >> It looks like code in network_interface.rb is trying to convert the >> MAC (which is a string) into an int for some reason and an exception >> is being thrown. > > The problem, from what I'm seeing, is that taskomatic is trying to set the > System.interfaces field as an array of NetworkInterface instances. However, > System.interfaces is expected to be a hash where the interface name is the > key and the NetworkInterface is the value. This is to match how Cobbler is > encodes and expects the values. > >> Darryl, can you debug this and rebuild the RPM? Make sure you run a >> full regression test to make sure there are no problems before putting >> the RPM up on ovirt.org. > > Taking care of that now. I'll have a patch sometime today after testing. > Ok. If this can get tested and verified this morning, we can add the new rubygem-cobbler package back into the ovirt.org repo. Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From bkearney at redhat.com Fri Aug 22 14:47:33 2008 From: bkearney at redhat.com (bkearney at redhat.com) Date: Fri, 22 Aug 2008 10:47:33 -0400 Subject: [Ovirt-devel] Appliance Building using thincrust Message-ID: <1219416455-18149-1-git-send-email-bkearney@redhat.com> Two patches. The first pulls in the appliance recipe from thincrust. The second modifies the kickstart file to use hte new rpms from the first. Note that most "flat files" from the kickstart files are now acutal files in the first patch. From bkearney at redhat.com Fri Aug 22 14:47:35 2008 From: bkearney at redhat.com (bkearney at redhat.com) Date: Fri, 22 Aug 2008 10:47:35 -0400 Subject: [Ovirt-devel] [PATCH] Modify the appliance creation to use the new package recipe In-Reply-To: <1219416455-18149-2-git-send-email-bkearney@redhat.com> References: <1219416455-18149-1-git-send-email-bkearney@redhat.com> <1219416455-18149-2-git-send-email-bkearney@redhat.com> Message-ID: <1219416455-18149-3-git-send-email-bkearney@redhat.com> From: Bryan Kearney --- common/repos.ks.in | 1 + wui-appliance/ovirt-splash.xpm.gz | Bin 0 -> 51388 bytes wui-appliance/wui-devel.ks | 317 +++++-------------------------------- 3 files changed, 40 insertions(+), 278 deletions(-) create mode 100644 wui-appliance/ovirt-splash.xpm.gz diff --git a/common/repos.ks.in b/common/repos.ks.in index ba5ee20..8605d6e 100644 --- a/common/repos.ks.in +++ b/common/repos.ks.in @@ -1,3 +1,4 @@ repo --name=f9 --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=$basearch repo --name=f9-updates --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f9&arch=$basearch repo --name=ovirt-org --baseurl=http://ovirt.org/repos/ovirt/9/$basearch +repo --name=thincrust --baseurl=http://www.thincrust.net/repo/noarch diff --git a/wui-appliance/ovirt-splash.xpm.gz b/wui-appliance/ovirt-splash.xpm.gz new file mode 100644 index 0000000000000000000000000000000000000000..8cd25b89b37a8660067bdc5de4f84614d40880ec GIT binary patch literal 51388 zcmV)ZK&!tWiwFoYW0Ob#18;U|a&#?oaBN|7XfAkgZ2;Z4{FyT6GU*`YrU;UT=`k(&m|N5W*@}K{g|MY+SW&X?m_W%B0 z|BwIrKmV8i{r~Zw{>%UJKmULJ_y6br_5b#l^)LV5fAz2b@$dikFMt2<{`N0_{fEE& z^^{<>%aN at IDUToH~;ka|NgK4`1x`A{P_2O{fB at 2>%af} z`1t(z4}bd)fA?C0`cMDx&wu at opC8xbU;pu+{`xGSuu zpNngZ*U#Va`2FfN_|I?GI2`XM*LTOD1FfL#+sDU82H^os{vV!0?m%3DTlvZ%7+>o5 zSC~_*;ZMFizFC&HgR+Y at fGn=1{Mxq*LD&G}9Irdl;xGNIv;1oVbNg$6Yo2xp&hv3! zA#eZvMBpciCi~@2jW&Z?xe^FJ|L=Ab>VNSAF&M!5M%v4t3l`JMfxs>Z-~0;@?&wma zb$-oJhOsK^h9Of|U%;v0NN2=X6$ z)%db1FTF38H?SEUEo{O%YO$OQok?DX>|0zZhp%R2mF&^tgE1^Gkx5Ty4Z6ext) z0^LF?EcEwYwBEjh2lHPGi)-*9;CG-cUQmP^xLdRYgAwNyTo?qXm6%tvGu*k5h?@&N zIuDLoi-iZZ8EnE(;O8)uG936DhZ>1LtQPAX+CA3V)Fc`Sc9BT*f%ZO#y7D0q_Yr{m zXhTmyxbT)=u`6@{_84$wAzrO2XBY|+yDgv=)JJ>F_Q{lsDNCXH32H6P!yCD3owfMvSSbhVF at kcCHD*0Q5K9*$t6y at 1viGcIX zzl^*^J}vr=t`Lk!YCOAGhM_=I5=$jsycR1UXK>z%+8YQgh1dH(r9TBBCCRB60s5$f zqa;s-07->`z9}_*@zJ>NP$57dO7Pg|1&8f>^Pv90miZtU3o{aGfSi75HJE_w{sakv z2G2Oq_;1Pb*wimd^kTI-qYq7ALZXNepie-ztQm!-K??SQ2Io1xf>%=kabhc2e%Kb4 zm)QXr9pP~g?2!wjO&q4ALndhgM}eXdV#R_2$nVXARKRijk+{(6?}5wPCZVD*-V}cX zVuSpeP+WjO at Sy6Dg>Tv^UJxO=KjA)9Uwd!W;dzP1FZG0>M4}Qa4R$O-U=gsZ8bN`d zlAjf16UWm*f`j&hN_ at u)U?`ktcMSx7M;;{o5r8I6Xaq*{_pPf*#1XW+cLJmPG=jLk zVH4n!+ADSU&8iFlp5t##cdKyYQWxS at Q@Dd}v8K at AS37}g<4#DBFGY+rDlxc^wU`Jr zzMBT=0f(u`kewO_Da!y~4Z}(ae`gG4DH3Hk%i~&@q>|iZMKpg^@Arl|BU;#`aiaz& zA;MSmgf4!C|7tzF3IkFS+c0Di0;E at 6p>Vc8A;g(C`o$>=N;O7NW<7Q$lva>djF{7f z3xC7gV?sOl+bim?*wKR!Vk*UeU$Y(j4m?=*e^QAFL}?6 at 48AJK@AL_%BoTUNs3N%o zMiV4%Qi~p`k=QE1@@e&-brm at NR&AHhnwuFgga|*+0zUVpF&^omPksQu+8Z2|3J*H8 zG8|m^dG|?o2T(`U-zgYxdJMTPCmy69*yF+NyVYVtVR=oyU?A_C at m>pobf$Cn_m&bY zo^%B`lP9ezjH2u4vq{ON9(M4N7)_6u_-OQlYy??^(GXIBh%&BW2qy;aG+#S&Vi2Km z9_*M$LEU36-wgPNcsbBn7*MzmW0CAf_fYsfFjzn*E=-<|d;}|o5z{Tc-U{+Xp?DN# z)?N9~Da17uF08 at Xoqn4bj<>1`6Mno6)Ge*D+cO5|{9#sOX3oEY#Vm at 85C#$heseQN|_iN+&9u_%0C{ zps+=RNs)a13^FD at pg6Gm+fgX!JFtfrg_w$;v7#9Y>EkehX%bIEAf5o zs-)!%UK at jwQGn!a8q)gxty;WdK(cBRsB)xDfl-8YNFu`#jOb=f&9EXjd%+S_FjYKR zISvj~MZ(5#$KL$NF7<>$e8}Tn5KkQ%eAvK5aL5w<9c!^r-#5`B at MWqW0Pku>EJln9 z5OD;Fz>iB7G%6GzY7qwD8Zns$Ia(by7}B7w?ZCyLrd^=AF{{qx)^!!o^}N9emY{NB z&y``}En1`eO~3Vin&El`@E#K^nNTLvVFx}`i42q`f;FqdhQRH3`C~jtUz*ieK8gz? z^I`L38rU;S(5vwKgPP={(jqq+L1_YE2Ff%f^Iso$x%Ngl3k0}8jBrHKrNLR=XDP-d zo3L6d800v0`$55<3`wd&{E5HcIB&oC;7=yF0;RGTqXapo(3#U18mM7m`lK^j3 z7nr<3ufbqKl;|D{QW#f1?;yag9eBcpMuA$3mGk;lI1S~>kBE_6gg?Qi4sp)LKGQoc`72!05IQ$Y1MkVGbqyUbc;rj;uRFH5g7sA+#>Y%`-Cc?smticjs zMemmfCB$Niu7U?=p}%@ef_ at NcID~P~-Ly-QswLcsFyu%Ki(L*sMo}W7PFj6nNa8=t zgYLMv6_Yh_!QTx<+=%L{^YUTSklS92yjIS%UotQ5RbwT=@(rLS&a}|sEJUaiVwsGH zD=iSDJ5$f+7*K^P6UaMX9}#y(J?W{UL9f6EC5{-|xkz!01{LpUj42GW9`cN8lTLx at +8-{rMXFujhghm`p-z{z!Xke8f#b75uRrU;XJKS!i-J({zxk{I32FwC<{&0#bc$0 zcTOD89hOgzkwl1b$uOWXysE*83x5X&KL9`hB615KToj=sv60;g8%7;Y1z2fNz3H!t z5^V;>hLOJT?QD1s#8s-WhZFcop$9>XYI}T6fx$ePOa9<$AdCsgG9CANv?p9hgysYO2DRU~PRMUbp7grPb_n`HBCtxwBx{GlzVIriV3GvW5qi>?+|8{{5HmWdz1j?l79kNbFf;Yj`#0P{#1qV) zIdWQuLbf{mm`shRLK%Eitmw-r>eDI7Z~fr!os;+mv1(y_P;2JW610z{Ajj at -lEmP_ zuax3yN at o;kA|i*U z&AbeR at EqM?$YE&*4?l1Y(5WnbSRoz~2nPz{3m|S`4Gv%L2VIUxTtP;6AI^U&h|e+# z^6 at R^OqL`vFaAAKIvJF3N}`hFMjc)Se=Nvr)+WugjJAU_5Qxf*uNpQ@)ewprNjPk$ zR_#Mc35qRf+ni-+r_CSPa1cgxQ|1zUO*sCpTFiNHbcSgtVgRhInI;hBB#)2SqpQ}H zKE7OleT7LP`{o5+s$tt1$+CEjlANr;7W`~BqCD85J=ihSC3==ZV9~+}O%y(68Zk!| z{`_JmyU(xo8wcPHr_vBULL0Z`a14wK2i{P`TX5m;&?F*2NPU4jo0D|oS>F_QH4K#k zKVokrphy_rM4~KoE>@1;hclz9_<-XbAg-w~1QRkoIBR|o0NiCt_5n3I$Uab7!&HGC zdn$-EH|-#tl?!orLXjbetwM|h6o)x_!@;`2mMyar;X?{?2ZS5j!7vDa->j~H?n7>J zQ2*PlZqUZVAJ!SZqZrc}6gvExB0-88#!SPDxpD;)>XJ3MEX12QgH>=)`#>qdo=dQ- zj8Yev*vN->(jF(Ry^mg6br!{WFP{`Qzs9hP%Gh`(H#H at qLc;P07} zcM~!MstT)^tCvMi+5~B1w*$2boZP=|`}kY!esE#iVY)3lZ2a#ZV+) z;EM(jHwO*Q>Q!4U zCJh=8XT3{GxJKVfciK!D#8>k!r!gUB#0J2izRBPxGhwZ~CPt9oAFnd|YRYVpn)xyh zYF4X?PFp(|Huc>JiB5%mHuBLz;1`$>-e6i|s`p2(E|6YRl&tdM^s4G`_GQ=-3INyW z1Sf-E+?|ezL4ZMvEXwK+9{7tMPW{-G{Yb8Yy3nV7Fj$eBGk1dI;?@omgR1*GZNU&F zUW{izPe3>^MUB*!4 at dGMU0E#|k2dj9q%bk)6&cbvT*LkU&r}AJ%UBFCZG8eOv1`t1VNN4krRE)oLvFdUpm!`wln{*x*6iK|SB1obij44S* zA=oh1ncCf;Nn$5J(IGOZi_?FbHCPJqEco*ReAs~Vn6IeAjda40!Iw307lwf1_H|A2 zEjNCufvHL=Rb1il?f2{jy)SeGzO@!#hh>xoWkw7dl<@76b*>ip27&um_P^4P9c`8&G(E`+d^((6M+8!1O7vv&${$u!luCOJPk&j8eQd z_t{fJNEw2Jbp9a>MHed(g8Tf8BUIj=B!NN08L>2iiT?%)WXj`dH14BOI5-!c15oa^ zuVD~4ETGeTOlChr;WYr?4!w_hcjTktO5c?wJ{jRcyADQf at F2H}D-v9EuQQ!7ShnXf z0YVq2;YfmGP at qyH{b^Zg4^dLpVhd>n3Xes)XawhDl`b^!6p3j)eVS#MOsX9n)HIfC)a&?-8?^SyQ;FSUX- z^(`pIb#z}`>9x&+hPhQ~@DpbeoG$&}amTn;8k39-EKa&sj9NUEk%*pdGzj+0nUV4f z0rJJ90vf{G{$REQYb9J;)WL(@xJ<^MCuRhO^k!~eDutB*Ax6?3-30^2OPdDQbzK~Y zJ%3PE>E>ioCj^QV4j8VQQ;5+BI>FOZJ$TxnDPGvq0mDy2W)2Yy21IDpB?-O|F4T{8 za(Vy-ZrT+7hQj+i`0bmcE^I>MK(z at k;P)0T4D at wV(`pdjgP*m5noVFV;dDONu|&p3VNi19BbtT+R~%WNFa-;izjm7e%9rrtzhNA zcwn_MWA4aeg7GW5_`}KY<6EN`H_`VkBk%1WiN0<}_$3x(K!Ny!3^rM^(5gN|pY(w` z6_I=R(J>xw65&iDWOzEQkF1&h+ at apVw-)5B%OP16J2 at 694o#q=)Vh z3m!3>Lkrli$3cT&!7YGzPm}0AJAo*HRiZ5=WG*^F|HMgMs>E+{;oWUcgCf~a2E}*C z&$9{(vA0C|r8Nx-T-G`H(=r-yFR=VecBe^$>%6l50)?^7K3QC=0lyO4S5hoh7>7)E zP&h=BI39Z=M`~eWsYSs}p7bb~?6aWr;H_Hxp_udG2)!Ay9}`;_*E`c-d1(rgFKA*J z5Q{OWIR!1vmn^8Fq%fg|w#a)Wsu162ELuIb)uqauHenp-<6Xq~yK${D5TP$9gAth* zO93|Qu?8QvDMNky(_>7Kyojx49u#?eSCbHqsrVuqW7bSueIz!wM^bj*UFqBCdm};Z zM{M!k)kp`3wm-7Ui)IvHQy(w$(x5?KpUXU`x&wqv{65q`9w2K0Zi-Nfv+eCNO~6gI{Bl&Vn*lkkuZ% zLxPI{Fg}U6Z45GMo7Wjz(Armg*iq|5X!Ll?3}kGjRSxf z)$L!;j}{8U5F`#okGKdn0QQRU#tc8-Z}H%V-2#2zyaB9w!!`)thB|_erMaPtzd9#Y zYg%iujOlJ6F#17@@S7|V1g>orRldiA20b>1EXYZYT(*Mmbn`au`!EE4#hcdL^c$sj z!8=>P3joIQ(ke7qSK^{iq#^7&gOV9k7+PC6^L at wWB#}Y9GB2YUnFatoI9>ecBehVr zi#@dp%aBhe-nB8oH!`t5fc$7!-%M+HKX}WV$#zWFpoSc2_Ju7NT3EHIiG7LWCJ<#z z=iZ>((sxI6ORB)R8Y})}Qs-+Uyq(z+8Vy!3Y+lttmnUU-KUpSPLCeXM&_Y`eKW52I zQ=OzL%<1uE5f=JWUIYs7_=7@#o)P?(iZE^JD<*u)8%l7dK~6~C_sX#?(eYztQZI=U zy8x`ZKnJ4&Fx~7plnoNYqZooz#c3>(8bPH%w1vQ6!=4_Gufv|H%;_m9Vi(0I!aLTc zhX7G{Po^U>8Ob=5lLk?JZD4Zh4}P#0Y-CmqagICBEVL0wLF}C&Z9%#D5(_HmJgSO0 zvCv=>g{vb`{pl=-Nsn^`w<lUJxwg>>4qm54EPm2vgD6; zuFTRG!wnz??rKG%Rd|W0OcmZwy6)e#4v ze^?mRN at L=4QKB at MT|pHmkR^?cb`v9-l1Yl#OK>NykV#PgaK&(#l&I5 at QF=j)jpf3A zaj+c@;FptEhfz={y5u^kV4*?1J+H-eA@=N6;k%vu0KRW$P<)#piGDC at Z(~$<6NWY( z&U7f#I;X(X=rkBmyF%S$M?szo8#)In0 at u_DQVvT$*iFi0SEkxw0tV+vO0Fr9W2=r) z8Fqwt^2n|~T%W`q$$nQ(R-hKC#&c~TVwp3yg4>OB`6&WU0=lEafOR7{I~M5=%Ips1SDCOzBE3NYU_UP?M98YlxI%@( z(k~XLdQi at j#dfu806z}tPErbXxCh{;9pRNGNqd|YmI)U8;H>x(ep!@=8WDf+tw+qf zZ|ahiRA_pF*EJaY=e~4-oG>A;odD0w=|*mUR#xO&(IcfE$1L=x zfrT*!B?k&WC&+YUhU00RP$MHzDDs1XSWEEOL7{ApC=n8V=b9uInX)JCctj~ypxwY> zs=}l}b!YC2Vm;;!P_#*%&q}USgAPNXJ;Y{c&vYgvv%XZNI7PMGA3PR`#gIWScs`&j z6&dw-mYz_iBgcTa;Zqkrn2&y#LKJ%$u4zzw znK3tn(=^P}ND7p#PN6q6=z0I zRq*_ at wSr>`ZD~%%4pYG01HB+DnPX7w&R_trHfK&)(3P+ogQX%jG5En(g`s3+U`7WH zl at l)r(C5QV7BqwK5{Ib}i0Q&<>L2FAeM zz0^!D!ZxI9_%UBu=Cs`ELMDVIcx_JSz*Z@~nF1+`8?>jAxNx4KQqu`x%ygWYvN)WL z>7E`bub)mKY-+Uz6 at 7dLQ^DLP`7tR|(V#bh$(0!s{u(@34)^eae8PhtQITDe*ss9v z+Z78Y60DoScYeD!95G{>Oh|3qvSPYGf at fPmrLs>9m-trFM5uUpd= zqpgoTre~Ff3`*VXER(gPW@(C-K#dHyck at kV##4P68J6 zQk-3B<-Uu^*4thxbNa*hkN5ZRXPl_*pa_y{UvQE2B at x9d#>1hAF`qjE3!IOHELqH# zQEPCg!SH2dI2RWg(T>%cnlz{=#QGc|D7GNuTGPJFZhat*n;vyMTx$_g#aE4>g%zR_ zD+~(-_U6Un!Z-*uJXk)y#9nQl#c7crw zyod@>8e|7pc+ko(co1nBd;Un;T#{LF4Zg>$uHgRN(FaOwe-h+9${LCDAgw@{g$&P7 zm)tXFC`*PR(_jddc0ZVABhd-MkI`zZ$g5EJ&UYG^sk8eY-oF=C$XqCkbhb`+XOC$W zZ>H)6r96WL6AA@{aR7RJ7YV`D2E`=6<9Bk0T28&}wSp7$$28Yn;7*A1P{m}1gCq9` za)t<*;xUNjC5~4h=MH9<>gstI4Qid>0p;ey{ z+l=H3W%zA+#5IU|G|Q6TnpJq|5MUz0Lz2Lk$&gmUL~$gbI@lI^ldyE*JclA+xT-IhwF#W+g1;#< zVK2CP at Lr7Vu+GlAF)b6*oIYB at N$JmPGA|V4j7QDxTVhM6Bj(H0*)1p$VMOl$rR0W8 zIc33CDfaOQS at 0`~X|KXYg?cM>_!NcO6zaU#oN493r81`%L*gnZqzIOvf|OwZFO%TJ z4E5Bl!|(+s3S!LX#yD8vA!`Tivq6fi0dD>ZU8qiZCmMG$LK*eJhy0p5%7xlFmGmz?b-I9X&&@ofG0gU$E-j4%0cPb_&1q-Bo0qnOCPubqZsr3o6##9M({_pP zh+ at a0iV?gx3*I-XTPG?=8`#mq`HQr_RCj?3_eZokw~(kRL`TL_b5|um7&DU%izj4d zBskrKVOb-V0z5 at -`heav#M#p+ECYG$#xV%eHgpHm^qZ}6eKgcgZc<3Z)WZ#4)p zAj#Y8_e!wRUqCT?({EKJw`ed$WzvWadiX{m&XbW`g`r7&i!+08VQLFTj8$ZsGAk;u z(I9$39}@e~3TruAYdZ4#$AB1$c+xRl3d{%x9*LRHFCO6zh9jt6wHPvkzBNF&DGUif z3CaL^sK`|GL+ zt^<&JODM{yz~I6m at gPl^w7q0j408*&v~eQ9RE}efPQ96%z2JggFw%lH7S>vPg2UBe zsBqM_kmF)GSfN#kBf1(>5e`F=HK_UNY0i%(p3w<cJMkntr3Mm;J0~V!D_M3JLi#a+!W)hZy1}AGG%h&tw7328yR3;OlxH2kB6nG^$afqmumwwfMu at LE$|BQh4y2SL9pe*i}jT z`6Bx at Sg`g5FXkfcR%gNj{r^QNn z)3HRS1PC6)yXpv9ebLYxhT;SCanlm47p zgO6F at H)=P?;9w;|hNCMC;+qoojvA~**oMR>$w7&z`g`gGr6bhL0#WCtbaJOvh}cU9 zbYc&hEUvu4CKgZO*MZ|31sLC*s$v2#7h>6BGOxNPN^)c?M2Wc5oRO$IGf6ARmE!nX zOL2x_e8!-7jMShRoPqC7TiDngDLo)Zb5?;bmzWHG>JF}gLegNn4_qUz6e|yfMWIhfG7b8uqyt2W$1D^h z%7Rvkn?qtQwR<(b7!^-57wIM>7LM`oqzov<_+Y^)N^^l7#j!~f$Kp8~)+ihlhE|6m zQ_`H|gJ`U9`0=$CFcYG{4mONRk*_&3ioKQ|K>@xTk|g>QQ7oo(@&^W&6G%K*n zfUQf8SD+=>qd=*}>0lYXMM>7(r4Yjb(?Q!n_hcS4WIEV;2>xt^ZfF)`mf_?N5`G^_ zb85(Z`w%^HZOpt;gQfke{lPD8knqTT;xK{nS`Rx9t|m-e7!!pp0-V>#ss;c|8p zT_Vp4M at eky0W%josEO4{ed%e6qC4(P=>6cqf%Ai02-lCbm;@+tq#s>P1G)i74E)u7 zAP_hR20btxRoM{u!TqIc7JNgF5QK44JA)qvyfYBiuC#)V9pNJHSN`~&ZChurSrTNOlq-XT#=Mku#lq(VvWgU;8nG^jYZ-w zCUcz7X&Dx8y3K>?2&OSfLg6iWA|%*ApO)qO?)1`V1;v_{j&Qji6o(g+3$c6DQjT{V zYBoWeLDj|PNV6VKGOSC{X_?PSOIX{2MuxIL;s6}MgcBP=&rApIv;}-rd6 at wjnsXwg z4QQd&JRmXfwfezkT0xANgt#68-I4id=@F~KVN`$RLN6%IU?9-w at VpAAyr8s!BtYp0 zmssNz{P{GPt_;wIn1Z&1`^h z@!)hQqq%-H)*39%G;ydcp at z1M2b0oOONL(-bNX5l6NVq`1zR+z5$aoH7&c5sVo+c; zDLF%8$|#sG!@?Z+TMEY^LpT$L_yWCQ7&0uv>Is(2;IxVvs9m8wjOl!dNw(T0FepX1 z%E%x*IE-V-${;vs8zj~do^#+ZI0Ygqt)fI*K$RpZN1ZCN*wRf!d;^AgPS@%N at 43@e zFSxwzXA$7_o50ixw#;dxz{Zrs%dn}Cz}(sh?VD8{jHoQgBFx#`>ESYESQnf(Awezq z!TKryoCX$T$?vlUTX%SFQhPcfYBP5^5B8IS>@Wlmx+SBlBOgM5XC=4-;9C$_#}y9z zeKn=f0%Muik@(&>U|0|7tOxX<%<(d%mH8=B-uxM%|G|AVj zL3V;4G^CRlv46OOzoq`+ at 1i_-qKxwji2yLQgrvcgT$RNU*=U!c2ztf99qMwSKwaTP zfO@(p#4#74D3$?nn$S&Sc>;`nH!8%y2_N^UmJTQy4wycN=_?xg5yOixY^r*Ke-cMF!|Ew zJZL{0kbJljx#|hCGb7bmsV_iyRgxEdQ%^ciO5Q?-XGw(sW3 at Zt-5|Z0S#*b*GBdxU zMF=@U?6tD{T7#?5ptyrAHr&Z?jx#+-e+M8R^H417-!w9Fs}h!u9XgK<=CbTMxa-fT zJ&k~Zn1STMp at JVVXv<8FzZ#5pE{*O(9xD&h5By?E;axGH)Z>Zq>Q1w#;9Jw9%a$(%n9b{x4uQcl#8W-tJF zmDx(IAoxx|Y2S6d8R}wh1{Ha at zmeVt3|reHZ{Y81P&7@(;$a)9R$94uE&>7^;q;>5NMHAH)zfRgRo;* zki7&;6<)OyTojM%amW$!{8Ed7L((73nJJfI8IzRc^hFhBPq^n|_P$UYnj}aj?E}X+ zj2|`k_+$f-QLFHc0STrf(f~UB6`!VAiF-U~H0a}!n-M}a2CFKp==*YFP}vapt31NX z*eYqTDT=in>_`&&fjSQ%4O&hbz)R~R?#eJS)4j-odvsN^TM2Vi#8a^ZV>TkM(hHWy zGFkl%81}LT=QeuBd zYD87B36p*+mjzTmeO2>ZrTG9R0sVDNu9 at dPC_6Z7g>jv9%(dpoe>$D3S-h!<0m`GLe_rKkhf>p~c3D z)V~5kA`vwBU~a36MXquprp163i|{ZQ4f<8_px+2F7WiFhW7(Q}8vq4=9#puiMTGS# zms$tK*8ps7vzs%W#7K8sHwz&a=pFDAM>^bSHLOv9SH)c(kDR9lnNk?>xzM9Egt14$ z$sCr74S{A5>R0CtVQAmM`@t%>ssZX6i+0X}w0$Z=uC=gFM?TO73Met9Q|R5T!{osV ze#M#LbvwVGK!|#5eAvEbL0_UlccrZtTt$7PM)1fe%qJuW_^BceEFM{z?mn at -J;a`| zO6>SM8;?kQA55tBU`rNvXfSxtvx0S8<|Bs&+laKVmcUPE!yk6{rOUm+ckH$Y{oOKU z#Ec1rY>uEggIzLo9<;hEBf0AHw2CxwAQm)M+0_;b1B`OA%p_TvYDPEjpxe)xrZmQ> z(}@KsKQy~~0!En?7g8kUd3yi4A7lyEqDz}5t}Y?M6ig|FoSy!ABe-ZeRyne>o-pDK zbD9OYF&fb+$=YN>m%%}Zlp%DiMUDjUN>8XKDo7W2QHLWhT at D!BPgvlYmt!5*YGns? zR$T4S2?`aQO&C%M3l5o=35N!N?4a-2=m$R}oKVl`E=7qVFm;A`KKJp(YPDrSvLe!9qbc3 at 6ShXJvWFEs2qro&TKBYkq%J`ga949E^{p0Z| zdccWq8kwn<*;$NFhr at W=Dio3iU6XXZ!KlSy2R7VknU-9l(d-xt&uB*&^!KH1kcY17 z-SQS+=GG#ohWCX8YhApoYaDK5$>{(gORfM at i7$XhLadrW#8#&eB)z1JVlm|Kv`9#e=c0lzeIz#21+v84_RZaw-&W zppOg)8q|#7jijL9Fzpgog;-=r`{?r$TxEE5ra_(ciJ-0$BNE%Uq)d|tAAvu#hR2US zh>q`-B{-rO3k_C+_Y?k2Ol#WOw7j9`*e43tq0*BDW4IHK^-3b)g1!_??_N!d! zFBD@)VbLLM3*Xn37~>IEUt20xIqX;U1c=!-m~1$VTjpeH;2r2&C#}Tu1qMlfNNL3* z>4NsA(GHtF{%tts#CwBy0B~O=b`XO0NP}mk*z@`Wll&Wim?p*jIxIZ+;rZ1hg|9n7 z%gSU+`a@|@#PJROlNM0zX%2}~89twqz!Y4(9M|qlA;SZ4&Veg;IU!HTfHY`kXT-V{ zw6LJ16q-xzlG8de)CG*(BH6`pJcwx+B}hHkJB>jzWX6gHPo?md at K;4I-gsLzC_mV>-y;gu03bJ+);>Tn&lV{WW{R z6KlG%X4)qz at Wlpo2(A`Ye0bN7svk5FoM{lF;alCH!ccrbYYz>15}Fw at 07VyzEUv2Y zo#^1(6bZoShr88{;RPzIr4;tDM37qDZ0Ti%xTSTk^3k at Mg?K+31y*kP=@8=mx_$BmlvJ zjLLAExJ`noCmd at yj`kvLU^OclkK~}hF*`Eh)fDux8aGh5r4I+#toy@!#C at 4I6rZ1! zcuRYA=1bp1WjQC28R+FoQ(yw1k;Ndj0T4>&ey71Lsch@>tQzZ0Y$ybU7$u- zTL|2!i%k at _bjdXpz6Ff2zG-uj`0gG87k{ZIbc@`a*W%_egOv(t3`nvf4)`7KbOwPs z3k`n0PIP6C35hL{gd)xZk~t$#IM1|kdnR0^sL>6MW4M4qh$4cAjudV(qUBY$I=p`( zM%WELoActz9=roTy?Hl=#R9+E6qsWQMtUMhF4aijujx^%D|lEojzeHiit?f>B!a_^ z4%XcCDp}RJ9P7dC;Q4jvk|KonMKvG35xBN3;slmAMAtl5bpuNL=B(4-5bb53Y#_)s1eF zUjvDl53Bwq4{By#3$mVasq0IU9c*5YS(#86 at xUKLTgQJ0-*T*U1to^k;xj^V=~v)^x{G|S7i5R z0)e(fjkPXjjDqdiP> zI(4K+Bf|cWgr*?>gc0dSw;jT#ycZC5dGMYZHYS8GO+V225L|d0ic5vV=mnD&=_1j^ zNTVH;0SOYT%PLProT-wLlv#m0j7L`1;@OahM-)eF)uwb_gdK5)y~XT2c6CxZB7iB4en3 at U^!4j%Nt%ry?9-%}x6jp^QR zLhq&|W at y?=u{DC1Qlxp#pqF7as{?^^%1!S$oaxJORHDwH)?)DotK(LA*5y>X!p?{; zZF0(ZPNFhpz4}`FmPz>seD at t&UzGM$lqf{jsj?DHtK$vtfO7X2` z at U3z&sSX}b2Na7e3Qx9Ln_f3SfwdZ=;Os>FmIBKwI3s{u*76mv)d z9<;i9R}g1GqRn7qKzHkWt;KFNM9osnh}NAwI*dsg at bmGAaN+{wrD at 5mOo%2>9KlKp z>yUUhNemKf3zDe7b7}__i9FQ<=zC0q;&6DxQ%I!rf(Du3KlfzPLNN=n2xAmsr^3bp ziRh6nc+d|zk`c-6$jnlY2r|t z!T8|apyAM%aINuo+)px~>uoaejA_n{tprblG=`}R54;;jo~JbkAvW|#6Mqcr>ld43 z__?)drc48X2o3J=dmUhWQWs|wp1XrJ2f7j_69x_P^^yd2nKN(*0%UBpPlAaGbwunP zq3Mvd!-n$S*jchMaeyzBan%yim%ctu0ppMeOYeZ&UBR+YSBVj)K~cyfM#hyTEn|}Y z?#LirAz9Fz!6Eg77c-J4$918LKlJ4m59<5dk(k_>-%o=QR^V{tYImf8yoHB)a5PdW z6Rp8;q%jnZeY$m4T+X>Df8HE=IOk+!GJ<8!6Ms5RFajTrM5T0sSq{hD(FZZ^EsSngS at kKZ2 zgSyudAk at qP2=IJRYze_=1gq^4Hi2I$!Z|)IF7<+!&>pnc3$E79ptGQ2P}Ipv zFK{L8RdbPPXZVA(q;G98X)P8SZ1jS^6NOMC>O}_)x<7qNge~w%R~QO;aTIDc2I+l* zjvfto(8zE`XgV7*JWxiwneHiDpF9_bdWbhT#`B7$`vVJ>_)HlLk7<**4zBjcb2Sm# z3Q5D2Hg~!qkP+HCl*s27ul>)wnW`Ljp{L%o<@hTEeq2^zg8J9jTNHTckMiM#Q60S*LEpJY$kcuSueJpf0^e6;xY)gTgE5|4 zv>Cjxr`rqd2Ej#q8Kc2~B8DT;8_rUDX~UHE(Cq1W6chBws>$rE#SM!g0V*;!dPCc1Xr54QY4TgE0H%l5dw;c3ou(;EeKU0A(YVpdA_V8n(3M(NNJv>7ai!TE&Mm}{Q zc!*z8AGFD6=l$XZN6VD=*rU_m>L>Mr7jlGU*{m4VB13{-jts^c9e;Ni?nr!b$K8O} z1ECM*gb`a*_ at z?@^Ztmu0*j(hE>(%IaRv at s^O0b|491!H)q at P@d0xSOxbw_v5M$EB zbL6HcY!s^~NmnEo9-f%7Vr(6uS~KhcCmf}q4s|NPr!Z`Fgj$8=re_HNjzDq0k_MlV zli8uSVN$X^nOVr?xW=J$f}Iu4OoQBNx`98L(&fms42mxoiC6Jsln+f0>!_qvg3~-W zcu;k)*58$d__OI~&I!&W)(ls8_KE`NRJ3;9JO(Dl? zo{R_wZpwr~f(4iE3l4(76L;`gv~kRfqeX1g;u9FYNr0|TTJ`vAtsslBua2al-T5W` zpbSW|A($P(W><);Adj79aDn`XcjC`>iAyuMe~`#vb*)pD#ryB)!A8P>ow!zL;Pgrc zOwC|UuAaJs6vAQ!QjWMQ*sjP=0pV6=Bt;az1cNs-g9614_HqTj2BL%%)TLgS71XiF zjeTNI$wYD*GzeF)8Qh{NBXNbW1Fa2IF}#K#uH>+U1tCZ}l2|p!5gUvQD=KnP$>Uh8 z={+K|_kPFz()2I{4v7Nm3CUG47FFDA2OWi7QT&v+jEG|HljvGqxBi+8=?LNv?*nfU zp|pWZ{=otXjG2pGu(qYsI(s&!SD>-i4q`460Q9&F09dD01Amaj6%Eb~z+8!^n2^XO z=VZrDONx3>h4MJIi5-h-OM3~<1zBwv;7BRFi0Qp~7K>@U3njinzn9^}=5qHYnB z5Z^To;<_b at buQ8%!$99Hd)l380b-t&Xk!TEu?t+pADl^sb&?*@u2n!*eVvxrv z2oB>-%^B?nMF39%K9U~~Kz|^w1y$0`%jhW{d@~xX*sy!~63JeNOGCKxU|{e--i=|g zI)oxmk~64##b3N^NU~r^B1Cv)3-^*X39mVdNUJ=B{?4=8R$R{=t5Vh!sLNrYUFM=UWY`|S49 zn~qpJC_YR%;bTYQ>+>Uc at OE}u%xTN<=MmlSV8J{shBp%>_`;%I{dKkat8%2_3@$l1 zV;-SRN>VFT zWZfts^3V#lfcR7(7Dck{*gIYhVti_D<_`XJjW{{ePA&U~IiE3owr^850fPVi^y zxNQucEYhhnt)_H?2A4GtNVElykAWE@$ zgXQUS0?!Wgk{x{++#jSBY=%~CFsHJlD#UiS3 at z#xe=d3Ooj=CA?~lxEn_})i-vQnALajw=}$DNy>bp at 0b<8Sc at -qIbjHXfDgSN4BXvKu1b90 z54tpW at XC_W9XdN+!7Ff+|Be?_-{Xac@>fzsUle!y7uCu8E{RWK3S*Gpj?;!Ulj`vN zFk`y7(GeS at 0I3aXA;TUDH at Rme)*Ne+B)vM!)O1K<2$H=7hcy z^92DtjE5sbJ^A at s<)KV+%nX>YL4*Y1CWl7F>)4ZC>Yt;ah1fLt-@^G2k`g3hTVe9?7CTp at sM<8MM^u`3A`*uVlha z(^{INhBWD2v<9Cs9DbH6Tv#yL!aF>uIfbenY~b(~A#ym9yFlSUYXwjD=+G6`7PZKc zlWPVS5|$=Ryd2NGGfQx8K=68-cywnb?vK+2wHrigb?^^7-e?@FI!riTRAHsUp+a4A zD0kTq;*i0J4aShJ^n!agc%h4JrC5m2MDhI-J;;5$!Khe-iAv)erPxngl>v#NP&g35 z)!Yx_NjT^NSAtD{oc at 45&PU|P>ApQ^>%?%QPUlMWsOkP)Brzy(<*1 zq{7-3bYr^9h&W#>Ka*~D+~slc+Q<&tkgiXFI7cD0N!*vwaO at Gw;R?gV9g^LdnWO`3 zBo+*br5;~!p;$BIKr^Vf3lwf!Jo7;#3gwf~;1=!Rc`bG&A|WlUz_{);gWRJ-P+#c} zp+OMgc_Y|aVk+=c1fCUS=ny}AP||{VI)|`K(jxOL>^FA>v5_HJFpgIZS0;HghbHC` ztDm_UMy68**3jUkB~AOK5FTo=`h&Cy``zFZ at 6X5z2IwL$b09BN at s0eTfly!Saov|r zRd}lwjtx2;65sHq*QyOftg?d!4T+21T(^U(qA-!*YU{*>2sF!6yvnSM9ifEJmZuZ9(I1_ zPA3-JZ)Td(Tx#bB>kDm<$;xnf;S(U>(wxhZP0O%UVHRWS2YX?Klp1_(9L|;W!gIF< znI3$ddoxypJAuJ8qx-_O7!OJ{zA>O%?Y_Z=krxF$W)>Ir3)PLK$XPy#b^ zbchWi^a4Cv4;TQv5gVMQ2L8%`WPh{CaKLTUoz5864b at +d2OkoT8HP3mk^_f?!d4{? z%6n>3RpukFa=^l?&|tG4d at K98DppB+2Y7898V!amo%G1PtU`+MulZ;7MY<@;`9bZH zScUSn*$4#5Wadm&h-Hz4;pq%NYXc?4U&<~XMrS_tA&diYB8aUUghY?>`|$pVQ)Lnj z-ruDYB&r>)Geel-6f$0CK<+q60;bo!6-{h(hCk!dhw%y{q8EA9dN|n5JkC^Xi+4ezh^x7R!n9| zEcm;>!}Qk7pz4vOG=g_JK_kMd_{xwZm*CU}qWY$O at N76D-5}BmsJ*sSd?txUAwmp9 zreY43lcx`BWun~gW}Tx2___Trl&c~!Ayez#6$j057i`Gn!(v^jy9vLg{R8?tpm%?Tq}3C8C2q8WZ2snBiHQ#c zBM~NdP{t$evo5(qce>MF0;?3mL5fTe4yg;ZYOQ^r*$E1_o0Ln21m)@aklbNv%P&e^Y7(u|zD|geHx2fof7~eDKC4 zD3tzhU|u1J&*-o-ArTt6ozYW0Ji;T&nORwG|YuP(U^3*scTaLRv~2G@!N zIVx$RuDs^wFs+6<)xxW+&#e^1xeDi`LQfX^0b6pb{3nMhOj>&5W+)G9Pd|{t<8W1l zxA0@?MLNQ+S|dIoLJcaE;C`e8pFxD5?{Eiu{h$DmUQBy5@|AYbn?bPP8!RYw`1YKn zKJzy$YVuzLisDTJe8q|(0m`V at e3?oSyDFCOblnKD^y-0wpuUMGPVaB>4A{@V`N4$3 zh<{;j!C?sawQ<Qj^lg}r^4EhwsGBACDxwMC9#y^E)m*cGFxnrAUXZosEfB} z;4k*-x;bhem87uj6o5 z#rt$_3PvBu-0B!_p9DByIx at hSDW6pYYgaf{8=a~~v?pv&Nz5S>H&D%KDaaR^gsa45 z91^q-Ev^$k69MXkq~sT>TI?%KZ!)3JN8Z)J3g-YkVL^*cCthrzQPR_^ zOiHpOJ|E9X9rnE?e5DH|#(cXtUP~Wnrp!EF8!`!tajpW^iH-lF5gbQ6YD7%IQGN%E z=L!}dioo88PF3*!F*L}am`jsY7wC*5 at SMqoP!0?2wGgB z6P59E25S(G1fh7+$(pt;_6cjmJK**+6%qmo!g1uWtAh%XZd9S>Km%d;fEXK}r)AWh zdE#`z(8)d@*67wNYO(8)rY%^J;jJRvAi+10h{$vj#r@#4Fkr2T69oc!Y6-%8;aSrf z+1jDDYGOu!IL)g+n)vfm$l^;5)N?)L5jc5P=ab=Dn4kkx9lXPkn)t^0CM}urRbCuP zEO?GTb*B}IPXV2e=g5Ma&g$OEdNu#4D|lRA5XCYPp)z6lHDmA&2{x9P>fhkN(-}!7 zMrr?8h^b2IO*-tdQ<8KS>A2GlCx*+z!-R(tPmkX|>H~^76wz_1yVk=KgmP^+++F~?GIgasc~ zlCw#D>I)8#AXPEms=v;q$I2i)z40OK8^wAkP}CFc=M?(_TvvgZ0Fkk#a1+lVaRRYMCmT=u5Xra7_1M4HiWlyTnm| zb0Hq^P&&R1d<*8Y-!1_%vyacJNL;M2Fkcqqg2oqfGDQPNboyD>W6e?P7Tw#}P_#%g zXufdFpz4uD>+oW_a5f(oH6a6^8Njk=X)+#)YHcZ|15LR%5OoLBc}y>W*5e<1BgDW^JdWXNCS&^uh1l1)Km_i)2`U$cXXZ`w7gLNrh8y(>>BPrzfkii_MbcZwZ2(w0W3Yd>n z;7e54r$AYy+q5{tg8}pgd}=CVKg@$l1kTJ8Cr=|Q^NG7h2$BsLe7EEXsjw2oUs5FD z$EYG^O%~MMQzOoSX*iOBxU4y>wTK?Eu%OI|sfuSx67K`!ZJL0^b|n at q90VA%5jr$= zd^KdragM@)v`JgQ+MV&?-1U3}f><<3(k3uwdbTt$h%$`xJ)#%1B8(}?Va=3F=Ol!k z*WqXYby9K*emak1Q at GHfUeFfatI1M~)t!MZ5gOEZf=3l}pDreIi32UG<Oo=h7LoK!`u`!|e(_f^)#qRUDGQi~Zq&R?wL*85un{MdD)k2 z9Twz}&a1Lc=RR*AnGpMx*!*ekw3n&zlgsQ99^7%qZtxYF7qCIAkJtC zBP0l9K8h%QaKW<-=Hy{GLVcRwY5|ZEm~ONwlH^C942z!vz=H?lj5nGxu|#)J-m^e& zr at zPRg~FV9 at Lfrg4+Y_u*wE6`N`n<4-dkf at 4%|_R*lf;2gPqeZmngv1gv3o6)aV&; z!FFa)<1+mVF+q9Pte}bF>5#xS6e)a&ClrS|J`hPHW{0{1MJd5H5{U*-!uzL^gy956 z24h&P6S^v~P`J=}Flum<22)ACjY7`l5&_HS`Em2U%!a12QLmkAQq=4kwm=?Ty__Mw+6 at Gv+L%3RJp!c$jp)tMHBFzHp#S=+vB9uEFPAsH-E% zpzg$E8d3z0$3_*-woHsjY+M`*Bv8fC|G6hqUg zHUOF}SgB!pg at j@ocNvC?Lds*C̈#vx at 1BGkuP$w7FrPWMDDhAsFM6k0#nF2-U~ z-*;z}0}XkyJaYG34=u#z%{UFVOc_ZDDjrdKo0DQK#B>Rz9b`8MWo!&sHH4g#%%$=^ z;z6H2Yfmweg*V``<`V= zr`{mvb+1k03XNG^H5su1$(Qu7Vo_)i0ak$T%%WadhZFu-h^rY%H?u`LOrUHgr#mb- zp%;{xLK}W^teZrq=@*714wG7)LizL%#HBtkjp at 3Tc+=Y|C}=rBH)aBe-S9NRgIpki zHhJkmgBsgd@(2qJ zcAkQ>L~b?FU`F3;cax~%N#D(psXD at u!9je*uxf#(j7bhTJUyl%L9troOk$X8Bx+2b zF`6oSTtm(E~r1B)dSaT#+3sY&3=Y>L2_@!-3v$hjbY1&6gWt?VajBp1avrMA`> z)2&3K^53#2250LD=P+W_mJSj$JzQQUIccyUZ=JLkOtQGoffU4yD&)()9CH6RvNc3q zelV at 7alx;q6e>}?oy!moiDgLl5+I5z15vE97=Dae)X@*dOeBqp2|ZEA_h6XAGEszW zR(x846Ct`C!}+jxgP8_rqO;(Sb$MNLC8i?raUEkavO(9g2E`MUPSC?L6C`mZh7F4> z$ZYdAx*B<6P?B>q9 at tVD#ON#`p7`OUC!5rJc!+pWAT07D#qfqiO4^rT!#W?yw zsl(ND?n_YAMlj0nTXcxcim&LfRgcvg6iRHw_|wJp^;D$8n_)#}{jC5vi#bCU>}?cB zaMe?TA$}9`%KltpL<#PX?ERDjlSSPw!V;RXFTw`Q1b9-8Wh7F;vHX-{jiMmOIa>vW z)(vv6t|dkqz`LV8A!P#|eqCUp*!{XTi!8gfT6E>~Nqz_+cdr z@{2iy at M_qE4v~&fG|8R347m`&t%n$ZQ$wFb6BvJ%8zk7sfk_iLX;7C%fWjQ%Hwrul zV6BTII2}EqJ%dDj!R=Kuq0dPKf7Jcvo*G`c}?q17q$ z;#;V1T@`w!N7hLIXiH2%gKc}-YVpB;B}hF=Jw8;%F&LSG{vKtgPSChxZfvU&A;LEP z&=Pj?g6a(#`Wm6ZjXWs$lcUaKN+*u=1`d2a9emagra9f)?r%bI$+yz_sYiV|qf3jN zUXMi}uP%u at J)i)ul;e3;It&gJS(0`~&dd_pvM3bc&798ZR^G at uu~y?MJiYsZ$5X#j0XF3;PV@?2hBX-ZnF7AmNKh=@Uka;Uz%P~HR%*50Wone-xn{x?Uxf at odA1U)xoMuK zpeuCeXRX4S*2ExU)6=0wVxz>sM;+WA(jE7Yku=yBB at a$UEGN at aBJocr)tIOKiXVgM zems~{9*fB1-5A?yT#+Aqdt%|Oba)4e6%7UqTbkq&gi?^RCJ}ouZ!y(KjOW1uc}bY) z5*^5}@E>W=bE^`WX(7-LT%F>@|mPZo54s<@H2=hS at DXF#9)flzd0Qh at YFgt^)h?^Ru4Md^86jRU<$y)V+4!-iL z&Wx|KV@{W7kmqZy!F}imXMit9rfVF+3Ov4}G3^s$c^JDZk)d>n0zSl8D^c86B`$$e zQ|N_wI8!8&-cM15LV~Z-8NLpW2E)0sQY at i`PCr;rNPJrN1s2SvOfh5LYz!}naOHb) zs?`r97~5AF)e(quM5npQ2|cWXx{e^WRxQ)0DL~_$xs%zP#E4!^nh*L-KxbRL|lag3j%o8 ztHcUCnawSo1~4b5^a$GEFiz(zH?7mamJPz(=UqU)|f8ypyw36LW73I zC8JObnj883R%DRW=kmmNNd$Tdw4#rN2Iqhx<1&n at t~Cgenz0k7dz*C93PxpSW)KAU z01V*=4yKM}MyDdUwrH*ZNF;&^tvGu%Zl+c%8hoPS78-0knwT9SoauHcCJ(wfsO)#Q z81rb4);_1aUbr%*L6#81mQ}qWNQ4WilEaTKHF)(3yf!Pbt}vk+?AwCeq2p98z$cZs z8q-Dlr|6SX{7~#c4xjdcR+Kj_#9Q at p?o6{9KNAT)w=(~TM-zu8*%*z9)M^KTau%?& zAhgMy;Rp_TtDR{L$GlJ2uckp^Je$p3^??=kiYhreK#oIP6N3fw<41%9$%f}ApW7$& zfdz+Q4NhYnn)cvonH1Z(=PHsfXi$)diJg|=fzHgufaK}Oi{CizvM{BxUz}?6kOMuq zO4s4Rc6$FyB{<%fP67UcIj9F4i7jo$par&C#W=Kx%uT3(Y0Rujf$I*tvQBIcp=`6O zAV~rK at S;E6m;o^b&1&J4t)2GJ^$~zXBdT-d%?t*^lgL&M%EW7;Ky_zY*31 at dAZ79X zNV;;Q(X&|5B|q~d4_=pH@}MWB?c;3#h9YSswu}_7Sx^SVt5TqVk%c&w<0!hGl`%_Z zt at y991Z9V$S|*Ov%nce0`r9MZcs at I;a*$&=c3*>nbYsMsmd4QV*Fa%M6xXhxc7T0r z+82n6S6y`RW||nT;Bf?|L>KZ#XRgKB9sEiwh(3@&T#OGzg`CnL&$v`;dJ*NZcqdpT z7Q~HFMwG)9!i}LU7I~bG_29`e2yFG5!O+5F!$Tf_YF=Oyh4-P&pjy*>CVT5e)X9jR z at U#Ngdz?O at V+q#abOxXvFvH;p<|F&53KF7*9C^M at k(YVU#v?%AeP_`7KyL^ukNT!F zWA2PpV+CNhFPtO8pJ-6bn0V6;@YEcflnL#!@cu|`qGU)JJSqNUJQCjzunBFeeXv at b zxv-1Fc!NDdWAK(aXs$HUF>%rs4m&a&MtC2G%3!Viq#tA%W=D973Ugo!4)vmdTvQdG z!B1c(62v at e!mbr zVLA}^U?&LDi#s0@>y)Ggz<8C2f)9$%{Fns8G#-h#3 at J|^P6w+RPoyiz>-B{6s6UMR z?r+gW8|Xx6TG;oBFKTh}W?YA)I$ZYX8pmBOLWAK9)~IuHW_1j%sSKYUuw~nH$P6AT z#C)2BNcI{lyy ziBXkrpfA^ARmErrGycxVFj~NBfn-qxwo9-q&1p<#4r^w{Hw_pf{VNhX8&R7AqBHW2>r9~#iTZv7-wpb-WGx&Nn zPnYTPV0kL>7sPNMlQ}DM3xEyqIsO!f8sML67_uWwG&qMGhB2f4VbUYK)zMrFf_mPs z?GjHTC9r$b1NuWx365i7ylhXKJU)k>FA=jOx3UU&=@01eE4w3c=x=%m9d_EK9G}!+ z<3XW89ItQ(jp>lV#c9x)@T?9?NR&Y>kT38`7pbbk-D9}VmLZ7~N9cr|JG9!-k^s&IX;oNgU zp*)n6I6S37>S9Z5eU%RD8Hsg+my!fJWXY`{(lX1H9zL9zWwlZ~OK8xA$ciL2vkF<2 z%yz(FqQs^uk;9GRMN+F66z?k7!qYr?-^au at AqEX*UDBzjUv!qnx z!x`LX#YvZlGktk&1frxBl3fiD4jGGR17&?Ah9f1tkK<)F9gz62g!atq=B(~_Xc8PQ zv*bc-L2Bah41QjK%@<5m*kD5QW_G(K4 at lB{j+5fq)|m8QBj2MtL!m%%iixp0GKVad z!pr^533fDl!WH2QROn!6)kR(;v?MiraW~4!qM2LKhngrkf7= zWhzW_VW-10B3#X(P8PLH=$3vjSd_=%3VtGN(W)CE>Szzicey4j3j&OC+NEFg1a+BC z5BHF at VJX7Fg`bGxx%*OplQC0;_s=MhuNjHin-+E4=>(xlU`^{^oOF3)7G7Madp_eK zIJYj>Bwz8Sb#=)P_Mk4g1|TJIa-|VmJ>HkZfHe|lMWfOm2vEkuwnCDHNSYFp0V@=) z1$pJ9Rr6$!+R91k0zn>eBhL4>a&ipzsJg$7^o zbYigK9USVs_$$W?E_S1xAV(wgr{iiMaU~8}kj0qE(~$#`v}4?vfh8D?p;Tj;76X87 zf7(IVV?j{gG&8kIb9!ew)x_nr)j=vTN^}h5MtXrczCWJh&ql-a2(v0yTgDgaY5;m% z3$nPR24AvDC(Yo#<>}99zk@*?(EY*mJ`UR={O;^>RxKdFS2u)B`kVcZ2M)3XTW(O$ zR}NiWDL{CzhfYWOZD2ZOka9%#=}I-`prqK((5Dkh zXVK|`#ZrH_uw~-p{in={)ebzeThAw_#;nEln7FFNgx*(oihW!{8We~61b%6Sq)yC` z0+${YvhQ1R at ts+f2|pc^tWkVd;Xa#?M7izrVDR5Ds#E6EqZDLHH%t$$!Nnp}3LNwu zvZN-&9ujkKKYvIaVO5C{RYgW&FTML|mQQ&waXIS}Y3kS0oS2KijPhWW9=ySU9U5FR z4PK}BnU`@cw0`gee#gbffQ2fV>A$R15ni1KZ9qa_5cYHni7B;uJcB8n{Cgl#&zBKy zjQC(EW3+%#r&|no$04+#v9n-Gt~Q*()Dwm;NPBu0BHOc?6>o9K(WdR-&02iz##Gd2 zaBTbt4zDM4+6FGj at T*N-iIWP6vm{m}MIEM%3`xw7Db}D2MXLCIM-!Ja{D at u7(8b8g z42`Ikh>;iiEnba=c(ZIa1o(ERDs zZDI{iBdVb3)z~N&ms;`)j}XMs5k4qMY8dv2aPtovn?jex!hCm^oMbA zoNk0Ix-Vex8hRBHTkD)aVPy_>Q7AkJS4MO&=5s=WR)IacI?v%uYmb=4$SRdrA583> zVB*2%p$bqW4~5 at x#Gvf6ry}fYbhsTLjz`j<8ZtK~bsLa36auW#C%UAKL|x?6=(HO# zp^4)}kJoYDc|P9qOBlTE4(j?+rHQjAeLABv9<*w__LkZK&LoAGph9Z}S7caeOp8NU zDzfbq%MRz&feMa8j73D3L~M%~L~4e6n^uy8^3)qt_?>}plXn4Q)+Jkd>lR}g)EoAR znH>xbvXz<~8=b+0&#(7`Uqs|!+C1zO02PA;qLL!tBoF37xq)0)`TFWvF zmyjZ4ho4O4ET at 2a+(jYcTDCtTGZGz;U^p`Db$P00B!hmY3aLo2J0uokT9=p9oE~n# zZ%ex)fMTqW at G(rWJQq~x2mgK%gK=GgP|q1maV=XUu8qMOigj2)^D+{QkruFi5ED*Y zWqw9u`vf5yLNRA#r5FX-oSELa3NjVe1(Hz1QHvpoOIJ9=r-$KhjPSG#=R_C}H&wBD zGs=T*Oq)DTAVgeMf#_r6q=#dD7zu^;+2>~%e82Ba>QYVyZ6R?e?sMNQK6H`x$Hv+M{Ox$b>pBT`^VG6oQ!Vj^*DF?lM5rbd%Wo&Kaoj4Hw^hsDo zftaTwc|ey%i51~8nd2#gGhhgh(C%8ZVm(`C7Dap!nvUS$=g&NqExKvaMljDyP at DJj z6|hT!N!_CcA+I~hnNEgZa8m{8aS2 at rVjp-kzpPK+_#mt;w*#kWw{Xb3SI z78PZ{!o?Wx{gKALA9ca*pB>pryX$j#?Yb9=MGNn0ju>s>DOyLhs6$a at F zYEIA{YW1Xp22aYbATA=*CBN$CU_YgR%<7g)@fN2Wp$mMH`yfCtnd=_6dV#^aD@<<5 zNF5f|GaO#QFm7kwtpyUo4s1vLfqww?!JRov45|*%gICoUwAoycUyAL20f&}ZxLuB= zAPesaEN{$-g$El1=z65bf~)9av18US24$OW6*#WN7PJA0f^yOkN$Z+jnAltdo5Uf7 zrxxh;53IhU$>d~D_aSI`nW0|lL at kCckzTL{q6fB03;5Jg-Kzu^D)lYw06C)rCC<>pnF;9+wiU6(wJyUl z$FW-CP74R_&fxJRx~duc1Y_(JQwdjh(hpvhU;5MAX6Q?E;;oB5i>t8Q-II_ct(a?<)k~KTA0k2k}}#h7tQcL(>E1B|(DM zb7HVzXc8Tal&_@~Jk!L{5yn2}vq)juf;iekt-&Xdn7Y9)VDOGJv&gHLC(V2@5XSR(xUH=lQN(;VEv_mGXwA$5W!G1zdY`>I%d7zxdQ2shQp4cv8TklXA>c;BsQ zdtf{Bs=seMk}-G#fHb4Yg)yb0FLQg1sX>E{2}uWrpA|VsP>b-Y_gaI^kV(&CVolc6 zYUheE3Bvp3gVy4DI!9>?@P#S{4O(*W4*416hCvE2`@u9J>4dhP#(xEjk(G`|KER_k zo8~xC;mfvkg~6M(m^2tV_;?2=UdE;gL{#GVt>z=ip>9#(YPA^6pb28F%Cn8;Bqpc~ zcw$VC#g7@#2_hRaY5m=8!NVWaJx&Jrr6Kemmf-}$HXJv>Vcz288VO5rn+czC6G+6_ z3R^kn(hrr{UVMASsQBV3B)=Pj64&xx at U6553|_F{>O9zN2#o}T2p31+EKj}(5Tfde zN1Y-v`pU}8#Eh)7pB-+LjTwfhWv3l4BOX3NBtFcNb`@Ob6dL&3TK52;jEPa0bvV*- zW`Md9Qz(ZUg&au_q>JKmyJ}%~At7nNVo+~5vptONnh0_il+-T_uLnupK9-3MtAyI~O0X#3mR3QPo zem^LibYV+B5#q;2J61?uFDb>2y+7bl- at sk!1Q#vBN`qJUtMdB}Kq}%yg;Ihs_W<%p zgB%bG6DFHFy243tD9Pz_8Vn_(o=iN6DNRNi*HBIt3{Z~#q;}4Tb5ZWpVj46Q#v0%! z>orr;1Yqh5Lzu*&zbMAK+#busLW4gDg91iXB5%rJA;3Gckz`N1KTSn^9vqa^eybZK z3Q=|&B1E0Rc}6qXs>UTJLzmhHBoo$2G8K;SWNdUi7!nPEgQ${bDW)BW8 at 7N@!drE7 zri*c^C4XKxg8x~pL8-#={(?D3E_~gLDi{VYD)I8RbAO}!dW>3}noC5b4K+4I|Dpey3JcfzF_Y7S6K at hZ!Te)sao$Ftuy(q|q2D%7Z#I;J$DM1&NNs zht%N2f-tQEcP>P#X7Fi$HZ2ZUFd9Pc&c*pgF)sey1bCANnOapdhCmc!<|?nH2TrR! zsQ9x9-5CY446`Pi6s9HZ3MAPvvlQg>2{GG2Jg9y51LHuC1CqgYMG_BKB~b+WaL)9I zS<@$jl6sRf7hs at osG*lhFrqT3!5gM at Qi^p-EaTzRXA^%94BC`VwTPABd#?1NyJ4(-0>=mInz(^9A36ysrT; z!EgZsE`;GCS($Z}ycSJB!JSNlL3|Pxv|&jFL+b at keL>V}(Nk(it(}T8z zr*+tfQ27shXwBbnQjVYA4zdPgm5%!47!^Np;^jk+J^urNg+2xWz(j*#(=2$W7`|Gi zTQUeM9`usjx}+nNtU^rdlmY7 at o%7&?_j-w>O at wlw0-3N%PwzhT>|{6 at 3d@PBQG`Vz zn=RcZL4%^{lHkECNLnj608O$F_ at cY1t7h_HPPTqZ?JLMGe|DmK|5j#Fg)b17=xCg-j7lc`cq%p9hw_{ zjKOfrzX^Aw!@er9O?I+gbc6mlM7o1G;qA*OD)cdU0>8z`&*pUo!5dXLTY|MUxH$9{ z=>=mFvc^LZS({~K*RI25P|V1T9<3!}xStn%%t3-N5;?5E5Gdu0nLR4wtL?1CQG6vk z?UKYDnO;yKrxmRGYv8bpJ_)T_f*Qp)wm7$!j7Xk*I_&Sy$=t=D(V(OhRE9YEZUHeJ z^rix(=Dy$1mxxenuyliESZv)OiO^_pA_Qk|2w93n2|omiW6+MR3=hk*FO5DZH{I&mod&$xND>Nor(vi%?0>Q7Cex7&4EEhbing zcGP8Dzp-L05$~gtN0ub|>+WE$9NVCH7zRV&kW++Fi?7DShQj9-GvYXOTDC{xgh4!( zPMGkIz_6=}g#-yj8Z-rN_wriRlz4|)1B$5~6!u$LnE4CTMP@$~GNnj4N?{cMN+q5w zGI({+o1~{lmSJ$8UH+X;>nK1}hrkp(l_G*Xai>!kXk(FXL#X9fr*siteNq^|UfLH1 z;J$||)5oB$5HlutbcIoaxw&+m5&q;Fn z85$W5wGWHG7M8idV1>teK%zLj5+$ZWk`j^butS2zffAmcwGLM^y7*Mi`mL(49WmH@ z00|pGRAI4Z_Af9kX`pZ#@SI{j?X6m$!;o0m at RSJUgYuw2kS$>kiOc zB=NK2K^%FB&CVZ!LBv(7&{oPS2o3guNc=V2!G<)(jx}ym>z7A zp9+yGDRXkB0*1s#jv5Di(3hF`g!bYM7X5pO;sI;L$DwZK3M%y#1ddGx+?ZR{V1l5J zMK>#yUOT9;lbiyU*-ePV_Q(_Gt$w$}LXQV^Hm5b%1|u$xFKRLS!Ii~WsnA)leY66+ zX~Hi0LjW(_8G~e9iKQAhmg&qF#DHYtXig6pEW?r#rQUJL=hKNLsOI2U1F(%gN21+& zjNg!W=Er~rxzQfO;tj{GmZPYj7|VG{{=QcOgNxsLX3*B?u10jZ4BJofXV$dI)JpG1 z;XOumIBg3KO`6jw50()L3vty4HVJT60O}G6g8U`A&&*7AXyQSfV-m^1gE;xEHy3Fk zFuBrOh8{`qY at f~y8K*zgGPc}AzAcS>hO6nUc4C$cQB2K zyUk!xDl)JG>{Jhf0U<`#*<`T-Q34CSm;&!d7Gg{8Rvl}5CJV2kvI)nf7s+d zvjo9|0XjsrM4WV6K_U at tU}3=+*C8t%l{v)*zu%QGD7h!YPQSSM?L8Bq+pMftr2qAfI zgHuXw9a9i*klE=ZlA8>;VUvzpWFHV8pxXm7TUlSiYg!`tK^QbJaw^BTOrpWl=wKCD zZQ^kCD!hMoKjz5G1QWUqqf-)fre$1=7x~1a2HWs6A{;d;gHO{m+JlKdkyQ$FNZ;e= z)p$1NFxn4essQ1&GL}1|K>$zvz|#!>VabFW6KR?9#qm}W3fmL*;Oj;7#ryBJrcW^F zXAFu_sCm`vu#BNF>)>UzkByMlDoR*rFussMiHc$H;EYohRDtZ^N-~7=;XvbQDfY)dpffB_Vk)B#g!o at LX)xNs z at 99t17?i|T?M??VS}E at Lfl`Z;C1V_ at yuvUe?7ZL at 7dcIKw!&V{wA`1%rnJ=TdWT_Ac*lBP-+b_`%9+caxI zRyiWaY_+cu?+j(_$MCcF^TBZ=IfJgyr^9mIbi8y&BU}56EkgXNe`{N&2|y!1Hi9_e zsMvsqLmg_^1JmD^1rradD`PQ!SH$NOSdrmc!B7fv2A>93kAlnt-VQrPF-rOqNok=O4961P$mxhHVyVW%Stpn|5AzVsX;oA>Q~*>S ze6k{oDZ{u-9C7rD)82#xBrzG0vpu>qI$feGTDTnbd!X_WnJqMh`+yju5NiryN9&VfS$dWKZkU9|eFNcU zH!T7b07hDi*7S3evlO0i2r;88!njUNe>Xa;YOzd*IiS{d+H#Y2U{ZHpt%bwYgffiJKirgP!X=+Q>s`Pwn4fkEzzfc{jP$aTF(*QPM^p? zKAe+LNuLFEBtl294Cms9D)#gReY%Gs$9c2aop8x0i;(gtVo}e@$t8b4KS&Ov;HY z%Nr~5d0JtsO_{K(aoj4)@hc*#59DP6f6wnJOxpfnZOdr)*WVp+8dPsu<|0iRY*Qhu zY0f`Zk{vb4s@&%lnJ;$k>L~}t8l6q)lmg3%TS|RXIcM-Ns6U6rkI4`?3^n+w&`b+| zOlDbP)p)RB3f8Jve<($sWmyUC;PF|G{tdu=Z#|qbD9OME{@kBACqh*twerx?1}uy2R!rvm>;X+>%M1s^OCWzsO?l%}wQq!Oxe$ha`+}c~#a{kRuXRB7wi_ z*n*~H5Zv-TlFFcI(%|;Ys(6csavo at WZclNfpSnc%<1m;!nN)^d1i$6=q!#CXkU(tc z;r1RUYwm((kd5Ce%Gja`34RdA@^iw1SmT_yIycdr4h6D*l~3N6V-)C753NEh-Qcx5 zy^#qw?F=g#>`q8zHbUry5ZQIHJdi!|f7Js5e at sW1<|LIGE^5TxnH$MfmBhk^ zp-d{|;k&uQ;Sm^A0BSclD-vfH#RZ5I!Rg%TekVAw{(ipC6Jmo^bg-mn1aqv+1B&r* z%u6bJxI-*} zvFVcqrep|KkL8 at hlUdjV&QW_6dq#e>7B)=`A#!faOgw5D+C;*Fg$&D?)59ocp`36T zY#2&J7mAY{9 at D#A59#~76nitM#km{ZuWE7;D0_l1c1SAhg+&-kb-?0}z+i1p%Yib0 z;g at Jo`$5)anan{J*H2-JWjX+oG-yQ`f&RGcgYls3FP#9e{F?+6EGxKE;=t at ya3HI& zAFnU~kUE}oS<8hnB~d1+6BJznGHBOxL16Zf^2$59V|PaIL2!u8BD^&bih028obhjD=|jPUASVkO$0JMGhTOSu^*& z8E6ydKU8IJ0~=t>nZb}GM@*WFbds0Z!S+Nomf-3SBE;wi7s4+|;#_4(fDHo;}h4;F%_-AdVLz(UeG)SRIc3uuSTr2^=#8S~qwj zG&sbK7h~oW#2!3bq_gd%7?bSdkd3JYi7sGo$yeEbf;Z#C;w4w&$|>xyAWRv9q2&eJ zDVZq50X;wZca|z_(j-c9$!e{WV$t21)1~6kQ3w$30l~ncKbW}8MO=kQS at sGnNJKk$ zHYA}IN$Ygd4T?nW7GUSXvc7)2zKSM}kMw1zL;l!_=QlB!`@jXuoVT?|X2e<%FIIxR z9w#U+thv at 3mbz@%Gjp01SXD_nWb0Is0DyaVW(a9qG%=Dh`@apjDfAJYy3^?VF0(SB zgR3uH0+-_J`^25?k*6`?v#iuHx)mvzBQ}HL`;f4VV(|M7NECw>Tn$%7YVgGZlaQb~ zgKZAX#dsm{DHr0Cs9~&($bBF&sS%#$F{cKwTZ;E-2r`4@#3Dr|&PY-r*2t>PMszes zOHj?hQx4pW^zx8IZJLuMrYbgFrgK|KDBu`{7?i{_!7MoaLs`)CgXBRe!+N&Ir6Oqr z2P>nxN^Q05!BTDgJoKo*2r9_5WKPHLm!}g?rb(vGn65a}fxH8$g zj%As+C=m=utQ0qC at XDiMR3TY`j=LKHemXO+?FnNrJ)r%G;tQMR`#c))0)w=qNrQqy zg!pvDDZPdAO3QC=k?Gm-~2r%=NC#}$Vv zzzM{pgb-|_u&+yGk8_h&P|GkwGiVAsD2%AqW+YfpXb&KXf*idd9{%v-FY#tlSGd%l zE-o98pcW at xti52f9SlW+xm@{rc1#KdT05BQu{{1p`EPEaP(+Cij9XHeM{6DYX9Hsw z!_{*+?*s)6i;BYN1_ui*bUE-zBo&ON3Sf3`h)vOR at +{ zHFmH1@{$B~atGayiCcB4XUC#EnwaRc60C!g=F{jTwspQsZ2Ro-K1xw;#bhc3k_O3w z1%8S?z3DZ811v$R6IaDR9PODl584B8o at G)Jt0RY3{e0?>2WP|d at 3{N_q0u~DScr5k-NX&n&QHm1a$m04Fo-JDCX(BR|~5|bn}W at f?RSusv~Or<7= zGuUbh1EO)lR>9j8Pbt=s+(lrphM#<+tDI&K_G!=()8!u9BL at wZu*@sE!IEFy#R~K> z{lOoHM(GDlfMm>F#a44(#{3xO_>&50&Wfci)Gq{&mRgvyEVeVJbv)9^D~!kU=rErv z&a^p$pLaNYUn}vR6r5t6eJD{H3+wHsQt?f3p!}4~S|UiLJ&% zIpgr(gA(H*BtH{_E=iVb#ayW3Rq>Y%LWcKiS-i(rWpN~zW$go5h1W!fO+$tpcyEz& zM;kWVBe@#YcS;nvKYngjRYKT}n8}Sz8syZZhdqiBt-Vr;r55)X(BSBge<+sBUU!+< zk7r}=S1G}l__!2#CxRhaA)jbkD<_`MpjLWG-xotw9r>4R~?OmGT#tKepDqH$kXWR z6NhWP at Hi%W0Lf90$|6A5(dqgG1j|b4K9kpg2Xi;5#3<}c>zCakwtt!%;#tt1{yyP zT=hBeHgB*?&{>z38$8#Buniw=II(=chM^+qa z^u;|VB{+b8pN()%jFooD$o#Y_EEqKP at lG^&?oDemnC)pagS=@sgGPnmI_MDdWmJvS z6<8nrFexAQ1c at Hy;RbbdgBgV5aCCtWsmn=&NLOf@*scZ%7d~^ELHik6vLQFz)M+~KKc&WE$eD^*JgH{ji?%#)^ye?@RwJZ zq#W1yn*hXd(x1l>AUuxTml-h^oj&Pr3i7Ls80S5U at HY)=3(9Qr83 at Gc$kyK=k!51B z2{(`+;#)77(V;6WmrUt?0v5%aG3*&1URGn>q+8p^D>UVZWd@;07GO~ZB$$gNEV>gT z{h&t`&WiCP(1<8?96CM0nVCe2xF9hSOj~p*#MW0%z{X=ta?ss=KT3qZ5t~6yAxiPXVV8XwSHl-2IWN-59utHxp?5A!5FW&QBp$|z zGAumkYjnO%_cJi)V>;0%=Onliijc$|8UzVOK4w`(c*xTqXbm2np!$Lq+d3G}BGi^y zy^0LZm=nj4u3$0jK^)^Dn{orzi$5WT=W;>#(GSv;e$kaF zyL2xZ(ban-hz$l6{y<;QF!+{}J>D%eo}n;{5rPk*_+&l at LCjrFxYUensX7J@Cl+Hl zTPxJCk>E*7=!Ysy(*AiBKB6;mo)+ov(7;R#W@(J8gFrr at U6x3)9+sR!wae7#1a*lM zTXZ;WFtS?dL^ng=PrWLCFeSLIt{9J`0SUb6z+~zJ=lT=fYT(hz at BR`Zc%2gsiY%cH z>9Qb4b7O7J1P4y(v7A8S{P67&b*r@#OzQZY|GX$W7$v%Ykr)&(g8nvGGjhIB@y|n|>S7e&d%68Jn}ga7IurwEnGQw%u8olhE3rZmU0{`+c4MX^NYMWo2eJ(W z0~OBA!w4>9TUxeB8rvf~0Hqz|bmVkAmyY&GkwN1= zwt)Mao^dHzeY0gOyU;La{1guZAYNe15P&oZJ1`^y)uE17&&3mTEvnj^Ss)?hXT>l(Z!Q$`2HopLOO^o&;UGcesq${fr$ zi4b9O!H_0%yMYBc?oP_^h7VSSO&a%egUAU=C6;yKJ*gD|E^Lu(=m0lFr++>W;my#M zxvs^QkTL!P6qgJ=i*C)~)XcdIvqU#&kb<~I<1|?*E*AAv*J9w$!-G&IQ&Pna!$N|) zOxA-HNP2D-0(ECq8R*MJm;zaW*H2F)DXJb&!>Gz_f zc#XnpNT>K~?vSLyOZ^e#r!Am at Pz{#TXF2KZRT{#lg$50MKOKnB#c*Y=&o)|Y zJZBA|;!m;H$tmE7Eg#lNZq- at v^EGo|I%w5k*cHWgvIjm(V!@8F7$YMbvTEVA{N1m}o3`G81cgDP#J4am#43!4c3sy-o z56+!cIteS}*bL!Tp3#m$iJ>sY#36@!rC3u6Q<)Xh9hC7%TyfYSPGBvSmT0rDQ()$@ z46`L<{q>|mwyHbSx0wvtpo!xtV3O2|@*C$HJ%7U1$T60PAQ_BnrERHnipJ((nqzlc7Y(&ZJBiu?3#PJN+N zVWO`oi~Ug~s)@TZy8U|rB1-QFRJtG;8mJp3qw4qaGDL>@tO#?1WHaK+;^cHfLRtI_ zvl_A0XbfLZ=BAHN5#Hx<9zW^C!4?M7`pD_EE)7Y-vTFqx$|U1&Aq~!9)&v-nJ1xB- ziP2CPinug{mzK;lPI1(rr4(X7_pnEB4zfcxA|@lwpj$H3&b7V}BTzZ40{-)Z?8ZGmY%VrREAl$(c zV8GeGFX;t?2>(n`f-{eNT6`i)EVZTMVcTFL6*fA;nMf26niWXR0vhC9(4b`neO6pU zam3B&G`M5&F-2ckP&X?SWnjz#ytAR at 1!X#y1W7Zz-y_B(fs-j+ud`ch5c=5tnCA-) z^`0Ai;_(kW`}gOk6$FEK4_c)wQy1bSO&ai1TLw>kh^8?4)jA&*J`8OFV`c&yP7+h- zXZJ^W6;W~&>Y&0ziGxQ4?J$K=x7eK`0 zVI?VpnF;mCpFoE)FAnNEQ^Zn-Ybb*Lno`7gkJ*Gp=nivX_JdlA1BVivmNU=ImpO%D z94|ACu|KjSPogubiKkJdhvQIeLY{PYYIs`4!`cXX zGswA#PKO733A~v#c-{&Q==x+j0&+xmi0Epc1)0|3jD&i)WTl^gU)f`NKFd>>B)k9T z>f-LnZW)H~W*TD>*=9mDJ~?Jp4po??J>(`&m{f$%>}k)dmWOdF2fnaXERUlT4I%;RKL>}*QD5)U zMLlkJf~Ja3Y#Bo%dcZ&&$8?BHOKxG(0<7Q at CDNk7*e3=FHcp-<3S*BB4?`^H{Ynp* z64F$}I;QJ_Zm88SlGKvl*ZD}xomOw!X%K_qdNu`6Y1A{;#OEYojOLXU% z#Ex&J3{m~jKp$Gdxc7C=bi7vZUHf#S9M?i4NMokFKiCHHZFt0_SQN5m`LiK&R*M~o zhC2};J}a(Z7&DRT0Q54edmZDs7mt{M7jv|Kk6!?PpBMbWi%PsI4jc5R{O5376o08P zB=@a~K6eJ!7!sQ#p6qCuNq6bm*Qn->~2=WA&R1`+0_FmH2Kb(o}QkPI23 zmh_sszvG}cp};U at G_Bfd2W_DkN*Hm$+{53KFeDaVk=A;~D*N;GGnp5!CTE>QxHov?o3ADPsc+a)>!16 zWmc?abz@@8w2WjF42CBoI-x&H$xMuD?H|E;`G}c8 at n;>O?bTh_GM1i(9gWYmGRBn( zF>uJi$P-E=Qv4nklw$@DE9TG5 at WAUjeQ&9g{Sai11&~;B)f=92EH6UO4$Y z>M-5uKjFZ^bVWaSeW^WN^@JIB%}MdB2W-trCJX4Q58=YB<>Y<&u{lZSa2eMm0mqS# zr3+`U$l{6k8!@O0K|CgZFwtPICU1I+woiIN`>CMUfTFL_i6jOX<+;RQKRf_}izL4W z2G7hH$K1lPuxXNGTx at abR!1nt;OZq-TtZbLF&t5gTH`WWgu8lV#2THANW35)^2GE9 zX)uJyXhRXs?IVFe~w;@^C4ZpFY at YH)t`!|5K~=pR{vtHf3-upsRp^D?r5K#v{huNQI|iri5Xe=B~WP24k at g`X*PkeYAh^R%|}#_V108dqv972yVMdK?-_l6Iu5~uO%$>ROtpAflQu~# zSha#HPTddM6C)7HWYs`FxKKs$nk%s$@6j at +MG@PXtEC}q@}HiA$*=dvv+E<7k{>7( zc-9eaD8z$tp<05&1qW{6!1CykEgt^Bb>bJ;a3dI>kj8%f8F3k0m;Rt1E+brMvY05W zGlNxNi^Jm8*6Fki&ygIQ7#WNwj!0IpyGfj)`&C*i4qDwmA3FH+T$x89Ns+<@U4c%s z1!XRGLlvVTG#7dxIzAmFoPWblLrpMq%LQC{Q31)uNyqr+ at Z5>P@)PgK|K2x zv!GCrr)pRmLeH}Goc_gPK6<*@Z{3 at QHUR@saM?X}v;$<-a1{uzT;U^6)=mh6gekPf!yv2ycqFIG!7}(QUkyioP=QCx;R`;B&L$(rMYW1OT=C{t9JyzfR^PL|!NmS0V1pv}4q>^oLfDDU2yiLWInb zpVl+p!ih2b^(66VcccE4+FO`Wqgzuzu^e?-4C;rFA53en!MIw*_(2V_1rWuV5&m<3 zumeD>FJ)3ZXprX&Mpz%eFA4AeLM(XR56Y3N(hatZ!L}F{?5QnC02&9*R*gHE1s#jb zxfWMI#7g^U&0wbjkAv}|(x70kbY>V^C^xA9_`CzZ79+SBodjlN507mY%%& zD_tEP3>{+IOeRJe3ZxlUz+^67=A02cX2%*@ZId95Ig+Sg2Y`~?ACJ`$>S2!SLX+Tu zOoLG4q{8GAbo#9JB#5tk8SXE-vnZphgbFhe!LW8fTZ?^wR*J*AB znz(cLzL(^NGdKf#dsv3^yUL;#U at 8i$hjKEyN-=T at L_hGiTZ);RL3)3{F at ujO=@N~t zdJA}7k7Y_XNINJ=uG|a`hCVrK^{FAPErNp~LY}W!Xh(7e$DRLT-QXn;u08mvHC^bh z*%KZ~twps+XFk&E1nmi6np>R>gbhW)0RI?N$f)2jjn)W$UON?vG>*sNSY)e?DgcuG z;mGE=D}P%GY`FkwM+P^gKD at +ajt>PxTn4Vp^Cjls3xNDn$0N9!*)6gk26WjO)MFJI z;In*SqZ4!#hCrF|vVIC;eI8vB#OwpB%*>vTo?H>5H7v&r;+VJUg7}?|sKPLm%);P6 z)XY`~e~o&~AEiMN!vhxUI+fVZ_AokpmcU>H6rMPBP~xh;PEvtan+I2UJ2$3vQr9_N z1_vB9rHI)v<;71~os|qHq^IX at 5tapF%8{M?<}xsuNPT*h4U&oMmuRq9gW3_^g5Q=u z+6GfKA8DcR={VHDAg3aGXyz49VImFsKHcZ_E5Wem&A1ue{LtjV-105J+X at tF-zVi5 zfx%VPu^NL?j(g}M3g;-qMSM&s!O;>+Kr$ZL{h&oVT_Mj$&d0^clG!%jW{=Oe zV2Bc2PWC+%@e at y=JIZjBW;xjs26bGLBm5Pv8iQ-Ch+)SV4eFRUM9C5 at pw?txAK|=Y zU2|9_A1_#)tdM7QT^h7466`T?t6h0dnJ}P+!YoJ(uR0gvlk#gg6iy6}5a|SqYn at 19 z(4e04wrzYiY&;Fok5+iSenC$ruC6ALHR4>3Ju}$UBEX)oAd4z(E!ISduT{iR8a8SMqhg)t#Hv!-$DWjKO? z!;>g4|T^fWToiZ~)gfwI#I2e0NuYE|T zCl3BHNd;cbelXEsrHQ>WTvx2oHCw@$+|A+6(3r88B#WH}sf5o^NGBR>czm1>NkZ}i z2kr+AmR2wh94yDZJ%aqqod)dsu3&;8)W^UW|Da$@EO?&ULMwQ<(qawT3dt+RCE`y1 zY6MuW77uC-ZWz>+Kiv;c(~Jp3QVOd%V;$hC=*!0{oTX(9jcf+X34hsB at 3>J!!OnBPKtH0cpFu2MM z6-gI>%i8J)XFcQ+%y^VubbvlMmLW-IL5CtS*w)2-D(4hO4Kl1S7A)FOSSzqb_$h;x z2}yxO-N}5;0q6DDCnQ%JB>o7TI9(=Mz at sT-SWt7U1$kt{U+ppsu0WsCAmOL@>!h`2 zzoP+IaJa6lyv^+NI{VT4RKwuGvok%Wnft_(C%4=j9P>GY599}Nu%IF at Dd3CjbRt1` zgtavt6nN&zAS#2gh at 7&8oR&QP0lnbsc>M*NK)D*VSdrK|cbclCF3JR8dK}#v-5?x7 zlfor4y(apbW>_;erMvP5n`gTZCGMtB&yElD+m&!-hRT?|eX)Y(dD9S1c5> z_8zc%YVrNGX|UxF=4CoHWrX>9NtreB`(X{DAB-Nbv%>^K2Kjwg#pkg>J7ZOVSST<= z$~emR1mEb5A-0zW{~Zm0Kw!g at v9lDYM4}#xGd(0O>~w*Cyj$!P$L)bdJO(VKW?sKG z3 at aX7mKcUCOM+mozjAA1VvdUEBgeox%vo{m$|MOg$DCN?h-Rm|ku4pIOq3u6j)TE( z%&hnDCC;%4zbZC!!h;S(p~2|?Zlty(wTe;QD;^Rn5916s<{1Y~w}!>P-bD=R_DH7= z>u9dxz;yDtgcHP^HcJNWU}%vB5P9bBe4$8)L`|;Jr>;*X-26u at 7 z&kzV9ER|TS7;&l78dC#&puiW`he?1juzPM&lKptWU(}o~m+~O+r|H#HlNp=NNE8Sz zfx at NKbI5JuQW+eBF|OOz)Va?dKet-W+WGW9xyn`5pOyp$&{$`VV#bM6v+ln1Asz(dbomg ze3+ADbEe6K0A9)qmU;2>h3LYO-9Lo!fj&L$?Oy~2S3#gcu=b}{!(;_O65_n-MF4vA9{T{`2?BpnAbKr^J1qdL=fwS<5L$%6gcT_*9nL8s9!j_+ zOTd2;UG1-s%;3WZqJp at YAJP`4`7q`pU_;n6=FOB>>jqnB#CaZILC!-OD)^)z+jAd+ z2}^XfY|-)XUyE#&b-I56iX<=?fvrS?Xa{S7+`1yHrJqTIfjOySwj4sj7In&tqfN*sG)t9Xf>z(l4UwOZ1B{Uu_Zbw!O;hr z20>P)fbC(?g#cQs4qrMmn-^jtG5*m?@`)bi3R4_27&3T^sLT>=pZJ<0 zH*IhVjO_z7m%?wnZX27!GyBRG&OXAjS%&=g=Cb-h9Puo(npQrD=!EhIjJ1`kg#4tT?$IAVgYD9(H&-7n33dQlhin4={G z$*xDF5o`fa26kxuk{9h(O>zcvZWW1nW${b%3E at wJ4|)Ci&ETtGFl{f at o-{deZNd=#bf&-+ zOU1#0E19r&`N3M^&~%89_8 at 350gp^b@|*Oat%tx;@mC73nlpFUk7aqVVhqq(a=O8R zB7{|$l*d182ujDpm-JktXFT=cSI1slJ}(w)CWRFgcC9TsfKUg*ZV1YBq;!7?jU}ES zCo4>Sb6P4f6I$ZTBxCTAT0z7J2l-1<_ at ST4f2krjsnApV#)=lzI>}DUNvmoLZX1Wh z$C(OGIsg1A!sOQ{z;acyy4GlpVq{(Ycr6Pl%=wo%6hUHC*j%D(cru-XJ at jY~*5O@$ zl*3hZnJ%t5wnaw7RmU+1PkO(ROxP^R4fG*AjTxOOl8u1uN)qn|b&oS>ka$F3`WeT0 zlu?MBI{52f=m%RQ*w7 at Y4lmRq(HsUB(uig?mI>Vy-JsQAH3m_DPa}gGmHB*m%=`O* z$QJ2uj|>j%3HP6=NRs$Uy=fbi49bPwmQVoc9pMH^A{X6iB|%xFGjlM|cYy90qv7(* zhwX()@vkq1?UGqtvZo0~4n~$kfYc&{N1g+bGnPB^e0U}NrV50!0+IaJOv>1Y3QuT|NZcQU_>4o|I3+3POshA| z>G0>Z$^EZ>!JA2rj6x7bVOx%@46fRFgE0otg6xW9t?-uy#4d{)ID7~XYHTou#giSn z&lYf?H;8Yz*zVZ7b;+2xrHS*)RZhXqmkEk`xRUm6pJF1Bi%!N at Y) zIcgPsA?G7ee^>FOSA$*_#`(ZsW>>q#cqST|o<>||r#&FhCv=q}7C}5 at aUv4=tOO6D zMv@=~@re{U-yvaCTKUg4 at rDPqKhga8jBdYv&9n?J=?B9Z%rg?P1#54xbc1*@EHYZU zIzm-a6Ce+r4p%z+(p^tR&__2$3h!k4cXCfK%$PB`f@%vU;GMfNvPtYRI6H*Kf+rDb zijg3~@(@HEHI2DQ)MGha at Yjw`+f{flsbP;QtgC+80AaaZus;X=VN*I*Vnw3uk4#>T zZ!9q#w<8(wfZs62mj?=CJo4Ln8I{C?AEXwiAGfJbBmB&0!H70nW=lDlo-BSg6bV=A z;?ADK6tV+hN&i6n)uDd{TVRhCjOEBBOZghXlKxAUI zjEhl)A9I{UJp@(k8Cf6 z-oGFOdTvI4%#T^MF-)mx5TMrKS;r%p_*%W7HH5HaPH9jV=mL8a5O~}k7GWvRkCD$p zl%`O2O2m~W@{%m65UBZ>;6YoSb8pc0+Z!ZkWXKr*D*_AU#K9NM!Ot~1o~@?=unT_BNK%(l<0K?w~?gsY$sL77!(us-6bc|D{p6iMu| zqn_A_H7!awuvSGDBsqPqkI8>lk9oL<8ily}xtRlh8>S at B64=5vaUn%iU_IaH^~1w5 zkH0Bb^Iu-C>EmO%|G$!~-8fLLy8WIBhRAZ5nwntc^-cj{td|r|#6 at 7#3 at 4wLFXt=dK15J{5?c zmmj~;=VeP_UDue4|{rkq at p}VLs$?vrIxT&m`Q$JQCtP5YkQ_D!l#2`H3p>) zKZ69TGcUs#L~QjZhGiZtB79t8uz_3QL4jY>qQ*T!T6!B+V9S=y=o1ubcwdh6=tWm& zQ6n-ej@(S4K)FB0#FY$j{c8bgO;BGBNn|b;AJvkv=)PjHmzS}#R>O$d+?eJ===R^3 z&HY3Q3^dgkJg9N6#qEZ$#hs=}Jhh7H+{tW2;#!=J$m|%zs9oVyow&-(AP<+hFsFqE znH%JJN4pG;O6+s-H{`g#2p1DeETAw}= zfh`rqKLv)ZVq8!;e|)st!ABQ^O)*kwlByT9dB{pW40X&RY?;**QTJ z1gnTv-2aS$aZQAemQ2b22TcsdR^7 at Bf0o2;CS3Up98D#t7zvvuW3R!2(-gA!Dzz$y zdyKKsKIy><2NdT*g at eEl71>I#^nJ;$DUziDM-R&kTwuy2dD9BqEyadIT7$)viKE_P ze~!bsqKo744?pv&G1x*_^`G==ac&3Ksu#>0SdpLCUrvaP3T0Y{W2faAOgXpO-Y72N zEFBY1crdoA;!kJhnAHsmK4fOH8<=Jz?F!s1#sHpjo^CK12R6aC)f>g8N}?M)9!2u` z|903VChPrp`V(XNkrAad$nPKedcuSGnRHad9VpslM8B0`o#t1a1y>a#)(Z;sdH{?Q zwj`-FKMDoTk7ULe_oY at a6w0JeCD!t6nC!z)`atS(z9PhYtBERrnzgdH{oh_5ITWAj92pqyKr0!F&q88iOb0*f-iSGI3Yh$8%+u zc%9v!o-Mq8A&D&~IE4`AtZvF|I6ssI3(_Wm+^DRlz2OP;@>g4;!y1Yw^ARwiClw+r zbELM8V~$i4ogiN$SbRbpuXHL7|R zmU-r1`-v^O<50IK!B4~Di^mQAz)RRGu;+|MK;r9%5MX?+{)aOU3IGrI*%qAyWR{ec zG3twHUD$&Fpj(5qWhwyniEyMr_#g`XD4B at _xU1rc40yDErhgkQjj~|X6Z%+K-wPdr z6ulp8u^^9>c at mK91cL+*ag66bq>;!^QN+f1<-_0PFXf+km;Srze>r$Cw}W89c5Kzk zt|(rq$<_s`J*bCHW05%TFm(#?V at B@>COoWZDazzNF$ag=0IN&In2gl?cbQlf016`l ze#sJa4($5VP{F>^Zp*~xPyhDKgO<#XKKT4$@Ofy*C*M_2OD$uU2gRDU)U;RSDmJLH zm}3&k6ISE{Z0mmPeq{@~D{}(E2}e%bkZzcw5Q7UVWjy#PgRVgKw}${xDX?3H<7N{I zlnC`un9rft8PQ1}xKW7cvq%d5n9+Kw1pXNZ?>hzqWr{%oTf$)VpZTSk1u+LJ2E?OA zEIj at B1;f)WjHL=^YbLSa*)CnBNN}1*w0`UWL4^vwI8|AME5zwWFr<0DR-AkJLX!2z7vow2e|}U|BJgqy%71pwgPz_`B{&u2 z@)$7f1EoR}iXl&e2Ni-VCv-&;`+%fn%cv_8#|%#K{2RPzYpD6bp`1fK)HE14M3;!` zMruL#iZ{?;3hi at TlCkI;oKloGc`g&Zq`HASK|c+E^GFtpRB+m(3a&Hku%J!K2P<1M*RSaG)ndDhINC;kQy#iKqC zuIfHJ0J-Img8%jEaN~R}Pw^wO at WMHR1#>@biLOCtW)T{?x)?m+!D^|qbAj(v9V-pa zvw)Evb%gr&Hi&;GY!t>DVs zKKGdJT$+>lJYefMXF5*f|Gdgpu|B2PJ3%vOvLS==E9e^&@IZO=p%e0|*<6SJ$behP zN^2PoK_Y at U~OmkZ at o?8W!l{&FTkcm}U zVQPuuQhy__MHUQ)dSYURIjuqdFc?Wu{yZ6R9=y0VyK!HJzo*S03b9n;FsGlPge5D( zDM@)&?Ca+(wRd!JFk(;seuW2VQhSCPNR*h?YyeagiXkX3$Eo{sI(J(4IDx}U9*j0n z;h zGZOeW0W}B8_>UgczRZKy*SwSBLb3}i%AAf2NO$Ha!I=dsbGoO9B{0LFmTeL9+6gf| zk8Tn#MyM~1i*0h;M4k^wd~<{%Bv~@UDGe4qEIF;CA>6r;&uQm~Bs#(BVRS$CYRpL_ zpwOkeul9pge$`8H0bnV^OAO}<{xm9sUN9BnYO$SxnH~{7X2Ig93v>WtATciNw(BV# zXaJAz9|?_Jnyny=K^u at fDTxu!irn-wf?Chl&o*al5E7t! zR5WeGx$*3!E-v?6plfJ7>r04Ce)sY6Vi0TNi`P6T4(yoO6SA&4mULmr*a!Xt3=FGQ at 8MSyF@jO3eH|02qK{Wbi1s zg$n1#r~p*TOB#4&S9lFSDZ_00)RRF>Yv?qA!h?(q0)U$yKpi9qUoa*kg1(>4hH0-Z zt_p*Xzj^#kzDxfT@>R)EH!`bWKiYyKN4ykkLn!iCj2X!b@<+1MO)j)UWj+;hd;kp| z9pS`7j!F+`foZw%Aqjye`GjUvfyRsiFKH2 at zhnŠ9EV_honHL^P7hxHuS6}}^ z9twN&3QfZFV1#8nHa+K)esv^bV>+S1&kvIVk#L-BfwPlX9reQ`95}+HK7f}NIzfLW zzvb?<%941MntaBF;t}fFNPgR5Lfp at 7DGO4P#ACr;+jDaU41fB;)lQ8q{OU*VpI%mr zJ9RkMWJ4h)#;fD61v~nI8d*J2#Nx>W?xuJwF<j2|oFd zp=Y*qV!^r$Hx5p>1B&bh&7Ywrp7SLK zO)DE_AjV^tI2{@J&Vt-z!o5E%t)ZQ%U=(Q4nUmbULT at 7#ZmB8UhD}k}Z3dkSpE at Ky z81N9rg%35WP%Oa%c|ZNAw;y-Oyts+NX!>~Es=0u(eKIxD3MwErc#&8IBn^gnPVC!r zfwOYB>hTH3?fYCL*W{}zyruh107(8z0UGp%g99(7!c}hd6|vQj!B1E+v}S(h!3sa= zDz$j60)zD$-Mp at P?COjFjY?vh6W1)rA}rp_g*P3)`|+v=5Fe!_JkndCi-&|p{m}Ul zcltE35N?dKppNIP6|B}t8rAse-5|E<%o&skv5!Z7)|mKbT*iZaE~E-}F%nE at s-#(l zWrfL-3IV_d7LUnX#Iq)pVVo*+gau1(CV at _BZ(3w}2>jd}Y at u-5gVRHeJ^m(3 at Xi8V zH6IE5;Q{U3E8fcbTmc~Ss{gH^5dC2331`P%Y5A6TAmTzi&g=@4Gt=JW%yAicY4Beh zu!WDKFk%$sg1(0Uue|fb)he_%*eUn;5h^7>z^{dClJr&}U5t-0!7>hg+8IeMQ`Rr z90wV$3dCT;0>4TPPtq68GOLrrR=pG_?8OSFjEI}?E8I6fqy;S7b3+1PnGsW+G+3|~ zm+_oJs>ef4O!tR$sKN<_$NpRym6Z4v&sO*}05^LY*ndM;#e?-4F$2>!qf-qcouKld z1QaGKz!b#Ig<^WsVaz;OflCLtFL=sBAP(3LWAb7edV~|ZhuJbGESMHKtr0xnCP9TW z7*?YZp5}32?rA5x$&~K-G!Fc4sUf5;UK9B8kggij%}C6cASNN!rC9uFi7Vvmum+<6 ze6SvWZqKPrSQ^6lQDkwC`W7&hc1XQt-Ik)(-&l1hGjUuYz^w_I;_H2k_zPk lI1-zF#))r8%IG%nk6oktnb*Jm|God!{~!0la2=kw1OTIPExrH% literal 0 HcmV?d00001 diff --git a/wui-appliance/wui-devel.ks b/wui-appliance/wui-devel.ks index 66927be..6dec58c 100644 --- a/wui-appliance/wui-devel.ks +++ b/wui-appliance/wui-devel.ks @@ -8,285 +8,46 @@ network --device=eth1 --bootproto=static --ip=192.168.50.2 --netmask=255.255.255 %packages --nobase -%include common-pkgs.ks + %include common-pkgs.ks + ovirtAppliance +%end %post -exec > /root/kickstart-post.log 2>&1 - -%include common-post.ks - -# FIXME [PATCH] fix SelinuxConfig firewall side-effect -lokkit -f --nostart --disabled -# FIXME imgcreate.kickstart.NetworkConfig doesn't store nameserver into ifcfg-* -# only in resolv.conf which gets overwritten by dhclient-script -augtool <> /etc/hosts -for i in `seq 3 252` ; do - echo "192.168.50.$i node$i.priv.ovirt.org" >> /etc/hosts -done - -# Enable forwarding so this node can act as a router for the .50 network -sed -i 's/net.ipv4.ip_forward = .*/net.ipv4.ip_forward = 1/' /etc/sysctl.conf -cat > /etc/sysconfig/iptables << EOF -*nat --A POSTROUTING -o eth0 -j MASQUERADE -COMMIT -EOF - -principal=ovirtadmin -realm=PRIV.OVIRT.ORG -password=ovirt -cron_file=/etc/cron.hourly/ovirtadmin.cron - -# automatically refresh the kerberos ticket every hour (we'll create the -# principal on first-boot) -cat > $cron_file << EOF -#!/bin/bash -export PATH=/usr/kerberos/bin:$PATH -kdestroy -echo $password | kinit $principal@$realm -EOF -chmod 755 $cron_file - -ff_profile_dir=uxssq4qb.ovirtadmin - -# for firefox, we need to make some subdirs and add some preferences -mkdir -p /root/.mozilla/firefox/$ff_profile_dir -cat >> /root/.mozilla/firefox/$ff_profile_dir/prefs.js << \EOF -user_pref("network.negotiate-auth.delegation-uris", "priv.ovirt.org"); -user_pref("network.negotiate-auth.trusted-uris", "priv.ovirt.org"); -user_pref("browser.startup.homepage", "http://management.priv.ovirt.org/ovirt"); -EOF - -cat >> /root/.mozilla/firefox/profiles.ini << EOF -[General] -StartWithLastProfile=1 - -[Profile0] -Name=ovirtadmin -IsRelative=1 -Path=$ff_profile_dir -EOF - -# Create sparse files for iSCSI backing stores -mkdir -p /ovirtiscsi -for i in `seq 3 5`; do - dd if=/dev/null of=/ovirtiscsi/iSCSI$i bs=1 count=1 seek=64M -done - -# make an NFS directory with some small, fake disks and export them via NFS -# to show off the NFS part of the WUI -mkdir -p /ovirtnfs -for i in `seq 1 5`; do - dd if=/dev/zero of=/ovirtnfs/disk$i.dsk bs=1 count=1 seek=1G -done -echo "/ovirtnfs 192.168.50.0/24(rw,no_root_squash)" >> /etc/exports - -# make sure that we get a kerberos principal on every boot -echo "$cron_file" >> /etc/rc.d/rc.local - -# make collectd.conf. -cat > /etc/collectd.conf << \EOF -LoadPlugin network -LoadPlugin logfile -LoadPlugin rrdtool -LoadPlugin unixsock - - - LogLevel info - File STDOUT - - - - Listen "0.0.0.0" - - - - DataDir "/var/lib/collectd/rrd" - CacheTimeout 120 - CacheFlush 900 - - - - SocketFile "/var/lib/collectd/unixsock" - - -EOF - - -first_run_file=/etc/init.d/ovirt-wui-dev-first-run -sed -e "s, at cron_file@,$cron_file," \ - -e "s, at principal@,$principal," \ - -e "s, at realm@,$realm," \ - -e "s, at password@,$password,g" \ - > $first_run_file << \EOF -#!/bin/bash -# -# ovirt-wui-dev-first-run First run configuration for oVirt WUI Dev appliance -# -# chkconfig: 3 95 01 -# description: ovirt wui dev appliance first run configuration -# - -# Source functions library -. /etc/init.d/functions - -export PATH=/usr/kerberos/bin:$PATH - -start() { - echo -n "Starting ovirt-wui-dev-first-run: " - ( - # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=451936 - sed -i '/\[kdcdefaults\]/a \ kdc_ports = 88' /usr/share/ipa/kdc.conf.template - # set up freeipa - ipa-server-install -r PRIV.OVIRT.ORG -p @password@ -P @password@ -a @password@ \ - --hostname management.priv.ovirt.org -u dirsrv -U - - # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459061 - # note: this has to happen after ipa-server-install or the templating - # feature in ipa-server-install chokes on the characters in the regexp - # we add here. - sed -i -e 's###' \ - /etc/httpd/conf.d/ipa.conf - sed -i -e 's###' /etc/httpd/conf.d/ipa.conf - # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459209 - sed -i -e 's/^/#/' /etc/httpd/conf.d/ipa-rewrite.conf - service httpd restart - # now create the ovirtadmin user - echo @password@|kinit admin - # change max username length policy - ldapmodify -h management.priv.ovirt.org -p 389 -Y GSSAPI < /var/log/ovirt-wui-dev-first-run.log 2>&1 - RETVAL=$? - if [ $RETVAL -eq 0 ]; then - echo_success - else - echo_failure - fi - echo -} - -case "$1" in - start) - start - ;; - *) - echo "Usage: ovirt-wui-dev-first-run {start}" - exit 2 -esac - -chkconfig ovirt-wui-dev-first-run off -EOF -chmod +x $first_run_file -chkconfig ovirt-wui-dev-first-run on - -cat > /etc/init.d/ovirt-wui-dev << \EOF -#!/bin/bash -# -# ovirt-wui-dev oVirt WUI Dev appliance service -# -# chkconfig: 3 60 40 -# description: ovirt wui dev appliance service -# - -# Source functions library -. /etc/init.d/functions - -start() { - echo -n "Starting ovirt-wui-dev: " - dnsmasq -i eth1 -F 192.168.50.6,192.168.50.252 \ - -G 00:16:3e:12:34:57,192.168.50.3 -G 00:16:3e:12:34:58,192.168.50.4 \ - -G 00:16:3e:12:34:59,192.168.50.5 \ - -s priv.ovirt.org \ - -W _ovirt._tcp,management.priv.ovirt.org,80 \ - -W _ipa._tcp,management.priv.ovirt.org,80 \ - -W _ldap._tcp,management.priv.ovirt.org,389 \ - -W _collectd._tcp,management.priv.ovirt.org,25826 \ - -W _identify._tcp,management.priv.ovirt.org,12120 \ - --enable-tftp --tftp-root=/var/lib/tftpboot -M pxelinux.0 \ - -O option:router,192.168.50.2 -O option:ntp-server,192.168.50.2 \ - --dhcp-option=12 \ - -R --local /priv.ovirt.org/ --server 192.168.122.1 - - # Set up the fake iscsi target - tgtadm --lld iscsi --op new --mode target --tid 1 \ - -T ovirtpriv:storage - - # - # Now associate them to the backing stores - # - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ - --lun 1 -b /ovirtiscsi/iSCSI3 - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ - --lun 2 -b /ovirtiscsi/iSCSI4 - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ - --lun 3 -b /ovirtiscsi/iSCSI5 - - # - # Now make them available - # - tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL - - echo_success - echo -} - -stop() { - echo -n "Stopping ovirt-wui-dev: " - - # stop access to the iscsi target - tgtadm --lld iscsi --op unbind --mode target --tid 1 -I ALL - - # unbind the LUNs - tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 3 - tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 2 - tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 1 - - # shutdown the target - tgtadm --lld iscsi --op delete --mode target --tid 1 - - kill $(cat /var/run/dnsmasq.pid) - - echo_success - echo -} + exec > /root/kickstart-post.log 2>&1 + + # make sure to update the /etc/hosts with the list of all possible DHCP + # addresses we can hand out; dnsmasq uses this + sed -i -e 's/management\.priv\.ovirt\.org//' /etc/hosts + echo "192.168.50.2 management.priv.ovirt.org" >> /etc/hosts + for i in `seq 3 252` ; do + echo "192.168.50.$i node$i.priv.ovirt.org" >> /etc/hosts + done + + # Create sparse files for iSCSI backing stores + mkdir -p /ovirtiscsi + for i in `seq 3 5`; do + dd if=/dev/null of=/ovirtiscsi/iSCSI$i bs=1 count=1 seek=64M + done + + # make an NFS directory with some small, fake disks and export them via NFS + # to show off the NFS part of the WUI + mkdir -p /ovirtnfs + for i in `seq 1 5`; do + dd if=/dev/zero of=/ovirtnfs/disk$i.dsk bs=1 count=1 seek=1G + done + echo "/ovirtnfs 192.168.50.0/24(rw,no_root_squash)" >> /etc/exports + + + # The ace stuff. + /sbin/chkconfig --level 35 ace on + mkdir /etc/sysconfig/ace + echo ovirt >> /etc/sysconfig/ace/appliancename + + /sbin/chkconfig --add acpid + +%end -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - *) - echo "Usage: ovirt-wui-dev {start|stop|restart}" - exit 2 -esac -EOF -chmod +x /etc/init.d/ovirt-wui-dev -chkconfig ovirt-wui-dev on +%post --nochroot + mv ovirt-splash.xpm.gz $INSTALL_ROOT/boot/grub/splash.xpm.gz +popd -%end -- 1.5.5.1 From bkearney at redhat.com Fri Aug 22 14:47:34 2008 From: bkearney at redhat.com (bkearney at redhat.com) Date: Fri, 22 Aug 2008 10:47:34 -0400 Subject: [Ovirt-devel] [PATCH] Added the appliance-recipe code from the acex repo In-Reply-To: <1219416455-18149-1-git-send-email-bkearney@redhat.com> References: <1219416455-18149-1-git-send-email-bkearney@redhat.com> Message-ID: <1219416455-18149-2-git-send-email-bkearney@redhat.com> From: root --- appliance-recipe/Makefile | 23 +++ appliance-recipe/README.rdoc | 17 ++ .../appliances/ovirt/files/collectd.conf | 23 +++ .../appliances/ovirt/files/firefox_prefs.js | 3 + .../appliances/ovirt/files/ovirt-cfgdb | 11 ++ .../appliances/ovirt/files/ovirt-wui-dev | 85 +++++++++++ appliance-recipe/appliances/ovirt/files/ovirt.repo | 5 + appliance-recipe/appliances/ovirt/ovirt.pp | 154 ++++++++++++++++++++ .../appliances/ovirt/templates/cron_file.erb | 4 + .../ovirt/templates/firefox_profiles.erb | 7 + .../ovirt/templates/ovirt-dev-appliance-setup.erb | 36 +++++ .../appliances/ovirt/templates/terminal.erb | 13 ++ appliance-recipe/ovirtAppliance.spec | 36 +++++ appliance-recipe/resources/ovirt-splash.xcf | Bin 0 -> 652529 bytes appliance-recipe/resources/ovirt-splash.xpm.gz | Bin 0 -> 51388 bytes appliance-recipe/resources/ovirtbr.xml | 7 + appliance-recipe/version | 1 + build-all.sh | 11 ++- 18 files changed, 435 insertions(+), 1 deletions(-) create mode 100644 appliance-recipe/Makefile create mode 100644 appliance-recipe/README.rdoc create mode 100644 appliance-recipe/appliances/ovirt/files/collectd.conf create mode 100644 appliance-recipe/appliances/ovirt/files/firefox_prefs.js create mode 100644 appliance-recipe/appliances/ovirt/files/ovirt-cfgdb create mode 100644 appliance-recipe/appliances/ovirt/files/ovirt-wui-dev create mode 100644 appliance-recipe/appliances/ovirt/files/ovirt.repo create mode 100644 appliance-recipe/appliances/ovirt/ovirt.pp create mode 100644 appliance-recipe/appliances/ovirt/templates/cron_file.erb create mode 100644 appliance-recipe/appliances/ovirt/templates/firefox_profiles.erb create mode 100644 appliance-recipe/appliances/ovirt/templates/ovirt-dev-appliance-setup.erb create mode 100644 appliance-recipe/appliances/ovirt/templates/terminal.erb create mode 100644 appliance-recipe/ovirtAppliance.spec create mode 100644 appliance-recipe/resources/ovirt-splash.xcf create mode 100644 appliance-recipe/resources/ovirt-splash.xpm.gz create mode 100644 appliance-recipe/resources/ovirtbr.xml create mode 100644 appliance-recipe/version diff --git a/appliance-recipe/Makefile b/appliance-recipe/Makefile new file mode 100644 index 0000000..9e0a9d6 --- /dev/null +++ b/appliance-recipe/Makefile @@ -0,0 +1,23 @@ +pkg_name = ovirtAppliance + +all: rpms +include ../common/release.mk + +clean: + rm -f ovirtAppliance-*.gz ovirtAppliance-*.rpm + rm -rf ovirtAppliance-* + rm -rf rpm-build + +tar: clean + mkdir -p $(NV) + cp -a appliances/ovirt $(NV) + mkdir -p rpm-build + tar zcvf rpm-build/$(NV).tar.gz $(NV) + cp version rpm-build/ + rm -rf $(NV) + +# convience method to simulate make install, not for production use +install: rpms + rpm -Uvh rpm-build/ovirt-wui-$(VERSION)-$(RELEASE)$(DIST).$(ARCH).rpm --force + +.PHONY: all clean tar install diff --git a/appliance-recipe/README.rdoc b/appliance-recipe/README.rdoc new file mode 100644 index 0000000..7b981ac --- /dev/null +++ b/appliance-recipe/README.rdoc @@ -0,0 +1,17 @@ += Pligg Recipe +This example will help to build a Pligg Appliance +In order to do this, please do the following: + +* Download Pligg Beta 9.9.0 from www.pligg.com. Put it into the +sources directory +* execute rake pligg_rpm. This will build an RPM from the +distribuition. +* exeucte rake appliance_rpm. This will build an rpm for the recipe + +A sample kickstart file is provided in the resources directory. To use +this, create a repository holding these new RPMS, and add it to +the kickstart file. You can then do the following assuming that the +appliance creator tool is installed: + +appliance-creator --name pligg --config pliggAppliance-f9.ks +virt-image pligg.xml diff --git a/appliance-recipe/appliances/ovirt/files/collectd.conf b/appliance-recipe/appliances/ovirt/files/collectd.conf new file mode 100644 index 0000000..0b327de --- /dev/null +++ b/appliance-recipe/appliances/ovirt/files/collectd.conf @@ -0,0 +1,23 @@ +LoadPlugin network +LoadPlugin logfile +LoadPlugin rrdtool +LoadPlugin unixsock + + + LogLevel info + File STDOUT + + + + Listen "0.0.0.0" + + + + DataDir "/var/lib/collectd/rrd" + CacheTimeout 120 + CacheFlush 900 + + + + SocketFile "/var/lib/collectd/unixsock" + diff --git a/appliance-recipe/appliances/ovirt/files/firefox_prefs.js b/appliance-recipe/appliances/ovirt/files/firefox_prefs.js new file mode 100644 index 0000000..9072416 --- /dev/null +++ b/appliance-recipe/appliances/ovirt/files/firefox_prefs.js @@ -0,0 +1,3 @@ +user_pref("network.negotiate-auth.delegation-uris", "priv.ovirt.org"); +user_pref("network.negotiate-auth.trusted-uris", "priv.ovirt.org"); +user_pref("browser.startup.homepage", "http://management.priv.ovirt.org/ovirt"); diff --git a/appliance-recipe/appliances/ovirt/files/ovirt-cfgdb b/appliance-recipe/appliances/ovirt/files/ovirt-cfgdb new file mode 100644 index 0000000..71a27b3 --- /dev/null +++ b/appliance-recipe/appliances/ovirt/files/ovirt-cfgdb @@ -0,0 +1,11 @@ +rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 +rm /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0 +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DEVICE ovirtbr0 +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BOOTPROTO dhcp +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT y +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE Bridge +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes +save diff --git a/appliance-recipe/appliances/ovirt/files/ovirt-wui-dev b/appliance-recipe/appliances/ovirt/files/ovirt-wui-dev new file mode 100644 index 0000000..54970bb --- /dev/null +++ b/appliance-recipe/appliances/ovirt/files/ovirt-wui-dev @@ -0,0 +1,85 @@ +#!/bin/bash +# +# ovirt-wui-dev oVirt WUI Dev appliance service +# +# chkconfig: 3 60 40 +# description: ovirt dev wui appliance service +# + +# Source functions library +. /etc/init.d/functions + +start() { + echo -n "Starting ovirt-wui-dev: " + dnsmasq -i eth1 -F 192.168.50.6,192.168.50.252 \ + -G 00:16:3e:12:34:57,192.168.50.3 -G 00:16:3e:12:34:58,192.168.50.4 \ + -G 00:16:3e:12:34:59,192.168.50.5 \ + -s priv.ovirt.org \ + -W _ovirt._tcp,management.priv.ovirt.org,80 \ + -W _ipa._tcp,management.priv.ovirt.org,80 \ + -W _ldap._tcp,management.priv.ovirt.org,389 \ + -W _collectd._tcp,management.priv.ovirt.org,25826 \ + -W _identify._tcp,management.priv.ovirt.org,12120 \ + --enable-tftp --tftp-root=/var/lib/tftpboot -M pxelinux.0 \ + -O option:router,192.168.50.2 -O option:ntp-server,192.168.50.2 \ + --dhcp-option=12 \ + -R --local /priv.ovirt.org/ --server 192.168.122.1 + + # Set up the fake iscsi target + tgtadm --lld iscsi --op new --mode target --tid 1 \ + -T ovirtpriv:storage + + # + # Now associate them to the LVs + # + tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ + --lun 1 -b /dev/VolGroup00/iSCSI3 + tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ + --lun 2 -b /dev/VolGroup00/iSCSI4 + tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ + --lun 3 -b /dev/VolGroup00/iSCSI5 + + # + # Now make them available + # + tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL + + echo_success + echo +} + +stop() { + echo -n "Stopping ovirt-wui-dev: " + + # stop access to the iscsi target + tgtadm --lld iscsi --op unbind --mode target --tid 1 -I ALL + + # unbind the LUNs + tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 3 + tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 2 + tgtadm --lld iscsi --op delete --mode logicalunit --tid 1 --lun 1 + + # shutdown the target + tgtadm --lld iscsi --op delete --mode target --tid 1 + + kill $(cat /var/run/dnsmasq.pid) + + echo_success + echo +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + *) + echo "Usage: ovirt-wui-dev {start|stop|restart}" + exit 2 +esac diff --git a/appliance-recipe/appliances/ovirt/files/ovirt.repo b/appliance-recipe/appliances/ovirt/files/ovirt.repo new file mode 100644 index 0000000..c5d60cd --- /dev/null +++ b/appliance-recipe/appliances/ovirt/files/ovirt.repo @@ -0,0 +1,5 @@ +[ovirt] +name=ovirt +baseurl=http://ovirt.org/repos/ovirt/9/$basearch/ +enabled=1 +gpgcheck=0 diff --git a/appliance-recipe/appliances/ovirt/ovirt.pp b/appliance-recipe/appliances/ovirt/ovirt.pp new file mode 100644 index 0000000..6c8a3f4 --- /dev/null +++ b/appliance-recipe/appliances/ovirt/ovirt.pp @@ -0,0 +1,154 @@ +#-- +# Copyright (C) 2008 Red Hat Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Author: Bryan Kearney +#-- + +# +# cobbler thincrust appliance +# + +# Modules used by the appliance +import "appliance_base" +import "banners" +import "firewall" +import "apache" +import "augeas" +import "postgres" + +# Information about our appliance +$appliance_name = "OVirt Web UI" +$appliance_version = "0.0.1" +$ff_profile_dir = "uxssq4qb.ovirtadmin" +$principal = "ovirtadmin" +$realm = "PRIV.OVIRT.ORG" +$password = "ovirt" +$cron_file = "/etc/cron.hourly/ovirtadmin.cron" +$ktab_file = "/usr/share/ovirt-wui/ovirtadmin.tab" + +# Configuration +appliance_base::setup{$appliance_name: hostname => "management.priv.ovirt.org"} +banners::terminal{$appliance_name: template_file => "ovirt/terminal.erb"} +banners::login{$appliance_nane:} +postgres::setup{$appliance_name:} + + +$changes = [ +"set /files/etc/sysconfig/network-scripts/ifcfg-eth0/PEERDNS no", +"set /files/etc/sysconfig/network-scripts/ifcfg-eth1/DNS1 192.168.50.2" +] + +augeas {"network_scripts": + changes => $changes, + notify => Service["network"] +} + +file {"/etc/yum.repos.d/ovirt.repo": + source => "puppet:///ovirt/ovirt.repo" +} + +file {"/etc/collectd.conf": + source => "puppet:///ovirt/collectd.conf" +} + +file {"$cron_file": + content => template("ovirt/cron_file.erb"), + mode => 0755 +} + +file_append{"add_cron_to_local" : + file => "/etc/rc.d/rc.local", + line => "$cron_file" +} + +file {"/root/.mozilla": + ensure => directory +} + +file {"/root/.mozilla/firefox": + ensure => directory +} + + +file {"/root/.mozilla/firefox/$ff_profile_dir": + ensure => directory +} + +file {"/root/.mozilla/firefox/$ff_profile_dir/prefs.js": + source => "puppet:///ovirt/firefox_prefs.js" +} + +file {"/root/.mozilla/firefox/profiles.ini": + content => template("ovirt/firefox_profiles.erb") +} + +file {"/var/www/html/ovirt-cfgdb": + source => "puppet:///ovirt/ovirt-cfgbb" +} + + +include firewall +firewall_rule {"ovirt_nat": + table => "nat", + chain => "POSTROUTING", + out_interface => "eth0", + action => "MASQUERADE" +} +firewall_rule {"ssh": + table => "filter", + chain => "INPUT", + destination_port => '22', + action => "ACCEPT" +} + + +file_replacement{"nat_forwarding" : + file => "/etc/sysctl.conf", + pattern => "^net.ipv4.ip_forward = .*", + replacement => "net.ipv4.ip_forward = 1", + notify => Service[network] , +} + +# This was wui-devel.ks /etc/init.d/ovirt-wui-dev +file {"/etc/init.d/ovirt-wui-dev": + source => "puppet:///ovirt/ovirt-wui-dev", + mode => 755 +} + +service {"ovirt-wui-dev": + ensure => "running", + enable => true , + require => [File["/etc/init.d/ovirt-wui-dev"], Service["network"], Service["httpd"]] +} + +# This was wui-devel.ks /etc/init.d/ovirt-wui-dev-first-run +file {"/usr/sbin/ovirt-dev-appliance-setup.sh": + content => template("ovirt-dev-appliance-setup.erb"), + mode => 755 +} + +single_exec {"ovirt_dev_installation": + command => "/usr/sbin/ovirt-dev-appliance-setup.sh >> /root/ovirt-dev-appliance-setup.log", + require => [File["/usr/sbin/ovirt-dev-appliance-setup.sh"], Service["ovirt-wui-dev"], Firewall_rule ["ovirt_nat"], Firewall_rule["ssh"], Exec["reload-firewall"]] +} + +# This was common-post /etc/init.d/ovirt-wui-first-run +single_exec {"ovirt_installation": + command => "/usr/sbin/ovirt-wui-install >> /root/ovirt-wui-install.log", + require => [Service["postgresql"],Single_exec["ovirt_dev_installation"]] +} + diff --git a/appliance-recipe/appliances/ovirt/templates/cron_file.erb b/appliance-recipe/appliances/ovirt/templates/cron_file.erb new file mode 100644 index 0000000..fea7f4b --- /dev/null +++ b/appliance-recipe/appliances/ovirt/templates/cron_file.erb @@ -0,0 +1,4 @@ +#!/bin/bash +export PATH=/usr/kerberos/bin:$PATH +kdestroy +echo <%= password %> | kinit <%= principal %>@<%= realm %> diff --git a/appliance-recipe/appliances/ovirt/templates/firefox_profiles.erb b/appliance-recipe/appliances/ovirt/templates/firefox_profiles.erb new file mode 100644 index 0000000..cd483c5 --- /dev/null +++ b/appliance-recipe/appliances/ovirt/templates/firefox_profiles.erb @@ -0,0 +1,7 @@ +[General] +StartWithLastProfile=1 + +[Profile0] +Name=ovirtadmin +IsRelative=1 +Path=<%= ff_profile_dir %> diff --git a/appliance-recipe/appliances/ovirt/templates/ovirt-dev-appliance-setup.erb b/appliance-recipe/appliances/ovirt/templates/ovirt-dev-appliance-setup.erb new file mode 100644 index 0000000..1859ca0 --- /dev/null +++ b/appliance-recipe/appliances/ovirt/templates/ovirt-dev-appliance-setup.erb @@ -0,0 +1,36 @@ +#!/bin/bash +export PATH=/usr/kerberos/bin:$PATH +<%= cron_file %> +# workaround for https://bugzilla.redhat.com/show_bug.cgi?id=451936 +sed -i '/\[kdcdefaults\]/a \ kdc_ports = 88' /usr/share/ipa/kdc.conf.template +# set up freeipa +ipa-server-install -r PRIV.OVIRT.ORG -p <%= password %>-P <%= password %> -a <%= password %> \ + --hostname management.priv.ovirt.org -u dirsrv -U + + +# workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459061 +# note: this has to happen after ipa-server-install or the templating +# feature in ipa-server-install chokes on the characters in the regexp +# we add here. +sed -i -e 's###' \ + /etc/httpd/conf.d/ipa.conf +sed -i -e 's###' /etc/httpd/conf.d/ipa.conf +# workaround for https://bugzilla.redhat.com/show_bug.cgi?id=459209 +sed -i -e 's/^/#/' /etc/httpd/conf.d/ipa-rewrite.conf +service httpd restart + + +# now create the ovirtadmin user +echo <%= password %>|kinit admin +# change max username length policy +ldapmodify -h management.priv.ovirt.org -p 389 -Y GSSAPI < <%= principal %> +# make ovitadmin also an IPA admin +ipa-modgroup -a ovirtadmin admins +ipa-moduser --setattr krbPasswordExpiration=19700101000000Z <%= principal %> +<%= cron_file %> diff --git a/appliance-recipe/appliances/ovirt/templates/terminal.erb b/appliance-recipe/appliances/ovirt/templates/terminal.erb new file mode 100644 index 0000000..e5fefb4 --- /dev/null +++ b/appliance-recipe/appliances/ovirt/templates/terminal.erb @@ -0,0 +1,13 @@ + + 888 888 ${g}d8b$n 888 + 888 888 ${g}Y8P$n 888 + 888 888 888 + .d88b. Y88b d88P 888 888d888 888888 + d88''88b Y88b d88P 888 888P' 888 + 888 888 Y88o88P 888 888 888 + Y88..88P Y888P 888 888 Y88b. + 'Y88P' Y8P 888 888 'Y888 + + Admin Node + + Virtualization just got the ${g}Green Light$n diff --git a/appliance-recipe/ovirtAppliance.spec b/appliance-recipe/ovirtAppliance.spec new file mode 100644 index 0000000..8367643 --- /dev/null +++ b/appliance-recipe/ovirtAppliance.spec @@ -0,0 +1,36 @@ +%define aceHome /usr/share/ace/appliances/ + +Summary: oVirt wui Appliance +Name: ovirtAppliance +Version: 0.0.2 +Release: 1%{?dist} + +Group: Applications/Internet +URL: http://www.thincrust.net +License: LGPL +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version} +BuildArch: noarch +Requires: ace-base +Requires: httpd + + +%description +Thincrust oVirt QUI Appliance + +%install +rm -rf %{buildroot} +%{__mkdir} -p %{buildroot}/%{aceHome} +%{__cp} -R %{SOURCE0} %{buildroot}/%{aceHome} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%dir %{aceHome} +%{aceHome}/* + +%changelog +* Thu Mar 26 2008 Bryan Kearney 0.0-1 +- Initial packaging diff --git a/appliance-recipe/resources/ovirt-splash.xcf b/appliance-recipe/resources/ovirt-splash.xcf new file mode 100644 index 0000000000000000000000000000000000000000..cc9e993d09808be57f72f6780e90373f01969646 GIT binary patch literal 652529 zcmeFaX_Q at 8b{=-mbnklwfb3=uYBt&3?6!rPXDGIm*s)q!nry2{YLS?yDoll`FatH9 z3P52X=6NIlVg^CPk(O*(vZHv}3fUS~;>6t=Y^{|nhmjQh at bV%-09Eh3`)=j?_Br=e zacDV?25Ti|z5DR+?mp-2{q1k>bNhbPxU2g%YSzA`%RKWrG40#1eD%uK)m!>j at H<$mLc`W=1M645)VFcl$}L-0 z_HOIz-?+80u3^>c-v0IdTV86AKU%k}?pgj)M_Y&d_Zc5L!>L9L?fHzM`T3WA at COdv zwRO|--oA~iU+V74C#-82_^dC(cdgqu_HBdny=zv|>s|e;R&8Cmt>z%U8u;{A{+o{v zB_82F^-rVd>yPrkP0JP0o%wSscfR!ZmJh7l^4B_P660WsCUF$PXWA9RXZ#`QqYeL? zfg}GsbK2)w(>_0o&zMW`{P%Gr)6V6T;JV>I-Qz#hIEDH3LB#OuIQ}J$Kfv+FAGa~z ziz4$r4#a6Sj^}WE3r81 at Z{v6sM-PrIIQHQ!Ka~%IG zj(>~egD86OXK`ST7cs|+nBzsv at gnAU5p#S^<9H6ow{Udf_%@DLarEHWf at 2?!GdOPH z_$3^_j^lec-pBC>_-Gu@;rJGgE*#&+ at hXlU99wYg!*K at 3EgZiT;lI(({paI9WALYN zpfEeiRv%CYvV(m4(db8_boH-)Fli>UJJH{WsxRJ|)RWl<(T_&i_xNc+^f#ldkDvZg zG%Lbi8>8$4{`DKt%qY9dzux6v*P(?}Uz&`XBFywI|FVvM$=>J5PV}1U_a~!oMEW~P zlnwK{A at riOYjQGrF{&PUAM2+d+>qRRPoNQ{ z at 5AExDC?h8lhqG5V%wxsX^13%!z*(kD{#(p4 at n1&~!1#OKU?C08)aFVd7C= zTkg-NtnP8GA5>QV2Tx9wRr;Nmzrz at Z`1SP4I{F!vb at WfGtZyUCk5^Vd=urKUf6O4i zPnz{}jlbfRwUx<|%Gw%5C+M(L){i2HKU-z3`pnAO&K0(?Z2s<_wX**9|Mx5F)mml! z{9jaMtz|;~-)&f*s4UsAq^yRyD|MoF{$e()sj_nywJ8`iIWe8ZZ@^HUqr)P at zx>J#UstbX8z^&7Pfi at RUt+JEGR zHMOJtRoJk8 at 6WVh{da$c4XZHuUqpq;zZqrSaN)-yH~FJDo{6$aT-sqT9OY#mKfB49 zQS~yf=`22}$!2^r1Ao30>B$72>+t8-q9U$9tbBeUnmK9keG%V(6 at P+N&tu=!_{d!x z7jCOiq%o{D4*!+KOmIo1FY0`pQrB_y5#aqVA}4 zesc1B^wazFzU-&@*ALK}qJI9hAGczvhkh29XDHS0)xP{4%~pikAHjl7NN!Kg6Hai2viD^j`GJ ztFOqF{q9%QtLl{(kvQPbwpuxcj*97V*?0_7eam0vI~d at Sew4l+pZ+y+x|y+j>`PoR zewW>g7xvR8$fPBn^k1^`aaN=QmY?{t?z6sOFT`xW83w|JZRtPs4X?F7ZWF%YB`zJn zJZHVlpZd+YPucLB>=3SRv)`VYYLH+4_9wpN|KutE8m^9il2pj8XGGZr5dO=;+RA~0 z2c~HI;=)s>7QR at c?V at +|JK3TdZBOy9r#?a3 at TJg}|62oO25pznba9|9=_$1R=91H= zmwfa8F>Og>PuuTI(f03rnzkE{y!!)a`yNgRZOwaM{uE_j_LO}kr|eg1l-=klEAQ_` zV5a(9k*zbH+pFKK7*h}x$`s{n(e$V&W()Zte at 4a{5Wt)9DdS!Z>4V(>a_A8%m zv!CE>Y4 at wIy!z at ZD1T|Rf6=s+M*HVT+fS49)8G7M&^B+KsI5PRwy*wajn?40&0rtw zM(4=7K~Zc$5w7Ou;3A3_Q(s1ld?9 at P68_9Te-VZ+Nc+rwnnQmCY_#$XFK~Fq|B35A z?2(gfl#|@wab?D|HS~l=9F1LeYQkulYXx8Ik at mT30@VTi;*iXNzk!y3KW`f5!D2 zuSm1YQXM=$0~guY7XK69o&Q>t_Wa at _4KKotVDodB=XL>$) z<_C@`;(rR5$KM*KA=bb9mrnL;pD#?c;DqeMla*f|0;ZLz at K0DpKE#~_0YPSZpxF| zTcF!PLd1dghUVfw)B4XrJ;f~kzt2)nO at 1^fA?Y({mK6lsk&;UTDL-<|Kmt)-W91cAn9l(zpLvq`&*!NdF0L;neMoOw0Eo^PL%y*}Nk%XK at q% zmq#M=`~Op9CjPg`zVLEn{}%3|FP$CPz2A=P?R}AbuN>Jw*%G`9F^0|MK^uc*##j at t#+r7@}+Z&;KBb- at g+j_Mb(`?1xeE z(@Udd?LUo at WB)oze&G+Iv9XD@!#9zCjX at _C+cIFaqe|In`N-Gg&_Pdd71&!`zyBh}H7v2TpU81 at t= z_!`|a>PFKMeE+qvDpu3`(!KlWO1fw7-aTed6$W-xkGwdh_GM%H%sx#|=}oqGkK1E0 zX*xPGGWPW`v#$(4G1l%i_`W-bZXC3{3z=9A==q`PAZ$4uj2z+U-wsEavdR1n@>IPIGF6 zqj7Fx9|Gl%*%Ob(BkAsu-D5vAmh5wTtMa9+YDYP$DyPCopOtJ6z%UGqX`_+2{skYO z7ED6=0GQoVK=68ZIA9fIRhYR?3~-ZP4q0<0OqgEu3)_3kerXOb7d#RV-~ zH6vxXikIH~dB2+5H_i+o7A8IOXxfp&?&k<1(qNGSCe)0&kqSnR>^8d-GA2R1K#52Q zk`@W{FjrxucBjMe_c`Gy;q<`3NtneW$?j};?DLWi1JtbNaEvvcVDN4X9o{`W_N*8% zA>fbOJC1-EEa_+SNC4h&Rcwt=Cf-|SEDVI~1tM;Q4w>D082p%(gUnRK$ig&&*pWvt z1bD+(9B*SY#)|uDHincR9nnZvHnKb3ZH8x!l}JdyrvVcKBnGmkM#4%A1oK!J3e1E` zj6!W$A1oH6orZ(Ypd#Xs*kBaM at 1fVb3&VBOBOt|*G>vuLNGu@^o8fF|tl+K8z69Ww z2^zUpgjd-LGyPO?#yue<-s$_sAVgLlx?u1h%V|rhSVFEFYA}*8o$ zbmFHOj)$^Aw9U}TlAJM~>&6$G_5SH$H>3t_j;!ar-t!T$--E^BAL}y__tfC{&>-0( zElmnlqtxQjMDq9q at DQJ&!J$Dls6rv2Lwi`62lNKz>&A>0P_a&yFAre@;0_?R%zkK~ z|M8rBHj3~FRoOnpNMmFPRB2TS|t40$tThwLCW31PhgSFL21^*F_vcvuZ( z!$TUT(l41W?aPu)hHS)3p$(u*&>;nf>gWslL?!SnA#Oy>0|%#3b#`|Q4rGHMnI6mr z_E~t~0>7kC2p;*RhxJfO^xU8t5G)KD9eK;Qe8+IodJR_hp;ZC49;^-w0=@}uORU&B z)E3KGmgn3y56=yz1A_ywjX`qm10-!mzx*?c(c6m>o?+~_T8LGyc>Vc39G-Ga- z*xN@?xJGDegQt3kp1MIhATx)Z*~4m(iUOLG%v!?=jt&kOAYtLEY~JjPp5uDBKs3oS zH()_5jjh#l){T-iYzH2DHB=oQ8cZ1X;D8omco~|g-|uQLnc)Nka0As{1E?(SVp at 7O zCSwz_LlwlpMY92bt_IRw0|VHm3R5jK+l^|Oz|!cByN6>8Ne7Wq$;-3Fw))IhZBn-3 at 6dHBvd5Ad=N)Z6NQ_V#^q-`&gZK78q+c}G3S-g%%N zSbsv_uim?VPv28_zy7d_VeTD^Q`P%#-&b-*;Y{`3-G?(CX74y(9wWp1WJQbNB9 at htEH>h at tWhL$GfrSkGcm6=%Nk z&|x`;l?aPNOeK!Ea~o$8xLw5vg|jh6=sVThcOL$b%vHr)9yS9X8J4+g?|k{8dMA5` zcw at wC5wE#B?x(}RFC!NY?x%#&$a)6RnLFzC7yLMjT)4N5Eavd_7X$nO4+9AGy$aU4 zcURx7-nn~6-AQl5)h}RX`yhQA!PnAn#3_w1%M9nvZFT!gSfvVxxQGQhJ{g?H-Z5Cr z$XWA13qrAv_qM)Wy>&Y<0nss=GiF>D%R7w{K&iXGj{7nahwg9$g()Ft_!sXRwBS5Wnr`&d(#$h|}Tx zEXfc^Pd~|Q5{&7|ZUZsSkln;O8Vd+!xu*u4O~L3p`cA^2Z>d{#V7Bm10kf$hnI5s> zY%}u2lb_>KC-;$Mb-(1x;jSeU#Xobadh-?rSdc-3`{TiaIej8FFvH at G^A=%N5nihY zC0~D-RNc$&qr%MHGVI~6x?^wKTjpk+FpSI-3ypYWMj-$hpl+qNZ{50SZWbPX=t~oG zV_Fc0sW2e9z2idc_GWhDp?jFp&;TnYl_ja}r7Up=epHb2J79>!glQ8vpnbckh_{~R zszzE6md>c#r8~^_?OXbml~HU$(zw+9l$>IBkz6>$W^TEg>IUF&NE#XJo$;M0z(L;) z@@5I|O$B=sT8w(YdJKi3@~(j{2k?v_=WeDqZr*sPe5 at +(n7;PQLiBry%pcz_AvL$K zg2f6B*)brZ at 0>1g5x33l7#`~z8t3V*RK4eeZ@#|rV!`kBtr)4ZH!5H~TL%Z^eJ8H< z7K5}{U_}^i=o?B_mM&>S!=Qz`H38Rd-as_!#`s$|WJ(r%=neXQb`OoS0+N$%hV6s- zv$vk!L{uDj^M<(*0|8&cY<#}W11yD>G?h1XGb7INjp|zuAvT%^Pe>!Q1oj0bz0bEO z-x!qS#UfDi)&w_&2Y9inbe{0Mv1#v>MV*Et(mu-yw&ptb&RE*@huk7j?~IXYQ(&37>9L^;hBIFWcM34q*jY5X zYDPN;J0Xs`5x=Fcv#$a+!R;QmAZ&$F#=drJEhKwg!A;q($i#pzJA}TKNG9EN3k!{G zhYj2pmAaiW12)hRq|yN;dp)}bgi>D(F?yJ~Q(-*l+{_jVkqmTisq0U!UBAX06MSx0 zxH`b$ITd*8`;I5!jkgrDc3oXp*Q(dK{}}15_azMqXgp@?5o#de?3d&svvFNxOnMz_ zF&37nM+H1$7%osZ&$PX6u007<=j9HsQS_ZSbU}q_UGOBmcK!M_b&ah}x|&5tW&j!x zda!G`X2j}RcKw>UmUYkh<{Z44IcfKtIo)x0*43ThZO%*lR?Sh}YEHF#PIsZZ($$S0 z-Bs-lC(Rt)opDIYGe!);DJ|n%x9Uo}ySlnHK2$h*&e#1is=G9Y%gyPwTu^oEF5PXq zzRorEoC?05gF%?w_|vM3CY)=|3$$@_W?@Rx?Yi)nZ(y-9N6hJV-Q})suBf^TU0?IF zscthT>HdH|(gitXxx$;O3NMR>B~l<{884GR^ZynDPlox+jPQ11 at x>on4)CerV29;^DJW zM4SxK``w8MzBuQxKTWr}0 at nuEoI&D?^JxUm>CB*?mXI zU-E=Xe0anfb9qQHRCg6PusCNzqQsjRa(UHVl|aG*VDGbZmp}3b_F^6*R&`yj^K%4& zsh8X(Qwz<27im{#*PK~X1OP)x53W+^Dg&ynu1?eWc~2Xpf-9Kr$J5zWz%&`HzK}D) z2)&3FOGt|+aaVuYFusgw at zUK-0|+v2ftE%xiaA}U2 at RnJ1$j{>2;E(_>v6s~eJFM& znAjKNOwVqq0m%Y7BuqSy0I>13%T^LP<;Uv=4aFnN3Ly7 at UA3bV=^hX zbPp>|XtaR>{RI*oSb!v+cUE`o z+ at W@4+o6HTy~q6isLSt<%6(5RQr)?0r`;Lv&?DoxLC6F(a3IG0$(=iQ z?b?~_)H_PscgQ`|d_vxM4EM;#aVK$bz~4`WBLlniu6Snw6UVqon&77Rl%IOKpU(T~ z)r`7xbIyN(MAs?!L5$~-!4Xp7qNTHb!4UOBfAqUf;-UktB8BmYS%{t zghXCI+U-cU!&+^Zd+S~>gm at L>hWS(Cfthhw_#b;qvmF`(k;W}}i8tiZ_`A$NPVLNg z?R3a}1y1bPuD93jMnfb7=G13!@7CW8XX at RqL_*a&s<5})6Wr>x-sJ*j6-e~1!cHUS zW4P*tBafZ}4uKLDS*su at f}pog0D^7Xw`~I_N+LAX(#;$9 at PQN=9B>1ASJ?xicY=6+ za7IVeHegOp<+4i*1Q8(esey;uZn%uB0Xr{Xcv(OVmV|)3-*0yn;kw$6DcyFxtrGM| z2fx)oHaNiP1hd-m*>(irwp%X08`%&U!2&itkdoFqkl)p!^-vp3pKgUKyzTFGlvs+( z211z~MFf>?-=??OP`Mt967t~_67NdMBZK(j?a4ML^oKCOb(KuU;GY0v&u4O{yaPaQ z-xk9lEpMX` z2QkY_E&`jy^wKia&a!v|%mifk!v(f)Gu!l54gVb^1>Y_7&Xiu;9R+|}+PZDqR;YXN z5YtNK1LO@%u&jPSH39x?<&WAL`cE!=6ws>Njxvjf5P$_jwykcf9V&re{4r2IOs!2N z+pFS~+N$6fD$=VS#L@*6Tp^WIjIco0n^*6 za9D3mw`|>tswZUAIy0N5LN^4@?T&G}FS%3ODiWvL3V9c9=iN1#KC?y6^pRYWDc#80(QSAbC zBx%VG&a1Xowr$w~OBm=P^hlm_(wCA^z__ydSpf(T)3{wLYnK+j0Ib>LY3G~2j zHSpCh4+ReGCI$n|wrnelWovcIR?dZ)e3_VCDVuI#XX49549pg6YF4_kmQqoH&SZai zCz!HhyAc+|+mbM)skMIkq;{liUl!yGRf6^5q~}1qMQvsi;PHP+9K at Y3vKlX0AwZxc zEQf?W-n<1r;y7zqm~M8JFpeXo<%P_#Kb4s|^2co%$FR+Sj)_aK1x)1({3R)girJz! zr<^_>$aZ0RjWDF(5eoyx_5~)#+u}SScC*y43EMsisY4^vzJhg5AdkD+l5XCzWwYLF zL-u at 4Y1F8!;E*Oy`HaM$e$$HSE at 8OXiKa!Lt#2Z{WW{ zjU>Vk4fxru)Rv4K(3`U at n-ym_dDEC4g4yvT6cr2DAZ&u9@?q(w&70Be3fQS)AvECj zr2_KM6~~!bDyCI;U&A-$Vq48t_iY&~tbOXC3Hp3vH!>JKi_mvbdccU(;9PH|-VNM#imG-q3eYUmo#BGELsP&O5y7 zy1kZOy>{)YzG`4wy?h1whrjQwf&k!kBTa!t;l?oUC7bJI-od?g)m^Q=dG$^GroN&s zSFi8}u4sw+c7-C=%>=j1)mteSbGSJiU)67_E7_Y at 3>KnGykVYFVbPEytW1sD at YmGU zj1HAC^_y4TRN|KxV?_y22*clVR=7Ew(H(cqVs3ZkgUeSgUxp44?q^dqfcggALhRIa zja%T?t|o{zhR2j%sYsRKATZI86)L#7e*KyTbn4CQ>YE0UWtXq0D^D(8;>~ygn|c%F z1r0Pb*H_o87{vH!uP#r(iA$H at gsn-271$F$A0%&Sm*L3OYgg%a^$HQ=%U+LEk}8{1Kx3451b}gV3*7Z3 zebM92`AbqCo(>b9=HBrVR=EP#xlVTZlD?E)ymV1?sqBU*t4~>0h*s(<_H|{D-vlw3 zOXZ7~;F{Pwijckpp|Lo?POe{NCDjmT(CU)AC`vGRqiWow|Gu;pTo+Hw#q0uNuBj%1 z5u1RI)`6H#0tMt34d(O$nQs?NkGfW3Jqz+|8X7oW4jxo4+(a at pl37A@blYAR1&5XD zguTIQa=>1!T)23F$uEN|FgJOTTEv+$N>yXLyJRj-T)23V3*1ORW(yKJ;dzN*3)&uu z0z$;3+UH-3WlEn7f(xnCmt!DiF0h>l1T0Wdj}*8fYx`=`SL|hntg8zONBKN@;!VhF zlnWKQz!XQ&12o`3ZC)Ce{2_%G>;-CMR3XQuU3l2o30a`=WqT>Us4t|Pl~S5l-+YVk z+_so9!IaE~x>yDPfXrNAQ^^Og4-yC~^LaxHPcLv*gT*a)!@QcjM42#7mtDH3vAjB; zUO0aqEhpa**g{H3u+M<>VLHncRI$9ipwDyh&?)IIx}^}py!n)+!gP at R_L90dK^*9} zzF^L?5sF at D;B?qc92`@Z%Pe%3IVGjA{#v!LB>iROo-o>D74~2I%VvpduG)h*~(WE>tgGIPYMFJt&qvh%}dt zD|9!-9sROFn*h%-L|-VJH|L(BMd8QqbT+R^+5IKGR$R6h?D_Z{TLW7UInG`tmB!7= z$aL97{{OJxf;tN)*l%i)S!p at aTuIo#e%Xfx_^=Hp>S5Goj7IDd}or{~U|MgDjxP?7!12c&(srn|-itIw-*85b~T zSrh7N6-5zHLn9;64M=VQu!I*QJbN}fgUPLwoZqjL%Pa!c0m3r?%bbhPR?ZMrKz4P) z=QrffkGBu=rgQ#)+QODuwNJ{OY{&@%^(d=&z?Jb=Iq%snBG?<`9MDu z*sLy!9NVl}dN$ zPUG*ItM+O~J8q$mw|8{3x8nu#prAmPp%R6k86B=2W7^ZU_U^8k`CWL6d-}L_&w1n) zyy;Nw6PQeX3hfkmOQLU3$rIVDvm=HLRGD_DRWLK{)wcFr&s3ddFPm`Qe9jpirakLu zw|s-Eu%?do)C(ZgS*!>>PzgFainVz)=6CH>n{eZQhF9jG?buE(kFaor8M8J_>%5c) z{edzQr3^@Qu+o0F>Sxn51#vpYurh({Xph^&Y8!8F!`fb`5nWJ=wk1w7Sv4mKoP>qMVFY~<`79Cj%Ey#)Y6a|y z?N8d;+uGaP+Bi3uN0lpRH6?gJJI$ct_NOqUEiAq84#FUQ-8loR3!DryCjS^7{~$t? zsF{FgI%H8{it$DUVZn)b`^?Ii)Fz0JTw)dte@#}PsyMoq3G zmr|~fv{z(S1)tk&Te)4*nt&Y;-w=;5ug)h*9BOxjRD8ie-6o3b)Cx1i5hhbe<8oXz z=htnb0uue0%u>_BMA5Kb at MhZSy>6S~DJp%x1{2X1TH3Vf}EIBq)T%TUSf{bcQnXb6)8QT~r1%GzJr>vvB*lycOoJoSPQnLU-btc|7c3{!z)k5-8JI z5O_XM*pYtb(*YI|IBRP)ty2568JB$WO~CQ%)qt01WC&r^`7rJTp6{#=-(}DlThW`6 z)-wLdZ)6I4Yv=-Dg~Uf~=t}`u!cPL*)*7Z2NOi}v?T at 7}0;9ogf`pgC+(m4y70lPs zKFbFskrmozww7C@;(gc&h{h(#1FMN^k{T^6)vZ|z{6j^`RL=>P^aXD3_?(ZGHbyc(m?W&!fKXiz)FVrVy%dW7~Ucy zQP$FUB#wfoj-(4Ci9gD!tt@$idAX$(KY1sZNo$8Lpj#&-T`;R_RV^9Gt=gXmjzR=j zh5Zi1PTD^5*_Bz_T8kJGoELc5iY7c0XslY{F<^Zb0C6%YS at Bld>9n3v$U*TTT83y^ z`P&;6xfSmv8Jo*Wa>scq?>O^T|K`n`EN+)?>Now_#(vdrHco8VxB)LpIZ#$9blPn( z<+9C`S!9IXr24Z>{jT3_tin`ppfXUQG1Rld)+e~9jrpM_ZP{$(uC|sXEbQHeZ2k5a zYK+MRBHk at GoAsuN&A9#D4^KF4x?$tS4Q2yw#H;Q9UM7YPNTCWdv=A%`upr%XtDc%Bsi#BFc(ys-Mc!OR~Z9ZsuDV?TlreHe#7Jq-bvA=(# z#=I$<#>{GckOqP%BN7yKoMe;fuOPPmjb@{!7Ya+1H>{`TndvQ0sg(e8vUl(1%RWxR`?&;tbg$38=s^vbc)4&3e5KzX(Dh z3gS34D3EJ21eth>lzM$eJGD-WP-wx$l5m~M6PVyOIh2u~)NQcq?K-bGd-+tvP2)ME zHdPP+)E-P;BIDsk2tloXj1bnXU$;*5S>>z6Ziz!Z391m7Hm3c;c!Fzd5oBxOJ%1wr zqF9hTrEnQ`9r7k2AQS7-wI~h+)fi$Y^)s(@OAb z)!L^_0lBedv)vp^6J*ZDS;_)K%^9^mTen`3d~36H7 at 79T3I;VN&BcagqzQ$>Q?i)z zL-bmVJ(#o#`h)29WS!dvH zMtZn4RoLp)AV|1+Q{s^Gb?#eSjKr(H%39*);CFe=s#S2wGg*Vl9%k`}8zBJEYiA(a z@#+})D*JpT=s6BtPXtxFW}Hs0TD^*NNH7Vm*?>Zv_h7Y2> z=B&j{^^}X~Q`M8FPU at 4@6DLoo6AE&yR}N at B&Rg*3s2ixWRmc)bmawPoDQ~Y&l#h#A zs3jKOhF4gk%n at hLm^0aF3^Avxr|1xuQFQ7!#VsS6GsQ>>6i;Lsgn>o$87m`l$6|!x zbywVVz)EwR23XbOCyoQKAg@{qvLetxJEM~0fUfqAX{@4-KRR*z*l{cr zQckIt11P>!2munw%rhf8U`FTEF^#l80SI2aJ%7$Y1M;C8q+3F#Kkj&n`61(#ya)vi ziV}^_1Nmo8S1|XflTM8FG5qoh)pE{U;}i0%py~)asz`>y7-FkV#wYCYN5_sIJ9Y^p zIOxK83k@=cE9vPoXHM(W25EEPPzhGB!*539s+AHgOhE!lk!m>NPL((-cpL~g+iV>lZha9xAWC73vnB&LHG3uri zj at 8++0FE4~)9TbmQqCv=_`^cWj^%kuGw63%jce?Ft5cvB*GvpG;yWSn%@7b2k+my!FU{7nL}_w2F&kuzH)L?3rp zGd*_nC{#J9?w;g6e3fS)gwpc_Y6d3CR$`FS!YlaY&A|;S2+%Vj~bX? zI3J4|8>Y0N^*GQvb;85NI#9<8$LvuBn5?9lO)PH}zWg;-SI5en^w?2#bo|KCBbYdJ zC3W_x^cRvt+L8^OkF2)Gs&E8A9P!j}wX#&F%k0POVA)B$sgns at x5r9c0}H4lWN7K^ zxqvW>z#@0TJtSM7oH%j(_z4`xoGhY_+9Uiu522q_v4Pea*e5ZcM9{*H?8I>fq>g6v z%^u6W;*Ri_8 at y&(W_R#m8t}7>GQgZ#BDI|9usUCYEt-3v zG9}rJ6&7-7P-fhHM!|E{Ab*RELWM#1> zkf$tMbIcr7N7PYsL?4E*?-gfzIzdn3vN4tfg^rjb_Aq~|U|fWB-vNB@@>I(a^|t1) z?*UY=*?pyJamzuSVN!D}mPwIgOv+7D!99nTqEA*30x@@hyM(^N^>E zrAyZQ1|w}qqq_1S)EnhP+%6D-L;trYQ`RAoBqNkC&U4Zpu3{Da2HL0%mj~u!mlQF8 zcbGUnY~SGS!yQZb5M%$1(On#H78ndH(nDAjEu6Vlp;`Hj$5YWBjS)JRRBucidV|(9 zoXD at 4)TxRzS#(%mLnSmuc1DHh9O$Ip$mp&*6t)z at KFfW=om8Z!@KXUr2lE)_Rji^8 zp-{P}$R!i;&?i#b8u(At^x>39WAa0IQ-}CF3;wc4olM#E)rs;+5Qbfhff$S;X|<3> z9d>V6EP*Z%uFBYFSYOCIGvo`AAv`^TN=IgKXpAz4Gyn|+7M~nvtHFXE~5xDc{Fh<;#C$KbCHrU_bm!Y1HBEMvlhhYl(l zYS1KH?g;kmHG${8mi3Vm>VuRd`r~gLI&=_Y4+2yVRNxq|$$VLBNi}1vPAy`9{l6m@ z86^W{Zv7 at gifQrP95M&}g at p_4N%?f?(9qcIja1150)|s>9CC;3!R&zaKlT=>RCWBR zU?WAxzU=`D(La&s3BZAaxZ6*4n9_-e8bM-_`}RqzYJsNFk~O!umLO(P+X)iL<0+w~ zt(Edkt7$E?RGM2_np;$h4Qc~L1S%y;6d*^jq>dT}MT|mANvvDip@)h1#5ZvvC?C(X zTDag^R5Si;rhY+jFz6SMBt)RX9|a0MEe5u_rP4|dY)jhQ(u|4eF(hBAv<1p3x=l1t zs5Km-XpSG_NBWKA58 zF&FIWnk}OvE8!L6vs{*?q*}^cSYjmE{Q4NiZ!NUV#P+RJxB zW0nXEXSU32&YGG{Gnh*U4CfQ9iazjJQ}7g;)28Mon25+oYx*gO${SQu{3Jf4&m$yZ zqVOu23dfV1p%@Qiu~{`)%<0A9*iXoMkszdi87;hY&9NtS3|LGn%8%qntHFy`=l zD^c3!44TVfKNoXj?Sv)C*Gc`U{2vy;z!ilQSgRkAQhmQ5FAg%HG!od zdNU&Y#4}}ixIaHF5+rrk#j8y>Wx1VMkXrP*BHmSxy_z4+;SYn|{-t(nZ zp#I=ub5qhZ(b&YFCODAuvCi)ld5yyTCaeZ);LSI9)6^*5=+ at 7{5$T^&P4dS00jtF4 znoMJjD1M8d=_4!z4`y<@M!e9>eTNLti at y@0RGsiDKv&nK8wE5@%`1~uT*%1oMiR^r ze+6SS7aty~P9Owe8kb+v{ZU~pu}=wctB9p)%y+*Tf|~R+9U2k&B&3q0VMciEnIb(x z%XJIsGNU;LWME(;Xr&q_5FqlvD;Dt*7XqZeY*8 at Ku&!ctg`oVO(zrhi#G`4g9ZL}# z)S>wCh%WHps}a|q7+4GyhO7`S5R~~)%>_78g_%G>z*Qqd)VM4X+DePkO%)-pq3bAm zG0U17bz at PwtW-kIq@srf(+k*Gt5Jt&#UK7YOHINQt<Pw(gtl`b}6gTQuxm9MRU6HJh<<`7g zYvew@&Tq)8Rcf_em9AX1YNcCgU}tgLUliLuNC(g&y^cX`B8Fjg<*JoxWwv6a7ULN1 z8t4&FhoGal)yXOg_Y}^V^+6Pg*LwXx^*x0VUzo~;VARUWs+B7_n_i)MgKTcr#cLmX z-Hs4YbVydyPaG?=RV%p!5LmfFt;l-UoAr?8*F)$iKy)gsv8#*XB>l^JS7@ac!lj139gWH!n8wI!+{1t|=Vh}nKjP-Pn{PKdl(Lrj- zsi);fSW=`jsAr at StHXdqNH?mWH{)>2;aWnAgWe|jV`h9kY48~%+zQw0Xly_u$Y&HC zAqTRM?W)HtP!H?M6)`NU at Lu&~%h$#zKxP6jLnUtwl{_|zc%@r0?t?(6s at L>Xm*<|? zz9O>bT_~<3BpFR~PrAHkdGI!9me>NA+O&|B!qrF9maL1Cbl$eTp4SK^=J zbUmPq3xSIWh__X%If7`#>`h{9a^rDe?BlU(Dgv-x9b at fr37^D5cM)Rsn4S_ASiU at H zjd^QJ09cu5kz#_aS&@>-D32bqT(Av&&8!~x+Nb1C0#pc)wM3?6y*(W9bQu*;!8is> zobQ*Oz9ug!4N&y*DomNVP)=HdNpZ`wWqmHEa^5Hsbh!#lSM<=qHQWLq7ddQ!Ck)(g(he?qgs_n^|Gf3&spg`7GO;*LxjtKR_G}*N_h^| zBJ}OW^gafSr52e-&9V4dwnp&bo?}86Fg}l3GNXNF2&d|?%k47xJp#(})3-g+i<_nD zRns_Wd*bD4S+;x`-q>cm=w8Q0$`;AOMKP at C)q#}6KEy=FEViPleOMv4#+QzTGg;4a zyWB0apopOEDbDR`oHI*CNkOqR%ToenmO12s{Fa at KwZ+mVkX1n8mSs!vYXm_(=gJz1 zf+_bZ*;Sk`1>56ffL*4RVwdoJgZ6e68)D2h){BBkmM6>7rNGIBznzYyXeBj8k zCrg(tTe at s%;Aml$4LKKz*Z@)DBT>t$Fy-o at 9T=(boOppK3cT{3>ScOq#sO$?2D`Ro zUruXptd_c9~sjmRL+ATQT-4l0&U1tpq|yq(K(P!3 at 5ZEmzCqWoD^f zV&wWqOxe{)HRdqJWa*NnT-GAWkM}VqJBDE_?pw^F=p23!frmRZFZLt$)#W`}AW<^8 z4&&64%F-oE*n{O-F5g;RZ-U96!V6{+`O!-!;0i2R!fcYjzySlfGVt4kTCRbo3+arP zWQ(U}Er3Z at mbWM)1&Sbl%a$6#x`e*_q_gg16U=+N^&GJ>X^bc_DQaXT=p42`i(Hif z23|_G0+gNi!B6g%$W3XAKorj`?nQsZ+v&KU&ine*DEw`5dm!0w$AUUxk9pO@%6)Nt zNF7XhyILJo2deuI>^J+(nBM2bRo>h8l7tKTiiLT`+xt~K*;yZO`{S{6A8xu+z~UWr zchns5wgxxpkMY4=)YQvV at F<{C!P8KoTn)={Snr3Y`^Wf%A3k3QN)j}CN*i8v z4ANEZkb&3f{sRa0>-|-XGGk^R9-;>I4hk;byoykfApH<`NFOW#V2?@ym$JQ>K8GYo zgw)0?Ft!IQAsO43U|MFEau{VZus|KMIIs6-T-?JePlnsKcQ2eyAO)$z83QzL#D~nm z^uWP`2P_~aXa-L=gAoDtfhhqaNIeQ`lU)Y$#}b!DT=J+RxJvz=Lb%sIDIE$W4csws z*Mvt?@%cq&FI7g$y2%?F=a~`*7x>&fo>zPEP`xQN3QV9f1~mtE3{5d at eEOa@)_cs+ z>!3W#o9JYY|aR`-koxWFK0r3^xZ zUZVDweVGsrdCZ)a$FCtjqb5|9@#`q5YZf7+_K)E*c$yNP;3v)EWAh4^nHR$cN#O at k z@*!> zK7@|_2*nyknL{Z at n1edB&nGCwF+6;1ADqL}+QKvEt*i17d at dw4?nJ_2Xnk^>cKQ-&;}FaGgwhD^eP zzsyLkm{ZHv=px7#e=J-{_BoJ~PijJn at suk5o2~rajUWO`f7M_{*>@6XlY|?BY~?Z^-(;mD at 5S` zG1%~H0#X_p1bIw|4{#^IOR=(>TLo{6C62OnU3kEA)X0y5aXk8rGg)_c*vO at tWNX z)`1#a{V5!lsmI{3+dHub)z5^P(Grm7qqz(oGl>D*ByoMfmR$17j9HYPZ`=-E=g&|3 z_rvJ$s2neT%hnYmNOn)=iJVSG__#rH2%QT*>fj^U;$&9uy at 6qPYKj#Ut_*$|0b_9VXi^1RFyG$MY41nDX2?HvPSLmL#7P$CT>b zcwXP3g9(OA(NW=&2B;<}qGOXXKsp|-V8W?uHN}enc0xRrkkR=8=$?V8d=RWWfYa^fKLT9F<|1_}SXx{W z_%3KdaD1vI9$PpUfJh)h0<;p|iZ|gdI|kzh zJMOdNkEX`BvC`1km=DtpX`?9hSPpmSbCpVN{40tTaw}hRu_D$zl?ZhR{RZ at C2h98yYBc!Bx{d;m0;N)ipsM@)85piC7}y zxTHa~6s8G^&^sD!(=#~lFdw{WfPyJrQ-YB7T^_Ba(-x*YAWC35(;%q&H|otn`f^Zu zOw$ZZS;3Tz4TT2Eu#TDr6}zmtsUX47DkkP(^~MI>P_3sLCxAT*@35v=B+Ll0O0YSV7fm;Nf#EP^pHJ zc+&teD=1V*I{mIynrbk_1I`mPdQ(NPrup*%EOU at FTFeW|*24i#=LIWKD2oypQ{c1Z z$qO9nJpP!g9&pF8YmMrQ}3xQ zRIq+NR)Mstq`}sMaWK>MaXy=Xz+w;OBx{2`KK?R6YpA#NPo*Fv4^phPCRh>=$a?sU zHztHW1Hz;}(55WofJcGtLJQWyQrBSMk*Y6+%+N15ME$nJBnl`YNTH at _K+cIq)=(eU zPwyVC at sUtND9KL+fP!{MOIzw|~% z&PR7&-5`hP4f41hO065#s&Sh-h=+J8&`%;5>gRA$%1V;x#qAx~D`0m()2o}70a;^=(S zvoKVRMV>R$Qd+1_XV=%yRx8XBz}TDG*;SYl2PEJ6=*TABP)FbJ zqh?!tVg{sQ!ZnJtPQWXKlDOzevHl6i$ZIw`7panjJS4uV7?R||(F0;2Kl32p_`C6DX_@!fc|+VRHjA>wi`=4`@*Z?DDxf$Af^`j6< zG1L&Z)Gkq+&MwxNZV`U;LbXEVD$2dfMe5MYHSXd=3tNiWgi9+BbtYVm1WEWN! zFv?;N)dYxg1|pAHQbuTaDMY7$UlkLZg(X;0V$3z)p5=OZu2Ik*3J_|^WBRlNPSG!i z1+oRuwFBk}gI$_Z(K1U5KqsYL0;_bv!Ugcd^;86HgPGORG6WM$YL{sGtQHqw_!x!& z`V%?@W3+JHE{Xk|(-2>fE?hue#rAkL$RaaTgFudy&YHy_$3jF3R~M>W`VIjkK at x!vnO~4CT%Z at k7=qmE<#t&q zC_+HQyAv<=S at C3vv5@xGbjB at BJe~C7n7%S4KyBdyLx+t>LQIIrL3(Kgz%5;3{17D* z3riOY4U+}g{2tw->52kZgAiz$dAc1k;}&T+p%yxttDZ7Th693diU1@*$&v&t5uYq< z)AKyt8OHlh{$6{&cJ5UEAFAk(@smyE_XN#m-!_0}8n)zxTe;DhhF*KJq(U}xF6RU7MZ#QTiZlQO`%rDKGKYt#w5x8fi6ENE-S_A&u z9G_%MJPQSqEYS1E=gp^=^VlZT(sALf-yqn=jc?;-Q3CoV^Cw_DZ!T;#vN5p? z#Y|JRqz-dRt6I3gflH+Yw3|QA%tPa}%O<26V8~6U#1<$m_9M?Gb}bN5vpVQ;N_X_U z>fCt%g6R}tJnu;{>WMVOreYRa_ at 6D9Z|B>26LaUylV1p6h?YAAn?KmZJ`2W{umllt zz^)A2xpQT9#K0gJLuh%zL|-AH?@eH+jAiaKMy2LfLz;y~DQ)!eWZAo?XGc1-*zu*^3>wq%1b61$pTm}lo| zfCOHFioR5}FAWDbSfs&mH7{l4dampP8tag4C*;Di4;CUXUUNk|E z>9j%LDFaeiyZQ0FWUgIW42!b at LQm)7E@#cWxo&Ruy0kA+D at M0w9|Em?v!-zlftfr0 z`rNtfq1@%jLIb?yT7n3pv$Jaz0Wt8hb0=P(`}*s&X1A-$TduEL@m#7hZ7XpA4vlJVWggvfuj|*O7;Wwt zY2rgMutZlOcGJQzuaQvL%a<%dUFDZ$!a=`_kfN+CJJ&LV*VJnv2nAnKbc%(weN?fd z}XDcDoDU5 zK6+pLleqX?NIpr-KSK(S`I?e$)deqy&3AU_?gN07Ee3!90T3iW+LoAe&N(N^*}7MiU5HVBfADI-B z3QDzD%s4 at 92Bkf1&0yL_l1435zXG0L{#~r$IEKSt*A)YTUd0x&ZlX0Ek<;S8?XIcd zPk;07cWQbWi|?$NC9N|5Vh=<>1G0_C)QYi^rd?Le_%>*<5{=Q at onjsOM5^;%pb?## zL%JZ=txge8Va*c0mW5Iai%43j0iATUhFsDWh2^g{S_1tcy;Wr}na6!3ttA(V#;?4K zs&%=I*4p#0_%>UFp`SX0shTQ}t00FrCv}n*vB?XOR8pH589S)Kp5 at H=Wy^-BewJ?D~-ppNubiAgkQm88&u&7+Tng7Fj{lj z0vJI|z0!Dig78odmXyihEr457xP)4CT>R6YST!53sYR`6r{}NM(dt^M8m_%R*)st= zU12Fh*~K!Q8UwO>GVfXIHSHeN$nH-<-njLw1ToYJbZu)uwNAB$i0Z{i%}GIM6|9c) zX~KebfOrGfwtrkI-S@`HT?r|&LuPa_V5WnkH(v{X&c3uCOSHGuK at dH`*pVZN6gmyM z(p;2Mnh2uCzd%F5Ab#bo!7>YeNPE_^{RUt&_I_U4cDh*Ud1 zp~O~KzrlcWA{Os?0DCUt^m9>4V_D76^0a#Vlb2H-C6iRB|Y%<`Q3dKtOU zcz0574(SJrdalw|mwZuMabDFRRr^K14DfST*BGUTs`?eHJnv~vjCSixD9g{7D#>e`h9mk#6r1FyG| z#9`_>TKdJtQjxb*ogMt1c}mYp;+X at ki)TrQRDRv;Bos)tMJN{<HPB^y}0TE*CkVbRXF zRnVzMl_mDlUtYD6SjFYYir(Py_p84n7bqR*XmL=>18!8EF!|4vkM)YxDyLlifm(-% zA`HpJihzpMF`yop!m*$r$;0nz5h2cmeBTJU zfw+?9z?=OC_WSq1{=9#*vOnx^zW+*WfCd{Y&?$_-dOB8e6>>i{*=^Y z2;7PjL=Z6Xz^nZSY%o9hcJ_b0vVX;-XzgR_r(z$&H%!hZW|o^wz_>pisP+#Kg`q=MHbDvz9S*E%S*?6v%Yn)L>l(&)C9df41e}4}%o)Wn)qz!Q z>fa914jCQx_oV4qRWXNJ6t)$TC?DvS at kx%H*dI}Iw8CFu$Bai4GmAB*B=Q(zhR2M= z5 at yVgLA$eL=fIjqnlCY%@UyZ%B2APe`weQM=fi%rG9Z?cdBxQedd+0! z5(q>sn^`j)Xl%5WL<%N4gPEPt+bTx{>>IEz``7zBE0ZFy`9DFaZ~Hygst@$E2 z__tm$Z{T3}%J&Xbt|60Le5~ST4iat>;h at z82z~gP%MEI1;h>f=l9bUS3a)YSVTBsu zz}x1w&dO++zZ>FN$E0kuYr}w~&GHIlq6Zyl5Ivg@^bIUUI`f&(0(Q-56Dxz|6~m4c zGI|(UMQqCI1e4Owgl+$wm5$?TqC~x6%d~yt9>qdZcy)QKtKo3MLfEoS0byF{nmq-@ z9{Vp#0z54HXs)F({3L`m?jiH^#7bC(-_{`0ev9OU$k(_nhOMcUxGYlIX>I{RVBpUa zS|+;E=9r0;Ckro}05YXBpi!=Z#gba^A zR0Ht@;$%D8^|uUKDYy-9GN$Fj5`Oz9R{GKm#moxmwSUO|VZ*_=Kx=!}2uv6)w?12T zADD|>Ak$S_kRw<6=HXiYUA8T{E6Zr`y#rfG1D;}CYRo*flB8r_CiohJs653XcE{bO z{mCx|mAKM1p=Feg1J#Tt!|Bu5IkA6bi)0Neon;H`sy%!(gfDRP6ld9?Q((AUOOCi~ zR<`rJ3@;Pp6XgAFEALhbY9B+kCvIf5XjHsntY%r(>Dw?l%})HFvrlgDA4)DYzKbi9 zvO%)2tMG5V3AQDZWkGxeTdgdo<>9_%PGLj15 at +&dwXS{mkrm;~E7i)x@`REBAGrv% zl>~~Bwl2%>al`Lh#x8GszIJ8D8l<$x&s2K~nxc3eC{Ni!L8lrY`%&x4pp|9{bFfTj zd9<(1GM)XNJ`68*>R{oC7B`d2-F;>{P)Hb;d at VdA@UG|x*evd=%w${KTty%UHx}Ku zTrHOjXsb+N0LQ28qY&828gG{ODgNy%8)Z)y^~%=AaX_qIK!@iMb0SL at vDNf18V5s~?m~3p3km}pzeORD~B^+)9k1mJ=9F|X( zdpgT+5NY4?vi;O^Bz2*X(vKUvQO!;5_us4Ei!a~2c=yzgp*!vC?{i0AThvTgd;a3NmKU>agrYMznY;5N3A83Eg5mGK7vJl> zv}x&O^CCW9V+!-QHJ>44VF1dM8X~89?~R6{0$&u!&v|k}J2=0UomWfT{~w9I$nRCA zX-0JH^E^BslXY+xBG%kEMvFlHAWV8MxXL4$DZfb1N19hi(aw*>+8}>6J_6oKbU}Kr zVdUufi-`DxX9zUrP(>_gO8k*CUA{?>@8Wy8xM7HB`T6kK^M5r}9J7QnRfPiwQr>6P z=stSJlNHiD6`H8}Vdwq6BzoDr3@^Gb at c_8U&)+>XaL3(1l?KChmIAHOjP5CSewG-!T*^1Z~ck-TqP=jZ9!>QmEB zOn3bw5q at hj0^WRSR&HK2&*QW3)c7R$DzV2-Tp1E;U?owteRvt&k>-zQ@!9a{Gek~5 zO5(&I#Udg!z{>pX#u`0;7M}H=K70CjOcFtogmVRm=g=JR3a){)OrmQWKlwUxTS6PA?}g0>F8ep1nqZXV0F>27+9p05c8O7DF&BZ-C22j0v);vv5KrC at 1Hk52w zYzm_*RHC_?$`fN{@ey#}(8Q414K|%XS5Pa%m=KY5wSqsJu78Uyy)^Pwzj5fO1~g{?<207I)E7Fov$Pboy{gKvCRKOH=I`t(Wl z#1m44J2xwHh~1grKD;_(wai9#h?r5W|BiWX2OezkF%RM z7kIl9)*mDJW5Pig3Lt`b)Y(^?el=EmBjjKUeWGoUkR_LVmhISH1KE;SNow?zAIapY zo>Y(PR*d!_s-HL5ETa49$zx=FgskncxRD{-WM>! z-pj3*Ww!cpL{J at 6mdrgt_K{QBtSPq52=Vwa7N|BKehEC-k`-vkE^hspCj*G;@d0ig zb6Jad!#LXjHTPY993PcaygiTGDe>7rDYAsNTmnQNV`l_^$XKIARV6JrH4o&PZ%Vs0 at 0r4Xvz?gQS#v<&JN3~FJE-Z zNyDl9bRf_X)-p+StTd0h4|Qdx6vx$sU6T3*QSB)>cq}q8Kgo})M{5rsvAlAkQ~5kszJRE2??$6chvx)0;S;UkUE0d81YB?;*Rlxhs- zDO0)?qs2%0VQ8--@}{6po&yR_D=GJg_)d?ihb=2o7|YryJ<&Q94cC5O{eE4|Zqz%6 zW;ef~GbAPF>>fKb#QzA~)pi=id$eV4YSTiD=3(ByN3C>9pDw=@9{zh5>isW#MUnOY z^k+U1$|L at v_j`Kzc*Dy4TXnKy6K8b-x93Gn+MRH3=obH=zE^5lB%#*S8tON{{`IfQ zz4r9qoey+!r*IMZ_sJ%t-+DVUT7el0E%rhxK>s>N5dXNwZFD+UT8F68($Tl7ev^K^ z_A6tl&>_IZbl_o(SxE2z-gQ%b7ipW8e!FHZt0YuF6F|ZM!(mMUIrlV8+JE`ma z_=dj{J at 3L-+|ghE*OsZpA{&htW zIf2H}R at kFzJWR&Pze@~NcR^E9<=(oV{{=_=<tE|{VKB<1Ku3VI4{9GnzTQ54v=&akC z5`r~59JjWlHy{YGQk>o^Z}b`Tp{P1(Ykg=vA-IQ-s%@54e{Mq)Lyh7R(z~8&8D18VKBIh_3Z1Fjs`1=R%1nv?uK`mk#yR>G zh2*@PC*s0En|j1m`)L_T0WDwsVKF%Y$rwb zTID3R0x<$uGNAg5S{+oigREISs(Ks|1;mALanOZ2)gHaJp`668KCc)~aWr%kajUkO z^dxlTd0);p3}q6PYqSD*>C at 3CI7N;3Y9LKN%3q_P3Vt>Spn7-VQ`P-*D%kNXk&{QP zI(B9Pz|EYnuvbW=SQ;X at tJ}=c_6BW{tU0n_{VC-HEkmYFUGm&?-hnV#czIo038S?o z at z>^Zg%$~(53#5UvuhND{G3{wlHH0?hEGNx(*<(e`cOVnk7DmDsS2zAsnj&C4eE6| zqiWreht=j&DN1pZfBQA=7XmDOtI(q=e0H^+*{ak~_3pf)pA()6zoTxiL{#HJRe$xW zkGKkhd6~X}w(=&?8_h)3C{jK8Jki85HGRHr#eUfjkAH%a at e`(h43n)`35fbH`A_=Q zUQgGA{De|&pMLT&Gs4H}Eh-5Oecldm91Ln&S&AfjvC{Z)d&rQ{ETO4~ZC2=FkMyfj zWz|C{8f-pgvsHHb_?P88vm}u?csBIn^5|Eb_%=Hc*43x^ll*b-mv*#@o2SR9BG3%c zy3EpmqPA;(Q#sT4>8k4VNwZHrZe>-ELLFJk(v?b|Ql0WsFQBPHmR1{a?Ux^OaHxH! z_q5FDlz;t7*9k1{xDZU;3K;E|rK4#?4=b#!{)gi#vzBZW3RW^WeUd(=ns1xL`quu^ z9h!c!lpsIms ziKZrzs&cK`vf at 5DFbVR8;8NsM7c<2d*1=SrYc~P1Dv_p2xA^ZP4R^(o26{*exvKRn z{FOqFAAJa^>KFTd}m+S8}<$N?&EI0sD4PGG-kK9!Vo|M zXgEWo*aCj+Wp3KLwvVB9(?6`N>E3^#3VPZ}v`VGnQ~ToHw9g5Sjo-~VBN{%`-IezIg?Ov3WI8P0 at dsj^mBe#>_Ev4!>!>-4{Tv{*({%6{sH zF)e1hi0wxE_NKiivQfX#H)soA#lR{25i!~ut<6^^_CX at 5Fv+^4(8o-X7 at qf4dwVWQ z;a|+($XpnnNtxC*Py4d(o6Q$;LV&fZoI_f51+{oBNbC)J2U at q@>+DWkd94}xr%$~M za2U)AprrOqy2X_BUgxYu_lb%&pERc84rImx)!wN>QV)E!SH8X6g^H<2V46vW(Gp7; zg=M%fE-eb2Opw5)V+72dy)8crZzjto1T><{rInfUz5$ButM*OpU1N9uLR)M2Lg+q$>$zuTbBvI9}0Ow^1gSXZ)| zPO{O2Hr9x^uWyT0ELHTM*xO&)yR at Xo0G3xWRI)`Sqbyn?6xi^8ERr*>%}SlJYbmO!3pFd;S?wq_DqE}t%Pt| z#_+NUH_?GqI*xGZXZ3x8;=*qKv|gBgxlWGQg20%t at 8ffiU+LyUQnn zMp$~o_9nz$31MJM%Q#uqw9Fz4A?(!NuEs6x1r)e?Z1E|nY at aQABSNMnSq^xyDK_JI z0CFD5LZgK6>>5>jhb&etd3rV7qj_5UE_>8Bj!gNab-8R>(i?0r3tNPyHbG79m7SWh z>IB4VU~99Fp*HUOuAyP?Qe0}En$Rjn1lz!g)_P$n?yZ&tt}&|LGI*GEtGylc#14ds zbo2zYNz$~`GixoSB}1^3;ybx-6>a$FM~#cj^ixX{%H at m+wvzDi^>`Q~HNJAMPA-|a z3EhA?bCg1=eT{sS-AAkmZUn5fG~BbaT!?PPCgT-l2CA*TprS*b2zycqHD48IH4J6h z;OE at lNt_$^)=PsuOM8}!6eZv*Gfe>s=wl1*KWY0{h0vA(w7<+`Llr~cngul2fEbp# ztiIH4;Fw?*UleAdi!;GYPNSvK-ld=)oh96mutQj$KxmkG8`_b#SvkpFdj?B;S_{i) zQ}Ttmg0VG=NgP}2KTQM)E1%eJa*rX1i3%R(g}v)8wL~b+HY+amd=d}j6x1uTn8Hde zeN*^t at Tc`-i%$b-bk9t*t`2*?He)cdmBSKp)*&ieSQuAs>`KCC^bAWGU74|Q1){47 zJjro~nfU})b#iH4ww7I@&PUs4U&$zMGbUJ==A}Mk_Q=%wfrrc}mL4KGij_-Nivto4It*hElbs&a%E+p6F!t_%?yDkR3cp>s^ykFagXfF$4-NK z-&PZI%Jlw5+UQ>~SJ9-+J>~RNB9rTCiAv7elLTq4T(U(X$)i)#ro5%8rJfmJkJKuO zykj`FB`1rOOmj1lhO;7mkfd2~#93i{JWTF=3-k1CP`$@a4vz|pOtRe)7FC8cM24j` zUymijo``#uZ#cgMEK7|xgCma_DE8LUmOXlIq=IBMenF%}e7ydIJL>v`N8usd$HU=+ zhY#up_5GH#bABOm at BIlMaS_k}5A(z3LAXD9a6jB1-n(BIzQm3CG54eU^}&dP{Alpd z?#hoIJPZ%hgW>%L_pAF*nDw*&`_%Xmql=89cw9L>k#&dt!RY>j at F3nF-D8{4J$7+I zEI)O!2FB`9{jh$}Wx)sc at 81je`uFcaZM7^iLW}l5Xztoa221kraPZ*aL)IPKfAHXb zzHgI^V=Bxrq-}(wY+R7=s6h~kj7jdn06l*u#v_aD$WPMao)`(^b6C4F>*WVoM!6U7 zO=1veSF_xhcQIq+vS_|v-xKTOEQgq!8mELu11*}+u5vS{`-6LE81Iel!iPXBJ>?sq zMLIZS^w7we2M`}s*}J+|*uBDGR>lU(xbD61ARs|?zjLp5SGz-Q8;hEsteLdhzOu3L zVSF%p_&|fhz0v)9$wer}5-)1pV4n$+cpWo5d;o^hgH;*W9E1Dfy>NGUPr_Hmy~{BH z;zu47f_zMVVcEFH^jOp>Uaj07i6Hld2Qo*#8}10L=Fe<_n+gCdV6q2H zyx&OCJ9qC2s%0?zq&d-oRgt1~L5uvD?yfTUj#I*-`otxsN7zJ)#s}*U4I~OhnK0iG zF-UNUJ#HQ)TQ`j at U82AwPIm`dIoz?)?j9i=D?T(Un%i47g^zE|elp>m+jm-uyuwWi zis)u>n^&fSIq6P*0uTDQI8-#HaMvO>-k%b(S+a6DCLTvphC{kUZ4Hu4REFF(#X2Zf zq$9mOxG$>?@mRXsXo$?zfLpDS9`qDP%Csf^Dqk$!3AcN95KA;>Zne%N*h*1!-!hQ7 zAl!|2MtAS<<2KeB+=lp!kK2H?Xi8+!&V89EAgi=toUD{?n~VA3yk{TvA3aRund*L@ z9q!-DIL(xcCeLlHUgHIwCa%F|i;`^(ni)mo?cuE_Bz#L?nqWBd^C6ej!@UB|`c8G5 zL{mT<0kMozl~_&py23Eu2!axJxqbWAZM2+VU_F*Kn33zdT|N at lO9XAiW)L_cevEHAhvrx6L|}_o}vq6C{hPOL-qJG{7Um_LN!dis5$02| zdH|Hj5^leeiP(aPH zh?JEHm3eOSqe5MYb(6EF4fKXZv%qP-ipal)2*|;>c(cPLS9 at V04Qs5OP;Mk&);G;= zet~5dX1cO)LsTf$zaxd3Tj{3GEpv9eR(n>2JIWKXu4EfXsdN*)lkHoZolH7Yp)n7n zTSJp=bc4gQT(6BURSJIUJGCP&BW3TA9c&GN*AWlEl=7R~Jy$c`3OB=z;msQy4IRcP z0p~kC8BAW0)1z&PzBdYNqplF;N*y|x^hM|ZMukv?cU-{Xjhi<#!;{81G(6u~c64Ax zH>hutZw{D-C<&kUl!NcFO}zIC>+>D^$?zL*ht?H+X5GB;-)>loLBC(#wi(ks^Ta)P z^%_zGLvO^zjc6-|rgC{|R99HY>N98F>QDS;eE6mHPzoQ_AFAe at DmaZkfQ?P@k6RzeHj0J;Kc)N{ejp9 zHM`p^ePC8VgR00Lk)0x0R?kW_&@1MA_Bqsp^&O1x6n*HXPd*k~V2#z%N9n_LQ^Ts! zr4KR$KrAYEKCy0%iRHtGT{`@zj+(>HGEGY_X-oxHe#zjv~EM{ zG(>&5U+`2zHduTmHBF!I~8QxqBcZdfO5#7$PViD3H4JUd4D7sh}ulUI;^R8)uBHqlcZKM|w$t%BwpF1HQK~ae^ z1%g335}96~vn}cwDZQ0I!?@99CMF|bK!aq4k~kR~Sz#p=u7bl9OXv0<{1U)z?qlh_ z<-8|Wslphn441gT#%MEjdi_Xoud#sP-}pgYN_~PldGVnrlZuw9qg#s#r3!g at 4F`DV zuSmtv^z+`&e(ng}fQ9;`%B+D2)xX!2I8$Y}(a%^-uBAOIMvSaiDn|xtJkn(C7wdAV z?;UJvh z@=&eU9AX(^Jya>SDnU%Wpa1M<#-5YWKBk1Zyi|2$SYEu)Fic|u8yd;#Vl%l*dP&q!$?ETc{MRG|LV=B9^ zIh;XsRIDlO3PmaiS&Bir4IsALP_uTO at uR+FQXMocpuKusR$)42psLN!-j7phh;EL2 zR4LC3rj9m>-o>A#pYj-iazJ}VH6L}+ifu4PumY!Eg)jZ=4P~}|YFShnV|)z64{3{s z1!M#jWGo)df*v2r at F`zc1isoJD&}RUQh;D^Xef)QsuJi+P}Enb5Lt(dG3J z6M*^O$W2i()fMCIpPyhKg+wAGTH7SHtH)IYz3QM<&9w5R^0bA8BFK(nJNXccWJ=)v z{AU?;d#rA6JH?Mnp78dH-s?|0B#X=>-+FaEsjbk}sa=6Lt9I(8SBt_C1jV1WmH!Om z<+!HBp8V<%m%^$Xi?cS+&{k*Utt$4ZxR at Bov&qVT+CePeQmn0%Ahw!$D#_UY at lR|g ziI%{q_VF^nUdOW{4pdy0aXIAd zv+Ab}ORg#ld76lr-O3j0IzZv1(wnVAOJO`!K}`U-VedB0Nj=rJ?ffL_4IphS52}fz z2W8(ftEZYiaprX+&0xG6quk!GvH3fYD2meBIbz!62D6uikW at i2z6SJ~r^;pKHWj5* zxjbsq;*p9s;4CF=x*AfET@@fLhnfm%YKkgTQgl`&6salrkri0h)k?v4wp3fAW6i31 zDGe(CAx{o|IqH{A)NahIIq+Je at h9u@wZDf%NkipSj{v_w at S|8dmwJk-%}a^kANTED zG8GNgfozq6z44~IMFma0fMQ}W?}|lU`{HFkWo)~|Qoe2jzpd{X at VHg$ltA{TmfrM( zHz-ugt0kva`Nci?YH774Czke1?D=+a&*C2cE-vodJK+ at 0gb0>u2}WRPPrYZL5n(ax zsTRZDuunw(-r%HHy%hEgVGn``wmp@`H+v=*C&XCTc1heu1INH$CVi!}INGyVFCw6= zl%OEQ6$Kv>7UQ1CUU{()5nAXwPK*RG73{NRPtY2+P+H`Hh{6xqX2y4qIU=n*v1f>u zVR3EGBEO|fwdkCR*6xVUL4_!8h2J7eT7|bP)+`?PZbbps)!(-u9eX#6VPUk029ZC* z`e}aHvu;ERYjpN>i&?A1{=(wIA_g;}Nvr8!we-e!Gll9RAq$&kQ;QX&upEpKX2caT zuVm2g2K6T}P`j|XLpCU8GGm(-i=t^HZfCKJ6mkjFp0u74b*G2 at w;+EP`jVNY?7v%%ZgoI+X;mj}33IG57(Hl?GQfc!9y at nx^dGkgVl!J~D#2 z5P&a$&}t#<-O|!#ENu%{45{|mh=9Z{≥tmb0Zq_A*h zpnoP^nyIC?7(fy*71QDyx10^?EI>O86i&`=wLM#%%+hf&60r-FZ_QZ3umn`v3_J5) zaiK*Rj$vM2fKk>YW2z;xg?sCB&oHBc+GtuTLojbRzW^O>!nG(=R<$h*iZzmv@)ict zwzII2kU9*Cq*KKTCet5No4zH%ghEpiSSGAw;l%~pb<4tN9$DE`j{@+(fzlclu8J at 4 zm^RzGkmhYv^7u6QY+%^4m2F?iukHgzhlREIg at t+k`LaN>Bg?rAdln~!hOoGGp+C>4 zu`etgxhn2C!@RtlQeo^KC!Z;UQ=1pB at A5*2nr=0pUy&_C#lQWcD#+# zM at dc&5L;N%juz&_{F=t{Ehe`ZXhB;k+!ZT}3lnZlWLcPxc%ePq8b3NPYq6&Sreiwd z0TZ6#ZG at S#{KUk)fus at _)?N0+g(<8{R+;zIC(tQ&Ex@)ereYP)J-?6`HIO7}ernI! z*rjogC+Z~d*}z(Gp3extqB at 6ro@zj#(w!O5;-#~&&OpA>&XSnPr}$N at bR5uTxv;RR zasCZh{8h1_BdfLDRtu^kN|>(}x|-9j-zjNf3dKm<61b6OSwA?B=Jgba1q+eT=F<`e zZ|Tp5S_wO~$HHj}O9tC=0+GT(JwMznAfg73emLeWBU=(oY`cbMkq14JDX;&+R60{+ zBv)Kvl^3VXjCjU;&)VgpgHuVivCLL*GP$u7~3`T{oV7sr8c^2P30ry zd;++*gT%nchu27n8GE~n)^;`3m?dMI<0PxCo#y*C4~*;ve0o~MiCnu;AMf@=Ux-42 zFd^deWSWQxOV;ewQ$XeOi at A2?dDcxe-1Yq|8u-HysAeCYRG07$pke} zu|&SnyME(3-|G7C+VyMknsLYJ-ulKpcIPE3yPY3w(dtIHKDu!|*_3=OIh~MN_EI83 z=7<2QZ>5{{jluOBH?Bt;ov!t-Ub}YH=r)vM7b6So2EHhWwOcoD+_hR;*k!ZDkKv12dpbTCOl)#zH=HioHkIZ^RqXjb35&YgfZn z9yq`!Zo+i4gZa7`{Tc>O*9Njjyjr zk_04ivxVzDVNGZoUFAo-nyzS4p})|35Dd#@IuL?sBf<0 zG(2(R`q*#rDkHDPD{x4L^<2ngh$pxy0A1bau&7L0UHz7Y5!zwue6p|HpkKH?kdv$H z$wo&LBO`{G@&F!;^lBL`Eo=}Ch#lA=Tpei(pU^SeB`o3LmadPk0p{oiz{ppIm#+{{ zf at O9vL^)8FhQzuF8d#Swle at 2L3-fql`N8EYSBPmto%5=NFoph$H`a=MZZN6VZbED4 z%8>Z?AP|dx`&PQOep7)!*vPdOnLE?EeQ6_ zq~S`u91X4EX4lufQQeq8Zu9t>fM^5bmEPqmY;ar1Za{UCZURTVR^!SZ?qNW at +&2MS zL2!6;AXc>)ry^|YYwK5u0j=6x31zq|mkHT$qi^yC8!GSTs|`X9E}>asZzD|0dI>2= zP2qayS}Ne{n;elDHQ*cF1WvUXD%{8b1k~|QIIY-xB!T at IhFewZ#=1?S2}IXd(&gcm z%a_6>?QP4!nwBgiUjse)IV;ogkg)Flf_jRx%4jun3nxBA9;Tj-CKsjmND z?8!1($xL+hN)+HOSC{*jE?*J>4GW!aby!=$5zwY7ifdTBJY-n7#2}^n(1yN%o^7<@ z-1Qz^x{UcR-2zo&j91$Tl|9FCZ@~d#u8`N#mFjZ16fVb0jzrHdifq9V+E*z&UvKb5 zxH6K3tIO3TsP&S%D_~B(3?z&OVb)t{B`j87@~}@gUuivuEVvDzOtQ;@k!-=T=}Ja~ zk at Os1yoBs_juash&Wptt*(~D*OqZKW-HSvH-+0rBW4X@`wsMk`9A~lvXza;1m`STk z;bQt1=()DzbM)#!9t+?lTn at PG628cn1{W_~#9hkmB;kbOB$Dgb6oWmIJ1C6kZpy?< z>0%(Tly)j$t at Bxye4}whmV!%4#EXECXG+|;vDm!wELP(qr5W&oK`&ZfiZ|cLKgzv) zl`ER7>+D>#>j9X{moG&W99_B?k)Z-yQHF_(6~dJDY}Irb%tSDNUY9PWivmDmbhy5* z40DaMqMUGU`fS)umphm8#cZ0z8(m4kU^y03Y);v+jSqo1UaBwlF4(m}gHM}lU5|(> zwIu_D4&(&v+g$WqJw87D%Ekdxa7YZZWbHbJi|ZFI`pS8H&=p|}?ghTgy{yv=eaVqB z{H2TGV*kQLCWn#$BU&_$_I#x;#IQ?qFc!Z83Y5`Xz(eL|nJx=ae8(smUBGPl z!stBu`9)qih-%I(Wi1CtyfBiq3~LifxYoa}R2aBs at r<2txtTa$Y%YZJ(K3mj5gH%> z1xyw$tsw%~;0ICQ3y1R%Bh?4uqX)l$$I9FQFZan|8SK8cDrjw#`U8q#Qro#Sk(H*p zcovnrlWlsEYuF|!&$07mVp%&Cavh5Y5-Ym;;?Il>D=pwuWK)&0 zDrC%7+{_luJ}en$))qL+R^CZ<>Z<2 at dEl7|SurM~uC-hfqDr~6aTO#hmL!H$w6thu zVK*|zi(V>5OA)e)h1a$4Q}Eg#LWzGT<$QSc{yI)56`m^W>SYH9nvy_SNnV)2S_V=b zZscY2RJTk;|1hD7Jo!r%c}q$IcBzKc(hh-2%(x(6eZM> zs-vaabov*cis$R1q_U_>kpP*7O8^ySQ^~jRY+bJmJT0vU$3x{|zhqU_o8 ztgrTC0H%r%+dRPytUJH>%3jMJL%bKJDwle;ZNQPuUjBSV>?>(wHuH%I+P6>`kV zqOq0LBdkww{-Np|0YU0jSrm)TUng7lPX@&394zw0TC%Y8Qy#2MH=7p z;(cD1PbDt9_O^>vsZ=dBz3e5UN+g{u?FE8|T8$%yfWqEK3g$fNVdX=;kgFTi%L;ma zP~FPRD=B*ShthAjEF+Q?=T!l~+EQg at Mf}3kZv- at ZPlNh;3|8`0iAk at 7XQhEXd=E zVJ4M#YpiG}tx%a_jUh>pI{IwdW+(+-Bg&80WCR|JR{_y-RLT{Bt-O3E)s7uVt=k%2 zaaq-?O1iRAx!T&ST#i at 8n>49$Z+!HIl^@zLU9hz^fK}V?8#`ci$mnC0Qq5Yb$>%TX z>B2#^T>FG(o3B)z&t0yUm1;Lr;B^|++#FxW2tQuaP&H-a({%@jK&k?XJgm=)MVLq4 zE_Fh~%dTBksXlV4z^GThN;yNlk67Dn^RUo!N+*i*nAc7GurTDmwsIH0JI~E|zIk_; zTi?wsd3{B8I(d;7UTF&V=Jj)7zL{V1@%*fI$GNcEsGGQ$#vD&!gv8<*)w6p|Ti7O3 zY~qZ91Tvy0i#TB>kbWBg_qVi?lF44k;&$o|>8;Xf$K=qi@?x zB2?JmvEUn^EQTx}3SA)j0_s2;?T(rmcX#J@@1ElmcN>q|Zc;D2k$C=(`Np^y*HCt3 za(i!3*ogrd?+>(1_V*LnBc1iBay;O_|piH3J%m at pT2uNBisk;!%(i%8uK%m at Oe z>u46c8(A6_rsmf*PXA0cqDhO{$a=0n>yeJ2$?5LS{C73N!<*G6=2mA*tS!uMLAbt& zZt+n|*|>Y_T$&xsp`QD<(3V(Q1fYS#@UP^w&cf#8&fMDUTv at o7s55UGm$8;$K$6C9 zo$JnGWIkE$gMoF`@!=Mw3G-Sr*JH)Gx!EwAaC2cfv5-K6C9%_-nR95$RdZ&gINP0* z36zN<#wtc_+>Itglu at g>F#Be1c6QFhSeT!1?TWBO!zQyfj9o_*K8nAhUQ(Wx!}w8_ zFLw46ggERZ&jn`}u zaJPhtvqMbZ##P6_)}|8_aw{=DOPqvrB~qJcWhh%8jfI3TZ?IBE=$mIN^Q;R*N|~J^ zZov#%8&b^;#ahL(LzC%Sa~|_dbF!j4xf};&nkmyb%Gafg^vMY4q;pAc!rf%ai=;4& z6f|X%r8G^3Q2Hvt*n&hGnq!n0nN`aQwSmi~0Ki(QhL8Oy^DKV}ton2`r5fQ(frxT9NE#^sCjjy4A#U_jQ**b1m%2urAw$8 at +H;%duRSh9N*8FF>CL&bp z${B-QfH2$M(!2z<1T}k3%?&gzO8eRed%lyve$Ze`gA%5+Or^-MNm6giEJ;97t+5t= z4GV|eGAJjRuTA>H3YY`E!tPgcmVfw_Oi#cqKN*NLXL7c;YjzhF$s6V^xzfaL;XT1N z1KCezc8}}|yG)=qhIHX($8rn2Ni5$r$Z1;Mr5x`WNz!RBxK3zbe(aegCHvfLXZBmH zDX~m^We#28jZbXDConeGRciNzvKrxb*P2%d9{i?r_>3!pj+4 zaV3?wwTtOux)9HgE}ZAbx%1(C at 7(#b=gyr?XU8-`b)|vB$QK7<4sttqc<%hUc&M+r7sMd5QyWw&r9LCHBU953}Q8rP97 zRwx~C!9YsqbK&gh+}Ut;$b@(%U{)t^L}`k}&FEcU=pmaq!Hty9hBG5=0F~5o&rU(* z0*KlL3+F4-r*k%*8J&hrN>II{i>`$TA9HcIkk5y60h8vl1L+;lWFtKLs4N1h24FHz zWnN7TleqPn297shnkawGY!;k)4elSWj1mAa*YfpCypWLu5o)~gfkeSKGV{` z;?XyeQ#Pp}(V-QDTg|E1DwwpeNoHg5e?_mp>;?>6yZUT0xlW%p?zIqbwTo0-Sr>~X zgh?!16qEMD}}RN#RuPfre@{AY0CL5#f zI-ZJ9>*-?WLSsP0t7qTXV$w|AVS`3kLMDSfb3I1^8gPT{|NN?1-rgq#vCyfHstAjv3}8w0+)IFJ!!HhzqA zW4z#aj3tc1+4WZX`plYqa!S+UsnJQo#n^jyeoeaYaPj!BO?)?aK3$y(CwbTdmscbg z89d_}Gj?SID{wkQ$LtF`s3uucM#)5T_H5Ee1i>hu>YY4w@}%Hw4;v78N~w`Fkv+l% zyudWsyUSRaPu#nV;y#xMeY3X0T6T`7`^X at J^flvTI9ZlYf+b=NllH>a>@V}E5H<~Pn}FBhbK;66jLqB zh2k239Z;35scdKxq|^1Op~*csaq=Yl>-hxa7EFUerUs5ZJ2uW#XDGM4W5cl{69ojnM3G-+tYV?z`K9fDx6HXqTIvGz^C*twI=!lVZF($!nefc`H zh^k63BsQ7Q2GX%E*B-)Yg)EugLFx>9dw at rd-_!+Obwu%s994IqWQx~S;p>R<@9g)GJ z&Dci$GcRMT*{7g|8RS)wWIameuT1ak%7_=Yo~ViWsh91Ny<05`rRK zoT8Fxx-y*UT49`36}jOF=sO~2#qCQEs}YzAsWmP*CBKw)DLlkR<4Z88rx~p@`=aT#j{zPJk{I7uHuZkMq^p`CzHqP zJ7pfJr|g=0#j_S2y`Yx+zh)cNuM4VJ2CL_S2cmA>7)H=l zO6;k8R(V|`@V%rC6$$LgJ7aWZ05is=UOq^Ap4_9PF65^bPZYpZ zrYnq#nI$$VZ(#7SvUZYLMRa at 7F=rfAck$tUN_Zs%^vZ3Cf_WH0m9{E^aU5Eua+<12 z`cTy-%RJjn%~4bkH#sh4CbOzun7u&buL(pdsul3IP*=rM&oZT+o*wNU-W0yyC~qin zV98AGki{jWRYr{_X+c&*hHz`$iiWYidZsDm$j4USE|LFKlrs|IYYugs?2^CXxmg3pdR#y)^ z-;!#cw^jbn{;4)uB8!&Vbrmm5RQ85Jz+ie2z at ohd8nq=g*99`PiWUlYJ!h0vqu!Jo{|HyvbAq%)4otZ3 at B#R1o>l9;1RuSRU{5@=CVP6=f+&-l{f zwZFJzZ&qw*op`B0C0rG9I%jkqdt at 4@RPoTClGkJSZT-*$@=7&dWFq=ilx?jzT6*aH zhaOLBgFw|&^t?8t<~kA0c2sB_ziE}w(jOM)jUGmNrO{-y zD-|fE0;QWoZ at n}(432_`)eV)rRAf^nL1p-Cuk;wGHlA0`O77%emsX_;714S#O~E`+ zh-7<8d~^d*+tV)s5-r&l8mv%Wq9z_bZ^hIYDWyX|p;OA`sdgRD(?>m~ehu5Fhh5Jn zTx|$DR%cEH7}o(ud21}*Sm#-6eaHD<1w4Iq%F_{Z!7{g!u)jN{*K+LnYTo}TwF6ZH zuarhDrAAxvWodtj&0Zg~O?1GxFGoIzp5`%Xt!RM%?^UO-nh7?quc<~H?YB$qp7!od3sdV`9jwT`3gU{yX5 zOr)zvvc21)m#G{Dxjca0zp8`DF9%wPtN17z-BEt at c*ypSPhT$~AeYb4v1B!0bwTO* zB;Q_D(z2vzuU+{A=~1r_*)FrN zsA3Z)C72|e2{YYYY{VC)ePfHPIS@$Nhc$K$WQC2rs$#ORdsRvqu_5kK7H&)_5h0Vi z!c4uZnK4;pQgO5<3-W70epHgWW*~}(8D`Bm|02Uq&aS(b8a9yO!>&pL^Gq#5%$O>% zLvyoJvn({btHVODIt*=QhUjD>Y3CthWp5Vrt>4mu8kT$|t+qwfOq$)Yt8ZS5E>)fx zOwY_f!O73X#f*#4NB<=y^p$M(1JRJLnh^~z%fqIjh_h*Z8=IvtZl4*NKIZW-)1RK1 zo at S30Px>8U;KJ-wi%ws^gTUq|`(Z5NgfUJ?%UIz}pL!-tV+y3`n{!O=29eE?aaY$Q zpKwhIY at VBF**w=y0DJ8UvNYc`)93dz4l~nihX0falXkhXAV!#!J>_0Z zQ_u9U=JbrahNnb8OPu`@UlbZ}0JK9N>H-mRQ*Laqj z^z>kmP3~(VRr1u+g|RnZZ{5|=|4q`>x)al3XQ#yrfh5nxT~j_Hj-7x)wy~+=p0?Eb zUH5eDIF%t<6K6IR?T>rSy6!ztT>@&Fft~68&gq at gB(T0lq6fAWlExV)067!3%W%n? zk+(n1qB5<1l>Cqh$2Mw7xKev83jWxMKR8qrnPxN==(P4SZzm8!jz;0C2$fs$EkzkL z&2ULUEMcbW>E6!iX%nT*9y&kJ&{AZcd2OBpJAzL={hy59xl at W-lCmEsGyQ*L9c|%ZV!zt_7dbU15v4`v)@jm+u=AFSU~){(tZQ at q+cF(@qPl$yiItCg zt7Nz7x2Dt1(KJhRi*%jwi5od_O$V%MrX`N0QZqe7zPNKZy>sltMse)WRcbVlRSJj( z0p6g$V^;RJgU8Z?te<8krw6i7+S!{nF{MqXoNPLN%&-{9jL%OHDL}kJ2APQu(<}^x zVPaDzzxM335#AvY+-YfED_NFE}|TONshQvfhap~!OmSN%y*K%kMzfZ zbg`AHU2j;%cH_69FS2Hs=`gl78|9rAE^b^6)=6D*c!BF1hly})5Zg+UbigWvu^OwnC6LbJ*KDOTzV>LdvmJxfnvka_ zca|inA#3hllxRv~+~V9vrZ5Sis8Q9S{>~jIeG-n zNjg=>GujR at Sw;#m6;6g`NIdwclP404Lg!d at v_29K_m7C3kEe}EN5lxvCPrqUEvsV< zgVJG4vOyxnlRdF2VtPi$`H_#;u0uSUCDW0^>G0qTi^ANA#&(h~!DFQ3eY7}!EFBvh zJ$CG9l+6zx2}kl_akg?;jSj`3@=G?eCuA6+YdkzUgruVJ5j3A#$34g)eHuIfV|7f+ zCihadIq9%7jd6nrJ`ql)LcL^Q(*3b3;1vzhsufhxC=~`m(Aqlrpx27l21HiUA=WX%2Tao}o@> zMMQuqIziOwPgK(qERtPb~Ow}aKe(Yuhvh0)CJmNh{L=nHYRlGTyJ z89nNQU2)e21~W4R^RZ~)jNr#L3TC0&B(SBcHWOGLjAYa5NOd?IBKx5D`k{jd-+lL> zWT?$L~;^Be7o)3kCBboe_hFfg&6CZjqu&g2T8z9a06{f8Z#&_j!`$vSx z0sW$y3+y;j{vfuaZxRm<-bJp!RAIj)v-)^Ewq_Z@^LU#@nnS~bhYqHLLj()&f`v at Z zkyKIeR?^3bQrh$g;Ni_7DSeRf at 1AHZ43+HT(RDi^7#)w)KDs*8986j>otR*Iop^Og zdCBmQZ1a3DzdL#d5K6|L^gG5ull|E6{1^^(<#4{yB+c)JcYM)u$k_SSCsdV9P1NMp zs1AmA*AVp`q}8#sR^a5Gs!(Q%v?6f{T^7%wxYcYBO~N9UP^O7v4Bqw*BcO9#6$F9Y%ZJ458~qiCE( z2l(W01E%}PeCS}!qVM3lRbN#4bv)$;6FDL-K~T&hp+TZMJiR-RqT!u}Kq!{v_&tgp zHLE~(10WGUu~T}-kBscwpF5Hz){-=2|%y)vI^LXV|LaTshG$Cj5jYLTcq7 zGpy;I-U&Y#y~Ek3R6g(&TF&j-(NnYt at 5XmVveViR-ub}~EFGnCg8MP0^I_e at G8mao z>D~O!;0N!#^8 at KrOr=YcT4QA*We+n_^==1AEX8nOa<%7JYY!Da0Y?Wp23`E(w){?F zlI&&qqqS6<69l=Eq2!0oJJk=!PvtbOHZD<$s_|g`?hrd>MCqgD_qj-^NJ+ZnQxG=b z{l8kV4W1SxHOnS)UOcicSn6ZPAlf;`vfzT*qObacTxz^+zIkpXgH$W2qPR+J<%sNI zSMh_^WGvqnWs=2fz-n;c+l!@zQV*(qw3vq=!zFLjlS-{OBjR2g4IvTxH!_b9-tSmnWe%&RZMASeJ zR41M&^~a*&5Jkt|g4FR1y!ev}A$fh9OpzUb6gOVd!>w(py|y)#lQm*%e1_`%sA!WO zH*bsbIR}{`T*TdB&Op5)N#EAvvgZ*4I#ZN?4=kuFlRM`qdFv`L(iaHE<)R&q>rhV*cz9$!}RF;xeYxMDxtnJP(T z6 at 22AbrvsAr27M%JjJa?4Xkn&EQr{jWa13`q4)9te&8urgt>4*VA1+kZBo#zOLex0 zes6H6J^an%cUC6p0iDTz5$sYZHNP^Cg16p80bWQjW=xb!?-sLE38~ca zir0ExUrA59dp)!Yp?FfWFx#oE(O33JrZpwwP&pUWQ`QP4^c;Nz@?-i<43-LEwPA%4 zdhon|$)5!sork?0!YqjeEJc}Wt3&3m1(a75Bn*X!#*}qrwAYA&+*1q-$MNbbLP|2p z5sJiupKtZd;)U~i5Ki7uUAqkrs#;wbzdBI?&u`G$Z{VP+hy~HS^>Z zs}`n*aqU4qr_;yuCq?cgoa-;M%2ZzdN!s+-fGCLuSKOfh at cLyNE_7!kN at FEv^m(lL z9QF!8(0JSgSTjDOx|<%1_je5lw@@m*((zMDuyWL>P9KVse)04 z%>Z`vu7jXi;n8(_xDQiMG7fH1k}6np2`!Jm@~n6$&&ZxbvoYPQux4cySTm1W2|8Yr z$vnM3Ja?9Looz1#CTm1)Ft5B}j`!DHsBnm#qA`h99x{0BNp}Hu{62!7gD2YBDswgX z3zvJ23Kfj2?Ri^&4mqB~l=;RhGy+IgI7?9ScY8{y!_49`ogTRMr>H%#QR7lo%Gw%= zZ9Z&FXn(pfoE at AydzLgP`coyh62cJq5 at zG6^{&vy)04jVS#)f!Q*sqhE6 z_6`0pc{ra79pLtVci zM^!i~V@1MpyxJNz at Z4{R3fxH*vFJpN4 z%Ktq*X{#4|q8>Fqt*Ht1D!f|WqAAbv_t8-A36&RA#_u(q7H}PpIw?w#yg+E^4VY-} zO>l$iI_XP=L}g$1LwPMnj;rjMcJLuUdB7-PsXC_euiif>hASDz-mpr{)9Xm1Q=C;E zI&Hzj3Pb7{``QG^r`Jwl4Zc?h$S!GJncd&ODz6jy8v^YsVtV~&qv+j8H3i)WFzgR%;5|tMc0=Hb_skUyW zl2IlJ+w<&tCR$Hk`wK35*`a?%H#)fuvdLt6|E(NW?&I=qQ3gi!89jWhXUOffKmo3} z*e^{eC3wDTPhRWQ1qsdjC${!R8N~L2Y7fw#ita1CBFT_RTx8J9Bj&|$} zJM)gPv)SPfbQojggLKc_XcLi4+y}3Bh8_K#J9h5e5q6BqkGz9h=f=q-ySpAWLkvaW z7Q5X&?=Y*!EI##)j?pY{@pt=My>s3Ez>?8qD2$c%mzde_{)^+W+xqYZk=22q2iu;G~3G~0C;6{e?JHD&A~^4j(t>?&bx;8rMFF^UR_OLYv?djyD@! z`|28sEq3l`Bry|M+|V;Iz3P4w=87m=cMMrGnHZQ%oH4{Brgi#{?eS}J^*VA at -0{X1 z-3}H+h2yb|ZQ;3@)L>c%@^^Lwvo7{xSsRw7ySDxG&bJumDw&8{4+(Z`AFmkQX at yPl zl`(VNIq4>dJM#9H9qkxJW{g_Jx&y^S^j}Roy4t=K`OL8pfM{RI!S*pr8FZ4FLSo15 zXa%n**pz9^-8u%v6eA=;gi&TtoL+OgvT%~e%w%%Iq%GMFHjWNZYFU|AdMSu;Rgt^n z*vZ>RJGRH|rkQ-Dp at lFrE0~dOd4pQg at UVT0Eh-megUegyg0Q{6V>>QJr)}n5G!Yw& z2~2f!YTA*u4+QRJdox{ufC*MXbA?rhWyoWgrQK{BL?arP&sN2t!d?LF8#ZLYu%q5S z*tUIpYez$9#}R5OcXqrjk!0b#!E#n at MDN_8jTNAhOOmB+m4VD6)WWVvIAsGZI(KZJ z)S}7pBT%BLf*u=xmtm%4y*+HJr{6XsnGI?Ow&^$NQg3ooiLL)1KS{o~wkNauR$FeX zR!XRjM<)48!j_vODY_^P2Er|d`v69^;hrwW+O}Lx;uJ&F7eH0$ty*{7oHj=W*YZ0%&SGx2>JPAe^ z+%O72Y%B5!CIv!8JktYJ^0q?x8{pc!V4j%369(CWxv*^@5#zRz)Gzk7k(*Oa0Ybff zm9h5OzAbEH^(}5H%f60OV8};Mz))@gY)jxz at 0@g0+v;h at x-V+sv12=N=Myxkh!lwj ziv#mbOP9oVHmclvCF**zDN4y(b7r zDcd`5BNE#aehVApRHgGB+qblGj%4O3^X2$jAnmXd&;VdNHE?9R6S+rUgGmll7cJv=io++;HG zUMhA6J<_`;|-X(}pRS34aF&Gx=IEsV$H zZKJpGidmtA`=sT=4$5+DN#QNqzQt5BPWh3AM7d0f+zue2k)?52AoDS2VjFUfPl`zc zqS9>1{)Sw+0Yx+mcgP4#iJPyaI2i?bC7lBL3XwRWw8eL}{kOOEM8 at Pl<3GYzgMWPW zkFVla>C4gIzYJdv|NiCQpYflAfBf^G|49F+ zzZ!h`)mL9uU;4oMZ~2Sx*Z9TYumADa|H%Il|2g`f|4b~MzZz-bY-=W4GyHAz_rE0@ z-}~aP8ejcK{m=A|E+V)X;mgrih*AB$j|dt&{Nit41lIoIi@#QX9Wo>RNB-yVACjYQ zbMjZiFTeUSGNogK69P9#VZv7bXb|B4CF?!A<4BTZ(Fv7V1%N#>d*1g)Xn?Z2=lzG* z)LLszkN`+R5Y$brwU$lMYJb^1`(gLaPLn{XtUTAuJ+i>wdr(Akh8_{_=H?!C$|@sD z=fc_S%-OSN{271RoXY8 at xmd>}cp1bK&YN at j`Ewq2722J}YKR=Gis at AS)YKG0H$Ue5 zrR&-Y4Xn$R8^DaHl at OReou4{wPA8}GDFLAgAA@*^$Ov$PHO!g(ET&IR*AUO?Q>Uif zl)V^!v|j_}&zZCJbMWQNSwrW~oVKU)GpGIOaLP>?V5T!C=d*L?&z~~_uNMH)Gq9u+ zv{a;M>%@!nJcA50IdeKeW}8!7Ucj<4r!T0GoU;XL<HCxC|4nJaTDopL{B7k=dONhCr5Oo-o{X~NQ-&Q5^{>W7)Co}8LG32UGn zE~MwHT;VKu;LoOF&Y;XxE{L7Xr$7RKAyP(gNP(JU+LF8GOnSNjH>NPZopL9Q1Zb7M zDd?M>#q^4ri|$y`Bb at S6&688`!d$5Qh%C#?fn$M at nzL4{1H4J68Yibtp1ctKNY2*@ z3CkyF`H*oajZ;(6Z*#Indt}63u;&fjx8$BtXO!>`juSs$Ob<_-y6u&!=3J^YGDu~O zwFGkqYz!iW>nH8We6nyVfOSsIlQRxZOD;j9li3tZ%}K4HAn_%w&>Elyk#;9*lP4z$ zgF!J#rC4wSrRKG}3hJ8$pUgRbwx%rs>!KkBm~!vRJU-{OyrXjX2OjB zskx)?(AH0-WZT5__{2EUBmESt)*c+uXP0EV(E at 6b+5!SIS;fGK at o^?hD}klT_XXxu zg^i6IMVs)NXG^M0_z5 at 8RA_Sz*cZv at 6rgzNCWOSoDLdYnz|3b;Fj#y^BK5g18Rc*| zS&;_7B&P^cS_*z?Bd)VzxRJ!9aD|NaeljEB%tRC3yKy0r(>FB+S(@g)Bks!v$YQZ4 zOX9qn at ZnLQ{CgW}l?+}V^NC$+;No>-I zF7FuJm;{%fIFo>5i8vY%PPQ+Q|oJ^UW< zZ~m8ne+wVohsyg8AKt(J>8JNUxu5L2#@l!Q{_nrr-wh`*GKv4$eQIEmPaoaK?8C?R zAIt~;-oiHj>79L-z5Si`)!%;q{kPwK{q0wasr}E*Pd|S$pUlVn(?|0$#oFel{QXb% zC-=_2rH|n^_iF+>`^nPAezYHw_u;4XUF|LA!OQ-Z{Azz`{QL`k%+K{tKmYuR>o-4q z{NO*BVmhNf!0ESd;e`7wMbK7O_gFp1r2d_MYkqJ3#&~M*@V8%o{e|JvqW03)cFTF<69J*MSO%{i*)`r+4pudS_&e!x(0*86iZ>zaZwHKZQ>YNvdj+m?QtL z!f at Zbee(t=rN1_ at fcYi;88CXlm?0jxB!1bSlIW0oqw&`|Uw--7|2zY at 5d23V=0JjoRufTJfLy0fuZ1-!sAoT= z at Z7ww5m>w&yfJU>oBZt?^Txn)`%6=@E1uX-89 at CIKFk!MG8 at cY^2Yp{lUR(j7~?Q` z0$v4Rp1pnd?yY_6-G9UtxhX%Y2SiXiaBv<$)k>$gOCP-yaD-9sT zOf$=G->BtlSUOB?rAiyX^y3GlkI`#+k<}U(ow2VC0Rx#l3fFziKfn(Q1Tw|ey-VIY zAdsu62P5pK>TI at xpde5Yh2(W!fLYXw2cY(S3P9c4(i{J}`HIA<{LJLD{@kZT@|iK8 z9;r#+2B2wQF#-T&&?oA;V2X*{PuV+Eg_A1E$c)nK>MI1TAv*~?e2KoWL7N8m9` z8cPBsIKsg3hAhN1HQ{=ARekviOZ~zT(?zOeP;AQ5k({T4h?lh&FJFEVoZQDM;>A)Q-aFt_ zMHC=?Y at u&7VEeN9>g5abLfRFY1PjozBWk0FJ4*#a-y)H3F-jR^US+R=m3?VmG+(~> zWMivKffpOL02bI?1S|In>%8DP?$du2x%h}R at Pqb+?q%L(^e#a_MIK(gG%xK7FWckC z59x<#TIyyH`#PHG8~$m$244|;1s)-PSjb|b at Uf2YCHN3Y;@{SU+izZn*X~uVWn6pC zU5By9ZO6WEVkHxYDce0#$*^C&dg&w at _u`M|(zCD(35nK{pwzUr81yVcrMXlF11_Rq zUtl}oy92&!Ah%e?PC6XIKr?bm7{Jwh{^GfL9^h786B6(?!%Cym{Dqf+M%cYmn80Vg z5eVR_gSXi$aKgPxUb+|dxs4s$OK9ASTCF{KT?5~UoxybOdHxLZx!7O>%H1It|2ie( z#U=Y3J0Y8NEL&T&BX(;0x`El)Ihrz~dtsheUOa#P>>0-p;8c3{ySFLQ0f+=8FV+?d zB+rXCBKc3!;I$=a5{&L}Rw^U`tw3Gj)pPqyeB%43hOk85XAs&1F0r80A`w1Mjv{O3Rn8K0itlp|>zr?8bl35%U59R&*a_L*%*zY~_=5AhXO8cDKK%6F zqKRnviM8%i4*dq!By4!W`7=O-U8?m43{*ndy?d)0n?(kkG(ynGV}N~@KYwPh25F=n z-XiUZrro~sNV|XDd`6?}>9ePB-s*8p5bjy>%Ax&PKC7- z`?#0(ic)*|f~x?W=g;8KQzVR)!-AG~ut6GYKr3 at Evga?zX7#8FkDjvnV~eS2capan z;dKJw;{f}-iV&VX#mhXE$58k%jFsmtO5cAYUlZp86>a#A|Nr-ovi$$;^6){2d^6qf z;SG5ZMxGn+-?rh|I6OnAHtiVCC;B*ECb$1+=ffrZ-aVdnz<2feSQWoVk16;^9=rR8 zJWJyAnV2?x+{V(B&l_ZXFD3(znz6>OW@|8=S)l7h-tg_4nTqZ_HN4*WCa3 z_WR<&5dK9T28)j#rCb>>m||)^(jyPlac-Tr^gSO?;)8B{3J~$};mTk at qLNUKMHf&6e#np z!?RL`kCN~SHaym37DwCU`x&tn1fqGHdMJi?W=kG)az&y%X(_1U2^Bu8g+wIZ{*xdv zX$l3Lh);DPEPV(^!qVr;kOM;d-M6LU=|;b}L*|Fq9g#0VDJHR#XPDS6Zu|a=c*|eN z8_ at msyR>*-2d42kpW-<{L_q4;?`mRBwD3R^R~G)rQzjh5C*y!GX&xVfWn~B-oaTy|D3_R6ZfXXOd!#ev2nS*f&s6Okyfe z)PY>`BrcuMCt2YNd5p(A1^I*vM6~gj(&9+_$Y4(BBJ2V2V(t!kIuVKbhAAgW1idz?thU7!d6)*j z;583Qh5KZ`^w~LlDhjCvK%7Y)HgtUY6}}=M#itD=4L7A=Z0k`~akULg^D#C691)3;TUp0oSITut3RSBhM2F=i)P5DH0kZlV{12 at 2UzIxkmz3Ig)?AdfpivKsqEo z`0PPwK3mL3$_QS3)Y&hdCUSx|pY at gElISGQ?D~=}M&$PrI5$8CUd+A~T72N3luZDM>zN z$|vT*f)byq1WWj!r~XLt*@^iWmvrNHoi*jteS92DAKVn?p#zef!VvTjeF%_GbLI=d z59yB{scs7KTG*LIH7ri}^zqqrQAslj at D}6!g+Aks$H(}*{X#s4sE?-8Kj9}A)W^w7 zT(fwJImpA&$er|DJ{Jcr%ddnRwq%eUNpj;BSBZc+k?;X?JXX!Nzz#xUcSy^1iz`gL zl!?XD`A8*(C3vO~qb>;RzMVr17cVL+kTdW>rj12jk|5jo zFs9HUKK0N1p&DTgbFmjz&==5Jez9F*kKwpBJm(iZ5Hq#|W}Jr=2qj`|gKYo8`h4<# z;X<;Fo0axLeeP5k38=Dj7e6r<5dGjv4zhK86p>PmG;7 zF*bI>ok)(?j~zcQiVjDp?&$GBw|vHL#?vu>!W_>}9QVilF=$=}pLJ76YlA@@6OE6L zxv?guvnRsw{Md1G+#ah^1yJb^S}+AY$VgCqsF`>fg at Ilz>^3j_kOBC$xrop^g(i4=*YhfFIZsV}g)qA}sWT zOfzPVn-lhU^BCR9Mvrj?(Q>S+MNxtX6LmO+HQZPk=7OMQ3r at -SVi`c zb|Bh at XwzfKXq5NNWEtvMLuPa!Tz>q7fdykk!bh`_(Gkp)Lh8)Mfww3$X1oG>;W3QM zV5c25$J}T>0 at cY*)}e&~KC59tH-vF2L^a3_XHGD4l`}J$$z(- at WabjG9%*gILL_HK z6zs7&<09NOjN&kgw9HW?76YpbB*;G*W8hR6btCwyff5F`0Sc0)9 at 3A)6889KIKver z^`m~&j^JA*BFS;;m=+x)6fh^U<0noWx5`PUB(Owjbi|H8qJt91V1g8!8Bf4TQoVtM zau^E);lvScqzpwZ6{j3(66__vDkmiO!ZI_O9~*U}VMM^&-&Miz608dtziIQAL9juKP+9v(3xZnBO=@G at Y91z|GgSzNQT zrC7!)+3?5+7%8-*EU6Sq0IOq|qz_^>h&7D3VLOR$3vgzVNGV~360H{ZRaLpsbfh*s z0(<(=kQ!%o%;K0R at -(!=87B^uI#uhml=dL#o+zQL$w~m|;e#lgi89QMH4qmg)0$4f z7Bi9&3OnM58caWZK5R;UIV`CUWK``AJRz7Nu z@(TlOZ;=^E!Buslgr+cx4D00i(P1;pv|%G*Gr<^Q2DYNb`Qs(F!(&8abi|K at VT;M3 zgKLKk=-dH8$@+dI8Fu(ra?Bt{DOeIf!{}T(Tt5ouCdP}>n<4Cg6G#T|q#?D(=20Pz z;NZw`D$D7P<%_bQUq)lMvCVnS*a#xYM~2<7Ihqp&Wmkyol75B~;r8fJB2rFfB%g(% z0i1`A+N0zdb#j5ve8mqmaP6;fmwgC13Id}WPO;jw^iJK>ZLD;oB|!GG>8D^{b3}49 z?2pC_8E&2O!?yxT|5TFXKoUe`hm)hVBiif}&{>-ZcX_EUC(V!<0&$6300QR7%+Vu9 zC8bPiS=y+FH?m!^UIZ);&$+Mh-2)4jtC!%cWR3NTnTEqrd!*P}OCSY04F0qMb#f>S zHvyYF+B|afh&h5 at H0xqfB5t%!4v|92x{O>1N8J&@xOnp-E!7{d!UvWV+OXuu3bV3d z#Dk>j5F;Ktv at o`@MDvMlo{Lyn#KZN*@X{D^BS?<=Bl!@p zk_K;4UJ;LuPEcs3MdSn?HMp*S^Va zfH5H2kp>)~3$5t%Enf>b>{$hH=#Ly_C86#}1>=qk4IxJElL`rF=Vc)m`#(rfL%57H zIpT+~f9ab_9Z*75mk?|?njQ&5q?!W`Qi<%#*vf?*j>Uml?9p5^oefEhn9XVnL+#6k z?ljR}>FzOcMnLQlq#2<6*r`eOI9N2_=_Hm8wJ$wW?!o0~S ziK&y&te~Dk+JQ2N58z)sck&Uuebz*L_Njl8K6a0q4=H_6z~CqKby&4NtA~v7GLckhi|G90e<^st at 8QD-z|KVtHe#1T&+#y=6Y!@`jFRHYNyznS_ z=pI-A6_5%W^{J;$$?7FC#-A~NNW}wl-#{Y#T6Ic5E>hEhU=0D*JjfqDFc0eY$!o}K z>Nzz>^K3>`9)Z_CNraQkn1l6$`}Zl|rJ_z2RP)H12XVQ&d(eQrh;$a?fDoCl08riiLSZaa z(uOEkUY2%f2&44Ft5LMWLI#Dv{*gaK at TkED zGk_C60Q8=@*SvoZm8-%y+nWW`9Y&Bj9yBlyYplr!B#yaX=b-!d>^=Lk68jlNMIXDF z7Dxi;2(piuSptnQ@;wZwtpyMv_6YO%4=sP`MY59c31HZJjl1{mv94Hw9FRW-a0T_q zPnt52?Ebxbcgtk{S0`q z5Jo&l@@XDhjKNk?C7HFsplM-+`hEB$DBrQq>%uhbDD1RxmnJzMCg5a7ei=!;x##bi zJK0^?(q!+{AwlYR0CKv^=7!^|Cfo7Lyh2Lt+BP2Pg&hM?Wb3qlr-)o)`J z_636K1ypIX3aWC+K1h%PV(Ku$-VQReMbi!s6Re*gmElg~HYeiSEG1R~E|TjNU|aMD z!H<*p+xTjTCWUqNt>5Ao4zL- at 8#7kB)S)(&!coA; zZfOB}9Z7;q{!tz&0EM>Vl%*;GAn@$iC}5gU4c`}VD3$K$qx_o4Eh5(vrm+v at ZMdy_lv zw!5W!zmwh;8%_Y^nhta+&36TX-nq>s%q_fSc>09zbUlcrCCwsbwdJI(y=`yhG;AR` zyWynhoA-1B5ZtW}@wYv!t at ap?_q42j?tZL)O;f8b+?@n&H*eAhcCp8g5}`pPIJxTJ zOif_Eb34HJFE^byoC)@}kd_1r`>+Ixu6l5EgA*7os->3~DJnP>^RP%B!@{ z;CxIRz&Wev;DAjSLs8&qk^G at fgR>vufw)R at IKBX<=gM)4^g$FP5jNvlxI8b?FDlc_ z^Ou!FGpXwqvMCpUGAS$2T0|@w$lR$haA^B zyD?)X+=7ncz+%cy4gmA4%tZ^^;HDfui(hs at JB8oHA|xe?c#`g-h3$-10j+a!n(>^@ zs4SYrQA5HJrmetum?jqC#54wWiz&hhkUUjYmoCRsN|`GjB$_N}6GFAJT8erTe6R~@ z5;G$ama0fvq7q0ikwHREh$J4*!#u%fCQ!nWBDS^#kisP>VQ!qwDf$sLxGj#m1z&KwD30|MB@&_ at sNS|n zrZo%e3}Mm2ay)9b$OjJT)G-T?VAWEIq0o?EBjmrs?c%81cs49(K{kR0IKfury@*je zD2u7v<@i-#^UWebEk!WU^_bZN|CI6gUNfI at 6|!x_jxd$69#l(>3BSEe2K#CeSsQtS zXcX3T;S8)WHGi9hKunI at mB|;8;i#3uwj|+u`iNiiapta{-&`=C at v_1tlnQ|o;+A!0 znHcss5Lf=-JXQ%`m_dRIOy4YQaaSgW6&yz>^Hrc;yeOVxJV$F=&?xf&TDoDx4R}B| zs_Ma_h2Ln?NEg&KNjRxkyO*b{t%H}P6eAfLfWW=Zn%|+bYckk3h;F1U7$85%{776q4*RR}fCn&7i^y=et{ec{;t3ZnGrwXd6bDo# zhBim)BoOu at qp~b>Dx~rArC5ZXU at LKw*(8Ft1r2cxhdwWuk5g at 9cVR1HD992!u%0Ij z0`PQbv!HE$OnX78h>4WAlpq*AUq}cnF~6T5Yf6eRNY(+-HrO0hy7; zuifWwN;&ZlXGj-~1^onPri*i|w7())yCCIpxJVq&XqP=FZVISmWqT=}CCm&jnBRtU ze37CcAb5311p!7_OQ%8s0hYG;a;P)*kO&ELfpdlzwySUP at NQU^=QrlD$lRh1tz5gH z;8r|f7)B08Jlz^ep4}H0wP`SI1qCYc!0rX}6D(h!KaUOq63ouXage%{Bagt2d2WYY zRSs63N1IkbBy@#YEGxk!CRD|O`6V2ZtP{%iEU5DkZrPgJket at R=5Xvd>t4KSE3C1Abcy&2 z*7(5-?>X?dxTEL+?UGL+GLxk!U?cGBDsZ&?pA#}dgr2}3q5210&vPe@`;47$aam_T}j zqp?9M#_zoh`*x1|4iiaGh4nwD2kQ32gCe{@c zEzO at UT!3+yCs(c<6LCNV;f!K~gOUKzOFy*dNAS(VLx;`b=Apxf{2_ag`V#~%YKqiF z%rLbs{gA!RPxp at unW1nvKZGInP=4@`Ib;rsXiLR8E}-(5hvdOe+3`6(R&a;2Lx&F^ zvM at J?GD>2U&g-Jz4g2B$z$#pax&TxJS!n3+(9q#voJao_;Y>3jsFDg(tF`|w^ z0H8!xI+6_Khv8WfM+GAe9XxRC*l55i;#Q!n$oVZqj(i9bKsw|P*AVu>Lx&Ffg9zF~ z;ZfCz;zB|@3RPw(XVi?sA5MW$PIoAqi{|Er6KH5+SyhCF4kw2b;&SMqJD4B9yeiQ8 zVGms+8A=dSde9uO5MN4=B+AG_kPNs(|3PmK9^fmA9U^ctZ&bl#8OSCK)*WuZd&M+k zyyl=i(1-$wQ8}3{GA~3s_0T+h8e2RwIQ$x{wX`Gtm6)(@W_J>1?LnXf2i=Iy1ykDr%0L|s(@*S zF&9&tz)S#v!!!F2P+4Zdc#)b4Hb-FG;nJZx0u)Exfo%VQ{ZUP{!=*_41Zd|)Z3Uoh z57_Z=k8hbE|5CTfpHiuH%L>sL-GRpb z18~M*l@>44NZthCmJgWy#59)9Gz$)8LFvvF4%df(?N`3|gMp(Err?ntRXWYGHXKY2 z~GUGl$tXz1rBv343#i_dN5Av_GkO{??cU`LgvL^Ov!0|#wjY++u<(3yPyKD)2FmmL@}2UKW^grPka#I at i~o#D~2xpyDhx_(bU zxZvT>w6YTGusWm(6I7`_f*SVOz5F@@vg0woWARy8q>jA+?$`r$)<%JjEVI|_ja^kV zc_wRFDV_!oO=Z;LX-?JG3Sxub4We^uV3kN)T$WaJrFTG<(%tb0` zPLSsEfdroWz5Ewk at 5ue*>ZJ-)HuvduR4Uf^s8wXt(ZQBep%SB_H5^ zvR7-T*d?95HBcD5^f0%-gr&_MC#h#AYGXC*164uOz4^YqZm-=VJ1u^(wfI8~vI(U2 z2WrAl=@uLT>vV5@&)z+IhJXMV!u`;;f;Z>{0U3+5OR#JMww$((RN-Zc4VooJq*!8R zfAs*e#UHyb>}_Ixx5p3tQ!|Wlz?I}flePz2#E`Nu!d(nDMXLAY9L+A!a_{S2og8R` zd!z-hW7sxw!b!Jt2TMimq(C>>6DJY(wU;J3cSbn*ULS)*^SpO zRzvlK>hDi2>$*RyNauR)Of0wG02iU;^~+TU~fvAVs(52phR-F0_OK722$JdU7DKP*&V0|_SQ6J<4|?-4sRg4>XsOaTA^t6slx(Uz3bP^HFuTsSKtn#pgtF6ixAPVNQXxX zn^&)0HCHW+>kL^0YoP_b&nfz7b}X`!Ul-ubwfgmI*JNCN1)QQ5sp2BlADT<16ekCw z1h|u3xk_8Ad6EMLO?B+{Eq^N~`Is&zd9c at JF!@yuP_?$oD5x at 8(8b at L!3vn1^J@(L zYP~QHAgTvP)r;&V42|%hLR{dqM0z)?uxyfsnkW at +uIFt62w`LyDVL&J)4LT``wgKq^zSNVzt9hf!+VYgrLs9bUi< zio2HEkj#?%6;Pj=hp!2KCJ4=B3M* za8T;q6a}9AsoQAznQLDF0g=NfI-T>E#|Vv)9mL$s*`&0=)@gq2szsFG&!x+p9w(O) zHtO!g#kRncj_Y)rQP#B_Gnewqm)xbsMKT;;C1Cq8V1N^*aBE7~*8~?QVF3M$qU|#| z4A&?i7EM2ArTZ%$n18-}=^}r$a{O)P$lS=4eW_&9T&{pjKn5?)dWjymNI&MrT+hjC z?h{O?1(3kST at IJ*Mb~OLEP)I=6dJL%R)UgvjK0^9}i5>;@R9hxI$ASC*a~ZTF(7H ziauaZ(49xA>5!!w0>6kCj`965!>P9*jdptJ^(Ho}8*FlTlTfV2B5`2;j0BEQc=)aP zy8k;AN*zrJU5*u*Aa`HbIY?9zgJg||<~A>IJ4%XlGl{dc8!iQj2?t zN{zF=9BloM`31}tOT`wuCy^qap;*@POkKVK`h&ZbyuE?5?EH9l6PscHrv75`qq)G1 z&Emb1)pQ!!TouHOYCvZoE*7|GcHvY5r?sAk88=>T zl6Xrexz&DUuGHCn7*P`W#DjEkfa&@8)~={U;hlP8zyxy7U5qFC;^=4&D3;IyCa&QM zw+~5`1F$@N*j%vZ(bkInH{<&?^~tmf?JKCXARNv_b{85v^%V^&Y zBxI2=uTC>w5NU9iWK>!U{Q{~QE!*e$!Hh<{uoISPp>T+jViv&bd62@)yf4%kRAm^1 zC}9Teyo}Q)gIpb24`dF}Wbxk=_BFAk)BrE|Lm#k&9|R=0GZ?yx1 zZjgLoCHf(vKzQ>Z1Pua5Ua%?Fgr>MYHklt~Datb$VKH!{DM4}oVq_4q29$k-iby5@ ziC!j4Z065Pfg-RcGz}sI_%Q?%4Gbdblx_a1iRbO1b6|{F%Y(InK>))v&T5ZMs&KI+WK|6puLNRHCJBmS~EYXh+IB({a2dkWU0CskP=(s{VhqS;ui4Bw5dF_K{ zpaDaA at C}x1ApZII|CM&dvixrumQ7fi!7o5SQNVAZ6uuOIp#G{sff$V24%FztpnQQs zU at PnVu>1yuK=>tsGvEhQ4gWXgFf=tfDEM?>%^3zUfN04`C|nB8zs>tALBKzSL6YO| z)OYcdO7wt!-e8KsxcL=mH3t^VXDt=MnfdK-6}Puy9b}g=)n%>$Gmv4e`E%lIOjAut zIWnLPVS%%B2CMPTRsa=Xr8T69FKJdWV;D$Ca5@%=Xz2tNhGxgr11UqaxIQkEO$SPG zX{r47c~wak*wlfQ;Tz|a@<|Yb&_enS2{&)BjSCB0Vmlx~qjaTs{k%V5rH1lfKj;VQ zoN;g<8E9`6Vu{nbdH>H~X&{z!tco^u*`ZgEHRu at w`QU&XX!Z|)1-K-5epx+Y(U at 33 zLI%JCbEb=8Rdb` z{R91YNsh?~$y+L&W=^EH87vRf`UeJR!{#fLfpfTunLG`HU$7zgdy at rj^niX5Vh#1*s$tYIaz&}v0y-rr%4dXxIh-ce?>jxS7akV!mjQbmz z3I6goe>pK`60|=6>V6Q7 at eTCbekM)wEL|N_us!K*%0R zy(Yx&%Y`B~uq80~RUw$LroGTIVXnLkVbp&^5MWY2Z;YOG06|E0NX=qOG7gEP7q&ki z=m!8GRC`Pf>)H*bK$*eeXQ1Er<4Xd>$Iq63%wB>ijF5?>!%Qd*i3Xb;f{Kos0gqYD zU^{CQfXSk%zJVZC(jMDmM#N40V$g=Braz-2zQ5GU^cq>`eTE}&Ol0X zH<5ipdR~V{*wG&VrM|0(n?3s+7#BehR1Lt&uy&v6OJr$5KN)m1lj($_5d!oHK*rS4 z-#*}`gUY&mTshLI42%F+vyVxYw<%`upKw=uN%3DvFiTW&i(j7pa3H at 4Vj*;h>MEb7 zhy89}vNzjiiv2*7ReDKc~J}k$_?}>ZPlHyvxNDh0IO0jlush!_cRfoAz?^J19l<(2Alza+wFGosHIs` zJj5U%zB?yv#G|-I48-low(r=n-EB_+d)QmsQzW%6xbO0kQd_Jd#vZOwP{ua_$c0ET z5J*?aNd*IrIb=88k-%DWZ`2*CU`j)>C6QR&He2M9x*}0$a%kXoR*`d5(01~0yV+L8 zvSwdR9978;E85)wbkz(33A4SrZTq%uROCI_ltkEc**a#GiIW{wCS)6477j2Ti$Y5! z9Z>RsFuO`?eP%sVYbgdI=3yMMN_GX*l-ZsWPq8!GP`lLK1tt|`rNo$vx|T2*nrm#c zW2oUuhl31 at ghLBtMH)H--vtl5qXFl+R1=}vZTVJ&S{GFi<;l*7oQK_R7s6$u2&f${ z!%^F}rL=dDUB9r8BI`)Eb{47pWM@(^it(Cljjh|ZZH?-r+gs%zj){&r$&}U_qW9aw zwrnfxXSo}rw!26Tl!S#_5{BE6Zui^%$>}km&^5KSBLK}p8B8)eayAMF2ISkf`E72i zG&puHg-GcVl&Xe?$OUkr`(Z0G5Fv%2E}Z zXqyw*J|n`-o40H(aASPP_RfqYY_(M|C)YJ zEy1M>YoL5XtX!!sQUm21Ff%4)Vp3Q(;1EqyoiA3RHWKbRMB at w5*h>5mvDwl at 1a@;$ zaEas#agSmLM at c&2s at q!Un9TsmZBF^7Nf_;R)-|05%|W{xneDbVFgp=VH~ZZgv!le; z-Bmo7$yv#)7{NZ at ve|8pdtVZFB+hBX1I>{`n6L}5tLj at ZIvhJrA}%mWDOFHg{WY7i z%}A$-7{(3NOToD9CE)Bx*v-HrY;J7YESW at iwM8>aK)zfPX||eeLcc^)vw4%*)ZDyD zHqur at PElG;IOQ>8bJONcb`$v#um`8peCtjvUfqbv116`$5=BN=2q+FC=!#rW$J at Gar4i?jx_~P*Njl}JU%?|@ z>?R}JIe*q+{tOp1VTC$`A^5|O?IA|mOa5ZD2tq!8KYuR40A3B$;KghZJ?)ig_{0}K zuy+vD at HziQstH6E7Z5_^I5&Hn7;iz7JEU+MWz={JX(UQE_Q!BRKIzxX3(w**EZ%bD zaS@|xvIPoM6lbqav=?~Aq+X86%fNUir9ImH7M%K_P5_vO`sDGLu)tHlut$P*y`2e4IH<>#H0v2kG zYC;K;Lb%+8aNff#g&8h7It3+!-!!-c2}s;l1uUu{&2#6^xpOt}71uf;QqP&E;B{4U zfpid`@T2NjxT1{%cq1Y2V8T6j&?!}^QxJI-s9yf4_u}EQC at _K-t?^DUT+MieocJOd zB`X1}An%AdZ-3)8VzZZ5>BV^26cG1h9|b9}0e|G0Ni$2=I at 5dh3?t^c=SaJ at 48(z at KS|+m-_zzfk$)%mLmrLL? zMYMo_^{guBn{83DcgE`~FUzM2)D{W>K;Q`Sg}igUM^> zn3mab#chNQ(OtDenAzfj8Yn%&fB=eRI8)sGDHjV~q5vu~tpf{_^9{KrP9b-aQN1Lz zd6H!-gr&XGinY!Q0Ffl#7|Z)fY0XQ^l&Ky>sSs)2-x!OCV0$>>Tej#3Cbl--FshUa zK+&J!{j+lYm)s|)orAT>i(47l`Evp3 at JL4qncy*%n95z0D@|!r5gRlLP&CB4YF(6Q zPWdTZKnD91u{)`ef1Lqscs(TB5E#mHRC(tij)r7~8_3U$-J{P{*nSW?8ESZkC5Vli z#vDfQ0#2Ul%EnmSRckKP;0(tEM=)j9~|`SV`^>wPz~4>rbxj!aMBaSNf*SKGRk?A`%2}Wj zz$kX3M0$E;NI1jGQn5Sf^^qhiuMTySJo*$3$)fnU2XaTkWp%t_vN#-A?c}V at am+5A zh?vHW+<^aa>vN}q94KsCbK|g6A#1#6wgPXof#{vL%mdSBrb;-B)##0VNSQ-9#9Khw z?RdK$57fm)r#xrV;e1!gYa$6^UU~bmPUQg}J@$yqjL%GrYn$cU8YdJ^VA#4sGvLf) zGah7z(gwvo!q&-qS>v&^ygc(?IB{`Y8g`MfFOm!?SKf_WTq8VtJhHD5q#g)xky9tu5nDF4>1`fF71+sYO$hS2R8-rra5 zbG at QiV0ze4q{D>m{Sa6%A+#YHC4KeYzP{dGh#XN16NMoR{8jY=Q6spn>&vh%6f4SX zlrGXJ7&(Y0q7fvzZ0O5-pcQVTM{*A((It?QLFDYx&_$0`zH%2Hr7E~(r1HUOrK{XNh+WM-*m_M!;qO6KJ zqRX_ at KFV2bYMyX`0LW)#4DuT#qM%uhN>0>#5Tlr<%ypEAOn+6Cp at IIikbU}LX!=vK z2h$n}(e=)1N?s=qWG3TuI8mRusWMSqO?&Yz3L at rJV~Z&l_uAo}=}UWK&~vb!?H9%)is-muyhwEq zq|E~R00e7X3O^-S(%SU3_cnWaMMkFDZU%%1wqGhPR(>&Qxi{{seM) z7Bc|n%C at IY#Hhe_#MUGWx3Ssu^|r$`)LTt(C8MR;NfmVTRhToGr>)oYG<$n6i7Y9N zsZF~AD3me{Osf(v;hYvDa>ir&E5bNdW{gSf*%we|vH<+_JtzT~;MFF2f z2l`v?t at ZTw^dKTpenYD>p%IX8L{b~1{11%4FnUwLFpR?V?Z{J=^YW*x-MP2a>v~*o zopVz|1>r+~RSS!LVD2Dr(BbGsZ%=P;kLjt(yqFr~MThN!`y_B|Oo9mp1*VMVbqquD zu%QU#SWEf>qAe9;Wmp at M2&=$!~Pj z86)Xz at 13DH^hB)5j*_?y`ZAw?Bg|spd!+oOyEsgTf9?RvH6{+L!_S`H1S?zoDamq1 zv(_fL5DH1jF_?xd=!h9$24+CIsCp?WE9qckk?Kn%BXFdQxy6#u#)dAXCH&RI!OZsv zGf`+Tnl-9$}1#qWq7=NE4y0&P9M zJHFWxNWYPtuf4^d5`FBp-7VsXpD7~eZX_X*jY&^U{G*4a)g@*Dnj at 7Wn3okuWsA&K zYBf;S;+)=+WDumVWkhfamkJ$=!siiTfdDvt& z);Ga=!$yO3s_QpUkfQ4%OxSIr4LYb3Q0TUlHsb^Qjhi+m8#B&tH`EyfMOsL7+cJuh zafFE<`&&_C_?dp#RND9l*4eNgW7dl><;6FM6hW-RtVn=0%tpH*-?$-g4VqK;;wt%8 zC?Qt0FS{jU at X6)`kQ$mBKsCc9)~{z6etY!iPqK-nz_59flUU5g&x{40(XS2b?RsR+ zi5$P38ZWh2x1|n&2rG#+K^bEj9`eO;7Hej$p(YBYsVX`EZZrQt*RaSa(X$)#4ftg@ zG-bKzb?etbvgQkmH9!?AMn;$@08ZHvds1!6Ao zyY+S*uPJ)ttHdceJ@(1mnC8Yu(Q;zf!FKsGVAj7>j3uJ8rg!ut=evos1Oi6+?ypf zscj{qAmQWNBN~2)%Sl1lP07Y|gI}MDPirx4)R^s-f06xLHg76`wa66O12bP(xUwN zr)os5Q6S7SDtNFTvaTnzd_$+te{ZR!%9?YDH+x zvHQYaK=5Br!lhlr_{34KH?TO;2QkUr at UTn#xS zu6|=6Z-Fh{b?d at fzlK0b9Y&V=xbq)hG|>#jlJy12Jk z>`HZ5$Hs>}jK0!Yv(_Lmq+ktDTkTP~0l=ffk+-QPlvXw76IO0bwtCHKnVK~4n`)So zKOVeE0Kd at Nu1(k2)%cbHO2T97k;V}lmqp_U$dXxOkSWZzX0=%j{oTk80uTzaBuG5U z1sIgphBa=rHe^x1`TiibOYRT}*l$@@0g>0>-)bpv?xzHjyN^N9hObk at 8M9Te^XQq_ zZwMtT8{K9-@`snls^Xqo9e_3i)wc}N_}N?(I0xVwSaZ#4yE*}U(oc2skCsj=*sg5= z!L at 5ZBg_~!r5o|~12vYeEdedFDqoGAfKGea(UAaG1ZE^fW=+mK`PGe8t5?xjzc7$u zfK;@J{VM^mb)*X}1q8Wz6^gbBWMjKekby`Q_Kbp8pbR2_`RY~hW%DM!V~DMuAZry` zDHa8TU}a}uax%^pbg)tme(f;7u{*?8gd4RhgEK+ zywPwQzesb!Zalli(AuoYm?D|61&?2e8KV!xCP;zCf<|_lnA)wfEAv$=|FV+W4YiW5 zbQ$?Lo%igSiFn&xypE2S*Z31Q-m_yybILxdcE{oVWHK2hc&AcDZ|1=TSGdZpxYGzL zsdh|d#z6#0C+$SQeOdm5JMNF+g0d)fxG7Z{ihCH{xF6Hic>z-uNA2-;pws;70jHv# zQYbSiOf8&joZyvqyi;pNY)_xUhv at E9eF|bEFQ!Z;d5vI at 3lNX z#S4E@jV8L?yG}$QfU77mEh zZz3=kD!zE+tg2q|TBg>8hE7goG$VSQ9X#TFh!jI(0s~|fbD}7g)__$C at EKiudaz?GM0$z at nnj#?q;<*A6B*WKZIIF<}9Vr5}D-b$&bSn?3N=CEG9 z=|l&!Ah}*|Wsd7 at ZM<^}moD<)OD;sun!`t-F&o~2IXOWAGQc<9^QQMd>OER|&!1dn ziTDGCQMjT43#!|3YF0p4N2CZG{NM`1ZWxEfv4S;lmFGNk#dtj_>(k+QN*>6_LqY3D zzl*Vd#T4EX7mk`E za?L19mDa4&c$1oP-J!J at Ir@>;2;#6cOm5ME^qM|iIr^t5^~g?hv^IQ{C!$Rjp~jm* z3pa`s1mP>10p`Z3Wvc!;Og>vQ at y<=2G-^jufd*H^VeTVn^gfo2-g9SA)=of_yC8X7 zWIW2y9?6E}HcAl}lLB+SKGI-_AFV5OoE);sqXq2|93cu4;AOPjiY^e)@;&(cpGYb) zrJ>5giFd7MI at 1 zwSGLQQr6mY}GWH>Nw zR?22$Qckd%4&~w{bXF!U9Sf5I5G)?J%A*D;jf2d*aL^%J3Q&#P z;xT3zjS<(FB1-TWlO7o|Lp7Wc$I>Dzc-Nxr3fv!C)lPeR#1F~&mb{48wg_bPVnTYM z&C!F*@wiIE;|cZTM2piKxq_43TDuY7FMQ at e{E!^{zUjxp#ll??m(N{I2+;-S% z9r$WtiVOSzzB^3in{Ni>Doi5p`Hp~_Jdb1zDBgHhBJbklei?+B2{r)Xu|tqZB2VMw z+M}P5A`kqm9y+WQ#5V$7-Z#KnVF!x5*k*uy-P z_7K{mJ%L-BwHn+pp(7&EdgIBd{;)e#JE+CUdu)rhXz_+j9tkV=FCywAh*}Rc#<`EO zd2nJIBj$e2b8&fQDbL}n%A{ftUFNmdpaipCVQ(4cgv!z&wV+2zHV-P1uq9i(bK at 1u z0ND18?c%v64I)(cu6=Wh!rZ)eW!Mzkwmo zpk_-&fs%?mhZ*0~I9`)(RTLVJWY&$W$_Qu-@*c)79(yXs>*`_B8MjJaa;m!l+m_sw zsX#ioZVx2~|8hXIEqn?uDj7sFe6UZo1)tgTQ+xiDyZ_bI-PP5NKU8J#q67wcmY at C? zzq;|az)#}S?hMx5uDgyAq8`cz_7Ik$0!IzbbWiKtvPc_k`C~B_)2nV^R1GY{xI$y` z#dYU2YtS2N9bHf)I(i+oHY_08QxDdLA8q)@28_D9Tvt at XJcU61@Rx{U5a}dJOd!*@ zcl)ks#@Z#y7z96xr1u9Q)F2qZ~wM^8l at B_QS2jHNEof>a+7=@t4& zcTa%01Sj!}P9iPcUF9w+P at -c(?16IBrjAmxgTE>^Ndgmsj33*Tbpj2l`cQnz5hzMN zrW+DXPj`rh9bIW}L(mcgI`HpUfDWW|z;rh;8xlsOd`oea^`Yz0u8fpH5QG^5>SVx> zUKRhI5~gz$rkGjMMXaiwU7eCoS(5fa?5GPdmXr_)kgl6^=3UT^gpVCPRff#@+Lf{D zU>mGVmjFbS9RDY5i~?B}PGiE3u9?m*5Kc%pC(eY)IuHO9E-r~5*Hwdion2iNFMAPz za*evgM)-0Gy@(X9!&|(3cNLC`w{2a2&=?-`2eT5*qz8tC$zXvw-QRFs1tv%)15GM;RTjNV78M;OTpp`5LgZ# z3pvaz&ryP82>2wBxlYL?z5vkOkbpYSf-=nBMeH$W=qxdFj8zijv3e~gx&tuUu2N?{ zw}-_7Dxx>Q6vZM&0?FSaBnZ6I%q?qErbqIj9nW%W#z4<{Ws78LEW_rw8C_;BNFjtvi$!AV zU?}{N@@eaA!ZSD5_H at ivqE@uvh+3Tcg&v6eG9_J!)Wz1y!eae*C>)$C>jc)VK8;38 z?t;LA2*_6qDR9nu-Q!F2W* zL6JGqmLy~ax$u+DWlO?)tRk;`W6{=eLQzesEZMAnwPNiODyZA`qMD+xEYnPtmW1*4RcLQjYv#Z!oZ9$@$(yIWabv2x{# zJ(2bX|jr0M at SZ1N_za6IOfJn^pMCpXLt6jb+Ql+N&~!&?s6|*1%nk zS?%C(0ypjQe8qCN+%78=jv%s$UDFUt4`HRYdey3xc)68kMZR){U14+#mRLrWHVVYa zS_(^MRVA9QT4 at nyW916CZ{d-POPIo=3NeH`YgVVLQ}~{&$d<#{E)UC~I(lG2U62}P zlqnp-OeGP1MRWOz>*8IS?56<~upVV5_7gIks at UA7FiqP~(toY=1ps~WH- zc78dcQh_%ZD>7mkX(2zrC$q8!$N6JPa=GZGaMw`TAl(ZzcgR6R%JmdtEfZNnM5n8GRj$!5K}K(>XuT0Gsx>~{uTWg zLV^&OC!pw-R+o^d_~Sr>@~VpXO$L}1WeL%- zhCPFMNHD at 8+Sp=9_sEFYGGbA)m<)KIF0HFi>`EfZhFCzK;W%D&Lpc*i22A#uy-;2R z&@F4ghh<9xjLi at E63i=pB!C at CCi^hKlLjY17|p&}n!t at E(&M40s;ueJ4oO0>u44A( zOnz9}1Q20K{Reg`6m$t at RvVR)+%Gd^lvTu*E-_2&58M=BAJuFtWgS+!6?Jibd0 at L; ziY_FRgE&7_m;6vH(8>!LgNDo6vyv9+UC56v2jG8L;+E8YpkHi|hoa-5BG_iE^v`6O1BZ5EpADEzI zRSExSm2BEnJRlA~nAydWVry#k_!x&Io5uP7k08j*1bcyW9tL7oRGDg}vHL%4y$6>a z*O9H+$7JTM0!W}x1r$&Yq(~UT1BCY;B=uTtB=wt-^hAl0*xf_I8rJKfLyX(vu%WRbJ$4Jzb^Ep!!K4=BUE^9@!~XUPP{69q5Yi z%JAAN*YdTRuPZ*-_p+od&zL~^i#HzS29SnVuc7=`Et=%XkJ+hLa9DLf{I4trU#VVc zt|fxv^j!s)|MvF=3mb1Pl+Ul$SlqvQ?dnypLw^swGCPuGsqdxG(L+?5vEf>m;n&zP z#>Ju(Q$;@fc#_E1ulyCat^y!KowIBcd}IULL~`;bhN at fuTXij8Em>0=Tn at 9QjD|!7 z(W_SmieDB6SHo4SY{~}hJI!ky3tf1nzBbUx;p*^;l11*fpoSkufc5mszI60ClV23C z`uEFB68%+FXMI2wu9 at 0qF{@rl*G5;bMOpk0U3q2S^wNLp=>kFEo4L_Lv5uZ94cCRM zkHF;0)hk at Wg{`X%cq}uL&uh>qyqesqT5DWUM5sQ|s!z|7kSOEJxM~f=y+FE=7uUkI zcs1CC0IGO~@`WeD at JGfGW2{KE!gadl72FZ1T5fdpO1$B3gL_IG(5>)j?*P)PdMbLT zjo#oEyEF at X?U}T%{O^DLH@&S#ulD)ZH~GnLR&-{|zoUm55`t8I6PMFln%LjZmHxGQ zbMS{Z-~2=PgWAxki{2aI4WzGD{FQv8*KGCmnk`K5cPOd_pj!0#%ddFx&l at Ndu1Mv# zitdnY3A7x&_78jB^jA+MtXC_rWmBTj2mGCirY6OIN77-LV4mhT6CL!t(N%aOT~BYj zkWgMTh8#D3ztxKS%p&p4Wo%RTINr~r#$(=hDqdfe^O8uV?`H1&^SYpfmXdg-PyT&X z`gShi zsoFptrRY`Ys7evkJ1m7%D$E$a5-Rb&Cr&lgZw&z`7 at yo*d)Hi~y`sNsPVs8qNWcBd zZ+`1y_W$)3AHAT<^+Vpv`EPD~@IH<*2X_etP^l+h`c3<8yH-t=wB>J26sGBSE0C2A zk>;yjDfOGT^h(b{i1Hdd`5OKZuq*tYE|lr_ed{Ti->7I535g^o`a8S~A%snWT$>ZZ zreYkO!SGfC^_DB4QbGLccR`H+-%t;#z!<>{S(wb#D7~Kzq-{}3$x_9xy zEP7$-HF@@aNJyi1awTTp^AkKEm$E8=Vg&2JpJm#AsqxltT1Z6wl3Bc*^}_RM4}_%`gY?^hTBk+&cnxTah1Zhy>=j>nNnhcXdW3p2$ykgRy;z5^ zy*~Igv_wspuTo_FopyR3V7o}krqaEv|LfOHqI4c-R7Bxv+|rxGM9B=C0w at 1w1wdb? z|C|a*<(j7G9!jYH9mQ5E)3uN!|!V#QlMxUr}lH{_2pl`{%gvU zc8RufSOx~j$-7MzU5vpJ50$!%rC-N?|BG)cVgzh>Qpjs|jQmE;)aoC~4#B+2Al3YA z?-`5$lB~2sv*g|1)K8X{Wt4g^^Qsq)bq&rXHcwiAjsAzB0F_u>WApFrg^+KM_34ca z)4e-kP;b9q{~KAM*S|hV$7Dg*#EzI6mCdbZS#elT~V2dBvuJmiWek zB>}#qmf?(5|+UDo?o{dwbWO59}lkn!}NETvB%xR<>BQk9Z= zmgyWPE37*ot!ID9#I@~9_G(EAsy;0#)Wxx3^q*wMFMs(f(cG%uA1VTc6*629Cb(8J zkk)y?pzkLlu69r{(c at m)iDot0Q{Omsn#^y)6$2~~Tx}3-C&tLa-ZoNCjp at ynZ?b6V z*vj`Yb?sC~NcGkH&A4T(L*QK_!!K+0sTzDm7rCTNFZ~JC)YCiil`Tc`Duu{o;S{bumlSDyruxAI`&dZT7 at Sak2G{GdWoq)h#8};QXYIvSQ>5pkz^?s6u9*tf7Cp`stNTIo`1$i zuHBm2*^m&B at _zJu^L&>TH4L?5E80!iI(%+Cb>145;#0b<$uL}6=_;GtgpJN~aceN6 zC$_dSF9u6d#IZHct;%zKYUW$ry0mpkhDH|_HPEb)j#0EA)SU`T!gCnF+yGH=C5JB< zSW9-1`lh)jp6fELrq5gdZ~kM6AN|q%Hm!tG(?2XElO6<{an07 at l28lJ>yJTUdY&Qi z{Bxb>`et&6h11pnliJvBF}G^fE-6?BLVm92ZicM``I@$lmbS*NUA$f5B2*liR1`_P zGHS at nY@V7;S=WTEacR};XZ)na2Y;WN at cFl*dx;)`lJINzXz51@;Yxv2C>3tEo#{lO zJ|=8kVz`S!CJ5CYK`t=J8&K|J^>bS%Y~H-oI=bfhNsBJ*6)oUMd`_5*wl3u*Df<4? zd48jrwRUT>RHU~bJwGv~N#z%slK`V|Jyx77T=Kz0t%>L1pQ~zOEC#V?!NgXKOI^@( zx|CoAu5jjr=Y^)tZ2_n(YD=9Zo4fIOO*x!sv&Y6YBEr(d5)?2Vq$2+rFdZLixhs+j zp1#>#ElIu{g1H;8#w`Xyah0)41HoP|A)|_}q$9!M)3JCrveW`t6K`0s#OVys)X*tM zYr+|1F*f%#muZeA>ns>%nsQ~0`x`wp4aP~&er~NNP)CzhqN6foDJ_M?K1j-X%u9?8 z&zI~ATMLlSZhdBn^pQzIo1a>iH4bx`jQMQI&QfoY`JVG50**;XoK+A@#?`V7a2Ee! zOUt}=UgZI~Y@<6{S1{2(ou%R85=II>8=b8LZp9Sye9hKnAF1KZQkTIRC|V>bq>Wb} zXYT}16Fo+*SsKV%4KuGNe70w{_Ix%a;mAB&G=qmri;h%$-YB6$8J;sLr*VQoPMf^5 zG}2XuYK0Ekr5_ zW)6fgN9sjOy4tE-^b$Ym;Vg6Gvf0h>xTun}*7;G;(ToPpnx$s3zo5lcb-kJk2iq{j zyMTxASl}&AF0OTET9B))kDL(9#ZL*0SQHmWicYDs;-+sB=8OVqpz%vV=NEA6io7^l z017K4Klt`#lv3Gk1hXw6%%a7=UR3gHr*Pb?hKW$x>L#C>I4%wV1B-?Wi+r_^L1lYf zyLG+Tp?}ryYH_dtSIJe)D5J~nvfdU1Nk&E>tHzF0)&i)7KVk zU%NE5_-H|<8h^@HstKi)lBxta6=H{_4q#A{$yJRfbyT92268Pfu3A7ad(xb$o?GkT zr&eh)ehl7Z8I9+_JF(bZU~ztIu4gQr8V#`AbBrtylTbn<1GD~B#`%wC#TUgZ5oP`( zF4aP8VUf!g816fLF%?`2)hnv8*=w`gug+px=r1nVB4rO|JPBVGGt4T{-K<-~{17=T z=tPK#f4Tu5xZ7PL0QCHJwW}s3g5_{NrW4ml0pXQpyR9u1;%d6uyK?mk zKh>4t zJv|^^ZZ7pMnp#kVA>H|JUd?_ZKbpqkqQuoJ`AQEnE?>GFFXv06i_Fmh`Eu0M zmoCPO79J^*`IXMKd^JjIsLJ~CuYc(xbB2o(*epR%8eb3pFgE(y at k+Qnx^g*TK$%mT zGt#P5lfKad9Z at F#4K9_rn2OOCFI*(%eG^<#3yuNbdRkUT)21vv5g}InjT5lbf$2v z!^|QKL8>lQ7t;k7etgDUL^-)zUri2=;!|B5T>$dl1rwDrYOHf5UL9RQ9ILM6%Y#dh zFkR{}h?QL-{o8og%-0oFjUd&xU at h#F`eFGg6%@O;6Ad&QH$ zM5t|ORacfTUlBj#S0Z^z>_I_m^v_=)KJnFMi>ZlJZE%FNNM$)3#jI?G#S8fO&WlMBn;i zbD?YekR}(dMdFy-se=b7P_ZySf_-%%o{w#1ixA$lMX8rpub3DYn7t21Dgo3WJ`{or zBY`wHf8oOUa2^E}{oiR{UtLy?Qtp5 at VOF^O1DBd*xN-49bs?WusgXQPwkicx0yHpL z-Ebxhwf_s^YYq{L?rKfx)mTak&EJ51dt_5!0iAI|kuJLO|h zm at Sj?l`f`;$Kle7!em%y0vpZ;z+RC#TqfZ5t87wckoIM?-&EMTCgs|_fI zOEO&*9iG=p3_fu8T&shx5E}nHU9aM$#&}*6Mi4oFE}pZxXr at 19M@_OqbNk?Ex-dEq zvsPH+{JHUEW{?!`rfe}z$OJx>Um)mZ)au!DXSKoLLuGktMJBh*$`^XFUtwyB1NrKz z)Ah>bin>)CtqhcKeh7Nu+(^H|*{a=i9X_#kRd{<+Q11eKG~QI_T~I=zHOx&Vp7t8_y)Z7ug(v^yv_S>U>X3l}SDDx=oK!3PONhH) zll&_n$`345Mw=f5O_$^|p8gT9%N|}T%M+|>H$#I*r}^$uTa9o>)A`XkcpcBxX9s7_ zo;{P!=-;&!8gz+$p+294b0n1cO#k#5EDU^`QthiOj1|<(ZdZny*p<%KXFRVpmv6zU zqt3T6D5JAyf=q%R+E&{lT>;)|$>nqT+#_Ll_RQHcvM-#8r(LS8xxHN8614m)Bm-#> z>aeDlJ<%nLW>&PuF>xvXR<8xqZS~;#g1teIThin9SG0dqyCoXyz46;O-uP|&ZH;oG zxA-tKla at wt-XKh`2C^Kcxvc&1KLy!*3h(nWwDKYp~o?HEPOFMoHEj>7`oHU@^FVK|{ds zixq!q68(tORLQzPg at 2K~{sP8UVoP+kD=#flJ1%dP6#s>{)#;M}@P87cUxS62j)Q7# zXW(0BJH2g-)@SNzxT4;CVE9{Pm?x at 9D^ZlU?Rn$0(oyV5M?7`C8FF!k6axu$Zl8K7 zhF`AgBE8fp)0?mU at 9|%{G{BL0($e}^_C!h(Oub+7f~hBM`nU~CMEAeJ*wqE2Zd&IS6;pE19 z(>Jx#D~OJ_7Kz6NYffo`pd60b5(j!s^M1Y zKliiO1y9gRJOySlf|i$~_05X5)uVcz4Y6EiOLYprq&=cq=tXj5+$$^6bSeIbc= zFBU9zRF+NZoG6~uLgHsXgCss-{AK at F-oR1a#v(qI&{z-8pR%x%DgBQX3%eMsc7eoG zq1U_DdWIaeMzgDFpJYM3_sw3trFN7x>8$2htygLTmKe&j4@}=M)ZbQC0hfAiRi)i1 zkD?5Iim3st0$J)(r{0~W_MW^X at BiqQs41&yvZ(HUY7w`J4bsS=Qf+eJFMrW6-c!aZ zfEtXI-kU$?sRo*inFig^e^Y%YpOltN6RJduF1k4A{!GmQ?^K0d&jonR?>4W<75>R+)mNJ z+dHZOUBp&vJ{D<}Me{ek%XQpS)Qg}s_tX-?p>5TtzJ`C&qYPjsO8O=diJjaFP$STz zRh{J`LGD|-y7<$_dQjrmKHmFimNZvSOZ5U%uQzK&s7`d^uii4q3jobxC6G6St7zC2 z|5*aKHKBcq;fX#=h9ElfRf-jLRUA#T`P-e^f%dgvMKYW#+m zlB^pxGXc&E>`JRsl}B}mY at dQkY7M4EBF5{DbG#Z4W1PZjP-l<01ZyUzH~VS(V7Cj5 zY^$OI!`7sZwdv>9;?#QD(ZUQv{`sG*Q>?v2Gkfv>qVOGhXCVwKSV^TcIEsq$8VdM1iTowP}@4YqH>>oQn< zG~<6?#S!be7fTm7J^NMtQ>1>W0qkSVEYL00RYtvKpmi|CTQy^2=is1#+3d)B+dOrG zh3;&!))}zv&1X+A$QFM=C*{VG`J2q3qpCl!qbDi!oYp_Ggy*LEZd!>b7Afz-PoXbB z>H!dcT*NmTdWVkNbSlE*YW?${WiArk@;~b#44z2&M@&*(E#ExU05M~@ccxU!OMBRg z2Ohk%+8Yg3uGGy at Infh^)qb>)$2&gi;Tzf at dQ>x$)8A8R>S7Ie+=fu?4aY1kja~z$m5#*f&FBvg>B$szIn+EH~}>A3-siB=JC2?1-E8Pk}VOOk$tg=Mpd z>9;OTVvtE0G9+S1XCcmy7UsizAIl{mnEo{>$;v(r?nyVmMO=1Q=NIzA5c9*l#I^Q=8T5|ll6f&VZ; z11SS9)^J6&Sj)+3AuzZ*zreVyOV7B-vc>_#)5-zV82yR!Is$^Q*(zac7JH0hWf0RZ z_bx0n^M0BDEpAEn5lxL-Xcm at bp#S9gDc5ouX*mY{;=&|k&|+ZAe{8L7sj%2JS0&Xc z0Aj)lfBTPo7zm{>-vuhgqaKUOL>7-tU-Q5jy7j_i8Bq+W<~zuz(dENhgbRyV4mAru z*pTvTKF;T*fq;BsP)7*JHJi7CDX_!MsTn<5L_jqY4Ee_A?JV>#69ba_Ih>nE*d`;E zCS)zM+ni<6XFaN&`OzF|`T7>};)p|z|6=UK{2KF|X(U?-tF;RmKpS10pOzeLh>|cY zuEFB8P%#7#8CS$-Iciqdi~r?Fdh%Fo1noEyzEX)B16-xeYGs|c<5NQPJQVXlWUb26dTtHT13 z#h9qkwIjKPfpLC?k$7m)1avDjsNxpNfZuNjN1JP85=QDZG0<%OD6(|ngzwJbD~Fl=FJtAyBNHDw^*JB-i3%Y+_=#+ at js z#_C!06h;Q|9Q?26!u*gcoJyV|tpzse1e1>D=ki>4Uh_)ECHLfyCuk6!C0uKq5Y=2< zdS-FT at fd^B%>Nlsn1>gm9p+S)8=$ z>K3sI-+jC2@}K%&^#XHqL0B;4gc=1{Hr8`bm&>*8go<-EM!WZ7qEJY4b#WT3jM8yo zpotCNxiHum&nHz1U3neVgQtRYEBja322s36Jf|5ePEEs6Uu;md} zo0smV;qeUMu~j*vdt2tTa-8#TB1USa6(_B1S=kr2R5|A7o}EiuhI3ntjzcDHL_~wl zc#}zC(HHD8Fu*hOENtn`8E-oat4{QVd4(|AoX^Zf?6iRlV$um9chlqP(V5ffbUrma zdFo_5X}8>K!~s)VVtY#+5$6oI%JZ4}^zhW_Q^`h$ljSTyl&oqx$~hCW%H4IlByxA1 zTjs;lr%ngEHrTc4B>J37?a2k0gjO*`gD6XdGeMTc(-WuiNmpvz at P=`=K;#a8eY%f1 zr%$>0!3HL at 0O?q06bVfspJ{B}>Qp)zPe>?;Qb>c3$VdwPGrF~(n5;fkpB$Y)$qg5} zrgcc|7)vOfGx>CNs(bR(sgoy9fK{*q0HA5IJ8D6cpdxK5qIw}E;kP+a18>E|b`46Q zx=kJRw=84w?x0>X4^Afv;{`h*nRaDL^&dCZmgBei7ZoU{#LP=b|C4^EvveJY$< zg%icubRt|>HM_VnobQ-qbneVqNk%2Yik4j%fw4YOIfoLC)@N6l{mf}hN;nXKF`Vq3 z5R50niQ(}R020niGi;0llGC7Df;PIsV;l={;)xX;=Nf#FGpI*WP{p0fC{Y(K2Ms)V z+{T6rjoFdU3{GQ6jRhinI606N;lv{iJ1%((=reLt(~41!V-)J?RCBUA5svpy9A{K2 z(Me_L%B&5id+?E;h>L+gofCb{4M~KAX=W&w26o*Bvu`Me6Qh$SqTo0lj}K%^aFH=1 z>?!qHu2(0A00)_mH#okGoy9>$#$!<$vs6_juhpk$8Jtf&Hk?}COdRhWGyPI&PO7o&ih^tKH>7(yF%VO5 z{Md1#c1*csLZZy5Nxj0>k{yk at ey!0fR&xYd#PWC|9v_`J zp6xPQ1Bsf8_>l-1PXAdZ>4zND&&hl;ixhmq at o=nv{MfOh*d+od{E$f6kh6uK%D=)4 zM#H||vEwo?98>KY$vCm8qBs#qts%3bQ6!%jh0x&N> ziK)Zco|T0&H50c9SOIC*lFukAL-Mh3G?onLoJkg_HdD!P#VVaxZ70vi21ju=QqUR( zuUe{{*2WX4fZ6Djz(lx=j^blDiiZtWD-A19X;e3;d(2uN9~ureaC8*H2yZJ!r4TuY zlu8+Lq9#zfqCS?622x7Zd7yL&#X;YiREbH%>O__i$B#uBaqOsBQp2%uI>Jid2II+* z?V9dp5D%ij#>ptlb16uL-;tzJVyy`0qeuCZj{2TgGo_;}7wZ$=DBkL9D`NLw)3#x9c%3(U*IR>5rH61aZY}$0%%Ca^?V=;RU;KMNCnvZyqwH?^2GC8QR zT}2B!GLeWLVGme+WOSH4P*l@*fmCv+aRlmPl?*+sP(4-SjR4hCnDzld=^2jpi1Cpl zFsC{q+VFM8zXH_&L6IHrJ14k6*9c=GcO|JAC8_ zgpLF#TT!PPMBB2Am{tZ;VW$@AU{~1$(kZ^!Sb1$qZHlTiK#403Pje(5);BrrFEYu~ zJUjNj6*vW=#~{LkKpg&b_{ibI$}<&atAx<^q?x7=2QN~}Hl at 8KTRPgqJkX0r^5H&L z>T7`341BfFMX{}HqMS06ZG$u!9X*n{V)QcL6~{7hY0&+OsK+sjqO>7QFfIbY` z<-1Z)4HXW;tyIQR24qgbq)K--dTR}S zwKI*y-}R%uU+O~TO4ycd*jv`lN5~h34GVj#3s)lH4>e9CR@!mD% zEqQu*nXHrUV!ia#QOo;Xh2p*U>9MX%!MZ4E%~oKjc~MsWrv^zxeZ|z%MJ>IO_vx|z zWqRXbmR^U%)L@&Ow_b2G{==}gGm<*mBs$dS1OzWtlO!a$cEy|8S?9AhAl{=+{%87R z{fW*oujw6*k|IonZN)X(2U<@qNl1F#ksMKN7+D#^!8*-#RsJ+;0g_(3m)LYZUv}94t z%%0SYsk9BEP0y<-|3GEpGx{#T;sF$26I2E0$Xocxb_q?x_$N90^B&mfFKjGCj)-Es7h7IMC zMqq81D=pDBCA7yn93F2NslI_~J*GZXr8S?LFWKq-CygDw$7Sh{==DI&oobHf$5)hU zJ*TvE({YbRA(30q)HKQum|v5M__r at WOBN;dAZv)I+p37 at lswO$kYuIQRFov|5mp+B z$qMh?RT at UcSDw6DPb#JLTvSWWf$B at eaM?k#I*t}&g}t{>uNPC(!oFIGnbFSd^y43t zPeNKyb9ng?R}~T3+ZNga!cQ&t)ECUSNXIA{Pa8TtxsZ4agr%(2p`xmud%>IOsl8;N z(_-mqW(Uh%e-_ETpH{85Y^8xGk80SH3W86)5Y at 3Ymvm6uzo$83T>^P3M$ZBaI3-M@ zNQKdJ-7`}?_ViqiI$@1F;Kgf?uVSptt`2+QAAayIapOFY)dF3Q##Edw at 0Tu)vRO^+ z4C?GHx|GIhU`Ja#9pt`hM-NKh#5Oh6`%{r=Tk+K#%-6|j>pa{TcOqQ4K()D;wtu zT$^`{6x2vkSnpKzwev?s30K(NnWZ^^k+HloHJ+!*u?9aK-u|dY{!&fYc^b z32*KAX!oc?|9Vn`)5f;CS1jd%mv)6TLjdbQ*glNnOGLcV>0u?j;)FFKjnhCD`EYM% zCogqIUmixQELO#}cW9tJh~ypWbU at n98b8&`QQZOKf9pB4-gHfs&7W-$y7ti5WoA~R zMa!b6HVNNi)dw$G4?GW)D{$*+VEi7 at _&IHM~`_6#lkt^4e!`+NbX95i>m| z!*fTME)zF(Q*Jx_Rig|2tC}SM^tKmq)BJAyO+cveHTyzOMcXzm2U&S>9OP4BU|EsAO2tE zDLE{FoCt znIeDHSvI`VqctWGXu;(8`&B)gQen!Pc_gzrZtF`*;Lf at P9{*MD?N?j+o40J<%suvc zGn&Bs!u(i2a=l`m)G8zZ?!)s-1BYA?H;=Y#&YR=HS`#1Ea>L$k)VIH?Ej^odbJ#rG zqKihG=hx1g36pcR#%4`cX3Ux`Y%Y3Pazl~J=B4d$kxX7TiNR!s?C#o&0x@HoM*+;j6Dpd=>N?#*Mwj1#I7(HZwbaXD%{z zXvlcUBf+3~#;r**q~45>!chQp58-Yz*K;erK1zzA6t_4i*`U`&2ujlHi}6xFN}No7 z(f`U at x^B#tw58e{W`Wbj%XHIW)*vaNm$A~s7Jw17iW64N60B<8K($}U*9b}GXXQf? zdSrYQ0l22xJY?D+(FhN;LV>3?e%GFk*|Su5L#?;LTh#EE)5MD!^$dxS85V)I}YD at -JIdn%0=cf{bv z!z2g8IG|w`rv)Zw2j=<~u#PVR$L6-IB~oDv?35j8_VMOf14FYHDI3kvp|r@>28~g& zv-y9`8ax7t at orHOS!K@#;)vCWc}JUP!>s5o5GRZ-npBogVnN!{*&H<}&l(m)am$>L zYm<$9QoLoW|?a;Z2l9*`cLtYYua3J{48Jd zmI;B8HxC(%;q!CFqwHav(7a5%W%F91f$P=mKu%c>7n22Q&li`)&E~!>95&}!BSzx@ zn5DJ3?=b_cj;*VJ%^Dzt&t+1?^%4l=P}0S)=~y(x>SpsA*;=e%sC`N at Qva}~VkKMQ z$?R!Xg0W)ttm7Is4`g>1ta2`bumfRiKv0$u3Y(S7Y74;yu&ze)X*|%Xm2qx at ob!-% zoL=ToJv-X;|5J)APpv|d6k2g&cDQMlh#C>hb*!9Vy^_k5l-aIDR3Tu_nhFx?Z6n3Em-? zAaA;Yi=PVq2E||#6Q%KL6I*^L)xqg7=``0ekjqNGu>jK=uT<#?!98(7VrA3p?50h# z;9kwGYm*9+DzlUY7+%jl#=cpbtQ6tNF;l78VzlMBan{!7x=jSq%7EopRvp-~dC~_y zGn+PzW~l;e+_0#<#fa2Sr7)G4{-DL}BYr$%oYt;e5Ei~FFlH)AC6Me_+!T#|M${*% zReNf!n+=-=nl^0e1HSySQkt2cE41kvrPqjn7DnpFc<#Ju2p%ML+1AH9u~A$VWH64q zU6(~U)+(zQCR!nGSz~ls)*@O)Zi^25r7Pm5o(Lg&j`O(Tie-&%8Q5i@!WaORU%gpl zqm`V*8l_xxxGZk&$P2e7ZlcI-!UdJdb~~M5RNwrqXM37dHbA>g%4D0eprdanu-xLW zgn=k)G~Mi`w8{3OGPvC?tn|grYiE7gAv0 at QO9!PhY`37=o*Tw#zs9K2x at IBjSf);G zR%+r3Xw7b#*yK*MffIpO9iWnaC45 at F+4OAr@{l$^cHj;DQWcriVlcaD?WPH!@$WDC z-jJL-le0oxm=kBM4jndbVh<@_dH80BLq4;`X?9&}W!TtZMlxZ8Lq^qxN}33;HC5a+ z+Q_dsHjuHl%jC_4Bbw1%uN>}bQ`q>YO&d3Q0x2)_Dck>A$4Kc$T;fLYff>P9gZNP) zsMtJgQwW=?*)?v!-)+>X5l&T at Skg$gGC at GMz#DFVr@_g&WBbs z=^^Pf5D2wf2!SU+2D+H1-D!WDk zL40|1_~mHkCo^9c)hAq2FS1GzrLtaLCQaH&T^WUKBi7dN<0dP>rDnFVx3;EG2FWtV;|Bt<|Q z9XTpV#S{{RA6--vKwZ=v>>Vh+G{;;VQZy=btHBY>54a&}_!1RM#8yX?PSY*+MD#XS zc<|UwN(aM%(ZK`Zz;ZDNizHAQkUd~$@KAuR)q&Cen($oK<9Eo(7}Oook=$v*Z=(6WW965f2UJ}}&WfRWLaPFEN!QZl?8phBUjQ$b$j z1HJtR_;5+9CfA0i2qzY_*c z4;>s4!%C-~N63 z_wUZK)1^OCE{BJd88xGa*j?{axzyBdj3-j^ z3~Wi2EORR;ocH~(e;-TiHS#L=s>2hoMVV1y0`BcI%k$o>!v$tQO`4^Hs}9(~1IeHc zwtTa1xOX3(HRQ0!QWj37PAB0J;aA%CXzxCj-N(ZC6m_kx?-I;T*Iw#GCT z*cvhCOj_e~*e%JlrFSPjBy at J&?Jg+E|h# zHXnhU4h&TEGTxSu>iK2G*OHYnDJnze at uzP=wr>o|JYS^8MFw!a zC+?2yi2i-5_)pXh9#H)ekCa6{MiYl>Pqx9O2wKXGlT;+K=HDu69=_Ftj+a z20)3E+t$hH$FE7zeIl%_>PEi0K&io0 at +NSB&MInVbKZnKdt;I$&W1bd>Z$WrwK=1C zP3h~S-airmP2LWOKb7>lE9gtQWnXi!Jkf%M_CZ)@eE}r}w6E65%vh;Gk58z6LuzQm zfy{8edrsZ(B)4VMLf1_a>o05cWix;lcXlSaQXd%UCK9WyhnkuzW$)b*FZbMH8pm9q z`Hy#F;-D_4J~{9SWYQYE31UK`NFbrkHr5nOeXVGf#2}0=t+oP#B!(dbs8QK!fr3NF z7_!vc9 z&5e5AY|8sbRp+`m#ChdbRF{@BZjq=Cg|vLSq=svXNO_cC8K8;?XZoslbMo$I!8!mX zDCNzx{4{Dh+dFes0LRk0QSzF&>Oo0rSY^zHSZX&WD0D29L!tOtxK)(M3{#!}7w}c* zV+T4P8_vKM#G|$snpzjBTNZ8a%JFbDE#e at ig6D%I$o3H`)s3pO5^OsJ>aca at i0R)p z2vqmD$_&tP_ekbot2kXVanzgS at J&tiOqNtfHGb4is_pEi7Fhl`!ss-3NYD1Re(l2` z-T+QcX;?n<__Wk8A*S|zyIpu^{!@H at j*Vzc at r%HnWwg-9wH zFPv8oC!KZG+5Uk>bJEjk{3I$*QCTHI&od<8;PDg~Vr?(g|52k9j*{hiLZdw%%bCAc zKF)L_^agr*=D|Jm$0^j}thRHEG9keUI($J?dJA5p868i4$Yvf%P{*#SoYl`9P9^Vj z at ZpN5YO*@Y12Y$_v!GT`cQkt-Ld0kfN_u3xpO*Tasz!Gi?2lixmfNA*62+RF(IYPv zg}~afc`vY_E^&S+n;uylhZLbzpeRwbsC1Feh%_AY;ztWW!<%#YI+9-wlZMkjaEg*E zP5$-mKyk7 at qZyC&REmxOr&GSUs1kesN7?E-CnC{>B+hX+NT zI%XVCm3FHNP$`Qw0~MN)>`1s5POa#u{Hap_;Tr)@<=Asf(bBtpen*e)^iQ4w;s~R? z51t;kId9K%B!B8bQ#POwPkO3&_r_?Z>cJ>c-&KL;3ATp~Xm at nbyI7PPyYyX^^N4 z>6uqVpcMSnKJ%bc_j=r8+&S;bLu9JZettdsqZf9-H=J5#0Xt+ZW8gBocw1_03-^|J zU22U^_GW{QbJU3=w_MP6?Npac^@UsYet%+w&R**nIj6_142V1aSXeyY)%OXj;{HgN zDvVA6S2kX!3XBYaW{|aJZF7nKbp)k-I4%0I-*|#eOCQ?*S$|z6xjmEP6jfChsN#|z z=k9asQypw&lJ>6H?p^)>1y5yght$rAANP4HK)0vtR5c3Pp4U2BEgyTjn|hfRcS=34 zFYRL at h4G4}o>$PlS5EVry{S0Z21btroj#@G>^%N;T;^GYh&&Ocb=`~h37g2E#2`Ha z=ne1yH8D)b^Sde?_~8fR%eigNY#V{q8qtDan^TLAm%;Yzc6kaRaWivpj4I*5)3aap zxSo?qg<_zC;(CH=^^_f|*4^A>xA^QT-|E%yPfqG-qMkqaQ;~zZ_bW8)5H?TA=-ZUI z-H;B9))Nk1nkjK7?XjcG{piM3sdclJZj;yCPw#R+eG{L_jiVWEzqcCeSTtJ4n6PPL zqeKCeLi>cxweIrkUcUahZ&OTeL{&s~k(A7^y4BklH at WG*w1$S2)i%y-j2j=%n5@?!IB zr&Xd{J+RyTNMSOf+8Abr7!fu;YK_n(VRO;+T)6?F36um(Xg03SFdQSmNE)TBA0-z` z9x*+`6rr`?!T5UPUwr(=jheSi-M68#xsntIByrQKq0g at lCXF`E#2GDM5>ulXt(D-y zKMu5dnu#XsK0BNBZxi-!769|<2aVCsj8>J1)6CYc{iRJ^`OQ!9C(MM6X{Ik&9n9i6 zdXITCX>m7ap52&ddK)dO6Aq=|nQ--_XVc=a8K_wa7BFp|os=LG5y1+KC7R8~=zb0u z#=lF?Uo~L(wN~NTh<=kd4mRON+E_DVm?=Vf;fV0FadFcM!`*_gaiu`6u;v+?-on&q ze&FVhBWA2x!?fl;kV6Fr0p!P$nBufz7is}RB0C))Imo_TH4{wgGl?}DY8F%6*tlk6 z&j;ZJIb&uB-Y{$YPMa!C7(wK*MNQGzIFn~=sA0m;38$>ZZH+srNoVSrJo{&hR*N2g z*Pb_1$_51(M)AweS7 at jLx7M?YrG+nH(_ajlo(VG*;ldGFSR_`%IXipsD3dV7RJS2a zk7i~~jE-JZ@}@@~5D!fCWCbE3YX;NAU20(AhA_h}ArTaHWv5xc>??r at kl0nxg<6?6 z<)}=wyu;DXOq?dg3||FLs&ZqKTfjT+uw_|i=JE8*v=QBg7yVbGF=Jhu?LKGYSol=R zMhnG+5nV2=nF-TK$rc4etwcG;> zpXp8G**KxHCjgQv_Dnj)D6WcP#+)I}l at J!Djq6M;7?4M0QeA8bGt1L6Ol}_(R28O@ z`a=weGb>_dAty_unc=j at klfC#+0RgYH$oyNo)RQ8>o+~?n2-POl?+V$<7j3&POr$J zvZmbc<4ei337d=ITAgc}nN_lz-S!9jB?!n&(KssYMHjBa5P=afBura&*0NL| zNG_x)G7N at wiqWKovn#Mn7Jim1mFlxy;st=n3p`aCU1`RZm9$DQnog%_D|*yul`@%U zU`{^4rp&2F*s at cW_q+?Q-^|P zbt3^nHd|!|(Frp@*vw|S!Dcpx4R-b*X!lTX`I&?|VdGlofYGy=jvIok at G%UW at Zh)G zM#qCaNhqc_gbg}#(0-X(>s84%I`fVoR5Mt-0oI2NqiI4n?fW2f9C^Z at fQ zi at z+5N-ilWy3TT73>%AqhWdn{nI7+QR#B9DzMa;N%QzbZWWr>v?-XzR3tgK&)y#@h z3x4UU$?3=PZ-YZ`v&$N2ZS|lwidc-EmDK{@(pf}oybjptatV|3(FzS{#OZ2-&U>Is znK)G>lvr+NuicyWJmwBIH`TcZy?a;KwffR7eRjSSUK;H*P1iJ+R4y1uRfyd3U(pS7 zF5I)b+8uU{cJGS21~2W}^-|VlsCL|8mwYO4H+M2Fj{&?w?v{c*P~gLTi3&m0KAeJwSY;w1zTCA zz=d61g&M2EOV!S_!}MPLN2KG?XC9KPC=(hFsu?03@^!^ojZ2!+!1%U?j%kXC$fhx?OM?S zJH_&}b49~l1&B%DIIqB5@&f|6npOUANH zd at 1dWI|f=K?ig)1m}(LnpGz8f_!2omJ_yx;o8RBQWBYd2LFRXJB2ggikmz=ypS77? z6FR2fu{~`cNdHTPeopVuGPcwK#Vr+7!}fu%koR>>mvo;M1O;sZJclps+#%|v9bx-u z$M&$j_7p;$gROgI3>L-R^{zg?iWs#`UhjCkeOpl@#chK#>{%lgy#yDkm%13T15oN6 zd3)YB0oHN%${w(z;5l1*j4*4%j*-C2+eh1qchQ#@gen&G?mpM-dZ~V?&jqS0amNok z3F&rV*}iT2cIezdnSm;!m!JwFcSQnzs-2a)m$xUd4tti{lBgONo$}R79fs9z!uSF zvvWu0-@~?~a{+~BwR0dC@$=4RN8BE_jka%#+o(f(t;i*J8shB2^O|y`BpI6tli|&9 z+xBgFTTxJzu>Mgcny|c+tPC zv=tC07(rQ1MH9{^mJ>UZ`CPvkTicXEzH!H=lAbG+MYKCXA*zIg;C<`iy7TCNkGuy~5(6B<|abbLf4YtoJPsC#R7JoE)fmKyGN=W)PELf&8 zFqH6*UfdQ~K`!PO!jI%&&#jQX#!n+!<&=(Ol(Hne*yjpelYRtzwO1Uah#-;iK?PF@ zYuxWJZlJLESBk2&%D_kk!O=F%Qru-t$o4Kp-h?I`!Rbvfd@{wp~%H at Mvmpzh# zA*Dv2P?`_5ptjIkhnvWic1+ygz9ccDRj=q>Bf%s;kv}*N8;;q*&-Q$J;$&*Yf_|M^ zJxQ5DezX37V8JDH06VI)^>(_BLx{-7tB7 at GVLQr+^Tc zK(;nBlKrcW&&NX(i|D!Vz0N8hynUAYrz z*7d7RCXNc~>{~gV&KXT9#igH|n6U!oNh#7X*`pi26z!H2P(N84?9i?q88qottp#0c zmhMTDz+;2sW}M8Xt1Iq&sH ztZJXXt}J+~O591Ff9k zW|CVVv(D&&!mDG;};TF?Tr^&XWx~Kw;{) zTK4HtnU>bsPMO;C0De4nbdt)ahGkAN;h~fFiSn&ILZ@%@%iTij%=yp1?3b=?XANMd z$m!24C{A{~ec4W#>d75WjOt8ucfVE0lgdm_pX8ke<53=X33x6Bv01dh^Q4BJbKssB z$8ryNKedWw5|5csAf1&u;cG|4_3y0!?GHuR(=;AXa#J);R;B!0dE%y*cEnu5FL!lh z-7?El#JQ|fsUA^onU$vvxU<8hwK7YE()#Ef9`XQAgYofT&kfgoHSP-dt}xI;KIvGG zWgv`gCwgi}Yeb92Ko{x2tGsCKisQ$$^z_XtgAGy3#d7;c=ag-YT%_G+DAbcj0mAz3qMeJAyNW{`AC1r4 zO(fw`2@;i_g<#O+5-`;XX3t-t6Y7VhOBqf8=M<C-^32{}sW#qWE9+=Vpqc6i#G1|kzx1PxH`+TsApL(drAAyLvgTw#7x!1lXi4gXc z(1I8ab-gxIrNV(#zsKiy_~dhMk3Ed$#M36F=OXk#T{&qT4|L2H#;IeezEu_6J!yi< z1PiEi+A~usTw;Rmq(r!&^!3B>DppRv at hw>&W?!AOvsj}18 zA&Z2mp5&eJm2oB5=` zCQODFw*f(H>}1|B(0FtaU6iKQX?T%9mVH{5;NJnH*$}2i8>XtM0f68P2m;Eq>-0>@ z^wLcj8TG47oi_B%ra0A^`HKlGX1)EF&>jLEGN`VwYYQ&*luLw_+?B(IxKygXoMv`I zE==Kytg>Hm3Y%O2Hsx^*^F~wMDn=D2Co#lSQXLfVX$_TPd*0A<1fQ95mCE!iPJUnp z3aC`1Bx9f1pr}nw*=ZI|ZA`DW;!$SRt_Z=;GaFVIZ)g$L7kPHDLynC{^DV7IZE(W*H2BY7rGEdY->a5#FqG2d$>VcG4D+E z?TR#$Tm-ZjN`aF_ak>L&c|#XN^qBy+INg)+qP_$&O}WPLPYObs8ZaR%8?>hTuJs%- z+$t>_oocm at kL%%)Y+Yv)@{aj*+8(4(|^stodZl)g at 4qC+)GRz<|VEC0aR}QDv!#xw+ zJ#Sp)#RH{5m`WSgO!b7atich&BW0F|^1&UE->OK_{MQF>T&IhGewx55M#_bG6?&p)-#9*a&W+cH*J?rOqoGl8!)v# zu2+co at v0_ETDb9(EY@&ixGoTulg0pBak6E#JY6&8bGo~0vD$JGXeNEi%8Qs0q$27A z#e~d~?E+&$!zyPf%Wjy8gc%$~sCvB(tgZA;`zWvZjGGV|@;9yT6j;4h*}@`2-(X3E z!D%zGzA3<2z(jlktI(H)ahj?jSHy&RJrK%FD;ZACNkLP}A2o12S7w}=SZ_70)9y8G z4Q8lKn4JntTCE?go1P$tTGAwokD)YS%dYXcg7QI`;4E`;D}kP6F0g6V_t&jozYab_ zWm#!F)Udc=)xFv7$Rr0DB3(R#JuXcs&D}&4gU)_ls)?Bm;N at x9OlA|(K=YB%Q4N?uwPF8vJo2G zHr#$S>jS=YWg7_CmZm+^a at nph=)FiXw-qRUQF_*;^*zAjG7hE*J(wUetN!ZmYHAq> z!27Y#8jtQ)LnH43W;RDpEKt4Q`5Z2U?@m>hhxZoMYl&HfywTXkuk5G2|21 zt?Dlg9+TG?UZNMKCfCPxBN@;Kpy>@yzRf(%a|?z!x>iW8UstVLZjF$OVvlVc_0(T% zefLn75JIzr|A?$1OD3Rt9Dkc&?rQ3pcG|SwQx}qWLadoeq=B*~uV40=x^wpVL_ne8uZEqen at YS#2*`uSDH96 at rp58Hj}_wTGQ%} z(tLdf*9cl%H+YtOsK%2|O#L9_onB0DgSkq<#h7ND&cWpW$nS-BNAJBG-wp4K-hC&% zGkoyQgY;l<|G|U%`F^@r-yPh!d-qPh6K=2E`H%m2 at 4f%v6W*)e9lrDKJK>%9VDJtX zga at 7b^}TR+aPRJ&yWwuSGrWE0cDNnh3+?>r-O8rUK6Sbu?)C29yLb2A-F$a==kA?! zC*JPex&0nXr1ysJzV~jj71BHD!RVa_ at j-3#kM7+~cdIh{NQU(P<2?p9 at 8)-^2gCah z?x*|1d-v~k?!~*|&gkx)nrT-wDWl$dHBU_j*jtWs%uO5Ae2n&|pT51>yF{9K4m at 8@-DeK6890JcwWt?~fkb54c!n4$NG) z*9>pn#>E!EKIjSXu32-BtJ1wkpaL{^?+Ez{4 at S2zCwic&cas|v9`qC>g(=?~=zD_ at QP55gO>R@?amP3GvJI(EIOR$XIc{c!`vr0kCcSoX#2eG;x z8Px~oci(9MZ^grcQx#5yyTm(yTy?uAZ*JcTx1g#sqk1P7`$mqMJW|v}zY?B-uFAI* zMgRyca?viVu~*l)bNNn<#Um}Ta#O<{u<#(f(|>RugVKH0iHhl6*b?vdA?@8e at yt#m8CI{`_8hyTy_?i<5PFcnIRPHq1OwqIl^_~WiYqH{ekSt#dmij0C9C|c=OiHcvEaw zxN2x at Lz9vT4rGW@5dG85TeoiB6sz75effzGCdee=ncB=tw>!5oE+$qshO8RL?<-vK zUQdkmbT$a77J|RI6>oNL$k}%uf3a z=UTrytzr8Q!ZPLOZS_5O<`lcUEc~fN4IWS{VZ1o^ph$^Z at zSCjij9W5N|27G^&3|P=ZRIRl&?46LY*@Rl=d0?l zs${USof2ThE8k2vSg(GN?)QX|BAW_1gJq$Cnr`MBkIf%1d+B~(qhaz>AyeTn{#cX= z$4#<9i-en%7QwUo{Q6rc+VbrcKM9FUNQQV(E~>o{I^#mIEy1|x`8E)l;?0gPp(38S zVj!r45l;m`kr~{&c>`O*jo!^0*RS8el;*wwE_|R)iu{5aOm5yF7}w3@@L<(LcmH1W zqEd|$jKXruQrE%EH{$gn5P7b|cGHP>8$&+BA4Qqj>l=gXl;0ZuD8yxPn0Wiv&0A5V zyb-Y at UGKGsueR)o7E4teRgG*AXCSVw`?BeN2Qxi0)1684sJ at Y}v#AIfHWj&{E$gwe z{x18K6=n-tBo_?NC at a&A;q at EWycHg+AXlPp~ zT&xLNLr6QU{#3P#zWGIh3Yl-+Y;M-=(u23JUw=Ek?cjO_i_PPD9*ri6G)dPxZyN_H z+wJn$9c^A#zuNSxuJ_HM^me>AQ3}XoBa6IO$!eU;k*IYT)OqqXWrBZmN7MD@?e1G% zeA$xk*e?6XrV%hUk7{tZ*^oWPrk<9){&pH)*N|}jHAC?vK1L{&h;Y46GQVv!NN)|_ zev6vq%&~7w!acWMS>h#ZHL$|l!?)gkE4~#-K>M;6OPx^BQlVHbe1T+{IK;PlZ@=}H zZ3mvs6`)m!0@!|(t2ef~au z7rqVORo{kh;@88kzWyqGHT?3cFTuKqh4-?KA0j-==TlH-oRSD1Dv2N?#7#lKgr2Z1DMKpM9D?jh|qI#t**z z?z?X@!)3+S@$2dJV!IWY{M1+WXBjugV78P$Ktg*ckN zZE!GuHTsgkX9AL#-C2SqpMDZPi4RBLf0r1j=*dfQEDGnZzD!@HFNU8BkVF(8x$!t) zQ|Cesi(>RB1{)sT>GWAm)$KYuyoy6{Eyc at N$bi3|w_4>66kJQmq5h+nVZ4%~&0$=t6T+z7bchaW$D zXavjO(879k=aL|JWLPc$3lMO at h#aIzU-<9m_;?D$i|%bNq+bUb3Xp) zW4LMbtnqeL4!HGwBBfYm60c at -^)d6x*PhoG&!hgA8YK+7mM2e)P9MjQ?1~CncD)dN zQGsK{gnffMe3Bma2#vuKG&(T9{wV)fzmg$lcUReJg-_)x6%W$G^zrbck3T9`wp=TP z=6|c0H8DsJ2U;zBoIXk=d%e)6FS`nw`8)cIDjT7+Cs=%PjWOD5bN(upa4Bsx-f~Tq zLI~7YRR+A4=M!kNY4X48n(0 z&P?90WAH~cvBHb^NuT_B_;Eux`X7Gup~g at L#s3Db`F64js#Ye_Ck>{@kJ5)F%9*XqBJA}Nfd0ZKZ+k_>R_o2&d%0cb`8k0f`8`WNAaWHhaY|P;fH3bS2)d| zD2HxKhWkCGqH^2=9Oc|cABGPD6txNmJDeL|d|u%uIr#}?)X^lrX5AqnSVcD1THl>g%tn zGGW=Ilq=59=ru|N?Gl`iup(>TuLP%UgW-$DcFc;k!^eYec1ot!w)}T=eLbd zWhZ^EjnAqejp2~ zFCvRm&4fV0g34GVBdZU>*l`b}>XE`dQgKv?sXyxC&xap;_(A%h_dba&F2N0jqHk3? zV6w6&5cv$HEqY1Leel8i;e8P_mK`TE`(LecD03|8;o%UJsjUh~=3;)!!DzRUxtPQ;TIS?{NVjBD+*=!yi4WKb`xzKV>$W2Qi13g at aZG@9p6t*D%F}c zl#!@=MvOir$CB`QzxSV3mNF>TrJM%KGR^4>sl{?ZM3CNB3agqbd8$uWt at PQRR}si! zGl6-(|DW&UUTnXcMCNYmWHw9^SXLkA4_4m)fY1B?`MwdP>`RF~*ou$#)2EqN6ZP=^ z=!5qoD>$Lsyss#V)x-M#W$QhgvJIUva!go)Br6H|}-yhznkwv;x9-)CIM3KmV-%Gd#|d%SPf+ru at zkswjSr=VN4=MbAHF5VXPx-!7yia_?!WirNgloRh at YD6suvmiFjlV~#ZRD;?V7Ag zBOijoYP}Jj@<|HF$0z4l&s584rP5wEMV;19BlZ8hX8P)V)XzR=Axf|JGaVkr>at8G zUP;34C_l=#rLbm%PFN35_9jYXIIucv{vWD#{8>nTuB2>n*1 at +;o;jj at SZV7(7d}(gz<9bRvYM-j zr`KQQD*f=?-1y0V+RLzzlcTc*^?=EZTuZSWyw*SH#?(oImXiedwzVrV&;F$s z?eD2JzWkHlhPAS|Y4b;np3gqb_*Y;6$aE%j#+K5BPYvohROqGQR>~G7esfyj+kvfQZU*8?SosPV426E-5bBj$e4q*}_`rSo z5thQ>=@#)x8> zh*MQ#Ou;weVN3iUW?J}ZlQLgb*FcfT@!VjQIg|LeH~9H-E`#7xoMti&*Y=+Zh=xQg zZ#e^Vv^cfk>+t at wkC4iNEO;*GHdHZ|+R!1OpfO&!+qC&pWI16m+Nv4e%EE~;CL>iI z<;%K3$xw-;u&}|a*dOe{63d@#9^gxfTwJFWem=ngsK~7mgDhbTkQJMMqZpevRR|Yu zc~SWZ>{6tq)BJz+7oM=PrRv zrpZsN4nn*4e6cX+dW9QLGS)?gioqb3{r}14ejA^I^RrKD_R2U+*l!~Ln~B`o#36$g zhzulx0?*W95DO@{O%0j=U$<#lU?s3+GRG?nqFXo;z`47;2*YfO_UdQY)Qbj1GBPx$ zc*HB-T*el9$y+bV$4zpGUh2ofhC7OjbPIqF@*V5^hCkD(0PPqElp_1Da);6mhNGkd zlOKF%2N_J&PiN>u9rIh}1Dm*{PUrmdFXl5SG?3p&g;_S4|8e=jJ!vIc3tlka=U1im zl6?RgdsjHvR>?bjvI?eY=?zi6fNNUt0!WhKeOZcD!yvpyN_>=?csYfC)XzueyegUi zqLOc?V--t-d^2ArpiqocK$%dJL^Ztj$jTCgjiq%x@@>Tp3LBV5>xJ^j*TjFfiBEip z`ylm!8`%Z!*vGHhnR!@rH at k~B{mssen>YN8*7X}VuH)vrQB);H1{=vc-DMu+uY24> zZ)rLIEjcU^Dd5mR0F=1n-oClGc(b_aZgj8Ta5vm_XAE-qwgvX%8<0Rfj*55a?S6jy z*5b`Yw-|1=AP7a#+~V6nYSyR?vP|5fH-+2yo5hXJ^&8i at OCjBi`?5h{vB^P;H{H!9 zR>jxdNY_O{dJHCC86bU|WJC=r_D0hj;YPUr{@Qii{WlIPLDTS6_tsb8tKwEztYg_l z%35H_?zL~zx9JXHsgiyaK~H=z^vr?lxhI8!>qtmoj6LWT&ifV#QmKG643eS)&%|0|y%G?q6 zhUQ)x#l=Fn2{(%C;hG3Akh&ykJnZCQ(GtcKWXhZIM!ep>hDF1*GON?2umO}?<0~q2 zYcW8C<;9yc5x(BU_SZWQBwRD%y7KFa7r?PC*5CmE9G?g!yVtMb=PIz(C2f?xUV_!A zDSjL%EN(@w^`gVwSY~wagPCor4by)E<{rEgU=+T3Z>Ex%pb4wc>fXRIIT(`U#PqAz zu7bv=pfmw|g-D2t|1W&SFBHQMtcySNFogBj+*NOei&gxFjF}H&I0|1u+r(?jSFcex z$wy9rsTc`=^bv>E9t=!uDH(w{?gV%+wQfi?rL|TrvM at 7ZnhaJ;bzJvgvDX{_gAl7 zgUVMI7R<>j8K^|8TApD1!;bMxWn&Tb-fJ-HU(?DA|UN0a)SZFd#X}z3^`{0)x zgxN0v<674iu(w;tNyQ^i6y}r&2bR~Hu$Yke|47KI3lQ|HuetCCPzI98nTDGU!D=N7 zMS!YK<@j}1I}2pyJD6MCYSW`}(VUs8D2qfc0pJ$8R~G`rD&S0w^*NE06|4}$SJOiG z3Nm3IgoYsub#0;=Sr~i659w-LXkXFB?pE7Ujlhx55r4DHY6^JE*PtM!UGNKx8i!!%o#ptzQ>va0Ht8~;7 zcD#ZQ5`!ga)0E!oN9?4lp0;oc at d}b&d8@=h8E!O`D~O6hArrL|<#o537A$V%xar5P zN=e8lG)ly_E$(^6U2%XkaxbH$xLJd7OJWiqGlXC0V3 at zsng8lm0*rBl at yG+3>Fb1q zTIQIQ%aVy1Zq?vg4N+c0reP|_(GI6|V7auAuK52SN##Q(U6@Pv?&>jM>-4;W_`HxV+onY=B_`0N$xTA9$#w;;x?MIIGZ9_8k30*(d=^I0>b zEass=y*v)K>^lKE!V&J7YJS{vQ=K0_Rz!Y!Rc^vWRa}d9F?({ z*q-^#u3~lR7_Zl`utL*+=#Qu^<{+vK&)L9GVaHnqp@>*1=hIveTNl8yRJXOsh5YRo z|4Z?6dgEVrUcG)5UZt1a7kJK~!2NR4n&g8LZj;A1;kAF&ef`S6 at -OQzUcP+sQuq0J zFJ2r8?>aXtMkf7SV8QmQ*Bl>Sc3!-E;a=e3g-jq6|0Dj5oJ4vPwX1(sybLeIdV}Es z>jJJkGe!Xe{m-HUaHsxt_swhfIzdSPqWfIW6M&+Ds4E0l!n$SF#=UYcyRTmQm+nQE z5_nMuS==@mK>s)QOY3hWGCZW|yyo)$Rpa%mm;7*|d+uH|$sqjC|46-UI1NPLFH5}N z{|2|%e|`h9;&u5dzKk#0&tDh~p7{n+2;e4R6f3w&uy{;?oA={q!<3Vho_C1 z1|LArgkQQpgY)Qe7=ApgPk+-p5u$XSJr^CtIYBh-FQ6?cO=X$lRU4AL2w1%P;<^%=ydDU%Yt!?D;dwZn!dLrTC?8EYi0$kt^bb0kMz+rhf z={qLLg%#6X4;4V&pQC&`7_A$^x0E*C?JwRB^w); zZBFEb7ad3hm_2jYBR)0p$sdDzWXxg at YS4u9m1!D8Clv9TXhsdsI at rZMMQ-AUkt>#t zSt7z=$`I~l0?^{K;%NqQ7N~m9V~3!FDAHf!Pw<-;FW2Ya_m0m zCh4hr(tP~Hn0K>JLehyE>7F-%A_x;;%J>9MWjf9XlIWBLDcQXg82pPC4EX$6 at ytCf zpL8GpB3hZOa!>pK4|$R zkU5?v9 at cCU(~R#NEc8+j(Ke|A7q}&^68UAOFer0a|A5265i5Z_Ct74ha`$h)5twF z)mx9B;Nu>*Ar~US*kp?aGqPdOvdoIj!jt&8`RMT at L@DX*rYYm#N+sPSOS@;y=Sl(L zX#;i!6d%XO;gSEjY07(qkOQoQ6db}PA<`4HZ_`-uv40d4L;14c!*ipTQY#ar$L>-0 zA>8Atyso_Q>Se637 at wC|Ji^~F)}#2S^YG_4ypGXz52Mq(9*`)gDj^H6KXMOA-&<@t zq*ErYBnOx+1F*-2J05m2=qUZGuvoc{Qbh#%TVT49=kCq-{lZOwWDGsgL2Plt_ zrGyyiQGDng-~dJdGI8q)5%G=lhu2z`O+AtUBG1VaGRUKc9 at 7XtW`qL5TI#^@`t)ti z2!zm+$J`=8WW~0*!{Urtl8Gt69VRSHL3{WI^xo at NNmp7tqX;&{10$|{>ZKsfq45#p z9RiqF8YK)?r@@F{_hgB%d;+)7Y)$(6ftL3OP9fg|8*goGI|5~COv4;Zq5;Fh&Vz>! z{Da^9)DW*h(t#yVbwj;Ix`rSA6q`_Q5M>9elB;QBY0MD{7x(VDqs=BhoPfUutioxr z5qI)SV-|3CzHjr&-TAahRY6gi5Ooq+l9=UR#h~HNKiMWxQ at Tx`3k%nB9ci0TZhW_4 zBk30-VaQHZ%oIz+qV8xPWuVagtUPMkRlK1QA{@xYS^0O_D*K{#WdFw8B37$^R276R zVFYh#)ldesyOA6WS2g0fX3P#qxy|J!t3ipiD)m&W6l2Lzpd_gYO$ra(2+S2sUT#p^ z_$x~lLtc?f_)Gp6kf=4(O7)upOKp}LQBQlr2FR#?muVw(Q3YoF$i87ygEc%M2#Q>9 zzz<4GN`{i2NSjdx6O at tL*k(=1T%jE0XjxK}%3BfLzN&~W%MHtEva8!Pz;P_A>^8JF zVz~R{qs_}10C}w6v?ROA$5!2xML$&vyb?WgHfm}LTS$f4rlthnIFKt>qRcBOp25xF zN@=iM*|~r%atY~yjk&U`vB9~dw$)axY$8~sP4BQYOkx|TP&I2l%bZZ7Uwd#1ST?c9 zFh=K!y)|rtP=Lx|L(|Y#R`_5!R&LiPoUj0FsnIl!Yy@}?a4?salH#(kv#c+Kz>=sq zoHiH*Cscg2Nq%85Vyv at aV*>W%Ju6B6F+F0nZMj{PaB%||S^^C=RYdxXv)IGKzharH z5bnnY-CU*`)Te_{u`2nRirjA0n^c5I5kF9RmbVk*(El76RZAtoWu0XIdnhupg#j8F2y-PDE*KD9!TR at L!H)s9eC_L_cHMZIl{TwHBKSgV939 z?7eAY9|UW1RhEy3gld35)lu{p&jtu03bg^GE2S#g-dZ1E{D1}`o~@8~iNr69ui7{O z8QUz8?pWF#u7jKz7k*>h^bynva55270mp65cJmt>Km!2*)rm2oLISeC1iZe)Y3t#_ zoN$XxA7|HL01bqK=PZwm7++tu39I)oORI-BdC>IQ!WG$R1 zGJ>xXXlcd}@MR46&eUX?VWZ}wQr3#Ethqx!xA0ZzXTQDy+c5X`=^XB>l0S+u!*8m1 zj^g_G_kzkRy}}#X9x0B-NVXtLL-()m1si(>03?ZB4>D9GXWH=b`tCZMV+uE^-~Z3< ziY%;*E*dIn9oCn&(4aY9z^4ZgKvmgm*3xGgW?28joK&;(NJ9l@=&krgNViqT2b!7YFi~3{YYsn%&efH{pOdf5r9~u`Y~6z{|Nb- z34uAgCmsAJ-O-qV6}>SWiC*rNT$?`RzvB1{P01J;(EN+B=tRU+kwpP^3u#@d^F43ta!CpD6SL`BhA(>hZzt3dn`*m z^8gS5^31zADpbw}i0dFgrp-xMXPzZucM=?azDdSne$Gi~S^yJf6DIy&MxV&S#xq_O z*8(2J$dQCS-0U({oxO~2xSVFwRA03&NXrmh9)i3fygqpoAa~n%d&Q3<}!?Sc?LRPYLobpd0dYL3uIp$ zAX1wP*ul+oZLASs8AQproNy)1w`rM>aY47l%W>xKmu6-zT at vYNydxYbjw`_K5Y0=p zIM<$?qc at AoP55!<(u|vFX;yJHfLjt4C^axuj3`!Q}3t3+x z#LYJ6U~o#)rnYXTj(PmTw-w=60w^?5Iak+M*{6eDBF)NeeuaIq0t*BW#rOEbSs`Pg z0N!O(Or-o!12gf21mH{V4O;js?RhYk1zk6biC*|kK!Ur}ow?*MbuV5`VBazgsKL%G zo92qjbL9*Maxblv{q9YArI#>^&3P|S}d=|t05^;Tza|+C{mE+ty%FWzj*@X^5 zLYH=p^A|3hN4S(gf0Kje;BI%>13yYaOu0B_F3G?i26p(9te`TL=$=E; z{_tQgpmCUK!e2-%49)itM#G`BKOc3L!dZ3!5G?`NncxA0f?}ULe;%val?vCh5VB(< z7`)00koep(!<;e$0<=k*jh8JHJOZ- at -*hita2Hx~P=tz=qy-Kv&pZXZT*5(0I}2Op zoOHfl$mvo9MGrODt%$+4{P1KxYU`C{J+n_*dNr=ntdAPg|MIpxdbN-wzF}$Lv zrE^!2kv)lXf=EdSC4atdb2uqM5{Jv8|AO`w6ktrp3w`JPx$aryg$hlDLNfqOFarJ% z+Vo8K{JC^4*wsOmYyv`o;S=DQppoHx;7CY&mKUZSzK! z#4OP`drqI0=wJ^DA>ZaFr$bJJH_Xci at uzg(-Rs=FhsP?SnA4bPfbghzSUd;A*_zogVm~3ar?=d++XD5q(J%lz&oS*L3+oE?)`gQzQkSnghd;VpX6Kb2@*7ay3bFzA26hS?=HFRfN>_L2r=Ak zPY)Ah?Py at U*W~WxAu^@yB|`vfLB1eJ`|vP4a6dI3aPj;1(!B=e+`DrJt7q=SgT=As zK~G7!@E~amSIO+}w(i^&ow`(!2jplHR1FW8L7qH-iVuErn&s~&8v3rk+qpvm!#(Xj z1EEQRu9D1=G9p6~9&|KM#;5A>uDb)yFg$w)#!4g`NfOdgpeKwCqE!Qo5V237V0Z3d zd;(eHFboWkdsOGx2eQ7c- at Y#wgnQ|(zta$n_n}NFTgpG^{sfOXtlheIA0LMuns at GU z3kOA-CX>fb-6xOzil at Va_D`^Z|0&$B-Sg0)ZBrs_TgplaHVyX+tk;3W{th7^j`zu< z+9OY@=;4E(ifs0DueclTkoBgN<&&1!7V?2m&H(5DX at fz6(c+!@?K?*6_@^y!IwV1B za87(s{N(RNc zy${oN at 7;GoNW7D7JH(HFyetliu<#GPEEp}0-Tip4eHV)cEmZ$$*p3d|eXI;uSRBVz^t#-ECIMT~kN+1+ns=_71p5t43qU{+Kp zg at KG%WvV7b-F;XTm?;oy4ajpF5(S#(hvgp${fd}`^|PM?0LpCP?hu#mcJ~gAkCLel zliouv1{fU>na=QeptRJ2O at D&78INXtmP_?NB>0Ob3v;UipX*zYDJ2vB5%70Ec5naa zRdn-vsGfjnW-scnOh^Fs^jC2=nQK`y72`- at w;mDvK?hd_gLt<9ItvshMU_#?%ag_q z8G at OSURmDVS<04I4cUYOqHujgjQc88HKCfem&nZ50aD}|wW^kB8 at LBu69f||a!?|P z7+pc1ItG;2Z2#~*Pn`%S#h>aNP5pSDb?)806Yq2c8~5X{to6z=p{P6wEgtgtBW%LG z5 at zn+zT==~`Z2L$=Xc6 at z#(1f5KPRmg+y?Gc$c{pc}7~sA3O`|qAsMo!|GFN}zRqH&n1N`a6KOO?7%vfeZkL5Ang9MBhHJuiIx~{_oacZgYJaDY;>$uE z)nIkaxH;QZnM at QGd{6`OEXeKh29CDO(**k9HK<@UX05U_OJRQZ zS7nj}a5IQ`B30?lOzS@=z-$0R2}@S7{44SzUEXE>VN%4w;h23b-Fd_ZA!&tqS^wBQ z=;1LSVeu?Yu;@yCX9rbKS=4{l!@3GIuauM4PJgjWL$h5#vh!7LvijDP&|w=E!3}>YVPto2$wKKaFz$sW zcN?Xp&CD3AYRFWcWwUj^Uq9rK`iyBJxnWJ3)zq at Kj=e_N7kAw{&Rj$Ni1mmN;Vng$)a%xg*6fG&O%j>pIw)ms-yLZL3JbB;8iQACKpSff1xsRvt+RPI zKm8cx*3~I6zDA?;XD;X8*O%+i at j17hW*=@SL~a&pdn0IJsC6BzlhzmOev^dMUtjbL z-nfBME^%uLx?#OM5zwU>%Yso`+kq}(Zp33&y?C+w092AiHPo#`M{8PA7GEV64f=ue zlMVv_hx!w1!wd%NYYT7lU)hwp&vX|}q-mJh)`qo`hz|yU`wBf?5sssg zJ(|g?4Yq!?ak;h-S_nlp%f`3BNHnNj_gQw#bU%lG9elQgf7hUOYin!KV0-QKTGHu4lrSOhG8QtxYwOSq zooix$;*W$nq%;?`jq4lo_WE^w2Jv-kKZdKs z{^liVb9slx1Aw at 6vDQudwZ^&EGGMf|EL~tAM0B|^wxeFs43?XQz at m)ho>T=G)uV&Z zm44k?_Clv2yjpN8%IMN;U69N)opuVeAE)rQU;TppbPE<_EWs){G;&Y!!D at p+i0{@8 zW^6FjnCB9OW|wzJuAexK`CNn^lk?S>8arDe21Za;w0_w{!S5<^5=FvVO*`WP|{Li zx<7*%!qDl#bQR`cO&T(APqy&R&fva&{YUDN{4ZvcLohVK@^4y|4W=QIexS*>O#O3a&M%71q|Ko9e6%7z?UYJI4nPBB7ASJiZy) z@{MiL=F~U^k{_Ee!JGWtf<9MHoF0TZ-qCQ=Q$#Dh)hD|u(`e;d)#V)y2Y{yFgAdPS zouu3!F=T5i)_sn3Q#j*69lz1#ep=}?pTVVBN#LS4&HTbwnwAoNiqBJI)r)GI-08l5 z?GmkP9*NUj9c}2T4z#JPB}=V9FCzj9^@hC-J2)aeY`1i zVPy;Ol1Z7P at jH;i8^XkDLOI2AmkUQq9 at hOEbVOfze&Tcs4b2G!Ha7QD2owulT}I%% z$1drAK~KMAE~W%G{0u1Qc=*8!Qmuv&jcGb{dMZuHV);skXjC>t96%0Opv2fV2ga$7 zk at qsR;pnx~Tm~)FBY)gfz9OVd8FO+O)03;F)utP^EA!Tr#~(gWLN+B+r(9nB?bBz at oOY)}zHg7a-y~=YV;gv%pM*sFEas)N;Y@?0PoFvM zPuo)s*ro&KfRt3qB#bd1>0CP7Idk?*I at 7`Ma5~P^FvfTe5g8#9BprxSo~>i(8Lk~p zyHk{&qciNqar~RqlE`jWt0q2S at m=br< z_b9S-zAn2$u5i|!>7G3k&!p3xQ>Rb4Q*GM_lolyUf1yoIjjFWIYqJ7dV1hqWI%O4tgSWXSYqmx!OULJ)=No}}$hLKRZvUKIG at gk zbAg&VX&TNnF+NXeP=-?{FGe_2Yu6x10V^8R;TrnKoot>uNfF#d(Xc6d$epW5C5I`( zWpO&tey2{llbsWs08uE5QLwR6GvtU0r~6K&lLdBn=i5X(M=&gGKs-}oy(T1-YPMp{ z#tPsJj)=H$r`z;igcah+`iYY#vA4k#9(QmbgFjk8*wVq|O$Z3d8%{2tIC+8qym*0p zt at umxoU>2}*(i=iDNcoxZBFnY8X43PR;J)7yE&YxL6~&%JqBTz+#x|IJ>eJ5)iQ$8 z>4bs)L`(bAxEg at Ss&Z8h`6k2V^eK*pjwg=Ot^sg{tRCBEIM>cB)4>>MVsWD-SXm&Z-;#x4I#r&GCj^25|C|?7Ob3jq!LX(Mv(cuE<#4VJmH9oRY+)uMY}3i^ z31GmV=o~*G2O$3A`68!MW|bPoP&9YK9dDjEPK0C%G<<--no#X%>yszKiRI%bP8gID z7GsWbNae-pY#D#5(3;_dKkj>JL}ql^FPL%qo$!wcq=~Zs5jZEh+`=7iov_qK4I^a9 zc}*B&Cd at GbkO#xsiQ^}ZhvP-gS~6^eSUQgi0L-F{G0}p;Cr_jkP0WBEcEEsEW}^0h zL-#ZcY4OagOFPm=#R)ENkx^o0%R8-}{O**8bIRjP$70>#k`iH|<&-p&>?q7qNqFUW zJnrluD$XroJdzI6lM`;E89bh7!{8X%J%&_Y_Pm!fcR~=DMo}yJ-Im03Y~TR2$G zfd#2psE}q_2^`1EoNK{UJTwX% zUu8C`MVP0I{-e!iZJ3*h;u6-O%Y!NHE5y at NaQ?B* z(PJ1}_70r^q%8$Sg5TY-_VHuKj=H1&HD~-&IzD;A+*BNo$2^8IT>-4TIw%p429bm$ zP#iBYplQzw#C+*cx1_?H!erGr31HxhKs(l at F8-+EEMP6At00Xn!l^_${`X_Y at i}(% zDE=J7;wzUl!LDXP4)$y&8PowD$1&^pF at G!^#j_5am0(ZBAyZ)=g1tCa9MuUYbKCLd zvpw^1eCgAaqk~mIbla2 at Ag_x-yIeO&CQN&?k0mB%O z`0->7F(5Y|KZd_(!f>?7;dCGEL18 at 4N=oA~K4u|+9E|N??C*{kV^iD#dHiGJhaX8T zea{ANygeNLtUf{RPw|grf7pdF{?Nhz_S567ZS at aS_ciO#<^H^kK2PlQ#3pa#x$IWG zXw<5)A#Xmj(Ubdg^Dk-$7ycCf*!&iE=ScwB1d>wm$M6HU(T#U&ni&6L{oT6X;)Z^@ z6GUE-Kr6pbxCP#8$MwO&C=3X$hpRf!e-Ss!dFz|q7rho^Wx)C$zvbq4Zj&Tdb3t~O z%RL}_4HB&d8N2tO9N!AV11k527MK^BEek8dgunW~u!|G>=%qV7Lep5#@O=pzm;@2Z zvq4c--dIX9KgNZ4kPZ}AZJTJY^e6wvx-}a77Ce*7MzDqJnD at OkgsXb8$tiz^N6-t& z|1o~s{sx-HieP2OOCtl=TGFZgl2_|3&lZZ`cu^tCLWVU`3>Bx50FbDmxhq~kM+>VFn4+3ho7 z7#eTgCdLf-4oihz+b#E}1}!VNTW>`aBE*W7mmAOw?m$B{LmXRowmBmbL{?9ctF!lG z&*KzjWjNY{s@*4>dwq-Ejo+aDBVsioHo|ud3!j_h(R*rI>4CZ2WGz^CDcB7gM3}Af z13QG0$U?(6>o?}DKO2<*LfjwOst^t4#EoWLuQ-V9D0BNTc4JguI;z1&ax7{mHV?fo zE8*A)u7i$l?3QJ{5Ir5y2<20crfMC8yiMhVAgDx6qR9!0-bxfD;tJ%Uwdj!+!^1 zdaPY;T}fQgVQRo-6Zr6hcxJfQ+M=2<)eJO1RWxXo$JiANB`#I(k0tRwV4N99g4{9{ z0_NCB&;lx8H`IAlqsy<`ERTnxYG728@^WG;O>3t|xHr+uPnuXGH1`$BeE%IADH`Sp z`k=^W?qG>hE901(nt>Gr`Tw2rmjTN4y6|y=I$koi*@(_6D7vrSp>!lXN8ix`|4XM_ zhosJku-1$bh^kW!6s|*F%T`9`PJ%H0r2*Cdgw)3Nx-}RqeciN at fT&)|Jjmp(2DsFt zCSRB|s)WhTY%bl)#2jLE8_YcDzoSJEoZm`Z3-r|Ochs_OTlY#wbxww$S64Oxi{ zYeAV%&DCps#0K5P9F_0BgB(Hw#PLX(XbeoG{*k at tkd0Z1))^vLfb$=V#H+P#)I9f> zC1}AjSmw0QVCC5ObyQtScdNB{Ymr?`t@$AvA6n+BP=Pj#x5A^Zr)-4@ zcn}?|Wm?s&v-qIvt$nBe(-NX8ck^RiU^BQCWHrAhh+66pK!;COt7Yk6f9w4sV7oTxE7zp6=(Q2;_Mb zqio&a=<1Sk zuR|{-X|E{FW{(VcEAq at b$l+$VQC*d=gR1pcv_p)YL~}J}22BgYZ{Bb>P-GE5*Lx}i zoM;wgJX)jb)tZ0JpsKcelcj;#OWJJ8tKzC?HM>@_>@&&(;{>5Q%+D%9%rc_CyS|Df zqlgRs;LBU_BqMOUy_jlGPEAev$@&!ko+QyoMu at lP@$H{ZQ(-zzy`98APillllh*O% z!ZfN+Y)EY?Om?|cn`8Z?(WwD9?Q2G_aMS*&?&MUM>YL0|ZD#qz5Qy~A56Br11(eQB zf;Hg2#bkQ|4|unX!8 at QJZRMtOHEp z7Wic%Oaya`AY?*o at tS}Z4D(^~KQUHcOfo&mI!}S6GJ*Q0e$8S?Rp^i=Tl75Sp;0Bh zepZ|>Y7*=o1{2t;%mKfdU=uPWDKzYY=M5AvZqi#UCXS6}95+Ggns`jpby|`@RLt~Y zFq9{1%E>VCo+>~j4wWcb18-=+6h?4mPEA9wnN3&zgKj9M-*KaAvzqN;C~9aA(>qRR zpIC)6CJ_At(s^pKW*RTkPBJfSWy>3 at nNwLG1#?q*^gnDYP0P=O1%DLO6tPLB^3=Mf z=)-f^w!Tboc)VVD>PN;elhKglq%J>vy&h1I*`7OygXwmyWR z*lx0=U>uz6pJ{!a>9+f3Gm*8uAG9HqQ(Cm`}LOTj8F99XVuhu%B4`C-vVK` z3d<j)-$4k2 zGDi1VIu>?HC8{vd(u6ee+xWydj5$7TOXr}8&|SykDu at g#O->XOEe?Y{^Yar($%9h^ z6%r{g&EvV?#5lLc7i*TVW}M0=fty(7T7=}p;6!tLf at A!2t%B5hnu>13fHb*;QCYDx zk;a=QMROvIns6D&ebt;vZ0&%0Tn+aSA%v%g;6C zw~1Z|c&G%Aj^l3vO(BNoC^Fi3c5$qJl+z^z5(FFu{kTCG=Vb(atLu#cVTG|Z5jccm zgz><~BB**Wc?~qtA_K|^IHhnu?#A0TX~6v2UnNXed82?7P at Jwzo1aUWU*Y8phBQo{vJtX_%DTm0$@HjH`|F!4W-Lz?=9f-(znd z1C8L1mAVt|k8}W?%bgTzROj7sRSrlOp9 z!(Y at 2CnfA)GIt=cq;X(7lTl-yEQvl|Tg0R3Nc- at SBZu8#cc^>#P&m{%cnH*Grn*J2 z_!X(dCzMCS5qG$Iug6tvTbE4X(1*u4$^c^WM0qY-h z2U~imf&5z={4}^-f4oVtX!+XF_K~AUj|3U>J%)1!68of_NQh!w5OvnJg;Q48A8N=; z2M at VJ?qD4=PMthK78M2pmnI`cxFEp=E7~6P2gT6J172gDc_H?^0M_M53m!Of*dOj5 zg0cJ|f3N|$4nVR9>Mw~yiO1s6?h(*yBvD*Xs^($naOhw<7&GZiC14?UJVBb;kzjxb zhl+!p0|$HTFUj0eBBZ0yyp*y5!#@p#5shIZrAR`F4469tx(bR)Tz9A?JBEYq0G%WP z+sHOAN-08&WJd9anzT5*1Yco${Y-j+M$S*TL({IFjl8S zquJ14RtPdnG#~?s;3p2k!R`TULz$B45RXU6lSFX=^hBls*&S>X3gKWlAn{ugi9D*} zaD}}pj=)KVPHy54whq8g2i$=Q2=Q3v#posvEb|ZSn(X1<$wZQ)`){Tei zCKZ+F9y|~ZH1^XSqUNc6LKf8M zL-)J=c*4RKrXO11V2_aY{HKgkK>GUr0|zjk45kuRq0ANlPlO~o2;tsSaRoD-D9S>*Z zEVoRhvmQBb8KRI&A!d}J{DFeA2qqBZOzR9F2Y)k_`onceh~Yx4hr7%DCY=YN?2f!+ zEZ{3;(jl|HnbhxZ at 7up0b_#~NsN|3kZ668{O?KFS-~N3FvYgN(*2W3okRpKS(2E2q zZLszSYWutUpzsppma05BbR5~?6 at k?9V0j?y|2qWX;x-WUIx>~gRYYbsI5R;AVa0y8 zucgtJ1(||`VFZAD6%q2t5BNi8B at Az4IG~6%D;_x>S?)!A!1x?0X at 3V=hJAbt#0Y9g z;gKSA5=A7}HX-CNqX85XXmIMj{eFK7^Y$IX0~Uti0{JcQ*z(XY0%l!8*zN0bb>qqt ze7Iy7I|R91c6yt@^k(#Ozu(u|OWV0)Z($Tb9$x}89WJqO4W^^O#lEJ+P|BAEB_!46 z%GtL<8SB`hM{4gPK24g`xcl^Qsq$B0a z1N-4mNDq6)y$LcGIlLe)&I0%z;|c_rfh6ti?okTL;Lv#%;1&)E`|4C<- at ZM29ro9C zLp)L*ZZKYV9aNDR7%n`r6uN+ip|B4d)Um-H9zfpCM31CX zd4vaqho2Mt?Dn+x?%s_{E_b+1;Ag2RFnpbW2L?FI-~i9=J{Y^$<91gU$4#bgUOVz! zL-w$g;1sQ#LUuSYcdhkd={2BZ5#hbZ at 6P9PgEcU=!TDW?3VF5d=bQL6#M$!R| z9NCO0sx6W=>_N%91r#~!EN}zS%I=#2JH%URwZ$GrsS-Y3vx0!Awf1e>Xu$f{XyM0} zX%z>h{$sg47aP-23xB;{l?Dt~QOynnP$uLGsJ_B)cW^)6ukbe-MauwuNLP!Y|Tp1=-`Y;nep zgN at wn(cNs*O74cWHR8g(@^z$g!q;NKD}4(rFVEt~DlE2GB at dFj1$U*naD^=nNtcpV zLW4N$Mt;WV95w?(V=WeXtcv8RFe8bj`~(5HD7O3)!4g3)Sr5#r1Yy;tigH<7N_LyH zZg@?YQfnvZ9Iuf5Z_`}JzRrVWP=+a5y3TyANW1{~WUcwICqH5t;z at TVi$rgBQ`xdUK%v%BStZGtN$eZkG z)qvB1;VU7RPy>1EGs|WyCp#bN4+$=hl~)X?;*b!7-53i9EyH;%tqAmxffW^5N>g4u9+}#vy9X$qtHWSw`hpOpZhSe0vUFVdqbVg7I+lpn7JaLm_)+S}@R!VcrUd zf`p^t2;G(#WshV7&vXz$78Bg7&6sct>XD{elh6Q#o}dJ?PNuir&F&(4k5R~$0ySK% z)BaZ>q(gZ>74DnE4BIef>eZoTnuj&fDGrYk!bF*k90JLwU_>Tsj~O zX at WHG=ayzkB;vaWeF8AaeAz;?Q1&H7BTf~bq#i0E$%21Yb&@*NVKcf83RvEZ07HU{ zyA(cRtZMbmF=Lp0$&GS&lHY(xt_%bRjJ9f=s^JO$vae_Wsad6BB8Mw-cQ>a(SX|Z& zLiQ6_m=h>`{M z*F*(DTHUIkRyI;u$Q)ioSY6o>&Wf;o(er&(gwtPULt#QH!Zd6R$n)dcRr8ZuNHV6I zEiWT at dP*8nSg)2QwBnmql9d36kQSp8vXKp}EFR#~aK%cnIt)V`cR%4%;!HbnmLlrMSx0AnYtFPl{*gZ&IsXh8bMHGy at 6W%7+5U^a)@ zY*1C&%QKLuiU#%Zr8z8FTc)toXW?PB6u&V z+5w)gqKUX<^;1i(%rbG71tC#OPIx5f=}33AiD;JP3EpC^K#@$=z+)B2mCKPT%a!nY zfyEMFr?P%&204`eHhc5Z3pH+)0#%`xH98CI8gCRxeLBn{@;PgBBHt7GZkGOw&>Any zeGs#D4Z-A5&RNY{YP!Ui-RLa#)=V(|d&mx7 at tfgmOQQ53icY%Af472ud`<4sMd-|n zxrlkn*PO=MuvXX7%Bwoa8!j>W?1=L4ZwV2>Mx`QRVbZ(Mv32HUw zSoMz%Qr$)jb=Qe=MoxhC4ojM1ku`wvMqX9K6a#T_%Jkx~UV+UGAiYAjRgG46G0F(3 zhrp_CGwV^(+jyst6% zVqhUjh>6K4?RNk8SQzV$kJZNT9Ki&LA(8{49%3~b6bojN7y_v`=EqxO<6~o>A^OI; zqo4&y`r(@ynK0o&Yn0;!1`cvSF-fMa5C3-utAHW^DF~JU-!TvbgJbQ{u`w_X6cOJ( zBR0XwXz&0Dj4^IBmd1SMp+JC$-54BS#y at hM@v(C3-RKxUlUinAVhQ5n(_>^Pup8qa zC|i!XQ9l;Qw at yMN5nKB4WGupXgG=J;Ie37(yD>b>U?P?r4+~M5x|xP^-boS&!*5}8 z8jxrdrg28*qzSTBHYI>=i!>!RqEszyI0!YM0k`6B0Z+*dBxl+ji-pHw3X1<53^zKO zh2mH_*I|mtwgQWW at sCXqKiaf4`zV5ZIWgXk5m+t2Q*msWt6{ydQ8(HgldeJm!6H3P zZ1Po*SF*@0Ac1Ih%t}VP)+WqpA*-ZO0TS2E7Q_iTEjb6Gl;d?64Bv4W8Lk~`nj{nh z;&5egO4;IBNfMlcke{Z54hEyqFltW$fZx;dgM>KEra=Q^9SB;Ciuy9Tj_e<~S1v?Y z=aIyR(`Y~1r35lXzf6%?Kn4P_3H>4c=vW-9V{w at xU^vO at ari_OSV5x+Roa5?)2eQ) zHd;>n+H(kW!nZcgwPofG7WQTkLa+uK%XG9Jz9mL-cS>!4*BDM5+BXVh!)^qr9}gg- zxv0z#4MG65mZpi*XlG<}qMTS|5v3&uxi1wO9F2x}+8p+t=riH8n7j+?jkXDb8g0uh zIgAJ&Fr;W`4Wklta3g8rEtV1tdm6j({xO9M#2Fl29x(?p&}700wh?}=^n?zh04_{! z7)hhekWC=t|Fcb8d2?8-Pf{5wd|BTh3xJYsml_mZ(!P zTBjMfxf`iWk?jWQDMhIuj214CmIAc$jDI)8$Vwk&Ek;Meh?`(y({y2`5RhS#DyD1H zU^I@%>I at 74D@R9}^VtA>G&3O6JVIoTAp)4N1(JNs z5!2=Y%3X}KG1$gBY~jZTlpB>y5(I`HMwT&>gGWbdkV7`r(hM%k)5#o=Fuac|j$q>@ zZfsjKoA_SFPV}V?cNo4 zg`MKX7o zxF_u{c6G33+)2(*{Gu_P(8+74-|P1{SfJS5-nD!8E^6Q1wG(F8;ScuMn<-{jT=TUAuOMonePD)CszQ+1TFz&tfD? z!W4%c(yqRpogF)&A>`iAX9)I}psmOR;Txn`llG#pVTU-eJd#8tC{a>+3DUaV-95WY znD;%6OZ(zW^?Jly at u9rtEi6r6gq?AR2<~N(Bt%Y+jdA-t7%~{71A!a_k^#dmzq64y z(OgmXq}}%KOOS_y)1C%Az6U4*l~b9dxdV>hxx?*fZWr+iqi{Q at QB89_YtlUOQ{0($ ze4w at HGVpO31YXE046!?RIXEossBhn48zf*gnm6~`fZd zOer2I?&;edZF9P at fz`I}ARS~xEf;J;HGF+WY6MD%rw)d??QK$PUQTBO(ui6&Nmin& zfB^07J2;qLFAySTVPOdiGn%Bm$4IW~G0 at n4K+o-Mk(g9i%k|;EAUo~oZikt}_U<-_ zo#;i9SnWL^1U=m7c6EDkP(XI5xPxE69ULcM0nOi-cF&{(jqb zx*Z;5tmR#(3BjBG35@|3_dDWthxnBbE1WPhI1E_+rT&ub=We>6(#Tj z7ZWmW7lTlHx*c^O7-*p9ifw$J0$;bURtcCY;3S~IfU|v at +XnA#BY4PgI`G3ZQQXtQ z4i$z%&SA(0J0dh|N+L|fa}r1!4oV6`K^U at B6XA;HE`^lOR`c6C&^l~$UzT|r3y`q8 z%Sep7;?C}lodi|6J#Mplx)2 at b@c}HRBMQm(q(JkBj zw(unok4ZSs48sUOGhQ^{cxKWasb_|?t%H61m%u%b6gz&1-4;dNK{R%EY!BF?OM7A9 zR)mvsU52{F_6N>SHYaYc){c8y at S}L=-A#c7CuSi*B7&oDTl})nqX0S{=;@-$DhRoq zh9ipLw|kfu&JJIa<9EB;)GfkYD~UE?MZe9#YgEjA>9#gtLo)`oOIV!8U_dyxu{OfL z6c`f3fO3|@4GjrVY-8KzHp?&t_HS?7`sG%?)$Q%y<1MLFDj^FaoghVUSlGJEt)V=F z7l|PV_znhkOb%g3jluaPbo;Wo^~*1}(z>=e&OW=k at L2``(jgKHOpp;O{itJQV86RT z0|Rk9lIiNOW0_$HNNwMiAd%bJ{c>x7;Iy9vG9V+Z7?jhebX&qRC01-I z$s%z;Phy&S0wGwJAyP^!9*sE at GmvdifdAm}v=z(o5Kb1I^q||BJ*d-!X+}#k1Oa}x z)qM$HZmDnGvQ-$gD=TJ!4~}A3k?E{N(r6(xxmDQO*|K$u+X7Vc!>n(+ at tC9aJ=~?e zX!;Hy6Sp=oV+*eY?;ubOXp->d;J)>Bb-;L05J6p(R%zxQb#F~Y%Ex&c^ z7QcmHuVRxKgCAd+AqnzCC`{(z->{|i1*B3iAH{$xOLBB zW`S8NEtdRcmKyXk>f2`>$yi*DHK5SC>FmOl1>X4wCDtH6jZf9ehnt at n6;et>m6=oDpo+a_A1dP at +m~nAgU-)1 zq#TRFsT`kbrv+4qa%ba}a^BBXEg)whUBGyD4D+a)$Z|vRN-^&+!q1_N6aQJXjGJKt zEcO_J6!@}US~zQDP?!%G-C?~*Nn2eGtIM@!zsj-2YE%dNnHXXq(^f+ z>nK*Bu2lUD7uPT{VeDV!K;@zPBdZ8 at WFhumYr(L at M1hKRC?RxazM at hJ5Jc9WFQ87O z)%RmDssfe1NFq7Ii^rk at HoBemmPNg`4?1bF+9S$I=3p?U$XzbseSfhIC$SVCs;uQI zQSdHml)*b{A7}lh!ez7`WaG+8_NoP3fIzHc)IANO&>#V%*RpYBzyXI5UAfj8>ldjZ z9{@{J$C(7xSO?OIsYOm(<)L2JaI=AbYjUadXJ=E5>8nplC>&P ze2&$)Sj`U=08h__xlq9eEln>~(p%9x4?weS7YD6CAOetG7XI`Q&}w<2xtPj at 1r+5X zbIDeHObjz5#v)3JvX>kp#KK#3#jI!>N;MMe6-URovkH_ICOte9bFE2t8Wr5;=4K<5 zx2|z$k;>GQ%Pcxrv?@tfzyc_2mc`DED6PI`P}e$vOX;mDyvA~qEQuH}twhLLOFFj1 zWJ0vwbx2N$v4o;%Efm9Jb-Og1*+&7lP0TnL+XNwGxMKGtTvo^zjE1Vk9*c3ZdZa&{ zPW8BEva}2 at g-i}%k73RV))s1dl_i-BQ^9V)!xmzuTh+U!7Mgepw^bh zl1vCRDLvd`Y#|U?;gDtgSnUlj2~{ZrEmSWqU(y)3$pK`{0X_ZQsV4NoOD&vIqNS^0 zAS=Wi$wLGc`XX$v0%EKPiWzw211_*`irD~G>r!>)m17Y;L9hPRvvpG+Kv2oE)Yetw7Uby^|;M zdQqn^54a!$pmv|tq{S;!d^xFu7kf~Pq6UH zAXRU_T)cqFU0 z${L@{n;zg|6+=UIJY4_=m=R9kotYEI(azKiX2)@XZB&ZV09n=6vl4)KbBgUona(`4 zvFn`Pu|S+fMh%7Gt5a=AgynMuS^}8j at EleF-CTdw%8gQKj6Vjo3q%;ZCgwxI3NWfl zqY at 1)YA^fny)2y}l$>!!w;`*mq8~x*PQA4Vq37_mx_Np(j&W?$c=^Nd^j2lkKPIN89`d?QEo4f*B<9HFwk2q0dj!n(b(ffPys?*S>k z-c?OHn$KOHSIS=jRS6k%xp1e)+bMX~A4}U+g at cK#>LabH3et5#72gvjRz8N6RbB8X zsxa4ixk5=YE?8o_SE$jW>|%bM4)q_8;>cnNUtHp>jAvS5Rmtk at IC}?dNlbx$YUI## zqyD22KinM|?jLRs4G$0bA)`5r=Ld7gJM$vPR*i_?(BNX&4Y}d&&@dQ>p;lf{Lqm at M z0HZ~&V9O_fQdlC?7fkS(AN z>JAT8KjeBu*|^Nb7>y_!k#h-L!ChYtuU{8e(t%tuA~ zHM?nv&ZPhnc8#%79+LyvD^gN!IA#K-zfwygrJ)+BTT=+c<1oajn-}N?n0a_;U`Ui$ zFr at U4G|g9OIB(01K}tb at +EAa!B#4 at 9Eo$Sz=M2K5HYN_W)(j1;fkto`XsB!g(ob%< zIikHw;>8a)vQBBptsym9m^N8V@?n0sMZ#*Bkd=Gev_m=6Su=zUz~p0A&&p(!8 z&>tDD*?3I?T4a$t2h+6Un#!BCk$*Eh%iF%UvZwnhDm`Ld(46!&1RJmk{X=aU0hvUC zyv30M;%1L1CT5WZ~_*K%*LB}>{Y4i#&M6TO(Ch=@Uo<$a2fECTV{PU)~{inhz36fbKaYn#rz%&ORB`BvA)eIMwV7^UMlt& z8*Vr+Pn55zK@{YOh}_n5lw>HON}KNWYuc;VticIDJ7xqTOKYVL>?F6AP^=}l()h5J zR%Mo8#xx-Z0Ma0f*Vc4bV at nX^_$Ypd`^<`YF(nkNti_5o&DCpQAwdKgFlPwaorloi z(EE&M{#9E87CsM=7YbWh=MTLZA%sj7)-0`FV=FVF>8rOrbMil62fB373_u_S3zWnq zX-NSj4lUdMFjj(QfC&U%jgIk7w)k~ z|Ik~k3bkZgIz#Jsu(%y8a!74hE+ZgvGUf at oM)q5cId9=W%0qKA#PZdEA0A-L=)=*# z+iC#FuXbye%_}RSLzV%U&JEQdwOiAof)JsKt3ItMb1;7iI2a|V0Z9r0nO9?JCL^L< zXhW*a$BctPKEl%fpr8=4_mxc|6{Iil<6t=07oJ<~Rxk4}jV|qMmXY)Mjxz?zL0O9l z;8N}h5(Qeop%CQ*;46>w0AN}u*3`{xYgUKVbWzQ`?vbexqgfWkTXv~j6X8=#uxBG` z)zSQ6$O+VaYx-B~;HIa|2(lgmV9ihh=4fGR2qpCQpbS+V5*bGQgeHw!u2#w|43T|i zC)%{<3kX-NNfgel&KG&oL&30y7n0Ch^zIy&9qFhH{$oL;n~S at bHuW9)R})6#3nMi=Q zN-zsyOZ$s0TfPWiwEytM7k@~9=>8tm0&n!Y%&58TU?f4OZVTHAjMp7=x25yNmM_v5 zeSc{Eo;*@!UK0ooXZQL&X%N3PZNaT_za at SV{}6uP_`~lpmMbrbjRC*HRhwUK+Xg~} zq=n{q-jh#T{1=@+eDMeWho#^D;rINCWh7no|B?0H-*sG9zUQ1!RTm%t5`iQ at yKQC? z%sET4<@eg%p6-5Oy*IOFS|UY>nN;-r*h-`lDUr&aKXKNY)$g?}kpOO7^ZD+5>VocB z_X1WoPuStR_bJqO*RJ&3E%}B?edq_D&}D+Oh%Z!^prR_{cyl4n;%^7Pf#Vo!kY5Zh zgcl5*V3hbqTKf{ZomtG7uDcRbYwW&=I*<;)|-jvDnZyWb!#MU+? zyfBjSI+DeBcyUV3eH80CJ(Q8DLAxRHgo{nfJ%!({8H;43kmQJ{Xs`;HZX_iGN?wc? zhZiJ at F=xqmo(!^#MiLv-dC}$e4H#`KReZTC!Hqh5#uj|d at tIBHx+kkk`C^yl-I0yE zMP`f=VapbZj|=StnQ?IG1xeJo7=7K}vTo(1Vi6=FNbNy at k1irk zx;WC>;et88$nH#^iCz$vm=-P#h|G%;dvx)F&43%!CQjaUeiI7}8pav|gqe7;cL5D9 zUWgYmYC;ZH##VOa(U)F?e(aK7aMlYoD9aZI8eLteUv6OFMlYda5V^;vayQBDfq`cC zd|`B+v?A^sLi69`ECUKF+Sfy)BIeDn7n%#vg&;V86EJvVi@!(!6Aq^2MU+gf7`A#+ z)Hw>C%ZS;cF21maJbY!%>SA>voF85|k5L*HS4PP%H6%m}385uk_(|@(aQ^%Sen4Y@ zj4!FGFWFyg2)BHxCowKv%uE=a$J+5c!=3|H$Fj0#6>^*Goi1&-m at ee=V&h9nHgPMw zUO75~g)tdIE|jdJ3+EBo7Pcv&$Shl9kT$0fLKoJ~Bk(z7R`M24UE%JoxlU(ECjYs_ zebm#ktR2sXb5t8B+A#M^11Si%i*J%?DS%{`CSK^X!G-hbyw+c_xHDHK8y_vvDq|^w zrm|VY;6o;9pW*p)i00TW2VHb6Dg&0px_BX77|8O82^>In3h_%YQ!;mbYtTg!xR7ma z>~rq?xp1z~{Q;P_JTh9O7rLB70)X3A+61gsu>mHKNJ>cci>pdVjC|ozMbv5e>OxN@ zHI>hG7$my{Pog9_xl}_-xHaedh|vl%&YzR0Xn~(R73GWd1yk<)xrE4`w`%g1&MCp% z6Ib-?94 at +5!i8rpiX40{pNr2c=|K0RD;j_dmC6Bb`)8VT{@l5<=gy83Wq7eCXuR+u zso3)psYcGOE_AgO*jUCDu+}tB zla%gAq~W~D6wVFKV)cA>bcQr)mv$W?-Y;^-sWhGMxr5 at l`t0Bg8|f-1A@^SdM;FSH zQu5^$O(Vg;mXBwLXH+H9m?Gh?(8lTK$4B%`GTvwfrLfkSvuDuCFR64CbJFnBDY*Gr zpgDh5I+zW5XLKN-zYf0|Pq{PX-Gs9`#Z>W3K}*ARrk%e6J$a9Slbc58&f-kjw&%eTb&=tW{8Kdi-{TJTtZ7TVDK(|)%RXSMWRTzBr&t)9Yg*m!18*imMZzH{)4e?$)tH1#zgH0#bQ+S6!XSV z(V8J8I#j8j3w`kzr}xT<`}3Ekx#ZqNP2J|6S&Joan5F(r;U9nEg<}3DL$eN?@N6H!#-K$`y`j^35%?@H49xa`GZ37yr-&YOZsU-hUGmm)7!^t at BkDSJ~? zY1kLlyvADpL6tev^c?BZ_Uc=;{!7v=$*=#|GSW~>FpBCgsGWJMl{!;-U%_#Q9&ci1 zgb}q5a8e>^^foflC>4#sTv%_~C2^^!wzrdFWWBA?ctMR-uP3!GvVLPxmH>>_i^$(d zRTrCu|F$9)QEkN3r%a8()Yndx{lkC!2Miqkw?897Vc^-#=O!lPH^z{ zoHRC6N2D(nPfJ)eg7|BZjUF9p*ko&~d6Yepf;?VpUo)_M*_ic2 z3&`FlMYjDQy}46FmNk{D{{aw{Zk6gEsGzoXb`wuiuRYKF*Wc|_99&?psreiKQkN)=Ynm=^yVFQ)f z>j?#|t&YBGqi9WP4Y)e)HCee+9Tc@!jbHtFj_`Md?r+n*8rsprP1Y*uKYCwCWa#M;bELliuNVzHCckW4t#NVN3itiBHlMUqkBK_n at -I|bS$uk1SPI)FmPU&(D-oWl=fpq~ z=};Lh(b1Ciw#c~7(&R7aegQut4Ss<_PmVRQhCF4zO!KuR9 zm=V at miw9W)V=ZmTN*2s%{ctu5*?^$xY?<3n*$Yr3#hocwk=IA#l at c_ z^^z90^$iE&CJl ziPjb7Gs74>6h3uL;myp{7~to3R5Ltbz!Whlz1p{)I7v z1*&0bwaBz>se>fi0Y5Q8!bga(DS$OZyMWJEiqs}})Nr7RK>TWh!@SgN>zfg$Fts^! zX)!K3uh%os;zuOgRtp;S(yA at 1UzOCe5Ll0sT5*B^@}!x$8taBNwDQC+3j1iL16o)O z_&ESA4%1T4_ox>CjAkCL7KOjqHp6&Zx7NBCIh!3!lq9EUT!bd=u_WKH)VKdFaLP-Q zi=D!fZg2<8NTc$J5-~5n60^YWCRB8e(RV94x at kn$TtWg}p)b&;i{ zx{cdtV5~icw3$isj3JCO!L8gt3RK&kx~UvmN=r6;Vliz6C_*6{*8&Mb0bg`Q0A-1v z-E0;c5&~_uB2d`c)521_owU)sM6y^~Z20%?;#LhsS~CxU+2+KOTLRWmv~?DLVxY$A zm$}w3JK>O#NWfC58wAKNVe4>lE9-7_=UH+$ODWdnm}YS#8L^j{0$mhhQY^OlRDr-^ z_N!iuTL&5$wjyKlMAB)|)i5#|z{_wj?iN6St)k!!WIEF<*_Tnaa&Bcnnz+@%ym;PV zi-D7SxX6J-S*xeTytQT1sTeS?5c=}S1fB~^6B<=5R$HgWu}E0SUnQ(;fJqt_8>ux_ zGC5}KI6M;8jK8gAH9bo6zoND)gP zTQTya*{S14MIaZNJLM`((xEis>(SgUT5Wq~cH$S`$)z<$ zAZT$S36>=%wm#dkb<0+8t)W4v(tmZ9p88P6Gr`;y7Ew?#W7RF%V+$cUV`gVaiSMxW zsg2P{J{@o(>-)FUqDctp^Us3P^wzND=dEn2eT$@aEE3ETR>2x^QP|Q$P=?5;$*#D7 z=lCi?u5#R4tW0ekGB{dVw6l$+bdbBRHkj^HTYIj1)mq;3xyISnwWkxgGb~PRwLT!Q z+D?7t^Aj))PtS0({mf~;r_Y=YrzcLOzv8fJZj9 at 8cUA6Xhts1or_<^FsndMIsc>@b zl(;*->0)wPusWao{=d(OXNIhtP7j$-or)(18gI9^BQwMU<@3&y^>ja9ch*-W6Y`un z9c@}V)jfIYB;QN1WlNl|iOx5a{o>i4ChC at Zmn~hw-sw}C8cvN)A|i~kIMjA_|J-># zu3>`VV0L}R_soboI(0IftVN`@lvOICj?TjCRc9y8RHxIaA(G{jq8x(JbNNDZUKABK z_cJ%`S0u|Bq&a=6X3^n^lki0F)V5SS*PQJlGWx3C{%`cytupB$ZFccZ{XA|%`ni6bfSgego!Mp7KjV4t#A7h`FJ>9T}UvqFx2VXzdBf1+jPXi zz_rF7H4=|bp2#Q6W|BLce`+`b+`_jApKv;uIOe|Ga#i5d^=M8?hm$AbiNWy`$8l!H^>R`?+c0P#JBD`g z`XqKa4h!#q6O;9`VMHjEAQ3Afx`K*dhUpX4iF{nzDkgghE0Jl0fYj~FJYY-lPf+EP z8qE3$UaJhU;0xoC%R2F1nSKIlI^ApIuR3Xd z4=0A0p*mh{T at lMBK*AQ!g`nV%FVl%0qfeYT7LQRY!16WY#qC9qJvwFPkN`CzuQ2lX zF}}yJMAyu4?rf*9iq+|!pndvOJT(?%bR4IoCBXvmQM0%s at 8x{25OPE2}3Z zjK1-BI2Mj)fL`QxgH(i at 0%zbMfyV{j{xRe_#st{T1S`G4r;pEdwc?!j4 zrg*$-3LQO{6#quMM$Bq~vJS8YEz+^RG%qPNo7%b6VBJg2@#qodHt(ES$2fnCQr&%;=wdg#SBYH-99z{nJnH%%vx7+)5T9w$%4Vvp=QC;_c9 zNkG*(Sn_IW6H7)p**YHUA3b*T7?y}6Aqe19GHiPyTAE8{y=d0U$LnM9Xmp%Ay&O4X zI7UM`Mm at _EtmP9=Yy%T ztK-eFAu0ro&8|blsFneuXX$*B>~$RPhvTDTN_)rhQ4hP- at lk;Wr_f-W*IVNn*z)lX za=ZH*l&yVTojrt8BuZW2r$>(+J9_lU(X;13VuN~0R+d^?4I5+#$41AG)yKlo-mxP` zjvggtmBYf*il5gNDw0~X1*z-A at l>{uuJP!QJyJQcisbRs|ImpBt)Xd(i0*n$$zJ(a;VBFW(Jkt3FJ z9VLo?j*}B+4pNC}fO+ZAO=*Fpz ztQ|gL;*Bps8$UU8RIgczXAVhQ8X3NhG)DqEwLX>q?fOwx;F1f`^6`O(^guofNBicq zd?X*%6>{_t%*<;Irzx2&w2k>J9T_54I;>mkq7cGGG6(VKLQIV0=DMUS8VXnpHSf0~N&;Ab|=-_(?hMS=Y(Y=^hB zQP(V$kBfs-1GUm9%(_IWwTwC_sUzQWmE#ndB8qsQac91oZd~Q2zZ$vJC`k7i)GH&@ zh0FS(>5jj5tci at Hd|l1Gq8Bnt2pObpizqH3bHl%)#gTWwNp0!!;I~*qZ+XiQL)N8B z3`Oa&rhatvntGWIJKi2h?56i*D-FG}c}r_1>+zI-yZ&M_(Lzq#c!MSDTUc4IB$_ax zkPooVYijbSZC4;K^kKp1-FMU|O{JXSw`vRtUl(j%>u0GZ+G|w|iA^s3bk>XieLnS< zV=}3RhFK9jtbq^B!qgy0&lY$|HRk(m-&pf at FJ7i)4XbDks6K)=I<~#qYO73 at 4{Jy; z2^4ARRvHKTlL#t`&(!l$k7?42GEIN?XZBZ at L8ENE6_7u+K&QEvSAX|gw0 at b2(eT^u zZ$#mX))N0q<1CnEbS at nsy=#{mKB?_e;2DR$3VW at KA0SlYGiy91%F5c{nRi`jZ#|~D zrI{EGw8>)hjIs=(APWacb1&~Sr6z22ZPJ4Vs_*{!C9zg&5vk5-5np;^Jnv#9&|A+v zwTD8&)*oK#Z4w%H%T(`lS(;o%<{7HfdIKi)QZkB67OYB42`K^JwF{@;bSVdHiL?r5 zqyl_-SY4m&5elKRsHm5AO^|kEes!>GyCtdAv29|P{{AVh9fQW3Xa(0z;8$tA9!3#s zs(zj7(N`2#l2R?aDpz}6%*}%ZfF-I8s5)8}mz2C?(}H29!n`Z0CVlBenU~;q at 2f+l zaFhO)#-&K?t at 9EHv*uly!pXuncF{R^U1yHI%ZhK@=mn&o|^8ca*eNCXKwp8AW z(jJ`AGa75Eca|j4ipwR7B>^?fit5Seh*F0|Z^@Q0NT~u>QMDSMdYG?g at 6!28C=wdt zZ}cdHajadlwJ1%2NU89-c=NVhdeVWXGZYZsSuk7EIZsBlJ-YDnZ`+~qrJfb$TPZR# zgo^mLYeX`+M#-jOf+9{Pe2SuObIeZRsyE7WOjnjHV$o7ZgR+yl%2@@qKUc)_9DX{C zn_D(?`2zmqIBVsy@^L*ckSMckm6kzE|Ih?t*IwpX5aj~JnslbDUrELVqM4!fGDMY# zk#xM2XKOgMiob<9J at P_%?BAF6p8;5S3o_5$_9#nv6ezKe5cJZEFXR`3H4Fv#YJTUP z_r~~C>g)VwP4%rBT)m*)nMr~4w`zaj-Oz#x_T9^ zo1UuHFC7IF0hRv4GuMthqGv9$KmU^$tcL*ru=bZ;@~S!|mgaU9&Fb{XfK~clDzH*R z&i^nzyGoC@#F7Oo at x?XOEUWaDr<$RXLHMORs+k*jA9f6hRYR7h=A$O=8bX3!Bf_ECdFvlSMP zF4EOdnVJkQcPo at Dd)z{gPU#s1YMFKE7uB{;=XxmVp|tzcp{Tll>=#(mh2=>*WW9)9 zv233(prE93`N~Vl_52ABlyF;*)RX?>Dr at T+NRNH_u~e03542`PAKe9w7c*n+iqNkK z*LzahlN04pn2hMEsqflNPW3AE_I_=f+~*q{T7N$!mjZD;xT43!RB#{vwkN6lRPjS# z{1jD9OqA!Y!lk}>$I9(>DdGo;4l4OuW$H=w-l}VFE^;CDeB5~MY at Z_3ql#S=*25dR zUCzBm?zo2~QD-7!;+C49(bg?t%W&)FEon>GJlX|ty{Ki-BNEEZDwBF3}X*@z$Y$s?LWjd80BKqCbxDqjTx0 at q%FP8TedI`T26cL z=|G<$l{RX84 at Pd?l9A@=md%^DY_?Hj;=|ZaC=FYEYU`v{jmXj8g209Nn=;Zvd=CUl z8cR at Q9w5Qyu(@#5kVQ}`&S;2yDim0QWy01egb4ifByte}!e5k2y|}^Y8Tb#>LgJ2_ zBy2W*Qe+k?!Qm9WMCEMQ(nTS at n2wDn7uTH1k~&7GWM4CDy$Ls1Uo#eGS*~y587q_! zue+6)0T@=}Zlq>E{jgxUkvg8mSZICL?9-0nmt9fy1OWxcyjW=U4Go0vBwyWjqEn6$` zM>zW at QvoeBc+2LrxxXNIq{WriLNcTAVQZS_7%Z+_W1f95fY?7uE8LM-LBtpM*!q*^ zXpoQWY#uGZO&ZgtL7l?S`N|d*LSWd!7V=iJxud}>h#SpS2Cleu+5tGRdAP8db;P0a zv2P>WyY=t{!*YP8 z7~t~Oi7jj94U`iQ%p@{kSSUoT32b;E@?>q9#4>^qRJ(qe83jQb4D_wP8?sm0GPyY} z^rhHBSimK-j&(~}9saRv*4(_17G$AHxqyA(OM!y)FRCkYrv*g at T88X+n8{fPWH~q! zlVMB6ej{x*kUddHK?#k#<(b8nYvJjdhzwh1+ at XR)TqvY^mbMz6$drJ}6T-~qfvX`t zxkO1xEfZ}xbvCcbiy9XfdiV){PPvQ|N at r}DFq~UiP*s{?JX at RU5_Oyhba0V_m{FiK z9^P8ZjxOQI{v)?i=qzHd4F#(UjVv#l1}!edl!bM+C}&TgcvnL6trp7JVhhfg^0Gaa zT}Y;zl*0+5c~E4YX01tWtc(eiC&$W7EHvXIY67J>cr>6AzO>oqh0eloenDX&k9#<8 zSdVU<#Hb_-IaUO^({D6yJEsPE0;lQ$!l6PUgn%6ae4-KCG6*F;cXiFxF zn${X&VMUv;INx%?Vn;&Yz?E}kd6|(-(!#1_Z-v669^__kTqQ4Ru*W46%-(S!&*MOp zm~kANQ>)D at SjE1Q-ux2g2Qni^tTfPRYZ=}kV2EyWUR)S%o(Ek$MB&T&%4K$WR8o#f zW44#^%lc6!iu2Yi1SXDS=U;lqdGq2a+p2kV19 z?b9-rHB*0P3nNBsRz6Z6PKWvoJ#;V}>>W6G at W27sgF^4t+-9$i_QYcxJrcPg9}b1X zzH)Ui9f-zP8?TV;c6>bAIZ`obbQqy04#oqoiB#w2tJgS`ov+(V>IsU_Jo* zC62lS&)~}Y9sPW`Iy5+VNMnZBEFP#?DnY+=jUGFC1Rm%xj7>Z|JaqU_b*MVn9Ed0S za7Ch*BvU~CWDLpS at nZ2{I1mtjvhRlf&ryM|1f)-{qupr at __@4 z7ll`gH)GpAy2#++&_LreesVDxy9|p`IZc?3O~ARBpwAn7bO4LT13kvFa%6O&lW}dg zc&LUuF`pKj8X6+AMh6ds18YoNUN%lB86g7aA`NOYM>=HY4F|jCRZYmtBB?6uV(iRw z+=*O^*COb&;ou-17$RC&ZoseD!UTqkG#vw1I at mc7mq+`l7Z7ck3LzClFUdU4$xPrC zfB>$RtK~HuBLeqWg%RP<@NX0b+;9Pxn3N*8!_k4FK~!5pxYiXywGh);PEeB7Q6t$P z5al#+S^`2m)Dvh99gOCWe4tqlBE!YCQL4bI)zKA1)C_ZAa>Q!wcyJ(d)d#BO!G0!C zMsN%|mX9U?;DpqpLwG$OY7SNg3lx+6;YSr&5WGhwh>`}F;U+g at wH)^Yv-SPW_R(x4 z+XEo&jVWB_cpxmBzxJv+W>l6Urx_90AVp;)!R zl=m}$v)HOYXAcGWR{K3&#-o#Fa>q$1Ia2vpLAY$=ml+ at Tuk72uZ=d|B%wW#SHdE1V zf#UKQSW>a4+%VGQywA6Tq7&vDJ zp57OjWD#5#*a#p)3f za!tBvY;U0}>>KUhm-dCdkqA=quxyhoq=N-p3)^~s-1o2eXD?qnRD?vWx_%JU2PO%* z&c3)e7(^nt_Ow+UtT9aSR3A5M)3mp*@Fr_ at Y`=_T2BK`@d8A~wOpqpf!`|K=a)Qo9 z$(j#0z$tCvp!@cwy%kBzwRdO)9Xp-^n*FPSx(y6_ z(;o5eb_Jn0vZ&7Zu(Q8LmC?Sv=(9(5Uey^1LWNPXt#V7^OtmO30z?E at k&9S+&)z*@ zj}B at eqG;yi!@`OLox_&&*JEd|TR80vdm^D@($rwpVVdzPQI_`0cT%p$4y+&d^evso zR{V_kfgf30A{7k+%Z at bMP7T+0pLyWDcLuXzcEiU87$)wU;D+mcNA_eHGtI3DT^Ou(HNzHy*Mal at 3jD zVv`GfLXF`nw^`V;wtLT>-MdRRs3k}=t4-kRfqb3zvee!^StA)|_#l*Ybr}xyEzim$ z-C||sg}irlk9ly9L$m&eToUl>4gOGTJSEzj3dwWV(~CyVzYX zoTVsJ^|&olC#r(3DI(9jgHkr$z^kY;Lw}NhXU;+u84;%O-lnOrClPj{bRsHUqpai7 zSt~t1n(H>Td4 zT5&n7+FWuZN^PlaKZBXl8SCpO>Q8d0xtJFqdv&WyyHy;RAhAV4R^B5_O?>E}M|r)q z*9qtP*j%N%s+ugV-<~ISD#m7NJk>I0aN3OPRG-?~WqSZ1o>vzdt4K~Pqe{BdP}ass zuJ;8LZ2Vz?#ByFP>~*h{dOb&dsY6d311J|$O1`IFFnkK?X}y70^+L^XZ<0lOvU8Mx z2kfbpRpVG%FFDN{U+g(QD3+_>s5O=gw8k`~SBx+^8>^1~T_a)F9*gclo^5Zo8La9)j?T2NNh{mH`4g4K96mLhz4 zv}|Wp)~8Arv$k)gItvv9MyuLavFqpzrPKXViCRjtBY<&qsn^}mv?;dJ9x5sSi;A38 zzc9h~5$VRo+BP4~xjiGN9?JYTB&PUXn?Fw2ZCk zGImLh%hcVE at fyyST5**wT2UXBpXv+eJ=h}nrUTe{b-t6cU2;{bQr2peqjl+0H!Oj= zVq+_0j%_U~$`wm at OPUxzFX5He_LPa>qsKYcPMoxEWw4*mY&ZrniM{gQRn&tR133qC zQ%1;yXh>4Z^`>$Sk|r!5`+9LWt_y^fI;LqUZQViEgqSMmwR;y at CtT`zGE+;taK39N zfdWcTNwgKR;Z$2Wnof*-_k`CJ4w9`t?KQ}O!vFQIbUIpG3x7Vx9(oZJRu$BSG>NLp z-xEgumiA|%eM&+_+wyPmmzkL-AKYoWsbUS at +V*#uJU at PtCzA!O=2fN3R at aN6snrC| z;|_F{H#k%|z9*0M#{pC!%e at vW@DxYuw6q at IP^rHXO*pfroTqT{=NkN0#dFmNIX5po-RvOM9_nZcPB7_S)d)9?`PT)r(Dy9?SSrsk&=zg0Hcllag^ruP! z`NCBhm&eN;6iy#A^JYKh^duLT6K6PSBC*`osjECS=!TNzxpjn;rvs2ek6h?EB1N-` z$?M~5CN*%c!KgoWS()sENpgP?c zT67bTzpcq<{zR^v4RQco0> z$55;WT6Y2lN4Pd>QR!EMW-q}e`GZ2rM>19 at tJ^IZ&BVpJx>Jg)OZ9k7Vl}%;5In3Z zb8dOJ44+dYPI?4xrE?3ro*&N5&&9d5`MJ4y(IkZmt^Fo_*YApR_uX!IcjiZP^W0jW zo7bEvrvesqTRbhSZ5F}6J$)NFHUF;~q#u1**+>}iPLK%;umPeO!or;GpXWB%KR^@k zfhiLyqkq=^`qaK>GZ(gO5M8Oe{m==OMpg@*`7k${pG$KCwwkNv#8cEp9c<8jeJwCG z-!nn@*Wv7(@n2#iAQrNIZE*dJ_=xjSs|8=Jp2L8hmh at oT2`3mJ&5+8>Q8tGen==A8 zLirR8uyArR#kQC6>bWpGnuBfLVj6blyT~lriuhVYJWsn-SXg5#z6Y2gzL}F z&CMFGjQEY%l)|T&4pZ}eMroXBGMeSTl9_l_ma|V`)WSj|2|ttk z^}-{zQ>W}OGdG&W6GrN+6J%k&60JBtp;cpR`)Tux6Va7v5B)c`knEt(3;hb51x&9SQSlA3$B_HmP9-vcuhPg1`(bzE8#p7DgShEej8cS-_DuewC zwr2Ztv$JE`x;72M{HjcEAewNTp~kE;Q-y|m=KpM7+AzQ7Kobzd?CRVsYie2XSw=e& zDn4E^Z!r8PWNqdKj6>Qr{m&fYPDw47V#4u1>H3s`qggpcYKfN=i8brc?ackT?CQI- zRw3QYPI8P{w6V=M%9hB8!DfeZh1Ht at 6=OrD`AH#SHUZq(Fe_5i#UfM|WE6|LDHraU z#FxsLytDAMRaHhL6z2G)j8o=JvS*m9X2p?q4CrP&LVvDnXqcaC=ALRrUsT%{Rlq8f zVEKfyXPRbyoS)0Zg3Rj7P8-amaW-FjKAM^PH8^a57|3*%{5I8O(BdcDQL4q==IT zS;e>o2LTNH#n-YmwQfDz-z4Bsx*#_*t(kvXykPDirD zs^>QQV7ZeVJ^!;HAavRrrQNcs7hlN2f>2TgveT% zr8lKbmP+mHEwGWIim!oIo|#>fEf|U&yvP7S(tO4elAwV5Igz^>%qMN?n`@{2fY6Fc zlXJ6^vp at L|tvib^41x`UMttcC$w+-x(J~u!VyuI%E?Qt)IkswT)Y&x9Vx9Sw5`UO! zl8g&9Dw!tE4zymg$#iWvu}YrLw&0|X37bZ`IPaIjn9<2&0+#PgoqB~ImG2)W-*vR(k?20?Xj at 6w$O~hi+C+_PO z-U_-0{dK=P>`As^wX53Mx4lB4-i_!8`-gB54a|Wib*}{ACK5A{#})CQ147Ta-piU zxI7WvurMgQ?MIG?yJ|UMN8TX{D&lNOU2${Y*p{%TtA+Wvx8trpe%Q4WpYQ1IWZw22 z at K%KbggzDPl5t1zwfiGVW7OVZ(+1mj>|i6Yi-njg6htsXReJ^wuHyH+b4`k)`gUH- z=p2$LGC0SU(CebVKFNfY*_vHlgw(9GW0n238-F6&vw_D60%F`<6TAr7&d((7|zt#_!6~;wN!uRc8iQuoxL4Fqg7E*PCIJ?9n8h;1IYrI?0vJqG&z{#?!I9u?d}^+D+`yjBRW#u zQM|=a8t*Y;{rM2^JuEv)W3X3kuYa9?WrvMeRtqkE?IM6Z!aM^5FJ&syi^NLfx6hW0 zT3etC)s{yiSnVJ>@7FU9R at u*1t_Z_&#F*&?MAO#g);l2UUsdbJu|(iV-bv)R9763t z2r$d1U+aC1q#MG<`ElivfgqE1b;viZWBnbt`PY at bB-Uh#p2`(HCQ)edt7r_{E4{*& z*Wu}%g&m7APfyBZM4B?PAW#DVNu7SB7yeoF3S9y3c)^Nus#tl(s`I68s4b z!EIww at NPg}swcAEBeUMs0K$x3yh52>8#L2_{bEj%+VmCXCA)9(lbj zGydkUE=2OK=QDe~y-{4+TU&W&rOt3=9y{}8vf_>8ig^MeQ|V>3mA}wX?=6jcl26Lx zBy2F1SE9xyADi`}zde>yb+Vej?ecC%-0 at Vn#*?N(0wH>xsJ`~bLf-hN9A}axhj-)z zQJTkst?wNu at o%bDljwVt92ZNr5XfsFi at o(W#PrKH7dC)uO$d=7X}hD9GePeTbxAO1 z`eoH#Zk7(U)7a<^ai#1psBt)qz*K?Vv5sq>;xLCmHOXsxLc~AB=*0F z($M!5U#MO2W@;Pb1i-4e1uDT*3)YsjsfCU4XumSqizOeWPgdI=k4 zwpNm)y5ufGNfY_k8n%cBQ$rZ~)fl1SlT>)NqOwv-dk!9e{gE3^ooovI;%$bql}aBz zq#`bAu}qS1g3@%UO3xm6nuwaRZS=#{?Nsy9`B^mN&5 zE|aMBP0P at z4Ure$)f7gm>`!cWg=C_-yrj7*wc}pY9#z!798A7;6{k&%8Z!4$y{@uF zmAX+NB~m5;4E4dv;{s at FBUE7-nVuS86|NQXMs0Z%D$J^<@=n4C z?n;G%8B5if-X}_|Tz+MX0uD_aaK9_dlsImWD at f zUnaJ(!Vl^B@?lrGEtB*TcZ|K_;{+vCaYGr$FLNL*cp2sO|o4mwTIZVS<8ER#m`a5m34`7M+?c!0# zix;|=+IUOD)DPMt5(w4RUynRfB8s-Pv7}N_wr>IE at +b5V*mCV!aiDJQuRkKxVW)_os z+V)`VTk0=WIa*PvsYUop%#!8$$rEvBWoo#MR7I~pm{a?A_FghhI=+HCu&YcPZ1+ at 3 ztpmze`a{+k^m>dxuSgDuR`5yhz3$By+_^7 zsA{*|RKhg*8K-J&MV;TN(tShztdDzoy9PFu)-M8c#*q=TqkGNR$hmr*SAu?NBDbPxfxz%9z7R#TK)I;yq z^C)dKy&nO37$$SutFgZz`P&51+c1 at D+w5BIz$R=G_I9m+TYsJQx%%H-9#K1TxMo6m z^g~6xrC`{jLAK18#`Ms2RbA4&^yLK7{jp#**`*v?HwY0J%XlhKg7L?xyr^9dKG at B= z8XxJ7sh$tfU_E|P*&S15TUB}cjZ#~1*KQjn*tlvIT$T?iyQ>tqkyq`ur!*Ye74B8S zPzko*kK?|e?o at LDuKP2)l?ID5o_o|z7iT(V1WeU5mpfW^6Vm?1+`Y at rReo98Cu|Qk z9Al}yHQZq6mEw4eW`8beD_uv7jog|7#$MI;Kp@@&UPhmHMB8yQ_~96`R%t at _gN1&vq-_<|rxU{>@| zvng!sZDQ=EjcMa()5cm;I?jj{f)6T66oZLVZ|ZK`#9&Fn=w at SoX5&UNAtD5)XEO|1 zv#F=$%80zNJF}57GmL at T5L07ZUJ-4GyMiEl{ub3SEkA$U)Z0u|^8gWf<8`4|6&zNHSi|K6&*?&z+3|S0*Xs+WE zk|cyv^0fytRFLZYn!#j0GhxQ{LbVwa-7LjC(JXSBji1m^EHQ&n;(~mW8hzI!zAP+X zq)jHo#+f)X+6Xl%%4*uUK2vgnA#7f4T*bj!Z-&*=j4Bh*(V&oKr+jN!Vq#;QS<|Q) zERmtG#h}_s`X893jggC3zX0w4jk~gj(f$6w$Cd{m$gxO8_`PuB{ibxA{ z&Z=gyiMBCu>Y2jh)w3(Ev!+dgjx~+ar#Lar1fLRSjBa$8I6*hn!g+!reKTd60a1!M z!-jbq9LwA2sby=1_DuMhW~LNclo*YL_uJrpmMO5b#?~`s4d+w}ocJk;2;8BMF_F8KV%X at X(@@t$34ibMe^(e()$F$Q2-+0$D>k!-~#GM|iv1 z)0>`}V8z_EB>`y?*t5fe_{To_rARj0*seZia%MO^b~HSirg3)0tXknDD;h^ufXfuxOj zMn6Pto{7_IEN$wWjq;|I;$Kz`n>tG2hUUQk#Ob~p3ReZ?7KMajA0?#hv<;Y{Jb-56- z6Hv(Ztk3CY{$hMG;7T;x#xjacHrbl?k_X{7v|5;KNvcevO_>VKJ1$;s&d`eXcKdT;c{_riPqci-ca-tE8h?mO?io!<^`_1}K$?YG|wZw=pk z>&^6Lb+3E(-o3kb at 7;}eYbMq2hIdBqzLVY=yv_LdcJ)?|aeVWe^}TR+c<;{La5wz1 z|K1<@RPPO0JeI}7+oN~huHLTS3U7|IbiNnwj_xshaOdvbKZZXJnUUWM?~XKOh&*MJ z at OJaoif{7fy*KYQOzSh_4oUy}HQk zYF2L#ZESdRp!MUMLl%$s2C{s`lyEz~H+c8G_ug$-J=@^wtz at Gr1PFIVckiS-qua$4 z*?kmE@{EhREXwy at eXlOk4sPGk=*sLI+?frA>$oZ6y;UrSp!^`Q21k=PyxS8{-hHRS z&Jj-r#mFtUKE5mg$SK78v23288!zaKM3B zKBYn1<~GRA at 8&jiow`$6I|v6+XjXir^YIkw}yx zlu=~8bc at 6j-s!#jj#5toQNbVx1hU~JxMd;%5r9ItJ#zE(Z{NCglTc2?j$5cTdk;DI zjwe>a`kmX=?dq1m(?@W{{y=C;Zw-_z;+qrql82l{hO`~tx_v9VbkencdksHW$W=tY zl2Ew&^F4)=zS<#4o7=bIE#;z4!Mo>(>>39%P0>LcRJT$IXg9ni3&pghiKLupJ|+YK zB@^PU(al at oRt6W9A=2}?tolDRRG$LkTg=F}!p+{To6@*00n>oVvbyD%>P`Y};dZ_i zZ}v3sIpeg)MM+u;tZ)Ym+#1|A>x7$R|DepMyr=_)xFs{W2cly?k?hE2u6xBkt2b}m znWNEww2huy>DJ&T7>PIAOl+~i81IJrKR7p2SHw?U_!$8cf zX{J6E$B##;IbF*1w8gEP)y;t{))bhu1G9rQ-0ovw!_v)gV{{X}Yr;n%mP)4UDUaVK zljqy at t@>uXF}(TqTOB(VBy`Qr3K?0 at BrVFNo6m0Cym{k>J1ntit=zy`SV9iQfbwu4 zTq1jSZ{D!cm1W!59wnC5lAZFTps|zk&2(d+ at rq)h$35iPxng~Q>3B(ppofDJr1wdi^>fVELXQ0j4`@-gI_wUn>TgjSi&*)g*zRV z>)fnwWY)05!!B5qmVyp)LDe!wyxqaD&5hA@@h7ha1SuyHHxM4 at V z@`GIkm2D&0*<6qfECTY)8`X{O^&2;?b7>&J*kLE=cg+)b?_}n50e}Z_G7z9qzFsaS z!o5|)rm}jCUHix*HjI7~izA=z`sSHZxA_Wf!vTepGVx}9-_0cu1 z9_tcm*{+Ycj*yYFM`c3Pp4Pl(TQ`R9zCwW1-r%TwBU~TcK(ctfckTK$BG7blEJcT^ zqM at hAx=nhhTO*mMm|=VsQJMNRN0{aF3EZ2s==E#YI1%jvz%RN|0Rl%O=qn&4E8e&% z6V%t^wbAu!!$1Ase+qxfKd$}oZ|x=`cv~`{Gt9neK-8}yKmFC at tfh--+Z0<_xw11)qniemtTDuzl at AZjEdid zZ%5yK8@{c+slKkB#K-klnSZQUV)XqF;fMHr{;pw-=Ii)m^!Q15(tI`e@~f}D3||g@ z{L`O)j6YUC43R0Z(CAx)3g2b~3f~OAe)9E`^rT_S at MYj<@aV at s{g{5NSuuRK_WgIr z_T6{iW`>Wxeo{RdJbv=zaeSP<8X{i!vis=EM?czxXzPdX2j6}F{de)ZiU}3E^d3KX z{N$_0;c at t?&kW63ee~s*e`3y$Kg1sf-(#wb4eD=eO6T()RHMtYPUW>&4gOUUg1UI8p^AHJ%-jE|~^nlFUP z_>Rz2bgMu^Pu>y&t4EQgBET7b|1F*sa(uxWBdoCCW5$_hTc!G6JjAV)xix;%N1boJ z4z_kM8S~?ej1 at BWz9^xREebLwXoW2X4lRy- at yL{iTHT{-92WIA5uXcf$P-u}G(Q?( ztw)h<5Xb^DqiXtQ#RS5OLVhF|Wsmw{_+s>M9JB+WJ{G%lzy9VMjg|%1OxBcL=i&2;B@<~$X)j^vWU at ySD}9kZAA2x)f=q1!O9iYR!!;;$O|)!enyZ`$NI7xz zmmi-!Tp)90hlaF;tkUiE>k zBr-lRVG7322cNOJn=L%)+S-cQjIM%gz!n*sjbx1Q+30i2B9;}x*L4Zm3c-QEmst4G z!;Fm^4b`boVHG8uHh=Uj-joZn88VFxnLf+p|LBPuKT94K!@jM$)E_p{IW ze1;?*%Iy|?3m?U%%pJbygUQbY$|Y!&mF#J-**1fMpUpz}h^#rWEHHRZb;mx~C$WbU*#<(@(#3(0CB)ydk&rE!#!& zuJWw%20-hx=jWegDgSBsR8i!9`)eHcmB9f;8vujPQ7nF5eU?6rc3{@HtmvfkL6}cp zbby1_O`nEx0JV5fqT>`)n)boU2 at bXJc^?flY48d8OZFX~Zgy>;+~Zho2ot$3eHK3r zpWqcs*wNt(D3ptBO0*N-Q7T*YzWD4jv<_~qPd@!5d=k;T;)oVB(pOy$25Bt6lezie z#-1!yfP#4OladslJkD(7mp%y$7TYE^>N8&QY4qrUT}BBk&xa(F zS8TBM=_d~!JRwjuUU`nIl83d>^qIxIyca*MKOy-5LnX?d605FX5@(M_Mv?=TtUg_3 z>rWnh@<4F?_e$zmv#SkL)S##MtUl|rfq++i5*~~%W8(T{n&p;&Ni&v-pA9}m%gE^X zfJ~uKjtE^9JfttO;h`deu(yoYdw@*$*8 at nGu$EL3ST5G{kQ;pV=_d#hKdBz*Qtq#D zkRGj>;9Qmhee=V0eZ+u at k5jp^{usxupZ^P#Ra*;TKkYs6@^ zQ(gvEsoG3EL5GY~{T>YgOL^3RgG{n=n(4zdvMjx!D^(&}x5Lt0a^fIe6pz#hZOe{( z at Um`wh_=Woie%9ZbjqfJRh4m+N?9purP?|K*RKtSW{}d#N3$=~IqY?=`S6q#2$h!9 zKT*rW$G)sW9j(f`)am<@u2!R z%MPq~YZV_Rgk7S{)LR1?>VZrZACZ8*Hq)I@&SrE|a#$FFfab8AgG zA)MeJ%z17;ERS#lbijJke5MgSoEDQ-c2UJ3;{^&url9ZAG z^A{_{b&VzuY&4{`?(zo!HYf;MF-w8tXxWw|lv#F?C at 3Yxs(@TIn+wJ^I3Hl}q}G+A zCJ9G&p2qR&%V~Nqqft84)ju<)sAFi^)Nbo7ui4XklCBSo<{PPPPRxdpDXi`R*0V*4 zQ-7?2#RY9gQmcx|xs0n!0)!B(I>@P6t(AOX>J8nm at xUq-mH?Oa3s?YaL3uPJ8Wxq+ z+ScZ>A`nyG+1|3rW$1N}6bYq<>bR}slesfAdDb4KA8q5VF4;+ri3e*n>eZrQxpcvA zRJk`!l!{Bjmmu)Qw>&XR)udITN{G_(7toSRz4bPmBSIxn)sc0i&@VNIs91tBsSkgF!V=oygexWWGPU}L4^7Kt at z4ejHkn`&OO@`%VYx5RxlTx{45fn2 zd3Th`{q;k6JSJ$+Ap1ftz4jx#5GpPARe8^jRMsBO?TIF=bxPIz2KT&1(-^)aIKi>D z9UG5vGm=w|G4^j9!FbQ7b8 at O0V#GdE$9&s#&~x zib=7*9O73iNF}Nxma=YT&9F z$p4!{S6pJ5a@@z+9Gwns at QPmfxwAalk7q2e31|pHb+ci|D|8WEFqKKwZFv&o`}tkm z?TFuSpvwS#?AZ-<*k4{2G?g^vY*~s!h)>B_4Q41!r6*Xkqz*?$eQ?E8vtJe>ZD0GT zWnE^F$2<>%-1bobPjr~twSVT)m_4kmwD4&l)x`|qF(s%7!vl?+T>kTZi<^>AEaX at k zdBefU13b3k$V$sUv!vD1>o+;RIry^;Lsk=)BW4+UnHeTZwXuh!<+;W(kI%5&xS^n? z!4ViQ((S*!K9b$^G5~YI^nqIV$IX*kwLOdEv&POMb~jVWsNlecsy>}%5r4n1%~u(M zh%eV1V?XQsAiE_+G?BAk#s5JqH*6M>sC~>BOtD9F0G;=2-{3oVr8;Mht!bA6HoiHa z{`QD1tf}~#-r+2s+N&VF0ELzHW8qPfcSmDPh`7JMEYTH5gouo+OAP{85K`u7(*l^= z&m$Xw3H$j^Rf at nB-WiC7$+ss8Au)Nw at 0nkF<65{D-WXkbBfb${AHDHp>(`=BOHAv(@%rmm at A#eZShh%ZH+*!Bd-K(``i=B at xT;D4xF!)c zaA9I~3b{~S&)1 at 4sNaaM53auc`kgT!BUT*BjT`LZ+&a(()wTS_+Usw;@%kIDUq#x{ zRR%kQuG{>^pozVxZdS+&J8&HZxh0>jt-W!LSxhrMBLafCf&%_ZG-w+dSl4ywlIr_JJ}fJs#tu*IIkN{S4>`Oup5J3Pgm>P zV+zMDozXH~>%DOe=irv+^?Y?q(qWB!E1R$h_9I9~u at uP#4n>5~RkTc3DWkX}QqgHB zah9+Ya8}n5Lwk(m{b+Nt8UCuoDfCOrS{V;=4SpqD8%U;H_K>5l#;d*8uEMRrE#+dw zWMq+;ke^Kjx4H~tGxF8d*FcqjYuj`qgkXzUFv|P)yKF z*M-0i!|K;NSHo+VqjIvSf`^nnY5-Cdjbz`aS6|cTHG at XJm2N&2nxP)A%LwUumu;_G z6tsGUt1BSuDs%V=ubFd%*bX~S7?jPrL&iizSiAb#6<~K8j$XJ6j_j%!L1?XCZ?0Ca ziL$Nj|C=yPS9|gaAfBj8Xp>(XUBOcZ^|DGdB!J#{ZGwT?5(jCVEGL>6hOls1 at mf!7 zUVEcz1G$xOAOl3r8q?h!I6#Tkld)xOjRj_}jjq0yUJF;KQfMP|lHeR7={mXxGEcZV zaQlp|U?f``p`rN+>VPjq7)BH|;cIh9WxK?yL&Qn1#Vh^S#H1EI!i^3RHE(2#igDalHbv{bIJ}^&V32jaL)fBSU1ohq2R@`Fh`c8!hsMLad#^6^NBZ zYV;gjhMKQfKV7d)%Z7;Wn1q3U9=&!YU6EHK1atqU$1Z|I1PN~h3^@4znJ>IHyz<(W z>PouV^N3f5s6AOWI5Bw8zgArtUcSP at PO^sUeIXK}(~?OEH3xF_db~P%4U4Y`$SlsU zbVVZJ{{Td6N;LVSxzqwkE)Av%z88|ScDx)e2af^ZuP at 4!`4-*jjgIn-W<<9 at xbpP! zl`F`qQ$T=|Ycm!QDjM at _czuP1s?)CrIbR*E9QyrvCRYm~%x!tZc zT-PRwFLw+zZaWSsIZy^9F_0Z(k<4Dzs$O4f)vX0ojm(#on+&GUM@;>CMf7*B z)R)a$YZ&i_QchO(jo?`bYJjELT%Zh>`)0jxdGPAxSBt at u$?Hasq!i1g71(#Q$+Okv z(W`~3FOjEIm|jOSWRRSFMqRlaFR!ugtCAxVb9VZBbZ5rNShb^5D!ocLU+>{RM6S%Z znI%gmcf)otYJ7Ec`PJ|$+9wPZ-dM4CQ+6S at CH&OAD%ieuC0vP@!^iI` ztoqi`6iP%Un9G-|SKBaNSKR=R&8XF7_PqQmyDM}$CSUNN^mJ)sJqohJIkLG;%CC-I zK>#~GJ$MplZNrH(kf_oMsId0nl~-RSN`#llA#c}8i3<eue|z7ctzVPG<8NcjP5IAkdP{jDY;&WuXwJDgn9`kIWi)c?^d%Ld0R<~D=mE# ztMZXfpXN%3{TdcYZt04QS%Iz|qVkn*t5=5q{EA&74s^<8%exk_0VH5uB;o*I^p(ps z?ieC@{YnH2x`a}aw!y-oP;EA?UK#xJEC0-*o)v3w-A8N*ZVt;=8Z&7yhoNPU;jg^G zrBNss6|O_213*{|$lNMyhRLsle{S#Yr-xM`^^)Hp&!0xSyBr_X_v6RIk3Rk=eboQZ zS!Lam7FA~qPT&rF{xp9Q9&|r>@ZkRa2lwOs;m7ws4rS8dL!<3Ar{#29Mk0emY@|;jGs4GzzW=dyVH-Z^1*e=`p+fmFzWOAgefoIt5o6;=Lq>)VTjEH! zAPCmY)G#A_U`+cuJuGdVF4O3Af8ZuH4bn&9!}NhT+1e-$ z at k6;dxw>C}oJ_;~;qU{wYE`rl`pJTYGzknJJb(;|5Bm7|{>QoaxcV at D;N+$lnSpq* z3X4=IIAGw%73#V5st-g^!5XIG$qab<#`;v)I=HRFM>SG>_(5RR2x%jNE`C;FvG8Cd z#-qC5lVTr#iBjkzoBE3%pZH)Q;YB9d17&%rzAe2R1n{ikL0(^Ymc9QXJ zbo{KwR)6KH%3KwIz(^>rKZx&-{-yB60lrwE$93Xz!i9C%JTXBXW$$tjCD|G z3~_D7R3$vB5Az4B?|<;Y`~UL(zqk)-RLwRlfpF!J at Xi zvUnCE%nCwH?IR;$BeEDAC at TLyw%+thj^oJolrIsHSp|^l9s*UUDgaejrT!w407&fn zzV9n35hOuL-CDST*eLbPd)@Pw&bN1Fx=8?4mFt<`HS6PUHRCF>bdH((HiD}c1O7xm4l`L6&K4H)n zl8l)HLo59_d;vE+bK`%mS~`$s;eY<@)7$I at pZJ(DDP#BW#g8w at vp$hCQdn+8V}|S& z#MO^0FT_<*9Eks;|DXTi`K$QGHW;32_1^-cf8^!H4475&XR9%W{~5_o^1`=+z!H{q zfB##;+y09mb)jlXQ}|s|164WYvS5sV%Rhz}fj@~XNdM6=p>~Q`szanYbueOJM#LA& zm7-s}4Gj^g;k>3A5sma?^}^CZbu1duv{`AR@`Ue+5RfO>@M!M6c+Lwj2LGqKSF%Ku zbA}dfjJPuV-LK3nGR;s6kmpt)XWB4R#2D>P38>{Bn}X_4 at 5 z2#a21#*SV*x0sm5>=hws>xkqEd5RmJ-HB=tejNYp$M|D at k)B5|AvCu2c?GE$Ued~$ z7hepYzj*OnW837vKa_&F*|Ga7e9s{LHv2Zu)tM8MeTpUKqa{2BI+dy$Ha;B zqA_a}9MNt-EVX|-!QJ>!lE)kfKaL2kmKe)A9GB*;s75wPc0XR>?+w(ha5Oz5NJdwT zQ9>pluNasyp at KzY&6S?_pFMy6>{%gddb8*G))4=F_3x?-7NlZDB$zLrXRT1SKi?-T z@(ETuEYa6UFRJJ9+4w18C&PrvBoAt8y(bU5k7(ATLMemb<@3R_=e&Zhl$!(lcH>}r zK9sN~3u}!ns%A=vMC=`H5m=^2Vl-mr>P3DYo&_u)Jf%__?4CmH6*ZKl_=znEa9Cb8 zgr}otPx-DS8i-$tXa>qejc9PQV&Lfcv#054Uptcq+4mA9lPZb1r~LH*3cm2Xc^03t z`6)D_xfg7&fJ&TYGUG?#p)iep6yPHO$F&29$@+NXNh6`Cr_U| z)$BFwbFyX&JR3-xZLtI~K3jhJjNwn8*uGGgjIi%tqO4M=FGqiTk+6O!Mgl0;fY1}m zX??w-4E*of9mc9GDjC^`7z!>C)BCLQM5!A8kB$}Cs#H3$umm$d9We+uT_mud#Syop zY^=2tauF-iudEw!{*|32SvsxQ&-iQK`qjUjy;+*K(1fH at Iv>%@Y3IMZtC8_vrX&gQ zBfDCt^z27={hse_j9&hVo;~y?av@}jZYid$)W2TQ&+<07b`vzd-f6$~o8&BOr7x}F zr+a3#(bcc|-UL}k;?l*LdCj>s|1zzX)r<*@#>_Rp%IY1%fvBbp)aU^yiOguqhYqnW zounIIoM|;NU7#c(NTpR{7i5oGLn}?rtM-1%jH01V_g{W?0Yf5&UdSYxm5<;pUhJe* zYkI7ir&|%KNK0=`xs#dYKHav#sR*Pr;bvB8HJtNo-eW;F6=NsS3p>^NX1oKvgN8u- z!C0vrBZi_|^6x8UX~C*_am}@A3{cq2wfO5ba%v|%$YcS1t|y{iM_$MoTBv!)F`{Zm z1shvBJJY|sGz82{0FY`301~S at zp{Q~Ksqf*wXTb`SZSu}d~7v`B4&1YEeAGxz=&GK z%u+*5Eo*-L(gJG%V6+9ydwWeWwPv+(=-vu~wJtZS9p4gPn_QijoDG{71EOb;nNnZtdKGlT2gW=`h~) zrUu6bHr#CJ?p(3aT3Ul;f9KcBKC?cJzNlMTVrLR2UCg$Q<&z7honNwgdJl6$`YrF=(ca16^q at ze`)a37 at 0`>LQW at 9P%O03^fIpp>V2Y=Jv`5yl zkFF;yt=|RE^iB at 6Weq1b#?|nOp;JD}NXW3Mot#yxSl(kQ5~D_aC2XjnXx!c$%% zi>xrTHLUxqRf`Ikl;ue*M`h;@x}i;sP}OvqvSWm at 3y9>@&dv^(C8vJ9YJ}#aoZbA( zGF9A3+jFs_`Sm|z7TpBJfvkg-dD%zaVO6a0#H at S-wLs}Bsw6ej=R)7;B=b$#Kt2p%bc!G47ONVJDscs=*(N zCIe{Gg-N*d>ynl_i5hVDRBn2gJV?nE+s8X7sO&3NA}sRO^k1cN+8H8Or?jZb9*e#e z9xMLV>Al(6Sa&em!6_SZdqBL>62NGdM-~0qGC?1PZ=<$c%5=Tf*m2ozq+L?G(>{1= z$IlvSBkiN(=(c>qkV0m1C+t*jJN`rG4ycrH?}}Yyo3P-r5L#T+ab8zeJ6 at _qn2%>L zLsfwwqUf)Tx8a4}Qvj#t;>@fn{EqK}9ix_=64BAo%bJko+X|&4najZl+4DrSfI`zd z@^Fg}LV>_*|OXbCAk<_LxNfdT;NkKwp zLk-vMs;bUe162!^>Q6irf;S({j-gDiaH79syP{r_S6e6FHH^$+a%pPc*+Hj5c`N&( z95-OhFz;r#Mpc;CKO5t>r|oa;T11ez7>mg`l{p61@>3J4ap60@%d)s-v!9wUE$CQO z&?$D)Zw-QfR>bW?Of))L`}(xK%Dz(=_V4a|N8J$h|JI&ScIlco1yrfb>>RwVAaXkSFShGm4c#cAU*of@ z74`4ZZ_}qg>&JawpYnq?uk>Je|G|U%>3+UfM0mf#f9aPT6+R98+CSo&U_XO%QG77E z|KNVOKfcFDz85~1#;Tv!ee8g-pFF4 at g!|z^y5GNd|Ng!E_rg7-w)SUssl5DV9!h?Lj)+Om-HW|k>PKGruJXD`l532h^&7JP0hp+6{dx;ag zi-;C6JGw}b9;{%n-uLdu`{`c1TQukVnJbyD&q%C582IEt#`dvL8DV+47w)#|fa$oc z+K at 5E)CmAd8XxcDrC%UD(R`Hl7Z5Q&H8($%`jQR4`hIn z6`S7@>)-u0wVGM9S(Bjwe)qsT6c at +$?u0wzy9mG)_>E0FeG_;KPPjiHI7Y0yuq(4r z{wzv4a$(ZBSbtIz8w*Q)FEM6t_YQ)Mvq>^m{$yZ at kUbHCLl*CK?#4Tfubm5A2~B6L zcw}50YVFLj=?*e0Gz!1%iORek*w|8O8QvN5G`@=oCNU*c+zzXE9`yKE=dr<`?^bsL zZYLM3&?7&A3gWwQEEp%F;Z$Se==L4-Wob{Y0%n4>Y#y>vdaV|?#!w(%GZ1S=L}2O)eCK24uQLFNUc z&@oP^p>^V&ber2P6yt>2r`jP|(U4B0xEV!_(MsuVeJ9+OHrj)~DrDi4focsGs2sG} z%22Z8&Rxx3-5&oIeb5s;eEQ1f=L&h!Sdoc$$9EW?nR@JKgQv z$+t(hgv6T!)%tyfQuU?vFtEk|^zJG|Zua;Va;Yvu4##YeF%}SDH_K{eyjMFgx{QRm zU6Rr1pweX9L8YoL#3KwF- at TI+q+4!P#`>Ot{s|>U%qetW2T}P@#;@MJbLaML{^&Ad zE7i5s_eeqxxaf^HjqykD54h+S5$N6WM2c^=9fd1Cs1*Yh*vjn+7KnU%aEl9BL+NbH ziGoZ8KWhKsb+>to5LzSN5>^$#%Y at bjX=0TT99AIGE#LM^g-ldPBO at Q80h@vafcmBY zq}xB;x_#@`&6}SxE$X?5qb<-VVc2J|7vuAt6=n+=tq)R)$;wIB5gfkCSLSd1*E{J zWvTHllL=)Hnj!+tEgNS^*tB0%s+PhTFPTtChFh;>HZR7C`$HwT@)r2tY!u|>y}P2$ zBxOK0->Pqdj_l44I{)BkXjx`KP-<$a`sVlshHGoiFj6VWcHq?ail)?xuo&C80;XYD zx at pj~o3prF>SqO<1B*ez<4UuuCfk=F6`c|6oid|`3RE;R(ye at RaO37pjV;c4R%z?) z-6s!uC+9qyUJ0?L4>t&ueEo0B!h4vn{IKDx at Imj}l`UfQ!S>8WPB^N$zF25S?b{;c zRlYO6ZDqVRN8`a{ivzA`T>fG&H)ve}cyit3ca>2g2yJEC-RvM41Tk+=T z#?2cUGpYx#mD*;uXKX!zuVWSj8;hUUjRN`}^xD3Jy2ZX<)tC@&^^~%_GiHo$-Uv4; zeN)u*%#V-Q&AUzYbf@;bO1Fl(D%|YcNN}b at Dm6|jk50JN;h(gyrGNWHCEV5b6meuy zwMiSLX>+r>5w6F7UnzQus}d*;;m5as$BxhRyLaYy at A`-Ec=h|o-|Nk9_UZ2flnm)j z2saanN|Hc8&3ML->*2}x>67>*|1f&|!{hWgec$S4`ERZA5L`M+wn*RybFK(a!VjY- zKRm7;k60>xKa`F%5fvmR*;V0re%61As_^tldNTas$&(+#57p!0_m6eW=sR7)5A*Tw z5|6`+0cz%R)2eD at j)BAiE(5NCjU>~N(t@|)Y1JAbZ>z`jH%#A+WrftIR(A|9auK+I)RxFk(hvD at UuNry z{P+B~%8ZbmE(|pI6~E8lMbsVFRHTn2U}&DL8q%g8)IX%h`TOB_%tKQ`7h$B5ywP~# z^A*AaVy4B^Pa+dn->2_}-+tHXHPSuv^Gfrgkl4HSBma0J_(Gn3!47rGi at Rsx@p-_bA zi<)9tWR39;k0TkN?v*`y_4yLge@$GH{wF~ST at MI@Q6dwV at wc?3jNis at M77CS!QC^s zpZt&v&;UOKXvY5dO|p9H6e%2hoD*4i%) zzeaYG^j>4mD)7MoU!jeVI&^xs&G1d{Ycx8f$O$yz=}@TWClyK6Sb)6y=ZKNWme_+* zk4e$!=+@^_Xj19^8NZ?H-Zv^32;zleJ(UA2$;|7%CIVyqFoEXlRXPX0_|axZzkX&5 zgi>ZA1QSA8O0b+%DwpEC;Yz zg00gZ`4#&2N|};%R1xAe{Fe5L>Ngl()C>(zYd~phs>mW{nFHw?+9`TaoruyoHL2y) zz=9RNACOq8P}R5IRx^CvqxGI598}VtL0}Qn3V3QAt| z5m%#1k;0p4c}h=6MQbv*7sC!Ps=S16R|Fv#svG1Aru>zFxgOn=Jx3!;f7S>hBM_;- zR^v#f^?DS3Skc;6+1P1RI~7;!|C+$g+pZ2Ev8&9*Ke0=jQ~18Yz4XmcJt=)Oy(1}L z5%$W8T;k^>q{k^du{M;NPQ@!xi4^KYXq at 5H`F8#M^=tV>+dVNVJp0Ltge3)B6hBl= z10k=usQMPkNXyvoh}71yXqTqL*2uN`bGtb>NBz58*>p&m2>(2;-gZ%onB`y^ zt#oTE^VY`duCAx($J&*=EBK z1*EMMQA!7q+e;tF5gJ%d0=F|hYqSVIoTuCBJk9nMO=$08NOIY#BawrNizuWi&4kJh zGtvRxVS8sA2jaR3k}cM_IwRR+`?NV97}Nj&X8?79z(i91l9k9J{KdPoeYqHbCD{ja zOf0IsN$R*a4W>HPIP92qw5D}(FQOfKo%A*Rt0rUNh56Gl%ZCmY}QTS693)_cU#Z;tD0Z#`ZZ{OBzTV^oBC0!L!5nWmka>TDLvcS~k zqP)G{_Op&8)kWL33L->h$-yRC0{M>ZYi#U4ZF{XT8UaKTQIQFekG3Hny|iPfWQ6R_ z@>zx?fMUX~+hFec;Ze2I(@zh#xW~!it2=l660Hpm=a$27s5!MQZAE{Sp>IijMhpR< z<)@q&My0S76%V6z7?+Ml=57Z~TyKXxA|@YoYb&SB7sXF3QB2lCkrDo+?bC$Zj=R>e za?Dpi#Xx~t$xcldw--Jog-wCb`LLp$&X8hZLWh;$MXrl?KRHYqXoJJnuB&z>TwN^i zvxaL6Z5dW=1%VFJ+VWHFs^$%OCVp;fwl)kvHWhQjj)wd$6@B4 zw-9)vrF|H^YSv#-M0v^Hp4nRtZ?ANU{1&h^Oa+>9isRihe at Rc{zLHwUIorv^cC*2% znOxYVzJya7Z9zDJO at L%GA2(N at hRAf`&C zGUQbIfTH*lVRr0WS`j zB|R?jsGIn2 at R`Ev>r8p3y;BY at 9uY%V^Gj9qQ8s<2g*R+nF?(c6d&s>isUkDwz^%aw zET7&QwuG&(Wwor}74XxJsoTEoopKiV)Yi0xGuwFqGWD9&NbQ(f2YSP{#?7=z8=c#& z at o|ev8N=6X*Lud{ux+}HK+onZWv=nkwub8PX#Qnj)!p{NlFMBu z*Sj_tTMff{J15?mZANkJ*92{i7LPczDsZ*{$yyRJK~ilM at 91CCR_taf?K;s)O3-eq zdd2H)M|B{#j%(NU)xw}0wY&@4Ug~fCPpz~yDgn1{skb!7pMQpp1i)>!W3j6(B{gk> z4;W=ptrP{aK5m=Z$`JE0Z&AfF$gD_-jyktpxyuROZETHSoL}wGwz*Q<_F!{zNyl9l z4I=n%skU_R#?CdLaubc>6fwE5Di`#p!HqXxbGlYB6k#fps;I48!q(j~UfN!{GZraa z+nNfu%9-Xt1Op`;TuNSc!}fPs-!@cLz3lKQG)Z>WTU2VXQ{T1Lux{AaAsn`J+}dRx z{)Zt=HY;$vO*8p!Ug at LHwi3m8SBWc4JQh0fHL-qMyWOANHItexzDIwbrp zhBD=<25#B1ZF}DSOJZT!t}$^d$CNvM^_IcXmZc?PsDhKXfv at H%^us@$td6tzXkRW= z5v>|}X=ZRHUWHTTRQ2)F7Oq$tv9|T`xVyq{{^&)#bNjP)xD{`rFx|{I>g(~^==!y* z;cC1#yoxrE`GI?Q8)YEg?%b+wRyR7=t84kHe%P11)4zR3LJZ>Ut#EU|PwF>s*bnOY z#_IJO*RNl at cKuquR$cWUzfFYag64KBFeH90kJtN*;vKK`u3o!(^~zQBi-|J3s`(|T z$i|@-2sh)6-pv~~uJf+0C(OKd?P|I at zQSdai6y~wZ%KM_?Enem`dCBbwQzNKjV1C` zjjWLY>^Jz%%~d1 at ti5qPT_0e`^=skUh;i|1yprr^due@$FeZ6P*a)^BGFH_18I2GGqXx8p-vdG>LN;O0nm`P)_7Kt{=JbRhiSl=5Jt4eRX7()PNZU zeXWp6%St!;07D4E_1D+L8o=ix;AXg*uhdLiBb6B3;YOFa2F8@;%J?$K6;WQMW;iOS zD_b>PZ>~iq8DG7Ut}I`^as_FLH{(oQO1D-8^erqXQx~Q*#>6XdF<*(7MJTk`jL~A% z+p({SdlYa1;F>GF%bH!e(#17oI$rU{&^-%^x<-z#LiKbtT^VB0<#<^%S#>>AG+#D@ zu&MO|dU@qF4aIKLDN_~ z5c+@*$0Cb~``Ks=0`ba-tK#K+d)cLE0ao3tmAA at pC3qLom^1mZ*e;Y*Dh$nVePm-S zO*0^c%l#{tFI~Q5#DWFs)@zT#4N)OmfvE~r%+c!d_>w`IZ>Ae=YQA2R%w$3YMxsle zULIV!%%q}LVK!NP(^wT-#~>7Zcm)gfzAP-#&CP9f#;ksiNF88#=rOS9+By(Q_1=PR^*ZNNQRMIJ4Y3!OyAg(Zp z(eXxOgA?U+d_ at HsB!@C2W60>DsAF=xVq>;P@v*$lWSiy{z zTFSjPvlaeyc_?(lrQyX(ikMf|F80@$QxYs)9^qwmX$4zuLIJ6(xmX=Rq`9IG*X4zA~yO{!LD1vp->6|B}rUJwFCUqI`WQgxDUzGkOT9 z^s=>fbTYHE1!6!cYb6mq11fAVuBI#5$48L5bdmQ3UC{xltlA)ozShJ}(aRNn^)%|@ z#j>OMK2o_s8g7Z;PEFyl?^EP9 zvJ&&s#rk4nMi^65A-7^)U8&(kb1`2C=fef3#arLIwCP*u>#}Gx23<%O()pJCs^WJoGQ0lw#j at A*;*Yl}3r at LEwpRdUL*FJ7!K zROiR%_+lm>i*t4}!?*n6A6gAb zzl!(x>0Cc>>&J5)zN53wxaC0-U at Oj*=UAHfCqLg!Ceav< z(Jqddo)*5=v4Q!|Zw`@?F at A86di?c&Z<@%&)8c|l8$oB)`Kea2s$zla525Hdu7HHU z^c|LLh6g|NEB&$l!%#CzekjLGCiDShIH?ah^GMK?(VPxM|Gu_I9oI;N3x8dfu%kSHOtToO0&sniLLcb~@Kj z2M#v)#YttdK!;y>`?mOPLKPR(evqaeDJ2_pKw%>j-W=oV8r0a7e!uz^gI%N5L3SEiV~LKVOB`gDY;LuU zqod+ilUe=DJIOf`L39A9A4?YgRc9P|OEYyFlpW#g*SKqQml%aVaiSZtjGvVjY>D`X z`uFN#h%I7a!f=aez#N at 26`5IW^5{w?6-MW0hQGWvesE?bx{VGN^d6B36Pn+ruj2nF z9yMgPW4jV9C>wX__g`5zH_TL!?eMwqmsfu!fa0d+^~2i!%}=;gd&yKH$vIw%O86^> z+=)u at FXcSA{3kmbQAbiHj>HPqgDrhEe)P4h))9(DXetRTl#?A|r~Y=bXz=K(M~@`3 ziX#mvR+SV{pyMBUw2ae09Z=O1wtyQ++BbsKh;sxhh at l9p103z;G$o?4QcGv4!8<{l z{43WU`(P~_S at p|S zs8pTcAX?n{w|}jP at f+`lkG}fq5zRO0u?13oyrZgxywSn3q^NZ;jd)MI{-7X?9bkIp zVM&WwmT+nebA*+8(KC9lcJ8 za0gYY;B7W+mw!ZbBdsID&UnfT_OXShGjGff0wl{NhVO z&EbHmNp=#dPGvK%{j9&1BI!}{wocrO6Z7hi at iX)0n0Bz#F z#GlL+IwIE2Ev1UE1r}!%2|s63*1am$S?j*^sQNN~5&vT65bJzbioOo!^&qxz553W* z*c={>)w>YJJiy6i_eV!nVIctgQt5g|o;t3LA0qdwAo#NKjB_hh#h{TzNV#BbX$5uZ zp{qLtEKKbwvvi!Lv8yUhL9$kjlGO<(GY_}v%srj!s`%(T%Ukiu!a4Pr}Jj&MA&mQr^sJ3DzcT`Pg^(En}N9q$PQh5)+_}MG3;bs#V{fZ65CinTh zQ)=5HG(c8sEUgxo@<*p``Z10LP!82J+p=-I`cj>miIXCgX~#JJ9S4;P0IME_3EE!R z0QeF?QFJC$pX<59U;J*gO~T|*ddL*N&u4z_h! zl9UE}#U*q~Gi0^B3SGl>U|npgOlmKd;V2cw__Q=$T*^yfk(&WrhT<>DEvBW}LP$eP zeP5~8=&+O*i*ak%;^Me6(D3&RsC+P^+riCZkEu69 zh_tm47b=%2nR#ky#inKE#c6=Wa;!Bl6fZ_ARkBi6WdfdQ-b4*p)s%Vxx7lpUFePJ2 zbx#*qxu_hF8Ze>&;R<}-`i^p3uD#RN6e2-5%z(|%ENnLCupt+z6n4QOaLNwy^QSEIY)qrLd3v;-vHJb+}Jx*}A1us-gt_ zs4K<5)tO{LT8g4DlTnN}k9CP?BR$xt9=5zCw`y zW0j4eQOPmAm^Sx{*}%-+XYtj3Gv!XvdscZ_`zO5#T zU9v_d3;VFR1|S%z)yv*(K-*?dr7`%H7N;0b0x~~=+63K=s|uD?+{$@w1mt70wy82c zV|O28c!f>l%^+jn6;y0gM(q;ACSr;O#=yn{;UeOc$u*i{VB4C4EhIa-BKc*4M+RBW z`IiF=yOXiTjGkkn>a(eQn`pmy7>L-Nup6-R=AkU+zxBa?oAHXj*B;oih+Uxsp#hz+ zxK^X%=Co?=z0^QGeH8v%dAz=^XJ zm|9NRMNWkdwyRo0A^89-<(dkASt)JO{UeAaCQV7j3YW$baVq^z9xtW~qw^Qer}Lw8 z=g)<6y|d at +70#}lIeX@;U6V~_Lr~XsMY1STh?Iu$IezDF&NXL;XVAWq%Rb3RSFHqg zL2^U?-1+mYlFs#6gLga|&q!L5`q4i4 z5B!oO!iC}a3m4Ak^Q+8@<>$^;XY-lWR)ee23v at +x;ijk()%kQToE^&1aCU^7>CEVK zD=}nji5CY&??J1Qj*-rXbK~>p^0|mnLvY~|mxwM)e`-*B#!^Y7kT5)-ug_)RIID5t zbas{7#JScQ;b&7f7?aM9WlD`1XVAp61g**F>Y~)22+$n{#xufYeELi{(<=N@{c3EM z>SvJHyx9U;afv+S5`DzeraV_gE~<*GvkCzSjK%-FMFu9svlX_7GyT&hxJ#@W0Ochp zQ(ai$3UKG52sAC7u^m`aolejL zN=H-JrU-<1mxZqaSDhZ8!UgADQh;5fs@`TY(ZGx9OmlkWl!R}^EnP~k!{rty+^x=a z3?K}xP7hC=K7}}+3ZmIUYI3B z;a>&Jr%$C*Dvim9*5 at rC1>I}_L=Q@`(eYF~884Okk}lNe2SB9~SDjt;bdW6=RGp5e zR!^Qfg*jHiR4zIsbpupciBFWnslce7MxB%@oL!r&3;uDi&i! zV-Hq5Q#-EdRCUtRw-s6ysj97O)!7bwhnA;LVZ_Ptsgvv>k8f~N>rL?VaR8?r<2>7WQFu#`Gn?dLwni~up{DH-^)WZt8p%!3 at 1WMMb&F| zrq-&CnN#=^P7M_ejbAx=;)FOZ2KhR+ at g+~Zbap_v&z^}YD{>^^NIYRF=QKsZ*B%nj zjnDFtsP+w1jwe@%;t9Qij}8}>1-TVUcw}f=S!!t-;femh?32~W(c4#+%~1Q4Rb<<& z3IUz3WW`BBtZUQBe4_GdP;5(Z(Np{%bI2~KPgW-;bZ85(HVJIOX+>JB0o`!2b0SkU zJQd3J;5%(qV%-FGBj{iFl%S{KWASa4SMezqG>n8b_K_$w18~ z(sA3(olq6V*p42}g__v4d2%8EO*oz?Xm4k;ib*^*khx&pFr63DVxt$6cJI@=v8-Di zSB(;jRin#pR9m?T7-xWWPE^NLKlvu5WQ(nB7Pxr2!#F5 at GO=p^#PQ?Dj+5*4xn+im zrS`=nd1a`a;^E{_$fOgEyw<+n)5JEnnLy_2TStMjE-2O5vC8ZvKW*Pi*t~3C1#DZH ztdj`Y2;B4W|2}s7n7%yla{KGEsjXD;)GNie&5CH3RL8 at yp@v)fXv?)&c;O7Eq0=ee zR*ytNB}Ij0J=W1e9b&65d1p>5ag^IuWG%C^Ef85?eEe8E_EH;g at mybO_=c+T-DZ!9 zUM6wekMXhNx=?$&@_=!C!xFVsZhPVyq^^#y93uiUg-!I89JUw`hfFDN7 at AM?!G(7` z9^H&+zl%?#P95x%zvz4(A6C|CY(&$d_^C9j%&*|6Vd0U^vJ(EEz%PS<@Lq=@Ut>XaEt9Fz%5~2GY?%93jP-d<}z!YU`9f89%_L_*D&lZ_k&?;b=6v zqg|Tb<~!{NA0T?AuX at Fh^rfAQ7QYCezy9Lk=bu0P94j7v*18^kyXu!}srW!E6*b!h zS6@=X6DMVI?dJrhJ>`nC&g@!sY*YiVNNXiQP;LkKvL at i^^ZH@>%peOKaU{p-zhpv= zv?`>vK-9~Tn4dq)4E9cW>Kv&_trwQVWF$%Gvv!D;qjiA$ z(F4|O$E#YC`-H)GnOFA{!8+`KMzGzwmN>}mX7ZPODnNkKNKXlBGs8SmykYcbPu0Imd at xx! zfu3xjbLGR&V(A(gXgDTR0wd{PB at H=$=?4^s&xZ=H+CWyni)}+k9sdP}ve}vg`4xo{ zQm9tNO0`OighxI1e5hJG-G7uUX%&45l|m!hiCpRzSL0n at Jw4U-ibjE&SvLg8JLPeD z*kH5{S6eO(KpCl)g5lBfmt=^55Dq$|3tp*J=foB at +*2+G>`|Xc?vZe$orpn)=(#=0 zC-n=|I_g_z&7!Pp9R;P_pTHvJm{&*~+bHQMHPKQ12s3T at 2KTf`B%E3#mgsvc&_uRZ z)D^H4@@hdB?$amBE)dDXL(&T_ijF#et at Ibv5p)Ww^(4K=0?Klo5i0Yo9z-!feJa&W z(N1}!FN^725wp3O|Noo#?@G^eqiBVV(O>^UwO$49cre`q}`imQp8PJ z?hWeh-oqG`$S}r%hkPd=xX(Ok1OP_*e1Gb zaNB594Nt?}6{EUlPw31QWKe(9njm4pn+B9d0*71zZCjb6g*E(kitcl?Ftcuit*dmk zQVsC)VkL4{pRHIPc^^)1M;Kc+4CvgYMwmQY@{SAastJV?r)0|FS%y{@Gd{JAXhKFj*S>mMFJA&4mOsz~Sp`u5i1UjgclQ9Ff zc&Ec?d at l{pJgoG_yk}3_l(Q<0`zkfH at wW%;pISk(c6JF?)@t~v1p?{a!JIaiWKHQ2 zs*EiS7p>)-2D?07iZw at c{h*$R9FVo1YcX0HfL&^^RQxA0|K}O03xQG(z#KOEID&k{}q3===_Jm>uHBw^4EHW_IPN?9|+P-0;NAI2aoz@ ztQsJiktV_^(!;Id*(I5!%_VwA#un^Jm($Y7ux)Z$Iu1uNG=qOV$s_OTN&PbXv-PDD z_+^_`uvE1sO;^2{o==;3GK(Ztd*aXRL{jfC>UFMd7rM&C_Mo3XBBgGmRw#5NrrKX- z6;HygFU56i)jUJ3=?q;+AmR((7W2YUH-HaPsNq_f-mzN)`z7X8$x z^VTiCbs4Wz@=HOS^f2^}%i*573MTLFc#Azh{LW5t@#XJDXl^++?qfi&6oK{%1ew7j5E| zsXG!Vcy%{TZA9j>S27(UPV~Rp#fI*t6-~HlBcIJiQ!cr6LCOLGMyp(9$s;yny=kby z&Bm}OfeuBZ9o5;)ziH9DX~bM{<4~67jZ>0*B&^w=w|C-m(0l*Dpf&Vg< zE-o&61Z9ilF2tZs1GB~4t67Rm>W!m?jUaBoCwH~+m?z0IU4|K+osDD7-FNS}^Ffhv z;G`~D0HWhou2 at 5;e3UU{N_eErQ=68F4w00b0h(z2xCkjG>Z9pGUBz2bAmR(JHZCk| zWSoJAP$qMQpmR>K=iP=MucA<74bWWJ$c0g?5vN25Npuq49E at 5viwayeEgvJu+kFnK zP^dkyK!wd~0AtlA#02FIb&y$So}H zZXz^q!kT1qTo}(YVXbWahteb-coivxvfS)5#XCe!(=nS~Nxadp5(QAp8((?6WJrlYweU*z z*hO~L*lMbnrda;&7X%T zalULrjaOAwiAWMB<(t--vM`2uB}rgEr5*vIOaVU;a;R}}V^@|Z2Cpv6JKkLt5K$v~ zK3Vw!xnz)3?&)ipZ{CW^Bp(}wuCjugHyR$zLYg0|mT5a-h~uDObW1U_SWMx}cwrva zi5IHKP~D~);x8sD67|AR5r`PtUziuN&N6eS@*+|)lPsE~lKJ*hR}vR4;+=fEGB_}# ztYMLceDlphJ>S{%%KsTFcz2x;tPM3ga>*Z$_KfvYEF&5}pN-ejXt7 at ow1` z8uO_T(kh6herElF!J6i$Hoex`fTh2Z$ivG%-nA(Wv^3usX9TK2(U~M1Ln(nuWv$)F zY>_cJ1Ip(AOPi`QKbo6ovzf$wl9iV0UhalkzxL>_oiFKI^0uB-59RMvv9EPyI5$6E zvam(W61cd-v{OE9a2{Z8p6_E3=m)12^_B$cl?2KQ1FjVtRtgOl=BMU=F=uDm*gXUj z4W>es7)yWQ_ihB3<554+A3t{V=&^8YboAKKaCCI!=#g|J9G22xzq|8Wy_6;1HNTVB z$HTEca>g- at 9qS_;86Q28kE|R%a)jUa(TYM*9b1L0i#B7ca8){19UUGystdy5q2F{+ z!~{WHkr`k}uu6)l|X>sNj$C;9k at CBm`Z zQLMw6c(gh)I(+1C#F-+3Ag?1JxWFVuNEg({QfpH_Y!X7TD5EsUm~>(&wA$%~Q)M{C zBSXAqekmIXakFv}C!dJN|N8{E3R^)XgR(CYj#P(-hsyX4!j;V(j}I7l9Q{MH=VG{- z6HIt2N^9nVa6DjnJ~q^>HAC~^c&G;|E_`Ut>Udw%9angcHD+r#QXdY7mTeG2Pa=_M zGU+JMU`WTtM_GS()Cd6B0kB^lIeh3aZ%r+I=PiSWaxR&@wMR#;#Y2+OC2zLaxl$93 zh2uUrIb))Q1`=rgc&KQEh~xk+EfzhJS2*5*F`Xj~6PrHWN~cjY&L!m4v6 at MLI!Y|` z!rnu?!y#ngxAayb?-#kxrZjU+Wz? z91f3IFCS{9%OrVI at yCAh>i99 at 5RdhRtX7DJ$43sQ!|BkggNF_tRB|c1Ts4cuam31F zmSCx5q%p0)l at E;$9wLAZf?f@*1TGvCY5s9gpXmv-_)d-pRu}UkpcXcI6e_bHgbxl6 zVM#_TZI0Iv$MYvaoblm9=}*GbS6U*JR49k?(%f_=qhp;)>MN;|3$6-R%LIIgoYgC;8FgkUztUiFBq2y{uYlv(z zAHc+jbhtV+JjmqXpo&;^9P(<==$Md8M_;QXi2yd%C>ajDR&==L;DG~BAd`1gBvfCA zyR}|frT8B-eByzO=4)n)Wlk7R>TtBLRos58GJ=XVE569V1K=dvUdc9I*d~z7=V005 zajFjGgM$OIs&&~c<3lc0DLR?-$+kc~Jb=iute_PS z^jJvcwIbK**7c-f>=k8|Tw?((K^P7mhzCf_4yn>%))m}Uf+$4{)eMKip>!}Gs7}P9 zj33MYKTMB@`?5r)GzUj`8xNR*FAI`!y*``|jSuqg*%wX+RxuM&s-QJim!cuzuBn2U zQ?QHa@`3Sw(!q*v8zoDl_L{0d5e>iS_~ip(f3lj{UK5Y at R6KZ#Y|t{4>nc1o%=3Y` z-}5e03{2w=M+e8mgvRPx&*FdRU?x0a|IY{Z??0f_Y70m{)+y2PGE;J~gB$w~gafbl zAK34sk3l!)wXMb(pv|v>`eJ~)T^;zD@%!7^k5llI at 2|>3%&!lxg1d;1JY_G1+gH$>z;cpx3fj8-LjV{A6cR)=Z` zGTeXQ!2Y~HDOZmZ^Ul#;$xww7wuyA{qAw;wt)@CR2P;CSn7rP%f8RcgkRkC{7q5KYbog}(7LY~a zH*K!Hd-v^CVbqrG-&OOG&f%IG;5DL;EzlgI98ko=aWK1;ptNj%h4d2kG;ww|rmKV{5^&tW#?Ay!w_0a(c zst)>>%=Vj%(z)Q63>=bZ-+2GNuy4eLaj(N8^dsmukiA1FqlTn?$qi_$kY?7`J5|k! zahi$}w6ET)pY)Mf_~D&*E3sH-zEFC(1#9}H?lWU4q0)d5{`n=HpT~6&pDQA2XqIR? zrlwDHF#N<184M3ri&(_mt!j{OS9ZXZ_ZzFYpnnxrT*o at KI>cM5mH+aNxWq5-GRZp? zsTUvZ#LSw&@A^^>w1RroWtzWPXEn73q#=-cs;Oa}7LX>A?yeS%tNftv4ffimV6p8q zHNJ|b-yn=NG~|=ip|*W$*;KlcS(_y0bX`mHIuxxcQqhRX+Ayk5Te3b;c9VbvP?Nvh zST*xXv^i3L8|#pG6?`>kV(IqSB0mI6%EN1GYe&N$>s-|K!)i|?iwK$(oex(qw{6^5 zV~6_pM8^z60wG_O{Ky;F?8rPRacZ;&$fW~fvPNBM-X&|w35|-lR5WQ_g~GXb#vwyyT(IxTq1~YVV4iGZjat#z}^4N^d+h$Z|Zai=8)f)6rU- zEvdGe?YUiUb_Vm>`aWfPA;$YJflQ}%nYKp*NNY|QY0_wI at 3N(UFsm7>8vt+!cW?@| z4hA`my at 8we`&@;Z*Ys3}HG#T+PoiCg8fl`PodISiWXa+HMQR?jpd;5rD3ku$cNw+9 z!OHllPDtWsk&Ov zvl?P5Yb)4f9k(JKw?>!Ru{uYsf_0_qpohl$EQ)_i zX}v4GZJHjA;g)r3O~vF+B=ryV$9D9Tfo at WGgIb(4#I)|2m7yH3_UKKf{)&MPS at 9Ce zd^!0|@-uKpZ at TGm@s?XEO{Mo-yj-zL!U9s;`=F{3rdou08 at +T*bhU?&vPYh~@>efP z=Qesb)L!qk>OJ8i4y<5}A;O~)ddP(*F)seBq_C6#6SXu{v9y+%>TRdiZRC%Qjywk9 zO0&wZV%RoUmE9#cK5^s$hGkDQRX>x0t#qgrs5-xTgRx4zHT}63^>?!eLO24`*x2g3 zYIi$nZWpdKn4>3W(#3Ws at Gz?IsAsP_TfS-8=$#CsJu}6P=G+^R$OqMU+mF<%cEwQy z7d^?9c~T+5DRr_`-%gsoSu3;&`AQE^*5?-e3l~(W3#q-5c!$c;@KdB!S2{gR#8V%o z>ZO}KLqYxSWU%$It$3u#yA9&mh0(4 at Ko3RWMw`cKL~P$22)%VRRA43fd~#lR8&ScuhW56Vew4#(V2{^FaCVt zqCN1W)TOs%u)RjjjD09ITh!JAYJ~7Y-^Mb^tE=}HR3p%~1*`^U29^g{IQvtrj8kc_ zS!04qsv5BD(ZUmkfsU)8*Ub(azs7(HTq2ZTqgL@>N}a#k at 2awcepTQ&sWwO_!ha(NVe!MLm&(!YeE^ z^KouCKQ}+uos)E0Ne`4Rkrq(D>_>SM9J&l!ou6ae{QO)!7dDv8s4Jv7Nf*ibwZEDl z&q?~3>-ks>H8BA(MT1R_<- at d22EHnG$N>K5h`-*Q8*e}$DHli?kTGFi>dU~?lv;~4 z^HX!94X$7Npvpx!xwSCA273|JWbOQ1XRbE4&~OljBs2^)#vGfPA82j!DYEojz~bHl zLs_>~CZr at GEAz+J4)d>lVsjGwHq339OLHa|2|g~K{s*mQZk}uB at L{gEVUDqL8`6fn z5wA8 at 3q6CmSc9%Jhgu>W)&lZyj;Txlo7OlKI*k(wb3px$7M|M>H;m_yP)96Wxq at OS zwc1mdN3faWGQ69YmGg6X?xif|y at C0)f+J|Em9UNrUB^z>b>{{Q+%N~A7+jP)p+UTB zbIW2L at lSRq>`sJcZbR4r4+IAm z>VmH~$E>{39OsFXi`R^=%pwi9(AOTE at CGWb7`jDbt!Y#punF at WS(4`J4J&x- at voEw zpk7orToy|ufI$<0Z16SnhujcEvZpQql?vO%mgvK?Pu>^m;#k_yHHMf_3uAeaK$?Bx zY1ar~IF1$NH4DaCv$#@t^vd(o;*`AtL}1*gMa9Ay7p8rzGTew7OK=#RH?(OkBt}?4 z1&|qOewo>Q3Frba*{-TLjAl0gHK1rdPzbGPR`|A!n>78yP1?nV(a0 z%M5ospo(3h$t|GpK=V5R^4!2|nR1l1O5QM>1 at AD4kK!ac+SG>4{M;G<40G=Qh)d!F z{+6=XD}GHo^=xfEW|c{^IHy(LnT%R9*O75$fV*#OgBSB1;beheP9sowgKoVc%?j7J zu*Us1&QzX(B0MzaqpMYut@?HiRW3nWP0n at V3~7k(VNMrd4+zYHfGShRh?Z4#OGz>+ z^AYE!H!K6y>;|k=c)RnjWN~Y?NLG at gYcm3Z+x(6(kqPHXPp<$1)0P$ru7s~8boS*liz4n*>l0 z%Qo$cn`<_N*=4TMWf-rFXPY#D$u}m?mKR3ZNz^bU&WdGTKD9G2Kd}ZLmB5xF$24D>C&Q39+<+r|oFy6B#&JKls+)&L9XJ%*14q!#vGCY&OSVNF3XEc}H zkDw^2?wcF7e=E8EN&Ye&qw1D}ok at wEax^rRm at N{{)=USAk=@<4gcVCuRjDb5q5#O? z8FFDXGYh-lTwvb`7;GhGZo|9yi$5D?o7p&%@!7#OW|Y#p#-}yEH_UcsEunG7qVQI+ z7Dg70xS{c&|2#9x>PBVdR;S$+t+rr6V(~J#o?X`986%Nd!3t#;Qyd!`8fNVgxhlK)m^1^a;tXERud%=v2#U>Vc3_EBa0 at f9WxS^6!%AtG`w62B z?t*z``vNF)sm1UW78O7Am8$R54(#%_k5%a zidK|CRW`EPTa}UdP#3{R|6cF+#lzn5zP;7nR2B_;f)W{}@GP@*|D!#-QHG>9qyxAwxDd!}(FkNeYas8Bz<_tyBPrDs7*n0xlDL0>BD8;> zUl#VRd~7gqVfZ*=%tzs)yr)6Bv1?qqiD)I&-Vtc!kE at SH%$@d(cO&f$4oYE at h-9jk zJgtyzs=f8c9Sk1t#t2lsiIn0>T=6a;1?@}wqFhw)gzi{ok9O~|ONNI`ES9{y-(0Qt z_4k^%5OEn1z>W$fsDn_`OlX|;)Vp1Q zD)ddQ;$xHudtZvcdp&+169mH|3ya;%=}J@)50iKqO5l^(QGZ+mLfGBI_+7iDqS at Hm zgvOyj^Ks7h_|foV%(Y1Gh9Hti%cRx5ym$OD36hzyvnTHkyBNS-9VUX25w4sya9nxg zY=}4)c4?LdIF&6G*X!S-K at Oob&u+B90C87=vv9C6>w}FA9}Q&H9(OwL9_-q^YZpsP zj3xd76J6N~dwcst+Px59tPe|jq2KHt?jmaPUJ5I6Sw^oTjOfot~uJqyXgAWy@ zrYzaBN+B|pG8ApexO=<{EAy`E!?NEvzZ1dFm_+~yq?jt4>JJAWh}x20OQlLAiWR90 zJcZLFbjFFn2Ooa$A!AMXm0sJ0PfhW(-qraqd=LsG$#XKPLaW|Ygr?fne7MRKmP at s0 z?rGb#lv7G7coCysfAHalA7~t56CsTYibn9gMWxyjBJG~St`U>Q50nqw3Sewh_LP;Q zxtw8H_rvr736{$CGZcW?Ck$^m-WN2xcU8C(Kj?|D&|5}m{rcndQC|t94W)K6W(@9O z!TTS4z<%mgt0Hc-GbZe2MxkBp3ILR3#QTu3wh1-R3a6&Cj10T_BH{-h#t-uc;eGCe zP%%_OTFTT0mh!iP6XRVB`^nH^`S;(4-Wp3ukaO{42+2!@(-sjHl#vLZUfUv=ZP$F826$5e<&oJ+*?On1N4W(FNlV-sfZA-g at rm3e;9%43Uc(8VgV3hm<%-vI2*_v1lcyVnwc0yE|Ty6xsR%O>3^cnb+fE z-#m8PmZ!wYAC5oxFhH=*`|&pe+ns$=`-jcB+MO^P=7^4iLV%Yz at cwUjz5g4m2_LU0 zQ1*&efIKs0dCmOk{h@~E-`Ej~8C?Xwfnp=WY}dj9EG ze0AlafsA^isLfmCo at O=u(WzQ?l9+TF9m*ydL0#fj5PA3&pI%0>ZMf&H_SEWXe2Sy$ zbV{F8q|$IKwD#x+?}~;k9SxJl=*N6{_rK_Un>25*wp7+TPkr`Qb!WEUCu}EcwLj)d zCG+!(N*_P9U&?l4`cT8;DNdFwqNJ$_c3xWB?oB=YyrYzLj#JMG#)L1khYEo`0`iuSYQjXbLu}WbwBe*{DFNjyW0jO0PFS+uYi*}Nb*obc9NHtDz%&=o zDu!E^ut_sTzbN3g-?^^46N+vZ%UMu9dk+UhI<-GDl0&o-3Scz~~(k4M{aui*$-A9l|n? z4XD%42^gSOdum!YY4sJ(4oH@>XWYjEpzIdp)?`knU5#jLq)R3Zrb_p%TH&?3raesp zvR*|RT2rvI=a^Skl(twtdD30Nq9GsZ-{!QR*pnw+%8ZO1sexmp4KmnY^fN&N8WgmBFVl3Xs5P>vHFJd&lD(TL^sWGiR-#CVi?zUV1RuUEy{+ zu#6U?tg|EmSF0yA5%fnJteKxWFWGIoUSmqRDE<7}wr`~aRXX!CwEzOxvm?%2?jV*P zDNI~cYg=%spB^3R?8aKTnA<{R{l6p=db`O8vD8YAA|qG(e&f8lZ22wy!($|ooM=-g z8&B++4u5c8x!vMScn&J#fK|-?~}cdvCs#)hnf$l((zY z^B?M3M!TtFj72__&e*5r9%yQbe5U81&}OeZRN!|8hvT%JQvQ4F#TSH8?^EY}vl4+- zX_7 at yGJvHX@i=yriv*}vYAo|gcS!5-rp9OfK!u);Nw}=BMSe|arZv2&K;PNxPu>X2 z_SW!ljAI6^BsEfXNj)XEI1x_Q(4hvLn)^6ykdT}-BlUXg^V&`4Z%pRa*3uZ%3k+Qo zHLp6~sf$;|yyYn+H^rao;daks@>BT-?xly9c0q6SHhHwEy~EWW1HyLSU*BRrO$GG; zjGl{_+%m5Fb#-HAI9giTrt>S9rMf5E^fc7K_-wRO9NOC8)N8MHHVG)}6RW1L%i^mi z0uL)GfO-(40=L9s;r##Otp>H2xdjY#-sK{#ryA4%vM{K)V01(U-1^(f4n2RQVM6;D7MQL9IFUn>prR8#1wrmmoB zr$&23-IJb~nn$v9r?DuZnN^}Z(6v37`Xqwoz*S4 zrM)nlkvKEy%2}RoXt^DaHr!{;jtF?s`%BxSbc(OHNz?-GWk<0?ZYm9AH6-Yz&D4|xv}l=s%Luyl-=g;PtsJF#vRnk8yl=GJH6S< z^r%;ac$AK#wahsb&i?l~z6Z-7JrAhw8rnrPv9%o}nWH~)m+S$A;b9U?JBTMjI5jxU zb62FXRi|hVQrRPQda%nMggJyU+E;A at QFZrk121tFGN}6UPB6l60OtWIY2SMBHqnIN z&`tNLXTuCSjP#4?neqCW#0S5 at yUIV*708COX at gW*#)J)1vr#{$cW1)-)tU7(GwavS zAhxXCkjoG5`OOdRnpuD9nV|;e^{ci%f|dP9ANi5KRa;)$ulf2#e|Bc=%+bcJ0L~X>H&ye2)i at c ztX|Vvy)wg4ERhYSfJq=S at Vc|85NH?vo at u}Yf7f%t%uIn=kgc5s)gnT4`1kS5`mlZ= z2l;z#QD)wnNp4jNMz#s7^`@8QBDtD|v}U$v%Q^y;ri|-d7j2}AZg7zVrKdGdfkg)~ zu)_Mt%sm5Hd_Z55 at 5Ro}OyAI7->mN_?$X5G@%6ymoosM4tskvZD8uZEma`>0vr_72 zre;=|!d`Kvv%X*~+_LnlX14Fv*KSmS1ShKXgLR6G-3A~vxH!tfNplKWxnWmy7|gOb z(0W`eATA4OfiN?$@oA>JzQ?@l7z~zjsFocHHnP=Bbhy(@6p&M8+O*-H;5y`2 at amZr z2Zc{tJgpzDW5P1sgc23QhhRR_u?q}>^{IqhCUEs=ofN9%pgY-YjcR5zJHy~m*3!jT zw2uF4v~E31ncgp?O`ufl1GY=x>Iz5&wM00rU$({8ttT>(S86Pq$ z6Uk8SDUb)iv*Kv-*@zS}=-u^U-N0;6VX$f8YGJp?Ie3&LOn7(#> zNB^!Zq=#$ru4}+${VPRD2pe|II?)_<6R~$Z28LB)wWNjWbtI&sWSdJVy=IbEO3eiC z45-e!?wdVk?TjsEWP?%#n<=J)i?cI^bjR~LuYYYh!6dx$y77BpXc{y^zXqq75mu21 z9+~X6r*#QGJ3gP)MvwDqJDL7@<-K+IFAR{qXFH0O%2dgjN-G=wTDiNf_ujhq1efxc zb*Fqi!ej>27KEUX*+t35zen)cbi`51H`ZC-_efhXk}4-_#Pw^}h4&c8<~k`3JrM$v zEYlMshFCy!2k(jQm79^-K;@c>&QN~|a{U>~kY-(cZ at lik@E+C8Ehpx>8aHjLa_k6v zz5W&BRq8Nj{kmz)k)w+KWV^P|X67C-O#DpiEg+dyYES5zgtjyiNg$e{72kWM6V*H at SX`nxt^cm0ZiDk at ppEnk|Nm0;re9VaNtSQ>bk2=A0#H^}W(oljLs5d4 zln|1b5wm6~HGxJzLbFm*{gZF4_vO9rE~S8oJFfTJW`1svD=vXD@#Aj8HuH0R+pz=I zHJPm{J?`!3A(bs?Ti=4}UZ+TWAouGsr9$+_k$d_k>LYvdo_QbN{!ICgPsN$Rfd7`} zOtL3FlJ}@?Lu{@Nu#52r9bYuiiM^xcgMAbYc0od#fb99$gQLfe9{W9m#b?YQ$qX{YCq|tABkDx6HphNCdhFQIqrdx| zDh$LbR^lN3W(!+5>(NxfHE(>p#U96whNDaXio}qHWCP33b!4?Z;U} zKk=w&URg1lqp~PZQAm}$2~P|q-4lV!CdZI6qw(3^Z=} zb+uV}Vu-h;Oz&88bZ`U}R109{Ey$Lb1^)>~CLJz0Hbm*ikHurf;UDI1(J3733nh3yAH9!+M~@uV58PUDFpB#! z92)%b_t8GqQRP{`zIgiNEVB{C{3!;!z^$isY4P2uCJIkEA18 zCc&Y at hcJq9l$z8QkLGj*(&Q*3C>z81JZHz+hJZow8kde2A1vkqx4Nki+S4 zJd{k#I-}!*yelL3lqAfRvfAXx;lk9xy{XW$oHvy^JU);O6fzIlo2$a%#8r)hdTKb! z?Mg+>v28#DV5IFx+BPgRP#O>v6X>d0bvnO;+zeEz9@|*xkTOU9wdHL9lb at I<6c*7(X^-mikD|xCB-k8EhbEiS z1x;+Daga at Y8!#eiuCtS4jq!1FbZB#v^!5*7QE`;GXe1XaSkn=q$W}u5&A4eP+V|M< zL}O{8A}C_M=5Tu`Z?@zi+rS?0QkRL5vEL9E$O-MCW|JZX-H>!oBg7sw7!)7F7!BDY5PB|p#MqLm##~Yhi&(sS~Jn*EbV5+iak5}?Cv48kb zK2$ct#zYp04&U-z~CE!*vRY?c7Ha+3d-Nw zQ966iGPbv2BI~XrvgI|^P;m#OBL!xv_T7jZlXcq~kh?q#9&P~T%eWJ!KwDut+9a&w zX4%+U-&mI=Wk}t($mD(QiIj$KsJ=eica9n&R|Oc5n^aNo5v%hQ$Awo83| zBCSTYRUWHa#Guke|U{F%$tv2e#G|LCRs-?mLU#EaOxP^Qf? zp=-tE>`hdwGPUi3oU3li-pq>XUj3+=cBsW{rD?k&H^0c4#%hW~ZBgW%Z^EC%dODd~ ziI#_Yo56eGw1Hc$@Dza8?6S(AB%v71uCLHT-Q2wTKT_ISOaBrhwS-VzXIHPLX#lB6 zxdCdUf5liT-W~lBQZxOJ2mcO#B|7Kd at k^rdPbLT?hI&6)VIzW27z)%9?TTppr%6 at W z?~RAN5?1ASss?sWSZ~>~In`UPgYvWy-PRN#swlN0UR|v4+96OyEJJ1PaI#lgBAFKr{EvTE_-?z8)w0ZcOY!7`msk5`wdImO zQbD1s)it=0)a0*wa6=YFG(&o(61bLaKg7bLyE;u3Je*bVEyyNdEIVsLtDM=KHRsZ;hKQPuw z(}u=f8m_)&+j_b|VcL~b*YeT)CspFAK_Fc|m4s at GPXy9sq?N2ZryLyrm7-Tg9W{*p zz`*@K=n)O~dL$>vcVpI3$2%_Z%Kq*V5lRiKTuEdu{k at cVm9iSMqdhpmvmq#2qaJO$ z6kVSh02UUJ5idt88dXVRqJ}jp;d+)yVpf%zYF1QBI;-{Xo_=|#^zq-Pgr;PtH=Wr? zP0iw;A1D~*1l7wXJr?nY-*+TyoNff(q|5`uZR^3D{@kw at XAGQv?|lk)HjvtMFcNmE zRagJ6x|G#et1xp!c_{Ijr+wY3O4=9vDV+&@AE;F3@g7pmiR za}`8lXt$r||4Ig^dru+*weNW%Qx8A|63&cWzWFG-v~AC*De!;KmA=`r*c{e6Hle?2>_j8$y52?2H1!6Dj--P zdPH=4ge~boE}gRSqm!xM{ALQb4n0Z(8GGunsp?f70_m6uRLcJTw3__dq1c`|i^>E7 zjvpguKO)oiqB~F5v-Fx=zj=Lcy_!D-+4OL1n zJW_tPTW9=_?CJ3janE at 7p52WG7;7kEs1k!l97JO#7WX8kX?8D&Q{JHqj42ScFnWYV z!=7!X;%Cq9uzRHcD!VH)C04WJ5SJc-wRmKXwTCvg?1r}x6D6_<3PN;j*z>^juwTVQ z%)2Kx;jY~b6~&<%0H&RVq}XQpI`n~-_dj7z-aTe`kKxQ($ zntt+K-aXibe!Fmu2xf7As)qR4BR$-a9+}0#*tgw1kqzRmiL7k%Lec+BD#F!C5}{_m ztb9OK{gt_5OS79mvGQL%H{gt7V!` z3A+;(*@an*ymo{_rtH**&v+BuR^G9DX)08d39T zwr0;yHj92~HZU)T+4!`pFN~mC@#xib5UvcfX>Ba%kf34Lc=yj9S#ZqtJQvuY>fZ~2_lmr>T16c##b zF6_oA=KW?@Pkv-M1&yKF{9RCI#;~`m-K89`s0xXR#W9Y zTW&fN8^g(dv%4lMN?OuTjBcm&Kq_OS9Y1^JA7<)(4xjC)2-b#SlP9*YZq>@@W)mrL$#LW z_KGx_qWav#HeV)$7NI=X8NW=0H6WYqvP4-~p&Fx7-RAV{zO0ZUrraXJ>lPBNNG=fQ zaoCzu%!A7wB8akr1rc93BEs}*o?-nOB6+tN#Gz*%3 at d^020kQsWRH7#o)j8(91XG< z7FPPZe5Bm+kAYh8opy~h_uQ@@CFu%AsHiSV&k8Dql_z`K9(K*{%1j+sM(&p^89c|v z-DywHl8~%yZjM!sZONgTdwbYMhykCuol30~3XI3d5LN~fWd)p>db+@@!S81G{Tdbe zYu?7k6|sYFi)*$3s15rPdj<{r7AM*$ELK31 at E=x&29L19 at -qrXHWo?W$mRM` zyiP&1(yYwa{b|aa*T{UBv-?dU!M$0%vHzDZ9A3|FCSi03l$tUf4;FIh3EE8MUEoe+eai6eQq_Xnt z%4C@!?dF at _0NKiDZXsu&Fq>7w%`mNnk9nXcJFs*eUGqvc17_W|(yok`g`;|RhjBel zfQF1KAy(pw=QOkZoZpG)pBHz{tb}DS!p33#G#EXpDh7ay z7K3j*plDQ`*bj7Ke`qsr=8Ykz;~VR7eSC2JU^$r9BAiV)vh8HE^O<(YuS4~03=C)X zX?<{TeZ5{WS!ESx9Xjf0dS0J4XEu84`CzjK#|3BQhx*2Vh&n2p4M+JCtv}L);b2^w z99)ZQ6M&ge*XME=+{K6kn7!`s*+S3<)kuJ~(F5yf#=Be{rN^F~yy^0F(8NfFWeJ2Ce?EM zA(vGx>|8PA3Yn(0q4{7jq2j8MHl1rGhv4C&FH$yJG#E0kW^UJ-)jo5Iq{#*p8Ac;; z_CY?Ss+m_O2cVct;A8`#qbmsrc{DXkkg5^ihtsRq& zyIQ&N1~K#I;1CRsEMYiF>xKDbYbksdkH)l6IK3vYSR#xMI!Rmnnm`Z%DIL#VS0T#@W zX?QO90DTUOR#{u60fbUKG`kr^U~Lo at AFZ)?uIRacV3k={SNAhI9u@&_s1LAsY2%(T zS%3(XVpT&?XNvKG)dTUsWIujOMt><%1Ix^;Zz--!zh*71YG65#5xQ?vAA+cS7-->O zz-AgbWaR3BaA3Iq!2ZLBdL3yi3YG-m5bNyGtd;{|ztM=$@^lsb)qG&2 zxxx-$p)rjH#$__vG&qLljkwlh-fgZyfWwChPAk}~Z<$9XP=J=TTt!U>;(jsAX5tD* zpir$Ar!Y5*ry3d~Sv()e`^Dgf%?HYg!kcvLt+zy5UbQg|V?1|uiICEQjgh=jEZh=f z!)m~Cy#r;xA}4MPZE zS1%t7)6BdY-6r`!yPsF|PPY98b5(BNuIvb!O0riiC^WSL{ys`*q@}^ zzQi(~$(4mHQ{M)jdaj%hyShH6?Fi@(I zR$<@v{`yAV^qAW?;r7A0<UOG1Z zhU~qM?cF=getrw)IbAR*I}i!`TD>^hwpPpiGM#U1Zo6%VsyuCPHpV!5Zz%8L+-U0U zKxQjy2}gUmbJ!Q_+iW9=y6FPZr(~;UoKlC-%&UGGtk+gYX6v$Dv&M~uZh4BQn!+o! zo5upus3bQx6rTp1m}b zSNwJvq!6s06SWB{RX2Ji_P)m?frir#nB(m!kEY}5=J%?mwFXJ5wMgv~J%Yj#FMT(icrC!A5)jx_4>k7t->X1Y&mOpM|6?m?lZac|ZF?EIJnUNB=nGy>scDD*) z)!8O$l&DYA(%vA at Db0elYF{Na$m#rUwUvPjsIWs`wcQ(tW>ZhM#3f#2tG-ui?&CK0 z;yo(nDr;I>s(CLS6 at WX%L3g08#?_!mv7+kLMgNIws|MU at f23l?QeCY&w28)flfMWj z=M8u=Wv*5YPh+UAFjQ5N{YuM;Z7EX#7HVy~+H$IMYr$9FEBI6`4Syr^WUlF+jZh~m zud!5(w<^k1lsh^Clw6p=;0Mml{gybAn&DSl)sw2Dl`B=Sn2u)5X?l7)I4e{Szj15U zwoqEctnXFO7D?jg#EQa(C6MS%ioAHWVYn*XsHDS&+g*hElLqOx1om%LaO=&X5~X@> z^uQ%5Z$k;^eys@>Odh at 2RB`Q(05lZ#C5rW^fCGY}=EqQqD*xxH1azfgAjRscL-tb3 zVqe?GM~`5cg2a_8!+^Exl?uyqKK||=V6(>ol&>>JtQEj|qQ%QQ{d_ at Xk#$FCFn5PDuP>H= zR8QF;x7ScoQ>~|Q^z;Cz=kbZ|2?jj`prY|aad6Oy$>BphUSk`8+IST+vDc%j+<00z z>LUd;WhQSTrV4Otv8ME-M`~2 at o=t+R%6LLs*7E$Rhku0ed-c!(JLqtNFJhqW{zI8O zazR(F?3JamoC<#ZTlL^D-`n)0PWa8e-=dmtbjp(Ti6<{Ab?M{bdIU1w{M~P5xRx-I z1*}fjlZ-u~a(H}1WygU+Y%oo8%DNYJPiUz2RA*(tBIE9;Gk{Iz3<`aItj=A(>Jv5L ziT at 4sK$8_M13jH9brr5vCW^N`&T3CKiqLD(c)~*s##ezW1;ARy&0fPsi}>RR$Bs6J zKZ(xQ;Y|{k_9UJe>-G&5P~Wc+*O z*lyF95Ia{WuQddFml(PL3p_gzHiw*6>HCNt{o-L5nl>V_D$FC!u~}6-?*YMMGaPHE zoZF5m^o19>wS&=_5#&k?g|N&Pxu$bYgN}Sh^oMyexwxy~=cx~?7pFwqPJ1O1fU0L8 z>vbn`ErVq%uNhZ_l8`O at kjKu_^rbGW`wpXrE8KTHDdFdX^0}3v>l6fGIFn*}pIWw= zZVm~MC^k5|&@q;uHBpUicSlOa>G7R=06#?5IT>e3-O*Iy3&qaV at _NsZp@TX5LTg`8 zcgI~~Z3;9?>j(Gq%YR>5UfyjKfE}#&oLYfSbC$yymdxoo=kb1UK3Q4L%af($xC|p< z6pGP-?XLTNk#RD!#&;~wEr+F_mX{zH^wBR{09IFL{msgj9rNoKQ|j0KrDX{ui8!n` zX3e-sNz;?e at I%W(iL#uQ`wZ15EKQa>E~O(}Dl;M01t}0WEI+W}CP~;mCt^rHe8R4t zE4adRVQHlG>*BM!jN_VFiOa!N3KDe2XWUvsOzE}DsIIVU$=Lo!93 zu)Gb;!IVqO?Q&ictGI~mArp~+PZ7Q2w(l)(%l2;1fnfrsQjD#$U1jASL-~>MSC*UQ z|7GUn3cJh=k;RZ{=3OiEb?T8WDsHbqH~$DsV6(82+`h>bGehKMyJXfc$xBv@&@i at R z`~2^o+s}rj`{%NpAv_Fle^s(|Xmn6SLP|y|5szDDK+*g1&9%`41 z56(;C^(#R*+aW?hG4HZAnoX>fbAv10G$teAVumV9lSQaqXO|&WL0VqH`L0gSXA+{9 z=9VaJK&-4-nYS^;0TPzN^1 at Oj!6M5o%6 at UB*Wrh&qo%-mi%WQXNA4LPX%LzLoD9-z z^I$`wIDO1|(abFz8|2J0b5(^p^>A^?=B|9<{K|F*4$~t0?7gM9I9WmjLx$|EaAPPx ze3AzNj%k|$wCUYb=;$b`x~M96ZBA*JP1J(V!XKHwPS4t6UB at DB`r`r z*IOByvSs<6BF3iXm$`Um>F>!~iRG|DuJzU3vGMvAqIN?LITLV39+3p}84 z*hwqZAAE<9ODnU>)kMtIfSxe}GE2K2PClcHp(hEwCcG4xbAt+SVz<#*`5y6FDcBSQ6&FN|__Rz}Xz?Low zvmY4QHT;=%$z)u*P%n!&>o5XYBg>4BNnkP#REAO1#1`*ilx-+YpQ zQiE@|W4Gr9$>uprL%>AIgT^S9k_Al)r9+9ZIJY>o<7LLG8Gh; zig_YIz?s$LWB??}3uf&0;bcBpGXV^vvPEY^CV*_yyI>m1MrFMFK zq3Eg79lGoETy#)h2j2X^B|NG>lf91yUJE$<1 at FPqtC=7bL}ZF!nLGwxki|MchKwKG z%Ul2K<$&>*=@;8Sht3q68CJuAB>G6_>E*HEbaE!P@!gBn!Y^8+N&82l3z|v_`9Xc- zuldtk+eSY(Ua)Z%D?vjzd!&KIgb2I>mz{W``F^82>OK>i-bkqwe?Nye%ZY}>} z%;dZ~3Kq#I!|X;3P7Y<$A?zJU2VJT+^68~&yqrt#f#fYwya#I))x-Xwkt7gTq7k7G z;m?e>Dd^I}t at e5@ldZComDr5thElE7JUaR%rpmrXeM=edRRU{b{VGS<7$`AR*xP$l zWBb3<>y-3bCmK28v#`H^K%|s7!sHBBC2eE^q0-E%7s7=DeX%=yqSW{*1FSm`fulf#;yr+ at L90H+8cA2m4CP?DDl_$)uSm(4LVEFI)JF3GZ- zlTwA%yV%sEdJUO1z~q&vjD|=vn01~R2?No?H2t#W4QH8gE!OG*!R|Gv=4JB&9sef( zj7{^yP)a6YawkU;iM^Mu$ZIrKj7(>1Dl0 at 7ztp|M)nC!)Xsz$7Q};dvaqqFV%QcO; ztG?Ec~>FL+BBe` zviq-x&)tG9S^9bOzM<)#2hLpv&^I->SuoWYvKKr?=579kR&o2b81JOB$DC(HfvR|% zWz?zfbBq6+(c0kQsW#$CUQDRK9O6hrwOpz0lXU0WC#x#eW;ayATP-gM)mt5Dm8FH1 zjPE-zAWZi at +7MOkFIwI=r+#t#^L?UB!1Jl at V{ zY8mi_W?T;?`^z5jm$8<;E3?onEjcIr=$8IfAGE3HnE846m;OJa41LgObBOh8Wca=j zemQ+FCmz*{5R0`S^gfYvm_zKE0vdqYkn>tpSC_)_&-5fzDs}USr=2J}Sc0tKT+u}0 z@|T at jUsyH0^k`}-pdZ at i{B3?+qgQKqN4LF7VO#dO?;4ufa&=j(Vq2?Fn$Z!dFy0Jr zEy4Nw8})|6{PWg?uf6nWS!QUo^hVSeytHf7?EHVMk?X(6er`gVtZp&AJ3H>Z at 2Q;^ zzWQckm+3`=($RV)!lw&3_-*f_smpSi_;3EDM=Qwj&${ii+p|1a{Y93^UsR{IwzZd~ zdIox**FfafRG at t9S%9~`nrASX8VX7R-FsN<6o6}4Fzqp`k4Qi|8kmS$Jd^$?RPsoKke>xF42Z5!4SP5q*^9#)r2gzhrP z=ux81%%MzgFC~n$d97{f%zBwXJKUxu+#0lCZWMdl9XA9GVQ?Srt&~4aEIlD*edfEpmEW)5OZr+M3q>h1lBe zFSLQR=61(rOOHwOWWyDse_L5e1P;VJIqn44VqQ23b{Hxua^{f)L=@qVC zLB|2V>dYt(EL8WzHLJD at 6eYoLICJb*7#pGLRjUD0g^kJ}RW}Rum|;$WpU@{ep|?Sa z!YHeOQHie=tTD9XOt#=m{NP=Uk5sljt>CJ~b76JBUF`wUELQR at p3X=BRfJ9pV;jn6 zBju~x+})z37!I|t at rr3XclTBedooHEaJs(8NQvAklEr;M7PFxoSE2>XEFTc0aQ_*P+f$9k--P z>v+&>Un%`t9eC|$sC9f^W8d+GP90^@UC1w^%dd^TSWUZrWk{NgOA3aws at Gt)NH(mi zRCiWs%KBhC)%NdZQmJm2dnQ3YRMxDN({(E zFC2ERh$<{E+G;4G^RHJ-+8DN70+dn#-L&pc1;)B5=3Y*usRyMPH>)8I1dpn5t)?{X zR4Io2KdLT{>-Je$yLwPL2-Qr=>StAXHm0zzuH at C0#0Os-Opmfp+A8R{K(a7QNBIe2bKt6O|K z(YF}CgRQ)ts9kHiB|c4&;r0wH&^OWbbv)_za377xRHJUsPbj!W*xldsrxE;-i>kR5 z=|}U28~P{RxITsh;o_uEL$K&s4y7lRR;s|5s&6SDxgSm{Shu2b)~G2>Rg*STeXo65CY|yV8hE1x$Mwp zBdr}D0=Fj~9RyenY*1yhe^1nHD}@Eo*icnt*9RrK!tlh`y3GA>+*?T94 at IPkJ|C_( zuBP0pFfU{euYb){W?Gf5Qn1^i at 3zoAK&9#h9Iv|9WnGnVgHrDN@|eC3sPEa%gA&rp zQ%JBg6t{IU#kTbYSl=V!-aePAP`+8h~xKt)i9Ou;I2!h4Sca!dx_Bt-@(E`Pn z)ou&T3NldBV~yx%C}HnU+*{caM5B}_wyD*cU!A5DzV+zapwrDVa9g*eLRzvfgiSr4 z;{I2)<9b*@)wkf#x4J2w5*bw%5yI;kPGn*+ at B?Y4f>H_UwZ at SoX)EZof#LS8LpL4b z_-nbo>W*t2v!`|;L*F_i6Zdo&TrTOJfrwqAp|TZkTIsBh*Xe+NSBjfkmb1xDh@>%KJJe(ie#Cro2U?X at PwqQoO*|O22igCdc_Hz??+! z=eq)h+%=|ZvAhOy*eO&*{}MC%sGbHZhH}pj^=X5Qjp{e_xku}5o9g=Y5Ru~3gSa~d zU1Uf;*eBA~NNHW1Jp>5F9b|B&%Yau2ZsZ}VXMt8Oy(ET!p!`5ZeF~k zEbc6v5t7m+aWS4p7jYp$US~aXQj2hQNwrFnJTslC19oiZ;f5*&)3KwCBV-Okqu}iP zXl{NEH&%w(QB)SU%z9c>0<ThuY7s>$uc%hm9KQ`U`{M_8UrgRxZ%$meY7ItQ|Ih4X=!#NEK zOY=saq0o{mV`GX8J3p7^#Mz at -JB?q%PS(PC8lk=U2Q01&m at _WTIeG=)g at vuDHjUJn zlTpJQ)T9)jar-NN3<%@7dvcV_tH0x%#X*<&dXic5+T_)E z0XckhBVZVQiq_!26y1$^LkcSy_E#OK(T(+NlE}Kw^WV_QSaY3F_P$%55Ef+AfOCrGrns9TSa62GS z1V906dBe;c-kY5p&(5J`7x=hUPMGVk!2GjS-F9v=OB7mG!l_CU0LHAZurjBHx%*~) zH)xu>H|zPtk_ws5qD$L}>@)@9{9K+J&dwo-8NMcHIoGTz6=gBOv4bU^ofST3Cv$+o z==Hj>nUfdhTVXQI58QCXycXe_H1~f at 7-^{;MxUr{0HHAI{74G*TpIu}3x?t>bqZ2= z-kme=nJmmF&DtPDm`imRU^X*&wC&X;pGJvEAuU>2eKyTn?lTb<$$4pRAXsTi5HU9! zX0>B>n{g*30q8<1;UTG81VO+Og2dUax!GA)jJ-r+7x%e^YVlB&5ST`b&CG2h%j_JI zr-h-}#&T6qdKb9L&Jwe81G!07tS}*xWHlhA$Oz1cBN-5?L$xQF2yBL)F&I3 at u7Wd2 zsE9n2-!WQP^i9_AxjaAP4#okDl<6PHMdl_D33S|4 at +x+kYi0>ng+xorXx%|+eu;mJ zCNtWVsh2JMmrSi$UnVka3&lXIdr)dwg0qw*>>;icl(`Gjj${YTF6-Hav$zn|ETE at e zl=#(|mebP1dT_TSe=DMyy_m^lTZ49-owszcsQfq0Xsu>*~LVJDk z#_Rd at hB4#UUVSCJGI{lt#M0ZZy!`S at FYzb8)V|of&|Dw0Vz6qr(}Wq5wIyfQ|W+>MH86JMCT_(FJLfRsp^uSdHgzBa%N zuf6tadbNF}dAWJ1VY=q}=o+d78zE6y at YQTnmX|ZunY{F3d~x!^i{Zue!tC`vrg`o4 z*I$cf((r2Y%IM`+wE7qU3p2JDQ8Qi(*M_gZ#$X>=7#Uuk$mGpSqZeO#>BVF=PYfSm z%WK!K#cQoini<-fW5zE at i-0=uI)7iET)Q5wr)!hfUrPvj- at GYH$yD3eKz4mOzAWpH z at RNz0uZL?BO+LA5S8ZVdF2KCSuHlN8UrKUXFsHG~3)flhTHw$4>NVuAR*P8LRLBU| z!*S{Q7_oC5HI!}f-q at ygm&ho=j53lVqFqp~#n(o1L$NMVUFgFhXqUEGhE! z#-(nnLt?eEz=dyx!*sp97OsZZ9$r1Y5 at qS=fQ&YGX6`MAF4qNjQL#%E8xM<}0DEo|27rOOj)F`uu>qEv0z$kqUy|M2Vg;_;<0VgvIoxN802jX7<%&$y5 zRxJs*&8y(~h2rLp*P5%tD_5^vL4fp151f)O6t0agoP_PGtedZ#fJ!_izb(=vWiDilv2tTGATJLFf3b)aUh71BNJ8~ zUA}Uem5R)xL}8gI)a*aGjy at 029`8F`wA|$Cm2_o%`3iVZP)8Q)OBc&8jYp~nc1E9k zW$W at 4)=-2b78shr0`j at RIyWeD1jdhKVq9@~po?C4x$Yv0l%HhbX~<52-&`$HSFxU!O_u|XZP|t#RjKB^d!Sw) z?0oe~!!RQ972DhN3uod at tQ}w2?##^|V>!a?T&_gvbNSMxmz#=P4JL>)NzW^Y)cJD0q?Fx~kK6}JND9FAO1?bC_TkdxB7x@?={8=|&LQ=q z%_mIgbNNaTfG$lgUrLucoEL5>>gMH}W3d(FFB=&zg^Ln52 at Q#K?tu`l2Jj$Rgf{bR zx-__W3G2P6dkSG7RA5(dUNh`8SIXs9V%vR};;d$Vf;j at VqTTswylQ|C#982ia%ubG zrHdB at V`cpo?1B>|icZ?xNh=u`=>Zb4ob6bp4h(o&6T0!r#Rp1ridg}}aclQN2`9%Z z;c5evgo%O`0)Ot;M4~v?{#KFHBaH|Q@$wL_T)q at 9jgdE9oEo>g^JvXv2`sn-@#5st z#dz_djmN~z*IJe)ify)Zsku0LzG9xJRMsmGhwrw6Qiyj{M9d2`(#6s97oR7-dW>$a z^{!er-4QaIkU^s^K0o-6AO7QqAHEOYPk#75exJX~e;a at Mw{P>e;hXYx`fB{;S6}8Y zn=iuW+n;^@`DZ`;$AA0~erUgs-);ZxyTASITRwr2;oJCi_-6F=S6_cszKUNCzWDOX zFTVJ^d_MY&OY#qeY2)9*w-b#XfAj4(@tg72-+UdvZoUd%jx;!ZQ9h5KP5$GD^h5r> zFnFY?q{q~#iP5X6xNZ#<- z at bk~^eulW|`yoTW|1NwtM(Bhfc0u|kR|U(L at r(9zE at 1c%->2`xca!hGi#9qkeKQ>x z5h8y+Muhm;`0i(Jw173j-{N=oF$X{2elt}tF#qVwufF8tc*_+s+q7Xhmb zzWDqzWJ;gKyWxl7_urvw#4u*V at NF?;2aIE~q%ZHedp`dHhqBfPE8UHESumkV=YCno zRtm_Kz8El?Rl;Wz{mOUa_X(vZ-~BCM&VJQ8eKW+ZUw-R9J^Be>B&-(i4nMtn_xtbicLk$`ZzjT8{(1~BgJC1oRm$f{9+cjnvPkuK{M%Ob z;kVx;e3HJ}wgqhdvg3Fz3!he*g2hV!B|&n9uM40afA!@T`HL|oOW=BR_cLiS{`Bsr z;nVb;5J)JbZ-;pFo39%@ldD)QqcbYs?MC*#WzH5|&3743IR1i3pYG;Q1M~G2E#MuLWb~;;VXXd7WPwZohR*{A z6Celf`gpft@L!zc01;I`$RR715d z;khiJuR^tNIYd`go%5$-bWWe7J3 at k~Y(7ZUnq8DDG{Wb7MLHhLcgv at lYbLjmGPChS zRGEB5-YjN&;e7Imf`>ul+jk7;+vX>WYzvM+aA;bgS)uI#m$+tf2VEw2Zim}}*-0N? zVF1qu(w^KVH7ZcIE202K`ec0PlRN3oh!x2r3e at nWRF_ogvn@t`c2{1 at NT2V-+oYSY zlQ;VW4SC?pV5Vw5YwyNS%O~Yd|Ms2Rw=M9uke(qHT#-2&%xJ-%n6O}c2Hs-jWYG8B zSZW6L62O4Nlv*P}YYqeccfuz_F1m9&-X6K5!&eh!>^TmrUm+q?8#{2MBCxe zWRBI85s5quCxS!1^S~rB>BB==HIqcDOoaPJlc#s z`lF{i-&;BWW`hwyXiMq3a4UR_R|<*G4cCFMRhGmyxhfDomI6Q&1x$nd8TbTrwv`MS z>0yfh4Bod!w{PA0ID9<5_4QZLocZM!Epci)aq7?|7=}9oS3lj3x3)g!s*e at fo+slM zeRJ05pJm*+#bTacEmI0)S}tSD>7Lb*OVs;;E!=e04t?cl+awD$ix@`J}7P%;ok76JvxQ z(dna)S)k%d$+>0UQm!dPYwuS67 at 5GGzD}W$w=|z_))~&N`6Cb zh;&ieLCA=W0fa;lE%TI*|I-EdqC!xcmM+Ms*4VHCqM8xoN8^t_vW!b(RW87Tk at C zh(qsZjp#9g*T_mF?_!N3y-^g3>!;@vb{ZEi3hr-c(j$lGeKZB%hYu~vI+|54*j1z1 z(FS8w4~`biVJ4CWK%+UdU~*}KaQJ~VmAXd`w{F72hbn>$Mk at 84Q~Bp6GSZAl!{-mH zEM7me-co0d328aYzvUxyl;jqcdmw2{pv`%2dJ9g0sDwdB at 7G?hm%v&GY^i*nqP$JU z$z;oimk;gQ%o{ZJHWL;{oOv2&W1rfT*-&Ix^Naa51R|3*o3s+w*PM~eo4}*ztEpZz z;ROj^`}r7ZN~fF6N}qGbg*ltq==nqKbd56gy;dPVWuCa)tZ6;OXPC1>c+MrY2Ed!r zlD_PlK4t2nL!GN-*2b)%_FPE1Sf9+u8NxLTpw%A7A znC;ND;C7YVJ*qoZ>Z&niW8v_;%j%B}%*Z|VF5t~(gEiIOO8wm}5rR#x_Z61CV{o&P z4)Z2%>}OU4>&gM3$*P_Wi*l2>NY+=;Dj2Krrsiy1P$ih(EGmiL+~{rSIgKrwQ4_bG z;lQ<}l(rT+yK&YjSWlgrHeK(~Png5jt~Qml^%;~v_w}OTa{BxsUg=vc-Vg8*fd?$M(XPi!J$l(^~PigiR$aFx-|!9((XwOkHgtb zm1bKJa#Q>Jv$050egf~8Cb$I={WKL+cERRGqt`C%%-*YyjA-NxBG9zles&%=LLfT8 zuW#7gW(vz4np+o{@EO0=7T@#Cuc5Xr%{pG at nOH!<3++bYaf!Z$n5YI9y~sOS#%8+O zs++eK=qG4t);6nYhn00LrA+m5c-G6Z)kZU#hg<9_HU;fQ$SOdawEiaNxsCr<3)m{_ z>ntFt`B?fucEuWQ$v?m&RZ^StMp%+z|sp{(0> z0Y1&f3TO}M8HvEyb-EKXF0S`HTgF3+_VQ|tTcVA>#j}}wVfz2uwJyHB9Dy=uM2R){ zw8wFyVLw^zyt^e00%lSc?5)?-*AqhhM}N(^T#~(A at 7$+!;xVAT!^`WJFdv1|vj}ds z@{C21wVJbefj$~q*LQi at bhI~4FxPZ1UhphN%xP_P=Vr?J^~phWuq0-??1FNF5~enz z*^TWEH#6(WH&W?Zfr+W~5_a>OT+wqeP1Z)W{%jCJvF=b9=lOfad}U+i)~%`o at -SbW zxl&n$%#rq()9L*D=)&{QUx*im=P#T;e=T3 at 8-pvJMZ~L^ymA?qNsL9l7 at m*%b77(@ z!uiRC^Wi)^K&A8{4UC*Cl^n^J3Zo|%pHI(^FFb!CTuA3f7tWt!5lC-Q48_juo67}Z zwsi>pJi|&i at zyywMK}iWF*YxJ0Ng>ell%7476<$n(ct3q4HC8IGwMyQFsn$s#)RG0 zzdkf)JU=;iO-w`|#dKVsE)S$y#~0*_<@v^!N7QrYofNdg{mYE*U5*IaxW&?io at NtI z(a~LShfD>tgJhR3hHBp+Yo!b2d^k5md2uGr+a%P1^lF$@b_y5!NGoSF7sfavosZ`N zq(Q*}(ReE+Ef at 3ieT~6TnlqilknPoUWq^Eqg8M35YLT;dA)jx~i6|;pWxJ77j*vgS zi|&zvN5Z+u`E%)9bG2ce7KUXY;h#tR at _fE9I)CB9`FMVOPGcr#uR7ZkugGUJ7h6W; z^8@~oPdqn1E2oN*5>F~yxEmrux297(pU(}?p2G@>^_(n8$Z9Cb&&#GJPdGO^d+sb4 zG at j3?9gHRVG9%(dluh&eeT;BH0%EOzrUR}yD?Ut2kPCp}(1uSmZm7vlMXO6ShTv!N=LFK;_sE?pEMgpIO-;XKf8+`SA>XUmno z*k*G_F^;FX*uzu_z#wQoSI)vE!{M8lv+zVVSL#9$m|i%a&FH}-NIt&Ounw|@wI>&z z4;bwM`pXJ9p`2 at 2-kGp?X(YYOT+%FD=vRB8oqHW$@RJ`F$6`HH499Aa){xYk9iKT{ zt!Ae at xnlM6yVFbcy@B`Y&aXvOwOL+m!dyaQZb{fvWj40DNv$Sixw&F7c}((nMr?& zM^idKgOl=^EeT}8I1i}IO0J9*fqheZI2}`*R%Yj zGcc{9nw4E0*U=^DpoMnl(%I(B==7P>)LW;|Ft=yez7fM+V$^j4Z$3Z7dO~hFo6e-uLmOY*KN7?C zr;L(Y4sj$Y1aOjL;+ZkB$J3@|Pj+U`#zNc>c&1^-9=6g}r+lWws;p_=<<}WS_ZcQQ zb2^;vpOTMN;Hua-ZGd7kTUsnJVn&XPA+n;A~%RUhst>B%;|J`atZ)<`**UCx^P}&!r74! zAg9{Gu`VoGyI5Az$EBoffva&r-H`e8gHxwZoznV}AU;3SK{hKPD;4%!hIo2QBW+;8 zNpYH6^7)=8;xj}~I at 6xcrzT35*>NJYjy#~lIpsnD8?j}8>WD0%PX%38%vRA{S_4eu z^VxzE%Bj)GQ-Z2xp<;ocsLv>cV*`Wo>Gst4q{h_y9POEwgu66l3ll1GQ##$8DkQ2d zqIy8m{=H$Mzf0efr%s+^Z!|E;f7<>`u1=41W}EN>lV?t61Pmv;eWzh$^rYLKC5%nS z%m8Az?tT_WfpHaTo}AO!ErQvnOs7YuPMQznEGv&WAsRO2GJmjZa2oIVw0 zs8c7y$+}S8*6ZG%3G0C}#@RD67)+2j$@3HrA8*h`HhS6 zVLsQx0Un;hid(OoR3s)w+OApHiyKXL8uTY$yFt6~oQ6f+>R3XqHu3atAU}JiRUAs# z)QKSO$m!7ZY2sWr-PR$0tFyPM3AQ+wa|_uqRTac)Am>3o|@oJ4}$M4G{!+qXFCj<<@L zP`YMBDeon$U6>gbPozagh~}d{Dt=gp+>??wnb%pB0)zGmL&NPMYKr911+q!_Fq-7~ z{pLMY8pwsBEr%ir7}r8)e2l4O26JJ=gz>$}`|pMK;!O_a)2DsR at d?vICEeziTtD-1 z+HtuQjqmr7=q3_{n<4_8gmjwD&;Fr>FyrV0REQsh_XB at G!Tax}_r^DGU>d>YlRM3w z0tYeF**~~SAB7Jm(xEVVqE(RWCX$7l;nNnPr6U*0#|3%P2O|MCW59e9=0<*Wl2dwS zJQF_{z0Zj9etIw6d~oBY4KdY3nwSC0EoUlP)~?G2mK~#7x*2ZZKQS at +_B|jCM{x_M zKZK-W#*Xi2=AYca7~+3BbT?3Fa2XY?;ln=8RD5OT4>t$$=gk}OhRE-U%xyGMf|ArS z85I*Q8h)rts_{CxbdggnvBq&>6+^e6o8SKt?+7ur1giutfU$R#TcSRHR1CL at pfN+^ zd*x=lF at E<3#JJOdK6AdMaL+`syS;!CtRlIG?>b6|G6F{s-10cK&@3MI!d19AdGBUq z`ta`fh5}z#2<2}1g5^hoy2o6o@@(If{5NjiNL(kH$dd}Jq>K}he_P;k>$KQE-Wc7y zapT?a?o?4YOyo)y{GKv0*6F|hfmtVln$ZoG$TueMB2~J*tysEk#!ofFcs`2n2Ze}G z)ZU2i8o_UrYzt-;b^>EjAm40mWCUnHwX_Rnyc%c;7*<=1(JS64jO-lP;1UnusahFW z#hV(M^4vzsF}6ZNnDCb2S$VY1>n3Ule2*$Z^_`In8r~kf^)|v-W{DpTJ_2pp zoR!&yWOsJBabJ!Da(vzar*G%Cv}*|?%DX)hyIGS>k(^=?9^T%Thu=cg at Ye86-#xUo z3mY{tbg4CX5X2kJyXBpMu6p~e^j5cl$e_4QetZ8tDZFj=HwQHDj2Rx^E^jHDB7PSw z+C;TIW-K?7+~J*q)T1}wQV0rWw$m!4JYqwaMe%8k2&8DboOG*@*Qdv~Q!bZ_Q+QjfZ{A1$Jv{xYZ(B)#Vd^VfP-joe%S2dnJ_ at jyKZqee| zD zYOO7x9I(hraa|+8I5uzAc&js(%>RJzw7$#;WYz#f-cEZtXP&C5c^&sbhacbtN%L#1 z(}{u&7WS91FLjfdTm{()Gydp3OQ2&xaMU~pn}Y+Eun zDbH-1ipVgBmeuh+e*-Q4GV^V$&v{scgTrdU`))xqW3SF>h_hUj{SE47ZpT5L*o=|a z{!=DI4%15d^>*O7;udRBe~mC$rGN$l&)NXxtr_K4`Y~bE*)@8JuBtZ^a-#1HgGiXN zP3uMsokrQGv)-d|H3&ZSNBO}L1xe4t;{o$qQardej{vjWr{Th^VwkBky9t^Y5%(?7zx{g>rle*^c-+W$8A%KZ9R1kE9cZ8SZSj*_+6 zR{b{-oi=Uerp7gs3zSeTLq_!X2%i-OR?8}0AV*rN$p*Z>las=ZwAb;ONTraYfX&nykUYlEOd~ZBRh?*Js_cd+9 zRrvX}nbpw&H?`bSH_{#hLYFIAxe|lmN_&GdzshRY0FPdnT-G~CR7hP at Sl6}!=-TS6 z8m4)A5ZXM3e1ij*!ZBTL*H(M0Wx8d~9<+95OlL`|@zsTlxmB%}YHFG_V?>j}JSrHD4DbO2+iE2sgo?mm|3WZ8IdiPag{u7(4IXO zI7YAlDC8B}W?g>A1KJ%O>Xr_4eF-u_kBF=p$0_G`|Wg zgU-y;$?eeStuP#XBgh{9F<4`CTCYDQQY;b%go|rjX9-;tGv`9ykpeEeyw3ZX0#y+x;}f#ErDV9=Ds0DQ~P;qj-RKF;7Nqi2ccBvfJP zu2Q%b1kNYI(~}cVV};|{2;c_fdI05TG}q)f#+)1{OWc>^m9b3T;^>OX6(oyi)3Yt8 z4Ns3xJbgSI-+KD^aY0w{3^~ak%P&1pkjbCsUki(5R!vXG<02I+Woly+qW=A)Dk^jSwCa8+0O36v;=*Ny-=T znIYJG=0rHr!x%w-+}4%2Y)EqCe(0-BdR)#T;rQe!JYyoHkl_+F>`pz z*yky$n@(;k-)QFaY~7g@!v%wuHRVK$Nikd$ZrQ?&6zi?+~V#;DQj<}st zx+g}ZzZ)*vD*34&{mLi*@)y4`&>bPJ2Vb6^Qg;!_K*85auD>Yb$k56k0=&j1lB*}D z>`r;=$?)XpFHcG$$6)qJL*1tg-3rdEkUsuYekwm1|6==;`Mbl&7U1h5$WlN7B(ijT zGW=zP*v2VZ#;PY4UOi{d!jbWjmWSTPDew^o6TRt8~OF}S~OZo zq+xlxflbgEdHCz_jlc2}-$*th-87n4NAO00WX|~0TiFEE=<-HGdp;WZK~m%!iKBf8 z%V09;tiGFc^oFU^ygs52oqC+X9h945EJXl at cXH*B-fZ41f6ZvDzIvH?VZWV#5n}{J z5?~m at n-d-Bm%pYrw$N0%s<|DV++Kx?t5kM;SY9Im(whl!Q`ZHs@=`|JzniKY1v^GF zL`IDCdU%Z%chP#fY3>AZFV;?sjm)0k9zv3c!pPXvoYlUbUdyk>n**2;hKhB|s3+RO z?fd_e^`75#BuBQd(p1$s07-xV0TLuUz4zXG+bwhF&0MMFTg{dq>Av?jzb|j at NNSP* zj$QY+BeD+I>n$9DnqgLC#150EYiCC0zce7WgR<%phESN1|9dEA3?^1sq`$|%4J9?< zDJk6iDbfoNf?iyU*wz4%SE#oNG{Rq at bN<@k({NVrt+vBFc9NkybYr(W)^9ZY+4G<)Ii+zaa#?H()@d at vmZ*6j;T?%CJ;Bz66wG^>UM! zl%{+lgWXDHz}VA3D#t(fz*5Zz)uE8CW{95<+)lpLL`r3iFzY`KOieX1B>r|J8`cUu zEV}uX at TzBHA|uEg`KkKjmOZ96U_6%p*MMh?AN{)=n=QMm-AKTr-s_U?T*O#gE{qG5 zQ}ZO@^^luf(_L}!?f+u9N&_tFfBrY5B-KGhNtvvqizLT3$&9ss`r{vnPhnpDzGYKC zOH-j~d0*2aa9i!mXsadJ?#mrZA>v&DAn)Wq)pYtyzu(q4gC+mn^oZCyr*3HcKp5+FmeBX|6BZ9 z4_sxt%7=?}pd{M*>e0hgy(`1F?9>o*{`bGZZSR(ZTvDv$X8wcP>#1?s*6-iH|JJF> zU@)8K^Ivy%F*dJ<(v4HyQu~6>=6dfHNQ|p0i?CA~xC<5E at ek_xHed?X%^tRRRoPY^ z%aK%h*R7o($?@hN`sJyA7SXs{xzb;Tf5!D`(#19YK^-U!TXk_u>5jT?KtrRzelGhy+Wfde~qqNw8+ at 83_&&Z{Zj{0lf}@#DOZDeruuW=b2XS^>G$DVGV$B@`F;PJ z_urV`Ke*q86(6D7CWslX3{<~AwHmYDa8kG~RbIta4R2XxQCf{|y)kS0Hom7J88gJc z^GvG=G{zq$Ng!+n|XMK=7or|%y&i!##_1 at Y>r$P?jx_=X;G z>Og6$malU{K;I&^?VF2tStv$c-g-9BW|IE;#W at x+FR`}SO?SA_{snzfM zZ@!`N-0wilxqRICmw?xWuwo~-Dd;Wp)Nrm;(D7gJxxP}^TAuNTqTKr5(m+urXbSHJ z8Go0lxwZ&tWh}{8S(8#PH4%r`j7I#|sAA<`ndg0dwf^=>Q`gTGeTk1%7uS_5g)#q;XwZ=D=CZAV5Z=0xIx$&Ei-h-BNZ3vGl zAWAwFY5NkT1<5;ms!^rtZ~AsendWL)S5!Br{r0EDAfb|%s;trSfsF7DfPuEW@z at k^@457? zyJ@$J#|^u!Ow@&Va^oc8F}pU~5)31Mza0c<{9-p|{&;}IeI7i3{$3!c95~pFr#?ei`cuzOfNJT!3HhxmKQW;?- zamJQ(H$T`2bdz=vr-d%t!;}N#G#X8tH at Oi{`Y}fAWpo6aqujZznWycpZsxLEapFdo ze`h0v(OUd!kQRI&9eWip~>Gxd*}W=;YFJvWU5f}R+!gPOa{!m755!8UoU63WDn4b`3U=w5AnST}$T=3_^v z(IL7XwdpZOG#CM at s4-3aN;FiMlEXA at 2^`7VSeIe^AXLHNikJ at QtgGhj8AW8j)i|#2 zm|(!O}y31&?22Xc9 zSrZR*b$2?7a-q8rVBm}kwF}~8RMB*bDBu=H8|?A~L?L8T>u22#88`QCB){D)Y#8`Z z`0^Cd!t{v5y#i~vY0-tMosDf1EX?BifI|H$hV?wMSUH)W;63yu8BO{MnJSx?G9Ce1L`=vqq09$ABA z32fG_qKnk_ at 6(kzb_4qG~RKns^y8A0=caKc>kPNf_6m?Ug|$7nr(nX)i?iQcyn z)`!wb0QLP&c%fs}BoG?y!a at mD?%B`exv>rCF7>oxxEjM2%KsD*{@~o+EtHfowa&B z+5GG4o(+7%k&IH5l##miRrXIYavS62a{=8ZV*$+8XQdn5*)-wS^ji8rAbcmgy1!Vi7`E=@)al{Dm3c=M;UkV z<_PYz6v*~XPvuCJ!*XI;p<_RGO((yOk~+k?t6-sx3YG=!WwsdCb^jKzQy%)YR}T4q z*4%^p*VnSyZuKL0qR;r$R_E&Vfms4>@)I{mw%=g<%0w%%gf7Neft5{wutpG=^kz-F zNfH|%pc98qW5Me{)D*@{49XYP7wO&bEw8NWx1%?2-=sI=*YE*ijSA_Y2R?g9Z^+BL z_2ylE8{WjX)tmln+lG(|A)0Wk64FW&L8|E*q%zl(2cCV0bBdXJudyalWB zW9N0?5}mS%Uv%DuxBuU}w{Ovh&uf18Xr}|jf8i4I51j$xZGHW&dfU7SuX}G^8yVp5 z^%Kla{blEi%)j=Lg0%D|z5dLw>PHi*$P7wfRLnbm_cmeG`0bncCchrP`Z2p75EZ}N z5nB2a&@VIN2*uKC5$HQ&0e%DzAi}~>l at 0lAbTLEepI-OGgkWv`qx8eUmyCw}F^(!> z!U%kXrp>#h2VTG8gTbPs1CJQdBIYT)+x|in1E05{eZ-Xsufl7iXR9B5GIm0&#%F9u zV^F2nLx~Tsdas4PNHgQ45}1m?%S_RO0MSpfsHonU4e at n+1;zyd5Y4-sCs+>-?%g{jTX9 at VEm% ze&bNS#_QGV>ecuqpcrG7ajB78QdyDlza_~c@{P3S^YT at E<$SnPbLmTLBvfFz6#VI3 z?OqC|Z7%j~zkK!5Bj0#&k!X#LkL~zWtoz6$HVrcYkdvoIa*E#+dzis1z3pJ0kch9s z%kawdw-^&!>hxv_ at IV%Z*YV9ja$kqni3j?xUP_%j3C)J6l#3Kuf~1GOQkmT>MzKYF zQ3I`4jp}uH)ps?+%N-PzIHcvGVfX at mP*^zLdWa%`do!ZzmleFDGTHJ^MWWJL znSZZ9$J3+&&24jlawPG5din9i%NG{v3inns8QD${Ui`m5{L-_brA-M9yhE9>x-?XU zh(sF!=^+=(+~LLNWUZc7b~go at jNxO&dVbkQ3DgLM#5ccq_3L}KMl=sp1&jLpy7Q{C zXHYzT`B9D()9E1_-VQy#g;Y&U?zACLy&SxF`SJxmwW<|C#p-vW4hzumy04)MgXU#= zF??=?#;Q^(@mR*!jK^9}*8FNHrv)AvynOLOD%msPivcKo at vb7kN*%3;iB~*U9!W2U zQYgO2M1&7k(UsyE#XBIgWyrjE{^A8xNe3`|-mqjvEklun&ab=tTZI>yEk3Wm=%SXb5^{okr;0O%lbu+ at qC`8XKdcE;u1 at -$J+p87!9RUO_A#<-U at 3K?4p+()*?EA#RzvV!>QzNpH;ffblYjJqomqgX%%kE&X5 zt82zr&*by%$yXk)x*iy{>Doxwr_U%=+g?iuf0gka*(mc8YZeIUn(DfBx** z)2CM5>|0IshA$R29{X4>Uyd-Ywy+2D^lb2y at eEfKC)U=1jrL+#;ZY5j>RRtz;uc$)0XW1wiIwqTVMmI;;%_6S+?EIi#(nSQ5~ zCoNJ*`3jFxDMC>!Vsd=`36t%^@btE8D?Z4mm at 98<(ok;9&#I^TI_l-s*Uj+iqpe%7 zlpsn*6$EnJ$LFWzK064T_=m0E|A8OK1u=D^Bnkx}USe`>+VE>yg+*GZsreElYUF); z4e|P(7xVtoT$eOebb|A{xrV>~&2Q-TWB0oGyKXTt;rF_w32xJ0UPpT|P9r=Q{fTF+ zPm;+Ob7s5^8c8R;94{A#1t#L&q~D^F`mE{Noew-?H<|tZ_r~$na92H^cn=rT-m~TX z*0|2**T00q5NiwP;p!x~3oiT9?ON%#;Wy#8={EtX`ZNBe_JG*ydJ$WqoQA(Msg;eT zjai^=94h?!SHBdSFPcg`l~F?P#k6u2QAFif+6h*_k>vEN;V%t;y8lDoGVJ>7zTH?- at wI@AQaBt*Af08-7E-z-YG at h2L^f76|<&ns&Oo=vQLsB0R9! zExNI#lKiHoy7(6-Xr65BnRrR~(r+Q23jFbRHMUHoxAzTU&@V7PAb&y)@z^AqM1tr- zBLghaJns`K{qh%JWpL#}w!$L?uIww9x51nIw)#!mRWQbRNBoAB|JTK~{w z5}rpsa^yU*ysus9P^Y$2GE#q>4;6TJ8)1WsJ}3aJ37?vJWt!F1zevB&Xh9513mXfO z__dlN*7TlJZ$&QUq8jLW)AqK$%7T|H4dCYr_{~w?p)1nXO8lz$N%HjD_uu5*6mhQ* z@*cm5Hh9)EjfPb}|5oXwiw^BJ%-?$7L^aH-8C80Z(Ob~3JM{clZp7a;rf}&47fMUI z=*3tztb?4mIUz4-p>zY}^ zudT7%wtA1zS4{odthJfhQz6!GM|08YN~#v7VbUUsR(x$WP7KzSeHDW$!q;Lkl)ifL zD?*0owHT-|+*?|zV`W97ID&)@Wuu?}tW at 2MEGiMMOd=FgX#=A z;HZ#DXLquI(M6p~KpGpWg&b8NmmdiT3yELQyDNXCzM(8$`FD1&TlV>F&c=T)IR1p3T0i_4^Y>>PhJB z?BY*-<*qG{ypvrrUxlB2v}0c!m*OlLg1I#Bx45jWVivun7i}~TCuQQS(&}> z9^r`AI*fI3?-9XtI-bo+t2hMBdbv_)&@=jt6y6HM=?n(>AXsPPTA zO=Mx!t`3cw8~TYj9d)aIo2Y;d1L7~Xfzl=AudFMe8ZLSlNxBY-Ub1)PaU+Q-sNpj{ z83(|Ncs|uyZ*|RnnpGQI2!JWQ_s;7JYn at fS9M8s~K+fzf at 6hA=w4UVAwbigXUR%wp z0sc|EL0TUeiII(35_P~tHd%LV#8gwO9T)>2Bcdblhj;hYT3QWjQ>){ZjkFQg<3=NW zQCjcn!`hZH2PS5E&1P&?^XJr5F)pv~TkA at pYE|?Q|6iWo*kc at l%`>&OXLY!;x at xca z^{SsCQ=%C3o*#mMA1D;`jH`KVDEl%x$;cH*Eo1nMO at JQK6D_5Y*ZKyBMz5{btN*pK zy0W^y4rKcH|6uqhV<3dQF;=21J}{TzXn!}Wqm at -$CaoLn9+=6-KNxFLuZ>sXqNZ2F z%5ZgMrFe!JOnJl_yAjuC3`&eJ-66N6$FAZ4EYO911)O0m^5=7Fcci~mhnz&ZM@|p< z(mb#2pqAMia2p19S9^M3b%pV;{}uo|B+83vIxGeMx*sZT-#u at k^Hb?%%a7Q*g_CB z&o=TbVsONsRiKf*hP>EQ^J*Dg;IVj9EOgCU7fnnQHw#Y8ka&}vJ!>ikZ=0w1wUSrH z;zcFD44N at vcwh5NS*rxm at +Vs~YDywSe@(3pG>GKrtSI$sf at x%anOYk#L}S|OfFER0 zXJx|Yd1!m+^$fGM)s9C-wK85^;nxP#rlnb>Fse;x at o{a6%FjuI(~41b1)_Z(5~c$9 zmq?%_tc;ddJVlfNg)?VQ&_vC(tVlLhgXDI;CpSxt#(dOO6_M$E{xZX?R>8jmroz$y zWe!)ODY&piv$(Db03u&_wP#`XEuPm$FFv|Q-;44Fl z(&UlCkDq-j!{s)Mrm#J%F`Z(movvf@!g6nAS>lLFbEAc(R-Ml3M+tJ>6nI0dS&3+1 z`M=jIas)MpiDn4}Q`&GJwlQwFfItnRq;*H-tmIjS0CmaOiT zB$;-MrtqCPh+p+`|H$$Y at Xo8-HcHs0lNi at Cb(KA=`N2 at A@IZdDri+H^U4rcP!0Z$Iy at Ucg>Pt{)=%Q&@uSD#vA>HCuevXt zig8M!5;O3$dYX|J9@*RX=2gFCs3NNyN`jssC_L&teq_8sYBaU-5kAlPxF6E9 z>gnjo(d4QD$XqrWlizm zWg=QW2QfYE60t&?n5rZ(!ug1{lxGaD>|v4tLAvHgW%kj7hlta(FqUM2lC*?WctXk) zDDtE9u>as8KPX$6*-WT579+{>WnkEnH#j0c>>=0*C1bSYjIlA6%7oYkN+>{whv89p z7#|EDKF|z;JG`jOFG at -ICGgCyLX}NZJq!G3CQek8p4 zWafdJw|F;>(;*MfwnWm|A3RIM$XBSP+C=&MWVYh zJgoKPeF|wG-S`B8wddwMO;o|=al{am%MY6e(E(G1G;T860FQciU`F at B0~G~vf*-+^ z;E=dyNNX(KG_c;CH$OL$l&pIiz{D`5M3NwU+|xzb^l)Iolpe(UeJLw26?j%9Gs3Gj zKzD55!w2`{{o#ZAGB0_Zj1;K8vQT-bz^Hjh0WlWtK=I&yxNlTuLp&70JFd)Q)n3&B z5?p>tK0e;x5_G;;K_z1FsB};)>ac6z!k_nf=WoX9EiK220(f++#BDA+iru- zQ$e6A3oH-tTYO0BTYxfI#WQ93DeJFd)lxw7DGhABQkp7ys{8TYCyxjV30tuglU2#* z-Rc6iBeY8QNB8dELo+RLT4ST4L{m7p6O+0R^MmGox at W1SP_kmLm8iC`pvGyDC?3f7 zhxhK^zZdV3hi-2u70D96BIKIrzWe}!_wMR*_pa)5i57RI$3Q6Jlf{{)=*d$&B0D_z zYs;(ZaqrP%Q>85nB?WTv)QD-q-SHjjj;o}SV&5`Und2jspmr?d?%f5ar)7awVTdYx zWLc at q9Sg`kbvP at Z2E2!Mx at loH5BlM_D>A{vg2Y?E~0aM(&o9>1?1;UKBz at cwL8rlqCM7j)A436vbP8TARvbf_w9 zm){r&$)(%LCM-dFhmm*NiM0Ltm8h`^Df-co*w?aV0SJtp^V{fjDcX)Tk)wH6P`5EU3(@{Z*Y;TVFty^t z*p?WEKE?{eXTpi7i?jdhwR_4*& zb0Q-mlpt3o`n_!*fK^@P)L!gA=(|^;+`#Oj70gdCZ3G+vaz!^5wU^7mul!8j82wZ| z3tu*>mU^TSpDXN99;LK_9sO$Q;){h{_sN`MA)I!cVByl*UccQoSlP3oM z?rr+A4HJb`uFrA`$ve}jpQN=%3Z=Kv02m#|_CBlHVyW#(rM7(I9~*3WLbhd`lgOX8 z*B?Qk&PSS=ZazkHIcd4=R+o6s-n}hia#DXvq03nXcS!td+mSNoTU$xBsVkk?tl63x zt at YGo3fHKKnC6P+&6a>IH)r8X#aij18EN*UI8e7F8owEZrT9*EX&K)4j*-dojoaAT zj&$LF^gRUBCyIL%i5kU?6L_LDhl;oFr>V6rQz}I>PzN8^<5ZO_8e(N3L2mCF^L z_dbYi*F?J_vG~*M%_OrrVb=d3&4 at c`R-9yc1~KK+SqWvVT2r z)12&6Em7A#>+q+TmIBp}ArfSb z%hbz??fpN(XSxMZ37 at SwtBOOZd2(1(w>cr|ri33V^0h4J3El6fD(($EskHKTX at liQ zbt1=N!Vd>O`XNtoJDyiE=|t~8q5D}hs-;y`&iCkeh8}E}6ua)yZwid`bW}ILim0U} zm!GQ`e~A^Iw%g`4>MH9AZBci-)Oo)-opG_dSQVz;TXQ$^)UeHaqNzh=R7*XpZGMlO zuV-8#r*(=lqjdZ190`YEzN}h?Tpny|*H-B|XKlMOP57iq0)L&C(3vMR(fxT11;=aL zXK`Olr19gI!p{pccTi4(-}9xSeVm%bs!>_n5H5ac-A-$&tT&+b%H#&dAKR%P+!{GH z5xyL92;rydE^EQBzOojnN-g!pmC9;r=;ui2rC0jU+I5WhzYGrlw$-jH3LW)6BV=`s zO2?v97aWwfL!qZuWwB at nWa=-)xm#=ANVcebr@&U})p4g_2Z07(diz#2%(Py@?RS#W zP*DY~!OxMC8xB|>xF{BSz*{4Ft46xEVwaqevCNM?X`8+Jl9mpY`Ty!sj#Tp6How+9 zmBy5lLp|P{jdLWHx8Xl_RF!;X-;zK5%SY)`siy~OYx$$r|2>XD=o|ruCf>1p%2}58 zYCxxGQ40CVnp{pOjPSm0mJI*i_ldM}kh7|Q*0Am;KUVyf(>wT9=QlWF0SK+X)FDu9 znp*@|rZ{w_26Z~4GJIz(NlVMw3Prr>VfaZ0SCu1l-kH$%=u2=}$~+$R{Sd?3Ww>;R zRF~Se^<+6C!k%^Serk?P)H;1}^5(vi*l!_e1A~EEx4qN5? zl#6a5ErmBv!r6=YWDc at LZ5h@7Z%bSI&HY@)S zO<)uS at AU2OW_h3&`$wuHGt-t&ZTGI${Ch%0Q!+yAT>%@DTP{W-w1c zaU4E!1YT34BSe}0uFC^PnbkbiKuL_3k8~0L34W$!_G;*_5t7BJ4K+h{KBN#6%SW0c z-yvNeV|NO at R*0jpoL0Je03ub=p)!__OdlB?mO4gQ85Lc at VHkxIR`xFMh)B^-Mim~A z$-To)83p)g3^jd>^_g59h->8Y}}u2E$0M$Ugo)GB8+*OhYCehEADX!KlIr zrIvCj%9><@F&?Q77xK8Xl3D_<&_hEh-90ieC_+(uWqP9Q2;{`-R5FhrK2t{;%-aGA z({^=59IioJ%c$BEnZk6SJ$wYS*m at xU74{C^$z-DVXD%T`mXFj&(%}&hcsv0vpm z3l#&q0h1A`q~)$~JA5P^$%hqxVj4|y%ej4WeHpvVAYjX~lOJZ8M3RVI4?zh|%Tp5_ zFhKgZ)T9rpmkqISWMEkARY)LjeB>}bmz_>%59Mn!uR>~Au0hsR5X20wfS8LjSgr(UB+8k$nma zrDq#qq=kkUywjfk2337{yac#$@mk=3#S-E8Ey z+)IFIXoJLXI4?yn6D=O(u!eUXV~gD+;LtQ9Tw4l|>b?y}b`<9t57Me=Hh#)|f+o^J zcvM#ErLVEbm$VAZ1la_)*gDSn+A|h{(l>)zW}C5_1l^aU$GS zlKKdMS=KFKXIkn3hcdq-AWWu2XSq*5 at klJ0(;WVX;=@c`;sJL_f=Oq*IV79+qUW>4 zT}JD;G&;P5cvK0Vai+f2ZDmL+pE at gtOMbmH;~{}UR)uSa&zwUhn+^%($A_2V(iAlw zpc#uIS}3FGL6taixFer9f^lhl$itM9d}L6R_#`7`DWB at _C%4dkH%nH6ELA83USe(e z5HvO%8wr)A#2++4okK+47Fy_skL6d3N;uMyNT8=kFnx&$%1(QXpQl&2|wW8RdJxl#V_`MAel>u_d zlUB9BEUK1zhnLXt&=M#DT3nvd%49#&$~d#5UK$-*Ius6#m#|)gR4Av~7*+vnytEx& zn$oO at BN7G%Qmybg%y*pPT3ogV!JyfICewa*$o*=+%Su$1{E|nqd(Toh)L%Lz(Bq_- zlrK(u4}Zr94_ni>BYnw!skx^tY6drhRMxPWafi&GgqOUd{UVGmS#P>lmTBfoOHWgI z8z5Zausl6k#HY$ohIE%cFhtUYg6xquE!VUNVt#E{p at Df|N--iF!lCUYG%(OAcLKPT zfGLa25^L!s-1E{WJ$XpKmXGZ71S*kB8Iq~?D+}!IQam)!XdL6-lSs>;+LWJ>H`3BT z0{#1(N at d?eAsim+fAGboU?+hZx^yTVnqr)aNIs&7VJ at 4H!f*&xi5UV%G1F~BZ&Do@ zS~DUobq{GD|3^=Qf}EcDgmb$*XqW_={SJyt*Gi5j?oB zH}j$~>bqewI^B-9#!Td9x=6{L+Zf6C=&fPodPIY^9>-MesmN7#jAtb!H zU*F4!40rRL`u6zN?SRbQ&0EHER}ec!gGN!0{G at xGyAl@ejBek#t?9RK- at 0}4<}DtG z5d20^i25*2Ex3IN@#DL9;+=3i+)1~CR1UZLH}#-BWdy3oVpMmQFW;-sC)^(2L7yI4 z>nGm|H{qT;kniwQ;eij0Fit~JAknM{cZR|--0tfcVUuqT%$U>~BPyh6#mCzN6CQ32 zrI5{#CZM-d2>5(=faLE$%z=NB`2!%nb2|c9xINTl)y=UUDjZtIbH>cwyL+c;v2tVL ztr5e+&B2YEOyd+#EF`LVCRr$jiAAPfwD_oQRX4+pzV<4J>olb6qMu->>>)*JSI}aI z>r{Bu8qd{4Qsy7Ww{FIp at dlh#W0OO66Kf5D*f2@VPQj!OtdG$osJLiPo(=FWxqhUYfh;wBlGRLSRZc;uivE<%3vR`zfK at hWr#> zu5rKV5z)g5)`|d0cjE1#f;$)>`Bn#SRM)q!$-6R3I}ofkCP-TlPafZiu#$I at qUz?2 zD2;E#>*H&LL}#LJD#ghPO$KZm>^i6Gtrq!i%THOtn2&LRnSZ>{{3v#toj`y==p~4rb at V8#> zN)48soe9$MTJQQbb9lN}dw at jhP;GET5*9e5`0(2G>(|0Hc75D49b}~?cOoyW!lj!d zk}0 at blIhI@Xf2n=yFKE`!&?Psn>mzhWm8&JjwEkMc$>Przj(GEflEZ4xb>cl=Z?286 zVz0_zy4#_SSr~*{Dkh!lool_T{EPi#jeVLKwUxc;rsuk4O=jv(*QBu&RSII*ZXqn^=o?0CujGAEkxZ?t!zD!ZTb4ZJd>`atCl|*G-Otn?-jn{;*CDV z6iq6Rmdx0lD^diR*+y5cNFsj=$YjF}>EOVPuU$=7cgusV)6ohqK`t;V!7m~S;+3dh`tuE at ml96^HI}Eh1mkmzp?AGK0{ti&$ zCy5+iy^^lflch&1X@&Bo-0v!%uGUvZm!)+1>d+y^r#{QAEy?o_ieA1#WF`N4nRizm zssS}i{;aN5SM!y@HP`p%G znTtj9u7yjwT3tz(?OrZz6!4yQO-si)9Mx;-+`& zx-F;gx9r4sJG??ypOrV&n;tvFU-6I_!wxi{Gie$<)M#sj6H2QmO=4~3=rQy?9#@=? z`T)jv10<^{3vHy-zRUYg(U(Z?`t5Ep?;kk1RVdP1Y5WexCY44l>XoEESTA3}5A44D zFPDq?Jx*=I!SJ^4?Q}}JEp<7gg&DlHrqew`J+UNkgLZkC7#TGHjBTqZZ+_!VoYJd- z!KRBDcWJ*4deyYaQJbEjwjt<#JYyq-6BMo?tFC6=W~OacN24_DG#7K{oCLH)hDXaV z9Nv6KjqNgf=;O-?&+arw4cX at Hvq}@9qHZo-^5{eNQn)zjC6Q2bG^SF^thVuz#L@@J z*yi-(55^U#SD>1sB~*~QnqD1$=+US^sSy-RP@*xA1gQlNP5X!r^@L-C`s9)^Sbj!( zTRM5pJ3gsF((ZT;JNSiJqAM#%erl#9Sww-wCR6Kl zL3p7%;~;s(^iJ-6YJt}S)hq3#*Y19`klb|;$>JA938sj)>PO`51k+3F|MXlmq8bx& zk)bik0=2SW77pHckqAM&kVr3_j5k#(OGCB1bNsCXB?0WM<-Bo`wiJsOEPS6jJPKtg zJ$hqJ6p`y^2I7msbLTYGXtKrF$v5w-rwObPpYPSTa|tihbW(Q|3YdYVxw`SuW6YZ= z(Tz^s+|)a2>y`Rl>BH7kIz4K0fXYc5bu?}BHjl~rD(N@~&Ejk;ul70YcvDE_lG at U_ z>(!uba&kmlmJ1h{(5ER?;q99S#UmQn5di!z71$`R`T67h3)O~E4ysAzie3HMJ?llu z3tsj{c) zYVS9bp1_Z5cvtYH(z5c(%=%9<@l+c<%KkBbI;arPm|>z0sE%P1*^Isy(q2)kKUb)SR#(tyL%5 zZf6w-H4?j+hZ^AMM?4zd_OvV at R8?$uu$^Mi86YH5Nj(I2tI!8^0F)Z!NZV0Or>wSx zXF0tQp88X2(MQiVbu}xE11&a^AAQoHBMvg?kcHIabOAiR$Yzh>{yj_Z0qN7{ee6m2R6EwB~ zJU;uRP55l{*FJ-6`o%XLDw|HYw9V-Ni=X&(TaxKX*tXnMKesmUA`Lec6Pdl|Y-;{u z8`Ztdp3h%X`8|v?LFW6DIa6TSr*4h7oeYcwVd>D;9s*<2|5MGYhI+}nC#oQI7~8fE zOl*)Iae9LTEM-BVpPf9(AFmWTE|(~yZ5KVwAGC3t`K|iU8dIZHAj2zKm>RnAMPK=? z%&Rno?bjYZHdTE2^=`5p3sc_dB#KT=lw%4EOS at lu+A6K%TC_z~F;bEKr{YRG{e`g3 zTIdjtH9*Xzb|mZM*dEQ(UT}8o>#ztV3zB$c$Q-H$||RQr`lU(4w6skVl8A}-^#&ykHpKqV_89`o9y z8H7<;SV;;GDmNrN($Z1-X?BPM?+X}6Zad9E4?f>;9-nNnH(k5(DKWcWLiIpJ at ab$H zM|d7{QX)V0$*t$>L{qiItT?xmz%Tgezwk(%bemEnwANOhPFSy_IDQnsCVlis4K!^L zC36+glfyMphY_S^@*`1x8sU+ZL6yP^qr5+(4kNZbHH$a(RM93q@@CC#8S(>+j+#oR zs+|sH`Y|&`A341DvlsrxE964N9GX4!@!+9D2M_YmJA9)cL}bFmi7XKx!i)H(yw2|& z3J2q%?m;J?y1dYbn-ZthSxSdQAUF?nC>|OjQLpw7jt?D72jh`KV;Td{6`24nX#4;S zK<7~BAWBXj-eyk!G4>}f%^XSxhX^_t4vx^ysKs=+az at N#Ir+4QiGnPV9r`7n3wlGA zpuUYX+D9rm?GO)rXCpD*f8s%rg`b$eRibjzV_L>Y1x)E4EILd{?u4DGT6+g3tXS<- z4&scDHA=_W9Mln|(>XL2MB6;#pL`I;#TmkQxZ8SY at 1b7tkcq7iHit!diGJ6gqGT48 zQXkr}`S~?<&`2H;LOr>NpD8#O2BkyaEy?I&cQLFwI9faie}oGP0#i#{I7mDdKeYi| z0djS4ya*PluxgTQ@*AYbr9Fot0aE*z;o`xAkVx>?3M3E{h18Qs^E_tRPjDC42gBmv zpk at bRJs>OLGI5ycT^|Bmo(P9BYK(Z4xLO<^WMoe>@MC_i7!DVxX*^s}7Oikza}bSM z$;N_uOyE7)*c|FhpF;Ip2;KxP7%|%-rbv$%xIj%zHz&wXrH2k~8gNs1u!!?T# z*>?EQrIMpfycFpH4dVe4qwNvpkfntIWXgY-I7tDL4i4-g;9?qV{mlPyXhUOdz@ zr!F0ul9%|InrMKj{CL3D6ZcQMOCqi`P4dBi+Bp3JM~Q9P3;^#XN{YMO%0ZGl6)^E& zUM#K=nQrMFREh9s_Uf~jVS at RS+=FyEb)Sb+vC%5p1h1jgjf>Mu6XuoK8 at W7AT%Hg1 z9SVuA7z?fK(=#@Cc$kiPHxm{!+QvnzY_Q6*scWG;r7>k-y=Y}cL9gXKQHb0<=5`8-(h_?QE~dq>FkYml+T at 0#C1gX{QN>|KMs*pL7IRs*tbkPRL-NQb zRrqW%v)cnmabdK$fc3sEc??Y!vTe^U!;IYGXL>O&s6+ at U`?ZIK5j8221jV$?PaV>a zf?aE&mn}omK+@#<7Do$>9g+!l9hbs-(IjM(9;%?to6oRZbLhyx*YDm_6Q~P2uu4tZ}(UP z32o+GTwI6?4q&$|MF7?T5IkZTD6~ylT-;$=CSF`f3)roaQx-&3VLtU|u{OQfx6#Bi zk2&UD6tH4o^&=VT`xbjes-mQ)=)|m!FW^&(OIc!2HL~00UtGuw79d?8CtpF4ymQN0%;NN|#0#FI|im2N$%p!Wp}Gqu;*WFR$&x)zP)9SEhKSJ-&1~TpnKH znfhX4lK?Nw^9}d_e}I?k&IYuv%e$9-f^;!l2sc07xM at F53F&1#{NdGLFXYFUMKfH! zg7|Q0eECwi)V&yQ8fP2}xsqVT^40E@$~3PorHjK07cbn5m~IM-Nj2jm62)8OEAjGJ zmNuX;xNwod0jj1cJU|T(&=VAwFI|q8d|XOQJ*g$#^SW>|a9Jix;X3q+717m&aDI3JMyO-{ z5%9@@kF6{F#0#HvQDX`#bWj?gzlnVt&KA+AFP}!P!xi?Fo(oAM!})lwhiT$EClqia=Is*UQF)R`ZXe7|k9=-;mcfavHVC}tq{{eRXYmvT9aB)sTd9D z%&49@<@~vPE?sL1&+2)YklQD?Nrfq(mnOMj63&j!okbC9x$4K~RUy08R8Q5h>f#m- zmC^i)=j(I5v*(O^Eu{o9m=O0xAYLLtHCwPuiWjENSE!$^Z4+6Z at fF!%bvYr~6UbxH zos}5gKX+EKDC*N9Cl*jEo(LC)QYN0?F;H=D#1ZGtoegI at rBv4hS{sEWGZY5Zxp-FQ zmOx9TuqiF#r2#rC98^{;aH>`+=v1V!DN>i>8ZUMgHs=VN`dmI+qo|b~0ugCQisV@? zITp`1=SFAGoy}*{)uv!&Q5A_+2Y at tJ2-es>_JLKY`{B}{2y6(x>~nfhS7*bS!Pzrp zjSu$4tTiQ5X?hlU0*lB~9t=?9+*x2aVO!l#ur>GXIAC)g`lh=0ikAZ zJYVLo&*n4f$|o)5rBDR$QNs0=14_zBQVty)^Hw_Bobgq<^0X<-StL|lzR-v#^^7Xa`B8!1!F%Zv;m at A)d zYza!_lTvOdjZ`8K9nTJ>Q92V&kI$Trr&a4%WGk|&DT`-qpr^B43>}?1&DWT4>AO<= zH8+??^0dgUDG&|P!kJ{F+wZmdR-n|dfK9EMg!!4FBG5HWr^6{Xt&Q^XMX5ZsO*#>_ zd@?aMl&5T#Q-xR)4N};jr2bVRe|TT+9hlOiZEdYCdv^1EQFm$U zZ>_rY*vagFcETjXB^Tya at 59xh2hsMcrhT3|gJ~bIw#9M1RN+|4>vrC at i(@J+#nhxH zJp5>?*v?jMY-RJM=J95J>O`hKhUy|#)5)bYnu_SWH>c!V4m(CR= zweBN&6wt|<8ri-G_3e_S at ks~wd+R85?6WQA>SBeychs#+>`#8Y!^9vVaTPZC0R)0r zyNBC0aW%pk!lP%~)wNqYv5Pw0?P&|^o!jhNa3XfnC`^5ykg8^cP1%sF9oN1oP^}&( zUD9fEsp28AgSy=?UrsWpx_IM4!C4{AI(S7ymO~xd>q<|)z)jNwOWRK}G=V(PkLfh5joj$wWkgrB)C5|U;A=@w$rq)u*x9QzR zndH5tBIKnyUs<0)^~WO#wUv8h`+3U_bZzC0ZJDUBv+ehlFhDqOPd4h>uHRcIQxV6R zR+{ozwhistdw5q{^YU$x*bOP#sD~6K#zMr?OuMY}U5&l%$2Uf|#7q{;YU0HME8CX+ zX!+y&N$pe%A?TgJf_3oo=36?_QuaPen&yUrd_=nJ=1S>z)$ZwSx4P>POxr3<>i|8# zs8^ZOHS8JIR%xsw<_=U zW+_EdsbiZR>`6QmCY!#sBVPL_ON+_mXsIsNWWKhP<00Eh%06k^r_O%ZbW;p>yWo_* zdXpA}iD30j#qC~yUiQ$T%v*@&vc8s*l}N>MR6>U?mVy at hKaNIhz9bm06dtP9D|9?qu6vd$;{y zQ at A<0A)q}1)k}{P4_dP<3bYkm4*hHkv`!n)ztIBCq0=g-pvj+kQ(Igek-#o%?e%@a zH;(okS9 at 4T`54*o at 8_)|`~BI-euGWvHzupE$1?3jbqL98uAUNPWdy}mSQaLaVhZv?I>00cU`D1680!~W zrXWd=YVnf at 4gbJ*WLN`fTOar|f8c=AF=9?k6=PbUNMKJI$*f%C&Vhe2cD~RzdIpk3 zgi8Zk;XGC2Bc~5+%^zsTLV!%*sKu)k`bN}6wGahEePA?y0B!~DL#jgEwN?!mQ4^v^ zqr`L^0GIB8Fh5{&QBLea*nP&JZWgv>m{}+Icnl0XupwaqX(CO`8_mMN#zg>&^Fe=F z{-l5aC#La>3wxP=D3P_~#RF*`i_*caGc{TG*Y2_8nogl%;7|X6K!P?Q z*yC!&_TP;;%VxG1$?n2O=9+w}10MlXXzH)UnF-jnEG<8?%+}LCgGwQ$OfLZ(e4HTy z+_Ioo#Kn0*SX*$E_#l`qKC>WiWb1+X3^v7yP)c9|#m3);W5*&7M(e;dgCjT=KCncQ zsnG!!54foVeX|Ccg&=PwwvllF>hgO4bPs&zkMP^@?0{!ad53Q#d4y?Up*}FMVMLX5 z4)Y4lwD{ldMg4Anx(j;_Y};628V=<7N!USenporBK&};@&7(x={FKQh7fqr-AgT-t zDIpw$xP^6fV8?MjFyGY_Mn-BdB|v{!Ug at dbf?%0|?gLAoAVaG8PD^4cW$;RzByX7C zvVj&G&Aeh9wjHZ29+fhdH~dQaU-f~Z6q=e3b2USwd2tHl!a}9MNC)=J59a3Q=8#}4 zq;ZTBhVz4h%|808Bm=Q=pgS*OI78iADY7yoBzC7)TqqIweom9`Ulbnjr~5Jl#N z#s>}3kFnt@?aJV6<)MAbJemx5giDb)hX|f(Rq2`nru?)iOd+w-)cAOzKAyuLV!1tc z;zGkn!cT~}OI4_3VVE1(-*K++ at YAT{2CzN}L(c^M-aB8<`4UsfKk^VEW$xGiV!5$A zU?g#JV7|uUq0G&53UlI8L87Ws3agf0tC;3dvzEdorRL`7^pW4~^dlH7GEnsJa0tdsx+;~5=!4}@+3lF1J zwgulFOpEg)#>Khu{9JNnC;8r&ftGo~yRGwk=X<8d{9HZfe&5k3{n?9Y=+Gvd(hLlN zLaKD;0K!yZ1yji8;zD=jv{j4wx#_uT!2}9lGMtKH?j`}`#IA`mhnrLCx$h at gL!eSL zr3TAQnO?v(bG`j at b0|p;DAP+tk?THq+?o4SBc(7uaejI(OBa6l%&N9494wkG-pnd| zt8B7B-oxyk*qvQH<**&*)`x++Un!;;jl?NQ%=3HYs^l?Z!lYU$7iQ0tL`t)IF6<|p z_LpLB?zdkBs!0NUEJ0K7AMKyRc>CsnJ+{UHsmdu@?AeV2f_8tJD<)Zma91h!RjfSU z2$%|$EyO&|w8RlJ`caVY7 at +uU+G4!c;5 at gze{TPN;Ym`MnT^#poNHeXEM~&|wppsV zx^vb3AIxdLiS|eEfgNcX%$B0c at FaTwU!AT_4NsmrbuyleCx$0aIJaB3bd7k7t+Qu% z34hvOsF%^z$#epmy>8#^$8*hDn3M=B)j8cgl}?UMoIDXU#JLC2?C@`}3nG at nIngxL z>HewHr}$*X4^Nytc_N-Dd`mdr6A!^?=b~WtM5hE(86x7;`1GlGs(11f51lv}q|EWj zOh#d3k*JUtXU|kEeK9g$XSeC)z8 at jsSzuFiis}$Km*DV+Z588u)=JI{V>M!Yr}tl*fXbHnRqIk9G^N_pWH_DiR1b~vUYS-aDu``9_s*!;LGl=bfP*gF0c?% ziUE;}$*0e<^kg_y23IFWCe*ei6t^}+S6^`@swTRDEzZ>3km$jHk;^ae?(|5uKQ>r>kn zu<~Ao;GsrIn|N*ueJxsL;lF at Gd2w3VVQ5=Osj?zJX8Q at Ck z9?)of6uz)-sY2D1gsBZdX91c4d~9QnX2Py8iatzf;1Lx`n?ZqGaU#Y0M=i at T2+B-j z^eV!{BP5<60KnVaQy&XQN5_s5Rs|J3VzH&%iEV6Ip-Zi1_3?CU=jgFxM~_)36v{Rc zG?NI!tJ%C70Y4r4c=Xs&KFWND(^Dv(b}TK(BI0F1YVD3k`#gnvGu&r&x&g&hMpy(? z#|9EhNR>#bh(fDxZfHe7VPr4|rDG!or=y;cYyp5vEg9iDsEkv8(uocT=A*++CQd2~ zrzr~!l3i$t?^u#@e2f?;6pxON9gRmvn+h!Rl=96IGkJmVaOn`e3YUDWKI$6^vKLv{ zWMZ#j at _ej1I@mnAxrwA=mwc}5k_PfOtBp#&mj&YJ_^}2JhCIYGzB<(uoZ_^yKy;DZ zJ-V}LrCp^CvS0D3+S{D~tH`X;AZ(6~Dy3>X)bYxkC;`$v5i(etj;75*GFyO!c5%t7 z7)oi2;^NH-{3sbVZJURv at oWV>15wY4vsR3Bys!LLP65(p;7>j?g&tODLt8=rsnVrl zp}`8q`e@!9Z?JPf1tXp8fsuYnsHfvS4P{&x;|3d>n?AHUQ(v#h9&b at 1iU z&9s>&YrVj)N_EI!l#X^cw>PW=TjsiZ^2sf&$H$3dNo#=81;ve_S8El at tQGi_ufWl2 zg2L2-Iy$;h?mW7gH`7KqqXnT?idv+`T2&*Bj}J9VJicXiDIbsGnoR<++KfD>0$aYK zG~@!+9t46#SlArPyq%3re$YSTy>RlAswSX$d|8P|WG?}hNQr7wms+O{FF}d*$!fP2 zJ}kx?@^w636`S*B-tc_{4H%bosT8$%?4Kxm?5J!6XDLx_L|=1c;ZTZ1tFY-rg9`B| zqL219R(j_RZeYeb3yBFJDz3tV(#>$ZZ1VHw7UDP7H`a;viuziETu&nj9 at XJKitq^z zIT5GpzcqtZGX;nNPTtAL8vM4TJP)BuHc)KzhROFN)weibNoutBXo_;CF3yhYRH=Nb zZbP|4oYcTu at y~%jUfql5_=aX*2(@|)U1cwPn`o at qB2}?3+b!a{@E98lEL~OkV_r~? zYQbzR94h?%iMZ~8tKyR_zpG;n8A5nO6WrcpolLD at _k z_3 at 5}b2yrSHSlLBSJ?H$KUjy;a5_ocN`h8Xf0Pm1r_~ZmFT4NS`>0&ic3kaxGWw=% zh~*tk^s2u!-BMSTaQ$D`)E0(4eOGyiUy*HI**Kp#X(N;2eZ&uV=OTLV_#Qeo3yMWh za~*G*qV^|6e547FTKjm19CoLPUw5vdGy;-9?J3oSY(t8dhb{kQtf})1>YrD$z1H`I zm&P^Tex|y-bBQNJs=!ad+!=9%=D5{*>T5M~R>CcE9D8-S%^M`zn6+T(m}dgw!=){4A`}ZM1L$MsjH0+C_fZ4r+Nxak z(P|T+#s+JO*LJfhEtu&4ChrHfnhKdpdnXy2Te%{R5{e9EXocX4J>E&%25tTTsZ%(V z8 at A&eB^w)<>;ne?-N}_1c!a-ELV`nE5tfHua&t!549;8f0xV z+H*l6UV}%vsF3Z!2dc*&%=8V&Pla~bSRBoORJP29va^45!JCFv)|*dZn27b#Hpn0G zEylKeRxoLYF^sIbq(x(6#U&~flkZ5*SJ)2Mu)UD&=8RFxKgdZm(HR2+#>Hm5STig! zjOLiGcA4L;(-8x0Cf7z^-{cm2t1`RL_22N~sHKE;Jd%9RdCNR0Q+Dxc|2I3&jnB5m zn7aWG*~ZO0E5u4o665+Z)R+KAl~#P4&vjaPy-#O~Y1vSz0oX at E!-R7I at pxy~Ru8Cs5ar1R{4Ai7iy)nm$!U}#Ud?-b>~YT2EMJY`yow&#WR z^y-{Jk(AGSr?<8YtD14_Mw?r;^S5j?&*w{5JMUYj3g7oUh at _?+)woQJ7Yufwrfe$J zX3^;HSW4GSMOU(;CfaE&UD{(394$d-e~+NF&{-RqCs!7QWS{Scu6Z9r<3UAIjpuYo zq&;4g*#^u8R`ZuH7k*AiHNm&xY9FX?=MERQZDXzWvQD-rD#kDcAn+v(%mTWJ*G&fR&4UpS*U+YYQx|1T<7BRv-%Is4}&@ zzKgx~pX;@n_H361pVm~dk-|Ey6^nEo>+vx04g35@@8k at QyZL)ZEi{XEaJ%y1?Djd{ zsGVgB%P$$}r(U#wTL*g-s~xn^YpClrMR3;*1vHhOk6szHBU^8n1hwsAKC86QK at 6JB zSy=nKLvj_7w$+*c*!9Zl2hkL??Qt8uM&U(KtsPnSr^9)}V9v{Y=54*1(L{YP(4FNjWS zc`WC`vPEK>IlP%YRi>Ir4-)VC^Q2RQ=5%PW+RWjDInpg5hJ6!SpUzV33C zLCje>TkW$n3}c3~fIh=VPH~nmNdWxoZD-ZvUSBan|@CBR5ic at m{DSQ4J>7 zhSvMPL-6eW{rt99`+H$UQBi;FjK at kTemrRZmFf6^Qpoo0JHs2Ms&P6;;AhmAh~@4;Aj zYC|R(GCTWc84fmi{vV7sp!wx{k}7*>pD`Q>104so`f`A`0Yjh||fH>1(pP!dRG zxbw$J1l!Eq)c$R$T&zs9hKT68Fb|m`90kdo5Wqiu69S;}Xr7G+rsN+%#|Me~#oGx# zgS?iqJ{F(upXHY*Oe2j%8yasVvag*a?jLF{drpw^On26zumX3;Cuy|DoeB~H(G?k( z0eRLK;cUbae6*uz43E5Oj_t?>S&<|?%?@Y45Sm<)yRN%g;Z`>&v1P-{1Z)VH+bX`< zHCg^t0H^&s%p&RhpCuH`@-!<0m_k-`EqV0J9#cP=|IwzNEnMC{ci}#VAkGdEh^{;6 zIzvQ2OgS2o^~ntLU!bn&BWm>k|En|GpCLq?qcsaHuq`X53U*A~ncWr)h8J5IJcB!P zQnA9U&n0HT#;+MNY0G8nF*9z;o=~KCfGeeGMI1j{z?|s=<#9J z>?^rbg1Yjkp*Apk*HJbw!VHy7V?|qwH}>Lc#m&?e)_eScFf*Q=2{RKGy_uWw#FeU{ z^}4hTLIX9z%pMPYJZ+hxaw!!ip}A+aGJytfG|m(q at LHEjs$jEZowjtE$ungUNOK>G zs;$CtHctx9{@F_5DGhkq^|3grB;PDlikM{FJkfQw)fMYIW%^o at BrD8(GLsBl=Yx|K zPXzAqCBT9#Dta|YKT(LKnQ0=O!l%MgYZWS1+4!m1?hN5&HLi`6Q`Pd*r9{&sLQG3S at S0K;1$ZP0gJy;;6b#(Ezb+|5yeb*ZJ at w3Z zA7Xd*73Z`#SiP=Xh1$nYX()lXkFN?J%iQ*teP1XDcv2>L!hTvU5=Mp@&qie10zSLn z>s137X?D-dVBgHlK2{r#6h&g=-W4LgD4?^~_nCb&y?rwlP%{oJaN>taAv0|oqL%Lk z2JF;s-SP>0LKOY#nSW?v{$%h at 6tY1^15Be%)V&~oQ-LODC*Aa%ln2DR7~8SE9x at GKE_!QvY2vB z$20rVz7e=3ebJjm-|04KOa8SY<37gnt9*0wZ%H(I|!Yw4~KDLy1cJd^JjrXZ!U?t0aM`GO%xkVu-jPJ;Oe2 z1u@>%UyVm&9F0cg#e0Nq;_Z9dXx2w->uYH(tcJC;%B5eB!XH~sMnpk4T0_Y6HrI`Y zsMgckXmxG1S{>+yLf-3V2moUt^iDrJ0U;s)t{|^BHaM(~b(bL*J~CRY?T+&gpQ$lg z*PC^z5>aKmwwhM+ib%o?1rdxuLFJ>HHS|fdUai$OzbOuiHQBj+YMP9YqtW?M{ zPB9yyAqHk^6T*fMKWRO#jaRWLn^X{3Zb{S#pULF}9u`OEBdn3_%?aji%|bPAO&=4;=M9mhQptcS zD`soDLc{rm1v;NLbLuWeqc>FHt=zkqU5rv~`i(WyT-y&4D at ygZ3^wJZKdD(6lC0dq zCd^~*=3K*l&?;|C8+y8qYh+F5dDXHeA$LHfu(5|3)p#z_M$9vcUFBgt&&mdwnaUqu?xvb7+C7fJ)qFxSxjvK%$ovblo6t4=S6S`xm_`7lS{H<`p+ ztI^q$L^|Fd+$s~L3-{GaR$3Ub)P+p_KoJTDgCw9t-WR#D^CZF(Yu%bbb|{_Ixi3N! zrRy1r`(e at +O!U#2&G_Cx_7EKCG-9L z`y`da;<86TW+g?t(8sREdk;<>C`tFW4JI~|I(6DqZCtwvqup~eY8+uB5*;I>{oiekwFN-W{#_L^bllQ=+o#f`ldT~V at 79Igl^v%xc zUYc5Z8$_>JNrg#e+!Y&b!z*rRF+WZAsM7Mbv!QyQ?w2sXM1^Ld!2$d8h${xDUtJ9s zDKlynKqZX(NG8H$yi_ebjVH8y1V0&`s;OGvjdq!B5U9|ax`~WU zNV3L2>d@;nDHt|?Px)3w|A=+)GzHO!eM&p*xZy2$SXoxptMrP7D&VMH29^ebdg){B zLbMUr+JmZVETVgXQ**b!;y{U z+EA20)hv*##HH_PJH|wUtbVhUR;}I}{@4Ga0y|y`(6Wn>$MEm`RI(kI z)N#M|7-r%)VA$6G;J>Q;niAR#c at NHm@)iE!ojtPGQ+u79YR?ab4|*Kw+QucxD;N2L zemhm|nGh9miJVUJlTD1*IU4evs^ml2tl%kYFJ`68k#aI#btv1JHaq%f2X=K0m4B>G zjAeq#*=^DZlFEt?ailPE*oiZ38ogu%ZAe(Cz)|{76n(MWiJyd(*4d8Z+EJ&(MJ-aE zNv%kP*r`3zGaAgfZB7pA5UL++v}3udQ>9d4DJ!-{fkfS!#DOOri%R?gW5pF!F{=Zf zrAAh^iQ^xCWm=AZ`3ZFnQAM3fVqO(y3swnDrD_6DYED0HRn9o-EUsXzyzE>+Xip6# z&d}8yG)RgmnHB!GzGsnI^Xn{}PA~ay8DXnbD*2k4d|Nq)8+8a at lGZwE z$S+XsNmI^QmE5M+GfT)&VfOa?W}Y1Q%ZyC_xI$XT&c-Jzexwt`Ivw4fb?-YA)#`Cd ze(geh<&X-s+OkA;6kDKV13G+Qzt>4F{r+d?5E{}{zbcgqMQkC?9QPdX58KZkl%v6M z>#yAN+NU5a_0*=R%W+?K6 at 8?@4t(mP1Ud<;19fprZqM+0QqOr`eKg=ypH7pHKuc%T z`HhSvSfu@|gGkmMxzvXn?CiNl`k`5VIsV7~fBHwyLF?ErXYe#-#Tjfs&N|ts)0M48`pwFIeO4hfORAErRiQ5hiPeLI zfrE6Z{jG{yY$ziL_$)Y8F}YVHiu1Po{vEhu zwp(l+At)#OBW@;Nm!?H{KL at N|Q~6aSaQH9U2SJ34Vgw(gaDR6V)|}XnenH;cUo^@5 zGb-{sE0ooK at Ii<0#U;+2MvRykzIo9UnUq)i2f2uj%NMKb04lH62)gzEGrwKvHkS+S zGXf%gS*U{ZK)~ChR|H_$f?o^$_rk@^`T)&u>b at z?mvfblv!5%E#vK^%XB%}{Ndr@^ z?T;(%hY at o70RZWxk0SW*hm~Ko$W`y`*n(2W3dK4C8vXFDG_;Kc{SrK=Sc{XjRxxi) zjWIh at Y$taK;oCSK9?KINod(wU1g|KNrjK_O*pF3fHod&9nviAkctRE}zsBZy zeK?8(-+r$W3LVR*m~^sTR4h*@^cb!}E(P2VaXo#8hFh^o#7G3gRgzUa{0M(sX6)+c zo&0%k^aqwYo~~!TZlpZVhX`zF=C;thP8M~pOdbnO9 at f3nFGBpbp-V#XYL3m at BT-qc zfwy`#+|%zcJar0lHKTN}UZFz!uYEj+r&Risf&EHYek)1*!o;KR`MGjbJllS37nue8 zfuqihSNNzNX7^1JJ*gHh`XGirF~-Lqw((@SVY4ew5TgG+UVe4ZFP at g^3*=@=vyu<6 zOphfn>PecTS{G*Hy58)%b$X{+rCBWf92jnifa!JPS)~N at EzXYD%?7 at aiuv98cb243 zZejhpdfi)_L*HiC&Bocmj3=Vr`MS3Dlrj-YwUOoG)%bj!*^*o%H- at BSHb`%*BXO5J z4?!}bTGwY%-eGo#pvXC%@oarb+3V6S7x3r0x9d$8|3=1bc3GFq&dkn|>QL^;(lT93 zHd#uh{&&hD{ZON)Jfp=GnZfn?86VeWaRe#J!?3=mkMd1(6xj!sUo0vXGf?UX3#^H=H2m3 zZCR#~eR)9~{UPzk8Xtv_G*9dQ%>V^a$5l)CHCYmM1S^;dE@=^dXGv*S!f>5lH)7`L zA_!1vya{5;@-46LtXnBgINP0FVSrgG&WvWs+RC_vqNGh_THXc?ZiBh%*(F_p5WLci z9A!P+#QQqJFBSF~_vIclc}8+JjOlBx_3Nfvh>MT@#bzr~*t8_#TWRGPLrGzwWirF zmK4*@23;dv64I60m%+ySn9?#%}#jLh%B!WPg zdFz at ph&SL*gH$Sez&hp}7)}Mv zZCo*W7m;MYtPzhY+l4=L-cx40r=;uXLhX?V6Q z)n-{6R+yrpmx{j8+eo9|`vX5#wkKQmK<&P$^hQzwr8JHIaFWdmB!gVO`n^ftx~!g3 ztoGg?{=lymLnX^<@@jojePry&93WuzccmAZCJldBVXVee^e;{_>kXNg$~ttJ^42iW zuA(v^@>K;`wLIf(&9tVNOWmHPr0;vAiJM}r3Pp2ivSp&t`N~Szp}WHol=)RMM)@1d zES1!Ww}<+(@Z&vs{Xr=}&vaG*Z+V!~z-^tTPVY4vn)e52tjtqsj#x at D7Gp5V+R}jl zGLYMO-}3!^T_d2pJsFJ{>Goz;uoesCT}S;dDJ2szQ=j zM5 at dTZLhRdfjV?#ESjV5M~@DJXRo zlq`K=sLQ05Q4XiEKar8tHD8ux&a^5^#Xk&o!>VZHjCTrzzpsrY25wUtn#oqB{hN4? znKvv6V9z(YXXQ50#p+08g*pwx=DpCS+G;FYnm&rC-b&06=37O%Np-m6C(yA&J4Y#~ zj+yRyTKTWSNH5;X=z8|f1!%*8u&~aPNMRsW=96YkMVko0u(3Ql(Z*E^^0QhoN3T+i zvHUlcK3z}C?#gDhJDYcJ3xD_*7e<#Bx%@w<>Q-6CP`xRwX&KqQ$^5GUD4M}_v9&)$ zI%m0OaBCX$Et|F)VaPjqC*qk}9BYx6g-P$g(j`r$RLjV+qW%?Hn5~beNM#vobz+NF zbM&{9vx2%n7N)f25@|aF&hu(nr)W&uiVPxcp>WG1Np8`wP7pGj=U9u39OS z4yi~Zfr=JuFDg;3*p~{5HcZthOWZcwsSd4Rj|k>1K`H*Kf49UmKgsalx>eeyBXZib z0 at 5P71So6M>$BY=HOKfpf?^S6Mk+P_O;WfwPC_0qHr`~^c(`H!EEF zOYuT1LkjU at wM*@^5HJ$Rc5c5yP&<79AnH1*Mv?wg$AIdzB3{cZdJ1YDiEAnXKyl{Q z4eaMOF`MllW_`3 at uFi=~yMoCp4oF(vB}x+&*bp(Ln}jlKBumlOg|YEvSCHO`ALE(R1&sgbsu>!tpS5dLAJ8_~ z(i;^xO-MsTu51EW8%uR~^peJkKR$)=-rTN*LiZd}T|vBbnQbXzQzhh5D_>IvozxH( znQRXGR;XE`I6tz#wFE75v{Tv at rR`&Ju7a2(FVYLCO5wy1uV89xWc|nlnFz%&Kwm4q-wim3 zlWkkf^1x~nwY^HUiuN++<6nI8vZ76FqsHXaa+ks7u647zx9UF( zDks2H`xEWa={==|dSp(NI|-ZLDf6VK^1PLF;7E}kvHyoT>acmeA5VaJLM|@;+h!f< zT2yLA*_rkT>+k2RW}D}QJlDek%DDCKQX>=q^F&WIIfJI7r~k_wm2h+{+lB0q2hxdIojCIomH%;0Nj7DuIk-!{Oe&Wu zK#b%-SVm)=8~ekYC7IhQO**UWMlDU`*~8NP~Tf?fT0bAj4EnM8<= znAKboEInHim(8Hq8Ao66cUpM=sGxIX6Ny>(94V<8=l_wnVJwRFDe_KhCx2 z=lTDlsda2oXKd5_f1Z;dc5W{4GZB!_j1;!66FP8XlK<4lPv+fdU)nrsu}^^puK=`O?E-9>gr`xec!_kH}KD=YQ?SF4?5=6vdv!@Ti?@ z^h8j9>S-El{ZC!#SLnOF?3+Cx))N>#^U{;LJkybQT-(jlu^Xp*w&XOAqAXRx6HR-T zWzXZnjOkJ_c<6$0sXsEZr?n(3P{^Lx at _1jOblb_B>AoZq5bdL96MyoElEKr=JK_?a zox1cz1%yNAFl&!igPs}#>&*15$t$@FzCq8rrjZ!^1gPPvr^fu9XT7?{9yz+i2p+n9 zp&}jr%|PNhUFwgA{}$L+)f7VfOSa=8jVgHqp0hEv^!2BMQw#)K#L}ZQGv3m4*JkBg z$4oWluoM&9sFl`WJd5<}WC at p~)z~1lZ?Lvt%`3n}~Ng}xdUB~lQIfPj`ySY_G=| zZolz{LL?T}f*9J+3bKjT?Air^Jcf~0k5NPS|Ms8h$No3|;61 at HPn$Io>YL+D2vYtc zuZ>O{T~&LWDGq8TiROLD`RCD~#Uyka%-{s)(QT31AVIS(F;0^wqlxH9_~QXEDNHoz z`h?}O&|NmUrKd5#Re=6_$=qSOA}IZ0Na(*$2z;o9HO16$a4^FSbzFHEQ|PO>1DgPTXQP(iP@&3<=$LCUz^40V9iKzW7I+ zUUCca9c{5o>n3sfUv*K@&s5 at P#svNkp%82Z&oQ;^VR^;Bbph|J?Z7S%(|`3j%2fK9 zfJ)OVwlK?@<5a6o7cE{W=9#o5*pxrO!}(K9|G%!3e{Vs at rn;OpD?HvN;2YsgSrN%s zq~bq|!wL-x9r_FZ?eT$atRyI=rGd at Zu2E7JHj}2&g at djT^w8QmTLDn|mzS(LaUxl# z4c}!G4rDCEfhn1|TqJqiHGCZa_Z>U0?pRKwSgw{dy1FQ6;jqh9lJ8%b->0_kTEk`Y zi&M7-K;kIzlD*VG_jn94jR5q^Cz4Ahu=04SCCZ%u6N at 905NcOSC<`gJJNU{iSgH7r zc%_{}uIfK4MD>5z#$<{$McV?QfJ<%cjdnXCkEyjAvhBwWZmiRaQUHzMyYa_g{RR<7Yo| zqqPoRfMNgBcE?*H zJkWh!z_p~&trqg}l5RrMZjAIlwhsU8EGr^AL;2qfrXDqQv1GO#u>9TRPKY6|L~Y}< z6k^w~mo6x`F8XP^ok*HCvgv=tG_!IWaom&2blmOn^D?96oh;)Pm!g&3D*%zLYom<{ zQdFCxNveVF8WY>F-8v>7YbJt7 at PDtjhG?t!jaiLGivDHk)@`&_&EVFUC0aNN_WOZh zP%aN$?mAl)i*T#A0LX>Zf>QU;~O{Q&2S at LAK$nhuZL^n>({Dl)z$h+|MHb9ckbNIw at 0^b->Poa zH>(?i>o<65RJY(+i!2(zODDIo4n)A!Ht`|(v1<9rR$?> z*RQ2(!>iY>U5!`ASFQ#w9_Zpb at lLwkxm6*@_~wmpV|e|B#@Dt)WK{p^6^%=m)9vxC z+u?S)HPiz6=G2YkGKXt@&3lbW!;C at CqLo7shpMc8z; zdnI2UUovp3Rq at ibgj{3I(_CJgdYVEBVS$whhwYQo1x0GO?XMV~6oIOja3y!LCTCn=XZmB4)fbGF)oc zHDOyLnBWdqI9(CoXt)G3ag ziU9(V&r~l{$BR=J`sXj4KQGUeOUqWR*vW?y_S`Q8Q*|MokL+t2OWe3hz}7YuE)UFs zf(K%`==jn_T;js`;)TpLtsqsgK;c0)05bq0lD; zXzFO!jE7cZo;PB<5 at ci9e>vPMxdHq!EPkVCgQTiPpOmFQP|#+oXh}9I~esxS}~fK1T#9`+{6NwEGB#m2ag>9r at Pzp+t%2EM(=? zxMA_V)u3Udk)=96KwHH&Q9iS4ukn*)R!UfDU`p2K!@2QUxPpnWRk?StZJyxPDf-#G z$>u$OE}R=kLyMh&5{Y%$xnhy?0^1TIONR63(m7`#WCCiUDk{kbl1-fPgMk4p at RHU? zIrr=8a!-!H*nF|M&^cdAOlL*9+F?a#ukFb_N at i$GrSs>SbHlUe&LUn$ouTh&m6=(T z!v=i2z&Cjz%1g*}wtvoUTNJSRx=oO$+l|O7tsSt0q)W`t3VxsC_ZEUUUmnP82vsN= z2}Cd|A$)ZHdX1npQsf_dOzwm0 at 2l_Px1(>q{igcHRQmdLcs)RrKmA+*fn*7&G-BoowOsI# z38IVQH}UHcQ>SuG{J93U=7-4G;dkFl%L*;y*TdJS8D95ay>>Ngq=+_ce3QTLX(;Li zFd2RG^)~@oG;Mx8e)XsDr}Xpar=NccKZPGxe!?J}0 z>gV`#{AsMw%gC<5KmO1z6u)m!HheSwmK8=IAHRMlTwcB6OW?N(habkWyR=TNCW%Xj z-vFpgDnMVe+AD~Xe;Tq9!-o&73=)}rDBy_L8 z>j9 at G4oqCVPOru<;ZxGAKm8a1zj`x5(a1>gDSbVb;NkU%^_lZ!Y{!NlP3{E60)yW& zE%Sb(_t%vPu<{a#e>NUaR7obKs|nwhKboLUnf z%fJy{j$ggxn`B8W(ek_!ZHiOW-{h|cW|RCXy&S(_-ulPNd<`qe6mnd-;^1r9ExpPw zM=yT*v2w_Qjf~$7HJ?U02>ErMtp3xF`NzyK!{8m}m4%_~w+W9d{ElXmz}(@*%F9B( zY?{e-86hj+lIpKsy^P3-qt}F&&iBbQ*BDu4yMmOK zOyR5k^RK=#AI at A5_)10(c~8w?k(aqEAd1hkMW#^nZa^uwk>2!O1r*VrCmSstF9mh<*$|`*Yju3p1=7%7pE+njayb>Hk(ZH{P5LSh6>NeQYJnV zq6zE+&e?!Je2F$V6{5jrh7DPOT|}@p*r^TNsM(kQzI+iNSbm61f+nNt^Jn2%_Gk_uu&*pX{a}vW4_tTL_g?XzB!OLC<7(I)28?eMN?rJKub* zuvy{Vnle8hYG`^kdMX(bg0A6Vdi4%VisPbrV0^*3dNz3a%&u77&E#*2=*|}TgfR at N zKkG60+0&^xL!ZYPlrz|%GZRH8iVAQ4ICP@#)Hwr%%jLTEEOCG6(eM5{228 zI!}9`^@MkL;tgjhePFc at R9u1DAe07(^rxd}kXn^`^@o}|B;yaL*x>c5)_pYApl&IU zM7fWd4^#z+$|Wi>@_Q=uP&35J3wZINX?{%E2^C4Q;tBOfzo;GsOIb}1wK=8KiA}fu z1A0=rL{>P13pzgE)MQLowOJB{nXFB%)rII#R>oWAS$UaB`%EiE!nca at N2Qmj6*>Bd zicr$EQ8^w}Q?jZhGZb{79ztnko4uPY%`zpZ>7 at S3G>WALNrOu11MA`EI{opZuIgZX zq(+~b&il!BsXRev>uta?{n1;~m{q;{f_3=R;2G60vlj2Jy?3bd at a&MMerD$Raasa8uzGpoRH*GEV? zft^33xX}lK=YuKKps8otHl->rGQO(FXc$#(Y>eS&{dv;|D_*_Ag)L<}Ow>|mOjp*< z48x$(75)k|s+ at Iu7ejiLDMU7_TUnlDY@<$*^@g=(o+ at Dk^j7N1L_#WSMXLgI8t+I{ z+nSHwndJKR(T6ftqzncq)K=&V9HzE$8k9m~CUIAlMnGWFG1gjeM*P9rhN}9Os7F_9 zhXWy1pGNj{YjwBP6d+MA>Vc8H`S5Q|8mpSs*(UR;t$_2E7#X68_7&FX6}wPK{aMX>JBmWqz0I{+jcC6N+L*UfQr^?5N)%IAY9c2 z9ORJmVtWJ-N30UH^>}84g$d#J7vlgbV at B6pJ98ElXj%f$~?3_lhqOt-S;U4g>P)XIq`o(vt=B3<@hg+DR&5(WKRr>t ztTlx|rRn{QuVGG;w!M?4y!EZK)rjimE}fqh6TE_6+rb?}x!L2ksjXg=&f3x2!d-?t z;>8&t>sHu8r(w?6Yx`>4YpukfBHy%`oI#}sTkS%m57kf>^i*A}D@%wJVOw`=vT$pw zt#@3DYZ at oD9HBt!G+W18O2s at c*5{;5AN*4MuXtc~!ec#^P***1 at K*82mig5O?~1$X z4|7=16A05R!LfbmC4Y?sU)v)LE6ABOta0fv!G~|%Jj{*!6$+O2>jqYAk;vQDF}9TC z`hScVd^4dfbTF|PvSLO<;f{(Q+472+xRHSCtkJ5HnAUq(!@`H_9M<1^lrm at Ante>! zk`b>&d)v@#R{pkiTV|Mbim9(7l19{*Pt8s at fV;_>Y8GoE`Vn>{dl;F6&p%_{@2fEhWDu*S~@fThKnGbNFi zEuBg>)p9xEeE*zMR)|=iT{2TJUij9w5GIr{l>1QTg)BlmzhuIZ&X55?jbs5Z^jJm$ zR%cdDpYdFNb+Ni&$)fY;^10^hGD9`!vriqa`i!N_m0amwSdsh^1q+ at XDpwL6F1JV} zcAN&`@oAJ#%ErWtvCV6Y&new$B&p9P6Gbb at jANN4obI0~luF7Q2JCM6u~2AIizWdM z<};(yXHJLHq@w4jF at fq}hBd5=tIUP?cf#YdRP?9+L+`v*kc_PrK zXKEjv811GjnVD#u&F4Cd?F)VxplYW^mUwYR^ivL8smYStldgla8ZL%A13RZZp{;zH z9HB9{G1|#*k`1fQRHw(MCKAB*{*pN_@*=U z>3B-{!9tLHu4k%il(Vpce*!i$a40{j;km?9CXsLPY+v4Y78_tRCXQ!%xT~Per_-re zppECJ&SjgjHY1;bZwXz(spZqBPM!GTK;TAX at hKz;tsnN+(C&S5LP7zuT_7w&cY3+i5 z91d`Ox+mREokWyyQhrpSTqaNqg>XF~M0F~iw3KHgk=ZdZ6SIhcElSO)cyfFKWf1lJ zISW>e4Xi$ESZ)B60uvko6e07;Y(a{VlC7|yXe6J7wAGpBbi>k;tmJe(7(j;4h&>+H z6{O9RvN zGr@;}t|V}HR4}nJVu`mRB&uRI#>W#w-yam5-nw^qq)%;tGAcp#llg at 6vPDuJl|Fmt z$l$D>o^kf&!lvqEIuY2hY at 4eXTDZ|h;fVr}`P4~`9vwfy-d-HG at txE|UpljFJdm$S zmT&^EIx#+O8%iV;$N?HGAj{X71{6=8VEyA|eW4$bA^Gks*yEXu-7&eqc&G6vj_ZZ# zYkZjy#YcA}F#aS!I|WZHYorCjvX#mH;&pW<5q at DvmY0gJ>BN#IKEZJLQgISj z%r?GorpFS}t%K;YOFEH|Jh%50GNZz#?RU`$i08>EBz|==qUo~MIDVWp>O$~|H?V^- z+MT>+aNp!P9*!#%EZZidojVX_oV${tI#C^mw-c|WvpP<#PRrmu^LT?(k*E6D8c=q+i~Is4Oa{$d7fP2Zk%9N zBL^^!oIa(9IRHbsZNkul&)oC#0a=S at Pn)OlN&K?^vv8#&G^;%guWVXf zRedr1{EII>51(@!WHo=GoI!eS2?_Ei0k!xOZ5@@4umeDSN$ zlI~wx^pb;TE-=g-sQ_$s{|%7{w;=I8aZRBRW%T+-}c z*z6S}!=nh3lCwi&l}i$ZqRF1WT(P0d96yhb`;Q(!dZgr_Y^|)z^JkTfk7fS!WrA`; z$q_!!%szh1Xk|z&&zC*n6QB9`@MQeullUY6Q22bTORC55(PX?5L`J7*`KqT+vdI+9 zT){7itdbsumjle~X+I^E0wjNmA>z}D+0De!SeVSj at o|1MfZ at t3%gA++$90ZhE~7IV z0Di;B`bGU!&7{Jmf($h}RxCSw^!V|k_-N&!rPt&to?C604b7fDNtgvO`D&LPKN7tj z#fQoowYgZ4nM~zVSrJj?Fcm=LN5hAYq*B4oC&zioIl6}@L-mQGO_u6ZDZP2+=G&+j%3|!EJ^#PJt at I>a(#%F+Ys_-h^IWP9*!P7 zd=MTO`K6F at +xvw=i;LVR7OY;?ho!sNxv$T-BF=Afah53 z=sBf`v>8k12Lls5+}Bd_BMTy8G^jhJgTWXd)(?KU|KQ&Jd-vPWpIYZ15ooo5`-^~2 z4+oDPf=hak?}vM>-S-=j)LTw7k!k|`nCyEc!{ShPM zy>Qo{2=00aAg=flJvK|hkXkp&T=mZ9zk3&Ix$zZgJt~y at 7&mDwo`IHes7MhvVN=o at gm8R;faw^>+J%$EO;@0*Upd#n-eRd>@J+rrY9 zWBHoIk7dIV4^hEg4HHU|-krO5 at 7%tFEERs)l+7s+f06}?bU)gh1IGA$TYI1D(UyCY z;*vEkneH}s6!o=B=oUJCzDmEs3%MeZ{JzN>P0fIsrk?3q&P^~@zB0e zvm0ec^nFWb%V4vrI>jw?zR+`fUDA~7%S`F>+-;j1ht2dV-NIX$gOW=oA&_{YpVZUH zzSUHM@)}8-$+%d4W{Pwa?Mk$!b#LMM|5$5UC)T*x(y!zoXzrx+LahQ_CZaV;su4{? zOUD=1Eg2j#E!#@Yg1k#Xw80Uuo9oR}%1%Y{gKd(Ba$b6Ymgcdl zRWwK5ve}!DY_qH%(`x1}v(n<)prcqA7gSpkx>eSz1|+vMC%;p zudv35`BO`G36ff7(%)sXz3KI{C`a0=zG+OZ6j|8%dkBdTOuUFBY6in=E6y|PW=M>w zU5*E5un3Nut(^sa-7vS+YES{fcA^!RYXy<0 zh?LOKyrYpUtzD*Czc*%>7~|-adta;T=EX{*V;N0mS}|w}T$r|N^gvXJ zn-kQkQN at zGttnMctS-6RllzF+!HEXjHbixQU3_ zNOit#mX}M6Y3Y|YcNd$92OB&VbW+QOu%%O&$c&xE(g at 9>ji#gL7hj-RP0$ilAX=&Q z at 8YIKyvN!b at h6@@T(`yl9RM_#*A;P-aix5VMJF=Z;Gh?8HB*Y4r at bAsJgr#XME at Pa zhQ(ivCx~Ejl-~7&O~`D48W at V6#gn?~6vbGtw>P~=7cl(8N!~RYhRuB~UMOqCh?{3z z2E|^n8bPK^&|>1I%*xgz<(kE1gJTn}nLvUNDqAGyVn^ti`zWq4TE zxV-`~`gMy8n-({1a)G182(vPqg+QBSMjPBLE at _o-}a0%s3`GiZsA6d$S! zODPSwBz4<;p^(%7EKX{n1Q9lwai}JkHmy>rj1OBZBInI-9V8UvFH=_L)9M;hlu>G_ zm7DJ(9qZZ1pjp_ at zT>^r#|y4iW^am+TGR2eZ;EM~%vnr^;ew%#024M<+D%zlla~JC zi<|N$sz2(MqKFEr3KcSXa?Z`tq7a*RHx*{;zE5EiseC{e3##5G>t^&Y=4cLm)tf74 znMoHwzHJjuwMg_W@=a|vS2v>7(nDR>Xu79Lky|unu)7SeLn$|42=tX)%_eI+%{QG4 zsn1lW?FqZ`G|CJFdk)?luaCu}!y`wJ9;uFaTGvvZeNU-G9)2&MRQg4)d^|I_f9$A+ z*GIw`lBr4qDz#Mm1AZG9jt`F=KYpw_mXH4B$k8JjedO?w!-siL&cpJOfvry|Coon* zqsLWx7 at v<-E=oQU4 at dH!)hWvckWD#3GANN*LhS1JzcKdM(PKyTX8xl`@{vCCBT0oM zq)VCFu28O|l71T4Flq0|VWmnKH_5k=n>(3LymJwl_xQ1h+~Xrh_Ji| zVChT-ic~}-+cK6#($SIO)j2#qgx!>2T{(5~1bnb8ATppr~gDZH)BO_p`v`(<)WtZd2%j9laQGd?B23%#&Qdgf0D&aHIwd#>PYagNII&Nsc82>24d3_uWX*1g at cBefX`EJ9y~eLC?o` zN}@|iv3iwE)#1*eaBzGGdU#SZoap1>U{FIQ=SOp-I$RwZ9Ar9%d1?^8RTx5i6FN}y zFhq9kt3&Z%U#lu-+c at RY#6?PU0R#|0#(XFp934<@+{{}r$cAhx2B at 5HFkgfQv4d|> zOhE~*o;;B;gb;wN at mL2(71zpOa61Ul>EQUl$r1co_YeDqji5DF>=g(K;3!;s|z2M at 3+Z!y4JDMEpGBpj`X5l(>mU^w8R)l-Za0K(Cg z)fgoi!{O*=Z5SO+%7cw~iRn75RtPe}jOk!HFt9}x#%cp5O!%S6h)#!qG#}FN2&M-Q zgaZoM7AKw7wNyTOq+2AB+b&7_);h-%Y}^5VZBfq2^$vwI|y|GWaGe3X_m@ zXmt3{!OE7-2eL6vH1gOOz$X+p at ulOT>R=x+4<0-a56G`O5K|CZI6Qo)a@!4TM8$-i z<8q2{WW{0%PNb!UH!#Y|4Vn!1AJ|W7-qX4b6Sg64S?J&qhYse0`M_ZR0mcQU^w=GI zufd4ex%S8gj#&b-zM$Ms at YYN4OHS=Fc(MW&Q5G2M17UyffYRp5Xv@>GC=?i-O8hfK zHMU~x5D7g0oltB|z+`{QM;e??u5s`{V(j?9{??#EfKK7Sct2Lk`)&V_ zZZP#I-$uAc0k4vi9Qs8lbCq3q at IXE=VvV$av~NEW)yEo;^Sz*8kq!<8aXQdJj`1-7 zNhmnh=20Q-0BI#}I2aCPAkO>KKF`*h|7w_RjG3|F(255=`AdDE*&p@|$|er?iqCj7 zmzhh5F!Dr#mdRlZIg7gxkKb&vv79^cO-=%YE2aB7}%P#mqkR zEz|GSmaRLYWmDE`wCSINH09_*Sqs5b;q_sd#>Zr=3>6df#}jnfn;#wi_bn zbQ|zINEw8<>i)cMuy^0y_KISo_6pk}9FgR_iY9zy4Jq4R76_``^T<;Fm$nPvG33Jb z<+Q1>)Oi2CN|(8fqj7jR*Njha-$FG|mJ-$O(xP0qhCBRKpn!9nm zeO%x0xV{yk)h*l%lp>=Z*Q3Y0l({Hrk$AuV;Ql?*2H{>^^it8h#8~RqJ${rKI(qZ~ z5W<6HK)7!ihHRi^WDQ<0PG#T%SLoaU(<{|cA#ioKx|0!8%DlTt^)RFaQOhYPy{5ED zgId2RKR|-OYVfK08+8CRq1B?I)>vzZk{ppWl1)DX+yrP|Gp=Eo{&kkQPHMV-*n9Ah zY?6j5MPyI^BV*I8zUuC&kYT#9ESaufLnzNMDAQTp`$cIBrdHNM*#Z=uT(eqw%d^?_ zgIcL2h>_4%#E at YqfQAOujS5JV!M6NKu*6nSVoMpF24PfzQPuEz*EE~bCazq#*C$!U zFj=rd<~XU|H@;^0m6V{*spQ;PbG|jU2}-Kysr;NQW0laZU60o*s9~9{V05bra*4Wj z>Bdk{BnrE==}%>4hL8gpqNh$&ThdI^+)xd%35zVPC5yO!R1mZR0H<#m^pKX)!Kh z=N^`m;R3L&Ftz!pOzqVwDVk`Ngt%goeob&+M6JQA;E)lTT4dL*YM6Wf08s>S0Bi9s z-svHdaE_*{*Sw1ulR}iLnZ~t(JKXE(LeqCm3d&jX?iNMg&jisVf6cf8bx=Y!|LW5z8fGSZ^3sf;ma+RuVrqL>MEUIKfSb?yIi zvECu|X9o6|ni0Y6m9D)*!)w%)x_adbX<2g&ktf|XCF7l6ixu3d11w6{D^^kAJYC_* zR!OsBYnWrS1q##k;WhkU)vLTB7&&6Df%u-ebW{{HGFt=S%f9j2FD4U0X^qQl4Q&Jv zF~VwmuFSbB!#!M0RC^mcpHopdvWZCW+8i|?x at x#tU9fy5++%ct{p}v&o79A);kqiN zd9lK9Ip6C`=DT;aohgJjH!?t|sH4g`jxXa#O5B2i(>4&7O(!p_mne{DW$9E$R0Xyu z$<^+Vu~kWdHr-k>9zw($*R8r~;L?HBUe&c!%(Os|;Y)E}d?e0XR8$$^# zTvX#$HPTitYgS;3<^UCARXfr)hHK(RV5sVz^5G$J<=SofRt^^&lDU;v0QDlKs?*3P zjQA`QkwRq1jCZq%)8p&xY98bq1=Y0es{)z|iLKeBmc)wN2Al2M*R at O4NRq8GqCQ#` z0ZluD$65CmDHa*iAOxR2x`bN3Ck2G?f}g5EXth@}oUg8^?Bl?8It9#RI4Qagv|Sre ziBV1m`^*?RDQZ`s-}-c0Cr;&gD?ak2xL(L;I(WQ&nD&Jm~72uWb+7#W~45}-v@%n_Diq#>_ore8e(pvdT zQK~AGlC7+)ojyX=ZZTeaR&hkUvLGlS5)=N#KhSl-?p0(T-9&_2)JF9np3Nbn%HXL! zUB0CHgKZRC4c{t8>HA#eRba1NR_WV!Wp+&;v at N2Z7ASEix1p+(rwvzn=qctDCGtgT z+g;%Hqq^0ANL!S*^&dwu^I(aa`Y?@xM6q+S} z)$z+7d^2oO?}mr>VN>6qlTR-wXVzHGm-&| zQw$oaZB=){GEd3mE06DOVPq`Ln0Vq(!bKBXBdnxuSb0HBjg&+nAs6|o^_+gwVp#m0 z4<+BQX`x=AbrrpYKy<}w>7*7}qs at zG zW&(FTg^FIX!U$=T5AH04`6Z-XShQ?Q$=oHDR+}>*>uy>x?ZCKU>|Zn%<$SpP;rTPm zWp&jM%$mk&G<~`fmk3Nk9z+^KQa4A_C zT~cx=)&gu3`bDr~4fL at Ke&MEfkHbj#&_ga2?k;vBU4iHNs{o-S#%mN%s2Dv7iVNSz1(t4RfqwT$Ep(>PLn9uV)(@Qqt^Z*03x-Gn}Fn+2@oSS~YJNNiZESYF z1v4SiOelmk?4sIDGs`Mm7aIZ`~&Y zHVeOMylJfOX?|&Lex5gjQJeT-5J at PDk6KL}i3_#lsac>PButCR&1v`seYvW;kTo=+ zFBS-Mqxm^zma)nO(>A%w1PSGt*3PXfS?1>EP4cj5f)J}LwBb^&x!!`>!hCl=&Y3fM ze?Gwj>Oj)(bqLVBb0}Fs%{&)zPw?U0_AMsx`KZZW|P& zpWbQ&oN_ at WhK!yU*3S>o2oy$hqV{-kQ>PVDhFr&>fk`RDrTHXv7B{VP3A_B19Yy96 zcMQw%{9K}s9uwJYc(g>+f<}qXLSNdb8Q at sP@;-anTlH~~1 at H+*9_M`6)oeO%sH?rvhR-GDHLfNM45On8O)ZSA3Yj4`5l=lB|*%T%d3*qUxWyUj{`6TAl>fnjvh56~Z z0qV~YP3wifnsHrB0VUER;KWBeb8+K%Ze!T!cQ>o-VtUCy+f&oB_Uq>7tji^yy%Ado zEA`xj2`z`(cARR-Y0x%vf0i)3WfH-vF9n^b%@PC`=4a-6!UF~El8V_0G8=W+L)n%@ zf at -LT=50o)ul(16{b7G+U)nqPWG}Du$!O0fd(xiz at Dh*0m4V<}$%-T%={NhTz2i^z z#=W@=9N07Y)!bJ&U}>IxabI_D{Yl<4-c54MQnr at 3Rz}DYNlN#XtjTa6kK+>)hfn%@ zKlz09t36@&V2^S>dKf>H7@=eoJ&~05_4bnCAiI!QCG1)HWY3=6yh#p}$M?$pkOoxN zDeUX*_rdkv^huxjd51l{-FtTX1(D=83y|KDR8hH{YJat_X6kBB+HLt+4kBoj=Ts|{ z9PeZ5xUbne0yfF8A^|j|GDOO!lQ^tP`e34B{A9fMlkmv^2?TT4-Qx`{FeItk=KA(!=FUagxMwJU((VBlY1#p at PFRrk*ZYPtX;Cz? z#0Xj9?zg*k at 7}fBH1w?P6e8Qu%Ail!DC{1WnRo3%pb3&Dh+!X~mmydgZ*zM|+*RZc z*sAc+^b{Ynfc|8UpbNYDyLYW2Z-xN{kGyw9rfGSLBm!_n;2;f(Rz`v#qbTVY_DvP< z$$OgJ)vn*Ls?1qIJvm}@Lj$%Z+r2Alw2lFghz;0TEXfenwW5kLo;%_1(p5RCi6UJ<1Fq;XyZV5xT2 z2Py-&hQ&4BVJyr1XoNy>C*BGI?ycmQ6=D!I8!6)_!#yw};U+^#DTmrs?fR7qckbAM zgX^*y+-gPBKv;6Rr`?@hVdq%Wk9T0<4qWf=XX1T(&V9`NZV@`rO zo!to(M?3VxrFN$M`yBSPce%KPDC|+iX&HA7#D<-DXWHSBg5wejRt8>?8GjOb+6VIP zsaY91YI!sQAVDf?tN@%*O)Qgnib(CHM8?Tt7H1NKF+*v|ygLJW zwIggFk@#IQ?b$`>kcl$TCXL>)9W833X8?P)N6FP;Q)(@lcckq at FkxK6 zZQVcGKoE8<%WHY<+F9*vP}I&5NLCH?U8us`m1&iBPVI=>#bgchcu~7Y7xr5q5)xqU zG_7~6Y~Qhc$BymW(Th#lytD+)xM!-UlZ`1gvzN^nBewp=4!CO{LXvx!H8d*>B5eIEdY8S zBTU*m)>)ub?&m!)!qIeKCZ2*WQHy~Eru``(4V8gUMd>oj5P;RYsPbe>R z56%{xrgk*j^T%e7K0mgY+(A}$D1qfN1YAqAj<*vY^7iWEvhk$CIo}-YUckr{clIEu zyu8Ek5%R{5$Nz*?+HFu?RO3Z)r&nXK7)}Z6k1PoM4G_# z^~d3#64A7T=-O=nJ+y~ZJNrxy^`ab0;H8g;|Mc-cIW>ZXVC~x-96A?_`!bsb#(#=8 z%Cq%wEqIdRSIg^pUPif=#KY{;8cGHAtl*HI;;Y at xr16{gxAFpAUJHtjFrVGHe&f0l z3F;7+CM|0wb at 57}sX$4hGOu5v+ at 3$VZ}SCAlS%ImxYEj}M$>t!^X_AzT#8R6l%J-?pkV^1S zdaC5cJ)0Vp_aKSKw3Lubl*`K$A+vgGs&&+sBD{_y9c61TUkPN=;+p!1=|aRhD)$!7 z)}K*8?K-KIn!L)A4A8a?YOUE;bW>S6tJHJ-S}f-GCPo#Lx9!$c)UiWJK(FZYDI;Zg zrQRHJ6++CEH|nUy(-}nUlhT3EO7_usvh6SCU9?IqzXlpv1u4~Hlc0edqO3w^NL`?8 zPV~ZM8P0h{G}X*%dwYr88eg%1wOAsdrV45Sc6l|w)q;=IheEk at r6i23Kbcpfl%IL= zJ+Fh6uub~(tx6GD8vCk}Qr?x!$>lZ3s)R|>hp)ut#+-?P?bVp}pRm<}qJ zwrsNtAWbb08vtzUP8L|=r_?5srLSV?5%!xjY*dQRD$B{<=E8DGuPH^fDz4FR2gfTN zwhCIYMlXq|;VOUDKYd0A9o96PQ>n&xDqhITi-|d>yq;(EY4p-X7OxuU4UW_c^_qC@ z3IWHfDRQLpS(ze`SE2*&tvThV#a`ZVOBLNHqUqJ&(r2|4KaJYX!*Ka6Mz(^G0ySFk zN!1LbuGgfXmdZP+WGJ7|h^JSho?2+a3LG_0LXOusgmj;&X5BlkIDHEL05LT=YZ#jp z$QWOuhsrg1nLIv#B1htsg6D9nQqfbCNL)bWFuuGPomUuCu`EF=jyiqX#%TfJU6fL4 zDxUEwgQHWjosyvRMw2h7p`uELs!yP5rpg*t_fFw+welgS^35IOwFa*%ZPnVOi5r|c zn3C)?rAe#PD=c-{L#GEc>Q;JjQpNd9k*$i7dJWaM6q)3i=E~WE6P at 2M@}&AzI!%qn zlaO>^lB;E;=xNKZ4PaVdsPG9LmS&@)_nB&ZTA@!55H%oLS;bap at yP2|j!w$o)i%*w zT}~q at +IpJ$;7 at gFS%kC#oRNyAiNbqtsRKsv!s0lp105KuN_(0wSv5?rhG*4l6>h2S zr*H>QRB5A-t5M6#q=l2EWt(@xv)+S7Uk_8F at T3kP^)UIEfR0)e0{kWjPRi)Dbg_z% zUWT!E!YDvNPw#}N4O`K=#sf>y!tw!@aB>;0n9kIjq&TZPu9rLvwwbNgVGHHMK(Tz@ zNSDe4a;*;ZYFI*bAxk;4%0BFI>7sJ@&T~a$GfxJ0ucjCIC{4CHaLlBL+HQ%e>D#kH zvWhldDNwZ$%crw~3Z%8o0y($P)_FD7s1Dz|VmH<96KfoQ`v8u&T9kd|5Sa+5Z4488 zjW_DNdCg3HSWIQuY)>tH4$D5QSg+c-VxvBWHa>3m0t#jVySR?j6ay!f8uO`!k1(mm zZVQM9$hNw0Zq1{QeJ$$~Qk?SFZH7XN*E?1nN-toiPpj$UKYaLw33X>gkxp)*tiGgG zmQ=0fQnyydNcO2W=EG_FGId){*KQFNQc8VEMQT+}&Gx~X8g#vetJPUnNkEcoK@>#^cIc6!6uIDhw7m{vJ z#zD!Nv~k2^_AuA8r|lay=8c058#itsSD?cc3-cXiwirq>&#vh3q`jvH^SnD7|E#e# z-Uiz<{f3;1G74i&YiR{?1tbjWjpGgct+!#Lja7CnS`w&c%64SrZ~9lgF>K)P8-|i$ zRuB7m)~_6i&5E#yFt at Q{MDl5W-LP>3LzNrg56RH8^uYXFRt5xdC3IJVxr(r0 zS)ELZrk#}`nsq3Z!iKlfO>Y-sgb&@086!b~W|}!{tPy3BTx#(^oQf<u$avB%&n?+fg&|=>y>Wz4jY-s*A8MvTj&&k~0X(&#VU{poX{Kf^YZ|115PWqu zjNab>0G88tiMn&p)fURONN2-%;|6egpZrisB{=~tV>PvLS(W!O2(9t#_w+cviGt2?->-8s&k^`jfQ8| zY}sdHc*oPh zD6gqxox>#CBEr;$$c*9r)!4N*t4JT zwS7BW1(YpaJhic at 3b$%WH(nZ=-=<3^#lJ9UV}pVqydV$`+q5DKFicq$lt-(f?d6M3 zecBO7P6U%-CPRYKOKI5U&boQWNQHCrbZeUy*M<3t$-j%%>MF|lAh)Gt=IVC0bD4hTa0MVrn}6zpxzw{!_Er-A56u^2;VXS$7;M!kw?eQ7Sq zAY}AZUlAUtui2h+!#}##49>_2i at YB1tE%4LcU5#QkV{XL}p^T{4Bid)mMU&S(yl z(&7%|sUMrOx->7L zYgRAa7PiyQ$+kF$pt$D3sz{`VNIC2d{%7jM(A#nQbcbyztxJm%(Oer!dplpr z3t#%XXw7obno43=#%Lo+vL>n9VLO5h8j{3cDe964t|06|mqhyvZ^$N)uziLZI0aYI zG3k}1%t=LLPO42i0qofvQ%*!@wr84w?2w^>F`fTuoK%bYi^c=z5GQB2fmj-Pu;ohL9tHRSvf# z2N!DWsE601H?Lp6dL3TpSL2tjUcHPj^WP1)36C9hz}mkV{ucY#gy*#FO*9~V$DMWU zXTNzJUstatFJHY(FS)m#u($4r4ma0pY_5-f{T*6YZziwbyiTttuU@}OuSV#=nF3&h zs_c~$LaS}Bym}K~5783usL|2%MFFINY5o>i!wBQE%bb{)l9KT&aFSl-m*W>NUxF5b zTYz^6TZP|zB`kpVuYrCLQweV(dRDIooCnn8m!lUiU%UXOQ328uoKa!$uWw$z!5c3K z=~aFON?uK0z6dWSFJA~6(D3;;H4uUD<_x at kr}pu6cokm%s|FXh82shS7nEv6=mAo7 z*l-My;^*j6ZC*F8k~nw~UkKJQP|^XX2$2rJ1CfFNa06emq)O68uP_DW`a;O0kO+f0 zDq##2AQ$SPd0li6%1rfd0Y(y}rOu|m{;q;>((CwYN{Ldz`DOLOC6mqrJyU`#9F!cZ z^?+ssI>O5dm4TL7aHPJ}^p z-qdJNy^Jp;_VApjrm;grD9;QfyqwCYUIYr at Idn95j?pw6tuYtTK}i9U$S~dM%U40{ z1*xCphUa6b2?QH1Q-9Z|PH6?XSeRxCpgBCBydc>IOd4303{OgL<{&<`gl|9)$!H4C zVZN#G=U9>uq8j3O1u-V0Dr5Y_#@TJX3?JVBNus>rA~eeQY6$({9bf)e+Sv0KFP`fk zS*OWY0Q+Do^jRv3e0MbX_*lY8K)a!rY|fY849(huO>2XmTaa4wgS|X(G5#M*j_$R;ffy#y3bpC6~=Y;sM2LLvMh;a4C)Mv-Zx zv6p#45rn`GdxmPLO6Z3ZQRIj=5{h|w4KE)vVrCOy1-q=;0#J<@osdb4(GQO^=_&XJeZt zycv*X_ZY)v`OZ z%&vm&)Eq*4I(>$vol9b*h8Y;00renVR7x$fBHAOd1X4;MYz$GsYi+I09_>aD at QdeW z9p8?hJ$=ge8}!JSf)rKIMe~9_WsRVM^ejFdK7)bMsNFgmL>k7={GtQt)z8vXsWrR~ zFoW=FBzv#T8iyA7;`0$GeSTaWL#I!lJ$o9T7E-b7YjUHtvgI$B1u4`#r+PY=#UF4I z!z_)BemiL=L*h5JmvjC_YSM>a-~m_(PE4a at -1A25owcPveoV;GGhc5`1t- zxiLV>ZQ}6=RzQQPo{o>69ziax3wB)6uKq at vF-<kKiB zymIV5g#`T^k))1mrz~3*^_&{oytk+m?64wG<|v$jwVk?MYaW&OAL97B$wu59*6L1x(r8wleod~u at B)Rf^>!L z3dT4GFBp(sFv!7=%0npI!S at +7T%FNK&&QPuusRA)@VLaMM0r2FI^Bj8{Z<^@K!nOLEDgijFe=-{>MDdx z3LF~#Wynsv6p1$$4OFr2zeS-KmbgL%H-bLHcL320mC;38`CkUckQWjxm+!Db8!HvD zrVz^gyn9O!iZ^)?z+nx)SzR!z5XXu(xKdA;o?p8Z(Y&AmDvd^#QFf4(a6%~zhAKfo z0t=>t`wzi5-o!M#S|DydLOnB8AB!jzH2p`=(gJ$9v||A~)^oF>W|c~6Vc3F^cxgUL z(~4kmOe-i>JTHU_N5G`6mk0w?GW^dkUy4wETR at REHqZeFoksvnM4`|GSkBRG(zKdX`HlsuFkr-ObtZ at O&kcOm zs+w7`xrAC>NS1d%IRSX&FjfKVOdf!$O at D4+BBx%t=udwVg+%D+3%sdJjs`ztwXh23 zH8QgAk=6evKzV+{7K&NQYx4P`msYW at V#zX8U!!0L%C7$tC^l`|B-EbS5+?|MKD7dT z6a;g{bulEdG8Vuz+dy2hI$X4H(u4$bPx!a$a<~@Dyo}WrbuE_P=A{CS35JL=qJJLJ zrdYzOoIM${ZY4`D+MQ$_mvr zBaIoOStUKIx61N(I+RkTjGoLs{G$RrgQ_8&3LX%`Km9i~Mo*xl{)aw@&><71JCFY! z?6X26N7iD%GM5>WjqSOG5fW?v2n%J=!BUNp#l%>((a+3e33)^3RMAVAvAePF(QXQ|B=RylA^5O8DUAPOxS9_uP6o6rKpsP zuMFVfAGkXHGcBjkVbPgB4|>A6;)pyeQ{glTdQcIp>1S;-oY`t9CL=u}nViQE{-_#= z360xoee4lb&ki+e)r-DV{k0%Z!?`Ii}9*&y@?k^?@}usnq=Uv=^qUv%};M^w3IP$GVGPkjI%&2GlH?2O4NJ at efK&CemN7^ke*G z*s$8UzM|kH6ssC0ofc80IvjsvJ=2Vb9Iy4~7GpNamS6p_0<*<=97PuwW>utAmr{$e z!noyt1J4riXhrc8v)fJ_Pb_~=UxtxM? zmE!oB7McJ$+9I{|Bv#>T&rC#Qm51M*u^6JPkPn*-O at 1QGypH~0nd&@dHbkbno+&IU z26#Q|+QMFORF3Ckyu at G0j*h?cqQAjQ>J at R+IPOLLaCqik^XS3v+I24c#C- z+o=>vf(Hsr&IwuFC}%i()Xj)Lz7R3u+BqJcS>QnlWbrc_Jp7$vA0w-kT5Tuta+*BE zrK3wc|CWCz at IvT(_ZpU at QMF0cDvD8an zn(d*rja$?0t!is at iQE2;y#PZIn6gduXF$vWNJ`$GZV`#unqZEwowtM$T-+hfCv*yw zgl$lg(5>DMTYXYPa?;jla|@%-1Es^jMCX8^sBQGN0GZf+YmiLxTR%$An`g4ovqGAVSII&H5QB}Y>mh`Krm4Y(SoT>h*r!mL83r$K*yXkfR>%D z=_WWEY=QvM06htw+TJ02RKz+ at w&HeoYXV^s at -o1)3qgjRGqObH&|<{dw`y|M**ZgG z4oh%fLQ6XXwcFWVFomYAG23?`DUOP%yPP#q^c=8b;+!LuP(H>JNKf?uZv(jXp1fy#kPXKtqTd5j?lthWQ8On+~-$DAaXL^M-p z>x7hTZBje1F^Pu~CuZ2X1b!i{)7HU1Um#@@jn!jrCvgU_QJWH>ZSsOQ4RnanF+GS+SwxYqp<_H4X+U#sjH{hnypER#d z;J;%&z>tDwcsFPX^GeVj+R%DEP%EO4iiNFH(8{*>27)&D55g+QucXR`Ql=tS+^Su$ z8=HBPmce1okt$OIJ1o-Zwl)_q9+VFW2+HnCqLNo#JSJ>)(I{@l4MY7q+b8r!jKOa` zNECd7ybej*+z1;OiD93fXwh8bE!c$g|7LRp6_NFC8#EEV at dBKVLAg%Rd>2gkb0(t! zJD|-X{)phl2GqID+-&~LKWog5O|;@tCzHz-IToaLS7zK8U~V?+Y!57WVQNXTqH3fZ z#0~xgrZBTcjvJIDaso)Bg(C(vg$cHtlih7J8~sf(qv72^q#Q8C_~1rp?gmX!3KfQy z0pN?srcsMS1ucdkYoDbp*zATeL5a*Oy3TLS)0Q`?jZ-iOf;S-4YWrvB&$I=jcBP-0 ztk5iPU{ZHFigsyhX%m0d82qbS& zf058&T-bp))eLE4d|_h)HlY=|Yu1N|LZ;%z2+Ya0DH#xd0Wzbwni$N`=S zG4aNYu~9p^uyJ7nP63JeLQO%)Ao+uZt$Opc3{OTuNN3|$7noWSg`XyXGQL%xM^BE9p2R2h2dXF`tU^tD(p~OHZ}lBp>2IsJsE)KCyyV2 at AN1>6gK|t#d9K* zph;vUAQ%l=HbzASvI#y3xoa?-Ap{~7EIxk1 at e-ssc=+hiLkyIj6S=FPr|M~kI0ZOL zkEdnI;iHF~u6o`8Sm{7c74#SJZve4Ae)JHc4G#l2$j8HHLX7gW+8~t#6eBzyVZtYm zqtpQ69vWgqbWE7u2#668vgT+Gf|TUrqshZZ5933d6GErhfw|EXIA(r4grcO?>fz+U z!{_mN-=slAn2=ErjuJse1WJ>iOdrGS(&Hie;2j>Fk at trW9zFz=S|7W?RcH#cp?>0% ziEItzP!2%^<%i(`F)7ERnwgb_Cmm;xgolUmfg?KM_yp7(3qKGFN zh76|zp# zbUcQ|Jd6Jcl$0q`MzYBGn52v;ocLh)@WBJfH36v0rqQTrYG^<+P at Mxv@xg=pFrc~8 zDydZhZ?ugmRT&%B!IVf_7r>yIZ6!q{XnJKv#S8 zuzFZOFnh`t!>9~Eh4E+vVbhDvO5h1F6AGLrGJW`mW=&Vos=-(|l0wsi z5hmhPj^-!RQ8=n$(ewqy%|{PwD6{79X=bL`oT_>}!oajYC_OwJlAMPR@`HFk-%Hdy zRW7}|gDISN)Q{k~!N~o4_wmwNVqTIMbs~ts+B0)TsxqVb;NJcF_wF&QQD0Q2`?msb z1*J+f0YpGo5ac~7KEu%!$wP71j%*GV<#LGk#~eA_J2 at mVbTkgnpp6Bm;_68*v!s#l zO%DOr6QyR{YcME9TzUMhAB<_C@%~7>#e3n9k=LG>N)!zXl`ydxwXTPB4LHgm6Zy69ys~FkP;`U){@x zW0N5bzJ at _5Skq8MM}kj~goHt-_YRY_HdCkh)uN$-yHhl&mF4shN(zVhoTql2T$)ow zL2$TMd-!tkBptz~*exHH7?n$`PLCQyV96}s|3(|Rucr47!(q6q z1c>ltJX`!2;LPg at FSoktYNX zuvObAJ?KLspyU3%Y{CnNr*{tz?{d_0&sALQ7f1Y{IB(bI^TU3 at U$K=L9H80bc( zqrLnWkJTTZl9PeLBJK!aK^FE`99Y-HQ!cXh5D46{{PnlA!ea9}B+LRG1R zRO;feqEYo5;tN*cj2rt_dyZ|R%0Uf4u;pVT+Y)F63!tgS5(f;wKl6G?EU%P)dxilm zgB9t~Xl&2|FQsxg2jzBtkBxnRKrDvCGFi2%QW5;cMP$xQ0_$GfjkR17irO&WRY+ at P zuv`EF!TNO-N|`aqSuKbN691-PSP6^-st8^mr~?WDu_BNsBMU3H0&R(!VjZjkf3Q|g zDp}~3hdlkxD*$F;9|fq1zdNyNWJnTc=6XUxD6&_7{T)#CuTF at 88WF@$LmU|?(@gY5 zjV)LK6Wftdi{V)puM{Ap^i?(wNCs2;S)V5VdWf9L at 7{pP at F!N~69~k*W31^G-NaPn zhm?4ss_ at -6b#iJ9gd at yEV=zNtK`$neBaw`y at K{o6h6SazIAD!InZxgS$8S(_&EO~s zy81n3j6!R6q#zb*EpZR zJo?rUg{Op>nTuuA__EY)9!$k41ou_1x%-Gz6NzCEKeM7;-i})pqcj7Q|{| z=3HK9h!W#@36 at bS#jh-z(hQE(@KjBztpdl&MG0BW71Dl at bjjjxEZ60g9jOH?EEJ## z)DI+4Y;I>Z;+p|ee$~gZ;c%{66;N6 at C_6~g32ENR62WQw1$r#w+gXqE9?JSm6ev@; zt#l{px4KJ0&{#{!gyhNnp;Sg<7u;ngpH{`FQ0l(H1_FvC`7snmIm==;-9==!qK=y( zyQN^)1V+PSC5&?s+*lGnZ32mqg7>UMU#TZ&mD2o_2<^-c#UO#N4TZ#*5nfcpv8bCy zP8pT-T_pUzZfmy%FZ`yM#5!>_4F*xx-s1V0l7}B&vFc)mX;Pqc<~~}2O&t#5sgR%} z4(XNGt4pwBE9gke(fJNk5VSn>3lhi%GzKKLf>GG9 z!f1pE at H~!Hrc*^Y=87jzAV&TxS}FwEQgR9rbN#_9qAJ`-XHqCr366o(bHoQLD2A+ZqnLTPC1SEX8_O}$xEh>b zVR)q_rZ>%N%fXH>Lxf(*Ad8$zgX*{u>*yiRJMlCKsVDi> zD;#xs`4Y%7S63?w=oQR&;11(R;AkUen zRB9xX4qqrRng2x(c3Tm~+$R-?%`e?0ea>&$j zo72nmB4b6Nw^>0();RMJ$;@*UB`YTI+`G>xCPn?dE()l z0n}qD@^rHNcPJW*$0aU)v=Ki>Lv!V+F=hk*{&z)<7CukUSuB;BY3AVt%4p*8p*)GXd66Kd-ltb*FL81J%JZWk&+%AO z=$1 at uiLv-h!%eM4r5Al}<6+;>Nnv?DbLLrvN+>?VIV#xHH>#1%XFX_w5iC?dDnaKTpiDk$@ib7vaGt0ibPh at 99x}Fi#XJr);Nlbj at oIpahctoByWXDyW z2hc>ODptah6QUUpwdEH-Ia}Bar5*j2GbaRU9 at csDxJSG(d;qp%I)G;QnejJ|$3LP}&l9|If6gZ#(4S0uZhX(~S$wh3WbQ{Im|J&=KS4 zP>^E*0t5)ebvCrGexYK&cww}DVf_MdxUhL9IE<5ZfE)}r1s~;&KHvpx#9zSX`j8v) zoNRzDY#jmLXkfu7f&r?H6YHR+fs<1J8Mft3lq6Gj3m5Xw9o7djWd zk*0NL+aZ+zNM(qfe!M#y;ljv=2MlPVW0n(%N!mVttPcN*8_1MC&?BP&`L<6x>KJyoTA{7_bB}-oRo=vj7(8&NR?7 zqX_GLbdqw#EHHJAUtpwKvWe#j>q`NcEvyr10XCBjC=>Dw6i;(Wx$(k~+>o6KdL>SF z0fHs6$aw(wv?iwx5`F~RBeH at H7#;W<86sj8HkZh+Gu=@1lv4EOVLfgBt#B>YA>FtM zoQ-|DCejQ&7%@wm-HlUPt%;oel1%y3RkJSilLPdMaf33+qq&gR$7Cg~yNL)MleJ?u zS|Qy5?+U!sny)4=*#IeXA6K9&1XFKZSirQCwRQM`sUhh5oRMjw6p6HqiFhA^3K7yZ zh;D8JkRRElO>_2W28!vaF%CXQU!fWGEIuSU3Z!o2FY1Wti8e zYcfx{IK5+m(=!UdlF>jUb~s?t)HFpZ5+a(%m_dFa!t}+o-dRU{A%W&_ltkAk?YKLq z3v=s3&)7-)?igbrCu)@wbkmS`1x)r?&G+~`F{T+bx7T60Obt1lpY z3 at ui7*3;TZbLTZ`E^mC(kenMscWi3JL3g&OsbLlp at +x{D(E at x$=MBCr62gU56BLGD zUDLoPEms^Daf6h~Kjg+03=^Z)YZJ8h$WUMS>I{i1`p_CIWG$}OYtvQm4Xu)W>8$5r zwS?}Gj4a8NAzMgd6>E?^UwE*$HIcE_8q?v#Sy-K}iL=&uY}En94fqP{75+7AbT9MB zj(L~ZaSM=UX^o^6hQP)$t+>&k^%+<)t{TkBnZx=VTf#rhWarSzf-GZQ4=w0qc+IWF z)!`agSHc1ZtA1z at d(l=_+; zUlPT%uy(R)W at 1LD=e&HuZMvn_U5qqfA{!qa7<;;;gJS4X0eDILjkS!cXP|^bv#~R& z03wH{)DS(q4+!Gu5?=tnTv6zeXB=IE4v5**oLSJAJW|W}64vao2lM#Tpq{_bkus;P zowBtAvkGA<1pAYMFMrZ|E&0s&5KVclMvpnV1zz&uj{8t6b9e||q9&~}JH!y*MgiJGgyt0I-8FUIJ7!5^eyH_1+Mh<3J~;G9eGI`6kjCA4Zr_JH)4O-#opgJ8=XSUq4<@${fMXHxZ7`^ioC*Yl zJA=Cz?Cxz)5pPdGMLH0kEpV>Fj(!)!cJ8J-fg at RycyM;>z+of;@8NzRc1yGYc;aCs zCUUm}t~g#enA|!L9B0tbJ>aqqB at rb!p#(s|o!cCJdH`Z8Qb5EFm_m3k5QZ%LQ$Rrt z;A2FRL2-3E987N?i!CE)c5DrlfSTH2 z7q*Ufrne#7bbCnQlKyln8WI*HJ8&5dyP}*^8N9*Z!pkZ^cFB;JNWhF_)ORM}FWeqs zz}pA$U~uc;;1*Cg$EzZgCY8`GLa~bgjX>r}V$2{z2&7?r8R%2v{6(~bx^Ewlgz3R8 zFbSlRRs>)*0cdL=GO|{~WSWC`YkCs^z9XN2U14BJ0*H5~cVJ=p&Je_k-Z5p64(eNi zcM1+Dw({ZN?jZ)H9wguTb~@n2~oj>wfjU^nDUe2>2IO~yoJqg!-oQ*x*1sGbCSD}r3~!4c`Y zo1sH6Yz9b+jp*?8;1(uhCz>B5NwckC+{EWHMAWi8x^;k8b8Ez=Xavj?>UXaVhYd!T zL6M7a3rs`&x8kjM)74XhkgxYh2;T4nS|GWupsM;-brU+QB%1)#4TvOLMmMNz_OC$? zZq>KK&A~p%0ODw1GrJg|IKuFzTl30frjl<>AenG;x(|^WrY!(4Qn at 7QPWMiBH#SDA zTlLKZq%s^1L^O#RlIlWtxa~$0Zw*|CV1naUGPO!#CoLV=ZRel@)dAv&`!3}Sfxr at p z1y*-DFe=0k2y9GsG&d*v1jBOy8FPtC3IsJY*W66|aTba&PPz-9X26tBA3(zh7#I_N z0_OA0us^-IANETQ0Fl+~DiAB-U`%(7w at zq9G>ZM3`)PknfVqH|W=gepOeTX!Ut`Dm zX1y;c)&h-%rGW6&S3H0LG0KR;x{%Xm7$EdZ9O0Zx&~pL|ifmLcl4^f?1GETH_vXSKfBCzmrEeQuh>W<1DQgt+T={xRMH-c75M#glf3qw=%q_#*jC6OQ>kt?8Y|tH>=6~$s?VsYEJkk(F|xp!0qpJ>f>)i?an4OByWPoIl-4M!GbY zTs~oMcw=u5v~YRt(@bo!Ix&Sq)wEnm9ua}Ggd1sZa-9*YJV4Oijr!^!L-DPiAR9EU z#ihALHG2aLdR=k5IsZUH>tmp+ToP$-dSee?t|O?*va$zlE`1{jB1+VVu{Q$k znjNaWa6Q6W{oz3Y)Z~q2it^6Iu!81kMTl;D_6%C_`I2t-6HHNW`ItG`*-dZ`(=k)S zgmEukAMahijxf-dU&F1P`+(gF7n9#fW7)86%PeRR#9bStbAw8o4u2Vv$Ll^R5E zkic%kCyARQOi6LqH-;RCbJo}6uTKOKaR;20bx`z=HEjTqScw%@Ct`*$tCKLB7S~x3 z8MFO$Vv1~LRd~FX4GYKw9&?>+>TFr3ehZp$ZA#jW#0fH)*fO3 zAMncrdKS+?(w(J^Aun8zRz__jNn&QK0hwK)b0`zUdbNv+fk zC04!mT5Kk5rj3W^5Vq!K;U1=Ud)}I-J+tIl!5II4og?UJD>9Y*;)o2bf?*d+PJsG2 z7j>lUSms|d6A at Xv@Qx0kLlwR9!cc5zIp-*pMD5eknBR at O*oe$h0%ZwI6GI%y#KJMo z6wqLl?a#zz<{#tq0lVm2g!MRIVaqb?O<+u?g;O0pu^eC|xUs!ivq at fxJ)a8inacru zRNw=$g<4AuGD2+)yqg)Os8V&5o}7RX<+g!(W*{bWV_J9hGX2l_ClAubIgjpPX_YO@&Y2W11SVM-|^g5lv~KM&k+sV)kVM z at s`U+vCUomm z0D}f2LB_c8p;eV56_7VeR%O_3lzz-`qbESZ;~ou}y38E`O2d!F;P?S!nboYaj&=|v z9Q_RXX`DPw0G<;zJ&a+7WcsX^yK5~*=Wj}SJQLTE%MTAWuZ(W|g}8L|m4UC-Do+TE zA0U9RG?oJ9tOtXod5hU`c>FgI1YSG=SG{Z+JEIcekY=MLTb=ep^Fcp99S|P&ksU0M zJisN0x0bd>qY>_My zN9ixd0%$eDBJ<-urqWJS27UJ*ID}u~3{%o|4kI0%P&X7GIz*-HPVTe3oi(*}7;7tw zkkN%Q(px4Yn?wNG$%UIOe8Ac;JMZr at CG%J#1iPzj|iONhj9$14g9{W*+gs`eya^|JmVHF;0 z!9LwRn0&jGd23TV55nk>hPJ at qQ8l*K=t)$uSk3S`Wd1&R%+n`*$Xv<%zU3lUs*YJ~ z(icZMb;6)JUS?-~YV4FTLpa`N&`nr+xrS=#jS%0tvjQ|5?Ne|+fpHPfG6*m3L6t&j zzZfV_hj0sSdJpH7&Mg7u1VFS5WD1X)oLQOTY at 286P_hN@-0>t at NriFq{h<=TK?D!O zl~B&~Z$D?pRy8*O4{_)z9sevDPz;a&&D7_&RC`lLXtUPSy#!C^1E2{kw}MC*R-0u3 z>CyPa#28o{HNlGd(8bXagpllTS3*Rf(#m!UECOjB&#)f4SqGW5i488Q!&*`!w>!lE zcNotOF^@udg|MHG{vU=NUrRiQJmN8dlJoDm)W}{x%BFC4)d!c{CNd!{|QR~)4T0%Q0mQN@#0 zfi3C)(pMto!&&ldj)92 at EI7DkoX)L&B{fD8ehMoCiV3`Cu#nd~h7v^xgc|6qezWG* z1;Sl+NVZzf~FMB%R$ltCi_8R34TGlpz{+*1j2+#%;6OmtBC-usTOo z`YSFY=$v!&A!%^GRR{s_Y&~67ee!L*Pgf*<3e<_}fZJ9^b6|5y9uSsfHi}WvEj$7y zML3j*Twf3=nIPlUa<7eL2!@pzqIoy%b#7%y8l(ZmDLGkklaV2IsUnUCit)lbEQ3?| zOATusNsMzwv6+Agy;(jtm;M6F#&GX7vpVm}dEw{)#l#p9}xyMvyN0Nn9-7Ld|SDAgn#jBFEqQ at ak1$iuE zeRNxeaa7BbrDbs8(M}93pdT2(VzeBoX3Dr(9xW{|FF|`A=-gZCV%H3G7&ggz5?l=b zmd48(+&qD@#yhnU(ifS^J#A<_*AOfGT8xzB1$0wjRS0Ul{YVCo*&K$&FI-{Td;~6* zNF5+2qAaeQnrx}nygVd3)E`xcH`l$Yn8Cuz;&NOHB!UkDacxdd4(;# z5CA=km%yJt>d3{-!Q521-srvDadA<`OVect zOXkQ4L{G=eT&)x7X+RGm+mh_iUAR?;8zE(~i1BPpkTY54ih%W#s{zEJ#e8lS=%HoB z8&I$mWn1{hCxFiCi7gB2IS+|TB=}M-49v}#Dkji)P!}YUk*RNl at hWFKcHCzc-0oNH2 z<`6_>XjKL}G`$Y=X?p!yb8T|<+SPbE88t zeePPkI=ONcw@*eOEe?edo2jjNbc(9$;Tqq$hwFyiDoSp*^ZYIS9N`O0OCD6Bf%l)&?Tb)&o2zfMi!9j}ck7RpbTg9hMgVSh|u zrof~Etm0lHp(Lp at U2Sl2rjbhc%kWg;f4F^LkD|l#UCA8v~(o1UAF<0Fu(R4k$mljO#wH;Mzcq4fLKE?+FyD zH^9CCAxhZ!T4Q3lf*S*YQxd&vajH!CiS`s=ep02>UtP(UsguwGR?aRl$`cR|lnnp& zV7JBC)h|+?n1)wfg5|J(3Z22u_%e7l?|o%YD1uJKd8?~q`bqMxxH%MJHpL}DfnzgM zB5pN=18kdbaQfoM12S#*ie|S-OLt8r0?2YQVN^-P z{RtdifkBU;;47DFPJzqi8aRhqs%emAvb_bFE7g^BnHPBiTNEh{Sx3Oc_(#hOP?Z^4 zfLT_!IMO0VjbUYpl?RQ|$OFNUW_IPW4^LmB^;UZoWFebrLk1<1G4ALRwLY`l3Pv`h z!mmQzSpvy^$1d-$#8r&6kL+HNuaD`asRXYKrEui>GH)QneR9$ZvjaS&sCfliEDB{< z1aFbaQ_Cy*1#14=*?F9(=b>Os#wo6 at 6plmw5h<2i^+N1vsW$1{I at GWaaZB}69+b*uq!?5yKN{F*3M7;*| z<1S2IJ;*yc(I7yH)gFx$@tmQnLXeJ^VIR_q-)PAjS1+OHta~VZl&{=+D9tv5eZSL| zSM2f*J6J6cf3uL`nb265s;>SV?qGBiQn)r|+hj`=SiX#~6)%5-CEyLUi6)gLMpauFVzecM zs;2eZb<_i-eJOqO<%L_KbwUb2VrtixU}E?#?Ys!4<}=C#-uxulMz^Df(%= z4ZKx`3nkG^7!l${UZlw`Nkwg8S$bckwIrK3<(w$z_?Ww-iBH`SacaI zT|5d2utnTsTtSk&^D}MBP_lbb1q-LKWayqk&lgz)I6$Q}W|WMR8q^R1XJW}s6!V02 zG#bK_g2dOvtrr6Ly)E2r-}*8uURzNh)CwLyfZ1cA2<}bXulB zN^72_Ns&!?+L_iOPD)+Q)GK%aNyC!mTr=aQo=Q z$Y$Ku;MCK?6Ef!_atSDo3aF5BTA4!X*_~>}Ljuf4<|8z#6IszQf=Ff-6^yN%RbX1Q zq4Yw_EHgc}nbDT>-{zqure`w&uArbm+gU8F6#b&k&nO0EN4G|rk(?TD5(KM+1~W7G zo555jvxpQ at rF#aYQ!~fhKKZc-mCif-=RA1p!9xH at VufjgUFJz!J}HY^f*rzte)59& z8BL51ka01Y&f?0|_rkXQU{ zD=THuFzS_wX304t>koEM_A{A05s9)VzTCi8xWxAb{BVC#T# zMa;k{nczwAJm;az^dTqXbe=uS`S`hkxuq;9Szu(Zl~!D~*mi?YvI$L*gNzv*(`WKK zv#w86YRryAgxlZlAUzv7vN9wyzHsxDn&(q`>0ahbhkY5ia-&C}@b&>SWRXLS4sg05 zJMlvZ4=OQ}AR~*M_0rIRK}+XWcx*!(1xz64mI7$ZkN_8slA_8Sc{)PIhh}Ae^0-3< zb)3t&PL?0756mVUemsHvX)0WY%4rei==x?*#FYLa4WAe2y;TY8OmufwRDM5F>2Hl0 zOA at w7GCt3td0S;L98Pe~f$_;>H{(;1j>7(spGx;21yWPlY7pHqxW?4ZoNts=;1Hu< z_tP1Eiiqj!j8;yIp9A0~0yhP0GlS#fOc5*UB?+(1ca8`=O!1{s!hWR>n~=&Z5H_GA z9(?7d8aGPNkROmiIS9w&c|b}o);u1%@HNj&`BXYrO21_?N1m&=0ll*y=(*07ErXcZ zVuMp=F#ZgiJc9`r3(?2l>zIMdhn-=olxc20MtRCx7%;eZ*G_>)T3B(0=-YE%OfKg~ zWNxUD6&;`f(MUpf3nreJ}%GamYu<|DaK3fsF;@Xjy zvMJSoxCRYRnS8)?kVlTNZgH)hWz91opBpQ-`&eBcTvM_F4h^+A{GMuUfh=Mx3MVD- zD%&Oe1gt`f#jMT$@<9W`%RY3(1_3{~F_uz9S{9k`kE40iG*r#Twy#8S=>r&^uW^`< zk)6_Fdbsa|CI|r=Pxh%6pS}3mq{IM(Qa at CYVWiS9^Xgs3{M2KgAn_*fSywXwk3-3bNY!8J}7Uxt*9W zEcb4%V%6i59#i}wJvv_5*R)T(+760nov%{q%&!kxX>oE&{7 at PE;HQl!hmx=_kJp0G z3+}Ubmcrs_X>n<3F)dD)7Q>>$7D6Iw2RR(*zz+T*z=U88zoFM6J1#CP0zI=0|FE)9 zz#QAn*PcJRCF?e~SX$OA^XJeB04|GPIUEPn_Zlq)%Mp^BLsLR>OASfj#(!L at 7sEnW ztPL6UAUuNdv~c1y at LOj&n_ft+%qKuEnm#NLfW at 1DBoH7? zJ0|y$E%V}uxZ)R14R}X*U>73_ts1haTV-DSxT=Im$fRy at F)tYM>9Eu}^H9G9+0sH-Ff7+` zJ0Z=`s4(5GX+$hHuvJVW3t1Q}Lh*Qs0ikKjOFcHOmKw?~K*(Y7lq`^L0=1+9Z?T!B zZD&;-;^M%B1A3;5B+ADZP^zAC-CWFLX>pDdMcT+hT9E98=FvFjjc%G$#n1r3XNUs> z=LH4?d37;fI-v}N-U?=d ziMn2Jz}z(=%TG%XulX%0rMX0O&^(=m={&kCPXLlr_;4f?oV zUi at b5$Q9(X at NIs9insT|(!BZL;zG~ngsZTDrj>wZvVkd at iAlBf{JD5vFMTVamEYZy zdJA94245hMDl;*IW5p at ON*YXDoHKjL^GGfLX++FrCkkwh1S`{-{5fz@*Pmxqsc9d&~{-y z&r1t);z?F+k=67`owyiB1V6gdt)Jn5%=J|NRX9y1-iLDrwTF2FZMnE;+hpc*dm!}_$ z+l3K=dHP#i$TsbyH;*_$9|v3bDv at T0AG#qh5cNDMY8aMWwQ_$k>XDq%NJS~l{1{xqk at T2? zJ`|r8B6?v&v+n#E<$!k&0iA)mWAcq{teT at YF3iu(PkZwixhE6n;-L6hm6f5$A)+l> z!q|%+^f2X!s>SkfVqPP*WH*&H4!y7Qk}+w-+n2RsJ`}Aa&?N!$y$q*;1$4l$ z+XU=EXdmE*OYCEpfK|H8%`fw@(tc$_J_B$2TK4WWpMeo}OI}pR+OC8`X*OMSgbjKJ zn8T$$xBU64#84GEAbvU0k}ruiE;rU`kw} zoB9a4O1l&E$(P1NRuZ=Thjt~HZgApVa4KC`>Iyo*?|?=mlnboDuELAkeO$e@#aE4M z#!y12Kr0ED$IuvR08C^t-)wn#!|4WqjuT`X09XM1Bu1!8LQ`E!XBRE$@<81kw}GmK zQ=%gV>lM}Oc0~doy!?!PaN!c}2c$NACc;h)khj|JXXLGO<&s6KV^?pc%N^h!Euy2# z@#<~fNT(a1Hc@#iY!ZwDOHdEq at nQ!!iCx(S5jS2 at k+@@np>&TXEXEWMP-Y84a%OnJ z7V1<|?L>w+ at 2M`lluaQ&jV0Ws8$gqYn1KqvN;A4>X*rO8 zG$qey;!S1Xlt}=UL%5EewRVSFR3R3Y)#QbHsGDbgOHeAqnsr$lZ_(p5ktkJ|HvdC@ zK+WLApM`NbRdENGVw#7$CuE37j7pke;~To+4a0*Y1Sbj?bq2^aZ4W3{%w}~zS;ddF z0ZEw^{xt8YiC*f~NnQl$!g3{t&99`)=)({0?&FP_saw>t`vP+tw7ftc?*3B&&dp1J zOfQ1M6S^vb5aSSlR8H9dNGu zLBY4#Dl3cncz)X)t~Z3W4n+vAuPu at AXA7R<7hhC5R3o!Dd<^6((jcF}1O~P9zP at s^ zZMxZLHuY-9}?7#&2bad9q5XUtQq;YD&N+$U~X0+j!B*P?*? zA+<%SVZTYFx8 at -`#1|F0^-8y=B8nyr|EA~C78N>bQpUyuvnm8P8VX+3X7*5AYl4i} z-51CBqgx$1d0X!e$Ca(eou4i6%i1MbyLMES`@l at v%n%rE>Vxn=6Jvh4 at zvL{Ik9Yk zRDy%IsGS*hg&1kKf2yqK1|p>sF50TipEfp&S<5DPAt+)<)*YBu=ye130p_7!2xK6F zcTD=YmXh}ua@|RrClH6pgf(chU8;zCWC`i>8s_mfECJU9x;fBS>>tMV;9$WMJ<4Ui zrZm>Yg7=16Q7A%-f>IgmiaY^ zv*n&ndQQ+n=@fd0*zo3KDS%fl9pmknv+aS6~P`av>IEaiRCALUTli_XahjiMk$h*GrH ztG7aWPynYI^Wsi>I^h!xhEe3z(D+Xq`S3G>83wnyQH9o)oB0_j$^{PEeu!{eF$Lds|qW0H9D<2>5}gNE-ygt$CtW${}VtD$|8B zvU!+ylXeMJT1GAbM%L>=@*;aE2 at +_5rG$GHZen()@OWl$6K5f?HxPQV2kc~)mZJnr zv;_1<9z0)~nbnp}#e^s3mLkcJWCWolrNU8-M$uCSD}35PYM2vAe2v(nfkp%zl!qHj zLQ}nKiRCPr<5}wo%r{6(3P|ni?2d#G8P-fx1ZAC=l9oZbWsn94m1)MiNFXI{Xtk7t z&M`x=25_isDyBst1Iu!@mTPX|7V>0v$#9Gg_LG5O~fR7Cc`R)IAP|cU8On@*{LZ+ z=3FB!mBU!ZIhC at qOFWmUz$I&q>8&&JX at pC=Dnlobtb#(62Z`YbeSvUgxYiiU+7fhX zjr1EW&!ng6sT3wz)|}XirA4TrZ}1*@rSC9Vn3>b-yT#yUDf!Iax16+Zqo5H+q>=EZq*ZO83YDLK>kilAN7zF76940a8mB$8 at 1D)~Vp8+f4Xheq6 zuYe|TQvlQk?qI2)#_;ZL^!OYRahMbh|BUL#eCc>~VD5zk)vLQj2Z$n=)f_sTLtovs zY;c{l0q3xR=JNGjMyVRwLZUAdwQt}T at tQn@qMg~&4RrUkW?Bf%ambC8%e#BKCGnPq zZei;NQ;c`1Y(}5z@~vG<85iaYSvHps^qZ)^mlWXr-3lzS{q0>MzDn(8 at TB4ob^{VL z&8$o^xAB{qp&jlr9wXhA`O1p6cX!E2yUducLwh^BI)6h^KEM#8UnC@#A&^b~uW!VCyVaA(%Hu(`oF)XrruV(YNi>vns1b$i&n#-Q_Mk5wDF*l6U|CEEA! z)&^qLi`~#0cYE9;?ivZ&!4w_ZnVg&NqqVx!y*@_f_-I`=GqssJBdWHYNzgst>-4^P z`yTf_IfFL-x#v6PjxP2xd;FOk#NL?Gq+aNb$wA;`$=Bu&cwGE3MW4s{I*gNZT6PB;afR>2;Uo^rde{Up4D-7A`mCafRq_W+8 z->QwAwr7K3L{I`DMw4H)0G;%-LCiKkNdS~s*(ld8^5Xn*FLj$<03;FSIeJ*Axd+|8 zhE!y)OZ5z`v&q;uz(;Bb)tSsr+1CCgsB{c{0>9t}xb1y|YmmG^l0&cVPVu8{?gQH3 zUowP3#ZtRuC-)jjkzzEkKz2jnU`Lx$g2q68H}+0SM;KAYW_m6-K1Ik|iJk$OLbiM8{l1DuTy~0+S)_c+ at 3|AnC|{ z6I*lmiX4mO1d*R3ghV96F(@EYx8(vqRO~}D1!a^^4a<+c|6n_x(K>}L+tD7VnxFAizK9-Z2wlNyLEDJEpmNO~ zpAbhQFBPGx%V^M;^kkW$%4P(WFS*olmt79i_|wQ0 at lb$s1ple8i(D!YBW$O zC3ye~y@}YPxp_1|Pq;7}ll0*J#coTY5OTn8;kPb0SPcCZ+&i`dVlk#(iqM=q^}b4M z#uI;|P)P$7qDc`mF2dyC*FJylK2D%lQEviEo zlVj+ at +Zria3uJ?quofGWL-QxBCHxk84S0k)O(yyQ;R=7ThWHyjuY571f^e|(h+I&t zu#|cg=FpJs$YmwOTqQCDgj5>191GuTcqnJmN=CZ)V=HO?CxA%~BufVlicY>TcM1pe zm48)Vu|~Fxc=lE>!55EX)F(x(s7R%dHaauA+Vm6_LSl&&>;XdLRWv8_uH5 at K&ZAD= z7c?f>i$6VNbn;~y;^Y4`DF<&QWpP}5`ZEOhEW)9=D{%V~Cn`z0Q4O`ao8)3zq5F+Q zsiD3>x*(A(=m7$A&v#0+BQv6?gv&B8Tov7nN=Vzng^Z-3A<%?~ zI*Qq9hyLzm9=Uu~CSkB)H(evqO;`;*$j|__Y{?@yi7|)RCsAma8f5Tc1TEQGGdQ>8 zoU&q13K~xcY$lm#`9V8`rp7`N8KK1}o||)^FI`Ll2PY46W=9%mb;f}d>z*EV1KY{rhZ4a!YSZgcW-CjP!fw=^qm_hVCT-uj;M zYzH1!+-W3WWkSt35(medgLBwyT%BjyH|2esmX|y^>S)tzR^OF883Q*LG~lsUshvhtx>?60%SuZ1<{@?a{i_bwib?hlIK5K5Nf`+ih~m-_)lT z8r$(I at 1);5Ba)$h;a_Y=pHl;uGRBL at Ff^$xII&?OXo$BiD#Lf*p)bFoOdMZBSM-;- zi&?cAseN#u|- z^}}pSoTf0_k{A8dovabxgv%6asRfFyV|MB6njLBk{B8EV)n6gneW at l6jg}ZyGGfX$WJJG-wT+v{R#jUmd1!^3c?6Mw|dK+?YFWk2X6M4v-!BAr5$`H~%M~XC at UN zOA at mwUF=jI0TsNfbDbQTE#^ZBfue*E8xgwZOeyLnc?-aE=SnbHQ6DzGQowlIHwxNf z$~TeefB8>D7Ob6}itjpVX0Z%G(Fa5eQlxn`r`k0RP*x%ql*G+S^NLb$S155pMMFjh z;k3?cF7h}dxgyyw2X$4S_r*JqNyx8w4Rj~sHFunlse2hr$xaWlCmoQ zT%7g?wzuf$IuTgrtjSbpxt6M7*EfPH{h7nnRGbs(v(CiN1}+{;U~wklk?B7g#h9tf4We{Oj%rsMI+$nC+)de8FoT={bh`eN zESYU6bYd(U&5RB6RVH#M$KYa{z59#bN~f-0$1yq3EU;0oZsNXVx?&6n^(#wpIV1&e z-kBL4yC!(aUJ}=razU-BOP1oKV(Q3T2h$Z9(r?bmj at s%<5hPKVs7XO>B()+H3#JSp ze%tX3((jh1m^015>1dTExpw_^m>X@>itNqetL1#0Q;b+!@JArs9gJ=q_niDBVH?eD zmw)s?_q_X`)*>lMOHr9*<_T`^wvL=|4vvdAWqBSlPmFB*yL~ao z2QeNL&h&fscB4?T1%F8m7;Ca0nML-s&>hJfk(Qyr`yZO75MpDRTwS?si1I`b$%HLL z58|;=iLY)|-k^;H96+tbp-l%)B&bwYHT=5JskW(vDGEV~aPl#_FFBeYUuFTw*=?tz z!MISE>SsUmcXll@)&+uN|I at GjSNryV>Rb3;x{tx<=4*2bVDV?SKErEj`gm?X;vvJ;yY;*sc at h!e6m3BaYQux_O<@a`HN_&d-i9Ff9P7x=^6nWeh z486apzlP)&+fxv!zQsq$J6KY-Es8j)VZ43&)W|K8?3BG%YoT2V8(t!_Js{4Q{WCmm zf+TT|qI|Bmtr+eba7xZpw$QjEA_>~YJ~#SWB7Ofr^e2&_&eq+wM at dP$@ueMTYeMbW z?ggl at -3wAf)^TT3k(iqxVI%jnnqIdznOLX|+ z?q0}M7aG3yy^2IQp;2I&FB8)G9gj_ zFMq93*pHq0UB0h~j-O}oq3vgEtAjVkpZHTQmKIxskV^1H1~KPrn5N)T#L4*by;%OB z4n{exLL#rsM5!z-OB{|$H_1pO8j7fp<}h-&HkeAyC5U{?Hu8N#xHLV7Y=~BNWlBNW zn*CQ1IunuzpBWFEnG?$e at K=UX5-GJ+h at ozz#+#NIn_FKwF!?h>B-RO}#H3>)vafVR zc{6ZSh6Il4c46bnmy at rXWZP}l1*=X{huQzkTG_pIQw0zF!u4h{uolLoNxq;$Zm&g= zB%3x!?%g*fQqk;$kVC0js}*N5C-K;#5uE at E`P_o-15h)hh!Su-6 at W!C=0N1H{8zti z1GwU*{>0x?h;0~hk~pCP41Z|#cFqk8dlo(1FOnewRAcKY`~hz>L<+=$8HF+vaZ$Tw zZ at 2561ZNF8XD3rZOJ-Sh2t(tzo$({SNd?4f_D&jpfs0Z(K)FepfBD4^QGhZdeY)WopMXJDsik=P4}CN|Ter%=zP^B$D`)EC$H|$@OPp^G9LqJbpp)!|a70NxwUybrbarhOM6{CgYE^X!j5* zQ9MK9BEUp911#)3l_ z%+^Yh{t^r&{O|r6|2q2duXu$Y!w=!d^h5YE{ozaaGW`7IGv4uY{xtdc>0|Y={xEs} z;eC8RdH4RW;jcsVz&rmq`r*ePe)!USnV at m_JpT0g^QZb#{5byb@#6>U65fY*;jhyl z(JTEp{Q(oDAEsY^2tULxg?iaM$$Ewl>HW_i-oJbQ?%iKW9KNI~Haj~?t2{@N*=#4pp&U?+T at egb{Xr}T09;bZt%eQ4fK z-+}pwSReoJ9 z%Aac&ek+ub2uSb8WHY{-QYsVlg662T>Prn02B7?-R5k$x;X`;oCDB6)Q@!4Luk=Ix zrStiWDpLniQd7V0s(10nAw@!=Oh8X6s%k#fAM=MXWnHOhd>7tE5mBk7D>Z*kZZ{Hl zgt%+8oM6cKWBg&x?PU%c%pZmyG2Dmu_4^7K4(%o*14j0?|2?dWuj)&pZfV7k zTmU7lDEcnDxAoh}_ittQf1_W?<6*$G;q++$dDDh#FmNVcq(5?w_LG2ZCy_utdXIudX)ulw{d$&KDaF8O^2 z6NB3C!}o{*7%zSqK_D~@*hdXFl1jn{^AW`_IQ at iJAkin7 zzL_7?2(iEYK7F5!#{>n9KSGMJG at X0j2XpVjm z3Q^&WFMkPt8PayXe9j1)4CL;<&Tar1)4xEjBZsQ^W^DPTOvlC0lNQ&kOslIC3zhE^11=)L#eds3sB+52ilD~S at FyqWoX@BOq!A_<`FbAKly ztHId^O;EGUj0|%kvbRoFWWE!?P9GnsBv)fz8%C*yFxpk^yiFi3%7B^ZPkg2V5GlxwH@$@R^vgcl42l_N$%**?Rc9U zt=wOcwOy!m8I5*^%YhZ2r!0&P>mp9cc{*e4$E(Jh=9Zt1I- z3b`eGMb#%TDDMk3*!YUTpbQ^If=_J{o&Z(Z{X7@*Cm>Ft3AYy+%%oem-4iC-j^zoH zKkc;ERv0>DY&0ztMoU+f&GhGyuq-guxBHTJE8NOnEtd*NEW0mbBNcgwQz}9pZ}nLH z<}Eah+5{^tuS!N>g6%dG<5D|5xs&hY+kYVc*3Fx at Zn*p>;hU;- at vl9EoBY(KKt(*3To-SSZrx1(tpAaJOTUI+M!)_N ze(C at G%g;amlz!^{_|uPk;*ar%(f2=uAL94%yWzLreVe`w|7 at 6$e@(yCKM#NU`KRzx z{bTxJ@%tZs`2PFvzW*+M7r!06`1Zv=!#{`r_~$>uKhkf*Uw`{G{~CXJiEK${l8Ns9 z at Z%4B!VmTL={p3!82r;_gx^O0_${);@E57n{2YD?KaPI-G5k1Wc>H1b{SV)#@AG%{ zx1)dlBmN`&rbUvC&cCFeD at LQWMyBtFs1#6X^xe0C6-M9wBcr`EideAl^Dk0}PxvYN z5;dlV?*`w0_bu}@C44)4p=IjdV(X-gMx&p9ip&W=1pd^vPW-NqhhEr1)j#^b{ra2i zmY>p33s{Ut&D^StuBFg(ToisA{KLk|;Odtmy!x^FA=}W5ck+wykE&Q1f63IHUw-}dmv(&oX~_6QxLj`hE}MbzMfW)hFd_Vk^MCy% z{j#V?$v5$*zNsF6RJibb_}tgyGiqsIdJ&!vG%#XsQy`gt%0Dim0V at 3PeFiX@ zxP1E}zR1th7%J)K+(M;*D_S^5(pT^e-_}z18Q%8Tc_zkL^3d^GNI8l2``4vUpx=b!?Ord%$j#g-ZZPL^uDGe(YndgE*IE>KEzx;fPbQ5Eh7@;JFBID5g{aftN z&+})@dWQV?%RrEi7M=LRK+X(?F=b$O%j5I-to!u0UmZF_U=Z4sGGOnJdhYy$^Lr*4 zZ7Nf`CWh&|MO;lLC=~>c(KGy#o(+&_*436c0|m1p;^Eugi*L!B7m2a;vqZ`yQic+w zobjv=e(sF1NBp8yo{HrwMpTAjD+bAmK>~0pI?qxIMXx;u63C&L zpM|F~qyI#OH+Hq*9X{jhaL12UZxVl%{WAfR!=nD*j%d_Kf*^Wl^Db59zomRY5j(><$N;}!7gX+|l5E&kBA+%s)_Lnvm* zQJF}lr`40dST>p;fSd|~M=^feMFZ)OO at YMF@MICCzj^YF%A5>Y)PsdzT>Gj#upXZx;Ps%`jLU8 zk~E0lFZw1d6H9qcFKW`cemVpx;mHuC!#9x{<|>r(+!h#_s(KWkX9JkL(r3!otdY^f zz6yA~@OqljoMNr=6`%D8=+h^la`5DvZxq>YzWxRWg&%7(AbdY>fVbKZm`Irwl3!Aq zp7LQTKj}&SM(L?-RezQ?d(Tc)Z&rKCL at mUu_@w at 3_;p(-+OG$`@p}#M(%b?lnlI9m z_)YigZ@&53W_AFGcC}>LL&Z|bklSPhp^o45zP6!?Mm-Lh5SbVir8EP=*Is}UnWgV1 zR{Z47zN+NY{Io6+$X^HTX11MC10z7!mlLX57pEx0Giay<1O+T3*9}v3_3vSx8E7S!@TenPsw2F+{#@w{zNAU<6LTY}{`>6>IC zHer+6(gY1pyp4sK6V-wZI>k2Ld1SVUe%G zS5xKsh`#l>uhjmZ4jJV-iH|<4xhaG87W at jd6j4{2w`{`1M_mo1_eJ{9dRWjn!a7j! zyYm6}!c1`ULE;a6L^`^$vUE&oJNx@!yDBt%@Rs+YX~v6>`;OQR7^U?MtyVtPJuCW2 zk~?KLN1(zozYBGO0bCzLuYNToi61xK3FM;>JJLVtVhkTxf1o>F?9xiaa0!dlvtPQ3 zf=`F`Aatmh(2bGY1|p$q#KYj4i#OHk#y{2sW=gF&5Y7bM3(Hjpbk>pdp8nF5V!q!k z*eSO$FuXDOm0NEef*Ot0B`Rt}<4scNh4JABlOL4ZJZZG at v0Z4POC>(aegWhptNSGn zYK4O)>+J%LpzBz;XTX;C&O>$!qHEx-Ji><^U1O=MI7&w#-7FDkdtyVS#DX-i*j7lU z!G~Im*aEus6Q4|!J0qrAH-`_W>~2hiDiRE_ai)oy*8rIOVb6qq at XiMd>pvi59<~mP zdxw_)qX9DAugp|Gkhore0;mkG+SpnM8XQo6xJ>S(g^GEeQFy)uDHk=;UnhJ}VNc}Z z4D3kQ(f>fhmVLBnF~y_*=fes9&+&t_o_q1M&J+rv?mNu2D*@kf7g&!IS9xk_c1ae4 zg8iCY8f{wF?S-B3qX|!}5 at XzJZH(q+Cm-}Om&qS2Z-a%g_K$8r6e?seW=IoU;<-D% zrPz|zetBaS18Bs$(#^-=xy9D643bHSwWW63p{_YJ1wyeTB9!Z7M2a z_VH!Zqo^o8X_^S9T7x at -9e*G}1q-!Gj!ywn>D-Pq8}s^Q1EN#&1tS8~IQ;*Pr0}V=)&wju%npgx2MnFy)Y)Ko`Q^ zSP++4$!bGg`v`g0T9}P*23;@jmK$lqN0Zh#$##SFri5B$AvCJ1&-*xv%Z at E1{}OR8 z9Ek4FW2lc^?^6y-#Jn|^nYDxs4`KRX!o;F-x$oGEoI&~Rahw|;g#RpdlR&*xZi9Qq zO%@Jaz(dC;Zbd28Xv&*7$)6geKjDK(-E^zFVE7;J&nsjWIooN}Rm~tz<#w5kWF|)b zzFvmQcK!N*E%qH%OQro3|E!K}u|%>n&M#$frNlJ^GE^O at EJSvtGZ$928CQTiMbjg* zg(YdUL_4GL%{aalP*&b+fXR*6O;p=` z+SDku#Cu5 at 2vt=v{72ix>bJ^tk6H7(Ib_A~&hG|_ju8Uj=ZRdT?} zRH at X5TG@%@_u4or at MvpLyq7Dh^s z at E8AV)P|nQ*SD)%`R3 at x&3H53=wH9VPrTlIn*KcTi9az({Qa za{jY%GufGdd*9^5N%?xX zHeNtnES|0Jh}(jq at d@?Kd%1b6P5=6}t0M3EI7eJczSB2# zGC>PB`?utmfOLyXxV|WJuk#bGRV>q0lvyG|>1I%j@{Q(tx;DBB3lu(y zuwEYYY#LR!s+;wVAT!hT>e~EOV`rpFU|fx;(d}EAXqu>msOnn0D%Kv~C1RaS&j>wJ z8y3>_A=*{f!c}Nomeg|LHr)8_Nu=Z(qw9bs$)nffwc*ukSHo2|J%93t`3mdxksZ~o zdI^yd8?FtmUelCU<}Gn|*l#YoBVG$#D>NxkHjH$a8_5~uftdm{~fLYGH zel13k+5_7A=_=AIxfEa&>eCw{%(p&maO}6mG^_$ugqC zc$rc&RwP&rP6O1I<-iR~nG%f}ANJ7tiX_H6y-x}*%*^B=4n<~ty}tHeGVux@%X}lm z18E>s2Or(o=y;u3fX9^snXN)QkOH_Es zg^OWTuJln at bt+RKiF&%0u8ySfK#mVr2A8i~AsL+*?#VgIo at xpA9Zk5xgnVUqSwkBt zh}Eg;W`k7yyIkyAhRe=>wzbKW99PTYxLEL17jO43U%7m_rT4|0X0MiSq8+MEsH1wrH=H9kPL6Ca#NyOQybK&FIQ|^mW(%VRLWlgJ6~(A z)^hFTblKsYC?Xw_ZsJTM*}zgV zwYH)>P&HB+0AdNVOoMcJ5y6&t`Bn$tHx~ayS?*o~+TbwbeRPeN!=?UZ9XN>iz`P%B z^i;BzJgEvyJ-^aLW&MQ9`I6PSWIh=P?WHc>(9RRFG+zx at M#?zg$)AC=kCze!PSiM> zm+bK(K$&xO!sR}SU%r$s)xfMNyG6X#wM3R0;ISPHd>9%pjV`{q`%IFSrZ5hPZDwNS zNKzLr+F1odWcePGDT at svlZOt|E?&CG9zQN60!)X9DQ~fTbctW_((8+t80P5Kz80=) z%WAGoAWE^#m#a(hqLQV0W6x`@)%^Qwb0fN&rQu=^NvISF6nn8Cs=d(yO6^sen-57@ z!1AB+j~2gl(bu=lKls-W?cx(x8)X)ws!RD|akp(@WsoJlVeEYh}E}k4gnQZTvu;3#-Kl-)_>-#}PokG_uQwZy!AD+P+mnr at 9 zUBp}|EcWrC(mq}4Yv{#rFdW|L{^Hf6 zFCKlt<8LE#0?d-pTP>GR+35IH{xW5&tvPLPCWl|BBhcv4?~6~F9%@g+Zu zL}qvtUn~@YLX6L%t0E32elxG<+TZYa`s>H>U-YTI$job==VxLmoGD308K8I%#ZBuW zt3+ERJ{moQHZXp%dM555KFutkC;aWR`}|e?W%WgR)PLx#hVxYc&7I=1hwQK{#ncL? z3?6+U3+5j_G7I2X;xjm5J!K!$ZuQsIo*Ev5+3!wkiHM&Lu0WEm0dKh zkljaF#VGkC53yhn9)?Hhp>&2KituasNnyY0uk+&!7~Ca|w0VFw8d^b9CA6uL*Ogtw zF{5yJIC}IjKO8=gpVQOP6QY=&R4_nYTVEW;5Cg}G*GLTy`VSw#euz;PV`)_A*Uo^M zdy at wzjDyq1(nk-&!}LIOOod}=yj%h98hGS~`9Wx-T(}egBsy`G#20;~tO;Mf{^Cn= z!GFN{!zfkm-$!|o+dio;d_zga$Kk7y8Nk%^WhCAex{n?{zyRXQCBcTw}|P= zj7-MW532{m`wvfQO`wt%65BFQrF at hM+=JdF46N9ir090u#Q6sS{9GA{9>;3cw;n6_3zy`o^qaOLtuLtLKaUx;NaK>*c at +^MUw^VFYF>MMpq0Fe6fnBjcB$Fu(vK8HNBGwPW{o%d)_o9!? z-=wb>N)kN&Dv?*|i;*l(j~0q5(j`gq{d?ga#~4tfBEh`qDYUT&^mRo*hR1=_mTiIx zZv2vZmx)2;?vMBLJvm;vtV*R(85Cqt5byT at 0UD-zkoU?Dt=DCg7w6#mWnf;TS9Nc6 z7bY``{8g9ix}%`8avUtG#OCq6&xVM#MXwo8kU~x%2+Ldw1)*#*lLu z;_Js1knJ1JwSbS#=FcI@$GZ!kx0^{O*o$6G<)b$$MYunZ?B-s)8$UPELbZo90FdxS zA6=wPeAvZEnMvk7xF3O&mPvQR=fk_7e_dPgtHE9c2Bf&)pYOe5%-zrRW1ft_xuV3C z?UP-_3qdjfX2P34PsJTxTm*h0rH{z_RK$2c-Ftl(S4b{m!{@?`k{1MgLAm(d;{OHi z_K;S%q((6Bb7}Ozw=3UWJE&5UUE4)ZHChH%_o}-?6CVC9lmSVBH=|2>)Du#KN?E8x zO!orbTR8qtMJdpA@ zJ>P#NyWMiV1{eR{eoyFm!3!AFSeDlJV+oK9iJu4FW20QDKz{Y5;v}b*f~wlk6}%7~ zi9r6ls+5`R+XUP117I%i7v00%(dTyqV}o9#LU05vQrb45irVfOn at x!t|0KPhB^$}3 zFZrdfmR|mBbC>@$CEb6COItG)F at N1nZ~B!I9e`!evqTk%P5J)oafS$z9=eK`w%Avb zeKoa;xCoNXr;#$2L1%446S{@Zs`8CQA at -`19;gzuNJxm|T`=USW(egOcQpLFIrnp3 z63d_QEyOnQ3ik%SWLfNNC&zR5$h1UdsCWY}@=hIrs3;D;TqsGC3#6*M$t+L275H!e zH;TMzsgQ~F3-;iCJ-;hGc#nR6hOR-GSl6<(q!Yo#;kx2lc>ty#w(al z-fy at O1hYEQcinqX116T?x~`;J4 at I^flgw%8`u at 6dNyi^9SaYxu+2SmW+Ib(6m&- at s zQ at Bb)&g*bBMOgP*6EuX6wD8NHChSHO#O8H<+$>G)<_vDhaPB8D6b(Agm()_VTKBsz z&J7yw2C2#*qdu|9{s(-CiF|(@lXPv6?#hVoEtW~pxiZf@%_MW$O*Q(Io43k`LA!bfKoi#}%$M ztRq$UV`rT at cuHp zn8w3r?$cc4{T=~QDrqG8u>Sf69n$+RaSp!Wx1?Ke z462i;)0%7AG+Yk at OsCfM4MrWQOZeqB2`B9u`j?j4WKq+gje_Qo>DP}WG@(8KWX z9yiCB*X-_`uoe>6G at X23;X^~Ln_7arEw8JYdhe~3=@dYv(^4*1T3YBxMRIok>f)jI z(AVn~lNw*Z+}tTqw~#Zv at i}4b at 9(*tT1r33$`(qMY25a+?kykuha%%}((l at nK|{?m zlk5JSt8#*sD~Mf*;tm|c-4quR4HSjSC7CC5Ezyz(a1>q`C&v_`iV!TRk+k5U#9zvK zpz=2N=-7}6FUkolnP(grZ!Yz_T(kDQR(FA99l(|6P4x~7M|4CgqStcgk>5|$w<%T~ zN=a=qPCBG at Q>_N}#wRsN8yGkqzsVHw*=Z0*qB2Ijf`0 z)YVFUMV3`|-Q;9NdWxuU|JGW+e~9&~wU&yuUn*w<`-IgG(ogXYYjrD{DLP;pKDBZu zzrSsnWaH}h7ECB|*EVa>&k7Mqzf+9{QeLKgP0 at khQwQ$}f$YT5CqCHu)Z>4Y9G^0H1KNxP>E1 zBxqlQTMgpoOpBmap0_-+Uj>IYp~Hm1TC}w5?6lK)Ew%!&lpZR9Yp2%!7ne${mEZaJ zAJAluvKo>6{xyb9u6?;iQcH#qC(u;mvdD#0x`GS0x^d>ThP&8by(!h;NiY-xWnlf@ zqBPLYq~8iAJ9}$P7spnm?*~O~1x~9J6YXBWutwj0T%)3n-+g6R6rlZTFuUx`@9Bzb z?4~fq)6h0aLUP^$(PRV at _)O at o@3o6|IXbf6E)`q&m{`)evFo_2$Y}Ih(>3{;Bfr_`30(nZ7^#aM zi at E@94f?+0BOK0lPn^CktnKozGRZCu`?obZ&;jvqO$P+7U9EWV at 3c~3JF1I4o`YY! zcp+b?&gXNYl$}zF5JkrAL3&(*1B&HoJ;ND)Auv3g8=XHF&kfIx8FlBlM5;LvkS>M` zL4VHoFPuMr;e7NtOgsxIn*Bk4xRNg~UJ+#hd66%57(Y6HAzl~{4rfQ_V42JCbVbah zXep<5!o`su=XWl2&e!K8v_T#Z at 1agE!i7{9DmRgA2A?2L+QmL8M0 at Wa$#L)}@O+w7SroZx|yk zsva|L=Iea1Cip{^t
_RQI{P_APZ$~Z$LicO372z~i=fnN|k7t)1%zHd6`bNOul zjHoNlc;Kc`wDzbj;39oAPf)P$E@(E1{%8>lyTNso~53Fj-~-zS=9P&J+@ z{A()iC!80%<`TJa;(X^^#lqRMv?|%)BFP{jQpn0=MZ-8qH!*5r9Pv=XLxb}dv}QP` zb at SQI87J~Y%vscG9O4qF3uZw_Qc_X9tYK2}`E;&xHk=7)9Z&bJz at Rz3DK@aeUy at S+ zD9H(gUCxbXdZ*8v#$n+~^6({+zX2fe+<-Wrtxe+K45Q-dmX at m=rEU5o(mtP?Je$s> z(?lt#l2|DL^rHIs7}eJ%S_xW>qf0zqA)e2f)1v*txNwc at 9JNTXrUEF_&Yi7Ht8}J1 zU0pq`qXJFSkG%+sYxLg3>Fg+i%Y8C6NlO110ENP30Sis$Q7r!hbUNExk=hKnz)cwx7F0->$kq3O&7f at 2FJ zX=a5{LlpfYyCPoc)bR8v7-*|dfm{K!fq2@!k}xElStJLE&Y9EUbbZPSiq$9)%%z~H z?k-*+G>VQk7JfwwQGF(#?o0Jk7+O4^42d-^h-XJy-&rE%h0U~ufvk at NfJIN8l%m>HmC~VX0}5<3i>QPQuE4Y0u%HR2PO@~? zuCDN!k_vz-PR3t@(~PN4T2b)}XIvL28pZDWLI`Mh#D%z`WM7eD0OuPbI=KdW at A4 ztkDS~7Eid--cVWKPD)_55OW=%JV`Xt$@;`V-#HQ+U3G4tDpk@^Ajmk_KrlFQ^5ltl zqG6QMx*=6x5di%V%Tp&Csd`+AlBoOIL1dEf=GBgYr%z#))|6TOlP8WJXFJlO=1jh* zV35 at q)zOUW>k}glX=_km&$9ZQ3{R;fas;O&^T~8#{`iUGUVYla`!CyU#GZlFh^HD{ zG?G1|<0nLHRuAg?daT?&_3qOCOWm|m>C+VcV?hmM{`L?;Bt3L$Cf(2Xn!BkyVJ=wRVIuhbdsZx9MSo{#fM590mm-l8pHtjjBi6q at yYW|oBi`-&CS+0XiX zspU)07G$2$BH=z{+TDABNAd7GYP<(8qE>yHmg(QMpyg~zfJeH2Uo3-&n*asVvLijM zr at m>fY}*ToUO`5Cj6``Q#*gkndo(aFLq_w!zcj^*ZSOIx61UKjFM8JRGS%u${eqG--G<1YwFy;7hy&q;{IOa zMKvy*mjnNb(7!$qlPo$-eoJkLGriT)8-4uLW=`e23JGDUwO?i-wNA at _9sLDme0;*T{P|*wne&8`26u@*Vl~Zx)c4UFjRAE0jTWce_aU3 at x5h zW3}Hg%#cfqDWSpJOL-?brWU_DAZ3EQrbdZsRY{AtzkGVHC8J$=LrngxLA3i!5KwQh z(zP*JdpOMscYDB$jFRy1nRPDoy17&Uyd@(sQjb7-N8)n0a-Sj(IUc=dqIzxhizAIk z{p`QI7+VieqNt{A-Yd*HU8!>$F~~H!q7T%)yV+1Fsp=Fcgs8b1?a$N_EB|Gl{!(gD zNTp17E0Vbeehka_+-;PyYTcN41$2l z{R`iqP at +VN&dtn=dvp~o|Ru7XLVsqctUOS z#A0ht3N#h1z(awJC1TqbOik$2y-#7Qv5b00*c>}5k>rOr#q)NC>P<*j)z57sj%_he zOWN$M)YLt&?Xf7i-A{bqu0DHdHOsK}X4NXKUDYDcyE3Zr7%h4ETA>Nrk=jm|m9?)e z`sl4Jx^ntUJ at kSVGQ1~k4LA!bq7|EQYg4^rV~y58f5oWhk}np3mI%&ScP4Ms`eeQHa?MYRr0 zpDy5yPsu60rKR_tewQJumOFYWuCxs-eFh_M5)q?}=k-Ntpl>ni zS6hY`x6txXoj=u7FIxXxS4rhh*`EGVecD%nVoZ;QnY;?6w`;_Mw- at b2tXsFrcWkw|E!{iwUti(7zgRn2w9va~ zT0%}NO$L|P_DQD%>b0T|patr+)tcVwHEUL{SzWD$2w3KPkDixz{NI~3T<^p){?%zs zTpiXGop$|^SH>0aw4M(S*a!M6 zt@$4*ffo8%y at u!f&J$@JqsVH=4x3?XR9b^&SI^s|)#3&URrQwBBY%=v5|~;&jKaZi zHz z>Ox$b$YrkBkb&l`#K?+2VNJDKM2syeuj&ti&1`%u(eiOvJF%u=biI1C3OW>-l?|RS zPGgyS#1x!dJzOPXcYduX8l!38+eD9G`1q$@(=9#^s|Rv>WZG(%?9^1tO=?Oi91s6p zX(Fy&vwCWE?F5VwiL8c4w2T1Jv>~-9*sn&K7vDw)PjRsYwQiAU&YN7l#HPKowvAF) z9ZS53>ng<-BZWf!u6=b=0oWQIh21x!Hd@_$ zKNj-1c0m9#Pfo5GGFVXztNUhUXY~L#8sbaH!*Zc_;!s^0$;a)RQTMe$}EDTZN>lH^$&bDP at A< z>Q}CjYi3fzsscsR%(A`Kl-1=WUNV8J+W%A5*tZ)Pv1T+ zTf3&lUldsMkm|TZQ9_^0XndkP_dm-J2v at 98wvQ-lNFDs6|1w4Ls?kc~O5#{Nq(oN2 zn2~++pm4 at Ev#N*im8;rXVv)&G8$YgIlU4t;NOE1KT&^@RXTRm4F$44J-#sX}iT~h} zRxP-2v~Y^sg403&YK`op6lw~UdsSG;!JvYiw;ef?tI?Dnv&bTyR&`CFyy00`qOD5e zj?-VJPG_~0#e0&9d(mJDxh2#BWW=g2imv1*qd+tT7zuQT2&5qCSJ#vfc!_W&lk~G{ zB|0QIs==J(cnf&hOCG{&EuU7!mDmZ~!rHgoEY)R#)ry^1_0lA+v_hmE(l%R5*ohWa zCaG`ZaFp-jS~s#Oqh!rEG7H0do*h%Gh767?hpSXF`c^uo7%#9hzGFZ)gO;sID=psD znnn3eKg(Jq*&c<>i*E6%m5Ie8JL7n_FN^nCT+YFXhSrr~<-9p*m0bT8bz;F3Q#hC5 zlF=$g;JcEsWo0F!RcS_<)89rphNhKe9CZ)<#zx~u6)>?a_Y;7G6*+2 at G7|YWu!ChF{H>q)@lDO2Ni8e7O9A5;4fu^RR(GthT%ARI5 zf;}&ln|-saQr1c1?DGnI(whIq`~F#@fi^J-cayEt2GT#ToLaNY^4AnLMH0TUe at rVE zR{$nOQeSq0W!i#^LlyQkg49_#TCwuqR at zYurz67Id9l5s7epKdXF2S-eLlH(;^c`F z$N5ypJI8uQj~(UddGg2I#))c9f;@;P7kS=(LQmt|AiOzgkNKe{j*m_pkH at QH_0ho* zG!cIhPWR66 at LW&vokkg+I(eeup}$0Sj#WpO2%kbUp33&9e{}LhI5BZN9~)v;Jo^70 zIeJtF1jJt;(EMREg21a42O1tAk+bQUqKCaTUds8wT564Ew zk0ldc9~FmCqpH!APNJBD%d$&EH1`M~|F6Q at z1Y(F}_T)I7JXup%3c>NZ}+8is^$80!Jr~XSAun9NM_hfdG>5 zU_LpaupB!cz;7QU2}?;aLEppSFyvZwdQo)LX`F(o$OL)0jb)$M%Y7A5SL9uX!@VQI zAS;+VG4s?(QDUx*2Q-pgq)TwM@{xqC;!lnF6j)gX#?A6T2y-k(N7vCK at kqslgsoj^ zW(yemBu|<>`Pe`K5M+9Xi#cQJ%?M6hpN{tw|KrE9nE-H)9Ig+K4xxI3bGo?NDY|gH zdxGdNHWWYQf?q}UFv=MJCwf;@obo7~h{wB{f%t&60fh)_R#>II*DB?wGC#GRZQh84 zg$y{Oe?B}sbQtWYo>&Z2PptG*OmJoLkPsp$N;cJu z60QS(;?aS!p*CZx!}*Z>YlTr@!ns7 at __2s3y<{!-GSI4<8DLMh9_}fg_8Y z27tCuOc@(Le5n} zgj7z4;=y=oQK9oPRiYe;p0W%DWFjHVhdYPD!6C90jRln*gJ`gVsn|8@n%gc80w?rB~lvNDmK!wqH~IEa&id9@~!l%*=` zmeo-;gseN9Jb;M+;;ej-!c?%P>?_-hAt<7+a*whTh})rq@!;TqsZn-<%F5V0*x;kA z%fLtj>w^misHFOg>v}%cak~+Y_Dwqag~gDgkazyj1xWsf2spvf5sx*!4H!7fMbJFE*X+a zbmD=*{sa5jv{Z%~ih)8iB!)`W=5S9QlRdav_JsYT19+<{8^2Pop^DqyQd`}U`5Dh1 zJdh6b_a89XJtcrPGL-_uUS9CUa$$l!G^eZ4`MW*wBM^>DQ5BL;?h at +h;xUOv_MSb!A0~Ey!JON zQ at -^00%MhmBa~6;V4v{^57YLqgd!~;F~S(c3U z?Kf^vtgKTu)j3Z+Nz~+yj%{LME1tXiWm`Pd_wIT$EAr-1w0Kb$v3ULUHC=xL)5j+=p+cIj z0PNHlm?FIGFyM7G{!f7^L8 at M2%Lre}&^yN4> z2<&Y}7ikOlqX!{EE=0{(r4qrEOy*XnJ2k&C}@3Wa*@Q6M>{2k1q+Y z+QX^$l)6tf78C0^#VZF~%j7hplyJYPQ($FDrDj>B36Tb7Z`PRZbiuHW$7)bNgPrTv&NU(VjgE3wdT@@~A{xz#u91{yr6w;_MLFXMOoNtb7?pwfM&< z!3qeD{a%5PMImshTOB8-6(o0LTT5uTMslf2xsRBVc{_nq@>)Q*iHVl1ZjElzTF~2B z))q|bqeWviDbDgji776eByU5f(&4=v)73r=%XGZmUksszlhLc{Jn7Y1z1z}_|F6w7 z at 5tub+j;${UT97 at TjfkdLaj#=d7P{Tp_+=>33|QQFt~i_&s@~!B`r}J)WAy(xrD>^ zX~YkW%T=Jg1OLNaMI~o|hEw z0$Vze(a_BiX7A_d^uL;_rAyt5mmsOt1JZZlM8?Q6MZjZ~cts~GO#Uj5Q`Rb64LRFo zD^Vv$>nv(7f+P&!Wg$D&?)_dBc<>q= zlT^{tOA-~w#77rUl*<&U8s6ek^`+Def6q$6((5`&0ppN#-=H1|nK$o~3gdF27I+K1 zsw8doVuOi)vnFe&^zMd{GSHxfpDNeMX4|>d)m17Bl#v+uD at tl@SKb8BIefK_Ra06Y z8g9;OJ5|456)@te-fNW#GjB`qP6{WA`It9oPT7<)}I*z!{G4vSrrf zj%yq&)7YpO-oY2?0w*cj&W!%$sr?clP-&pQ%I3Ij!;LDu6Hsy#nKB zribU-PQAgt9Z?C}exT};DCf~pPOsd=3%sLq!G at Rmqu!miN2z0?!#kCCaLNhw>H>Xa zwXb at 2QvVV|A!G^EZ;P}QL%n$7R at 6X%H>B*FNwS8MHJhRYx*SY*S<$)I=d1VdhrCXz)K}*W}9iBAUN4Qdcl> z4mQn3|H%oHdgVYS$$AqzxdM{XmNm|!VU#f%Q)4}4)QZF~*fY~`nXOi@?3-^g-bcO3 z6=ANwVh#)9>VXC>lhcWp6r5c7)`|hHLre){fT}YVbS3+|Ts%1;ak^Tey zW1JIbBz{XVE1c1!_#J-ubXNS2Nm((+U=-xjAz0wWeoY|K%y?@>Ulya=BACGtq03yd zG&rtYaH0Hudj%`=r?)~@w_^+UD)vkOi!Qsf;<_Ozh#WH at s`% z2$n^L>N(#@2jg%2Wio8(3J(@** zjYkLlSaq6g1s5y3QkYL%(U}{}GR)j3n$+?c)^i0M<`~8{>kV^@I|s9Kb6zh=B&$FP z8?6LXXoZDu&3QN|OR_*hhXx@~uc%u3ma!3U;1Vi}N`XNHA{2ooM{3KFIa1z4*BXxL zvyG3PFv%L`ZC0a&nHT34 at EHr>a}zw_vsAb{meZStWG32t;+vJd05z9no~vaZQ^MRU{cX}&pcHy4 z?#dNYfT=52 at F_~i+0oo=oPC=t?5YIt>8MU!?MbuB=&T*f>qVdhv{`DJ z0(kT#Q)ZJKtTn>y0Pn4A(8b_c at KD^VSg6J_P1U1o>&X##Bh6a;Ds621SP4PM+-vuo zEj2l3Lp!q_N(}MSNDZyq&0~lbHe_;kY78#(K$wklZ_Q44iZ3C`sje4@{v=TtSUdjOJx8KYXg$;mj;$;dfWZ)Rzf)#R7ue*a_bc;bMwFr%~0k?)3lVol+W2i7PJrNm( zWlvHwtd_H8!c12l;xpyOsRS5Ivs>28=9yO-f(!Ipc+dvX8rD!LiZ3VRfZ3Tei7^UL5BvK2_wC)cH|?$V6dI$kN8C zJ=n8%@1D5F`TEMG=R=}00u)KLf4Fb|zRb94PXj3uVQ;!De2AZnep(bkVPD)k+P63E zt at l90r6Wef<-_Sv-$o_~0tgg)+K)Ka-piEH-aTOtztWz-*@XrI$rv`6rvnG$LD-x9 zy?y)l+xWP*D>ZQfgO9)n9dWX%DSSo6Q+_EL_XX)8ZF{@-;MBtnDt8adSE92z2j+RY z&m;bQHn_7l?+Lqydv+IgDilJX!uoj;6fGN&_6_##+qbvg+w2+bhQ1uLd_hE`h`vM+ z#F{L+aH-NK6!MAbvS+srH)N+sGqG)H76qf)CryUC_w0_lC0zdwi!vp(leB2ut4%T+}ku9BIYygNDw%0`d$S-+7i{CYIoS>9LxkfaNz=BU=$(vCz+A=)s9ei55d~C8|ptgH=eiSC_+T^MW55rr9=@G>Xcj3fl)5vMcTywbWc}bKY^ZNDvD4wUP(I zU7O_EU3r%i(op!KG7|;B5bgcL~2NOmZk! z-mES*DU5ME*uyjuOX>i)ougfcC}S4jrY)Va&kDypu4lZaq}|P~{?1*ylwILK-v~=( zYnC6p&MHs|w=a5Nckil@@63NX at P}6ruQ!77D=P at XLWytDtlkxN_IB;ufkgpCPk_*d zN~-mTSekbhDxQF-B2TjZ1pe*pN;^k84yJ<>rTSH5qY~bvTOLS6+S%W+bEibB8*Gv2`gsQ=Jd4ZHSXI( zQVEUOj7U4fjz9&8RI_Z=tgQnDP1*35Y;a_7f9DQUH}3!afI(K!qI at _U2J&LW3DvF| zC+rLu>CkDkwW-P^Txd>+COImW{G^=&+_59<@B(a@%=@Enhn{|hwm^9@=hTGMrlcJN zAegNkZ1n^pP07AeEXivWs$}ZUxWkxz91~r7#5)DYRyo4%35E`L?A(!eRQLqMD>2x_ z4JPAUqjKX#ea3-GJL?^b+c8+!irOYsoH8c6I;ApI9 at e6M*fGC-$Mzk*tF;xXq>82~ z4&AbpP-8ad9btP0b6#s~3sk*dXjLM*HOhqTsbJ8Yuf0#(PTtcuAOx^l at 2qwVQ6Ow@ z#@{K*cMl(=QfJU2Llw}3RHC$9$ZCqWf~-JYbGsfo1q1EY|q=n{%-kJQ2p=S!ONo5Z?=yP at HMN{^+b`&?2m$ZEQzXUMiUvg z54LUJMsWOVi6PQAZ%g{rR!7NY`E5f62evT_NDXvV>k=*@``sIZ?K^gCPZGPGky#+O zuRDA82EIIWcGtTaB9XWE7_e>szDdwv+k${B8x&JVn>k5~QeP3|-x4gJ8XFnRn#vIn z$3ESLU$?nA<=c>ojfJ9Tc)$&%j)WZ}IRcDs-ywCYwc2eb4H&t at FJ8>GHxP6Fx)Y!_UV(LMs zW at Qx*qbuvJrbp50;F9QJl5|ekXn-->)h#@VsF5Mf-h8lnr{ptzj*0rkW_q&k&&T}< zeX3Wa(CyYK&hGK? z=1$I2gbRy2R#%H7Oi{x%9j6XRU_lxVwVa%@A!@wjSMZ3I at eW^F&s%Plo;O-D!;(2+ z5$L*=cowgYRBgKz=VIcRn-Vz@=%G~l_ayqh_z`f))q&>7xlwMJ4IRlCY?moz zB)1LU)Dy4bG%Y7K#ojm-Fz);nN=u31l3K?TE|4qI~($d9O7cQ#xnw+Z+KGsV}-E&M$JEUnukP|8D{1%a$ z+N1ZviH?=_#Lx&RTsy`EQcj<=&ATG4miiomS1TjA$@(|-PjbZ;ZRlFN+O|LO{Ngz^ zxg*ROZ$&%58#yXkL;WcViLPB&g%gV>)!+>eim!%2RdYV?+$J zNq*!&>gWVL|Kvcttrl>RW=INr>BFU$2RL90-|ubFD#VpWXI^wlgE_sQp)O=Px}Q%N z?Qi$FwT-rxQpuZLdBi1Mr$LWn;kYkjNj^1PvUXF}z$sXsBs-hS at mw7>9ckA8>J64j zZ)I!%R2F;XMLFFaPjI3^N)?J$%wp+ir73#R#NQBT?N~rFjtZYRPAjwuL)r#-9wyCE zTPbr?@HR||wX#kFAJ>Iu9xvP7dylEH z7j4q~U?*oay>puaq^6nl;OG6WDU+s`TR4oX?-m3*(;L#VLOHG|(DqKnG2kA$qj5xDuVo~T z;|`A?bzYWD< zXrdJ?#Z7g6lOSGNz)P!a0Dd~}4bb(Thz|X>sI}UojihrXwz;ib^%K99y~I^|%&LQ* zzThBRz3}8fJFOmgX+X!Pby!*6i6FEla at tMxl}4fST$L9&J#3FsNm6e&=#AdIi>O|% zlh!1u-eu6cPWC>Rf|-w|BmQkd4PE7VccQ&2l2E2%Pv#*PjH4eOFx6w%aiw at a&?BbBO`9PQ_D zxShY&Jf8GgmD5f at SkSHAaM*YCc9Y&Tw1e2ZAmazt6HhqVZxSZgEMoOg>EQ1{ISM)1 zo_XHe=dt2pEK%c3y_(3^9l9(MAAjAYILV)Z-+9^-}MYWI9+FCR+#I| z#+kwFj2^*{W at jd5hSM`V`8T>-^heb)50JTfWDf_&;MtjKCeBXG#OcKu#%mz#gZ`?W zeV7nM5 at ww(fRvo+Pcv{jPKyVZ5-=N0OT*~NzA+-%7!ml0u{K>Mt$^W)#tAeo94ZWb z5pN8~vYF8|lti3<#qvgKNe^QAuWu30ILf{KPqEvbl-EVRTSA z%yg!Wk}51u%c*s6L_%s`Dk(D4I2>QhOi$0uby_||6pm4&H3o*+DGf_Ara+!vw#2a% zwkZ-aGcLQPVLHv!j586&8MXdjv}N%{^pUT$vetInvXf|)5Yf!e7r*gWk^XvVIDYFG z&1s9kbutQn_4L>5OpR{=kjt&7!Gwh^izcX4Gn$)k-{W-Iy$ zAr9_(y1RUOT0B){Dl|S?p(*%Va7(d7p$CG?K?=DblFb>hf<@uU!Wm|I%;BR6jgL*{ zgId0nbKF4JI|`~{da!(2l81)5w=D~CB90K6CTHH6j>~ma0B%}8xXQ^$(B?5W>!xS2 z`NCW~HLW89*j8|oQ4koZ4&Z?>GwHVUalmpT7DZFavkA~C%wZ-^4+J8 at yd2kuGW; zyxXW+8=B3E33r}Ft~31_K_0v59pj45W>4|AeQ56TY z4YIt0y3#Xfx8ZL&ld{Q#Wv+x(sS3&H`)-$bYt&Lb$u>4j_k0DVc1@)E2BNrp@!jR` z@)1fGG*KT*U-R0$dqS?8GzY(aclmM#SZpU|UZVwC>w}A5%E9hs{;OL42V;@VAB48J zva%W3*i3C%nPg>yl^P6$-X$^?l#b(6;>}cd%GC0nWG=@cfoKyCRO=lY?~bH1!>7uI)Nzqil7f%~f&;_e4exejkbk=gGg{x{WxmSG=gn7$ zmROV78E?X-v5HF9LZLvI%29&tS9n*|qHMX~o-ZECk=SOY#ZcxA6p^r8n!lUhE#<}J z+l0;%NT*u88Ckt+Z9tqo$~?gD*c0xwBW>^6^YHE4!nSJbt1Vl%ZrQ?T^Ons#4zG8^ zZ9^mPH7ZDh7dlZAPwb>fBjJ9q`Tf*ki7RENlsVp)UVnU=>*xui<-B_5>Hh2$YuM)7BxDr7hKFP8+1%FxR4tGwc}zLfFyS z-nh)Twc7GONZhh{^Ckp}MHiXtl%D9qf$WRWA934A5BfXUgqoX%SQT-tGi`+*aY`a@ zOuC8m}b at MM2fu>un9|GiH-0I8#vsLj^}A at 7G4kz+U97eXQBK zrP?wy>*J=L*db{OD+wsJivPp+{rZbFyMn>IoBb>5iaIwS*!!OqZ;H&>g?N21zy#sbm2y<*&G3(96(S#KI` zgp!IoUl}rGn9(#HT%EQIv~b+qV@(z&iW}v_LM}Ovn23tOP0(mIcQ&PeVLP3L6UhY} z+<19sB$p01ZbDj5l((W`&{>Ly2sQwe3o;eSW^=RYU)bZgBU&OUhtjs_s83sx+?zM2 zO}&kqHf`Jpl4FZx^4UPnNSpG; zK18-WD>PM9i_W&Rb)cN98P%85 at LAkA+O#oje1jg1MQ{QO4$-lTBjK<)199YHuPy&v zk>t4wccwK$fm*$3AeGa`mU}HssvI6!!BZjH=)8G|PH|JcF>J7t0Ax;!f3F68Ox?CM zj1y?AS=iJtsYavd+^%C(uBplKL}-ZT#<;P+VdI7kkbYHLj(lM4TE{K*=Fuj6oosm8 z*wb(^t1T^EkkV}J;6y`;4>idfH!#wZ*CJ{3u3#cP;?@cDnB0_5ba5kccUUcQ1H<+S z!mWxNfGPPbr_~$NhP0zA$XgAl9Lj>e at FoaZJT$KNu}a(aj;Fj*)9ntMf+YyR31A{^ z7<{}zC9o#Z95_vjne3~^jYE{n8-To47H%X;E7r1Df+`ymB<$lIg-;(FtBqppn-cxH z9a{NT5j-8DrH}0wk?LOI9A7+rT$Wem(+*_&fD%G`7K$k8i%ns}Xyb;kVgBO{8$SNH z6h1|@HUoX{aDb6{9^4?*r;XKy1q}FjLtA3CL0Yx&Ntt+Ah;xhkX2XyP;o}6%v_#RX zrdUEqEy_}eO=P;R at l?V~7C zZ3-Jz;x=u}8{>xXaoCVQj!xrCkFuv~pLDfVzuPdqHY-2=_ at j?Lwl4u}3c{o9!kVEq z at M_pR!6IF1(gr?xL;SdhcbKE9Tw$XX+~mG5C6bitY^Xkt9~Hf~pmzqsZGEZv=ZsxA z!Z!|=O&9r=8ASG1$fy z8A|m^q*K3uCE+ at jii*3LW(#>ODm#k4xkSRQ8c-|$m8uaNUZa&h+iVf#PGSYyD>J at U zWYWe3)r*ZK at Ebl(I3|2(Up2}ucJt8`&g*LDEaCBZ(B#i*reLskkO4L|&7DYRaT z)8+-7X68_~4aJc_jWntEk)nm`#*Wi!N*M*%Ba$fsVME+F at O7A?Ny(NX(}alx at cQ;e zQuV0QV!tv^*B|GP;)ms18T)kkwin5Gk7iv=i6At7+`;^@*hRDf^;ljHu5;LPm&PtAK4IeGY){j2qgNL;P7JN1YPN~9{v*C||C<3R^>7%Tn z$+J-3C;1MtksYT(h02_+A^-LP{lEfG;VVd_(n&=fjMTl$4`h}zg?7?QERA?iqrTLn z%9#jju;cA)BJEk!kB1g6&|c$*`~DbS9rZLL^0d2CIs)k!s=C9N65>L{bE?0Jp7eXK zs=#1Bu$?$nJlM|zapUQEyq{ZwOk at OIm!NcX6uO)k_w%PZWT!4B=p!bT{&LA$32MgtKz>KSM~=nD>`$sahR0 z7n$Xxin?6|7^rLq&Z7<&2LPu*-VzJWR}`_@=9o$WM$7P~22PwXRx>P}M~(Zsd-v at V z;T^`!*D9%_cN(lOo}bp1BD}DGYwURMKF+NTnI`g3x};+ZWAe$G=rSw7~I8#&BAXUO^l?Q at bpg;^PC|&7v8dK&zc{E~nokN7F^j zIp-L0O{Lcd#ty7O&tN|qaI~&?F}C4q0aSXRdG4ZjAhJ3r&!5(xB5wzx+1fl%8=`Sf ztQOOD?pkL$2YXC)=ky4&UIU4i209g<4|egX5=du9?T=0}hrOI+R_3d;*i>by+Hk3# zmik~7kS8kx9M9(rDPd$HC)@Y#QM!6Zv3Rs!W~#@Rj@(z6L>%_n+Zfahbx-s+Q}C4f zjp;mo;y`KZ at 6uOP?4V55>1#XEtYfeKeo5pAW4oY|N`Df4wbZgpk at QzA^fpYTL$tdI zWol6=&&ML#n%_Czy$#oXWtN at S)^XI{ZX>DH%ulnDv5{@nWE%2Q at tVVJT(E&?^6Uks8I)*a`lA z1y4W3`Buoz9(p;uCN=%Mb;L?Nkl00oLYm2`K4#pI^ge*cLoY!kf8m0cO&HqlQtG}| zBU8)P42m3f<*9~6*L#){-1c>wc;GeB71H&Zlun52{C?y(YCA=stL%LdfB7lw>uIPW znspd85-OfE95YXqy|<=Dxog=U!Hra~heSL^DkxK%KkxPysr2QIsY>rO*~2kE4$I>q zP6GtiuknuWYf5Onq@|PYX;+v3*L){o64-0p)4q>W7fafsvi8U**jo~cslFhTf?Zy) zDsB2|pio|gNp{NC9I;ay!vD|Kn>ERGB$4Bt12_X!~OH+WQ6;Ppsmu6O+`Vsu)O+8{mKB?gNukp{O-ehf+}8p5fo{Py8D1I zvA&rLX;}Xdq`216g$qszH#iu#Tu|eW73}sI#udqfZh_)grDMEi;~ujpY^_#2Vx>?E z8yMq7TzYE?$n6>ZHX?fDy6V4=;+6s24`ze$-jR%HJQ1nT7PqFr!m{Kf$f}!X(RF&M zk2yD);g=NN(7{V1byDs#8-Og-m!%jouhkfR2dcKHPdC{ymL$8=C1`xEI&C5f!#nAp z>2-VwGEdXpTMWdoI5SS#vr3wQ`HQ^yj(xa?-Vm-qa58YQa&cSw3*f$bab46ApzIzQ zk8DBV)`Lb{6Hm2!tEzGirQjl|DSH1^St7 zlVvRI=jGwHWn=-mSj_TIL%JleLrZ8M4SBc(zB%SlD0Qwrx2q54J5YBYQ&n(OTtae^qit)Ik#oB{o%Sqzy1gSZ3-eTk1&( zKjkDK2Rm*56%G*5&q?`^-<|(BDagwi>pDrygEiDr;)g|DUG at GeZQl!jBpb04aKhj7Qw{eveP8|=eNb>2?ZaqcUT&N z&@u*X3`_E}Xr}D8pO*iXEjb#-k|ZEnKij7OcvKv_0#&3*@zVi5(7;mIK5H`4I2KHV zZOg8gF=(-g<$*2h2)6{GjyWh)NA$wdh$F|P39|0;>cwZftwrYL at iuB%lH{enz+hUJ zLU?}AEs_2;xBROKV$#rXExDbZotxSs#;qhmDr0*)W>G!Ow_xVoqw(O8mM90)dJsUW z$WX;t#_byG6;bk%27Llj?K8Kbpba-rUtZ1tGB0&u-_gAl^vwuBJ1S at h(72p{&{DPJ zN#AWq`p%jX6^~|Muggm{+a$G#&w>GnLy7S8C=r&EXwEEeqPaw?mYP=43ZS*3g(9Mi zykqE7Lm1yJRzP$SJQ0GZNID`=l}mP(2vm4hW at F>A9fF}8GQ)I$0u at y)jTV=d5cZgB zCYzS&-PUXascp->-0o9BTU)tE-ZJJ#*0gIYzAqWF|G(EcwdwS0YMW47)1{# zWDy?NUtC(Gd;zAdFw;gS(WrokykNmcS!DQ9fLWe}Sq;cB6)R$TSqC?++PLj6`pcPi z_IcF+_eE$E9D`PvEM(hHxj9xU+^44c0{pi-D3ahD8taU2sHz{n89fE7}4wIdlc#Cwr!ZSc&&|}9udm2$i=iMCoh*qh(K?2PscT4 ziAD^NY5T(JgD_Q9*L2u$6!X_|7n- at ME|9&8WD_yb}_ry~a zHhGc8&#Ea?0X}l?0kBz=q)d%Lo*_4B~P~LzAV!W2P>K*7ags2*3HRz>6o{MZPZ6 zM80n9$H^v%(kh4Z;c6`$nyekl7(m(ih}IJVtmaHERnKh at +{2)bbjBFeiPONy zhl7(<2%%Iem4Yl0^F;ai8tm6=^`SA^g at dEjgG`Pw_YsumX5LEdm at YC}H)!2CSRo0- zgp;zt%uzDk)I66CPdIwLmJh{)6Os(8ULjDD!nMYf1teGw;I(GMgUn>1<^WWg$f|)D zR2iU3HLgtc`A~guxOxyxQFrhH(m&)PzR-ZGWq)QcHl&8yF?wEAa>jDP{~Tsr{aV;7)@nBjV9avq(I25&kKAxL#S>x33Oo3rF9vp+K zusS&a`jQ|ruZKn65&dH76Ya^v{D+H zX?5ekD$hhcupb7`#JQzcNq79*8e(9CtHC`Jkc2r~uMFy_fgp8yCvi{k+BV9MjkxA2Q{XM;Sg;$YPh zxhZ4Z66Y8!5;V#nL?FR=T;)*t08i$GUsdi`@i4+7o0BOCAS)=6vQIpa_lFlc=ukmS zDu}*HYa|jn4ihI{GPe2U0RBy3Bf>>h0#!WN;7r`OzmekNbA8ALSAshz`iDcKHLw~H z25CzZ9?P2c*J|7b*sTtYaB2RA^Kw at kO2yQ#UyU?;I^3sZh?fbX4TVHeyP=+WPG{IZ zUU}hp`gem%3vH=_be+|jQmBw={mMQL!?L1OeSk*@ z!(c7W`nA=NnSqfcirSuNivtgF;2*8KvXF$FH}L^qFdFjJ(0g$5t*Ac#Ty4#wHS>rh z&kW#m2I{FHo{ofk{<&hREy)3|RE(xFZQ+^Fg`(*QQyzmk12qyByK2Hj4#LJuOoDeN z0Ocp5@&H*mrFle_sZ at ua0(n>mjKP zc;;Cal!6&_g73_%RQud6)7r)%_#yn8YyeC}N1bfUV^np9B2S7Q?cK+K=WNs-WN4BN z4#vg7pAL2-r7@&I@(N7SBmC at XI{;WmNNNjVI*)r0qFas2dtMzF4B@~gfj9ujc+98m zxV#r%Ejh9P2#T5Mz#A~{OGRHMmyFBPF7YsCjsCYA5D z5?)@{;96O?ZDdfsh*B|D`k)-B&Bk$GHrvmJYxNbX^jNkNRL at uaJrWwsvnerY2`Lan z4(5+|i6|zk|KV5F0{tDbq0A^7PAv50{yD63Y*0fE&V1(~ZU)05a;v0HDAQxf9cr)0 z{U#wKINXlTb81`n?Xf~KuL)!MZDJR+$1`%vJ5^tE3o0g0Dh%&+vVoP6N70n*g;%g8 z56jM#W?}951`--5q{EL_I7R4+MN6m}1f*rFK5q5I-Fv7dFO9AKZiw+M1I!#rRwBu& zEz=)10HhUp2i8`zOc0YZ+pmy$o=UBmPGNO(sc+m+IpN>^zU?|y%Tdk@;?KLNB+K(18HizudJ;pITwwDsmFR_7SLDI<3cV!^s9xLIS& zqDZ2_`cA7w#7qx^pmMk*t9Vm at wA9u;t7vz6qXA)TFq>p;fbU{xx;O91Q}rY(cIJGC z=B~sXQ?IOQ*VaN5)$Fnq+p|cG?c=L`c at yjYQ)^kpj_Gx%EoN;!n{(y`>k|Ed>>#VW zxgnR`R6)hYzaO!-&C4iR1llH|!^?f%hk>Wkko1EZxLTE)Rr0cSH2iMrQVIOmj-^$Y z*MDDd#DqmQt2h9~GOI{yBrjHBnH_r;{`Dz>WI(AQOBr8HaHTda%KRk_DI+r2y&C}) z&{lU7jH!b6z&vr#h4xM+Bc8p{e5bSjj`ECSOTwAI#&@FdKq8SR-jW~h!_HPgXvli)2aR+`ERct}7S(hNBKZXH8e97KUWZo;54oJ594 z^kK2Q69$2^*hj62DR*&fz?yC6<@L%D=A`Jxve|@6K-5wjwkrPT{WH=VrKw7NFl%!M zym^K99~6`Fm6rGdsxf3y^m#BNUuK=;5R%ZG;6W%|{$0f)%;&)q1Hiyis;m?a|1dBC zN|`rSV^~{ibLElUDZ;cl_5-wDS$6_4YLsa33t2PReuvIB!_6;r&}ZH55TV)`87mdj zOAg at t+nQnSLdlC!d}&jiJb0K#!Q?pzE0YUvD0zN?w_tF`L80bV at V_!hf<2by^6G=- z9CVgSwV>0^b>s6)y<3!LF*a&?*()O0TYYK54GTUmR;}#w at PG!^Z;{8mD!N2|D+DV+ z)x}DMF#$v{lsEEzmJwrkC7a=GHRZMiTSnpH0A{6JA>pJ0lEDvP2fXvaJOO)=wKqu+ zyM(zQil)&RPFcZV{JsyyG>6O9mpD7l&u>2WJXv`OHS{QS$Mq4;GNjD7J&Gb1Z|P(0 zfCanp`93F(&z0LcG6XMo7vOd(sDs6|R7k?)4+X@}KZkoA*h=>;REP`-WE-siG?>WY zt1ZBu`ZTaGOESD2 zPxk~d`YJfE8*hW~CCiNErTknv%n=ZCd2g1~ZO&w6;jzRk-DDAdw`ppB;rU8c*Ml_$ zYqNDq!(J7y3{XN at wydC6TQ8;$Yq72bQqSj8o0HwRMIEDCblCVq_XZ9O=3vZ+#dbmKalcjJ_M zYT)*Lg7;?$QtBG_pU^qEbcr>Wau*``u64sit+i4}#}#5t{SV;=NHD>>@G4v%b=N}; zf2pu4WQ&lR&m3BNW-T+}ZYd)!=hs-6k4qKP2zha^u(+_exWM)Kg=S$qzku9YKn{}` zti}>X5v`eKO13Z`*);K`_V&nRu%=*nX%6|cu(Yt4`DtgtGeb-zD8t)f+6|8y>42~} z;<(*~FrOBb=nTuVC0h`eI%+Z`a~x}a0VLYXvR1x8qii=BlH z8+&1%=^xq+tpFL*rNxSNL%4}cSnMsVgQ}hjkQ8l|k}adFLtMy<%L~79bT(A_at3 at x z=3qsZ)PNk`0?B4!#D>u^8iEPtZ9TI$hhf*L4l%JXE2+>v&0}oYMt5ljZD5AvZ&p+~ z^JWPhS?SE;6ZWUckP7uWF>O#Q)%-%qq}7X?YKTT6*endm+5F6WcM6E7xu^^^>Odo7 z@$Y!2dC%fC1-h^(CvsC|Gd!Dlm=!e)v)a&aTEonuSq$)iwbb*2IZz{JiH*!43_PUV zAg=+~S{DVY=O=T}AZrp9=N30))O?wt>cFT*@6dc`%(COIY|GtUw%~G)IQ&+#mnjBP zb%iuQvvSbY8igBR)HC#8VCt;%Q|QHJm>Yw-60NlM(uIQcU0*_Y#w`P#s`<^i`T4n) zD?upO5MyXiGh2KKGDy=M0#d;MVSF?d1q9k1G(j-n at BE{l?}IK$TM$(HX7N`LHVh3! zwe1=oU`1)_VD63{Hv&&|X1=jVEJurO*2h>|-%OY5rU|AxNO5(mQ{ zdCsCg at uCOQlWTR5NiNK1(^{UJa8SHd=mFwc($f$-X0$Yi-|88SXXcbyPl&>meNONz z(rM-zjA+-Aa#cdag-+3O4p(S4>daM(Pld7>e#hjHGYLD|Q<#So^Ud6hP;SiR$j*#^ z2tvKN!K}0!J+gvmXTdIX?wr}6gS~;))7iOM=t85Slw;v3To0^?KS0vr;wBxz!ekZz zg;{q!|Bh3erZaevVx%`mvNtFHuMsN3JVeI`v*MU$-xPY);`|nSU!3o8415R;%%NXj z{pbi0Ah2g=F3tWjH#-Xn$v%HLz=p*Dh%h at pGxx8M&z{~4t;DOMHba23+jo1BeDz$M z#U+pmA(-8AVef$=K+c5C=%S6O)*`a|M|XaGYJE`fEUjra&W&c#K7g=a52yHsxq&Qo zt~Na4>||~hU1-?4lx2$*vb+SNiNauJZYb%fkP$gzSx1BAaHNwDAQiyW*#KD|WdgWP zJ>0VwkA%a9#=DsBNvnNiDUf?UVCk3+4!f)Isj`<$T#fLda)n%1&nq+gDjlAd_i*RD zr at JkmLlTx3Y04UY*w>z7$J3}#DnM$X*pdWN zN~WYhc%EAlCp at T&2if&^?Z$&y0OO?Ej}>?3yGHv~m{PFA=cXt81*8`($CM!#_y9vZ zr!VZSct8{nJF>&(NHSSwtKL5%lzAoYdkO|{1m9jBrq?MZ%`U)seyUWEqYb%<8h&W~A3z!{S5=7CPw6pe}2c;H_wSz+lXysDG3 z5IA8i?Rlc(g@}eaVG}i2g5X4saX%DQvQQQCEX2{z_R2ck2#qacW!b+?Ws+b+X{Ax_ zBJS;h{fb+k?L_*G+0myoG3Ue8n)cc`ZVg(-okN*eT~ad%DHdu_XDafLbPZw=JZrT<4CSp+ z0YKYZ8bb}XJEL(Wv*ieDX<&?We(YgLskEV;*`KFsi$RSQlXz at b1Em={+gj8IR-VF+$Y=_eDMNFehBPR2?*?{y0=+Rid#15X7sUOH%b1d}IC>NLVF zc9h`WkT~-fcZHt7 at 3Im467t4Sud4P4uJF~XuYjJFNS}$#rao#*wKFQ5N;q-1XavjA) z4uPFWZHVdj8iirsMRo?!a2*=PR(kw_kNyWurE(Z zYbxq4JT{?2d!4jPe{^4Dh!a2z{oD@~WNi~wih;NAfCx31;w8oRewCzY#}j_+2mw&M zi#?7dFizTqFT&CwEVr at 6`I~5G*f5gL?#%M6HbU!C!FB{8?5Od>c2Kn1;lNr3TE;Lm_29Ast+FqBup(%o(yY!5%H at YHh3Rkq%Hn}c{q*>P| zy72jz4t}dqq)^*RtcV+3ArLxu$kO at 6&<~-dOnjf)EHc(f9~uNqYBwh>=QEjPh0#h) zJW**JYE4P?5$8vl3>HyHmBAIi zCF_eI24Fg)M$&`H8`gn&4P|7}BdfqzGltrs1N)hwR%U{A-K1;vv88CYRMfJJs6rJq zzX`L>&kNK`1`k}-!Ni0m$dm#{kIoKoM*Z7-aXLSiZU;We6EsjX&7-j*b+DY&GLTvvv9 zLKQGeqy{UDAZCG(svj$vHs0;TA{#GdtQ0< zZlz*Rl*?JQCyGvCkP#*pZ6=&CfhR#aUyZT7u&~unpSBKI8d42tsAL at zs01(^q|rh{ zNk+vvf$@n|0XYRjh*u#77&rqCLIFj?6gXnWY*T~I3Q5;!+N at bEDl7Z@P>Yo+c2~56 zl^H5_A48nLZ5&jN-I78kBHI&${q_FtiYmDi937SDZOJVdAP3Z?jck)rwN6aWE7+BK z#fYjwE8?VuP&RAcQwg!hD%S9pf_d8i97j&fwU-)KR#%&0pfX+j(MdINnq6Twoc at n| zG)5Z&N at B@6^8sPszcRbR%zjZe?Xa`@M5T-Rm<>kHBPj5850))j9dE5-T28p73cw at d8s$s69a@|E}hsiNZP-IHW8!Io*C15!vjKFwSSq# zy)1~g*jHg7FF>wnYx`H8Wdmx at C?kToJ+uw!7u!}$!MKTlS2Nw4!o?&73>B_(iDh}E zExazFmiH#n5V*=??)5Ahp~jY$LTpL{`riiaS`a?FVimt^f#Q0zXO?k$8T?Kl==vc6 z(-0#Du75Vq+rCmG#Ch#+b#N2FLeE at D)1n>SXZwoXPay1R+qN<-01szR#;%pONm43?rXDZu*fUb_%H#Z1u)86P%|L*tOV!_K? zUsL>*y(_BCr5S5!k4Urjem_a_euEm(g%OR$w0DIdSxjb;ZjEM!KJUGviRAqUU`H^U z>|Q~v)BYZfYugG(A$zRz^>wTo9T+_6$t1yR6PGIvtw8$>M~qmF>U+U)!uzg zxbv2bj6z~i-?gF}4tTE!?l}Vb(JnAGx*#j2bP|(9L*@~<@Qw9nECJ(%i at IYjpU;Ng zWENQmHTHFVG}*cYUSnBa3&!)aRZl4jEv{p=n(M?Ot;|E)t^=ZBcHEooCH%(Xw-)7@ za>>R3m#C4>1J%TpUoIB2bwumjv&`-cQyS5lC#^r at vpG_pu;F;NhfW(kTk!Xl{y2{X zXs&RVb>$IPx&v3E^K2gj^k&J at 1Z(TyML=ZaIl^oz5_V at Nq}%k;LW3r0es*q?H0;ss zS>_Zg-yLv<1 zN9xT+&>$N#T#Gk|nQf>FS_9C5G+`D);&(*T&K7mlkZGu)5{2)AyENND6Q&*{`OI8j zW6hy2THuA$^fKT3H>Ob9aq$;1g~>!DEif`49Wh9y)ji|^sVZ56`gM^j5b$BHp4~(n z!qS`Rjl0N=6}@`OKq-_oM;3okLu=gY_FzNZW`49f#x0k`Gr4%i{(V$b`I#G6ZG&qsF5&HdZuh#|7NO)LMlnE@?q31Zz zE3+3^&$8j%(@36?V;;!(0%n=`)@n5e*=QN41pv-4zj?um?2wuZ;%fE@=@LG^bb7-s zxB<{eM4A9p5w)VEmJsTQdXGF1zF&G&qdO at RebLQAtRPL~1AshWVxJL34qc>z*v-;d z8(_zto!QtMnc<~g><0YA9+FQSWNr at 3!^1g(Omi9opa7UNn*J&Y*p?kE0?ATkEx at t{ z@)e at Yz>tTtlPUbi_RO8Rurfx*kOmz<9-IKG(9LKo>Rkf`My^M_#Zmr7+IZNrt8BMo z0gRSt%61uO+$l=n=#ZHtgv^IL`zIHZIpo=2r7Ox$t)<@jbga}<5TI*X=xY$Zq*-#V z$Oqg_U{~L`*M$nikJE<%L51ixO9v^OD~xVc6yM55j^q1sNG5uaR9jb>pnCZ333?RN{`9ir{RnAnPDgb3K at Z@qt&g zt9r~v!)z0buS0=>J<+SELxV3uhtL#>BJi<42rtY&6{qByF_&gxCL&W!txA8Tr1rqC zN-U$PR8*78ZtQN7Do5z}2*f5p0UoM*7JlmP0L}0O*BEYHRPjRK*%EBeSj%J#X9gD0 zCUITJ=4NM1K!yJ~>PHcdLc;#KeQQofCM>h!cfqK#1+6uT4=^0Mj3dT?Y=zgonbu6q zw$VtVcYzYMHucy{BO!QTLh3bcsB2kQx22r;Ek)*GNDDGyR^oH(^W!c>Y&DJm4hQVk zU>h_Y4S6udt9TVAT}B$PGPNbb0viNudYBC=;({x?Vnx>_`l++yDEpHc(jGB3S*si} zyPhgkL0`%JWk%LQOHgxMI>;ynj@=DcA9XA*)KD{Koz$(&O%bI0LnR?X;uBo at XwVFz z9>+RTvw0K7Vhjm1KaB@|{%U4Ko3;nWBqiFZLrmjyHZ2q}XlSG~yNEykW}txq`@>=i zZHGiy%AljqP!!M}lfwnvxTCVwT&^W+>37TOE|ZK{Z1#MxA2-CMS1iSI8JOo_VNur; zH*x2$TDC1qzgU at NzH%p05?Y&$?MZG*?`Y9hhm0{}0x~%JkNtNt<6$Rli8>c}ku=0d zHmEImJJ%c>3-kDhDK=(4{K&k{&i|&j`H}TxcRlSKCMKYHikn2yKwRecMPv!3lrs0KB<5e$5#&G5Gli1g10wM$mHMqE7-MIyUp=?q8E zG?Z9yV3{nW*QP$BUgd?komOM+Vz6K_rWZfI4egOVGZ&{RiW9Ws$&sxeD5GWK(m&{3!`)O*PSDYGrIX;s>S|*Md(J+$7Gg9BGbhitzAQ{=b zHtRPHpt1Rg**4lS!zE6pE=*n1B*kU3^B0p;lHi3_tOUJD1`<*V(`_YFhWEP0prECG z#16;uGw2uy(2RyY*LpKS=Fn{-%^br-Cl#LDrO}F!84r-!+{ju^TkvBcM9L**2&3*! zi(lSC2((#aFh^6F}5S!3%Xmbv at nk9)V(bVR at N+_W$eu+mL6U1EW2rVjpwb7^krkfUd-VjA9ZYrBp;!4)5R>Q`|Fhf4iaYC+#Ii;iZ>)|q;d4dA6U8mfx=plMDS znKmTgZigCvD-l{>8~W at DR&2Z2gYi5itZ~U7cra~C>=GZ`&C0=*JvMWU at t1#$|2s&1YuEprvkl=yU!%rEa9oBvQ}WAVk%G)Sy9x>0tS_8ceaWXn|bHIaCEle at A4u5Tm$9eL!+2p+I?WZ z)}pJ`q!}$c6V^jjx!~(fHM9#=X+(!s`3XlamrQ;^5fkFl4@)jb%~H}0G0iSA#cbc` zH9Q5O^Vo&WMfq(2rhm}4T0G5U8B=);LHQnX7_TBxfK(N01t#>|C9atpY<#JkNlPGZ`FsM<=^{OW-wLLhxg_-i}u^c`l=g5*R&~P<_pd+KveJVNl2|r8HPFbU?{C`TWwTxWW&0JPj0hi6CVzcqo`zKH{ z?hMmn0H%DA3a$6p$_FHMeNo|C7ojM%R*FfpF^h7h!Lu#6l=c+g*5(MD!605aNO_|` zEYEO7V0h}?Op$sgC38YwsS<;Mip6KdNpEO4NgI42Oh^)2k^GHjol at vNF?iUNe4-_7 zeag6-h15r2g-luA6?|-uWlj(P-;O?)bqSuWTbU&iI6W&!_n+Hgok{%ik4-~*)n~I) z$bT^n at S)K`5S$IxAOifS$lhT{#FMhI~o}VMN%&2`r at 5d6Y(+ofN!%F{$pljxVmO2l37W@ z+MwKk9ev at M(95q}T60KKP4PnZCiC=X%3PZkX*di&8~{uRiEl(0+Fq4GV3v{EN9NMcL?nWz%NyeZ>D z;{%@+mH1%o*Cew%=~YqLhZRR#tn$Cv0RI6mbmP|t=|zMyA_vt^1|q-s=z$kHrdu<= zqtRpq>2G9L=wg>Pgo2H_z4DL(1AwgGB9T+^OM at xEbz8zf<<|f=7{54ru7!i~1N6k7 z>S^DHBtJ>;(qT?-E!*HhF^U(PAM27t$*Gg}>s<6N7qlf9DJkaOze`BSDfi>A@}&g> zXOmbAu@~8>1gU^-zuFcbw|Eh||4eC|75?S4k?NEa5>y z%}t2E916pfN{k30TTEQMPG4A4Lh$fJ*EaZ62=Iva_6e`k&~5>XyHp5HpLD*|En)FVEWC6J at vcB^qaGC5uYyo+MArww|P99q=UBe zp at kkTNi;kE2C7?DG>{o+S7OAjFW-qPUm*0q#j7vM`uc`1JNtci8!f;3*n8`5k!Tlv z=4?4ny|-pJ3I+w0t=R at i!=(-9fHJPPD>BMuOc%IZq`8va6AoJ7(O+e)#h0Q$nyW0n z+Rm+KR=0`_Qg;Zu{6 at cv>@+V}-5af0qOWi&F>4R6q*SjjX1 at +PQxrm5VW z-t$2ko0!x71vIr|>uZ)Wwq<%lnSXN>n4PcH_gvGzy$i6g+*+d4Ksx zb8Bc*gNc at 8;+PU_OPPV|)jNTjW$_7g at n#vU1!iBZ?rvSG7e$HEUO(D}atc)3-2szy zRE!yr)`jKO5tkQ+0se+*pr;J4DAxlcaWI$|CY<}${Cx?Yf at ayd;K7UI)g~c|i%{ua z#9;VRb4K3=?zmM!Yh(#esJzpg}y1s${h)SjtSGRMVx6K5Lew&=-*WaEFcF(^R! ztIc2F4T(|i<@CjKS1#L=LfrZ9?A=n}A1=LXz*u669ss+;y2Ja0M1G48$-2u&q=AUu zgskoQ`i2RnL88SrWx%HV(}Wez*qPZ3m at x>M%u3di6TZiM*3>TKFg}h9$~kGnhP$CI zrj(x4y*{E+ba%3LMQJ55y#{@qDBU=^mjuWR(6hLkK$8p5R>H4bKe z`kU*0XRAq@=vxX^1gI>iq`@5`vmbp at E=J`_?P0(|nS~0g!`OlQv?;9cb0gCLEds6W zdR(3kU$F7MVaI&AUeXkpTP}L_;oIx&^kQzwa`SH3y`X86G>qe=oJ9JhgdRmbkZc&3 z7)hwpd`j|2F}(EwiAY5$!j{?Fna{APXb>9Yhio099U{R^6XZ|;^`5=;zuQ|U`0F-bw4=ml_O5bn z{}^=l&tZEDcPTQX_CMLL|I6O|Pxjyc&0ord{o^zLi at o)q`O8vk&-|||f8n&rW^QHX zg?|(4G4*yk2QQOKr8Hvizo^@^v;I{Nyl|pxvv4*j$s!QsQV6GpHKvGLR=xj=(ZQ>a zDr)|BJM;6!5-z4_db)MOYtva@&k!9YcEpg+I-M^~R`H at Kw#LrY2#>S&RKjrlhzuhW1DUH%oa?8&5boQ zRU(KesPN+(fr%u45MsOMKrMZM#MPx1TK++;*pi^|$XJ7&F0_DS at de- zs;_07#6LTdYZ?$2dP})3-%l7o_22AT7Q|cKwrlM|`I2m^j2gl+{2`vcRPB z)g>;^@PVf%N{iQU#ffA8S2eIU`qNtDl?eX-IJP+FAY0_461S{|la4Pdb7;5lE$eZu zQ7SKC#4^jh{XwDlu9jZQifaiyY-5XtNL@%YAyHxp^=)fi`cMt7Uh`n8Fg(3enAE#Ju6 zR(6$A2Qo~bv+un8ueF`_5Bmnh at 17YFw6!Tgsm zeu)C~%$Vp4lmZ++#nZkw$m5L~X~<;p!tZs?gI}%qUJ0;UY+tu%axhc1;vz-yAU?f% z$tv`*;sD`jztXi=?)DFTR1AZ>nmuh5cm at g)QUCf+p>t37-b#LAK#?NyG?pC4zl;Z# zNNNG1_hNn^4E&e>jsAS2WtAvSwa_XFHxP<4+iEU)yL27=bO4Ogv$ zM|?f7 at CY_d+a-~!Z;j}$Cx0200`zZE$>w2Hi)4<7w at HatGnYnckkpo@%DsG$M4*JC%hAGP2RbMmz(dW_xta? zkH>rW>U)!S?}dBSyS4Wq-O25D(mP{OuHGM`U3{;DCe_^mh~k;FhNop?9isc(D-F%H}8!hV7gbo+XocyqFHk%-JZO2JAjeSt#C5|gX-QG#NxZ7 zyYIexx4O#~jf)a*jwo|RoA_=JNS!+k`c~*0 at Uw|eiOGB6y$mTf$rcBSf~LAt-R=NG z;uvmDZruzwJ8CCs!`<+1x+~_Rp`LG78o0WZZq{Nw+(U{&>ToK z3)@iCm+xd#GAsz4 at Jx`Xf9vMW8#ixakRdt}Xj4hLJA}%q6B$#y9cdo{Or at LQ#^~ma z8_riSORa$ejjw`mG9@>>bSvM?Hzq{4zPCxXsG=ffYj!fycxyy6$u~D|+`J({lJuG+ zNCn_Vx9{A6;uCoRL$| z$#gKZ1#x;!1?#9F=j7%Myj&M5Zd*>dHYqFEb~F&dbF#M4TtOX3I@}JhqWn&zCq}e~ z8O58`jnQ>*65MoQMrPdZHBqXK;<^Te3T=&u~JE3jh-c0nzI> zu7~U7t&yrSIzeb^DVvs(qc788mw3J7;0H9OKaOr8tOYZHaC1b4L_VRd*wG7AOKHqY zEW}wnLO3L at Nx0r1KWJZEU~=bffM*4Zi;)gsf=_N@$c#P|AziQD&1O1e$%0|LH6S>Y zR>0Ob()DMqjsN`L{W<E8M`#d!p8Wae z at bmDepMU!4$NI%v5WX9-?RVk32=T(> z35n-N;i0e?{q!^09{>2$kMYO)hfQ>He$sa}_!*G%ZypEq$Pcp%S^b!P82t$^{`|$@pe+W=w z^?m)_ at LOxz0J8XS`1M0TQ2i7E at c`mWtOVFbge`p=iNRy(uYOcL9E)oHaYUN&hxn)P z{e*TghDL()9nl3ygl}3D#!zH<7{13DRZ~53s24!;siO<29lA7SP0R8?c at qQMw4S z!v|k~O)E0Hj)j-%JKD8OBf_snj~_jHNW$Sk0OlLei-{Rw*_B&G{w6_1Ot at bnNIU-O z!H;3;#uiORbY&_kd=p^7;EtNAfaVBjK47mO)GI5TRS>s&JOtfG4;wV99!$PM42g1s zXll=(W}Y>o)zWh-7|G}%7_J_Szy*3BmVA%s5Wa5!%({gTYMpJzkS>0mA3Xi)!B<}y z*OFd_R}_|IPCrKT8BGYuFY^4Ks at m5qD{aKq-$jh$p(AR zX>&Ue)P8>;ebD#Z)4qM3gOfGUw7pbo?di8rcC(FT|32*SdT_*T2^WnZcmC^O|GOau z_RZ}Q_sFxo1i7_u51apOa+^FpIvh0}T0GdtPIuVr4txKF!*kqUlb<#wskX at L+Gc_j zBdxNP{XmLZ+i{{p!@Yje(qyte z^JTL{ogt4S)7RFutE7NHn>Fkq2u*YMUJ95FJ|#QihC7CGn?*mHocrnWp>2_|FD3uN z&Vz at bQOX!s9hJPQjS+z$ggFG{!96!Gw5=lxHVxs%HjmilG_4lhN$8Gk{Ve+ubYw;w zBBD9=(&Prb+$a?i_>PjlYL`q?(4m7%+7Y+bwwq9*?2+cvgKc3?MD8_A32C;D9=3a& zHq$1FCgF~rG%4s(EJ(t}j5yoIi;hV+7$D}%YJ;7Wfr2!nt&-_I zax)^#9{^FWjv-dwYENQfhGs04U^7v2(5y~)=1nTPBo=d9#6uGfYa5vQ0XZa1cGKF3 z&0RihGz%rdtH4gx4%65!lN!P?mSJ&g)Z&37no9%m#^8uM>_PxLktVGr8M!tlm63C| zP5uxKG~?d7n>soMj6`}ZIpWrw075^Vb{EfsJww}2sSo|yx}uFw*8(=eZw7SZs)7Is z%FQD7jJ1~12zd~z=x{*%O0Y=fbjtpxGHhZ(D^WLYn0at at hO8XGSmOgyWs9Qin|cT^ zz`(L?uZgZRgb^JQ_!>|^^2B}3>1u6^X!%;wj-<~V_{YvO|8Kg*X&Gh`=|;XjymlRr zbS+(-T)7&r)>oRh)8+A{%a_8X>gMDIzJ});_- at ZH_#-wA*WixmVEhYbtS$%L}Ri-9Ggq= zW(}f1k2sGZn6M647~oyT8v)|7ZX6QuTKnZ=eh&Blgj`j6)q+h!AiIo zv1-A2b2~&sRfenS%E0)JZ&$8S at 4@z*c~Ys(wHho>-o6q*KbjKBb|NU3z>5(N*Taoy zGURK`RWdV%h|T2;4kni_#*4Bi(u~(PWK&Qe at XJ@5E1Sj#0VL^oslFI4v at _Z2dIdFZ zn!c}IiO}TW3h*O<&85ty7h$TS>zHN6>$MQ9V5sTsJ~*&;`BJ<%x^(g4g>Zp$U6^a~ zmJ#YE)MN0_T4^#hO2T~c$pugb<2SBfBjO2&m{Da&DZ8fOdE;;)2szr5G#;*wWq%wX zTk{|>Yl%4qT|!qm-yQHRHD*9JhTSy~aCp&#lZ`~s4Qd_aj at 9K6jU!yzKx?Sv0;L>W zyl?>wr|V;2UBgN%YTK-dib+$&)E7+C=II%2iAXI(OpEdIfTl;y7~B`)g~@riO!|$> zWVp-|LXMVti4q`P+yqM(&V!-i>OVFWPFhx6h3CPWic4cyTzgf1gLbQq$wj^`0`$c>^?b`c|j69Os}p9|}v zoz4eN at cI2b+Dsxn2w#OSCtrOTzFhy}OFX{#{PQn951-ec#ZO0{eER7p at ssh#pL`rY z9(|;*^_&C*%vvTk33>2U{3?ApM!)<;M5pnmpM9D>O`oKX!$-r9KPpoxP6ROh`s#0A ze)T1v8V5s#&qtqq{`qGKgN;A=^po^SjegXFh2$p>=mBa%XPG5|{Z~}%Lo0x}>#~*!+9^u0gn+Hy$ zF-oOT!j~Ig07q#nI_Jh1!Dq+((dLKfhDjKvcdq1&VKPK;8RI9TkAY+OxcX at L;YS~S zi0NGoSWKbO8A_j3U?`zY{xAvV at ZeuL1vFiLG1VpUt3g|uq{V#v at kdbShtSf8AABGa z*n$&94xc~$f;uwAW*CkM&anm`i1!xT3hf74{F}SZjD}2+u>JKL$fB{-e{Hpr0g2iMs2%i9!Pd=_bu7OAXAbW!hJ2krtrjzKp zuRe|-6+eg%YPcu;tJp^c*?;4h)OGl13`Q%+)4xyE6kD8{RdmdZ3ZYReAfRQRwbDjj?kCx z5ix)Z$Ijt{bie;Tdczc=E0|%AszZA!V`6qcknLc!>i*HcHDx0 zvVs#fbbbX+WZ}f2vx+ at +R_D};$7D2YlJ-Br_BZzQHrQ^b;lY&^Pq*QoI$^&KrQ|sm zINozV8po9Zf308+xT(nnW+d9kU*{d{$KPaUPFw5nJkGKDhzm@%4`A-aV-ISMk`An6 z2#v#$$PLCJaUD#@A&2``!5U98z-*Jp1?X%a<1ED_qZTzk}t+T*yB)tLi; zlH=1J9;q6>X#woEV6MAL42cn=OHo5K0)DC#d3R zM;|+^Z0Mj2NVJNnD|x8g*2I8bI_PG5Da~kKB+NHqkbtLh-?|aiEg$$I*K{KQq{|Gyp(WW~ZVw zYPV_lyLoBnB}j{%m+VkoT4p1o6v{lO28^U~ye;^4nB%_n6|Emo)pWqtEW7MMhSWIk z+rSZ|Y{UT|CcT3{zypU{QUGGUF1%$Os+A9Z5-2(dNQlxyCvIiMlQ%R1UgCaiA;mLt z!@h*G8HXzzNWR#hwOLeW|9X*m|H>R%0wUlEZxJYpe0`;&{{mt+7LHB9*SQTl2md zb4^HnVE;T05*d&Ry$7e?QWWX9Ww1kVG9c{(Qu>>nH;jV^*{j~yF*BpD$?O>>BYwc> zf&}k7h-KS=Y)?oYniZOmZn*>&^IPJRLM8$`{9&HKYsO^9jv|=gyuvd!{*4y;Z-tdHT&a zPuutT>OynAK3AU|;zLqu-pX%=(}OoppT;Dworl%u!@0@%bHRqqXB&+e-+KDyTX>)` ze!}VgrHhy&aBfpw2;?@NAD=saE}g5+jzv7YH97-M!&{v<^XUeynj%g*J2?Y!!d6KF zB$>Q4c`IF}!AW6{brU&gb(T zFfEw!nRN_)<}I*Hm_}qepYET+ED+DE<`qcy&z?Jb?%bKP0b(X2pWdua4^N#wbqd6X z0QDl~roWM}J{!+W&YlTpI&UST98V8Uoo0`unI6zc=O^c&z<6$q0n*t`Ar6KDMhR~Q zbd=87QwZ{woNZ-yA?rJoD*zs&k=+bm?q;CcZT}^HzLo3}sY+cEpjx zDXv*S{}mc>$cTn!C~OzdPUz-SG?Q>aQ at RZK2a^A61(8HIzf}oc*wv}gY48 at d_P_-5 z-0=K4=sBDl0XM)F&je`l7eXzxWhCLv`gEg?QAh{bMs+Tq9T13O#o?_1EsFdF@>fAY zr%#C3sIrV7ol_eS=p6}fFX(-6xCT5~Dr_-s?$y29Jg0|WY0ke5OT``;)kstix z7zvz*)9bPhXz4Uq^1cLx24HST>;mzl-DDp5&%;v6=G0$bAxe%PXvz1DR_B4 z711l6LLc#y`6U{6k*JaLk2v1>ro zV>l!P*^I3Q6A4Ugo;-0v%@`T;nT--Q;9Qjx3 at 7^}hQ~7}`dBrv=}P{fMtNR*uR(nj zN{lL$pY&PXrkXs?Qxd-JZ}1B@%FkF7mr;2Y?mktmjurK=zUwKA{Xo-*CQU}d{rLXi zzRK1js;YvOy|L1W)o0e%J&oM3FEzW6cVvcOqE&DTE at 2g2&)#HxCPEvFxNs2Vt8-D zV#RbXzq^S(sC2R-$iJ|Pkaa$M+H4!6r28zi41fiNfh-zJ at BWMRVv(bZDJ`*}D&Bf} z)4QrK(P>yoXmw^Rc?+u8ODJLTQp?Son%NWm2SG(i&9Ql)p-ov*uAfW<;e6pwxQK}75D{)n(v at AbWy(rQKH-3>`W73bSS$sUnk z0cPZfqR00$iZ(|q6D$RAsAIgt9~OABCeO?9{=#y+n@}PZNi|B50Wx7wbSIC&3bN8x zHN|&oFdAXtllSiju$s*TE0|Pm#jIvaRu;yD0kyB`CmVZ7X`(vcg--6?i7 at S&^=jlO z+#KE at R|&>#wo2R1lmV4?R0K5_Sagb<3ro(Rx**w;8B2hbd4X*W$MiBp$~}JP)=gIa z19r!M*Qdu^sLQtfu?&tS6=)Qo1wUgP+JAx$Z2(5 at O0}RVrB(%@+ zu6S<<_^ce%P){gjM5);=8R71 z&01|ZD$`9=`iOj%GS5tb|CSTRvov>WaXR3wWa zN`IrN4FK-#cWzav-J^_539es*e0V|=5yh6Z_U#ROCp=Pn1E-bSv%5$4 zA%~UjzF*lu?f6>l?eE#E}>w{@zr5>AN at J+!VH~Ln}+==oaBx!cG7m( zlQ=3K4Var?Gh=Hxqf^hr-ng$z(h+Be;{^8_2Z%qlX%vXA?3>?*@o;`QT4X0v at sw{K z^Nr)aB_){^$2>zm?A~3XN_`WjlynpAp&7v)NHG=rgH|F=;@`}Gs5BI7+_;JAtTN+ zl21e{5Y14Gxb5tzcViih5NP1UqwDw8XrhmCGW*^J0!5v84yhdeyhUT(y$P)nA!zpe z)f5fX1i{L_?r)`sKB)sAea0Z(vpZ71V$@}#HfCv)Xiik2G~zU*T)TBzeA*4LWDB*~ zkZd8*V)nqMiSYcM3`0P3%$+5dY|Yh`jo}Sa)HCmfnh6&?gtj%i1=emk at x9@wU4S{1 zN~KN$`jkwPRs71p6vI}#J6lldnDK=@o5jr<#}s0rJ*o1vdU9vn_b4);C9Tfxyl<1f z$cH=Z6AH at l_>kUVhV0ScuzQ?L-#Q1(+HGDR4Lco-qLFVK}(H z!UG50x3{}@!9SVo0-)8tzF1-4fXjd*YL#|S6<>$^0%5#sw;>wAkcR6*L>NmCcBK}e z2<&%>AjlDev}Y*MDwr^}1AE%t-TedyD(&7a;N6pifAp|K7O3{xS)jpmcjE*lGOJ#* zAS}B^L;`)1#yLxSHf;==A$X}E{KDQ0S>}o&aj4SnT^L-88kny&Cp#zd@!=cC at knok z*C%hh9$(L|jgGzc+Od2r98G9b8C++%;>h;>129O;bJ9-pzBh432)|f|xJ9iSBP4569~_;_Cx+QLlU~ z9vxvEw4a>BY`Hp_PsHQlL^>Ycm~f2s*WbWHt at CU3vFfN1Zk#Zs4J|a)>*2M@>#xPv z#>ZYe79qmq*wJ)!!g*{wIpKV5zrCZ9g&O91c7a%nbe1uR9!MJRBQ?pL{eQ!G!kINzNx_2f;Ly zA)ySd1I~x!9E^{S96gE=)Z7f9rqvvWD#IJi>y;TxIMyL(M*=8ghA1Y|iTe0h#+6Mm z3HC_6MCuYRM?gLuAs2mgAR_sA$1MtGLi05)($VG!t!W6Mh*|x{grip&Jsj)527txt z01V)nj!a&KwN`=-M8ok&`^)l(#xcuBMz0=WGs+ZBbbvrOo}fB8Sq1Y<(pfsvfAt6& zo`Au*?MIj!RRM=duQ#%YjMf+(CIP6NFzr$XftS}|{jpfZ1~hT(HL{!ygX&0pbqF?! z-s15Q0WL;O>&V4A4CZtsy=t}@>Br%CpB)_Qb+qS0ej^{C)@gzhDrgM3t#<;4jm4m} z)$=Pzlfl!34Hb$)v2Y<7-P_1m?f#jM!bXlBsoCw7Hgq|4iavzjAA>tb*d0CD4N7Jjlqp3I_dCAkne=!Gm^4`&)3HqlP(;YdG(oBDt$SQm=y5VFfRkD zSR%vH7b{Ki+NFSclZ=JXz9t;J6N=y9aSInqj!@QhZFrq!HK=7`(HQIKtV%1Pq>2l! z7jWleg?cp14^*8pvZ6m08CjiE#yYvJV`2z=+vgBga~D;e#EPQ|bwC;QKV at N@Q?wdY zQLJ9dsQwb$s0_41F%Yh`j-Igej60Kfn^C at j$pb31P_q+3kTq9{MQErLV56ZS6OwID?OoEgVI3OAfajxnq!$0R$I>ji(J%ggU73Vz`$xT6Oh$nrWzzX z&iB#h{CP5KcWJRW)a`1-k}B3xSskBh;+Q2-h6Ia~0v)sY>mycnRp8!~%KFLL3N6W9 zz5p4{sRA%u-(XWbuU)OZPA@}{Vt!m7XX8HY9M25=SW{TBsOu7cXxt+fpfOcaZjSR3 z&P{5D^`YTP2OXprlvCx4eOfjab~BbdO(_kG)XL!k$_{CHm6o65xgqD$C_PmD7wga} zX)d^&n!X~mrB7c5m+fhuu`SEP}M))lJTYAAwB=G8eX`EwK!Y%Q>;l2xfFXk^hV?WsZ* z!*U|XX;DHl(714^lpWHbB6QqhY#9w2a&am%3{qMgJ_Mp7WkP3V7glN!9xt1|52 z31}mA#Vin4zAYbc=B%nPRrhKROWPJ6EQ4hBDk5{n#1(4t3JXk5KleJ at LK3w_q+1+UwU@CGl=%NGdw6KIIb{byLI$Pul+kbTTzrv8(doZ6ae z(pU+&THs;PaSeDZV4A%sujmOrCgObh%c)Gg8xxRSIKP!II0g-ek?Pr$bJA?jH=kp4 zb`Q~-GaUAc?A74ZSDY-$t at pI?n{WJyxy5w)5_i{Z+SxV0VW)UvOFO;T2DDFXTe=-P zIL(;do!GRuYeYt{J>9O_&fD6_9S3)CLW0!;Fpl}{E=)+GPd&)Iox3XHvwp at W*y!xu z#H7- at I4YlE!u z*jDbJ3-N91yLKx*!dmTqDxmB|BF*f=Hh;TzYU{qZv%8~%vBK^R at +RhH3`m`R7b@;D>Wljc+g?3_~4|tCzeBqwd1NTi_Ir%GySd z-xO(#@2BaCv at 3LjjRiY)o8CUz0Z`VR7j)LeMo=l~L ztdGX*sO#|6(hMgX_V_R7nW7svKiaR#S zTsasMeT at 8eXnDMS2bK!FC9HSnE+~}9%xgE;2IK(h?oP4m;nZ5o{)p|4)Ro4Tv0zyM znxrG7T|6h)alrPI;*w-1JGO(idr#awkG3NkP|;%8+3c8X$F|U(>E)MS$}f#y zeCfsb;^2=j{t?gc$H5=|_=i9K at elD2m@ov)V8W}zBkY}DO|Qh4C$GGmUd}It7sE^W z#qh at od(?l3|By9t&6q2UUwIYOt_B)_Wd-Jpg}r5MIlF_2qeZ82<7w zJOtZIvZRt&%m&@^-aF!4WXUE#B$_yJB6h^Nh=|kR+pdWfZ_0JJ`f`1BWf>*2B(~{7 zv`0wATd+Rj`_;|+_c!roWs1kE6`M8RbwB3YuVj4-V$pYudAaF8XC?JD6>M%h253Wd z4VZ8pZqoJU>N;GftLD275TtsyA0pIy{O~P+|Mq=(|H$3}dbn8w`*7V{rSF!2hM4Eu zHR$t)j`Uj6HrM%j4It9h<`P+Y^AYy^c$;t22ll-D7QEi4_es3Ub-oIhmX$H31j^;M z3;V&|BD^W0i{co- at hV(BT*}_xeT*XB&>HF62Z##J?{5-xUtL{aU&X7t%d4wPxCSJR zA6K_XLXr5vhT(%6nQP_uotq3(Y_6~J)$;P{@)A<4s9kVhS$(*rSFM3?^KE+Hb&)y{ zeSLNLu>dw`7-b4N2ko3d*t)FjmZU?z%9qHv8;DNeDEa2Yw*}O)NrC at C_^Zo!xw*Ow zmjRhJ9X8!AK&=KE6 at +qg^FF-KZj+^)PuwqH7RS8#P|StW`vyu at qIkKwfSUQ^U+AD@ z%g5?N^6Ll-Y4FhG`I@>e0L{fEiUrMW-ntT`h>+rru1Wu=;X9X`3(#e_^1>JwjK&9A zhqrh`Ke>h}fqA-G(Ms_$UI4^hp^_kWe!r^`@(5Qa0bsizAmA!{M7IZU_!c;HKnzEY zaO5>?7%uY#eTmlQ^?=uqV3cb~5Uy8rRXVdny}r1-h!+^L9|1~+iXF%h2?B>IJ(|9{ z6bRumT?k at c6L85XFr(OWXa!-NB}a2fw&9{Np at W3x1qVX=F!W(xNiIc)&bTTO2cHupet05%O*>9V=loWrvk49xKU z!N!}7pK{e)b}rWE7x3!D!4iRC1HHs212lr1F3UwcM=txT#Xi0uEnxLY`a=hce6Z$* zb>-|87v{`&T&04=HMn!(7MILunGWmX>BpZ{DPlnfwJtK}IwGx_iCA-#uuiPhRbZ?a zHYlWthZH?70rf+H@(BR^JdS)xRVj_GLDdLdh`V^X%u>ri%1Kc=T8#dbN6rDTuH zQLthr`Qjp5h7*3TiZy#mRx;mw#saJc>%X#;Tl|Zb14W?0B6|R#)xpc>d*!GEqhOtp zt=O@(R9rRm2x^uL3#JqDXIoYl!8ZNtLhE+^(D>@L`1^++RjB^)pF0pj#XErEi`E*f zuCvup4Mgamf_$$cE9;5r^XijU3{k at bfispRJyKvTF~WMdf1`C%d at vhwvI3QIB({Z# z9v5jPEWDHj0}#)Eu&p?+P_aZYo3 at Ft4UL^)mqtLNBn`#F=Z8-q`uvF`>EHg{G4}O! z5o_zXvMpdnV8xn|fjZm^2Wma2u{-w`rH&5C?PLlYnI6REXaGN1_o zi4esL_VmyK#+Zir0qdu@@{mj6q84c_|G+7d0O-D`?ybT;Tq>d5%%yPA0nQK>3khk8 zgxWMFMp%>%V7kP#j^V3?qFk1x)sDZ%GOnmu6!QqLxn^DqnZrQ}G=I|t+_c4)BcR3oV7S*h3+>V|{{{JML0Vx%6M z`QvOppUr1T6Z1ZP@`&oC{YObiG$=)PFU|x0OO-f7nz)ZVD(*uFNSgPTpsBQi7Gk4; z04VH$T_B-C_KyZk at dPwk6Q-)!aL#n at -mUd)pI`#Ey$F-QUSUsQ#`%Jh at 6F3R&Ng$T zfHcl%|DGc29`-i-up?ET&KAtf}U at 2=quab9LaGXO^WI zmtw-e<}rCD=remHpz63^-3sc*xhoXS$!0U4N*DJA9e9j{?7~&z9E#6pc{YIRVSm>E zw=pHN)1T6OjlRol*PtE>L0ktYe;2k`ai|oTB)0~ZuILv2BS*qR+!NvnP0(kdB6Wbs zNc}nn$rz at b!JB2u-mGhS_UfqTRS0*#n$Ul2z;r}O36Id0`Fo#vC(@yW??Iu1vh zV9N}=PaP)|Crw+wBUHn4(coRsyIz2l(P2e7mHz&v8cyR>DkB{O}#<4nR z5|I- at 3S>3Hrfra+Cw2V_;$xm`g?oBuX*2*AG~pcNlr5a))6K~#>`SeaER|p>82BB` zfU|0#b*5-lGPYU6w&@fWz<=5{9taP(7U9pPixw_0Ld!bA$%) zHXic@(;XVU!5Yu21BJ7Ax`dr+3$!^o&c~0?hYA%47G(?bA8G^dU{9Mf=puzomvFok z3`rU_Z9_A7_TCDc#M5-rm?8oGA>nwK6VBl>&?4Mp0X<-rSiOYW=VNn{@~#}x{Yg*bX>P_(|9MfOFf-jB at I(#djAU~|waX$?w52td|%Gr)DgU9mZpm4Z;dw9`+oY-SB($Y5)A0}(T at 2F zhd4mX{eGdQ at Pf{1ftv;RXBURo;Z5amK}WebH}O(+VtH3KPmXY}_E;^R!&EMkl*zy= z(GBS&)F9ay33iNBK1^>}IDq_>J7v$ha5Ru)_leg6C!CITao%ZRO(z|5fV-Du#T?2J+TGd-zL92|# zT&0{mRA_ni;i+P{$3vdc^!5!}oM&7GpBjT#2 at QOMqA~mD!^i`x-T;GQS2qkVjx1ah zk1

T!i`6e^ZMOKA-{DI4F zxY`9vqg3GwAX~Bor{d`5`g2`>^94~oV)Y-mP>U6BF1}GMGu}S1ZpXrJLa8d>*xEbh zGq^`Bs_}JQ1;uL=)dQ+p1+^X)lJZT^(wDj#uwWse6=KuJj%sEFELYcB{}fHF^gY)Y zMbLFVK3J`ZB}`Q$BTATetnjh4$Bci&r9E67hb4?yx&>7uRJF2aY^z!U#i?+;xVh$< zHU$wBgR#~6N~LB-Xv^ZTu#k(ELhD`tNUbR!h3GtwdijwR1*dR!YyeER+`15fkc{ zt4rWfUCNdfY8ohPQKYsJ4vWbE_CV24w!OY8S6Upa6<{0 at 3D~GWZk5XPehFw-YZ90tqk0 at g9oziiehqs4+_l%uqPG#(Rb z+Ca9=t&h_cX(yi at VgZ>U7=uBq0j_X1tt+DbYST%SAYB?T0TN@`AjTSN(q>V*7AN37{fGjO%$R at jr zgl{__Gdq}moEXTh-Jn^*C}=0fwm4+bY00FpDi}}=a7s2Al&%U09xOMQ&zKp{npvD8 zeVopwVY-=3^E6Ku(+O{bNzJp+V75D3fd*~DG|Zam!(=+0 at PBV|H=az!x+tgMUA z6G>;&-t^vxz-v4SWZJ~*fGcs}b=wk~7~v)yShs)m2Y zlQbz~d>Ady0^tNj+H4glPN}v24R-ipflY}rs#v+ at 2W){ zZzf2^!n~>a5}L`hGuezM##9UmNpF at RUZgUh0DR;%C1F6XO9C?M&z6L3I)@p7sQv6qsubMKzMidU<|37 z;0M8UGITTm&p23uOAZ2}nAri;bPZ+_Gr?SUG%EX?jN at 22AZ;IKz-%@xkkH*kaCc4X z4h;iZ`iXp?YSjZhdNeZ&$p>;K^EBQy>q(HtrKVm(NU<`K&1lw<(z0`B`nC0}DX;;s z5Su7 at gRW_rW{(2jeH$j90AIK+Z&N^aFA=CHooYQAqnCR$v3nyx%pB;VOCN?|!=xEY z9%(2)YF#vo1}3BL!O(OkJcQ9^3>yO%lq-l|*9H)69H-sZ^Q;kUG+K={*vxq%h7=Lk zSdoD;)s2&dLD-`_#T*uOKX$;OAo7Wr4 at l7nL46FY*|R(cmNZ+0 at gvd3y)(|BT|)$v zVgLrY0f8_{=8)8G4caj25VcVlxif9S6WDa*HLwoTo`4e^Wqdapk8y|RgTLhII at CUT zo5>?JlXn2XJYJxa at d$i^kEcup3Z2D0Ax_+*Z1hpG%?M(K5w9VJW0#HShVF{3)1*mv zoPfEj@vuQspJK2)#sN5Z^7C at F6Kd@XmN& z2Rz|`S{}8NU-QJ84Vz)l9JkA1^HvpYW>HYzwnr105ZE{Q()WOI zyVI8!Oo{Oy{qTt3qVuByX_aB at w0RSwp_J@{>g zD0iUa1~!0hiC%u&dDFZuI1p+VfN?7z_LU(qVxDM+7xQqR4G-PH5iqtjv->i?Ftu^o z?i<*k;EWu8&b^|%L3mnujm{!>l)=E{h_k~1JfY`#QaoxX0{skAt+;oqpnGf?i|D`c zzJH;KUca(?sD{$UG?rR4f+6B!Y}2D;5R at Sz4~Yji+U zeAQsfr92e761hna+y6uwxIOI~_CSvaZz4`Fc1-MSFXLXYq0$uACdL(8q*_IdB3HQx!=t=?F(+b5Auydq#;oMawY~8~;Og9EjQpOF(sL>sGWDrh6 z<0f5<7;d<;Ew1J at 83^$tqvA+nzh8pWXqzLK+tq=aY!HUmUu0tqT|A@%ZN}x84;;j@ zL&knbE`tNAnFp`o25fNLG$i5wx=|UXtGjzmjcCm(vecML at GL1FZ3U3HjW^h at cW8J$p?*@9xVfiGq|!5asU1s)iOQGs5vda5((Qtvvn~J)uBgIn|#Z=nQsu{mKL_)>X z4l4|~_hYt4AVZ3{HZK4c~wn1(a{mXVA|?_+j{O-(Xo+Mml=OnF1CpYcW0oZI+ at bD$SB$I(`GAVrug! z%|%8f{t<4$N+Ao~EJJ&6k?0sDB&l zh!&b3YG<`rJTCB^pMkeV2~bdtJ%N3C!{uKYo?zAD2z-lXqY&8e+fl!o at mfpS(zzG7 z%?ScZ(wYhikiosCVI}&_J-&%w%YFf at eGbNFAPhK8q@V_ip#-xxXrik*QDzGpPzCj7 zn?Nux;cr-UA at J(8E;Wgjdbkv8^gcyXU}H`-WP~F;Y0&2zf)62^?5e{k9(6&=ZxUYW zUZdl7*cV;AeCjW>@g1;wZ3=N(r2y0pCPg&VJNSVEeoJ^jhlmT8!tB!)RD_5jug*$UKRO z6S`R8o53sd5taGuu2|_=Gl$Ih&psF^E|C{7efr`NK7;)6s54p*N5e2gnl#*uhTY+M zFdQ at kCM=>yTEsMBMcN){!e{|{-21~Y2*Y0n!{Goe29}yVk_`9ZD3V(kF7cFH|3*6A z2Lnh!F_1Qa6zSP!JW8X at 5YN7l*0|%bQ3ne&LAy9mlG0Q9iDsSA5+o)#73hh$Gw6=v zXekl;uGoSstGIEnA+LdwHn7=+Ml15^4Ob=}GpA_kP7IRX at WDPB4g#C!K{iZ0hlBhb zlRp8S-Qm4SETKi-Wm1J3?$S1o?kJJz+8<^z?GBJjcG|415z;HF=!VHq5Ol;m at kui% z^X>h3^z=)nZ*$yLx-hX@*mlfjDpp#U*rb;WRlo+Er7 z26>2%`pJ`U05%?F(+*BY!F%d20fgNFe$aOSQv-w2`cO0KQtrku>Vt;aIqoJ4IJEW# zSsX4Y*PvqxZu*cbl2=#WWvIf9vteN)V+j}T4JXd-xxEDr8q z1Svy at gWt-kUGumoUDn`4efXb6i|$CakE3E%#kd>5y9SRMY_z_2Z|z_dnobbW)ze^Q zdf~^KLAf0C0eaWWL66elY$!nWl at 9!XdJXUc!uQk*B9BLdryfy;cd{Yc&|i%;FaYs@ zBbdXO*9K`fXs-;CxC|B~qmr9JKlM#Vb0it1q8y=Q9QFqIk#EwOBf-9u`rcH;V7fR=yL4lL5_v-^L~*Ei6G2p!|ee z5V76pnYM*-N6ypzIbX5o2Dj(TXd=!nDcnwH8wl5UAKS{XG3il6e6(h|iF^BOU!V3=ZMU|mePB9F;+~k0aW7q_ zhNN$wXk#I^Ha_cYm8WFbk`w?d;dvqfdMqtm5+HHQ9FXG8 at +#l>m23lA1A`E0b|E+j z`2*gNY_=0jIac^hmkNodl}@nvb||IBc*u~hqit=ZHbl9>O!n6UcFBQ1jHf1z_F=gJ zesQ#fnRa|(DV{THK?kjE>cnO;g7Ao4Sp>GUgx*{8{ENZKM<|p%hvjxY`B|ST^`Wt_ z>Ofl90~XY-OZ{Ca7#dhrCBYg(nL*fgY{05)N1x!Qmrdv|wT~h`kp=%87 z^s7E8HmL-%+??%(&tcTey`(Z~=lP%18|dujy{pmF at k+Je3>vVlE$DW2z;|rO@(l>2 zSI;owcI|EK`$XsE`QKdgs&#k!+z+HKxAj)nAZPiPZ=#05 zK$F~A1)T;Id4jgKyf+izO)-}D+hqj9lb?}VR;L)w(`=r!_cB3teqcLHsW`6|)Y3*7 z5e3Q`jU1}H78_r_a(`kIv#5F?n!Jq4Ub1hlxVkVR)1|`1q6E;1n at s6-&|DvyFv5y& z)AK3e&NKWd8FOa+$Qg4;1mY}<;xKw1qJDUglvAPJ6H#A|uTBs2qi+Fu0e(<`rMs*u z at X>)*vZ(fp?9va1e|6B(|CtF2c&liJw1CyblLOC%75`f!zJIFsBa)*Af}9^%<%~5j z`;CSC>J0BOA;Kx at 0Y#!p67d#tjnZNt?v!w zh$x1!`ws_6#n1BU_Mmd(`?m-Dbv)oiU4D0fS|MI#JmZuGenbJc?+$c&eSG!lV8fyy zn|+T{`0*mV`h1XI{S*G*u)|&d!GTYxQ~r3MNCY7V1C7W4BKN=c`hM#noLKQWqS9W! z-{b41x9TH^$q0)PA0ha|pWXgn+BTpjp*QXKLJt8>kHJX~F=(_H6m5EZH}+%iuWDg! z&<=MtiPkLiC7^ZF>!;ot)b{BkJHjpXGicY|E)O0k7w-Lj|G`Mu0-dFPZIb)HR4LIP zlu)`L3?P$pi at i<17kiOh?3U4g5Fkh{?!A*izr(lsy+nG74ygD#^h%#E>;Er!Q(`Fq z?NS($NgHqJd>$Q)2&f at n=_jEa6&5SV^oF^Y!9Mr at uGb*7hTbP5SavvpN)J?l9U at F+ zm+C1%xnNQwhZ#%_6{Dl-3llJbh_HA2_dP)io?WwE5eI%qqPCNU5=k5gKp}R~RDAIm zOe;#h)O)Tbr6v~wsBQ+;_atoW?OLCd8dyqYd$fFE&~MN>%NuAz;!7g8i-hAlfYI85 zlKR8zi3q*E@}wxc{h!$h--3(CW_>d)sjDYZV7ZAN2t at 4v&DxNgnvMG%h$C-TPpxB_ z9Hsx)N{LZnJ%-+w`c!`@B at onec8`{zOm&oK;C3NO!8-sIx4d;jbBEZwmoi`ikJ3P= zb3-DH#N+ at -0AL2sqp9SW$bMiEncN at _y}$Eg6Om6iBBQ&ZeS$~yLO7%|hiB at +v2n9e zlFVKBj at zL6S;Ho!4m+b36B|j9nz3tP8Zaj5r+6JDX0d^_{XF!D3^Fqj2|LhBE?zn` zApA0yz%21vCMiX4c*s at pX4|D?q}FEg1mty at pd`f{Tyo-NYpyQGmw59J#+ODnjc6tl z^TwQ>k~G!i;-RYwnHiMnS18CkoOyf624=iswWG570so4~p#3x2xyB})x@)Z_Z`GJ3 zxkL{AKh_3qQ!$}2Xk)(H(4qK5%tP;IW9~w#53?r$$oQZ7J3Z#EIJl&ST}%$@OOLWF zK31 at V(`oQGJnJ&ZLKBaX5Z36KatsgY+1R5sqqVpSq{ZG>NV^S&86fFk4mD#ZcaqBc z_3+Fib??suVvty9-Lrt(14$nB;h`jYW&Ytu`{0#MSCl3}5s65>d~ z#+xZ11usw71&4AR_V4+BJ#eLFt!U$+ycEjMaf*#)LsThwcg^#kr2|;j(A!%*ft^7l z4_aNXV(P=WxMgHZqNlW0YFod~r_I at EV`LDZsDZS{ z`FJPT5jXe zEj15fI}>utxMDXl9qz3SnzcS;`dV%(SzG(K4YkdY1 zf!~&DU zB3gxWMxifk1eQ!zyb;Jni@$oBJX}#64H~2l5Zu1YJ&&>1Kb}hx4^3j(DqV8?suq?Kz3y z_JFwcKyZw`zlnR*o_-X7gePR#B3*$M`-nn8?DTqO73b`Caqtj0dwz~k}ScdJoh_G-$i8!+}j-b*>;`?dCl zPH7r*2cwrH?ytY91TdWtWAB`H;U$1Va){NUN5xYTR)W;`F^4zT67 at H&M~7uUH~+ at 3RGw_Jc7Bs;&1zmJhzXiFy(38 zMr*XU;jX at oqfMJ3>|oj2f)fvZ5K%H1sVR|SDt^(Eh~XT+>%Z1`1*GkvsTS)ogz$W# zjnq9L;|H7<+(v#iuncz6r2-P3=;-m2#T#s<)UELlf-_T;NTON9#2AdxMx8sAl8XDD z#;hK`b9PH_`wXBf9yKK(8K55HItFZtR}|Nz_Ow-eOdcfpE78Jh1C5UgkJ>7BXFBD4`9!ZVA#7z9#AaqvH~t(tnI)&~93g?&8o zW8CAjK6=#z3%jBzDVW>bXA{NB-xRLdfMN^jtIjpSqqdYuU6P=wY0TjHANktuXx2Vg zXAF^L<86%UhI8LxVWP(VXsX=Z_;~{JGv0l|m9aih;*^kNW*lxQ5Z-lM?!5(L-K6-{ zoJbUgtmXgY;$otWbpsocAK_~Kyfuq^l=qB7cR2?IO5FG$JGg#N=NvupV?Z7;&RP)b z{IcX`ukPMniIEXnc-VnFzwyYvwdgkabV2PC^K-FuKgY*%c8Lpiew?qRC2ck?Y3h6< z-O%TEYxzR0&C))EZGsYN5ezp&Ykmsj3Zj3xI_deg)OnJW)pZN?8pX~V9G0ws=GcG- z&t;|Ey$dq|JFY!xwE$l5MiD(JZk@|34n9D|Mim{<`xvU6=$>0B(6?WEd}QN|CwUh2 zyf+Bcv1$k=h4}e+; z%Y$==)NW&E$?Jm`>3p#Q$E`ZD%dlF6XFu)ycgx?Dv;DO5-}vf{z4agTXXk(LkACX; zr=9>Dfj~a`r6r=E~v at o~uZ$l&+EJnA1 zs2J7Ihdo&2fUj#s(=5C5+tp*dnk?|I3&NT@#nAU}wxo at H>Fyy`Ta8u}z{WOP1Ue&q zk|bH6hvqEg)}nM3u$>5_OjU8nRwF3yPw^?g^(7RTh at k#;qMGC|E^nvT7|6BZO^sPB z(qni7esx*|EG0ezzv8mgl!Osk4Q(tewp7y6RIp8&i&A at p({1mT4~wzQ at c{&u#;Jga z^^6k}7UvoQjfrK^4d|5au>t?rqPM*kFNStC=&jiS`-p9>Y0|SX^Fs at 6TPL)p*0mx; z^up|$W_7M8?fJroq3(V9N9UmecD z4gWtMFj?8CPI=bv3Moupo{yP}_`Aw&nQigQK)N}OBDPC1K?tfPLQ<(fC|VIsb}y>< zwHP0^bptJY0Mcq4YT=7~oWyNkE$8b6d?fjYx-J at CU}R6)oH6ygPo|nxo+79cwZEg~ msXII2Uv_qOe#U?P`>*)_pZNs;{vH1RsAoGnXZZVy&;KtnC}rUQ literal 0 HcmV?d00001 diff --git a/appliance-recipe/resources/ovirt-splash.xpm.gz b/appliance-recipe/resources/ovirt-splash.xpm.gz new file mode 100644 index 0000000000000000000000000000000000000000..8cd25b89b37a8660067bdc5de4f84614d40880ec GIT binary patch literal 51388 zcmV)ZK&!tWiwFoYW0Ob#18;U|a&#?oaBN|7XfAkgZ2;Z4{FyT6GU*`YrU;UT=`k(&m|N5W*@}K{g|MY+SW&X?m_W%B0 z|BwIrKmV8i{r~Zw{>%UJKmULJ_y6br_5b#l^)LV5fAz2b@$dikFMt2<{`N0_{fEE& z^^{<>%aN at IDUToH~;ka|NgK4`1x`A{P_2O{fB at 2>%af} z`1t(z4}bd)fA?C0`cMDx&wu at opC8xbU;pu+{`xGSuu zpNngZ*U#Va`2FfN_|I?GI2`XM*LTOD1FfL#+sDU82H^os{vV!0?m%3DTlvZ%7+>o5 zSC~_*;ZMFizFC&HgR+Y at fGn=1{Mxq*LD&G}9Irdl;xGNIv;1oVbNg$6Yo2xp&hv3! zA#eZvMBpciCi~@2jW&Z?xe^FJ|L=Ab>VNSAF&M!5M%v4t3l`JMfxs>Z-~0;@?&wma zb$-oJhOsK^h9Of|U%;v0NN2=X6$ z)%db1FTF38H?SEUEo{O%YO$OQok?DX>|0zZhp%R2mF&^tgE1^Gkx5Ty4Z6ext) z0^LF?EcEwYwBEjh2lHPGi)-*9;CG-cUQmP^xLdRYgAwNyTo?qXm6%tvGu*k5h?@&N zIuDLoi-iZZ8EnE(;O8)uG936DhZ>1LtQPAX+CA3V)Fc`Sc9BT*f%ZO#y7D0q_Yr{m zXhTmyxbT)=u`6@{_84$wAzrO2XBY|+yDgv=)JJ>F_Q{lsDNCXH32H6P!yCD3owfMvSSbhVF at kcCHD*0Q5K9*$t6y at 1viGcIX zzl^*^J}vr=t`Lk!YCOAGhM_=I5=$jsycR1UXK>z%+8YQgh1dH(r9TBBCCRB60s5$f zqa;s-07->`z9}_*@zJ>NP$57dO7Pg|1&8f>^Pv90miZtU3o{aGfSi75HJE_w{sakv z2G2Oq_;1Pb*wimd^kTI-qYq7ALZXNepie-ztQm!-K??SQ2Io1xf>%=kabhc2e%Kb4 zm)QXr9pP~g?2!wjO&q4ALndhgM}eXdV#R_2$nVXARKRijk+{(6?}5wPCZVD*-V}cX zVuSpeP+WjO at Sy6Dg>Tv^UJxO=KjA)9Uwd!W;dzP1FZG0>M4}Qa4R$O-U=gsZ8bN`d zlAjf16UWm*f`j&hN_ at u)U?`ktcMSx7M;;{o5r8I6Xaq*{_pPf*#1XW+cLJmPG=jLk zVH4n!+ADSU&8iFlp5t##cdKyYQWxS at Q@Dd}v8K at AS37}g<4#DBFGY+rDlxc^wU`Jr zzMBT=0f(u`kewO_Da!y~4Z}(ae`gG4DH3Hk%i~&@q>|iZMKpg^@Arl|BU;#`aiaz& zA;MSmgf4!C|7tzF3IkFS+c0Di0;E at 6p>Vc8A;g(C`o$>=N;O7NW<7Q$lva>djF{7f z3xC7gV?sOl+bim?*wKR!Vk*UeU$Y(j4m?=*e^QAFL}?6 at 48AJK@AL_%BoTUNs3N%o zMiV4%Qi~p`k=QE1@@e&-brm at NR&AHhnwuFgga|*+0zUVpF&^omPksQu+8Z2|3J*H8 zG8|m^dG|?o2T(`U-zgYxdJMTPCmy69*yF+NyVYVtVR=oyU?A_C at m>pobf$Cn_m&bY zo^%B`lP9ezjH2u4vq{ON9(M4N7)_6u_-OQlYy??^(GXIBh%&BW2qy;aG+#S&Vi2Km z9_*M$LEU36-wgPNcsbBn7*MzmW0CAf_fYsfFjzn*E=-<|d;}|o5z{Tc-U{+Xp?DN# z)?N9~Da17uF08 at Xoqn4bj<>1`6Mno6)Ge*D+cO5|{9#sOX3oEY#Vm at 85C#$heseQN|_iN+&9u_%0C{ zps+=RNs)a13^FD at pg6Gm+fgX!JFtfrg_w$;v7#9Y>EkehX%bIEAf5o zs-)!%UK at jwQGn!a8q)gxty;WdK(cBRsB)xDfl-8YNFu`#jOb=f&9EXjd%+S_FjYKR zISvj~MZ(5#$KL$NF7<>$e8}Tn5KkQ%eAvK5aL5w<9c!^r-#5`B at MWqW0Pku>EJln9 z5OD;Fz>iB7G%6GzY7qwD8Zns$Ia(by7}B7w?ZCyLrd^=AF{{qx)^!!o^}N9emY{NB z&y``}En1`eO~3Vin&El`@E#K^nNTLvVFx}`i42q`f;FqdhQRH3`C~jtUz*ieK8gz? z^I`L38rU;S(5vwKgPP={(jqq+L1_YE2Ff%f^Iso$x%Ngl3k0}8jBrHKrNLR=XDP-d zo3L6d800v0`$55<3`wd&{E5HcIB&oC;7=yF0;RGTqXapo(3#U18mM7m`lK^j3 z7nr<3ufbqKl;|D{QW#f1?;yag9eBcpMuA$3mGk;lI1S~>kBE_6gg?Qi4sp)LKGQoc`72!05IQ$Y1MkVGbqyUbc;rj;uRFH5g7sA+#>Y%`-Cc?smticjs zMemmfCB$Niu7U?=p}%@ef_ at NcID~P~-Ly-QswLcsFyu%Ki(L*sMo}W7PFj6nNa8=t zgYLMv6_Yh_!QTx<+=%L{^YUTSklS92yjIS%UotQ5RbwT=@(rLS&a}|sEJUaiVwsGH zD=iSDJ5$f+7*K^P6UaMX9}#y(J?W{UL9f6EC5{-|xkz!01{LpUj42GW9`cN8lTLx at +8-{rMXFujhghm`p-z{z!Xke8f#b75uRrU;XJKS!i-J({zxk{I32FwC<{&0#bc$0 zcTOD89hOgzkwl1b$uOWXysE*83x5X&KL9`hB615KToj=sv60;g8%7;Y1z2fNz3H!t z5^V;>hLOJT?QD1s#8s-WhZFcop$9>XYI}T6fx$ePOa9<$AdCsgG9CANv?p9hgysYO2DRU~PRMUbp7grPb_n`HBCtxwBx{GlzVIriV3GvW5qi>?+|8{{5HmWdz1j?l79kNbFf;Yj`#0P{#1qV) zIdWQuLbf{mm`shRLK%Eitmw-r>eDI7Z~fr!os;+mv1(y_P;2JW610z{Ajj at -lEmP_ zuax3yN at o;kA|i*U z&AbeR at EqM?$YE&*4?l1Y(5WnbSRoz~2nPz{3m|S`4Gv%L2VIUxTtP;6AI^U&h|e+# z^6 at R^OqL`vFaAAKIvJF3N}`hFMjc)Se=Nvr)+WugjJAU_5Qxf*uNpQ@)ewprNjPk$ zR_#Mc35qRf+ni-+r_CSPa1cgxQ|1zUO*sCpTFiNHbcSgtVgRhInI;hBB#)2SqpQ}H zKE7OleT7LP`{o5+s$tt1$+CEjlANr;7W`~BqCD85J=ihSC3==ZV9~+}O%y(68Zk!| z{`_JmyU(xo8wcPHr_vBULL0Z`a14wK2i{P`TX5m;&?F*2NPU4jo0D|oS>F_QH4K#k zKVokrphy_rM4~KoE>@1;hclz9_<-XbAg-w~1QRkoIBR|o0NiCt_5n3I$Uab7!&HGC zdn$-EH|-#tl?!orLXjbetwM|h6o)x_!@;`2mMyar;X?{?2ZS5j!7vDa->j~H?n7>J zQ2*PlZqUZVAJ!SZqZrc}6gvExB0-88#!SPDxpD;)>XJ3MEX12QgH>=)`#>qdo=dQ- zj8Yev*vN->(jF(Ry^mg6br!{WFP{`Qzs9hP%Gh`(H#H at qLc;P07} zcM~!MstT)^tCvMi+5~B1w*$2boZP=|`}kY!esE#iVY)3lZ2a#ZV+) z;EM(jHwO*Q>Q!4U zCJh=8XT3{GxJKVfciK!D#8>k!r!gUB#0J2izRBPxGhwZ~CPt9oAFnd|YRYVpn)xyh zYF4X?PFp(|Huc>JiB5%mHuBLz;1`$>-e6i|s`p2(E|6YRl&tdM^s4G`_GQ=-3INyW z1Sf-E+?|ezL4ZMvEXwK+9{7tMPW{-G{Yb8Yy3nV7Fj$eBGk1dI;?@omgR1*GZNU&F zUW{izPe3>^MUB*!4 at dGMU0E#|k2dj9q%bk)6&cbvT*LkU&r}AJ%UBFCZG8eOv1`t1VNN4krRE)oLvFdUpm!`wln{*x*6iK|SB1obij44S* zA=oh1ncCf;Nn$5J(IGOZi_?FbHCPJqEco*ReAs~Vn6IeAjda40!Iw307lwf1_H|A2 zEjNCufvHL=Rb1il?f2{jy)SeGzO@!#hh>xoWkw7dl<@76b*>ip27&um_P^4P9c`8&G(E`+d^((6M+8!1O7vv&${$u!luCOJPk&j8eQd z_t{fJNEw2Jbp9a>MHed(g8Tf8BUIj=B!NN08L>2iiT?%)WXj`dH14BOI5-!c15oa^ zuVD~4ETGeTOlChr;WYr?4!w_hcjTktO5c?wJ{jRcyADQf at F2H}D-v9EuQQ!7ShnXf z0YVq2;YfmGP at qyH{b^Zg4^dLpVhd>n3Xes)XawhDl`b^!6p3j)eVS#MOsX9n)HIfC)a&?-8?^SyQ;FSUX- z^(`pIb#z}`>9x&+hPhQ~@DpbeoG$&}amTn;8k39-EKa&sj9NUEk%*pdGzj+0nUV4f z0rJJ90vf{G{$REQYb9J;)WL(@xJ<^MCuRhO^k!~eDutB*Ax6?3-30^2OPdDQbzK~Y zJ%3PE>E>ioCj^QV4j8VQQ;5+BI>FOZJ$TxnDPGvq0mDy2W)2Yy21IDpB?-O|F4T{8 za(Vy-ZrT+7hQj+i`0bmcE^I>MK(z at k;P)0T4D at wV(`pdjgP*m5noVFV;dDONu|&p3VNi19BbtT+R~%WNFa-;izjm7e%9rrtzhNA zcwn_MWA4aeg7GW5_`}KY<6EN`H_`VkBk%1WiN0<}_$3x(K!Ny!3^rM^(5gN|pY(w` z6_I=R(J>xw65&iDWOzEQkF1&h+ at apVw-)5B%OP16J2 at 694o#q=)Vh z3m!3>Lkrli$3cT&!7YGzPm}0AJAo*HRiZ5=WG*^F|HMgMs>E+{;oWUcgCf~a2E}*C z&$9{(vA0C|r8Nx-T-G`H(=r-yFR=VecBe^$>%6l50)?^7K3QC=0lyO4S5hoh7>7)E zP&h=BI39Z=M`~eWsYSs}p7bb~?6aWr;H_Hxp_udG2)!Ay9}`;_*E`c-d1(rgFKA*J z5Q{OWIR!1vmn^8Fq%fg|w#a)Wsu162ELuIb)uqauHenp-<6Xq~yK${D5TP$9gAth* zO93|Qu?8QvDMNky(_>7Kyojx49u#?eSCbHqsrVuqW7bSueIz!wM^bj*UFqBCdm};Z zM{M!k)kp`3wm-7Ui)IvHQy(w$(x5?KpUXU`x&wqv{65q`9w2K0Zi-Nfv+eCNO~6gI{Bl&Vn*lkkuZ% zLxPI{Fg}U6Z45GMo7Wjz(Armg*iq|5X!Ll?3}kGjRSxf z)$L!;j}{8U5F`#okGKdn0QQRU#tc8-Z}H%V-2#2zyaB9w!!`)thB|_erMaPtzd9#Y zYg%iujOlJ6F#17@@S7|V1g>orRldiA20b>1EXYZYT(*Mmbn`au`!EE4#hcdL^c$sj z!8=>P3joIQ(ke7qSK^{iq#^7&gOV9k7+PC6^L at wWB#}Y9GB2YUnFatoI9>ecBehVr zi#@dp%aBhe-nB8oH!`t5fc$7!-%M+HKX}WV$#zWFpoSc2_Ju7NT3EHIiG7LWCJ<#z z=iZ>((sxI6ORB)R8Y})}Qs-+Uyq(z+8Vy!3Y+lttmnUU-KUpSPLCeXM&_Y`eKW52I zQ=OzL%<1uE5f=JWUIYs7_=7@#o)P?(iZE^JD<*u)8%l7dK~6~C_sX#?(eYztQZI=U zy8x`ZKnJ4&Fx~7plnoNYqZooz#c3>(8bPH%w1vQ6!=4_Gufv|H%;_m9Vi(0I!aLTc zhX7G{Po^U>8Ob=5lLk?JZD4Zh4}P#0Y-CmqagICBEVL0wLF}C&Z9%#D5(_HmJgSO0 zvCv=>g{vb`{pl=-Nsn^`w<lUJxwg>>4qm54EPm2vgD6; zuFTRG!wnz??rKG%Rd|W0OcmZwy6)e#4v ze^?mRN at L=4QKB at MT|pHmkR^?cb`v9-l1Yl#OK>NykV#PgaK&(#l&I5 at QF=j)jpf3A zaj+c@;FptEhfz={y5u^kV4*?1J+H-eA@=N6;k%vu0KRW$P<)#piGDC at Z(~$<6NWY( z&U7f#I;X(X=rkBmyF%S$M?szo8#)In0 at u_DQVvT$*iFi0SEkxw0tV+vO0Fr9W2=r) z8Fqwt^2n|~T%W`q$$nQ(R-hKC#&c~TVwp3yg4>OB`6&WU0=lEafOR7{I~M5=%Ips1SDCOzBE3NYU_UP?M98YlxI%@( z(k~XLdQi at j#dfu806z}tPErbXxCh{;9pRNGNqd|YmI)U8;H>x(ep!@=8WDf+tw+qf zZ|ahiRA_pF*EJaY=e~4-oG>A;odD0w=|*mUR#xO&(IcfE$1L=x zfrT*!B?k&WC&+YUhU00RP$MHzDDs1XSWEEOL7{ApC=n8V=b9uInX)JCctj~ypxwY> zs=}l}b!YC2Vm;;!P_#*%&q}USgAPNXJ;Y{c&vYgvv%XZNI7PMGA3PR`#gIWScs`&j z6&dw-mYz_iBgcTa;Zqkrn2&y#LKJ%$u4zzw znK3tn(=^P}ND7p#PN6q6=z0I zRq*_ at wSr>`ZD~%%4pYG01HB+DnPX7w&R_trHfK&)(3P+ogQX%jG5En(g`s3+U`7WH zl at l)r(C5QV7BqwK5{Ib}i0Q&<>L2FAeM zz0^!D!ZxI9_%UBu=Cs`ELMDVIcx_JSz*Z@~nF1+`8?>jAxNx4KQqu`x%ygWYvN)WL z>7E`bub)mKY-+Uz6 at 7dLQ^DLP`7tR|(V#bh$(0!s{u(@34)^eae8PhtQITDe*ss9v z+Z78Y60DoScYeD!95G{>Oh|3qvSPYGf at fPmrLs>9m-trFM5uUpd= zqpgoTre~Ff3`*VXER(gPW@(C-K#dHyck at kV##4P68J6 zQk-3B<-Uu^*4thxbNa*hkN5ZRXPl_*pa_y{UvQE2B at x9d#>1hAF`qjE3!IOHELqH# zQEPCg!SH2dI2RWg(T>%cnlz{=#QGc|D7GNuTGPJFZhat*n;vyMTx$_g#aE4>g%zR_ zD+~(-_U6Un!Z-*uJXk)y#9nQl#c7crw zyod@>8e|7pc+ko(co1nBd;Un;T#{LF4Zg>$uHgRN(FaOwe-h+9${LCDAgw@{g$&P7 zm)tXFC`*PR(_jddc0ZVABhd-MkI`zZ$g5EJ&UYG^sk8eY-oF=C$XqCkbhb`+XOC$W zZ>H)6r96WL6AA@{aR7RJ7YV`D2E`=6<9Bk0T28&}wSp7$$28Yn;7*A1P{m}1gCq9` za)t<*;xUNjC5~4h=MH9<>gstI4Qid>0p;ey{ z+l=H3W%zA+#5IU|G|Q6TnpJq|5MUz0Lz2Lk$&gmUL~$gbI@lI^ldyE*JclA+xT-IhwF#W+g1;#< zVK2CP at Lr7Vu+GlAF)b6*oIYB at N$JmPGA|V4j7QDxTVhM6Bj(H0*)1p$VMOl$rR0W8 zIc33CDfaOQS at 0`~X|KXYg?cM>_!NcO6zaU#oN493r81`%L*gnZqzIOvf|OwZFO%TJ z4E5Bl!|(+s3S!LX#yD8vA!`Tivq6fi0dD>ZU8qiZCmMG$LK*eJhy0p5%7xlFmGmz?b-I9X&&@ofG0gU$E-j4%0cPb_&1q-Bo0qnOCPubqZsr3o6##9M({_pP zh+ at a0iV?gx3*I-XTPG?=8`#mq`HQr_RCj?3_eZokw~(kRL`TL_b5|um7&DU%izj4d zBskrKVOb-V0z5 at -`heav#M#p+ECYG$#xV%eHgpHm^qZ}6eKgcgZc<3Z)WZ#4)p zAj#Y8_e!wRUqCT?({EKJw`ed$WzvWadiX{m&XbW`g`r7&i!+08VQLFTj8$ZsGAk;u z(I9$39}@e~3TruAYdZ4#$AB1$c+xRl3d{%x9*LRHFCO6zh9jt6wHPvkzBNF&DGUif z3CaL^sK`|GL+ zt^<&JODM{yz~I6m at gPl^w7q0j408*&v~eQ9RE}efPQ96%z2JggFw%lH7S>vPg2UBe zsBqM_kmF)GSfN#kBf1(>5e`F=HK_UNY0i%(p3w<cJMkntr3Mm;J0~V!D_M3JLi#a+!W)hZy1}AGG%h&tw7328yR3;OlxH2kB6nG^$afqmumwwfMu at LE$|BQh4y2SL9pe*i}jT z`6Bx at Sg`g5FXkfcR%gNj{r^QNn z)3HRS1PC6)yXpv9ebLYxhT;SCanlm47p zgO6F at H)=P?;9w;|hNCMC;+qoojvA~**oMR>$w7&z`g`gGr6bhL0#WCtbaJOvh}cU9 zbYc&hEUvu4CKgZO*MZ|31sLC*s$v2#7h>6BGOxNPN^)c?M2Wc5oRO$IGf6ARmE!nX zOL2x_e8!-7jMShRoPqC7TiDngDLo)Zb5?;bmzWHG>JF}gLegNn4_qUz6e|yfMWIhfG7b8uqyt2W$1D^h z%7Rvkn?qtQwR<(b7!^-57wIM>7LM`oqzov<_+Y^)N^^l7#j!~f$Kp8~)+ihlhE|6m zQ_`H|gJ`U9`0=$CFcYG{4mONRk*_&3ioKQ|K>@xTk|g>QQ7oo(@&^W&6G%K*n zfUQf8SD+=>qd=*}>0lYXMM>7(r4Yjb(?Q!n_hcS4WIEV;2>xt^ZfF)`mf_?N5`G^_ zb85(Z`w%^HZOpt;gQfke{lPD8knqTT;xK{nS`Rx9t|m-e7!!pp0-V>#ss;c|8p zT_Vp4M at eky0W%josEO4{ed%e6qC4(P=>6cqf%Ai02-lCbm;@+tq#s>P1G)i74E)u7 zAP_hR20btxRoM{u!TqIc7JNgF5QK44JA)qvyfYBiuC#)V9pNJHSN`~&ZChurSrTNOlq-XT#=Mku#lq(VvWgU;8nG^jYZ-w zCUcz7X&Dx8y3K>?2&OSfLg6iWA|%*ApO)qO?)1`V1;v_{j&Qji6o(g+3$c6DQjT{V zYBoWeLDj|PNV6VKGOSC{X_?PSOIX{2MuxIL;s6}MgcBP=&rApIv;}-rd6 at wjnsXwg z4QQd&JRmXfwfezkT0xANgt#68-I4id=@F~KVN`$RLN6%IU?9-w at VpAAyr8s!BtYp0 zmssNz{P{GPt_;wIn1Z&1`^h z@!)hQqq%-H)*39%G;ydcp at z1M2b0oOONL(-bNX5l6NVq`1zR+z5$aoH7&c5sVo+c; zDLF%8$|#sG!@?Z+TMEY^LpT$L_yWCQ7&0uv>Is(2;IxVvs9m8wjOl!dNw(T0FepX1 z%E%x*IE-V-${;vs8zj~do^#+ZI0Ygqt)fI*K$RpZN1ZCN*wRf!d;^AgPS@%N at 43@e zFSxwzXA$7_o50ixw#;dxz{Zrs%dn}Cz}(sh?VD8{jHoQgBFx#`>ESYESQnf(Awezq z!TKryoCX$T$?vlUTX%SFQhPcfYBP5^5B8IS>@Wlmx+SBlBOgM5XC=4-;9C$_#}y9z zeKn=f0%Muik@(&>U|0|7tOxX<%<(d%mH8=B-uxM%|G|AVj zL3V;4G^CRlv46OOzoq`+ at 1i_-qKxwji2yLQgrvcgT$RNU*=U!c2ztf99qMwSKwaTP zfO@(p#4#74D3$?nn$S&Sc>;`nH!8%y2_N^UmJTQy4wycN=_?xg5yOixY^r*Ke-cMF!|Ew zJZL{0kbJljx#|hCGb7bmsV_iyRgxEdQ%^ciO5Q?-XGw(sW3 at Zt-5|Z0S#*b*GBdxU zMF=@U?6tD{T7#?5ptyrAHr&Z?jx#+-e+M8R^H417-!w9Fs}h!u9XgK<=CbTMxa-fT zJ&k~Zn1STMp at JVVXv<8FzZ#5pE{*O(9xD&h5By?E;axGH)Z>Zq>Q1w#;9Jw9%a$(%n9b{x4uQcl#8W-tJF zmDx(IAoxx|Y2S6d8R}wh1{Ha at zmeVt3|reHZ{Y81P&7@(;$a)9R$94uE&>7^;q;>5NMHAH)zfRgRo;* zki7&;6<)OyTojM%amW$!{8Ed7L((73nJJfI8IzRc^hFhBPq^n|_P$UYnj}aj?E}X+ zj2|`k_+$f-QLFHc0STrf(f~UB6`!VAiF-U~H0a}!n-M}a2CFKp==*YFP}vapt31NX z*eYqTDT=in>_`&&fjSQ%4O&hbz)R~R?#eJS)4j-odvsN^TM2Vi#8a^ZV>TkM(hHWy zGFkl%81}LT=QeuBd zYD87B36p*+mjzTmeO2>ZrTG9R0sVDNu9 at dPC_6Z7g>jv9%(dpoe>$D3S-h!<0m`GLe_rKkhf>p~c3D z)V~5kA`vwBU~a36MXquprp163i|{ZQ4f<8_px+2F7WiFhW7(Q}8vq4=9#puiMTGS# zms$tK*8ps7vzs%W#7K8sHwz&a=pFDAM>^bSHLOv9SH)c(kDR9lnNk?>xzM9Egt14$ z$sCr74S{A5>R0CtVQAmM`@t%>ssZX6i+0X}w0$Z=uC=gFM?TO73Met9Q|R5T!{osV ze#M#LbvwVGK!|#5eAvEbL0_UlccrZtTt$7PM)1fe%qJuW_^BceEFM{z?mn at -J;a`| zO6>SM8;?kQA55tBU`rNvXfSxtvx0S8<|Bs&+laKVmcUPE!yk6{rOUm+ckH$Y{oOKU z#Ec1rY>uEggIzLo9<;hEBf0AHw2CxwAQm)M+0_;b1B`OA%p_TvYDPEjpxe)xrZmQ> z(}@KsKQy~~0!En?7g8kUd3yi4A7lyEqDz}5t}Y?M6ig|FoSy!ABe-ZeRyne>o-pDK zbD9OYF&fb+$=YN>m%%}Zlp%DiMUDjUN>8XKDo7W2QHLWhT at D!BPgvlYmt!5*YGns? zR$T4S2?`aQO&C%M3l5o=35N!N?4a-2=m$R}oKVl`E=7qVFm;A`KKJp(YPDrSvLe!9qbc3 at 6ShXJvWFEs2qro&TKBYkq%J`ga949E^{p0Z| zdccWq8kwn<*;$NFhr at W=Dio3iU6XXZ!KlSy2R7VknU-9l(d-xt&uB*&^!KH1kcY17 z-SQS+=GG#ohWCX8YhApoYaDK5$>{(gORfM at i7$XhLadrW#8#&eB)z1JVlm|Kv`9#e=c0lzeIz#21+v84_RZaw-&W zppOg)8q|#7jijL9Fzpgog;-=r`{?r$TxEE5ra_(ciJ-0$BNE%Uq)d|tAAvu#hR2US zh>q`-B{-rO3k_C+_Y?k2Ol#WOw7j9`*e43tq0*BDW4IHK^-3b)g1!_??_N!d! zFBD@)VbLLM3*Xn37~>IEUt20xIqX;U1c=!-m~1$VTjpeH;2r2&C#}Tu1qMlfNNL3* z>4NsA(GHtF{%tts#CwBy0B~O=b`XO0NP}mk*z@`Wll&Wim?p*jIxIZ+;rZ1hg|9n7 z%gSU+`a@|@#PJROlNM0zX%2}~89twqz!Y4(9M|qlA;SZ4&Veg;IU!HTfHY`kXT-V{ zw6LJ16q-xzlG8de)CG*(BH6`pJcwx+B}hHkJB>jzWX6gHPo?md at K;4I-gsLzC_mV>-y;gu03bJ+);>Tn&lV{WW{R z6KlG%X4)qz at Wlpo2(A`Ye0bN7svk5FoM{lF;alCH!ccrbYYz>15}Fw at 07VyzEUv2Y zo#^1(6bZoShr88{;RPzIr4;tDM37qDZ0Ti%xTSTk^3k at Mg?K+31y*kP=@8=mx_$BmlvJ zjLLAExJ`noCmd at yj`kvLU^OclkK~}hF*`Eh)fDux8aGh5r4I+#toy@!#C at 4I6rZ1! zcuRYA=1bp1WjQC28R+FoQ(yw1k;Ndj0T4>&ey71Lsch@>tQzZ0Y$ybU7$u- zTL|2!i%k at _bjdXpz6Ff2zG-uj`0gG87k{ZIbc@`a*W%_egOv(t3`nvf4)`7KbOwPs z3k`n0PIP6C35hL{gd)xZk~t$#IM1|kdnR0^sL>6MW4M4qh$4cAjudV(qUBY$I=p`( zM%WELoActz9=roTy?Hl=#R9+E6qsWQMtUMhF4aijujx^%D|lEojzeHiit?f>B!a_^ z4%XcCDp}RJ9P7dC;Q4jvk|KonMKvG35xBN3;slmAMAtl5bpuNL=B(4-5bb53Y#_)s1eF zUjvDl53Bwq4{By#3$mVasq0IU9c*5YS(#86 at xUKLTgQJ0-*T*U1to^k;xj^V=~v)^x{G|S7i5R z0)e(fjkPXjjDqdiP> zI(4K+Bf|cWgr*?>gc0dSw;jT#ycZC5dGMYZHYS8GO+V225L|d0ic5vV=mnD&=_1j^ zNTVH;0SOYT%PLProT-wLlv#m0j7L`1;@OahM-)eF)uwb_gdK5)y~XT2c6CxZB7iB4en3 at U^!4j%Nt%ry?9-%}x6jp^QR zLhq&|W at y?=u{DC1Qlxp#pqF7as{?^^%1!S$oaxJORHDwH)?)DotK(LA*5y>X!p?{; zZF0(ZPNFhpz4}`FmPz>seD at t&UzGM$lqf{jsj?DHtK$vtfO7X2` z at U3z&sSX}b2Na7e3Qx9Ln_f3SfwdZ=;Os>FmIBKwI3s{u*76mv)d z9<;i9R}g1GqRn7qKzHkWt;KFNM9osnh}NAwI*dsg at bmGAaN+{wrD at 5mOo%2>9KlKp z>yUUhNemKf3zDe7b7}__i9FQ<=zC0q;&6DxQ%I!rf(Du3KlfzPLNN=n2xAmsr^3bp ziRh6nc+d|zk`c-6$jnlY2r|t z!T8|apyAM%aINuo+)px~>uoaejA_n{tprblG=`}R54;;jo~JbkAvW|#6Mqcr>ld43 z__?)drc48X2o3J=dmUhWQWs|wp1XrJ2f7j_69x_P^^yd2nKN(*0%UBpPlAaGbwunP zq3Mvd!-n$S*jchMaeyzBan%yim%ctu0ppMeOYeZ&UBR+YSBVj)K~cyfM#hyTEn|}Y z?#LirAz9Fz!6Eg77c-J4$918LKlJ4m59<5dk(k_>-%o=QR^V{tYImf8yoHB)a5PdW z6Rp8;q%jnZeY$m4T+X>Df8HE=IOk+!GJ<8!6Ms5RFajTrM5T0sSq{hD(FZZ^EsSngS at kKZ2 zgSyudAk at qP2=IJRYze_=1gq^4Hi2I$!Z|)IF7<+!&>pnc3$E79ptGQ2P}Ipv zFK{L8RdbPPXZVA(q;G98X)P8SZ1jS^6NOMC>O}_)x<7qNge~w%R~QO;aTIDc2I+l* zjvfto(8zE`XgV7*JWxiwneHiDpF9_bdWbhT#`B7$`vVJ>_)HlLk7<**4zBjcb2Sm# z3Q5D2Hg~!qkP+HCl*s27ul>)wnW`Ljp{L%o<@hTEeq2^zg8J9jTNHTckMiM#Q60S*LEpJY$kcuSueJpf0^e6;xY)gTgE5|4 zv>Cjxr`rqd2Ej#q8Kc2~B8DT;8_rUDX~UHE(Cq1W6chBws>$rE#SM!g0V*;!dPCc1Xr54QY4TgE0H%l5dw;c3ou(;EeKU0A(YVpdA_V8n(3M(NNJv>7ai!TE&Mm}{Q zc!*z8AGFD6=l$XZN6VD=*rU_m>L>Mr7jlGU*{m4VB13{-jts^c9e;Ni?nr!b$K8O} z1ECM*gb`a*_ at z?@^Ztmu0*j(hE>(%IaRv at s^O0b|491!H)q at P@d0xSOxbw_v5M$EB zbL6HcY!s^~NmnEo9-f%7Vr(6uS~KhcCmf}q4s|NPr!Z`Fgj$8=re_HNjzDq0k_MlV zli8uSVN$X^nOVr?xW=J$f}Iu4OoQBNx`98L(&fms42mxoiC6Jsln+f0>!_qvg3~-W zcu;k)*58$d__OI~&I!&W)(ls8_KE`NRJ3;9JO(Dl? zo{R_wZpwr~f(4iE3l4(76L;`gv~kRfqeX1g;u9FYNr0|TTJ`vAtsslBua2al-T5W` zpbSW|A($P(W><);Adj79aDn`XcjC`>iAyuMe~`#vb*)pD#ryB)!A8P>ow!zL;Pgrc zOwC|UuAaJs6vAQ!QjWMQ*sjP=0pV6=Bt;az1cNs-g9614_HqTj2BL%%)TLgS71XiF zjeTNI$wYD*GzeF)8Qh{NBXNbW1Fa2IF}#K#uH>+U1tCZ}l2|p!5gUvQD=KnP$>Uh8 z={+K|_kPFz()2I{4v7Nm3CUG47FFDA2OWi7QT&v+jEG|HljvGqxBi+8=?LNv?*nfU zp|pWZ{=otXjG2pGu(qYsI(s&!SD>-i4q`460Q9&F09dD01Amaj6%Eb~z+8!^n2^XO z=VZrDONx3>h4MJIi5-h-OM3~<1zBwv;7BRFi0Qp~7K>@U3njinzn9^}=5qHYnB z5Z^To;<_b at buQ8%!$99Hd)l380b-t&Xk!TEu?t+pADl^sb&?*@u2n!*eVvxrv z2oB>-%^B?nMF39%K9U~~Kz|^w1y$0`%jhW{d@~xX*sy!~63JeNOGCKxU|{e--i=|g zI)oxmk~64##b3N^NU~r^B1Cv)3-^*X39mVdNUJ=B{?4=8R$R{=t5Vh!sLNrYUFM=UWY`|S49 zn~qpJC_YR%;bTYQ>+>Uc at OE}u%xTN<=MmlSV8J{shBp%>_`;%I{dKkat8%2_3@$l1 zV;-SRN>VFT zWZfts^3V#lfcR7(7Dck{*gIYhVti_D<_`XJjW{{ePA&U~IiE3owr^850fPVi^y zxNQucEYhhnt)_H?2A4GtNVElykAWE@$ zgXQUS0?!Wgk{x{++#jSBY=%~CFsHJlD#UiS3 at z#xe=d3Ooj=CA?~lxEn_})i-vQnALajw=}$DNy>bp at 0b<8Sc at -qIbjHXfDgSN4BXvKu1b90 z54tpW at XC_W9XdN+!7Ff+|Be?_-{Xac@>fzsUle!y7uCu8E{RWK3S*Gpj?;!Ulj`vN zFk`y7(GeS at 0I3aXA;TUDH at Rme)*Ne+B)vM!)O1K<2$H=7hcy z^92DtjE5sbJ^A at s<)KV+%nX>YL4*Y1CWl7F>)4ZC>Yt;ah1fLt-@^G2k`g3hTVe9?7CTp at sM<8MM^u`3A`*uVlha z(^{INhBWD2v<9Cs9DbH6Tv#yL!aF>uIfbenY~b(~A#ym9yFlSUYXwjD=+G6`7PZKc zlWPVS5|$=Ryd2NGGfQx8K=68-cywnb?vK+2wHrigb?^^7-e?@FI!riTRAHsUp+a4A zD0kTq;*i0J4aShJ^n!agc%h4JrC5m2MDhI-J;;5$!Khe-iAv)erPxngl>v#NP&g35 z)!Yx_NjT^NSAtD{oc at 45&PU|P>ApQ^>%?%QPUlMWsOkP)Brzy(<*1 zq{7-3bYr^9h&W#>Ka*~D+~slc+Q<&tkgiXFI7cD0N!*vwaO at Gw;R?gV9g^LdnWO`3 zBo+*br5;~!p;$BIKr^Vf3lwf!Jo7;#3gwf~;1=!Rc`bG&A|WlUz_{);gWRJ-P+#c} zp+OMgc_Y|aVk+=c1fCUS=ny}AP||{VI)|`K(jxOL>^FA>v5_HJFpgIZS0;HghbHC` ztDm_UMy68**3jUkB~AOK5FTo=`h&Cy``zFZ at 6X5z2IwL$b09BN at s0eTfly!Saov|r zRd}lwjtx2;65sHq*QyOftg?d!4T+21T(^U(qA-!*YU{*>2sF!6yvnSM9ifEJmZuZ9(I1_ zPA3-JZ)Td(Tx#bB>kDm<$;xnf;S(U>(wxhZP0O%UVHRWS2YX?Klp1_(9L|;W!gIF< znI3$ddoxypJAuJ8qx-_O7!OJ{zA>O%?Y_Z=krxF$W)>Ir3)PLK$XPy#b^ zbchWi^a4Cv4;TQv5gVMQ2L8%`WPh{CaKLTUoz5864b at +d2OkoT8HP3mk^_f?!d4{? z%6n>3RpukFa=^l?&|tG4d at K98DppB+2Y7898V!amo%G1PtU`+MulZ;7MY<@;`9bZH zScUSn*$4#5Wadm&h-Hz4;pq%NYXc?4U&<~XMrS_tA&diYB8aUUghY?>`|$pVQ)Lnj z-ruDYB&r>)Geel-6f$0CK<+q60;bo!6-{h(hCk!dhw%y{q8EA9dN|n5JkC^Xi+4ezh^x7R!n9| zEcm;>!}Qk7pz4vOG=g_JK_kMd_{xwZm*CU}qWY$O at N76D-5}BmsJ*sSd?txUAwmp9 zreY43lcx`BWun~gW}Tx2___Trl&c~!Ayez#6$j057i`Gn!(v^jy9vLg{R8?tpm%?Tq}3C8C2q8WZ2snBiHQ#c zBM~NdP{t$evo5(qce>MF0;?3mL5fTe4yg;ZYOQ^r*$E1_o0Ln21m)@aklbNv%P&e^Y7(u|zD|geHx2fof7~eDKC4 zD3tzhU|u1J&*-o-ArTt6ozYW0Ji;T&nORwG|YuP(U^3*scTaLRv~2G@!N zIVx$RuDs^wFs+6<)xxW+&#e^1xeDi`LQfX^0b6pb{3nMhOj>&5W+)G9Pd|{t<8W1l zxA0@?MLNQ+S|dIoLJcaE;C`e8pFxD5?{Eiu{h$DmUQBy5@|AYbn?bPP8!RYw`1YKn zKJzy$YVuzLisDTJe8q|(0m`V at e3?oSyDFCOblnKD^y-0wpuUMGPVaB>4A{@V`N4$3 zh<{;j!C?sawQ<Qj^lg}r^4EhwsGBACDxwMC9#y^E)m*cGFxnrAUXZosEfB} z;4k*-x;bhem87uj6o5 z#rt$_3PvBu-0B!_p9DByIx at hSDW6pYYgaf{8=a~~v?pv&Nz5S>H&D%KDaaR^gsa45 z91^q-Ev^$k69MXkq~sT>TI?%KZ!)3JN8Z)J3g-YkVL^*cCthrzQPR_^ zOiHpOJ|E9X9rnE?e5DH|#(cXtUP~Wnrp!EF8!`!tajpW^iH-lF5gbQ6YD7%IQGN%E z=L!}dioo88PF3*!F*L}am`jsY7wC*5 at SMqoP!0?2wGgB z6P59E25S(G1fh7+$(pt;_6cjmJK**+6%qmo!g1uWtAh%XZd9S>Km%d;fEXK}r)AWh zdE#`z(8)d@*67wNYO(8)rY%^J;jJRvAi+10h{$vj#r@#4Fkr2T69oc!Y6-%8;aSrf z+1jDDYGOu!IL)g+n)vfm$l^;5)N?)L5jc5P=ab=Dn4kkx9lXPkn)t^0CM}urRbCuP zEO?GTb*B}IPXV2e=g5Ma&g$OEdNu#4D|lRA5XCYPp)z6lHDmA&2{x9P>fhkN(-}!7 zMrr?8h^b2IO*-tdQ<8KS>A2GlCx*+z!-R(tPmkX|>H~^76wz_1yVk=KgmP^+++F~?GIgasc~ zlCw#D>I)8#AXPEms=v;q$I2i)z40OK8^wAkP}CFc=M?(_TvvgZ0Fkk#a1+lVaRRYMCmT=u5Xra7_1M4HiWlyTnm| zb0Hq^P&&R1d<*8Y-!1_%vyacJNL;M2Fkcqqg2oqfGDQPNboyD>W6e?P7Tw#}P_#%g zXufdFpz4uD>+oW_a5f(oH6a6^8Njk=X)+#)YHcZ|15LR%5OoLBc}y>W*5e<1BgDW^JdWXNCS&^uh1l1)Km_i)2`U$cXXZ`w7gLNrh8y(>>BPrzfkii_MbcZwZ2(w0W3Yd>n z;7e54r$AYy+q5{tg8}pgd}=CVKg@$l1kTJ8Cr=|Q^NG7h2$BsLe7EEXsjw2oUs5FD z$EYG^O%~MMQzOoSX*iOBxU4y>wTK?Eu%OI|sfuSx67K`!ZJL0^b|n at q90VA%5jr$= zd^KdragM@)v`JgQ+MV&?-1U3}f><<3(k3uwdbTt$h%$`xJ)#%1B8(}?Va=3F=Ol!k z*WqXYby9K*emak1Q at GHfUeFfatI1M~)t!MZ5gOEZf=3l}pDreIi32UG<Oo=h7LoK!`u`!|e(_f^)#qRUDGQi~Zq&R?wL*85un{MdD)k2 z9Twz}&a1Lc=RR*AnGpMx*!*ekw3n&zlgsQ99^7%qZtxYF7qCIAkJtC zBP0l9K8h%QaKW<-=Hy{GLVcRwY5|ZEm~ONwlH^C942z!vz=H?lj5nGxu|#)J-m^e& zr at zPRg~FV9 at Lfrg4+Y_u*wE6`N`n<4-dkf at 4%|_R*lf;2gPqeZmngv1gv3o6)aV&; z!FFa)<1+mVF+q9Pte}bF>5#xS6e)a&ClrS|J`hPHW{0{1MJd5H5{U*-!uzL^gy956 z24h&P6S^v~P`J=}Flum<22)ACjY7`l5&_HS`Em2U%!a12QLmkAQq=4kwm=?Ty__Mw+6 at Gv+L%3RJp!c$jp)tMHBFzHp#S=+vB9uEFPAsH-E% zpzg$E8d3z0$3_*-woHsjY+M`*Bv8fC|G6hqUg zHUOF}SgB!pg at j@ocNvC?Lds*C̈#vx at 1BGkuP$w7FrPWMDDhAsFM6k0#nF2-U~ z-*;z}0}XkyJaYG34=u#z%{UFVOc_ZDDjrdKo0DQK#B>Rz9b`8MWo!&sHH4g#%%$=^ z;z6H2Yfmweg*V``<`V= zr`{mvb+1k03XNG^H5su1$(Qu7Vo_)i0ak$T%%WadhZFu-h^rY%H?u`LOrUHgr#mb- zp%;{xLK}W^teZrq=@*714wG7)LizL%#HBtkjp at 3Tc+=Y|C}=rBH)aBe-S9NRgIpki zHhJkmgBsgd@(2qJ zcAkQ>L~b?FU`F3;cax~%N#D(psXD at u!9je*uxf#(j7bhTJUyl%L9troOk$X8Bx+2b zF`6oSTtm(E~r1B)dSaT#+3sY&3=Y>L2_@!-3v$hjbY1&6gWt?VajBp1avrMA`> z)2&3K^53#2250LD=P+W_mJSj$JzQQUIccyUZ=JLkOtQGoffU4yD&)()9CH6RvNc3q zelV at 7alx;q6e>}?oy!moiDgLl5+I5z15vE97=Dae)X@*dOeBqp2|ZEA_h6XAGEszW zR(x846Ct`C!}+jxgP8_rqO;(Sb$MNLC8i?raUEkavO(9g2E`MUPSC?L6C`mZh7F4> z$ZYdAx*B<6P?B>q9 at tVD#ON#`p7`OUC!5rJc!+pWAT07D#qfqiO4^rT!#W?yw zsl(ND?n_YAMlj0nTXcxcim&LfRgcvg6iRHw_|wJp^;D$8n_)#}{jC5vi#bCU>}?cB zaMe?TA$}9`%KltpL<#PX?ERDjlSSPw!V;RXFTw`Q1b9-8Wh7F;vHX-{jiMmOIa>vW z)(vv6t|dkqz`LV8A!P#|eqCUp*!{XTi!8gfT6E>~Nqz_+cdr z@{2iy at M_qE4v~&fG|8R347m`&t%n$ZQ$wFb6BvJ%8zk7sfk_iLX;7C%fWjQ%Hwrul zV6BTII2}EqJ%dDj!R=Kuq0dPKf7Jcvo*G`c}?q17q$ z;#;V1T@`w!N7hLIXiH2%gKc}-YVpB;B}hF=Jw8;%F&LSG{vKtgPSChxZfvU&A;LEP z&=Pj?g6a(#`Wm6ZjXWs$lcUaKN+*u=1`d2a9emagra9f)?r%bI$+yz_sYiV|qf3jN zUXMi}uP%u at J)i)ul;e3;It&gJS(0`~&dd_pvM3bc&798ZR^G at uu~y?MJiYsZ$5X#j0XF3;PV@?2hBX-ZnF7AmNKh=@Uka;Uz%P~HR%*50Wone-xn{x?Uxf at odA1U)xoMuK zpeuCeXRX4S*2ExU)6=0wVxz>sM;+WA(jE7Yku=yBB at a$UEGN at aBJocr)tIOKiXVgM zems~{9*fB1-5A?yT#+Aqdt%|Oba)4e6%7UqTbkq&gi?^RCJ}ouZ!y(KjOW1uc}bY) z5*^5}@E>W=bE^`WX(7-LT%F>@|mPZo54s<@H2=hS at DXF#9)flzd0Qh at YFgt^)h?^Ru4Md^86jRU<$y)V+4!-iL z&Wx|KV@{W7kmqZy!F}imXMit9rfVF+3Ov4}G3^s$c^JDZk)d>n0zSl8D^c86B`$$e zQ|N_wI8!8&-cM15LV~Z-8NLpW2E)0sQY at i`PCr;rNPJrN1s2SvOfh5LYz!}naOHb) zs?`r97~5AF)e(quM5npQ2|cWXx{e^WRxQ)0DL~_$xs%zP#E4!^nh*L-KxbRL|lag3j%o8 ztHcUCnawSo1~4b5^a$GEFiz(zH?7mamJPz(=UqU)|f8ypyw36LW73I zC8JObnj883R%DRW=kmmNNd$Tdw4#rN2Iqhx<1&n at t~Cgenz0k7dz*C93PxpSW)KAU z01V*=4yKM}MyDdUwrH*ZNF;&^tvGu%Zl+c%8hoPS78-0knwT9SoauHcCJ(wfsO)#Q z81rb4);_1aUbr%*L6#81mQ}qWNQ4WilEaTKHF)(3yf!Pbt}vk+?AwCeq2p98z$cZs z8q-Dlr|6SX{7~#c4xjdcR+Kj_#9Q at p?o6{9KNAT)w=(~TM-zu8*%*z9)M^KTau%?& zAhgMy;Rp_TtDR{L$GlJ2uckp^Je$p3^??=kiYhreK#oIP6N3fw<41%9$%f}ApW7$& zfdz+Q4NhYnn)cvonH1Z(=PHsfXi$)diJg|=fzHgufaK}Oi{CizvM{BxUz}?6kOMuq zO4s4Rc6$FyB{<%fP67UcIj9F4i7jo$par&C#W=Kx%uT3(Y0Rujf$I*tvQBIcp=`6O zAV~rK at S;E6m;o^b&1&J4t)2GJ^$~zXBdT-d%?t*^lgL&M%EW7;Ky_zY*31 at dAZ79X zNV;;Q(X&|5B|q~d4_=pH@}MWB?c;3#h9YSswu}_7Sx^SVt5TqVk%c&w<0!hGl`%_Z zt at y991Z9V$S|*Ov%nce0`r9MZcs at I;a*$&=c3*>nbYsMsmd4QV*Fa%M6xXhxc7T0r z+82n6S6y`RW||nT;Bf?|L>KZ#XRgKB9sEiwh(3@&T#OGzg`CnL&$v`;dJ*NZcqdpT z7Q~HFMwG)9!i}LU7I~bG_29`e2yFG5!O+5F!$Tf_YF=Oyh4-P&pjy*>CVT5e)X9jR z at U#Ngdz?O at V+q#abOxXvFvH;p<|F&53KF7*9C^M at k(YVU#v?%AeP_`7KyL^ukNT!F zWA2PpV+CNhFPtO8pJ-6bn0V6;@YEcflnL#!@cu|`qGU)JJSqNUJQCjzunBFeeXv at b zxv-1Fc!NDdWAK(aXs$HUF>%rs4m&a&MtC2G%3!Viq#tA%W=D973Ugo!4)vmdTvQdG z!B1c(62v at e!mbr zVLA}^U?&LDi#s0@>y)Ggz<8C2f)9$%{Fns8G#-h#3 at J|^P6w+RPoyiz>-B{6s6UMR z?r+gW8|Xx6TG;oBFKTh}W?YA)I$ZYX8pmBOLWAK9)~IuHW_1j%sSKYUuw~nH$P6AT z#C)2BNcI{lyy ziBXkrpfA^ARmErrGycxVFj~NBfn-qxwo9-q&1p<#4r^w{Hw_pf{VNhX8&R7AqBHW2>r9~#iTZv7-wpb-WGx&Nn zPnYTPV0kL>7sPNMlQ}DM3xEyqIsO!f8sML67_uWwG&qMGhB2f4VbUYK)zMrFf_mPs z?GjHTC9r$b1NuWx365i7ylhXKJU)k>FA=jOx3UU&=@01eE4w3c=x=%m9d_EK9G}!+ z<3XW89ItQ(jp>lV#c9x)@T?9?NR&Y>kT38`7pbbk-D9}VmLZ7~N9cr|JG9!-k^s&IX;oNgU zp*)n6I6S37>S9Z5eU%RD8Hsg+my!fJWXY`{(lX1H9zL9zWwlZ~OK8xA$ciL2vkF<2 z%yz(FqQs^uk;9GRMN+F66z?k7!qYr?-^au at AqEX*UDBzjUv!qnx z!x`LX#YvZlGktk&1frxBl3fiD4jGGR17&?Ah9f1tkK<)F9gz62g!atq=B(~_Xc8PQ zv*bc-L2Bah41QjK%@<5m*kD5QW_G(K4 at lB{j+5fq)|m8QBj2MtL!m%%iixp0GKVad z!pr^533fDl!WH2QROn!6)kR(;v?MiraW~4!qM2LKhngrkf7= zWhzW_VW-10B3#X(P8PLH=$3vjSd_=%3VtGN(W)CE>Szzicey4j3j&OC+NEFg1a+BC z5BHF at VJX7Fg`bGxx%*OplQC0;_s=MhuNjHin-+E4=>(xlU`^{^oOF3)7G7Madp_eK zIJYj>Bwz8Sb#=)P_Mk4g1|TJIa-|VmJ>HkZfHe|lMWfOm2vEkuwnCDHNSYFp0V@=) z1$pJ9Rr6$!+R91k0zn>eBhL4>a&ipzsJg$7^o zbYigK9USVs_$$W?E_S1xAV(wgr{iiMaU~8}kj0qE(~$#`v}4?vfh8D?p;Tj;76X87 zf7(IVV?j{gG&8kIb9!ew)x_nr)j=vTN^}h5MtXrczCWJh&ql-a2(v0yTgDgaY5;m% z3$nPR24AvDC(Yo#<>}99zk@*?(EY*mJ`UR={O;^>RxKdFS2u)B`kVcZ2M)3XTW(O$ zR}NiWDL{CzhfYWOZD2ZOka9%#=}I-`prqK((5Dkh zXVK|`#ZrH_uw~-p{in={)ebzeThAw_#;nEln7FFNgx*(oihW!{8We~61b%6Sq)yC` z0+${YvhQ1R at ts+f2|pc^tWkVd;Xa#?M7izrVDR5Ds#E6EqZDLHH%t$$!Nnp}3LNwu zvZN-&9ujkKKYvIaVO5C{RYgW&FTML|mQQ&waXIS}Y3kS0oS2KijPhWW9=ySU9U5FR z4PK}BnU`@cw0`gee#gbffQ2fV>A$R15ni1KZ9qa_5cYHni7B;uJcB8n{Cgl#&zBKy zjQC(EW3+%#r&|no$04+#v9n-Gt~Q*()Dwm;NPBu0BHOc?6>o9K(WdR-&02iz##Gd2 zaBTbt4zDM4+6FGj at T*N-iIWP6vm{m}MIEM%3`xw7Db}D2MXLCIM-!Ja{D at u7(8b8g z42`Ikh>;iiEnba=c(ZIa1o(ERDs zZDI{iBdVb3)z~N&ms;`)j}XMs5k4qMY8dv2aPtovn?jex!hCm^oMbA zoNk0Ix-Vex8hRBHTkD)aVPy_>Q7AkJS4MO&=5s=WR)IacI?v%uYmb=4$SRdrA583> zVB*2%p$bqW4~5 at x#Gvf6ry}fYbhsTLjz`j<8ZtK~bsLa36auW#C%UAKL|x?6=(HO# zp^4)}kJoYDc|P9qOBlTE4(j?+rHQjAeLABv9<*w__LkZK&LoAGph9Z}S7caeOp8NU zDzfbq%MRz&feMa8j73D3L~M%~L~4e6n^uy8^3)qt_?>}plXn4Q)+Jkd>lR}g)EoAR znH>xbvXz<~8=b+0&#(7`Uqs|!+C1zO02PA;qLL!tBoF37xq)0)`TFWvF zmyjZ4ho4O4ET at 2a+(jYcTDCtTGZGz;U^p`Db$P00B!hmY3aLo2J0uokT9=p9oE~n# zZ%ex)fMTqW at G(rWJQq~x2mgK%gK=GgP|q1maV=XUu8qMOigj2)^D+{QkruFi5ED*Y zWqw9u`vf5yLNRA#r5FX-oSELa3NjVe1(Hz1QHvpoOIJ9=r-$KhjPSG#=R_C}H&wBD zGs=T*Oq)DTAVgeMf#_r6q=#dD7zu^;+2>~%e82Ba>QYVyZ6R?e?sMNQK6H`x$Hv+M{Ox$b>pBT`^VG6oQ!Vj^*DF?lM5rbd%Wo&Kaoj4Hw^hsDo zftaTwc|ey%i51~8nd2#gGhhgh(C%8ZVm(`C7Dap!nvUS$=g&NqExKvaMljDyP at DJj z6|hT!N!_CcA+I~hnNEgZa8m{8aS2 at rVjp-kzpPK+_#mt;w*#kWw{Xb3SI z78PZ{!o?Wx{gKALA9ca*pB>pryX$j#?Yb9=MGNn0ju>s>DOyLhs6$a at F zYEIA{YW1Xp22aYbATA=*CBN$CU_YgR%<7g)@fN2Wp$mMH`yfCtnd=_6dV#^aD@<<5 zNF5f|GaO#QFm7kwtpyUo4s1vLfqww?!JRov45|*%gICoUwAoycUyAL20f&}ZxLuB= zAPesaEN{$-g$El1=z65bf~)9av18US24$OW6*#WN7PJA0f^yOkN$Z+jnAltdo5Uf7 zrxxh;53IhU$>d~D_aSI`nW0|lL at kCckzTL{q6fB03;5Jg-Kzu^D)lYw06C)rCC<>pnF;9+wiU6(wJyUl z$FW-CP74R_&fxJRx~duc1Y_(JQwdjh(hpvhU;5MAX6Q?E;;oB5i>t8Q-II_ct(a?<)k~KTA0k2k}}#h7tQcL(>E1B|(DM zb7HVzXc8Tal&_@~Jk!L{5yn2}vq)juf;iekt-&Xdn7Y9)VDOGJv&gHLC(V2@5XSR(xUH=lQN(;VEv_mGXwA$5W!G1zdY`>I%d7zxdQ2shQp4cv8TklXA>c;BsQ zdtf{Bs=seMk}-G#fHb4Yg)yb0FLQg1sX>E{2}uWrpA|VsP>b-Y_gaI^kV(&CVolc6 zYUheE3Bvp3gVy4DI!9>?@P#S{4O(*W4*416hCvE2`@u9J>4dhP#(xEjk(G`|KER_k zo8~xC;mfvkg~6M(m^2tV_;?2=UdE;gL{#GVt>z=ip>9#(YPA^6pb28F%Cn8;Bqpc~ zcw$VC#g7@#2_hRaY5m=8!NVWaJx&Jrr6Kemmf-}$HXJv>Vcz288VO5rn+czC6G+6_ z3R^kn(hrr{UVMASsQBV3B)=Pj64&xx at U6553|_F{>O9zN2#o}T2p31+EKj}(5Tfde zN1Y-v`pU}8#Eh)7pB-+LjTwfhWv3l4BOX3NBtFcNb`@Ob6dL&3TK52;jEPa0bvV*- zW`Md9Qz(ZUg&au_q>JKmyJ}%~At7nNVo+~5vptONnh0_il+-T_uLnupK9-3MtAyI~O0X#3mR3QPo zem^LibYV+B5#q;2J61?uFDb>2y+7bl- at sk!1Q#vBN`qJUtMdB}Kq}%yg;Ihs_W<%p zgB%bG6DFHFy243tD9Pz_8Vn_(o=iN6DNRNi*HBIt3{Z~#q;}4Tb5ZWpVj46Q#v0%! z>orr;1Yqh5Lzu*&zbMAK+#busLW4gDg91iXB5%rJA;3Gckz`N1KTSn^9vqa^eybZK z3Q=|&B1E0Rc}6qXs>UTJLzmhHBoo$2G8K;SWNdUi7!nPEgQ${bDW)BW8 at 7N@!drE7 zri*c^C4XKxg8x~pL8-#={(?D3E_~gLDi{VYD)I8RbAO}!dW>3}noC5b4K+4I|Dpey3JcfzF_Y7S6K at hZ!Te)sao$Ftuy(q|q2D%7Z#I;J$DM1&NNs zht%N2f-tQEcP>P#X7Fi$HZ2ZUFd9Pc&c*pgF)sey1bCANnOapdhCmc!<|?nH2TrR! zsQ9x9-5CY446`Pi6s9HZ3MAPvvlQg>2{GG2Jg9y51LHuC1CqgYMG_BKB~b+WaL)9I zS<@$jl6sRf7hs at osG*lhFrqT3!5gM at Qi^p-EaTzRXA^%94BC`VwTPABd#?1NyJ4(-0>=mInz(^9A36ysrT; z!EgZsE`;GCS($Z}ycSJB!JSNlL3|Pxv|&jFL+b at keL>V}(Nk(it(}T8z zr*+tfQ27shXwBbnQjVYA4zdPgm5%!47!^Np;^jk+J^urNg+2xWz(j*#(=2$W7`|Gi zTQUeM9`usjx}+nNtU^rdlmY7 at o%7&?_j-w>O at wlw0-3N%PwzhT>|{6 at 3d@PBQG`Vz zn=RcZL4%^{lHkECNLnj608O$F_ at cY1t7h_HPPTqZ?JLMGe|DmK|5j#Fg)b17=xCg-j7lc`cq%p9hw_{ zjKOfrzX^Aw!@er9O?I+gbc6mlM7o1G;qA*OD)cdU0>8z`&*pUo!5dXLTY|MUxH$9{ z=>=mFvc^LZS({~K*RI25P|V1T9<3!}xStn%%t3-N5;?5E5Gdu0nLR4wtL?1CQG6vk z?UKYDnO;yKrxmRGYv8bpJ_)T_f*Qp)wm7$!j7Xk*I_&Sy$=t=D(V(OhRE9YEZUHeJ z^rix(=Dy$1mxxenuyliESZv)OiO^_pA_Qk|2w93n2|omiW6+MR3=hk*FO5DZH{I&mod&$xND>Nor(vi%?0>Q7Cex7&4EEhbing zcGP8Dzp-L05$~gtN0ub|>+WE$9NVCH7zRV&kW++Fi?7DShQj9-GvYXOTDC{xgh4!( zPMGkIz_6=}g#-yj8Z-rN_wriRlz4|)1B$5~6!u$LnE4CTMP@$~GNnj4N?{cMN+q5w zGI({+o1~{lmSJ$8UH+X;>nK1}hrkp(l_G*Xai>!kXk(FXL#X9fr*siteNq^|UfLH1 z;J$||)5oB$5HlutbcIoaxw&+m5&q;Fn z85$W5wGWHG7M8idV1>teK%zLj5+$ZWk`j^butS2zffAmcwGLM^y7*Mi`mL(49WmH@ z00|pGRAI4Z_Af9kX`pZ#@SI{j?X6m$!;o0m at RSJUgYuw2kS$>kiOc zB=NK2K^%FB&CVZ!LBv(7&{oPS2o3guNc=V2!G<)(jx}ym>z7A zp9+yGDRXkB0*1s#jv5Di(3hF`g!bYM7X5pO;sI;L$DwZK3M%y#1ddGx+?ZR{V1l5J zMK>#yUOT9;lbiyU*-ePV_Q(_Gt$w$}LXQV^Hm5b%1|u$xFKRLS!Ii~WsnA)leY66+ zX~Hi0LjW(_8G~e9iKQAhmg&qF#DHYtXig6pEW?r#rQUJL=hKNLsOI2U1F(%gN21+& zjNg!W=Er~rxzQfO;tj{GmZPYj7|VG{{=QcOgNxsLX3*B?u10jZ4BJofXV$dI)JpG1 z;XOumIBg3KO`6jw50()L3vty4HVJT60O}G6g8U`A&&*7AXyQSfV-m^1gE;xEHy3Fk zFuBrOh8{`qY at f~y8K*zgGPc}AzAcS>hO6nUc4C$cQB2K zyUk!xDl)JG>{Jhf0U<`#*<`T-Q34CSm;&!d7Gg{8Rvl}5CJV2kvI)nf7s+d zvjo9|0XjsrM4WV6K_U at tU}3=+*C8t%l{v)*zu%QGD7h!YPQSSM?L8Bq+pMftr2qAfI zgHuXw9a9i*klE=ZlA8>;VUvzpWFHV8pxXm7TUlSiYg!`tK^QbJaw^BTOrpWl=wKCD zZQ^kCD!hMoKjz5G1QWUqqf-)fre$1=7x~1a2HWs6A{;d;gHO{m+JlKdkyQ$FNZ;e= z)p$1NFxn4essQ1&GL}1|K>$zvz|#!>VabFW6KR?9#qm}W3fmL*;Oj;7#ryBJrcW^F zXAFu_sCm`vu#BNF>)>UzkByMlDoR*rFussMiHc$H;EYohRDtZ^N-~7=;XvbQDfY)dpffB_Vk)B#g!o at LX)xNs z at 99t17?i|T?M??VS}E at Lfl`Z;C1V_ at yuvUe?7ZL at 7dcIKw!&V{wA`1%rnJ=TdWT_Ac*lBP-+b_`%9+caxI zRyiWaY_+cu?+j(_$MCcF^TBZ=IfJgyr^9mIbi8y&BU}56EkgXNe`{N&2|y!1Hi9_e zsMvsqLmg_^1JmD^1rradD`PQ!SH$NOSdrmc!B7fv2A>93kAlnt-VQrPF-rOqNok=O4961P$mxhHVyVW%Stpn|5AzVsX;oA>Q~*>S ze6k{oDZ{u-9C7rD)82#xBrzG0vpu>qI$feGTDTnbd!X_WnJqMh`+yju5NiryN9&VfS$dWKZkU9|eFNcU zH!T7b07hDi*7S3evlO0i2r;88!njUNe>Xa;YOzd*IiS{d+H#Y2U{ZHpt%bwYgffiJKirgP!X=+Q>s`Pwn4fkEzzfc{jP$aTF(*QPM^p? zKAe+LNuLFEBtl294Cms9D)#gReY%Gs$9c2aop8x0i;(gtVo}e@$t8b4KS&Ov;HY z%Nr~5d0JtsO_{K(aoj4)@hc*#59DP6f6wnJOxpfnZOdr)*WVp+8dPsu<|0iRY*Qhu zY0f`Zk{vb4s@&%lnJ;$k>L~}t8l6q)lmg3%TS|RXIcM-Ns6U6rkI4`?3^n+w&`b+| zOlDbP)p)RB3f8Jve<($sWmyUC;PF|G{tdu=Z#|qbD9OME{@kBACqh*twerx?1}uy2R!rvm>;X+>%M1s^OCWzsO?l%}wQq!Oxe$ha`+}c~#a{kRuXRB7wi_ z*n*~H5Zv-TlFFcI(%|;Ys(6csavo at WZclNfpSnc%<1m;!nN)^d1i$6=q!#CXkU(tc z;r1RUYwm((kd5Ce%Gja`34RdA@^iw1SmT_yIycdr4h6D*l~3N6V-)C753NEh-Qcx5 zy^#qw?F=g#>`q8zHbUry5ZQIHJdi!|f7Js5e at sW1<|LIGE^5TxnH$MfmBhk^ zp-d{|;k&uQ;Sm^A0BSclD-vfH#RZ5I!Rg%TekVAw{(ipC6Jmo^bg-mn1aqv+1B&r* z%u6bJxI-*} zvFVcqrep|KkL8 at hlUdjV&QW_6dq#e>7B)=`A#!faOgw5D+C;*Fg$&D?)59ocp`36T zY#2&J7mAY{9 at D#A59#~76nitM#km{ZuWE7;D0_l1c1SAhg+&-kb-?0}z+i1p%Yib0 z;g at Jo`$5)anan{J*H2-JWjX+oG-yQ`f&RGcgYls3FP#9e{F?+6EGxKE;=t at ya3HI& zAFnU~kUE}oS<8hnB~d1+6BJznGHBOxL16Zf^2$59V|PaIL2!u8BD^&bih028obhjD=|jPUASVkO$0JMGhTOSu^*& z8E6ydKU8IJ0~=t>nZb}GM@*WFbds0Z!S+Nomf-3SBE;wi7s4+|;#_4(fDHo;}h4;F%_-AdVLz(UeG)SRIc3uuSTr2^=#8S~qwj zG&sbK7h~oW#2!3bq_gd%7?bSdkd3JYi7sGo$yeEbf;Z#C;w4w&$|>xyAWRv9q2&eJ zDVZq50X;wZca|z_(j-c9$!e{WV$t21)1~6kQ3w$30l~ncKbW}8MO=kQS at sGnNJKk$ zHYA}IN$Ygd4T?nW7GUSXvc7)2zKSM}kMw1zL;l!_=QlB!`@jXuoVT?|X2e<%FIIxR z9w#U+thv at 3mbz@%Gjp01SXD_nWb0Is0DyaVW(a9qG%=Dh`@apjDfAJYy3^?VF0(SB zgR3uH0+-_J`^25?k*6`?v#iuHx)mvzBQ}HL`;f4VV(|M7NECw>Tn$%7YVgGZlaQb~ zgKZAX#dsm{DHr0Cs9~&($bBF&sS%#$F{cKwTZ;E-2r`4@#3Dr|&PY-r*2t>PMszes zOHj?hQx4pW^zx8IZJLuMrYbgFrgK|KDBu`{7?i{_!7MoaLs`)CgXBRe!+N&Ir6Oqr z2P>nxN^Q05!BTDgJoKo*2r9_5WKPHLm!}g?rb(vGn65a}fxH8$g zj%As+C=m=utQ0qC at XDiMR3TY`j=LKHemXO+?FnNrJ)r%G;tQMR`#c))0)w=qNrQqy zg!pvDDZPdAO3QC=k?Gm-~2r%=NC#}$Vv zzzM{pgb-|_u&+yGk8_h&P|GkwGiVAsD2%AqW+YfpXb&KXf*idd9{%v-FY#tlSGd%l zE-o98pcW at xti52f9SlW+xm@{rc1#KdT05BQu{{1p`EPEaP(+Cij9XHeM{6DYX9Hsw z!_{*+?*s)6i;BYN1_ui*bUE-zBo&ON3Sf3`h)vOR at +{ zHFmH1@{$B~atGayiCcB4XUC#EnwaRc60C!g=F{jTwspQsZ2Ro-K1xw;#bhc3k_O3w z1%8S?z3DZ811v$R6IaDR9PODl584B8o at G)Jt0RY3{e0?>2WP|d at 3{N_q0u~DScr5k-NX&n&QHm1a$m04Fo-JDCX(BR|~5|bn}W at f?RSusv~Or<7= zGuUbh1EO)lR>9j8Pbt=s+(lrphM#<+tDI&K_G!=()8!u9BL at wZu*@sE!IEFy#R~K> z{lOoHM(GDlfMm>F#a44(#{3xO_>&50&Wfci)Gq{&mRgvyEVeVJbv)9^D~!kU=rErv z&a^p$pLaNYUn}vR6r5t6eJD{H3+wHsQt?f3p!}4~S|UiLJ&% zIpgr(gA(H*BtH{_E=iVb#ayW3Rq>Y%LWcKiS-i(rWpN~zW$go5h1W!fO+$tpcyEz& zM;kWVBe@#YcS;nvKYngjRYKT}n8}Sz8syZZhdqiBt-Vr;r55)X(BSBge<+sBUU!+< zk7r}=S1G}l__!2#CxRhaA)jbkD<_`MpjLWG-xotw9r>4R~?OmGT#tKepDqH$kXWR z6NhWP at Hi%W0Lf90$|6A5(dqgG1j|b4K9kpg2Xi;5#3<}c>zCakwtt!%;#tt1{yyP zT=hBeHgB*?&{>z38$8#Buniw=II(=chM^+qa z^u;|VB{+b8pN()%jFooD$o#Y_EEqKP at lG^&?oDemnC)pagS=@sgGPnmI_MDdWmJvS z6<8nrFexAQ1c at Hy;RbbdgBgV5aCCtWsmn=&NLOf@*scZ%7d~^ELHik6vLQFz)M+~KKc&WE$eD^*JgH{ji?%#)^ye?@RwJZ zq#W1yn*hXd(x1l>AUuxTml-h^oj&Pr3i7Ls80S5U at HY)=3(9Qr83 at Gc$kyK=k!51B z2{(`+;#)77(V;6WmrUt?0v5%aG3*&1URGn>q+8p^D>UVZWd@;07GO~ZB$$gNEV>gT z{h&t`&WiCP(1<8?96CM0nVCe2xF9hSOj~p*#MW0%z{X=ta?ss=KT3qZ5t~6yAxiPXVV8XwSHl-2IWN-59utHxp?5A!5FW&QBp$|z zGAumkYjnO%_cJi)V>;0%=Onliijc$|8UzVOK4w`(c*xTqXbm2np!$Lq+d3G}BGi^y zy^0LZm=nj4u3$0jK^)^Dn{orzi$5WT=W;>#(GSv;e$kaF zyL2xZ(ban-hz$l6{y<;QF!+{}J>D%eo}n;{5rPk*_+&l at LCjrFxYUensX7J@Cl+Hl zTPxJCk>E*7=!Ysy(*AiBKB6;mo)+ov(7;R#W@(J8gFrr at U6x3)9+sR!wae7#1a*lM zTXZ;WFtS?dL^ng=PrWLCFeSLIt{9J`0SUb6z+~zJ=lT=fYT(hz at BR`Zc%2gsiY%cH z>9Qb4b7O7J1P4y(v7A8S{P67&b*r@#OzQZY|GX$W7$v%Ykr)&(g8nvGGjhIB@y|n|>S7e&d%68Jn}ga7IurwEnGQw%u8olhE3rZmU0{`+c4MX^NYMWo2eJ(W z0~OBA!w4>9TUxeB8rvf~0Hqz|bmVkAmyY&GkwN1= zwt)Mao^dHzeY0gOyU;La{1guZAYNe15P&oZJ1`^y)uE17&&3mTEvnj^Ss)?hXT>l(Z!Q$`2HopLOO^o&;UGcesq${fr$ zi4b9O!H_0%yMYBc?oP_^h7VSSO&a%egUAU=C6;yKJ*gD|E^Lu(=m0lFr++>W;my#M zxvs^QkTL!P6qgJ=i*C)~)XcdIvqU#&kb<~I<1|?*E*AAv*J9w$!-G&IQ&Pna!$N|) zOxA-HNP2D-0(ECq8R*MJm;zaW*H2F)DXJb&!>Gz_f zc#XnpNT>K~?vSLyOZ^e#r!Am at Pz{#TXF2KZRT{#lg$50MKOKnB#c*Y=&o)|Y zJZBA|;!m;H$tmE7Eg#lNZq- at v^EGo|I%w5k*cHWgvIjm(V!@8F7$YMbvTEVA{N1m}o3`G81cgDP#J4am#43!4c3sy-o z56+!cIteS}*bL!Tp3#m$iJ>sY#36@!rC3u6Q<)Xh9hC7%TyfYSPGBvSmT0rDQ()$@ z46`L<{q>|mwyHbSx0wvtpo!xtV3O2|@*C$HJ%7U1$T60PAQ_BnrERHnipJ((nqzlc7Y(&ZJBiu?3#PJN+N zVWO`oi~Ug~s)@TZy8U|rB1-QFRJtG;8mJp3qw4qaGDL>@tO#?1WHaK+;^cHfLRtI_ zvl_A0XbfLZ=BAHN5#Hx<9zW^C!4?M7`pD_EE)7Y-vTFqx$|U1&Aq~!9)&v-nJ1xB- ziP2CPinug{mzK;lPI1(rr4(X7_pnEB4zfcxA|@lwpj$H3&b7V}BTzZ40{-)Z?8ZGmY%VrREAl$(c zV8GeGFX;t?2>(n`f-{eNT6`i)EVZTMVcTFL6*fA;nMf26niWXR0vhC9(4b`neO6pU zam3B&G`M5&F-2ckP&X?SWnjz#ytAR at 1!X#y1W7Zz-y_B(fs-j+ud`ch5c=5tnCA-) z^`0Ai;_(kW`}gOk6$FEK4_c)wQy1bSO&ai1TLw>kh^8?4)jA&*J`8OFV`c&yP7+h- zXZJ^W6;W~&>Y&0ziGxQ4?J$K=x7eK`0 zVI?VpnF;mCpFoE)FAnNEQ^Zn-Ybb*Lno`7gkJ*Gp=nivX_JdlA1BVivmNU=ImpO%D z94|ACu|KjSPogubiKkJdhvQIeLY{PYYIs`4!`cXX zGswA#PKO733A~v#c-{&Q==x+j0&+xmi0Epc1)0|3jD&i)WTl^gU)f`NKFd>>B)k9T z>f-LnZW)H~W*TD>*=9mDJ~?Jp4po??J>(`&m{f$%>}k)dmWOdF2fnaXERUlT4I%;RKL>}*QD5)U zMLlkJf~Ja3Y#Bo%dcZ&&$8?BHOKxG(0<7Q at CDNk7*e3=FHcp-<3S*BB4?`^H{Ynp* z64F$}I;QJ_Zm88SlGKvl*ZD}xomOw!X%K_qdNu`6Y1A{;#OEYojOLXU% z#Ex&J3{m~jKp$Gdxc7C=bi7vZUHf#S9M?i4NMokFKiCHHZFt0_SQN5m`LiK&R*M~o zhC2};J}a(Z7&DRT0Q54edmZDs7mt{M7jv|Kk6!?PpBMbWi%PsI4jc5R{O5376o08P zB=@a~K6eJ!7!sQ#p6qCuNq6bm*Qn->~2=WA&R1`+0_FmH2Kb(o}QkPI23 zmh_sszvG}cp};U at G_Bfd2W_DkN*Hm$+{53KFeDaVk=A;~D*N;GGnp5!CTE>QxHov?o3ADPsc+a)>!16 zWmc?abz@@8w2WjF42CBoI-x&H$xMuD?H|E;`G}c8 at n;>O?bTh_GM1i(9gWYmGRBn( zF>uJi$P-E=Qv4nklw$@DE9TG5 at WAUjeQ&9g{Sai11&~;B)f=92EH6UO4$Y z>M-5uKjFZ^bVWaSeW^WN^@JIB%}MdB2W-trCJX4Q58=YB<>Y<&u{lZSa2eMm0mqS# zr3+`U$l{6k8!@O0K|CgZFwtPICU1I+woiIN`>CMUfTFL_i6jOX<+;RQKRf_}izL4W z2G7hH$K1lPuxXNGTx at abR!1nt;OZq-TtZbLF&t5gTH`WWgu8lV#2THANW35)^2GE9 zX)uJyXhRXs?IVFe~w;@^C4ZpFY at YH)t`!|5K~=pR{vtHf3-upsRp^D?r5K#v{huNQI|iri5Xe=B~WP24k at g`X*PkeYAh^R%|}#_V108dqv972yVMdK?-_l6Iu5~uO%$>ROtpAflQu~# zSha#HPTddM6C)7HWYs`FxKKs$nk%s$@6j at +MG@PXtEC}q@}HiA$*=dvv+E<7k{>7( zc-9eaD8z$tp<05&1qW{6!1CykEgt^Bb>bJ;a3dI>kj8%f8F3k0m;Rt1E+brMvY05W zGlNxNi^Jm8*6Fki&ygIQ7#WNwj!0IpyGfj)`&C*i4qDwmA3FH+T$x89Ns+<@U4c%s z1!XRGLlvVTG#7dxIzAmFoPWblLrpMq%LQC{Q31)uNyqr+ at Z5>P@)PgK|K2x zv!GCrr)pRmLeH}Goc_gPK6<*@Z{3 at QHUR@saM?X}v;$<-a1{uzT;U^6)=mh6gekPf!yv2ycqFIG!7}(QUkyioP=QCx;R`;B&L$(rMYW1OT=C{t9JyzfR^PL|!NmS0V1pv}4q>^oLfDDU2yiLWInb zpVl+p!ih2b^(66VcccE4+FO`Wqgzuzu^e?-4C;rFA53en!MIw*_(2V_1rWuV5&m<3 zumeD>FJ)3ZXprX&Mpz%eFA4AeLM(XR56Y3N(hatZ!L}F{?5QnC02&9*R*gHE1s#jb zxfWMI#7g^U&0wbjkAv}|(x70kbY>V^C^xA9_`CzZ79+SBodjlN507mY%%& zD_tEP3>{+IOeRJe3ZxlUz+^67=A02cX2%*@ZId95Ig+Sg2Y`~?ACJ`$>S2!SLX+Tu zOoLG4q{8GAbo#9JB#5tk8SXE-vnZphgbFhe!LW8fTZ?^wR*J*AB znz(cLzL(^NGdKf#dsv3^yUL;#U at 8i$hjKEyN-=T at L_hGiTZ);RL3)3{F at ujO=@N~t zdJA}7k7Y_XNINJ=uG|a`hCVrK^{FAPErNp~LY}W!Xh(7e$DRLT-QXn;u08mvHC^bh z*%KZ~twps+XFk&E1nmi6np>R>gbhW)0RI?N$f)2jjn)W$UON?vG>*sNSY)e?DgcuG z;mGE=D}P%GY`FkwM+P^gKD at +ajt>PxTn4Vp^Cjls3xNDn$0N9!*)6gk26WjO)MFJI z;In*SqZ4!#hCrF|vVIC;eI8vB#OwpB%*>vTo?H>5H7v&r;+VJUg7}?|sKPLm%);P6 z)XY`~e~o&~AEiMN!vhxUI+fVZ_AokpmcU>H6rMPBP~xh;PEvtan+I2UJ2$3vQr9_N z1_vB9rHI)v<;71~os|qHq^IX at 5tapF%8{M?<}xsuNPT*h4U&oMmuRq9gW3_^g5Q=u z+6GfKA8DcR={VHDAg3aGXyz49VImFsKHcZ_E5Wem&A1ue{LtjV-105J+X at tF-zVi5 zfx%VPu^NL?j(g}M3g;-qMSM&s!O;>+Kr$ZL{h&oVT_Mj$&d0^clG!%jW{=Oe zV2Bc2PWC+%@e at y=JIZjBW;xjs26bGLBm5Pv8iQ-Ch+)SV4eFRUM9C5 at pw?txAK|=Y zU2|9_A1_#)tdM7QT^h7466`T?t6h0dnJ}P+!YoJ(uR0gvlk#gg6iy6}5a|SqYn at 19 z(4e04wrzYiY&;Fok5+iSenC$ruC6ALHR4>3Ju}$UBEX)oAd4z(E!ISduT{iR8a8SMqhg)t#Hv!-$DWjKO? z!;>g4|T^fWToiZ~)gfwI#I2e0NuYE|T zCl3BHNd;cbelXEsrHQ>WTvx2oHCw@$+|A+6(3r88B#WH}sf5o^NGBR>czm1>NkZ}i z2kr+AmR2wh94yDZJ%aqqod)dsu3&;8)W^UW|Da$@EO?&ULMwQ<(qawT3dt+RCE`y1 zY6MuW77uC-ZWz>+Kiv;c(~Jp3QVOd%V;$hC=*!0{oTX(9jcf+X34hsB at 3>J!!OnBPKtH0cpFu2MM z6-gI>%i8J)XFcQ+%y^VubbvlMmLW-IL5CtS*w)2-D(4hO4Kl1S7A)FOSSzqb_$h;x z2}yxO-N}5;0q6DDCnQ%JB>o7TI9(=Mz at sT-SWt7U1$kt{U+ppsu0WsCAmOL@>!h`2 zzoP+IaJa6lyv^+NI{VT4RKwuGvok%Wnft_(C%4=j9P>GY599}Nu%IF at Dd3CjbRt1` zgtavt6nN&zAS#2gh at 7&8oR&QP0lnbsc>M*NK)D*VSdrK|cbclCF3JR8dK}#v-5?x7 zlfor4y(apbW>_;erMvP5n`gTZCGMtB&yElD+m&!-hRT?|eX)Y(dD9S1c5> z_8zc%YVrNGX|UxF=4CoHWrX>9NtreB`(X{DAB-Nbv%>^K2Kjwg#pkg>J7ZOVSST<= z$~emR1mEb5A-0zW{~Zm0Kw!g at v9lDYM4}#xGd(0O>~w*Cyj$!P$L)bdJO(VKW?sKG z3 at aX7mKcUCOM+mozjAA1VvdUEBgeox%vo{m$|MOg$DCN?h-Rm|ku4pIOq3u6j)TE( z%&hnDCC;%4zbZC!!h;S(p~2|?Zlty(wTe;QD;^Rn5916s<{1Y~w}!>P-bD=R_DH7= z>u9dxz;yDtgcHP^HcJNWU}%vB5P9bBe4$8)L`|;Jr>;*X-26u at 7 z&kzV9ER|TS7;&l78dC#&puiW`he?1juzPM&lKptWU(}o~m+~O+r|H#HlNp=NNE8Sz zfx at NKbI5JuQW+eBF|OOz)Va?dKet-W+WGW9xyn`5pOyp$&{$`VV#bM6v+ln1Asz(dbomg ze3+ADbEe6K0A9)qmU;2>h3LYO-9Lo!fj&L$?Oy~2S3#gcu=b}{!(;_O65_n-MF4vA9{T{`2?BpnAbKr^J1qdL=fwS<5L$%6gcT_*9nL8s9!j_+ zOTd2;UG1-s%;3WZqJp at YAJP`4`7q`pU_;n6=FOB>>jqnB#CaZILC!-OD)^)z+jAd+ z2}^XfY|-)XUyE#&b-I56iX<=?fvrS?Xa{S7+`1yHrJqTIfjOySwj4sj7In&tqfN*sG)t9Xf>z(l4UwOZ1B{Uu_Zbw!O;hr z20>P)fbC(?g#cQs4qrMmn-^jtG5*m?@`)bi3R4_27&3T^sLT>=pZJ<0 zH*IhVjO_z7m%?wnZX27!GyBRG&OXAjS%&=g=Cb-h9Puo(npQrD=!EhIjJ1`kg#4tT?$IAVgYD9(H&-7n33dQlhin4={G z$*xDF5o`fa26kxuk{9h(O>zcvZWW1nW${b%3E at wJ4|)Ci&ETtGFl{f at o-{deZNd=#bf&-+ zOU1#0E19r&`N3M^&~%89_8 at 350gp^b@|*Oat%tx;@mC73nlpFUk7aqVVhqq(a=O8R zB7{|$l*d182ujDpm-JktXFT=cSI1slJ}(w)CWRFgcC9TsfKUg*ZV1YBq;!7?jU}ES zCo4>Sb6P4f6I$ZTBxCTAT0z7J2l-1<_ at ST4f2krjsnApV#)=lzI>}DUNvmoLZX1Wh z$C(OGIsg1A!sOQ{z;acyy4GlpVq{(Ycr6Pl%=wo%6hUHC*j%D(cru-XJ at jY~*5O@$ zl*3hZnJ%t5wnaw7RmU+1PkO(ROxP^R4fG*AjTxOOl8u1uN)qn|b&oS>ka$F3`WeT0 zlu?MBI{52f=m%RQ*w7 at Y4lmRq(HsUB(uig?mI>Vy-JsQAH3m_DPa}gGmHB*m%=`O* z$QJ2uj|>j%3HP6=NRs$Uy=fbi49bPwmQVoc9pMH^A{X6iB|%xFGjlM|cYy90qv7(* zhwX()@vkq1?UGqtvZo0~4n~$kfYc&{N1g+bGnPB^e0U}NrV50!0+IaJOv>1Y3QuT|NZcQU_>4o|I3+3POshA| z>G0>Z$^EZ>!JA2rj6x7bVOx%@46fRFgE0otg6xW9t?-uy#4d{)ID7~XYHTou#giSn z&lYf?H;8Yz*zVZ7b;+2xrHS*)RZhXqmkEk`xRUm6pJF1Bi%!N at Y) zIcgPsA?G7ee^>FOSA$*_#`(ZsW>>q#cqST|o<>||r#&FhCv=q}7C}5 at aUv4=tOO6D zMv@=~@re{U-yvaCTKUg4 at rDPqKhga8jBdYv&9n?J=?B9Z%rg?P1#54xbc1*@EHYZU zIzm-a6Ce+r4p%z+(p^tR&__2$3h!k4cXCfK%$PB`f@%vU;GMfNvPtYRI6H*Kf+rDb zijg3~@(@HEHI2DQ)MGha at Yjw`+f{flsbP;QtgC+80AaaZus;X=VN*I*Vnw3uk4#>T zZ!9q#w<8(wfZs62mj?=CJo4Ln8I{C?AEXwiAGfJbBmB&0!H70nW=lDlo-BSg6bV=A z;?ADK6tV+hN&i6n)uDd{TVRhCjOEBBOZghXlKxAUI zjEhl)A9I{UJp@(k8Cf6 z-oGFOdTvI4%#T^MF-)mx5TMrKS;r%p_*%W7HH5HaPH9jV=mL8a5O~}k7GWvRkCD$p zl%`O2O2m~W@{%m65UBZ>;6YoSb8pc0+Z!ZkWXKr*D*_AU#K9NM!Ot~1o~@?=unT_BNK%(l<0K?w~?gsY$sL77!(us-6bc|D{p6iMu| zqn_A_H7!awuvSGDBsqPqkI8>lk9oL<8ily}xtRlh8>S at B64=5vaUn%iU_IaH^~1w5 zkH0Bb^Iu-C>EmO%|G$!~-8fLLy8WIBhRAZ5nwntc^-cj{td|r|#6 at 7#3 at 4wLFXt=dK15J{5?c zmmj~;=VeP_UDue4|{rkq at p}VLs$?vrIxT&m`Q$JQCtP5YkQ_D!l#2`H3p>) zKZ69TGcUs#L~QjZhGiZtB79t8uz_3QL4jY>qQ*T!T6!B+V9S=y=o1ubcwdh6=tWm& zQ6n-ej@(S4K)FB0#FY$j{c8bgO;BGBNn|b;AJvkv=)PjHmzS}#R>O$d+?eJ===R^3 z&HY3Q3^dgkJg9N6#qEZ$#hs=}Jhh7H+{tW2;#!=J$m|%zs9oVyow&-(AP<+hFsFqE znH%JJN4pG;O6+s-H{`g#2p1DeETAw}= zfh`rqKLv)ZVq8!;e|)st!ABQ^O)*kwlByT9dB{pW40X&RY?;**QTJ z1gnTv-2aS$aZQAemQ2b22TcsdR^7 at Bf0o2;CS3Up98D#t7zvvuW3R!2(-gA!Dzz$y zdyKKsKIy><2NdT*g at eEl71>I#^nJ;$DUziDM-R&kTwuy2dD9BqEyadIT7$)viKE_P ze~!bsqKo744?pv&G1x*_^`G==ac&3Ksu#>0SdpLCUrvaP3T0Y{W2faAOgXpO-Y72N zEFBY1crdoA;!kJhnAHsmK4fOH8<=Jz?F!s1#sHpjo^CK12R6aC)f>g8N}?M)9!2u` z|903VChPrp`V(XNkrAad$nPKedcuSGnRHad9VpslM8B0`o#t1a1y>a#)(Z;sdH{?Q zwj`-FKMDoTk7ULe_oY at a6w0JeCD!t6nC!z)`atS(z9PhYtBERrnzgdH{oh_5ITWAj92pqyKr0!F&q88iOb0*f-iSGI3Yh$8%+u zc%9v!o-Mq8A&D&~IE4`AtZvF|I6ssI3(_Wm+^DRlz2OP;@>g4;!y1Yw^ARwiClw+r zbELM8V~$i4ogiN$SbRbpuXHL7|R zmU-r1`-v^O<50IK!B4~Di^mQAz)RRGu;+|MK;r9%5MX?+{)aOU3IGrI*%qAyWR{ec zG3twHUD$&Fpj(5qWhwyniEyMr_#g`XD4B at _xU1rc40yDErhgkQjj~|X6Z%+K-wPdr z6ulp8u^^9>c at mK91cL+*ag66bq>;!^QN+f1<-_0PFXf+km;Srze>r$Cw}W89c5Kzk zt|(rq$<_s`J*bCHW05%TFm(#?V at B@>COoWZDazzNF$ag=0IN&In2gl?cbQlf016`l ze#sJa4($5VP{F>^Zp*~xPyhDKgO<#XKKT4$@Ofy*C*M_2OD$uU2gRDU)U;RSDmJLH zm}3&k6ISE{Z0mmPeq{@~D{}(E2}e%bkZzcw5Q7UVWjy#PgRVgKw}${xDX?3H<7N{I zlnC`un9rft8PQ1}xKW7cvq%d5n9+Kw1pXNZ?>hzqWr{%oTf$)VpZTSk1u+LJ2E?OA zEIj at B1;f)WjHL=^YbLSa*)CnBNN}1*w0`UWL4^vwI8|AME5zwWFr<0DR-AkJLX!2z7vow2e|}U|BJgqy%71pwgPz_`B{&u2 z@)$7f1EoR}iXl&e2Ni-VCv-&;`+%fn%cv_8#|%#K{2RPzYpD6bp`1fK)HE14M3;!` zMruL#iZ{?;3hi at TlCkI;oKloGc`g&Zq`HASK|c+E^GFtpRB+m(3a&Hku%J!K2P<1M*RSaG)ndDhINC;kQy#iKqC zuIfHJ0J-Img8%jEaN~R}Pw^wO at WMHR1#>@biLOCtW)T{?x)?m+!D^|qbAj(v9V-pa zvw)Evb%gr&Hi&;GY!t>DVs zKKGdJT$+>lJYefMXF5*f|Gdgpu|B2PJ3%vOvLS==E9e^&@IZO=p%e0|*<6SJ$behP zN^2PoK_Y at U~OmkZ at o?8W!l{&FTkcm}U zVQPuuQhy__MHUQ)dSYURIjuqdFc?Wu{yZ6R9=y0VyK!HJzo*S03b9n;FsGlPge5D( zDM@)&?Ca+(wRd!JFk(;seuW2VQhSCPNR*h?YyeagiXkX3$Eo{sI(J(4IDx}U9*j0n z;h zGZOeW0W}B8_>UgczRZKy*SwSBLb3}i%AAf2NO$Ha!I=dsbGoO9B{0LFmTeL9+6gf| zk8Tn#MyM~1i*0h;M4k^wd~<{%Bv~@UDGe4qEIF;CA>6r;&uQm~Bs#(BVRS$CYRpL_ zpwOkeul9pge$`8H0bnV^OAO}<{xm9sUN9BnYO$SxnH~{7X2Ig93v>WtATciNw(BV# zXaJAz9|?_Jnyny=K^u at fDTxu!irn-wf?Chl&o*al5E7t! zR5WeGx$*3!E-v?6plfJ7>r04Ce)sY6Vi0TNi`P6T4(yoO6SA&4mULmr*a!Xt3=FGQ at 8MSyF@jO3eH|02qK{Wbi1s zg$n1#r~p*TOB#4&S9lFSDZ_00)RRF>Yv?qA!h?(q0)U$yKpi9qUoa*kg1(>4hH0-Z zt_p*Xzj^#kzDxfT@>R)EH!`bWKiYyKN4ykkLn!iCj2X!b@<+1MO)j)UWj+;hd;kp| z9pS`7j!F+`foZw%Aqjye`GjUvfyRsiFKH2 at zhnŠ9EV_honHL^P7hxHuS6}}^ z9twN&3QfZFV1#8nHa+K)esv^bV>+S1&kvIVk#L-BfwPlX9reQ`95}+HK7f}NIzfLW zzvb?<%941MntaBF;t}fFNPgR5Lfp at 7DGO4P#ACr;+jDaU41fB;)lQ8q{OU*VpI%mr zJ9RkMWJ4h)#;fD61v~nI8d*J2#Nx>W?xuJwF<j2|oFd zp=Y*qV!^r$Hx5p>1B&bh&7Ywrp7SLK zO)DE_AjV^tI2{@J&Vt-z!o5E%t)ZQ%U=(Q4nUmbULT at 7#ZmB8UhD}k}Z3dkSpE at Ky z81N9rg%35WP%Oa%c|ZNAw;y-Oyts+NX!>~Es=0u(eKIxD3MwErc#&8IBn^gnPVC!r zfwOYB>hTH3?fYCL*W{}zyruh107(8z0UGp%g99(7!c}hd6|vQj!B1E+v}S(h!3sa= zDz$j60)zD$-Mp at P?COjFjY?vh6W1)rA}rp_g*P3)`|+v=5Fe!_JkndCi-&|p{m}Ul zcltE35N?dKppNIP6|B}t8rAse-5|E<%o&skv5!Z7)|mKbT*iZaE~E-}F%nE at s-#(l zWrfL-3IV_d7LUnX#Iq)pVVo*+gau1(CV at _BZ(3w}2>jd}Y at u-5gVRHeJ^m(3 at Xi8V zH6IE5;Q{U3E8fcbTmc~Ss{gH^5dC2331`P%Y5A6TAmTzi&g=@4Gt=JW%yAicY4Beh zu!WDKFk%$sg1(0Uue|fb)he_%*eUn;5h^7>z^{dClJr&}U5t-0!7>hg+8IeMQ`Rr z90wV$3dCT;0>4TPPtq68GOLrrR=pG_?8OSFjEI}?E8I6fqy;S7b3+1PnGsW+G+3|~ zm+_oJs>ef4O!tR$sKN<_$NpRym6Z4v&sO*}05^LY*ndM;#e?-4F$2>!qf-qcouKld z1QaGKz!b#Ig<^WsVaz;OflCLtFL=sBAP(3LWAb7edV~|ZhuJbGESMHKtr0xnCP9TW z7*?YZp5}32?rA5x$&~K-G!Fc4sUf5;UK9B8kggij%}C6cASNN!rC9uFi7Vvmum+<6 ze6SvWZqKPrSQ^6lQDkwC`W7&hc1XQt-Ik)(-&l1hGjUuYz^w_I;_H2k_zPk lI1-zF#))r8%IG%nk6oktnb*Jm|God!{~!0la2=kw1OTIPExrH% literal 0 HcmV?d00001 diff --git a/appliance-recipe/resources/ovirtbr.xml b/appliance-recipe/resources/ovirtbr.xml new file mode 100644 index 0000000..9bce127 --- /dev/null +++ b/appliance-recipe/resources/ovirtbr.xml @@ -0,0 +1,7 @@ + + ovirtbr + + + + + diff --git a/appliance-recipe/version b/appliance-recipe/version new file mode 100644 index 0000000..4e379d2 --- /dev/null +++ b/appliance-recipe/version @@ -0,0 +1 @@ +0.0.2 diff --git a/build-all.sh b/build-all.sh index 74496c5..18ef309 100755 --- a/build-all.sh +++ b/build-all.sh @@ -138,12 +138,13 @@ cleanup=0 version_type=git bridge= err=0 help=0 -while getopts wnsacum:v:e:h c; do +while getopts wnsakcum:v:e:h c; do case $c in w) update_wui=1;; n) update_node=1;; s) include_src=1;; a) update_wui=1; update_node=1; update_app=1;; + k) update_app=1;; c) cleanup=1;; u) upload_rpms=1;; m) fedora_url=$OPTARG;; @@ -299,6 +300,14 @@ fi # build oVirt Server Suite appliance if [ $update_app == 1 ]; then + cd $BASE/appliance-recipe + rm -rf rpm-build + make rpms + rm -f $OVIRT/ovirtAppliance*rpm + cp rpm-build/ovirtAppliance*.rpm $OVIRT + cd $OVIRT + createrepo . + cd $WUI make distclean setup_repos $WUI/repos.ks -- 1.5.5.1 From dpierce at redhat.com Fri Aug 22 15:06:39 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 22 Aug 2008 11:06:39 -0400 Subject: [Ovirt-devel] [PATCH] Fix how taskomatic adds NICs to the System prior to creating it in Cobbler. Message-ID: <1219417599-19511-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- wui/src/task-omatic/task_vm.rb | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-) diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index fca978f..3588224 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -159,9 +159,7 @@ def create_vm(task) if provisioning_arr[1]==Vm::PROFILE_PREFIX system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) - system.interfaces=[Cobbler::NetworkInterface.new( - ["intf",{'mac_address' => vm.vnic_mac_addr}] - )] + system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save elsif provisioning_arr[1]==Vm::IMAGE_PREFIX #FIXME handle cobbler images -- 1.5.5.1 From dpierce at redhat.com Fri Aug 22 17:20:18 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 22 Aug 2008 13:20:18 -0400 Subject: [Ovirt-devel] Re: Added a configuration generation for managed nodes. It takes as input a In-Reply-To: <1219338751-23974-1-git-send-email-dpierce@redhat.com> References: <1219338751-23974-1-git-send-email-dpierce@redhat.com> Message-ID: <20080822172017.GA26621@redhat.com> +++ Darryl L. Pierce [21/08/08 13:12 -0400]: Can I get someone who does Ruby to give me an ACK or some feedback on this, please? -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 redhat.com Fri Aug 22 19:30:01 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 22 Aug 2008 21:30:01 +0200 Subject: [Ovirt-devel] [PATCH] use vinagre launcher plugin for VM console access Message-ID: <1219433401-8523-1-git-send-email-apevec@redhat.com> connects directly to the VNC port on the Node running selected VM needs virt-viewer-0.0.3-3ovirt1 Signed-off-by: Alan Pevec --- wui/src/app/controllers/vm_controller.rb | 6 ++++++ wui/src/app/views/vm/console.rhtml | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/wui/src/app/controllers/vm_controller.rb b/wui/src/app/controllers/vm_controller.rb index a9deff5..e55ec28 100644 --- a/wui/src/app/controllers/vm_controller.rb +++ b/wui/src/app/controllers/vm_controller.rb @@ -16,6 +16,7 @@ # 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 'socket' class VmController < ApplicationController # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) @@ -183,6 +184,11 @@ class VmController < ApplicationController def console @show_vnc_error = "Console is unavailable for VM #{@vm.description}" unless @vm.has_console + if @vm.host.hostname.match("priv\.ovirt\.org$") + @vnc_hostname = IPSocket.getaddress(@vm.host.hostname) + else + @vnc_hostname = @vm.host.hostname + end render :layout => false end diff --git a/wui/src/app/views/vm/console.rhtml b/wui/src/app/views/vm/console.rhtml index 5a853de..8724c51 100644 --- a/wui/src/app/views/vm/console.rhtml +++ b/wui/src/app/views/vm/console.rhtml @@ -28,11 +28,11 @@ <%else -%> -virt-viewer started
-hypervisor qemu+tcp://<%= @vm.host.hostname%>/system
-virtual machine <%= @vm.uuid%>
+Console started for virtual machine <%= @vm.uuid%>
+host <%= @vm.host.hostname%>
+VNC port <%= @vm.vnc_port%>
<%end -%> -- 1.5.4.1 From hbrock at redhat.com Fri Aug 22 19:42:20 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Fri, 22 Aug 2008 15:42:20 -0400 Subject: [Ovirt-devel] [PATCH] use vinagre launcher plugin for VM console access In-Reply-To: <1219433401-8523-1-git-send-email-apevec@redhat.com> References: <1219433401-8523-1-git-send-email-apevec@redhat.com> Message-ID: <20080822194220.GQ11219@redhat.com> On Fri, Aug 22, 2008 at 09:30:01PM +0200, Alan Pevec wrote: > connects directly to the VNC port on the Node running selected VM > needs virt-viewer-0.0.3-3ovirt1 > > Signed-off-by: Alan Pevec > --- > wui/src/app/controllers/vm_controller.rb | 6 ++++++ > wui/src/app/views/vm/console.rhtml | 8 ++++---- > 2 files changed, 10 insertions(+), 4 deletions(-) > > diff --git a/wui/src/app/controllers/vm_controller.rb b/wui/src/app/controllers/vm_controller.rb > index a9deff5..e55ec28 100644 > --- a/wui/src/app/controllers/vm_controller.rb > +++ b/wui/src/app/controllers/vm_controller.rb > @@ -16,6 +16,7 @@ > # 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 'socket' > > class VmController < ApplicationController > # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) > @@ -183,6 +184,11 @@ class VmController < ApplicationController > > def console > @show_vnc_error = "Console is unavailable for VM #{@vm.description}" unless @vm.has_console > + if @vm.host.hostname.match("priv\.ovirt\.org$") > + @vnc_hostname = IPSocket.getaddress(@vm.host.hostname) > + else > + @vnc_hostname = @vm.host.hostname > + end > render :layout => false > end > > diff --git a/wui/src/app/views/vm/console.rhtml b/wui/src/app/views/vm/console.rhtml > index 5a853de..8724c51 100644 > --- a/wui/src/app/views/vm/console.rhtml > +++ b/wui/src/app/views/vm/console.rhtml > @@ -28,11 +28,11 @@ > <%else -%> > hidden="1" > - uri="qemu+tcp://<%= @vm.host.hostname%>/system" > + uri="<%= @vnc_hostname%>:<%= @vm.vnc_port%>" > name="<%= @vm.uuid%>"> > > -virt-viewer started
> -hypervisor qemu+tcp://<%= @vm.host.hostname%>/system
> -virtual machine <%= @vm.uuid%>
> +Console started for virtual machine <%= @vm.uuid%>
> +host <%= @vm.host.hostname%>
> +VNC port <%= @vm.vnc_port%>
> <%end -%> > Yes this is a temporary hack... but it will do what we need for this release. ACK --Hugh From dpierce at redhat.com Fri Aug 22 19:59:19 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 22 Aug 2008 15:59:19 -0400 Subject: [Ovirt-devel] [PATCH] Fix how taskomatic adds NICs to the System prior to creating it in Cobbler. Message-ID: <1219435159-6036-1-git-send-email-dpierce@redhat.com> Taskomatic was using the old constructor for Cobbler::NetworkInterface, which took two arguments: the interface name and a map of the attributes. With rubygem-cobbler v0.0.2 that constructor was changed to take just the map of attributes, and gets the interface name from there. This patch fixes the call to create the NetworkInterface when creating a new VM, by removing the interface name. Signed-off-by: Darryl L. Pierce --- wui/ovirt-wui.spec | 2 +- wui/src/task-omatic/task_vm.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/wui/ovirt-wui.spec b/wui/ovirt-wui.spec index 0b17151..519a223 100644 --- a/wui/ovirt-wui.spec +++ b/wui/ovirt-wui.spec @@ -18,7 +18,7 @@ Requires: rubygem(activeldap) >= 0.10.0 Requires: rubygem(rails) >= 2.0.1 Requires: rubygem(mongrel) >= 1.0.1 Requires: rubygem(krb5-auth) >= 0.6 -Requires: rubygem(cobbler) = 0.0.1 +Requires: rubygem(cobbler) = 0.0.2 Requires: ruby-gettext-package Requires: postgresql-server Requires: ruby-postgres diff --git a/wui/src/task-omatic/task_vm.rb b/wui/src/task-omatic/task_vm.rb index fca978f..3588224 100644 --- a/wui/src/task-omatic/task_vm.rb +++ b/wui/src/task-omatic/task_vm.rb @@ -159,9 +159,7 @@ def create_vm(task) if provisioning_arr[1]==Vm::PROFILE_PREFIX system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) - system.interfaces=[Cobbler::NetworkInterface.new( - ["intf",{'mac_address' => vm.vnic_mac_addr}] - )] + system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save elsif provisioning_arr[1]==Vm::IMAGE_PREFIX #FIXME handle cobbler images -- 1.5.5.1 From jguiditt at redhat.com Fri Aug 22 21:20:30 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 22 Aug 2008 17:20:30 -0400 Subject: [Ovirt-devel] [PATCH] BUGFIX for hosts tabs Message-ID: <1219440030-27983-1-git-send-email-jguiditt@redhat.com> Somehow lost the hosts_per_page setting. This includes a slight tweak to the grid refresh as well. Signed-off-by: Jason Guiditta --- wui/src/app/views/hardware/show_hosts.rhtml | 10 ++++++++-- 1 files changed, 8 insertions(+), 2 deletions(-) diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml index 2981440..61d8e01 100644 --- a/wui/src/app/views/hardware/show_hosts.rhtml +++ b/wui/src/app/views/hardware/show_hosts.rhtml @@ -29,7 +29,12 @@ $.post('<%= url_for :controller => "hardware", :action => "move_hosts", :id => @pool %>', { resource_ids: hosts.toString(), target_pool_id: <%= Pool.root.id %> }, function(data,status){ - $("#hosts_grid").flexReload() + grid = $("#hosts_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); + } else { + $tabs.tabs("load",$tabs.data('selected.tabs')); + } if (data.alert) { $.jGrowl(data.alert); } @@ -68,7 +73,8 @@ :on_deselect => "load_widget_deselect", :on_hover => "load_widget_hover", :on_unhover => "load_widget_unhover", - :is_popup => false} %> + :is_popup => false, + :hosts_per_page => 40} %>

-- 1.5.5.1 From mmorsi at redhat.com Fri Aug 22 21:35:55 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 22 Aug 2008 17:35:55 -0400 Subject: [Ovirt-devel] [PATCH] vm creation / start / stop tests via selenium Message-ID: <1219440957-27181-1-git-send-email-mmorsi@redhat.com> --- autobuild.sh | 5 +- wui/src/test/fixtures/cpus.yml | 68 ++++++++++++++++---- wui/src/test/fixtures/hosts.yml | 10 +++ wui/src/test/fixtures/nics.yml | 13 +++- wui/src/test/fixtures/quotas.yml | 8 +- wui/src/test/fixtures/storage_pools.yml | 9 +++ wui/src/test/functional/interface_test.rb | 98 +++++++++++++++++++++++++++- wui/src/test/unit/cpu_test.rb | 2 + 8 files changed, 189 insertions(+), 24 deletions(-) diff --git a/autobuild.sh b/autobuild.sh index 6c95cb1..95395d0 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -80,7 +80,10 @@ fi echo "Running the wui tests" $ssh_cmd \ "sed -i \"s/#RAILS_ENV=production/RAILS_ENV=test/g\" /etc/sysconfig/ovirt-rails && \ - service ovirt-mongrel-rails restart && service httpd restart && \ + sed -i \"s/development/test/\" /usr/share/ovirt-wui/dutils/active_record_env.rb && \ + service ovirt-taskomatic restart && \ + service ovirt-mongrel-rails restart && \ + service httpd restart && \ curl -i http://management.priv.ovirt.org/ovirt/ | \ grep 'HTTP/1.1 200 OK' && \ cd /usr/share/ovirt-wui && rake test" > "$RESULTS" 2>&1 \ diff --git a/wui/src/test/fixtures/cpus.yml b/wui/src/test/fixtures/cpus.yml index 5586303..995b433 100644 --- a/wui/src/test/fixtures/cpus.yml +++ b/wui/src/test/fixtures/cpus.yml @@ -1,21 +1,65 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: + id: 1 + host_id: 10 cpu_number: 1 core_number: 1 - cpu_id_level: 1 - vendor: 1 - family: 1 - model: 1 - family: MyString - flags: MyString + number_of_cores: 2 + vendor: 'intel' two: + id: 2 + host_id: 10 cpu_number: 1 + core_number: 2 + number_of_cores: 2 + vendor: 'intel' + +three: + id: 3 + host_id: 10 + cpu_number: 2 + core_number: 1 + number_of_cores: 4 + vendor: 'intel' + +four: + id: 4 + host_id: 10 + cpu_number: 2 + core_number: 2 + number_of_cores: 4 + vendor: 'intel' + +five: + id: 5 + host_id: 10 + cpu_number: 2 + core_number: 3 + number_of_cores: 4 + vendor: 'intel' + +six: + id: 6 + host_id: 10 + cpu_number: 2 + core_number: 4 + number_of_cores: 4 + vendor: 'intel' + +seven: + id: 7 + host_id: 10 + cpu_number: 3 + core_number: 1 + number_of_cores: 1 + vendor: 'intel' + +seven: + id: 8 + host_id: 10 + cpu_number: 4 core_number: 1 - cpu_id_level: 1 - vendor: 1 - family: 1 - model: 1 - family: MyString - flags: MyString + number_of_cores: 1 + vendor: 'intel' diff --git a/wui/src/test/fixtures/hosts.yml b/wui/src/test/fixtures/hosts.yml index f10a756..a4e1fc6 100644 --- a/wui/src/test/fixtures/hosts.yml +++ b/wui/src/test/fixtures/hosts.yml @@ -79,3 +79,13 @@ nine: is_disabled: 0 hypervisor_type: 'kvm' hardware_pool_id: 3 +ten: + id: 10 + uuid: '75f8d5f3-faf0-4308-9f20-e4b03d013a27' + hostname: 'node3.priv.ovirt.org' + arch: 'x86_64' + memory: 4194304 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: 1 + state: "available" diff --git a/wui/src/test/fixtures/nics.yml b/wui/src/test/fixtures/nics.yml index 008cfb7..db57e55 100644 --- a/wui/src/test/fixtures/nics.yml +++ b/wui/src/test/fixtures/nics.yml @@ -5,18 +5,25 @@ one: ip_addr: '1.2.3.4' usage_type: '1' bandwidth: 100 - host_id: 1 + host_id: 10 two: id: 2 mac: 'AA:BB:CC:DD:EE:FF' ip_addr: '2.3.4.5' usage_type: '2' bandwidth: 1000 - host_id: 1 + host_id: 10 three: id: 3 mac: '00:FF:11:EE:22:DD' ip_addr: '3.4.5.6' usage_type: '1' bandwidth: 10 - host_id: 2 + host_id: 10 +four: + id: 4 + mac: '00:FF:11:EE:22:DD' + ip_addr: '3.4.5.6' + usage_type: '1' + bandwidth: 10 + host_id: 10 diff --git a/wui/src/test/fixtures/quotas.yml b/wui/src/test/fixtures/quotas.yml index 2d56ea0..757c934 100644 --- a/wui/src/test/fixtures/quotas.yml +++ b/wui/src/test/fixtures/quotas.yml @@ -1,11 +1,11 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 - total_vcpus: 8 + total_vcpus: 30 total_vmemory: 2097152 - total_vnics: 3 - total_storage: 500 - total_vms: 2 + total_vnics: 20 + total_storage: 104857600 + total_vms: 20 pool_id: 1 two: id: 2 diff --git a/wui/src/test/fixtures/storage_pools.yml b/wui/src/test/fixtures/storage_pools.yml index 34159ce..0610333 100644 --- a/wui/src/test/fixtures/storage_pools.yml +++ b/wui/src/test/fixtures/storage_pools.yml @@ -9,36 +9,44 @@ two: id: 2 hardware_pool_id: 4 type: 'NfsStoragePool' + ip_addr: '127.0.0.1' export_path: '/tmp/foopath' three: id: 3 hardware_pool_id: 4 type: 'NfsStoragePool' + ip_addr: '192.168.50.1' export_path: '/tmp/barpath' four: id: 4 hardware_pool_id: 9 type: 'IscsiStoragePool' port: 121 + ip_addr: '192.168.50.1' target: 'rubarb' + hardware_pool_id: 1 five: id: 5 hardware_pool_id: 9 type: 'NfsStoragePool' + ip_addr: '192.168.50.2' export_path: '/tmp/thepath' six: id: 6 hardware_pool_id: 9 type: 'NfsStoragePool' + ip_addr: '192.168.50.3' export_path: '/tmp/anotherpath' seven: id: 7 hardware_pool_id: 9 type: 'NfsStoragePool' + ip_addr: '192.168.50.4' export_path: '/tmp/apath' eight: id: 8 hardware_pool_id: 6 + ip_addr: '192.168.50.2' type: 'IscsiStoragePool' port: 531 target: 'bartarget' @@ -52,5 +60,6 @@ ten: id: 10 hardware_pool_id: 6 type: 'IscsiStoragePool' + ip_addr: '192.168.50.3' port: 539 target: 'stayontarget' diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb index 6563b44..eadeb74 100644 --- a/wui/src/test/functional/interface_test.rb +++ b/wui/src/test/functional/interface_test.rb @@ -27,17 +27,107 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' "*firefox /usr/lib64/firefox-3.0.1/firefox", "http://192.168.50.2/ovirt/", 15000) @browser.start + @browser.set_speed(1500) # ms delay between operations end def test_ovirt @browser.open("http://192.168.50.2/ovirt/") assert_equal("Dashboard", @browser.get_title()) - @browser.close end - def teardown - @browser.stop - end + def test_view_pool + @browser.open("http://192.168.50.2/ovirt/") + @browser.wait_for_condition( + "selenium.isElementPresent(\"//div[@id='side']/ul/li/span/a\")", + 10000) + @browser.click( + "//div[@id='side']/ul/li/span/a") # click 'master pool' link + @browser.wait_for_condition( + "selenium.isElementPresent(\"//div[@class='summary_title']\")", + 10000) + + # verify the title of the pool + assert_equal("master pool", + @browser.get_text("//div[@class='summary_title']")) + end + + def test_create_start_stop_vm + @browser.open("http://192.168.50.2/ovirt/") + @browser.wait_for_condition( + "selenium.isElementPresent(\"//div[@id='side']/ul/li/ul/li[1]/span/a\")", + 10000) + # click on 'foobar' vm resource pool link: + @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" + @browser.wait_for_condition( + "selenium.isElementPresent(\"//li[@id='nav_vmpool']/a\")", + 10000) + # click on virtual machines tab + @browser.click "//li[@id='nav_vmpool']/a" + @browser.wait_for_condition( + "selenium.isElementPresent(\"//div[@id='toolbar_nav']/ul/li[1]/a\")", + 10000) + # click on 'add virtual machine' + @browser.click "//div[@id='toolbar_nav']/ul/li[1]/a" + + # retrieve current # of vms + num_vms = @browser.get_xpath_count "//table[@id='vms_grid']/tbody/tr" + + # fill in required fields + @browser.wait_for_condition( + "selenium.isElementPresent(\"//input[@id='vm_description']\")", + 10000) + @browser.type("//input[@id='vm_description']", "zzz-selenium-test-vm") + @browser.type("//input[@id='vm_num_vcpus_allocated']", "1") + @browser.type("//input[@id='vm_memory_allocated_in_mb']", "256") + # select 1st storage pool + #@browser.click("//table[@id='storage_volumes_grid']/tbody/tr/td/div/input") + @browser.wait_for_condition( + "selenium.isElementPresent(\"//form[@id='vm_form']/div[2]/div[2]/div[2]/a\")", + 10000) + # click the button + @browser.click "//form[@id='vm_form']/div[2]/div[2]/div[2]/a" + + @browser.wait_for_condition( + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]\")", + 20000) + # verify title of newly created vm + assert_equal("zzz-selenium-test-vm", + @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[2]/div")) + + # start it, click checkbox, 'start vm', confirmation button; reload tab, check result + @browser.click "//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[1]/div/input" + @browser.click "//div[@id='toolbar_nav']/ul/li[2]/ul/li[1]" + @browser.wait_for_condition( + "selenium.isElementPresent(\"//div[@id='vm_action_results']/div[3]/div/div[2]/a\")", + 10000) + @browser.click "//div[@id='vm_action_results']/div[3]/div/div[2]/a" + @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" + @browser.wait_for_condition( + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\")", + 20000) + assert_equal("running", + @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) + + # stop / destroy vm + @browser.click "//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[1]/div/input" + @browser.click "//div[@id='toolbar_nav']/ul/li[2]/ul/li[2]" + @browser.wait_for_condition( + "selenium.isElementPresent(\"//div[@id='vm_action_results']/div[3]/div/div[2]/a\")", + 10000) + @browser.click "//div[@id='vm_action_results']/div[3]/div/div[2]/a" + @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" + @browser.wait_for_condition( + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\")", + 20000) + assert_equal("stopped", + @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) + + end + + def teardown + @browser.close + @browser.stop + end end end diff --git a/wui/src/test/unit/cpu_test.rb b/wui/src/test/unit/cpu_test.rb index 5b64ca7..9558525 100644 --- a/wui/src/test/unit/cpu_test.rb +++ b/wui/src/test/unit/cpu_test.rb @@ -1,6 +1,8 @@ require File.dirname(__FILE__) + '/../test_helper' class CpuTest < ActiveSupport::TestCase + fixtures :cpus + # Replace this with your real tests. def test_truth assert true -- 1.5.4.1 From mmorsi at redhat.com Fri Aug 22 21:35:56 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 22 Aug 2008 17:35:56 -0400 Subject: [Ovirt-devel] [PATCH] small bug fix, the include_vm variable is null when passed in from vm_controller.create In-Reply-To: <1219440957-27181-1-git-send-email-mmorsi@redhat.com> References: <1219440957-27181-1-git-send-email-mmorsi@redhat.com> Message-ID: <1219440957-27181-2-git-send-email-mmorsi@redhat.com> --- wui/src/app/models/storage_volume.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/wui/src/app/models/storage_volume.rb b/wui/src/app/models/storage_volume.rb index ccc5839..4044a13 100644 --- a/wui/src/app/models/storage_volume.rb +++ b/wui/src/app/models/storage_volume.rb @@ -53,7 +53,7 @@ class StorageVolume < ActiveRecord::Base def self.find_for_vm(include_vm, vm_pool) if vm_pool condition = "(vms.id is null and storage_pools.hardware_pool_id=#{vm_pool.get_hardware_pool.id})" - condition += " or vms.id=#{include_vm.id}" if (include_vm.id) + condition += " or vms.id=#{include_vm.id}" if (include_vm && include_vm.id) self.find(:all, :include => [:vms, :storage_pool], :conditions => condition) else return [] -- 1.5.4.1 From mmorsi at redhat.com Fri Aug 22 21:35:57 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 22 Aug 2008 17:35:57 -0400 Subject: [Ovirt-devel] [PATCH] moved selenium params out to configuration file In-Reply-To: <1219440957-27181-2-git-send-email-mmorsi@redhat.com> References: <1219440957-27181-1-git-send-email-mmorsi@redhat.com> <1219440957-27181-2-git-send-email-mmorsi@redhat.com> Message-ID: <1219440957-27181-3-git-send-email-mmorsi@redhat.com> --- wui/src/config/selenium.yml | 8 ++++++++ wui/src/test/functional/interface_test.rb | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 wui/src/config/selenium.yml diff --git a/wui/src/config/selenium.yml b/wui/src/config/selenium.yml new file mode 100644 index 0000000..83667b3 --- /dev/null +++ b/wui/src/config/selenium.yml @@ -0,0 +1,8 @@ +# oVirt selenium configuration + +selenium_server: + address: "192.168.50.1" + port: 4444 + browser: "*firefox /usr/lib64/firefox-3.0.1/firefox" +ovirt_wui_server: + address: 192.168.50.2 diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb index 6563b44..9052b87 100644 --- a/wui/src/test/functional/interface_test.rb +++ b/wui/src/test/functional/interface_test.rb @@ -17,25 +17,33 @@ # also available at http://www.gnu.org/copyleft/gpl.html. if File.exists? File.dirname(__FILE__) + '/../selenium.rb' + require 'yaml' require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../selenium' class InterfaceTest < Test::Unit::TestCase def setup - @browser = Selenium::SeleniumDriver.new("192.168.50.1", 4444, - "*firefox /usr/lib64/firefox-3.0.1/firefox", - "http://192.168.50.2/ovirt/", 15000) - @browser.start + @config = YAML::load(File.open("#{RAILS_ROOT}/config/selenium.yml")) + @site_url = "http://"+ + @config["ovirt_wui_server"]["address"] + "/ovirt/" + + @browser = Selenium::SeleniumDriver.new( + @config["selenium_server"]["address"], + @config["selenium_server"]["port"], + @config["selenium_server"]["browser"], + @site_url, + 15000) + @browser.start + @browser.open(@site_url) end def test_ovirt - @browser.open("http://192.168.50.2/ovirt/") assert_equal("Dashboard", @browser.get_title()) - @browser.close end def teardown + @browser.close @browser.stop end end -- 1.5.4.1 From mmorsi at redhat.com Fri Aug 22 21:38:48 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 22 Aug 2008 17:38:48 -0400 Subject: [Ovirt-devel] Re: [PATCH] moved selenium params out to configuration file In-Reply-To: <1219440957-27181-3-git-send-email-mmorsi@redhat.com> References: <1219440957-27181-1-git-send-email-mmorsi@redhat.com> <1219440957-27181-2-git-send-email-mmorsi@redhat.com> <1219440957-27181-3-git-send-email-mmorsi@redhat.com> Message-ID: <48AF31E8.80704@redhat.com> Mohammed Morsi wrote: > --- > wui/src/config/selenium.yml | 8 ++++++++ > wui/src/test/functional/interface_test.rb | 20 ++++++++++++++------ > 2 files changed, 22 insertions(+), 6 deletions(-) > create mode 100644 wui/src/config/selenium.yml > > diff --git a/wui/src/config/selenium.yml b/wui/src/config/selenium.yml > new file mode 100644 > index 0000000..83667b3 > --- /dev/null > +++ b/wui/src/config/selenium.yml > @@ -0,0 +1,8 @@ > +# oVirt selenium configuration > + > +selenium_server: > + address: "192.168.50.1" > + port: 4444 > + browser: "*firefox /usr/lib64/firefox-3.0.1/firefox" > +ovirt_wui_server: > + address: 192.168.50.2 > diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb > index 6563b44..9052b87 100644 > --- a/wui/src/test/functional/interface_test.rb > +++ b/wui/src/test/functional/interface_test.rb > @@ -17,25 +17,33 @@ > # also available at http://www.gnu.org/copyleft/gpl.html. > > if File.exists? File.dirname(__FILE__) + '/../selenium.rb' > + require 'yaml' > > require File.dirname(__FILE__) + '/../test_helper' > require File.dirname(__FILE__) + '/../selenium' > > class InterfaceTest < Test::Unit::TestCase > def setup > - @browser = Selenium::SeleniumDriver.new("192.168.50.1", 4444, > - "*firefox /usr/lib64/firefox-3.0.1/firefox", > - "http://192.168.50.2/ovirt/", 15000) > - @browser.start > + @config = YAML::load(File.open("#{RAILS_ROOT}/config/selenium.yml")) > + @site_url = "http://"+ > + @config["ovirt_wui_server"]["address"] + "/ovirt/" > + > + @browser = Selenium::SeleniumDriver.new( > + @config["selenium_server"]["address"], > + @config["selenium_server"]["port"], > + @config["selenium_server"]["browser"], > + @site_url, > + 15000) > + @browser.start > + @browser.open(@site_url) > end > > def test_ovirt > - @browser.open("http://192.168.50.2/ovirt/") > assert_equal("Dashboard", @browser.get_title()) > - @browser.close > end > > def teardown > + @browser.close > @browser.stop > end > end > Gah, didn't realize that git-send-email sent all patches as part of a single thread. These three patches aren't all that related, though all need ACKing. -Mo From pmyers at redhat.com Sat Aug 23 01:20:01 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Fri, 22 Aug 2008 21:20:01 -0400 Subject: [Ovirt-devel] [PATCH]: Remove bad documentation files In-Reply-To: <48AEA822.1090004@redhat.com> References: <1219403232-17849-1-git-send-email-clalance@redhat.com> <48AEA822.1090004@redhat.com> Message-ID: <48AF65C1.1010902@redhat.com> Alan Pevec wrote: > ACK Pushed > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From apevec at redhat.com Sat Aug 23 02:18:04 2008 From: apevec at redhat.com (Alan Pevec) Date: Sat, 23 Aug 2008 04:18:04 +0200 Subject: [Ovirt-devel] [PATCH] oVirt Node local installation In-Reply-To: <489C54EF.80905@redhat.com> References: <1218094541-23280-1-git-send-email-apevec@redhat.com> <489C54EF.80905@redhat.com> Message-ID: <48AF735C.4000805@redhat.com> Perry N. Myers wrote: >> /config - local oVirt configuration (ktab, local admin password...) >> /boot - boot loader, kernel and initramfs >> /LiveOS - oVirt Node compressed livecd image > > Is the initramfs repeated twice then? Once inside of the LiveCD and > once in /boot? Or am I just reading this wrong... There's just one, livecd-tools created, initrd0.img, LiveOS contains only squashfs.img containing ext3fs.img containing real root fs. >> ovirt_init=usb|scsi[:serial#] > So if you leave off serial#, how does it choose the disk? Does it just > take the first device (i.e. /dev/sda) and start using it? Yes, it will take the first device, skipping current /dev/live and format it. As per IRC discussion, ovirt_init syntax is changed to: ovirt_init[=usb|scsi[:serial#]] just 'ovirt_init' w/o value will initialize /config on /dev/live if writable >> [1] one-liner to list serial#s for all disks in the system: >> for d in /dev/sd?; do eval $(udevinfo --query env --name $d); echo $d >> $ID_SERIAL;done > > Problem with this is it assumes someone has booted linux on the box > before and has access to the above command. I think for the most part > people will not have the serial #s to work with. It's fine to leave > this functionality in, I just wonder how much it would be used. In theory, external USB flash could be plugged into admin's workstation running Linux, and if it's internal flash device, OEM might have a way to predict the serial# But you're right, I don't have really useful use-case for serial#. I guess we can still leave it there and document only in troubleshooting or advanced doc chapter. > Other question is... Should we have a sane default in the init scripts? > i.e. even if you don't pass in ovirt_init the init script does the > following: > > 1. Looks to see if there is an existing OVIRT partition and if so mounts > it > 2. If one does not exist looks for the first hard disk with free space on > it and creates the OVIRT partition there. > 3. If no hard drive space is available then it looks for the first flash > disk with free space on it and creates the OVIRT partition there. > These partitions are for config only, unless the ovirt_local_boot was > passed on the cmdline, in which case the local installation is done. > This way the admin is not forced to always use the ovirt_init parameter > on every machine in the datacenter. The node will do the right thing if > the OVIRT partition is not already there. I don't think we should make any local changes w/o explicit admin action (i.e. ovirt_* parameter now, local console config utility later) - for mass deployment admin will use PXE and cobbler, allowing him to specify boot parameters per machine and for SMB use-case we could increase usability by providing boot menu option "Install to local storage" > Also, steps 2 and 3 can be used to set up local swap if it doesn't > already exist. Local swap will be allocated from a logical volume created from remaining local disk space. This is TBD, I'll address it as a separate patch. > Also, if ovirt_local_boot is passed on the command line, the OVIRT > partition should never be placed on the same flash device as the one > that was booted since that device is transient. yes, device pointed by /dev/live symlink is skipped >> +ls -l /dev/disk >> + mkdir -p /dev/disk/by-label >> +ls -l /dev/disk/by-label > > Are the two ls lines above missing indentation? debugging leftover, I'll remove that >> + cat > $ovirt/boot/grub/grub.conf <> +default=0 >> +timeout=2 >> +hiddenmenu >> +title oVirt Managed Node >> + root (hd0,$part_num) >> + kernel /boot/vmlinuz0 ro root=LABEL=OVIRT roottypefs=ext2 liveimg >> + initrd /boot/initrd0.img >> +EOF > > Ian will need to add to this patch later for some serial console stuff > probably. fixed by passing console= parameters from the current /proc/cmdline and adding them to grub entry attached is the revised patch I'm about to push -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-oVirt-Node-local-installation.patch Type: text/x-patch Size: 16672 bytes Desc: not available URL: From mourad_auditeur_cnam at yahoo.fr Sat Aug 23 12:39:20 2008 From: mourad_auditeur_cnam at yahoo.fr (mourad tachouche) Date: Sat, 23 Aug 2008 12:39:20 +0000 (GMT) Subject: [Ovirt-devel] recherche procedure d'installation ovirt sur ubuntu Message-ID: <538711.32230.qm@web27002.mail.ukl.yahoo.com> Bonjour j'ai essayer de suivre la procedure sur le site d'ovirt comme suite "http://ovirt.org/download.html" mais cela n'a pas march? merci de me venire en aide Cordialement ? Mourad TACHOUCHE mourad_auditeur_cnam at yahoo.fr 06?25 11 12 54??? ? _____________________________________________________________________________ Envoyez avec Yahoo! Mail. Une boite mail plus intelligente http://mail.yahoo.fr -------------- next part -------------- An HTML attachment was scrubbed... URL: From pmyers at redhat.com Mon Aug 25 02:34:22 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Sun, 24 Aug 2008 22:34:22 -0400 Subject: [Ovirt-devel] i386 appliance problems on next branch Message-ID: <48B21A2E.2020008@redhat.com> Trying to get i386 built and tested for the next release and running into problems with free IPA. Everything works find on x86_64 Fedora 9, but getting errors when creating the IPA server via ipa-server-install. Can someone take a look at these ipa errors (I'm out of office the next few days so won't be able to) See attached logs... Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: ipaserver-install.log URL: -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: ovirt-wui-dev-first-run.log URL: From pmyers at redhat.com Mon Aug 25 02:37:04 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Sun, 24 Aug 2008 22:37:04 -0400 Subject: [Ovirt-devel] i386 appliance problems on next branch In-Reply-To: <48B21A2E.2020008@redhat.com> References: <48B21A2E.2020008@redhat.com> Message-ID: <48B21AD0.1040809@redhat.com> Perry N. Myers wrote: > Trying to get i386 built and tested for the next release and running > into problems with free IPA. Everything works find on x86_64 Fedora 9, > but getting errors when creating the IPA server via ipa-server-install. > > Can someone take a look at these ipa errors (I'm out of office the next > few days so won't be able to) > > See attached logs... Note, this is with IPA v. 1.1.0-6 on both appliance versions. Perry From apevec at redhat.com Mon Aug 25 08:52:09 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 25 Aug 2008 10:52:09 +0200 Subject: [Ovirt-devel] i386 appliance problems on next branch In-Reply-To: <48B21AD0.1040809@redhat.com> References: <48B21A2E.2020008@redhat.com> <48B21AD0.1040809@redhat.com> Message-ID: <48B272B9.8040904@redhat.com> Perry N. Myers wrote: > Perry N. Myers wrote: >> Trying to get i386 built and tested for the next release and running >> into problems with free IPA. Everything works find on x86_64 Fedora >> 9, but getting errors when creating the IPA server via >> ipa-server-install. >> >> Can someone take a look at these ipa errors (I'm out of office the >> next few days so won't be able to) >> >> See attached logs... > > Note, this is with IPA v. 1.1.0-6 on both appliance versions. I tried build-all.sh on my freshly updated i386 and it worked fine, so I assume it must've been some temporary issue on your build machine. So, as far as I'm concerned, all is ok for the release. From pmyers at redhat.com Mon Aug 25 09:03:26 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Mon, 25 Aug 2008 05:03:26 -0400 Subject: [Ovirt-devel] i386 appliance problems on next branch In-Reply-To: <48B272B9.8040904@redhat.com> References: <48B21A2E.2020008@redhat.com> <48B21AD0.1040809@redhat.com> <48B272B9.8040904@redhat.com> Message-ID: <48B2755E.9080908@redhat.com> Alan Pevec wrote: > Perry N. Myers wrote: >> Perry N. Myers wrote: >>> Trying to get i386 built and tested for the next release and running >>> into problems with free IPA. Everything works find on x86_64 Fedora >>> 9, but getting errors when creating the IPA server via >>> ipa-server-install. >>> >>> Can someone take a look at these ipa errors (I'm out of office the >>> next few days so won't be able to) >>> >>> See attached logs... >> >> Note, this is with IPA v. 1.1.0-6 on both appliance versions. > > I tried build-all.sh on my freshly updated i386 and it worked fine, so I > assume it must've been some temporary issue on your build machine. > So, as far as I'm concerned, all is ok for the release. > Very odd... I did this on freshly built Fedora 9 i386 with latest updates. And I'm able to reproduce the issue on multiple builds. I'll take your word for it that everything is fine, but I wonder what is causing the error on my end... Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From sseago at redhat.com Mon Aug 25 14:06:55 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 25 Aug 2008 10:06:55 -0400 Subject: [Ovirt-devel] [PATCH] added hardware_pool and vm_pool to tasks to facilitate pool-level task summaries. Message-ID: <1219673215-12353-1-git-send-email-sseago@redhat.com> Signed-off-by: Scott Seago --- wui/src/app/models/hardware_pool.rb | 1 + wui/src/app/models/host_task.rb | 4 ++ wui/src/app/models/storage_task.rb | 4 ++ wui/src/app/models/task.rb | 2 + wui/src/app/models/vm_resource_pool.rb | 1 + wui/src/app/models/vm_task.rb | 7 +++ wui/src/db/migrate/016_add_pools_to_tasks.rb | 58 ++++++++++++++++++++++++++ 7 files changed, 77 insertions(+), 0 deletions(-) create mode 100644 wui/src/db/migrate/016_add_pools_to_tasks.rb diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb index 276779f..3678420 100644 --- a/wui/src/app/models/hardware_pool.rb +++ b/wui/src/app/models/hardware_pool.rb @@ -19,6 +19,7 @@ class HardwarePool < Pool + has_many :tasks, :dependent => :nullify, :order => "id ASC" def all_storage_volumes StorageVolume.find(:all, :include => {:storage_pool => :hardware_pool}, :conditions => "pools.id = #{id}") end diff --git a/wui/src/app/models/host_task.rb b/wui/src/app/models/host_task.rb index 5cce5b9..ae597b0 100644 --- a/wui/src/app/models/host_task.rb +++ b/wui/src/app/models/host_task.rb @@ -21,4 +21,8 @@ class HostTask < Task belongs_to :host ACTION_CLEAR_VMS = "clear_vms" + + def after_initialize + self.hardware_pool = host.hardware_pool if self.host + end end diff --git a/wui/src/app/models/storage_task.rb b/wui/src/app/models/storage_task.rb index 5d67b98..e57b6de 100644 --- a/wui/src/app/models/storage_task.rb +++ b/wui/src/app/models/storage_task.rb @@ -21,4 +21,8 @@ class StorageTask < Task belongs_to :storage_pool ACTION_REFRESH_POOL = "refresh_pool" + + def after_initialize + self.hardware_pool = storage_pool.hardware_pool if self.storage_pool + end end diff --git a/wui/src/app/models/task.rb b/wui/src/app/models/task.rb index 7960056..a7faaf0 100644 --- a/wui/src/app/models/task.rb +++ b/wui/src/app/models/task.rb @@ -18,6 +18,8 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class Task < ActiveRecord::Base + belongs_to :hardware_pool + belongs_to :vm_resource_pool STATE_QUEUED = "queued" STATE_RUNNING = "running" diff --git a/wui/src/app/models/vm_resource_pool.rb b/wui/src/app/models/vm_resource_pool.rb index d3f7d43..fff6dac 100644 --- a/wui/src/app/models/vm_resource_pool.rb +++ b/wui/src/app/models/vm_resource_pool.rb @@ -19,6 +19,7 @@ class VmResourcePool < Pool + has_many :tasks, :dependent => :nullify, :order => "id ASC" def get_type_label "Virtual Machine Pool" end diff --git a/wui/src/app/models/vm_task.rb b/wui/src/app/models/vm_task.rb index 3f52478..ab96e6f 100644 --- a/wui/src/app/models/vm_task.rb +++ b/wui/src/app/models/vm_task.rb @@ -107,6 +107,13 @@ class VmTask < Task PRIV_OBJECT_HW_POOL], :popup_action => 'migrate'} } + def after_initialize + if self.vm + self.vm_resource_pool = vm.vm_resource_pool + self.hardware_pool = vm.get_hardware_pool + end + end + def self.valid_actions_for_vm_state(state, vm=nil, user=nil) actions = [] ACTIONS.each do |action, hash| diff --git a/wui/src/db/migrate/016_add_pools_to_tasks.rb b/wui/src/db/migrate/016_add_pools_to_tasks.rb new file mode 100644 index 0000000..58ff83d --- /dev/null +++ b/wui/src/db/migrate/016_add_pools_to_tasks.rb @@ -0,0 +1,58 @@ +# +# 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. + +class AddPoolsToTasks < ActiveRecord::Migration + def self.up + add_column :tasks, :vm_resource_pool_id, :integer + add_column :tasks, :hardware_pool_id, :integer + execute "alter table tasks add constraint fk_tasks_vm_pools + foreign key (vm_resource_pool_id) references pools(id)" + execute "alter table tasks add constraint fk_tasks_hw_pools + foreign key (hardware_pool_id) references pools(id)" + Task.transaction do + VmTask.find(:all).each do |task| + vm = task.vm + if vm + task.vm_resource_pool = vm.vm_resource_pool + task.hardware_pool = vm.get_hardware_pool + task.save! + end + end + StorageTask.find(:all).each do |task| + pool = task.storage_pool + if pool + task.hardware_pool = pool.hardware_pool + task.save! + end + end + HostTask.find(:all).each do |task| + host = task.host + if host + task.hardware_pool = host.hardware_pool + task.save! + end + end + end + end + + def self.down + remove_column :tasks, :vm_resource_pool_id + remove_column :tasks, :hardware_pool_id + end +end -- 1.5.5.1 From jguiditt at redhat.com Mon Aug 25 17:37:41 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Mon, 25 Aug 2008 13:37:41 -0400 Subject: [Ovirt-devel] [PATCH] Fix behavior when adding/removing 1st/last item in a flexigrid. Message-ID: <1219685861-8740-1-git-send-email-jguiditt@redhat.com> For instance, adding a host when there were none before should make the flexigrid appear, and removing the last host from a list of one should make the default 'no hosts exist' content show again. The bulk of the work is in ovirt.js, checking for existance of a grid and adding it if not there. Individual delete methods reload the the tab as well, since the flexReload does not seem to provide a callback interface. The problem here is if you don't check after reload is done, your information will not be correct (since javascript is single-threaded, and continues to run after reload is called), so you could have the wrong number of items being used to determine whether the grid needs to be removed. The potential weak part of tab reload right now is that we do not save state of what page of the grid you are on. However, this may not be known anyway, as you are removing an indeterminant number of entries from the grid. Also included are a few added semicolons. These are required by javascript, though not by browser interpreters. However, this sometimes causes erratic behavior when the browser has to guess (I hit this working on this patch), so I fixed any I came across in the course of my work. This will also help when we are ready to minimize/pack our js, as most compressors work more reliably when correct syntax is followed. One missed sizing of a popup is included as well (too trivial to go in its own patch). Signed-off-by: Jason Guiditta --- wui/src/app/views/hardware/show_hosts.rhtml | 7 +-- wui/src/app/views/hardware/show_storage.rhtml | 10 ++-- wui/src/app/views/hardware/show_vms.rhtml | 8 +- wui/src/app/views/resources/show_vms.rhtml | 12 ++-- wui/src/app/views/user/_show.rhtml | 26 ++++---- wui/src/app/views/vm/_form.rhtml | 1 + wui/src/public/javascripts/ovirt.js | 95 +++++++++++++----------- 7 files changed, 81 insertions(+), 78 deletions(-) diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml index 61d8e01..6943db2 100644 --- a/wui/src/app/views/hardware/show_hosts.rhtml +++ b/wui/src/app/views/hardware/show_hosts.rhtml @@ -29,12 +29,7 @@ $.post('<%= url_for :controller => "hardware", :action => "move_hosts", :id => @pool %>', { resource_ids: hosts.toString(), target_pool_id: <%= Pool.root.id %> }, function(data,status){ - grid = $("#hosts_grid"); - if (grid.size()>0 && grid != null) { - grid.flexReload(); - } else { - $tabs.tabs("load",$tabs.data('selected.tabs')); - } + $tabs.tabs("load",$tabs.data('selected.tabs')); if (data.alert) { $.jGrowl(data.alert); } diff --git a/wui/src/app/views/hardware/show_storage.rhtml b/wui/src/app/views/hardware/show_storage.rhtml index 37af1ce..02b5180 100644 --- a/wui/src/app/views/hardware/show_storage.rhtml +++ b/wui/src/app/views/hardware/show_storage.rhtml @@ -20,7 +20,7 @@ $.post('<%= url_for :controller => "hardware", :action => "move_storage", :id => @pool %>', { resource_ids: storage.toString(), target_pool_id: <%= Pool.root.id %> }, function(data,status){ - $("#storage_grid").flexReload() + $tabs.tabs("load",$tabs.data('selected.tabs')); if (data.alert) { $.jGrowl(data.alert); } @@ -37,7 +37,7 @@ $.post('<%= url_for :controller => "storage", :action => "delete_pools", :id => @pool %>', { storage_pool_ids: storage.toString() }, function(data,status){ - $("#storage_grid").flexReload() + $tabs.tabs("load",$tabs.data('selected.tabs')); if (data.alert) { $.jGrowl(data.alert); } @@ -49,14 +49,14 @@ } function storage_select(selected_rows) { - var selected_ids = new Array() + var selected_ids = new Array() ; for(i=0; i "storage", :action => "show" %>', - { id: parseInt(selected_ids[0].substring(3))}) + { id: parseInt(selected_ids[0].substring(3))}); } } diff --git a/wui/src/app/views/hardware/show_vms.rhtml b/wui/src/app/views/hardware/show_vms.rhtml index 4555718..5ff5cc9 100644 --- a/wui/src/app/views/hardware/show_vms.rhtml +++ b/wui/src/app/views/hardware/show_vms.rhtml @@ -7,7 +7,7 @@ diff --git a/wui/src/app/views/vm/_form.rhtml b/wui/src/app/views/vm/_form.rhtml index b3a8957..60ed003 100644 --- a/wui/src/app/views/vm/_form.rhtml +++ b/wui/src/app/views/vm/_form.rhtml @@ -61,6 +61,7 @@ :action => "storage_volumes_for_vm_json", :id => @vm, :vm_pool_id => @vm.vm_resource_pool %>', dataType: 'json', + width: 700, colModel : [ {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: storage_volumes_grid_checkbox}, {display: 'Alias', name : 'display_name', width : 180, sortable : false, align: 'left'}, diff --git a/wui/src/public/javascripts/ovirt.js b/wui/src/public/javascripts/ovirt.js index 829738f..9a0d4b7 100644 --- a/wui/src/public/javascripts/ovirt.js +++ b/wui/src/public/javascripts/ovirt.js @@ -7,13 +7,13 @@ // returns an array of selected values for flexigrid checkboxes function get_selected_checkboxes(formid) { - var selected_array = new Array() - var selected_index = 0 - var selected = $('#'+formid+' .grid_checkbox:checkbox:checked') + var selected_array = new Array(); + var selected_index = 0; + var selected = $('#'+formid+' .grid_checkbox:checkbox:checked'); selected.each(function(){ - selected_array.push(this.value) + selected_array.push(this.value); }) - return selected_array + return selected_array; } @@ -21,26 +21,26 @@ function get_selected_checkboxes(formid) function validate_selected(selected_array, name) { if (selected_array.length == 0) { - $.jGrowl("Please select at least one " + name + " to continue") - return false + $.jGrowl("Please select at least one " + name + " to continue"); + return false; } else { - return true + return true; } } function add_hosts(url) { - hosts= get_selected_checkboxes("addhosts_grid_form") + hosts= get_selected_checkboxes("addhosts_grid_form"); if (validate_selected(hosts, "host")) { $.post(url, { resource_ids: hosts.toString() }, function(data,status){ jQuery(document).trigger('close.facebox'); - grid = $("#hosts_grid") - if (grid.size()>0) { - grid.flexReload() + grid = $("#hosts_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); } else { - $('.tab_nav li.current a').click() + $tabs.tabs("load",$tabs.data('selected.tabs')); } if (data.alert) { $.jGrowl(data.alert); @@ -50,17 +50,17 @@ function add_hosts(url) } function add_storage(url) { - storage= get_selected_checkboxes("addstorage_grid_form") + storage= get_selected_checkboxes("addstorage_grid_form"); if (validate_selected(storage, "storage pool")) { $.post(url, { resource_ids: storage.toString() }, - function(data,status){ + function(data,status){; jQuery(document).trigger('close.facebox'); - grid = $("#storage_grid") - if (grid.size()>0) { - grid.flexReload() + grid = $("#storage_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); } else { - $('.tab_nav li.current a').click() + $tabs.tabs("load",$tabs.data('selected.tabs')); } if (data.alert) { $.jGrowl(data.alert); @@ -86,76 +86,83 @@ function ajax_validation(response, status) } } if (response.alert) { - $.jGrowl(response.alert) + $.jGrowl(response.alert); } } } // callback actions for dialog submissions function afterHwPool(response, status){ - ajax_validation(response, status) + ajax_validation(response, status); if (response.success) { jQuery(document).trigger('close.facebox'); // FIXME do we need to reload the tree here // this is for reloading the host/storage grid when // adding hosts/storage to a new HW pool + alert(response.resource_type); if (response.resource_type) { - $('#' + response.resource_type + '_grid').flexReload() + grid = $('#' + response.resource_type + '_grid'); + alert(response.resource_type); + if (grid.size()>0 && grid != null) { + grid.flexReload(); + } else { + $tabs.tabs("load",$tabs.data('selected.tabs')); + } } if ((response.resource_type == 'hosts' ? get_selected_hosts() : get_selected_storage()).indexOf($('#'+response.resource_type+'_selection_id').html()) != -1){ - empty_summary(response.resource_type +'_selection', (response.resource_type == 'hosts' ? 'Host' : 'Storage Pool')) + empty_summary(response.resource_type +'_selection', (response.resource_type == 'hosts' ? 'Host' : 'Storage Pool')); } // do we have HW pools grid? //$("#vmpools_grid").flexReload() } } function afterVmPool(response, status){ - ajax_validation(response, status) + ajax_validation(response, status); if (response.success) { jQuery(document).trigger('close.facebox'); - grid = $("#vmpools_grid") - if (grid.size()>0) { - grid.flexReload() + grid = $("#vmpools_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); } else { - $('.tab_nav li.current a').click() + $tabs.tabs("load",$tabs.data('selected.tabs')); } } } function afterStoragePool(response, status){ - ajax_validation(response, status) + ajax_validation(response, status); if (response.success) { jQuery(document).trigger('close.facebox'); - grid = $("#storage_grid") - if (grid.size()>0) { - grid.flexReload() - } else { - $('.tab_nav li.current a').click() + grid = $("#storage_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); + } else {; + $tabs.tabs("load",$tabs.data('selected.tabs')); } } } function afterPermission(response, status){ - ajax_validation(response, status) + ajax_validation(response, status); if (response.success) { jQuery(document).trigger('close.facebox'); - grid = $("#users_grid") - if (grid.size()>0) { - grid.flexReload() + grid = $("#users_grid"); + if (grid.size()>0 && grid!= null) { + grid.flexReload(); } else { - $('.tab_nav li.current a').click() + $tabs.tabs("load",$tabs.data('selected.tabs')); } } } function afterVm(response, status){ - ajax_validation(response, status) + ajax_validation(response, status); if (response.success) { jQuery(document).trigger('close.facebox'); - grid = $("#vms_grid") - if (grid.size()>0) { - grid.flexReload() + grid = $("#vms_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); } else { - $('.tab_nav li.current a').click() + $tabs.tabs("load",$tabs.data('selected.tabs')); } } } -- 1.5.5.1 From dpierce at redhat.com Mon Aug 25 18:43:32 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 25 Aug 2008 14:43:32 -0400 Subject: [Ovirt-devel] Re: Fix behavior when adding/removing 1st/last item in a flexigrid. In-Reply-To: <1219685861-8740-1-git-send-email-jguiditt@redhat.com> References: <1219685861-8740-1-git-send-email-jguiditt@redhat.com> Message-ID: <20080825184331.GD3784@redhat.com> +++ Jason Guiditta [25/08/08 13:37 -0400]: >For instance, adding a host when there were none before should make >the flexigrid appear, and removing the last host from a list of one >should make the default 'no hosts exist' content show again. > >The bulk of the work is in ovirt.js, checking for existance of a grid >and adding it if not there. Individual delete methods reload the the >tab as well, since the flexReload does not seem to provide a callback >interface. The problem here is if you don't check after reload is done, >your information will not be correct (since javascript is single-threaded, >and continues to run after reload is called), so you could have the wrong >number of items being used to determine whether the grid needs to be removed. >The potential weak part of tab reload right now is that we do not save state >of what page of the grid you are on. However, this may not be known >anyway, as you are removing an indeterminant number of entries from the grid. > >Also included are a few added semicolons. These are required by javascript, >though not by browser interpreters. However, this sometimes causes erratic >behavior when the browser has to guess (I hit this working on this patch), so >I fixed any I came across in the course of my work. This will also help when >we are ready to minimize/pack our js, as most compressors work more reliably >when correct syntax is followed. One missed sizing of a popup is included as >well (too trivial to go in its own patch). > >Signed-off-by: Jason Guiditta ACK. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From clalance at redhat.com Mon Aug 25 18:45:42 2008 From: clalance at redhat.com (Chris Lalancette) Date: Mon, 25 Aug 2008 20:45:42 +0200 Subject: [Ovirt-devel] Create VM page Message-ID: <48B2FDD6.6080909@redhat.com> All, Scott, Alan, mdehaan and I had a discussion about the Create VM page today, and in particular, the OS variants. It seems that the general consensus before was that OS variant should get folded into the "boot device" table. Now, I'm not going to start an argument about the UI; it's something I'm woefully unprepared to discuss. What I will describe is how it looks to me from taskomatic's point of view. Basically, there are two important pieces of metadata we need to get during the Create VM stage: 1) The method of booting and installing (or provisioning). 2) The OS type that is going to be installed. These two pieces are related, but are actually different. The first is things like the location of the NFS (or HTTP, or FTP...you get the idea) tree, the method to boot (CD, PXE, etc). This is equivalent to the -l or -c parameter in virt-install. The second describes things we need to know about the guest, such as whether it supports ACPI/APIC, whether it supports virtio, etc. This is equivalent to the --os-type and --os-variant options in virt-install. It basically describes to taskomatic what to put in the XML so that we can boot with the right options. So, that being said, we need a way for the user to specify *both* of these. In the cobbler profile case, we can get the OS type information from cobbler (well, in reality, cobbler doesn't store this at the moment, but dehaan said he would add it). In the non-cobbler case (either PXE, or CD, or whatever), we need to allow the user to choose the OS type. In either case, we will map what the user specified (Fedora-9, say), to a bunch of individual options, and store it in the database for taskomatic to pick up when creating the guest. Anyway, I thought I would lay all of this out, to make sure everyone understood what I was talking about, and I understood everyone else. Chris Lalancette From dpierce at redhat.com Mon Aug 25 20:20:54 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 25 Aug 2008 16:20:54 -0400 Subject: [Ovirt-devel] [PATCH] While testing a patch, I accidentally deleted the very last Super Admin Message-ID: <1219695654-13989-1-git-send-email-dpierce@redhat.com> This patch fixes the small check in the permission_controller that allows deleting the primary record; i.e., the record that does not inherit from another permission. Signed-off-by: Darryl L. Pierce --- wui/src/app/controllers/permission_controller.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/wui/src/app/controllers/permission_controller.rb b/wui/src/app/controllers/permission_controller.rb index 813d9d9..5d3646d 100644 --- a/wui/src/app/controllers/permission_controller.rb +++ b/wui/src/app/controllers/permission_controller.rb @@ -102,7 +102,7 @@ class PermissionController < ApplicationController Permission.transaction do permissions = Permission.find(:all, :conditions => "id in (#{permission_ids.join(', ')})") permissions.each do |permission| - permission.destroy if permission.is_primary? + permission.destroy unless permission.is_primary? end end render :json => { :object => "permission", :success => true, -- 1.5.5.1 From lutter at redhat.com Mon Aug 25 22:41:27 2008 From: lutter at redhat.com (David Lutterkort) Date: Mon, 25 Aug 2008 15:41:27 -0700 Subject: [Ovirt-devel] Create VM page In-Reply-To: <48B2FDD6.6080909@redhat.com> References: <48B2FDD6.6080909@redhat.com> Message-ID: <1219704087.4728.88.camel@localhost.localdomain> On Mon, 2008-08-25 at 20:45 +0200, Chris Lalancette wrote: > The second describes things we need to know about the guest, such as whether it > supports ACPI/APIC, whether it supports virtio, etc. This is equivalent to the > --os-type and --os-variant options in virt-install. It basically describes to > taskomatic what to put in the XML so that we can boot with the right options. The one thing that leaves a bad taste in my mouth about all this is that similar information is currently hardcoded in virt-install (mostly in virtinst/FullVirtGuest.py) The data there has gotten more comprehensive over time, and will only become more complex over time. If we need this for oVirt, it's probably time to split that hardcoed data out from virt-install; maybe just invent an XML format for it, with clear definitions of what all the fields mean. That would eventually also make it possible for cobbler to provide distro info in a well-defined format (e.g., if Fedora starts to publish that metadata with each tree) In any event, hardcoding that data in virt-install felt wrong, and hardcoding it a second time in oVirt would be even wronger. David From lutter at redhat.com Mon Aug 25 22:59:25 2008 From: lutter at redhat.com (David Lutterkort) Date: Mon, 25 Aug 2008 15:59:25 -0700 Subject: [Ovirt-devel] recherche procedure d'installation ovirt sur ubuntu In-Reply-To: <538711.32230.qm@web27002.mail.ukl.yahoo.com> References: <538711.32230.qm@web27002.mail.ukl.yahoo.com> Message-ID: <1219705165.4728.91.camel@localhost.localdomain> On Sat, 2008-08-23 at 12:39 +0000, mourad tachouche wrote: > > j'ai essayer de suivre la procedure sur le site d'ovirt comme suite > "http://ovirt.org/download.html" > mais cela n'a pas march? > > merci de me venire en aide Your best bet is to dowbnload the OVirt appliance and run that on your favorite host - installing OVirt from scratch on an existing Ubuntu host will be quite a bear since we use quite a few packages that are probably not in Ubuntu. David From lutter at redhat.com Mon Aug 25 23:43:12 2008 From: lutter at redhat.com (David Lutterkort) Date: Mon, 25 Aug 2008 16:43:12 -0700 Subject: [Ovirt-devel] [PATCH] Added a configuration generation for managed nodes. It takes as input a In-Reply-To: <1219338751-23974-1-git-send-email-dpierce@redhat.com> References: <1219338751-23974-1-git-send-email-dpierce@redhat.com> Message-ID: <1219707792.4728.100.camel@localhost.localdomain> On Thu, 2008-08-21 at 13:12 -0400, Darryl L. Pierce wrote: The subject of the patch is mangled, and it would be good to have a little more description, too. I'd ACK this if the description was fixed, and the spurious change to application.rb removed. > --- > wui/src/app/controllers/application.rb | 2 +- > wui/src/lib/managed_node_configuration.rb | 53 +++++++++++ > wui/src/test/fixtures/hosts.yml | 9 ++ > wui/src/test/fixtures/nics.yml | 7 +- > wui/src/test/fixtures/pools.yml | 4 + > .../functional/managed_node_configuration_test.rb | 98 ++++++++++++++++++++ > 6 files changed, 171 insertions(+), 2 deletions(-) > create mode 100644 wui/src/lib/managed_node_configuration.rb > create mode 100644 wui/src/test/functional/managed_node_configuration_test.rb > > diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb > index d653171..b27ddbe 100644 > --- a/wui/src/app/controllers/application.rb > +++ b/wui/src/app/controllers/application.rb > @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base > before_filter :is_logged_in > > def is_logged_in > - redirect_to (:controller => "login", :action => "login") unless get_login_user > + redirect_to(:controller => "login", :action => "login") unless get_login_user > end Seems to have slipped in by accident. > def get_login_user > diff --git a/wui/src/lib/managed_node_configuration.rb b/wui/src/lib/managed_node_configuration.rb > new file mode 100644 > index 0000000..6d6b7c9 > --- /dev/null > +++ b/wui/src/lib/managed_node_configuration.rb > @@ -0,0 +1,53 @@ > +# > +# 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. > + > +# +ManagedNodeConfiguration+ takes in the description for a managed node and, > +# from that, generates the configuration file that is consumed the next time > +# the managed node starts up. > +# > + > +require 'stringio' > + > +$: << File.join(File.dirname(__FILE__), "../dutils") > +$: << File.join(File.dirname(__FILE__), "../") Manipulating $: in production code is kinda ugly .. is that just a relic from testing ? > +class ManagedNodeConfiguration > + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' > + > + def self.generate(host, macs) > + result = StringIO.new > + > + host.nics.each do |nic| > + iface_name = macs[nic.mac] > + > + if iface_name > + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}" > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DEVICE #{iface_name}" > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE #{nic.bridge}" if nic.bridge > + result.puts "" > + end > + end > + > + result.puts "save" > + > + result.string > + end > +end Minor nit: you could avoid the use of StringIO completely with a here string (they allow interpolation with #{..}) David From lutter at redhat.com Tue Aug 26 00:17:14 2008 From: lutter at redhat.com (David Lutterkort) Date: Mon, 25 Aug 2008 17:17:14 -0700 Subject: [Ovirt-devel] Lighter-weight "developer" setup In-Reply-To: References: <489C5391.6050602@redhat.com> <48A1D7A3.8050007@redhat.com> <48A98D01.7020001@redhat.com> Message-ID: <1219709834.4728.106.camel@localhost.localdomain> On Mon, 2008-08-18 at 08:26 -0700, Jeff Schroeder wrote: > > Just following up. Would this approach help out? If so, how can I help > > getting folks to use the new puppet based recipe? > > Please no. Some people aren't big fans of puppet and prefer > something with the flexibility of "everything is a text file". If you > mainly manage similar Linux hosts, puppet is annoying to setup > and maintain. The change Bryan is talking about would only affect how the appliance is built and move it towards a more 'standard' setup by using the Thincrust tools. Although the Thincrust tools use Puppet behind the scenes, there's no need to set up a Puppet infrastructure when you deply the appliance. David From lutter at redhat.com Tue Aug 26 00:23:30 2008 From: lutter at redhat.com (David Lutterkort) Date: Mon, 25 Aug 2008 17:23:30 -0700 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <48A97841.4090906@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> <48A97841.4090906@redhat.com> Message-ID: <1219710210.4728.109.camel@localhost.localdomain> On Mon, 2008-08-18 at 09:25 -0400, Scott Seago wrote: > David Lutterkort wrote: > > Signed-off-by: David Lutterkort > > --- > > wui/src/app/models/hardware_pool.rb | 12 ++++++++++++ > > 1 files changed, 12 insertions(+), 0 deletions(-) > > > > diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb > > index 276779f..249d744 100644 > > --- a/wui/src/app/models/hardware_pool.rb > > +++ b/wui/src/app/models/hardware_pool.rb > > @@ -97,4 +97,16 @@ class HardwarePool < Pool > > return {:total => total, :labels => labels} > > end > > > > + def self.find_by_path(path) > > + segs = path.split("/") > > + unless segs.shift.empty? > > + raise "Path must be absolute, but is #{path}" > > + end > > + if segs.shift == "default" > > + segs.inject(get_default_pool) do |pool, seg| > > + pool.sub_hardware_pools.find { |p| p.name == seg } if pool > > + end > > + end > > + end > > + > > end > > > We shouldn't assume that the default pool happens to be named 'default'. > I'm not sure the best way to do this, but one way is to treat the > default pool as having path "/". So Pool.find_by_path("/") would return > the default pool, and Pool.find_by_path("/engineering/QA") would start > with the default pool, find a subpool named "engineering" and a subpool > of engineering called "QA". That won't work .. if '/' is the default pool, what is '/default' ? In the above code, I can get rid of the hardcoded "default" by comparing against get_default_pool.name. David From clalance at redhat.com Tue Aug 26 06:45:43 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 26 Aug 2008 08:45:43 +0200 Subject: [Ovirt-devel] Create VM page In-Reply-To: <1219704087.4728.88.camel@localhost.localdomain> References: <48B2FDD6.6080909@redhat.com> <1219704087.4728.88.camel@localhost.localdomain> Message-ID: <48B3A697.5070108@redhat.com> David Lutterkort wrote: > On Mon, 2008-08-25 at 20:45 +0200, Chris Lalancette wrote: >> The second describes things we need to know about the guest, such as whether it >> supports ACPI/APIC, whether it supports virtio, etc. This is equivalent to the >> --os-type and --os-variant options in virt-install. It basically describes to >> taskomatic what to put in the XML so that we can boot with the right options. > > The one thing that leaves a bad taste in my mouth about all this is that > similar information is currently hardcoded in virt-install (mostly in > virtinst/FullVirtGuest.py) The data there has gotten more comprehensive > over time, and will only become more complex over time. > > If we need this for oVirt, it's probably time to split that hardcoed > data out from virt-install; maybe just invent an XML format for it, with > clear definitions of what all the fields mean. That would eventually > also make it possible for cobbler to provide distro info in a > well-defined format (e.g., if Fedora starts to publish that metadata > with each tree) > > In any event, hardcoding that data in virt-install felt wrong, and > hardcoding it a second time in oVirt would be even wronger. Yes, this thought had also crossed my mind. I think it's a great idea for the mid-term, to basically provide a set of config files with this information. For the short term, we can copy what virt-install is doing, and then once the config file format is agreed upon and implemented, we can switch over to that. Now we just need a volunteer... Chris Lalancette From dpierce at redhat.com Tue Aug 26 12:53:17 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 26 Aug 2008 08:53:17 -0400 Subject: [Ovirt-devel] Re: Added a configuration generation for managed nodes. It takes as input a In-Reply-To: <1219707792.4728.100.camel@localhost.localdomain> References: <1219338751-23974-1-git-send-email-dpierce@redhat.com> <1219707792.4728.100.camel@localhost.localdomain> Message-ID: <20080826125317.GA3881@redhat.com> +++ David Lutterkort [25/08/08 16:43 -0700]: >> +class ManagedNodeConfiguration >> + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' >> + >> + def self.generate(host, macs) >> + result = StringIO.new >> + >> + host.nics.each do |nic| >> + iface_name = macs[nic.mac] >> + >> + if iface_name >> + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}" >> + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DEVICE #{iface_name}" >> + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr >> + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil >> + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE #{nic.bridge}" if nic.bridge >> + result.puts "" >> + end >> + end >> + >> + result.puts "save" >> + >> + result.string >> + end >> +end > >Minor nit: you could avoid the use of StringIO completely with a here >string (they allow interpolation with #{..}) Will using a here document allow for conditionally adding lines to the document? For example, the IPADDR line only shows up if the nic.ip_addr is defined. If so, can you point me to an example? I couldn't find one in the pickaxe book that worked like that. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 Tue Aug 26 14:11:52 2008 From: sseago at redhat.com (Scott Seago) Date: Tue, 26 Aug 2008 10:11:52 -0400 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <1219710210.4728.109.camel@localhost.localdomain> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> <48A97841.4090906@redhat.com> <1219710210.4728.109.camel@localhost.localdomain> Message-ID: <48B40F28.10005@redhat.com> David Lutterkort wrote: > On Mon, 2008-08-18 at 09:25 -0400, Scott Seago wrote: > >> David Lutterkort wrote: >> >>> Signed-off-by: David Lutterkort >>> --- >>> wui/src/app/models/hardware_pool.rb | 12 ++++++++++++ >>> 1 files changed, 12 insertions(+), 0 deletions(-) >>> >>> diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb >>> index 276779f..249d744 100644 >>> --- a/wui/src/app/models/hardware_pool.rb >>> +++ b/wui/src/app/models/hardware_pool.rb >>> @@ -97,4 +97,16 @@ class HardwarePool < Pool >>> return {:total => total, :labels => labels} >>> end >>> >>> + def self.find_by_path(path) >>> + segs = path.split("/") >>> + unless segs.shift.empty? >>> + raise "Path must be absolute, but is #{path}" >>> + end >>> + if segs.shift == "default" >>> + segs.inject(get_default_pool) do |pool, seg| >>> + pool.sub_hardware_pools.find { |p| p.name == seg } if pool >>> + end >>> + end >>> + end >>> + >>> end >>> >>> >> We shouldn't assume that the default pool happens to be named 'default'. >> I'm not sure the best way to do this, but one way is to treat the >> default pool as having path "/". So Pool.find_by_path("/") would return >> the default pool, and Pool.find_by_path("/engineering/QA") would start >> with the default pool, find a subpool named "engineering" and a subpool >> of engineering called "QA". >> > > That won't work .. if '/' is the default pool, what is '/default' ? > > In the above code, I can get rid of the hardcoded "default" by comparing > against get_default_pool.name. > > David > > We could probably make '/' work for the default pool, but it looks like it might be too confusing. If '/' were the default pool, then '/default' would be a subpool of 'default' which happens to be called default -- but I like the other idea better anyway. i.e. the first pool in the path after the initial '/' is compared against the default pool. I'm assuming the path-based pool lookup is just an alternate method of getting this from your API, as the id-based ones will all still work. I just realized that full path-based lookup will only work for users that have read permissions on the whole hierarchy. A user with lower-level permissions only (i.e. only read permissions for pools under '/default/engineering/qa' and write permissions for subpools below that) won't even see the top level pool. In the WUI, the left-hand nav tree begins with the pools that a user has read permissions on -- other pools don't even show up. So for such users, the full path lookup won't be so useful. Scott From sseago at redhat.com Tue Aug 26 14:15:06 2008 From: sseago at redhat.com (Scott Seago) Date: Tue, 26 Aug 2008 10:15:06 -0400 Subject: [Ovirt-devel] [PATCH] Added a configuration generation for managed nodes. It takes as input a In-Reply-To: <1219707792.4728.100.camel@localhost.localdomain> References: <1219338751-23974-1-git-send-email-dpierce@redhat.com> <1219707792.4728.100.camel@localhost.localdomain> Message-ID: <48B40FEA.2080700@redhat.com> David Lutterkort wrote: > On Thu, 2008-08-21 at 13:12 -0400, Darryl L. Pierce wrote: > > The subject of the patch is mangled, and it would be good to have a > little more description, too. > > I'd ACK this if the description was fixed, and the spurious change to > application.rb removed. > > >> --- >> wui/src/app/controllers/application.rb | 2 +- >> wui/src/lib/managed_node_configuration.rb | 53 +++++++++++ >> wui/src/test/fixtures/hosts.yml | 9 ++ >> wui/src/test/fixtures/nics.yml | 7 +- >> wui/src/test/fixtures/pools.yml | 4 + >> .../functional/managed_node_configuration_test.rb | 98 ++++++++++++++++++++ >> 6 files changed, 171 insertions(+), 2 deletions(-) >> create mode 100644 wui/src/lib/managed_node_configuration.rb >> create mode 100644 wui/src/test/functional/managed_node_configuration_test.rb >> >> diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb >> index d653171..b27ddbe 100644 >> --- a/wui/src/app/controllers/application.rb >> +++ b/wui/src/app/controllers/application.rb >> @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base >> before_filter :is_logged_in >> >> def is_logged_in >> - redirect_to (:controller => "login", :action => "login") unless get_login_user >> + redirect_to(:controller => "login", :action => "login") unless get_login_user >> end >> > > Seems to have slipped in by accident. > > Perhaps a different patch would have been appropriate here, but this change was made deliberately to avoid the ruby warning about the space between method name and open paren. Then again, such a trivial change should probably just be pushed directly without needing an ACK -- and on top of that, I believe this same change already came through another patch last week. Scott From dpierce at redhat.com Tue Aug 26 14:17:11 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 26 Aug 2008 10:17:11 -0400 Subject: [Ovirt-devel] [PATCH] Managed node configuration generator. Message-ID: <1219760231-9979-1-git-send-email-dpierce@redhat.com> This generator takes as input an object graph for a Host and a map of mac addresses to interface names on the managed node. It then generates the configuration details for that managed node (currently, only the NIC configuration is processed). With those details it generates the content for a configuration file that will be retrieved by the managed node and run through augtool to configure the managed node. Signed-off-by: Darryl L. Pierce --- wui/src/lib/managed_node_configuration.rb | 50 ++++++++ wui/src/test/fixtures/hosts.yml | 9 ++ wui/src/test/fixtures/nics.yml | 7 +- wui/src/test/fixtures/pools.yml | 4 + .../functional/managed_node_configuration_test.rb | 123 ++++++++++++++++++++ 5 files changed, 192 insertions(+), 1 deletions(-) create mode 100644 wui/src/lib/managed_node_configuration.rb create mode 100644 wui/src/test/functional/managed_node_configuration_test.rb diff --git a/wui/src/lib/managed_node_configuration.rb b/wui/src/lib/managed_node_configuration.rb new file mode 100644 index 0000000..8933706 --- /dev/null +++ b/wui/src/lib/managed_node_configuration.rb @@ -0,0 +1,50 @@ +# +# 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. + +# +ManagedNodeConfiguration+ takes in the description for a managed node and, +# from that, generates the configuration file that is consumed the next time +# the managed node starts up. +# + +require 'stringio' + +$: << File.join(File.dirname(__FILE__), "../dutils") +$: << File.join(File.dirname(__FILE__), "../") + +class ManagedNodeConfiguration + NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' + + def self.generate(host, macs) + result = StringIO.new + + host.nics.each do |nic| + iface_name = macs[nic.mac] + + if iface_name + result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DEVICE #{iface_name}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" if nic.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO dhcp" if nic.ip_addr == nil + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE #{nic.bridge}" if nic.bridge + end + end + + result.string + end +end diff --git a/wui/src/test/fixtures/hosts.yml b/wui/src/test/fixtures/hosts.yml index f10a756..64707da 100644 --- a/wui/src/test/fixtures/hosts.yml +++ b/wui/src/test/fixtures/hosts.yml @@ -1,3 +1,12 @@ +mailservers_managed_node: + uuid: '182a8596-961d-11dc-9387-001558c41534' + hostname: 'mail.mynetwork.com' + arch: 'i386' + memory: 16384 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + one: id: 1 uuid: '1148fdf8-961d-11dc-9387-001558c41534' diff --git a/wui/src/test/fixtures/nics.yml b/wui/src/test/fixtures/nics.yml index 008cfb7..c37f3d4 100644 --- a/wui/src/test/fixtures/nics.yml +++ b/wui/src/test/fixtures/nics.yml @@ -1,4 +1,9 @@ -# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +mailserver_nic_one: + mac: '00:11:22:33:44:55' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + one: id: 1 mac: '00:11:22:33:44:55' diff --git a/wui/src/test/fixtures/pools.yml b/wui/src/test/fixtures/pools.yml index 4cf7c97..91dd6e2 100644 --- a/wui/src/test/fixtures/pools.yml +++ b/wui/src/test/fixtures/pools.yml @@ -1,3 +1,7 @@ +prodops_pool: + name: 'Production Operations' + type: 'HardwarePool' + one: id: 1 name: 'master pool' diff --git a/wui/src/test/functional/managed_node_configuration_test.rb b/wui/src/test/functional/managed_node_configuration_test.rb new file mode 100644 index 0000000..f5ffb19 --- /dev/null +++ b/wui/src/test/functional/managed_node_configuration_test.rb @@ -0,0 +1,123 @@ +# +# 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. + +require File.dirname(__FILE__) + '/../test_helper' +require 'test/unit' +require 'managed_node_configuration' + +# Performs unit tests on the +ManagedNodeConfiguration+ class. +# +class ManagedNodeConfigurationTest < Test::Unit::TestCase + def setup + @host = Host.new + @nic = Nic.new(:mac => '00:11:22:33:44:55') + @host.nics << @nic + end + + # Ensures that network interfaces uses DHCP when no IP address is specified. + # + def test_generate_with_no_ip_address + expected = <<-HERE +rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BOOTPROTO dhcp + HERE + + result = ManagedNodeConfiguration.generate( + @host, + {'00:11:22:33:44:55' => 'eth0'} + ) + + assert_equal expected, result + end + + # Ensures that network interfaces use the IP address when it's provided. + # + def test_generate_with_ip_address + @nic.ip_addr = '192.168.2.1' + + expected = <<-HERE +rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR 192.168.2.1 + HERE + + result = ManagedNodeConfiguration.generate( + @host, + {'00:11:22:33:44:55' => 'eth0'} + ) + + assert_equal expected, result + end + + # Ensures the bridge is added to the configuration if one is defined. + # + def test_generate_with_bridge + @nic.bridge = 'ovirtbr0' + + expected = <<-HERE +rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BOOTPROTO dhcp +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 + HERE + + result = ManagedNodeConfiguration.generate( + @host, + {'00:11:22:33:44:55' => 'eth0'} + ) + + assert_equal expected, result + end + + # Ensures that more than one NIC is successfully processed. + # + def test_generate_with_multiple_nics + @host.nics << Nic.new(:mac => '11:22:33:44:55:66', :ip_addr => '172.31.0.15') + @host.nics << Nic.new(:mac => '22:33:44:55:66:77', :ip_addr => '172.31.0.100') + @host.nics << Nic.new(:mac => '33:44:55:66:77:88') + + + expected = <<-HERE +rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BOOTPROTO dhcp +rm /files/etc/sysconfig/network-scripts/ifcfg-eth1 +set /files/etc/sysconfig/network-scripts/ifcfg-eth1/DEVICE eth1 +set /files/etc/sysconfig/network-scripts/ifcfg-eth1/IPADDR 172.31.0.15 +rm /files/etc/sysconfig/network-scripts/ifcfg-eth2 +set /files/etc/sysconfig/network-scripts/ifcfg-eth2/DEVICE eth2 +set /files/etc/sysconfig/network-scripts/ifcfg-eth2/IPADDR 172.31.0.100 +rm /files/etc/sysconfig/network-scripts/ifcfg-eth3 +set /files/etc/sysconfig/network-scripts/ifcfg-eth3/DEVICE eth3 +set /files/etc/sysconfig/network-scripts/ifcfg-eth3/BOOTPROTO dhcp + HERE + + result = ManagedNodeConfiguration.generate( + @host, + { + '00:11:22:33:44:55' => 'eth0', + '11:22:33:44:55:66' => 'eth1', + '22:33:44:55:66:77' => 'eth2', + '33:44:55:66:77:88' => 'eth3' + }) + + assert_equal expected, result + end +end \ No newline at end of file -- 1.5.5.1 From apevec at redhat.com Tue Aug 26 14:20:25 2008 From: apevec at redhat.com (Alan Pevec) Date: Tue, 26 Aug 2008 16:20:25 +0200 Subject: [Ovirt-devel] [PATCH] Added a configuration generation for managed nodes. It takes as input a In-Reply-To: <48B40FEA.2080700@redhat.com> References: <1219338751-23974-1-git-send-email-dpierce@redhat.com> <1219707792.4728.100.camel@localhost.localdomain> <48B40FEA.2080700@redhat.com> Message-ID: <48B41129.8090603@redhat.com> Scott Seago wrote: > David Lutterkort wrote: >> On Thu, 2008-08-21 at 13:12 -0400, Darryl L. Pierce wrote: >> >> The subject of the patch is mangled, and it would be good to have a >> little more description, too. >> >> I'd ACK this if the description was fixed, and the spurious change to >> application.rb removed. >> >> >>> --- >>> wui/src/app/controllers/application.rb | 2 +- >>> wui/src/lib/managed_node_configuration.rb | 53 +++++++++++ >>> wui/src/test/fixtures/hosts.yml | 9 ++ >>> wui/src/test/fixtures/nics.yml | 7 +- >>> wui/src/test/fixtures/pools.yml | 4 + >>> .../functional/managed_node_configuration_test.rb | 98 >>> ++++++++++++++++++++ >>> 6 files changed, 171 insertions(+), 2 deletions(-) >>> create mode 100644 wui/src/lib/managed_node_configuration.rb >>> create mode 100644 >>> wui/src/test/functional/managed_node_configuration_test.rb >>> >>> diff --git a/wui/src/app/controllers/application.rb >>> b/wui/src/app/controllers/application.rb >>> index d653171..b27ddbe 100644 >>> --- a/wui/src/app/controllers/application.rb >>> +++ b/wui/src/app/controllers/application.rb >>> @@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base >>> before_filter :is_logged_in >>> >>> def is_logged_in >>> - redirect_to (:controller => "login", :action => "login") unless >>> get_login_user >>> + redirect_to(:controller => "login", :action => "login") unless >>> get_login_user >>> end >>> >> >> Seems to have slipped in by accident. >> >> > Perhaps a different patch would have been appropriate here, but this > change was made deliberately to avoid the ruby warning about the space > between method name and open paren. Then again, such a trivial change > should probably just be pushed directly without needing an ACK -- and on > top of that, I believe this same change already came through another > patch last week. yes, http://git.et.redhat.com/?p=ovirt.git;a=commitdiff;h=9fc192c43692ed2315d331d0b9314059551b4923 so just remove that part from your patch From sseago at redhat.com Tue Aug 26 15:24:45 2008 From: sseago at redhat.com (Scott Seago) Date: Tue, 26 Aug 2008 11:24:45 -0400 Subject: [Ovirt-devel] [PATCH] Added Tasks tab/pane to HW and VM pools pages Message-ID: <1219764285-32230-1-git-send-email-sseago@redhat.com> A tasks pane is now available on both VM and HW pools page. In both cases, the tasks show up as a flexigrid panel showing tasks for the pool. By default, tasks are filtered for 'pending' state, but they may be filtered on any state (or to show all). For the VM page, all tasks for all VMs in the VM pool are available. For the HW pool page, there are 3 types of tasks available: 1) VM tasks: tasks for all VMs contained in VM pools within this HW pool 2) Host tasks: tasks for all hosts in this pool 3) storage tasks: tasks for all storage pools in this HW pool. By default all task types are shown, but the pulldown menu lets the user filter on task type as well. Signed-off-by: Scott Seago --- wui/src/app/controllers/application.rb | 8 ++- wui/src/app/controllers/hardware_controller.rb | 61 +++++++++++++---- wui/src/app/controllers/resources_controller.rb | 54 +++++++++++---- wui/src/app/models/hardware_pool.rb | 3 +- wui/src/app/models/host_task.rb | 6 ++- wui/src/app/models/storage_task.rb | 5 +- wui/src/app/models/task.rb | 14 ++++ wui/src/app/models/vm_resource_pool.rb | 2 +- wui/src/app/models/vm_task.rb | 5 +- wui/src/app/views/hardware/show_tasks.rhtml | 77 ++++++++++++++++++++++ wui/src/app/views/layouts/_navigation_tabs.rhtml | 2 + wui/src/app/views/resources/show_tasks.rhtml | 61 +++++++++++++++++ wui/src/app/views/task/_grid.rhtml | 50 ++++++++++++++ 13 files changed, 313 insertions(+), 35 deletions(-) create mode 100644 wui/src/app/views/hardware/show_tasks.rhtml create mode 100644 wui/src/app/views/resources/show_tasks.rhtml create mode 100644 wui/src/app/views/task/_grid.rhtml diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index b27ddbe..b95577a 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -90,7 +90,7 @@ class ApplicationController < ActionController::Base end # don't define find_opts for array inputs - def json_list(full_items, attributes, arg_list=[], find_opts={}) + def json_hash(full_items, attributes, arg_list=[], find_opts={}) page = params[:page].to_i paginate_opts = {:page => page, :order => "#{params[:sortname]} #{params[:sortorder]}", @@ -114,7 +114,11 @@ class ApplicationController < ActionController::Base end item_hash end - render :json => json_hash.to_json + json_hash + end + # don't define find_opts for array inputs + def json_list(full_items, attributes, arg_list=[], find_opts={}) + render :json => json_hash(full_items, attributes, arg_list, find_opts).to_json end diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index 019fdd8..00fe06b 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -24,19 +24,13 @@ class HardwareController < ApplicationController :redirect_to => { :action => :list } before_filter :pre_json, :only => [:vm_pools_json, :users_json, - :storage_volumes_json] + :storage_volumes_json, :show_tasks, :tasks] before_filter :pre_modify, :only => [:add_hosts, :move_hosts, :add_storage, :move_storage, :create_storage, :delete_storage] def show - set_perms(@perm_obj) - unless @can_view - flash[:notice] = 'You do not have permission to view this hardware pool: redirecting to top level' - redirect_to :controller => "dashboard" - return - end if params[:ajax] render :layout => 'tabs-and-content' end @@ -104,14 +98,47 @@ class HardwareController < ApplicationController @hardware_pools = HardwarePool.find :all end + def show_tasks + @task_types = [["VM Task", "VmTask"], + ["Host Task", "HostTask"], + ["Storage Task", "StorageTask", "break"], + ["Show All", ""]] + @task_states = [["Queued", Task::STATE_QUEUED], + ["Running", Task::STATE_RUNNING], + ["Paused", Task::STATE_PAUSED], + ["Finished", Task::STATE_FINISHED], + ["Failed", Task::STATE_FAILED], + ["Canceled", Task::STATE_CANCELED, "break"], + ["Show All", ""]] + params[:page]=1 + params[:sortname]="tasks.created_at" + params[:sortorder]="desc" + @tasks = tasks_internal + show + end + + def tasks + render :json => tasks_internal.to_json + end + + def tasks_internal + @task_type = params[:task_type] + @task_type ||="" + @task_state = params[:task_state] + @task_state ||=Task::STATE_QUEUED + conditions = {} + conditions[:type] = @task_type unless @task_type.empty? + conditions[:state] = @task_state unless @task_state.empty? + find_opts = {:include => [:storage_pool, :host, :vm]} + find_opts[:conditions] = conditions unless conditions.empty? + attr_list = [] + attr_list << :id if params[:checkboxes] + attr_list += [:type_label, :task_obj, :action, :state, :user, :created_at, :args, :message] + json_hash(@pool.tasks, attr_list, [:all], find_opts) + end + def quick_summary pre_show - set_perms(@perm_obj) - unless @can_view - flash[:notice] = 'You do not have permission to view this Hardware Pool: redirecting to top level' - redirect_to :action => 'list' - return - end render :layout => 'selection' end @@ -352,10 +379,16 @@ class HardwareController < ApplicationController @pool = HardwarePool.find(params[:id]) @perm_obj = @pool @current_pool_id=@pool.id + set_perms(@perm_obj) + unless @can_view + flash[:notice] = 'You do not have permission to view this Hardware Pool: redirecting to top level' + # FIXME: figure out the return type and render appropriately + redirect_to :action => 'list' + return + end end def pre_json pre_show - show end def pre_modify pre_edit diff --git a/wui/src/app/controllers/resources_controller.rb b/wui/src/app/controllers/resources_controller.rb index 20defca..5a8276c 100644 --- a/wui/src/app/controllers/resources_controller.rb +++ b/wui/src/app/controllers/resources_controller.rb @@ -23,7 +23,8 @@ class ResourcesController < ApplicationController render :action => 'list' end - before_filter :pre_json, :only => [:vms_json, :users_json] + before_filter :pre_json, :only => [:vms_json, :users_json, + :show_tasks, :tasks] before_filter :pre_vm_actions, :only => [:vm_actions] # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) @@ -44,16 +45,10 @@ class ResourcesController < ApplicationController # resource's summary page def show - set_perms(@perm_obj) - @is_hwpool_admin = @vm_resource_pool.parent.can_modify(@user) @action_values = [["Suspend", VmTask::ACTION_SUSPEND_VM], ["Resume", VmTask::ACTION_RESUME_VM], ["Save", VmTask::ACTION_SAVE_VM], ["Restore", VmTask::ACTION_RESTORE_VM]] - unless @can_view - flash[:notice] = 'You do not have permission to view this VM Resource Pool: redirecting to top level' - redirect_to :action => 'list' - end if params[:ajax] render :layout => 'tabs-and-content' end @@ -64,12 +59,6 @@ class ResourcesController < ApplicationController def quick_summary pre_show - set_perms(@perm_obj) - @is_hwpool_admin = @vm_resource_pool.parent.can_modify(@user) - unless @can_view - flash[:notice] = 'You do not have permission to view this VM Resource Pool: redirecting to top level' - redirect_to :action => 'list' - end render :layout => 'selection' end @@ -90,6 +79,38 @@ class ResourcesController < ApplicationController show end + def show_tasks + @task_states = [["Queued", Task::STATE_QUEUED], + ["Running", Task::STATE_RUNNING], + ["Paused", Task::STATE_PAUSED], + ["Finished", Task::STATE_FINISHED], + ["Failed", Task::STATE_FAILED], + ["Canceled", Task::STATE_CANCELED, "break"], + ["Show All", ""]] + params[:page]=1 + params[:sortname]="tasks.created_at" + params[:sortorder]="desc" + @tasks = tasks_internal + show + end + + def tasks + render :json => tasks_internal.to_json + end + + def tasks_internal + @task_state = params[:task_state] + @task_state ||=Task::STATE_QUEUED + conditions = {} + conditions[:state] = @task_state unless @task_state.empty? + find_opts = {:include => [:storage_pool, :host, :vm]} + find_opts[:conditions] = conditions unless conditions.empty? + attr_list = [] + attr_list << :id if params[:checkboxes] + attr_list += [:type_label, :task_obj, :action, :state, :user, :created_at, :args, :message] + json_hash(@vm_resource_pool.tasks, attr_list, [:all], find_opts) + end + def vms_json json_list(@vm_resource_pool.vms, [:id, :description, :uuid, :num_vcpus_allocated, :memory_allocated_in_mb, :vnic_mac_addr, :state, :id]) @@ -208,6 +229,12 @@ class ResourcesController < ApplicationController @vm_resource_pool = VmResourcePool.find(params[:id]) @perm_obj = @vm_resource_pool @current_pool_id=@vm_resource_pool.id + set_perms(@perm_obj) + @is_hwpool_admin = @vm_resource_pool.parent.can_modify(@user) + unless @can_view + flash[:notice] = 'You do not have permission to view this VM Resource Pool: redirecting to top level' + redirect_to :action => 'dashboard' + end end def pre_edit @vm_resource_pool = VmResourcePool.find(params[:id]) @@ -218,7 +245,6 @@ class ResourcesController < ApplicationController end def pre_json pre_show - show end def pre_vm_actions @vm_resource_pool = VmResourcePool.find(params[:id]) diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb index 3678420..a4c921b 100644 --- a/wui/src/app/models/hardware_pool.rb +++ b/wui/src/app/models/hardware_pool.rb @@ -19,7 +19,7 @@ class HardwarePool < Pool - has_many :tasks, :dependent => :nullify, :order => "id ASC" + has_many :tasks, :dependent => :nullify def all_storage_volumes StorageVolume.find(:all, :include => {:storage_pool => :hardware_pool}, :conditions => "pools.id = #{id}") end @@ -98,4 +98,5 @@ class HardwarePool < Pool return {:total => total, :labels => labels} end + end diff --git a/wui/src/app/models/host_task.rb b/wui/src/app/models/host_task.rb index ae597b0..0aea41b 100644 --- a/wui/src/app/models/host_task.rb +++ b/wui/src/app/models/host_task.rb @@ -18,11 +18,15 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class HostTask < Task - belongs_to :host ACTION_CLEAR_VMS = "clear_vms" def after_initialize self.hardware_pool = host.hardware_pool if self.host end + + def task_obj + "Host;;;#{self.host.id};;;#{self.host.hostname}" + end + end diff --git a/wui/src/app/models/storage_task.rb b/wui/src/app/models/storage_task.rb index e57b6de..db604d5 100644 --- a/wui/src/app/models/storage_task.rb +++ b/wui/src/app/models/storage_task.rb @@ -18,11 +18,14 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class StorageTask < Task - belongs_to :storage_pool ACTION_REFRESH_POOL = "refresh_pool" def after_initialize self.hardware_pool = storage_pool.hardware_pool if self.storage_pool end + + def task_obj + "StoragePool;;;#{self.storage_pool.id};;;#{self.storage_pool.display_name}" + end end diff --git a/wui/src/app/models/task.rb b/wui/src/app/models/task.rb index a7faaf0..efbed64 100644 --- a/wui/src/app/models/task.rb +++ b/wui/src/app/models/task.rb @@ -20,6 +20,13 @@ class Task < ActiveRecord::Base belongs_to :hardware_pool belongs_to :vm_resource_pool + # moved associations here so that nested set :include directives work + # StorageTask association + belongs_to :storage_pool + # HostTask association + belongs_to :host + # VmTask association + belongs_to :vm STATE_QUEUED = "queued" STATE_RUNNING = "running" @@ -50,4 +57,11 @@ class Task < ActiveRecord::Base Task.find(:all, :conditions => conditions) end + def type_label + self.class.name[0..-5] + end + def task_obj + "" + end + end diff --git a/wui/src/app/models/vm_resource_pool.rb b/wui/src/app/models/vm_resource_pool.rb index fff6dac..d6acf80 100644 --- a/wui/src/app/models/vm_resource_pool.rb +++ b/wui/src/app/models/vm_resource_pool.rb @@ -19,7 +19,7 @@ class VmResourcePool < Pool - has_many :tasks, :dependent => :nullify, :order => "id ASC" + has_many :tasks, :dependent => :nullify def get_type_label "Virtual Machine Pool" end diff --git a/wui/src/app/models/vm_task.rb b/wui/src/app/models/vm_task.rb index ab96e6f..31c4ac8 100644 --- a/wui/src/app/models/vm_task.rb +++ b/wui/src/app/models/vm_task.rb @@ -18,7 +18,6 @@ # also available at http://www.gnu.org/copyleft/gpl.html. class VmTask < Task - belongs_to :vm ACTION_CREATE_VM = "create_vm" @@ -114,6 +113,10 @@ class VmTask < Task end end + def task_obj + "Vm;;;#{self.vm.id};;;#{self.vm.description}" + end + def self.valid_actions_for_vm_state(state, vm=nil, user=nil) actions = [] ACTIONS.each do |action, hash| diff --git a/wui/src/app/views/hardware/show_tasks.rhtml b/wui/src/app/views/hardware/show_tasks.rhtml new file mode 100644 index 0000000..e49086c --- /dev/null +++ b/wui/src/app/views/hardware/show_tasks.rhtml @@ -0,0 +1,77 @@ +
+
    +
  • + <%= image_tag "icon_move.png", :style => "vertical-align:middle;" %>  Type    <%= image_tag "icon_toolbar_arrow.gif", :style => "vertical-align:middle;" %> +
      + <% @task_types.each_index { |index| %> +
    • + style="border-bottom: 1px solid #CCCCCC;" + <% end %> + > + + <%= @task_type == @task_types[index][1] ? "X" : "  " %> + <%=@task_types[index][0]%> +
    • + <% } %> +
    +
  • +
  • + <%= image_tag "icon_move.png", :style => "vertical-align:middle;" %>  State    <%= image_tag "icon_toolbar_arrow.gif", :style => "vertical-align:middle;" %> +
      + <% @task_states.each_index { |index| %> +
    • + style="border-bottom: 1px solid #CCCCCC;" + <% end %> + > + + <%= @task_state == @task_states[index][1] ? "X" : "  " %> + <%=@task_states[index][0]%> +
    • + <% } %> +
    +
  • +
+
+ + + +
+<% if @tasks[:rows].size != 0 %> +
+ <%= render :partial => "/task/grid", :locals => { :table_id => "tasks_grid", + :task_type => @task_type, + :task_state => @task_state, + :pool => @pool, + :checkboxes => false, + :on_select => "tasks_grid_select" } %> +
+ +<% else %> +
+
+ <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> + +
+ No tasks found.

+ <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   +
+
+
+<% end %> diff --git a/wui/src/app/views/layouts/_navigation_tabs.rhtml b/wui/src/app/views/layouts/_navigation_tabs.rhtml index 771958c..629ab93 100644 --- a/wui/src/app/views/layouts/_navigation_tabs.rhtml +++ b/wui/src/app/views/layouts/_navigation_tabs.rhtml @@ -13,6 +13,7 @@ + <% elsif controller.controller_name == "resources" and @vm_resource_pool != nil %> + +
+<% if @tasks[:rows].size != 0 %> +
+ <%= render :partial => "/task/grid", :locals => { :table_id => "vm_tasks_grid", + :task_type => nil, + :task_state => @task_state, + :pool => @vm_resource_pool, + :checkboxes => false, + :on_select => "vm_tasks_grid_select" } %> +
+ +<% else %> +
+
+ <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> + +
+ No tasks found.

+ <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   +
+
+
+<% end %> diff --git a/wui/src/app/views/task/_grid.rhtml b/wui/src/app/views/task/_grid.rhtml new file mode 100644 index 0000000..cab99e5 --- /dev/null +++ b/wui/src/app/views/task/_grid.rhtml @@ -0,0 +1,50 @@ +<% tasks_per_page = 40 %> +
+<%= "" if checkboxes %> + +<%= '' if checkboxes %> +
+ -- 1.5.5.1 From lutter at redhat.com Tue Aug 26 18:29:53 2008 From: lutter at redhat.com (David Lutterkort) Date: Tue, 26 Aug 2008 18:29:53 +0000 Subject: [Ovirt-devel] [PATCH] Managed node configuration generator. In-Reply-To: <1219760231-9979-1-git-send-email-dpierce@redhat.com> References: <1219760231-9979-1-git-send-email-dpierce@redhat.com> Message-ID: <1219775393.4728.138.camel@localhost.localdomain> On Tue, 2008-08-26 at 10:17 -0400, Darryl L. Pierce wrote: > This generator takes as input an object graph for a Host and a map of mac > addresses to interface names on the managed node. It then generates the > configuration details for that managed node (currently, only the NIC > configuration is processed). With those details it generates the content > for a configuration file that will be retrieved by the managed node and > run through augtool to configure the managed node. ACK. Very nice. David From lutter at redhat.com Tue Aug 26 22:16:22 2008 From: lutter at redhat.com (David Lutterkort) Date: Tue, 26 Aug 2008 22:16:22 +0000 Subject: [Ovirt-devel] Re: [PATCH] vm creation / start / stop tests via selenium In-Reply-To: <1219785949-3389-1-git-send-email-mmorsi@redhat.com> References: <1219785949-3389-1-git-send-email-mmorsi@redhat.com> Message-ID: <1219788982.4728.183.camel@localhost.localdomain> On Tue, 2008-08-26 at 17:25 -0400, Mohammed Morsi wrote: > --- > autobuild.sh | 5 +- > wui/src/test/fixtures/cpus.yml | 68 ++++++++++++++++---- > wui/src/test/fixtures/hosts.yml | 10 +++ > wui/src/test/fixtures/nics.yml | 13 +++- > wui/src/test/fixtures/quotas.yml | 8 +- > wui/src/test/fixtures/storage_pools.yml | 9 +++ > wui/src/test/functional/interface_test.rb | 98 +++++++++++++++++++++++++++- > wui/src/test/unit/cpu_test.rb | 2 + > 8 files changed, 189 insertions(+), 24 deletions(-) ACK. Some small nits: > diff --git a/autobuild.sh b/autobuild.sh > index 6c95cb1..95395d0 100755 > --- a/autobuild.sh > +++ b/autobuild.sh > @@ -80,7 +80,10 @@ fi > echo "Running the wui tests" > $ssh_cmd \ > "sed -i \"s/#RAILS_ENV=production/RAILS_ENV=test/g\" /etc/sysconfig/ovirt-rails && \ > - service ovirt-mongrel-rails restart && service httpd restart && \ > + sed -i \"s/development/test/\" /usr/share/ovirt-wui/dutils/active_record_env.rb && \ Why is it necessary to patch active_record_env.rb ? Shouldn't it be enough to set the RAILS_ENV environment variable ? > diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb > index 6563b44..eadeb74 100644 > --- a/wui/src/test/functional/interface_test.rb > +++ b/wui/src/test/functional/interface_test.rb > + # click the button > + @browser.click "//form[@id='vm_form']/div[2]/div[2]/div[2]/a" Ultimately, we should fix up the templates/views so that the tests can reference the elements they are really interested in by their id, instead of depending on the exact structure of the HTML ... that will become rather painful to maintain. If it works now, that's fine, but as soon as it breaks, the fix should be to annotate the generated HTML, rather than adapt the tests all the time. David From lutter at redhat.com Tue Aug 26 22:24:42 2008 From: lutter at redhat.com (David Lutterkort) Date: Tue, 26 Aug 2008 22:24:42 +0000 Subject: [Ovirt-devel] Re: [PATCH] small bug fix, the include_vm variable is null when passed in from vm_controller.create In-Reply-To: <1219785949-3389-2-git-send-email-mmorsi@redhat.com> References: <1219785949-3389-1-git-send-email-mmorsi@redhat.com> <1219785949-3389-2-git-send-email-mmorsi@redhat.com> Message-ID: <1219789482.4728.188.camel@localhost.localdomain> On Tue, 2008-08-26 at 17:25 -0400, Mohammed Morsi wrote: > --- > wui/src/app/models/storage_volume.rb | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) This seems already to be fixed by commit 7930340cf3e1ab8eefbc948eb35674963de692ea Author: Jason Guiditta Date: Wed Aug 20 16:37:08 2008 -0400 Fix case where an id is not passed in. Signed-off-by: Jason Guiditta David From lutter at redhat.com Tue Aug 26 22:25:23 2008 From: lutter at redhat.com (David Lutterkort) Date: Tue, 26 Aug 2008 15:25:23 -0700 Subject: [Ovirt-devel] Re: [PATCH] moved selenium params out to configuration file In-Reply-To: <1219785949-3389-3-git-send-email-mmorsi@redhat.com> References: <1219785949-3389-1-git-send-email-mmorsi@redhat.com> <1219785949-3389-2-git-send-email-mmorsi@redhat.com> <1219785949-3389-3-git-send-email-mmorsi@redhat.com> Message-ID: <1219789523.4728.190.camel@localhost.localdomain> On Tue, 2008-08-26 at 17:25 -0400, Mohammed Morsi wrote: > --- > wui/src/config/selenium.yml | 8 ++++++++ > wui/src/test/functional/interface_test.rb | 20 ++++++++++++++------ > 2 files changed, 22 insertions(+), 6 deletions(-) > create mode 100644 wui/src/config/selenium.yml ACK From apevec at redhat.com Tue Aug 26 22:34:05 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 27 Aug 2008 00:34:05 +0200 Subject: [Ovirt-devel] [PATCH] save configuration changes Message-ID: <1219790045-10711-1-git-send-email-apevec@redhat.com> always issue augtool "save" in ovirt-early, so that generated configs are sequence of "set" commands only Signed-off-by: Alan Pevec --- ovirt-managed-node/src/scripts/ovirt-early | 1 + wui-appliance/common-post.ks | 1 - 2 files changed, 1 insertions(+), 1 deletions(-) diff --git a/ovirt-managed-node/src/scripts/ovirt-early b/ovirt-managed-node/src/scripts/ovirt-early index b709632..e5a9ef7 100755 --- a/ovirt-managed-node/src/scripts/ovirt-early +++ b/ovirt-managed-node/src/scripts/ovirt-early @@ -35,6 +35,7 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)" if [ $? -eq 0 ]; then printf . + echo "save" >> $cfgdb augtool < $cfgdb > /dev/null 2>&1 if [ $? -eq 0 ]; then printf "remote config applied." diff --git a/wui-appliance/common-post.ks b/wui-appliance/common-post.ks index 083e831..6ad7b7c 100644 --- a/wui-appliance/common-post.ks +++ b/wui-appliance/common-post.ks @@ -83,6 +83,5 @@ set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BOOTPROTO dhcp set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT y set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE Bridge set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -save EOF -- 1.5.4.1 From apevec at redhat.com Wed Aug 27 07:22:21 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 27 Aug 2008 09:22:21 +0200 Subject: [Ovirt-devel] [PATCH] [Documentation] new oVirt Server Suite Installation Guide Message-ID: <48B500AD.5030601@redhat.com> FYI I've just pushed to the 'next' branch this big documentation source patch from Christopher Curran. build-all will be modified to build documentation (one more option! :)) From dpierce at redhat.com Wed Aug 27 14:02:50 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 27 Aug 2008 10:02:50 -0400 Subject: [Ovirt-devel] Default configuration for a new managed node... Message-ID: <20080827140250.GD3920@redhat.com> In working on the managed node configuration generator, I came across a simple case that's going to come up every time. Basically, when a new managed node comes up, it's not going to have a configuration in the database. As things stand now, in that case the node would boot and have no active interfaces on the virtual network. Until/unless we introduce the ability to configure in real-time the node's interfaces, we'll always have to configure the node and then restart it to bring those up. So I'm considering having a default interface configuration that gets applied whenever a node comes up. It would be defined in a YML file, config/default_nic.yml, and would be applied. Either that, or we could have some configurable actions for undefined (in the DB) nics such as to turn on DHCP and set it to use ovirtbr0 as a bridge. Thoughts? -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From clalance at redhat.com Wed Aug 27 17:35:07 2008 From: clalance at redhat.com (Chris Lalancette) Date: Wed, 27 Aug 2008 19:35:07 +0200 Subject: [Ovirt-devel] Default configuration for a new managed node... In-Reply-To: <20080827140250.GD3920@redhat.com> References: <20080827140250.GD3920@redhat.com> Message-ID: <48B5904B.1050106@redhat.com> Darryl L. Pierce wrote: > In working on the managed node configuration generator, I came across a > simple case that's going to come up every time. > > Basically, when a new managed node comes up, it's not going to have a > configuration in the database. As things stand now, in that case the node > would boot and have no active interfaces on the virtual network. Until/unless > we introduce the ability to configure in real-time the node's interfaces, > we'll always have to configure the node and then restart it to bring those > up. > > So I'm considering having a default interface configuration that gets applied > whenever a node comes up. It would be defined in a YML file, > config/default_nic.yml, and would be applied. > > Either that, or we could have some configurable actions for undefined (in the > DB) nics such as to turn on DHCP and set it to use ovirtbr0 as a bridge. Hm, Alan is the expert here, but I thought we already did that. There was something like "defaultdb" or something that was set up for this, and it already pulled the default configuration down. Maybe I'm misunderstanding what you mean, though. Chris Lalancette From dpierce at redhat.com Wed Aug 27 17:44:15 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 27 Aug 2008 13:44:15 -0400 Subject: [Ovirt-devel] Re: Default configuration for a new managed node... In-Reply-To: <48B5904B.1050106@redhat.com> References: <20080827140250.GD3920@redhat.com> <48B5904B.1050106@redhat.com> Message-ID: <20080827174414.GA16498@redhat.com> +++ Chris Lalancette [27/08/08 19:35 +0200]: >Hm, Alan is the expert here, but I thought we already did that. There was >something like "defaultdb" or something that was set up for this, and it already >pulled the default configuration down. Maybe I'm misunderstanding what you >mean, though. What we have now is a hardcoded configuration file that gets sent down to all managed nodes on startup. The new controller, though, is replacing having an actual file on disk that gets pulled: instead, it generates the configuration at request time. So I _could_ have the generator return that configuration if it's given a mac address that's not in the database. But I think we should be sure that's the right thing to do. -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 Aug 27 17:55:51 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 27 Aug 2008 13:55:51 -0400 Subject: [Ovirt-devel] Configurable options for a Nic Message-ID: <20080827175550.GA21262@redhat.com> The database has mac, ip_addr, bridge, usage_type, and bandwidth: - mac and bandwidth would be read-only, - ip_addr would be configurable, and if left blank is treated as implying DHCP for that interface - I'm not clear how we're using usage_type, but I believe that would necessarily be a drop-down list of values So the oVirt host management screen would need to give us the ability to edit the IP address and usage type for an interface. Also, in looking at this list, I think I would, as a sysadmin, be confused that there was no interface listed in the above. I don't think of NICs in terms of their mac address but instead as the interface name itself. I think it would give some ease of reading to have the "last known" interface name stored in the database and displayed as read-only with the other details. Thoughts? -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 Wed Aug 27 18:04:36 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 27 Aug 2008 14:04:36 -0400 Subject: [Ovirt-devel] Configurable options for a Nic In-Reply-To: <20080827175550.GA21262@redhat.com> References: <20080827175550.GA21262@redhat.com> Message-ID: <48B59734.5000001@redhat.com> Darryl L. Pierce wrote: > The database has mac, ip_addr, bridge, usage_type, and bandwidth: > > - mac and bandwidth would be read-only, > - ip_addr would be configurable, and if left blank is treated as > implying > DHCP for that interface > - I'm not clear how we're using usage_type, but I believe that would > necessarily be a drop-down list of values > > So the oVirt host management screen would need to give us the ability > to edit > the IP address and usage type for an interface. > > Also, in looking at this list, I think I would, as a sysadmin, be > confused > that there was no interface listed in the above. I don't think of NICs in > terms of their mac address but instead as the interface name itself. I > think > it would give some ease of reading to have the "last known" interface > name > stored in the database and displayed as read-only with the other details. > > Thoughts? > usage_type would identify one nic as the 'management' interface, etc. And, yes, the list should probably be constrained to a set of known valid values. Besides 'management', I'm not sure what others we need. The management one is the only interface that's treated specially by ovirt (well it isn't yet, but it will be). This NIC is supposed to show up in the summary list for the host (which fields though?), and I believe this nic is also the one that will be required when "pre-filling" host information prior to first boot. Scott From dpierce at redhat.com Wed Aug 27 18:08:41 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 27 Aug 2008 14:08:41 -0400 Subject: [Ovirt-devel] Re: Configurable options for a Nic In-Reply-To: <48B59734.5000001@redhat.com> References: <20080827175550.GA21262@redhat.com> <48B59734.5000001@redhat.com> Message-ID: <20080827180841.GB21262@redhat.com> +++ Scott Seago [27/08/08 14:04 -0400]: >> Also, in looking at this list, I think I would, as a sysadmin, be >> confused >> that there was no interface listed in the above. I don't think of NICs in >> terms of their mac address but instead as the interface name itself. I >> think >> it would give some ease of reading to have the "last known" interface >> name >> stored in the database and displayed as read-only with the other details. >> >> Thoughts? >> > usage_type would identify one nic as the 'management' interface, etc. > And, yes, the list should probably be constrained to a set of known > valid values. Besides 'management', I'm not sure what others we need. > The management one is the only interface that's treated specially by > ovirt (well it isn't yet, but it will be). This NIC is supposed to show > up in the summary list for the host (which fields though?), and I > believe this nic is also the one that will be required when > "pre-filling" host information prior to first boot. So, in order to prefill a NIC, we'll need a "New" button on this page as well as the ability to edit and verify the mac address, yes? Should we be able to edit the other macs? -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 Wed Aug 27 18:19:46 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 27 Aug 2008 14:19:46 -0400 Subject: [Ovirt-devel] Re: Configurable options for a Nic In-Reply-To: <20080827180841.GB21262@redhat.com> References: <20080827175550.GA21262@redhat.com> <48B59734.5000001@redhat.com> <20080827180841.GB21262@redhat.com> Message-ID: <48B59AC2.1090703@redhat.com> Darryl L. Pierce wrote: > +++ Scott Seago [27/08/08 14:04 -0400]: >>> Also, in looking at this list, I think I would, as a sysadmin, be >>> confused >>> that there was no interface listed in the above. I don't think of >>> NICs in >>> terms of their mac address but instead as the interface name itself. >>> I think >>> it would give some ease of reading to have the "last known" >>> interface name >>> stored in the database and displayed as read-only with the other >>> details. >>> >>> Thoughts? >>> >> usage_type would identify one nic as the 'management' interface, >> etc. And, yes, the list should probably be constrained to a set of >> known valid values. Besides 'management', I'm not sure what others >> we need. The management one is the only interface that's treated >> specially by ovirt (well it isn't yet, but it will be). This NIC is >> supposed to show up in the summary list for the host (which fields >> though?), and I believe this nic is also the one that will be >> required when "pre-filling" host information prior to first boot. > > So, in order to prefill a NIC, we'll need a "New" button on this page > as well > as the ability to edit and verify the mac address, yes? Should we be > able to > edit the other macs? Well the 'new host' form would be a bit of a special case. We'd only be able to fill in the MAC address for the management nic -- everything else would be blank (not sure about hostname, as I believe we'd need something there...) So that wouldn't come up on this form. In fact, I don't imagine you'd be able to edit the usage type -- unless the WUI is the place to redefine the management nic, etc. I'm not sure what the use case is for editing this field or adding/removing NICs or editing MACs. Scott From pmyers at redhat.com Wed Aug 27 18:28:33 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 27 Aug 2008 14:28:33 -0400 Subject: [Ovirt-devel] 0.92-1 oVirt Release Message-ID: <48B59CD1.2080708@redhat.com> The oVirt development team is pleased to announce the 0.92-1 release of both the oVirt Node and oVirt Server Suite. There are a lot of new features in this release, including: * Support for migration of guests from one node to another, including the ability to clear all of the running guests from a node. * Support for provisioning guests with Linux OSes (Windows provisioning is still in the works) * The oVirt Appliance has been restructured so that the 'developer' and 'bundled' appliance are the same. The appliance can be used to manage both 'fake' oVirt Nodes and real hardware. * The oVirt Node can now be installed via kernel command line parameters to local hard or USB disks * Serial consoles are exposed on both oVirt Nodes and their guests * General UI improvements and bugfixes * Ability to search in the UI The new appliance can be downloaded at: http://www.ovirt.org/download.html Instructions for using the appliance and for building your own appliance from our git repository are available at: http://www.ovirt.org/install-instructions.html Please download and try it out. Let us know via the mailing list or IRC if you have any suggestions for enhancements or feedback on what we've implemented so far. Also, bugs can be filed on the oVirt project at links provided on: http://www.ovirt.org/contribute.html Thanks to everyone who contributed to oVirt for this release! From pmyers at redhat.com Wed Aug 27 18:35:33 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 27 Aug 2008 14:35:33 -0400 Subject: [Ovirt-devel] OVirt API for hosts and hardware/storage pools In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <48B59E75.9030805@redhat.com> David Lutterkort wrote: > [ Resent. The previous postings didn't have the actual patches in them ] > > This is a revamp of the API I posted previously. The most important change > is that the REST specific controllers have been folded into the existing > controllers, so that the WUI and the API share the same business logic, and > in particular perform the same permission checks. > > Once Steve's patches to allow HTTP authentication have been committed, the > API can be accessed through the URL > http://USER:PASSWORD at SERVER/ovirt. Until then, you either have to hack the > config of your OVirt server to allow unauthenticated access and assume > that's user 'ovirtadmin', or direct API requests directly at the Mongrel > port at SERVER:3000 > > Patch 6/6 contains a little supporting client code and the example script > examples/script.rb that shows what you can do with the API (and how) > > [1] https://www.redhat.com/archives/ovirt-devel/2008-August/msg00045.html I didn't review the code since I'm not a Ruby hacker, but I did apply these and tested basic functionality. There were no regressions that I saw and the API code mostly worked, so I think this can be ACKed. Of course we make no committments about API stability for a while yet. All things are subject to change for now... Perry From dpierce at redhat.com Wed Aug 27 18:37:57 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 27 Aug 2008 14:37:57 -0400 Subject: [Ovirt-devel] Re: Configurable options for a Nic In-Reply-To: <48B59AC2.1090703@redhat.com> References: <20080827175550.GA21262@redhat.com> <48B59734.5000001@redhat.com> <20080827180841.GB21262@redhat.com> <48B59AC2.1090703@redhat.com> Message-ID: <20080827183757.GC21262@redhat.com> +++ Scott Seago [27/08/08 14:19 -0400]: >> So, in order to prefill a NIC, we'll need a "New" button on this page >> as well >> as the ability to edit and verify the mac address, yes? Should we be >> able to >> edit the other macs? > Well the 'new host' form would be a bit of a special case. We'd only be > able to fill in the MAC address for the management nic -- everything > else would be blank (not sure about hostname, as I believe we'd need > something there...) There's no hostname column in nics: that's in hosts. Unless that's what you were referring to. > So that wouldn't come up on this form. In fact, I > don't imagine you'd be able to edit the usage type -- unless the WUI is > the place to redefine the management nic, etc. I'm not sure what the use > case is for editing this field or adding/removing NICs or editing MACs. Yeah, given that we can't control which NIC is the management one on the machine, does it even make sense to have that in the DB at all? -- Darryl L. Pierce, Sr. Software Engineer Red Hat, Inc. - http://www.redhat.com/ oVirt - Virtual Machine Management - http://www.ovirt.org/ "What do you care what other people think, Mr. Feynman?" -------------- 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 Wed Aug 27 18:50:05 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 27 Aug 2008 14:50:05 -0400 Subject: [Ovirt-devel] Re: Configurable options for a Nic In-Reply-To: <20080827183757.GC21262@redhat.com> References: <20080827175550.GA21262@redhat.com> <48B59734.5000001@redhat.com> <20080827180841.GB21262@redhat.com> <48B59AC2.1090703@redhat.com> <20080827183757.GC21262@redhat.com> Message-ID: <48B5A1DD.4070508@redhat.com> Darryl L. Pierce wrote: > +++ Scott Seago [27/08/08 14:19 -0400]: >>> So, in order to prefill a NIC, we'll need a "New" button on this >>> page as well >>> as the ability to edit and verify the mac address, yes? Should we >>> be able to >>> edit the other macs? >> Well the 'new host' form would be a bit of a special case. We'd only >> be able to fill in the MAC address for the management nic -- >> everything else would be blank (not sure about hostname, as I >> believe we'd need something there...) > > There's no hostname column in nics: that's in hosts. Unless that's > what you > were referring to. > Yes that's what I mean. Before bringing a host online, we would "pre-identify" it with the management nic -- creating the host object + 1 NIC object in the db. host-browser would fill in the rest when the node first boots up. >> So that wouldn't come up on this form. In fact, I don't imagine >> you'd be able to edit the usage type -- unless the WUI is the place >> to redefine the management nic, etc. I'm not sure what the use case >> is for editing this field or adding/removing NICs or editing MACs. > > Yeah, given that we can't control which NIC is the management one on the > machine, does it even make sense to have that in the DB at all? > Well we need to know this in order to show the mac address/other NIC info in the host summary list -- at least that's the idea I got from the IRC discussion last week. Scott From lutter at redhat.com Wed Aug 27 18:28:42 2008 From: lutter at redhat.com (David Lutterkort) Date: Wed, 27 Aug 2008 18:28:42 +0000 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <48B40F28.10005@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> <48A97841.4090906@redhat.com> <1219710210.4728.109.camel@localhost.localdomain> <48B40F28.10005@redhat.com> Message-ID: <1219861722.4728.247.camel@localhost.localdomain> On Tue, 2008-08-26 at 10:11 -0400, Scott Seago wrote: > I'm assuming the path-based pool lookup is just an alternate method of > getting this from your API, as the id-based ones will all still work. I > just realized that full path-based lookup will only work for users that > have read permissions on the whole hierarchy. A user with lower-level > permissions only (i.e. only read permissions for pools under > '/default/engineering/qa' and write permissions for subpools below that) > won't even see the top level pool. I think that permissioning scheme is fundamentally flawed; at the very least, any user that has permission on some pool should at least be allowed to know about the existence of pools above "their" pools - they may not be able to view any info about them, but at the very least, they should know that they are there. David From pmyers at redhat.com Wed Aug 27 19:03:38 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Wed, 27 Aug 2008 15:03:38 -0400 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <1219861722.4728.247.camel@localhost.localdomain> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> <48A97841.4090906@redhat.com> <1219710210.4728.109.camel@localhost.localdomain> <48B40F28.10005@redhat.com> <1219861722.4728.247.camel@localhost.localdomain> Message-ID: <48B5A50A.4030909@redhat.com> David Lutterkort wrote: > On Tue, 2008-08-26 at 10:11 -0400, Scott Seago wrote: >> I'm assuming the path-based pool lookup is just an alternate method of >> getting this from your API, as the id-based ones will all still work. I >> just realized that full path-based lookup will only work for users that >> have read permissions on the whole hierarchy. A user with lower-level >> permissions only (i.e. only read permissions for pools under >> '/default/engineering/qa' and write permissions for subpools below that) >> won't even see the top level pool. > > I think that permissioning scheme is fundamentally flawed; at the very > least, any user that has permission on some pool should at least be > allowed to know about the existence of pools above "their" pools - they > may not be able to view any info about them, but at the very least, they > should know that they are there. Not necessarily. Consider the cloud computing model... The admins might know about the fact that there are hardware pools, but should a user of a VM even know that there is such a thing as a hardware pool? To them the hardware pools should be completely hidden in the UI, including the tree view. Perry From slinabery at redhat.com Wed Aug 27 19:16:52 2008 From: slinabery at redhat.com (Steve Linabery) Date: Wed, 27 Aug 2008 13:16:52 -0600 Subject: [Ovirt-devel] [PATCH] added hardware_pool and vm_pool to tasks to facilitate pool-level task summaries. In-Reply-To: <1219673215-12353-1-git-send-email-sseago@redhat.com> References: <1219673215-12353-1-git-send-email-sseago@redhat.com> Message-ID: <20080827191651.GJ13694@redhat.com> On Mon, Aug 25, 2008 at 10:06:55AM -0400, Scott Seago wrote: > > Signed-off-by: Scott Seago > --- > wui/src/app/models/hardware_pool.rb | 1 + > wui/src/app/models/host_task.rb | 4 ++ > wui/src/app/models/storage_task.rb | 4 ++ > wui/src/app/models/task.rb | 2 + > wui/src/app/models/vm_resource_pool.rb | 1 + > wui/src/app/models/vm_task.rb | 7 +++ > wui/src/db/migrate/016_add_pools_to_tasks.rb | 58 ++++++++++++++++++++++++++ > 7 files changed, 77 insertions(+), 0 deletions(-) > create mode 100644 wui/src/db/migrate/016_add_pools_to_tasks.rb > > diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb > index 276779f..3678420 100644 > --- a/wui/src/app/models/hardware_pool.rb > +++ b/wui/src/app/models/hardware_pool.rb > @@ -19,6 +19,7 @@ > > class HardwarePool < Pool > > + has_many :tasks, :dependent => :nullify, :order => "id ASC" > def all_storage_volumes > StorageVolume.find(:all, :include => {:storage_pool => :hardware_pool}, :conditions => "pools.id = #{id}") > end > diff --git a/wui/src/app/models/host_task.rb b/wui/src/app/models/host_task.rb > index 5cce5b9..ae597b0 100644 > --- a/wui/src/app/models/host_task.rb > +++ b/wui/src/app/models/host_task.rb > @@ -21,4 +21,8 @@ class HostTask < Task > belongs_to :host > > ACTION_CLEAR_VMS = "clear_vms" > + > + def after_initialize > + self.hardware_pool = host.hardware_pool if self.host > + end > end > diff --git a/wui/src/app/models/storage_task.rb b/wui/src/app/models/storage_task.rb > index 5d67b98..e57b6de 100644 > --- a/wui/src/app/models/storage_task.rb > +++ b/wui/src/app/models/storage_task.rb > @@ -21,4 +21,8 @@ class StorageTask < Task > belongs_to :storage_pool > > ACTION_REFRESH_POOL = "refresh_pool" > + > + def after_initialize > + self.hardware_pool = storage_pool.hardware_pool if self.storage_pool > + end > end > diff --git a/wui/src/app/models/task.rb b/wui/src/app/models/task.rb > index 7960056..a7faaf0 100644 > --- a/wui/src/app/models/task.rb > +++ b/wui/src/app/models/task.rb > @@ -18,6 +18,8 @@ > # also available at http://www.gnu.org/copyleft/gpl.html. > > class Task < ActiveRecord::Base > + belongs_to :hardware_pool > + belongs_to :vm_resource_pool > > STATE_QUEUED = "queued" > STATE_RUNNING = "running" > diff --git a/wui/src/app/models/vm_resource_pool.rb b/wui/src/app/models/vm_resource_pool.rb > index d3f7d43..fff6dac 100644 > --- a/wui/src/app/models/vm_resource_pool.rb > +++ b/wui/src/app/models/vm_resource_pool.rb > @@ -19,6 +19,7 @@ > > class VmResourcePool < Pool > > + has_many :tasks, :dependent => :nullify, :order => "id ASC" > def get_type_label > "Virtual Machine Pool" > end > diff --git a/wui/src/app/models/vm_task.rb b/wui/src/app/models/vm_task.rb > index 3f52478..ab96e6f 100644 > --- a/wui/src/app/models/vm_task.rb > +++ b/wui/src/app/models/vm_task.rb > @@ -107,6 +107,13 @@ class VmTask < Task > PRIV_OBJECT_HW_POOL], > :popup_action => 'migrate'} } > > + def after_initialize > + if self.vm > + self.vm_resource_pool = vm.vm_resource_pool > + self.hardware_pool = vm.get_hardware_pool > + end > + end > + > def self.valid_actions_for_vm_state(state, vm=nil, user=nil) > actions = [] > ACTIONS.each do |action, hash| > diff --git a/wui/src/db/migrate/016_add_pools_to_tasks.rb b/wui/src/db/migrate/016_add_pools_to_tasks.rb > new file mode 100644 > index 0000000..58ff83d > --- /dev/null > +++ b/wui/src/db/migrate/016_add_pools_to_tasks.rb > @@ -0,0 +1,58 @@ > +# > +# 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. > + > +class AddPoolsToTasks < ActiveRecord::Migration > + def self.up > + add_column :tasks, :vm_resource_pool_id, :integer > + add_column :tasks, :hardware_pool_id, :integer > + execute "alter table tasks add constraint fk_tasks_vm_pools > + foreign key (vm_resource_pool_id) references pools(id)" > + execute "alter table tasks add constraint fk_tasks_hw_pools > + foreign key (hardware_pool_id) references pools(id)" > + Task.transaction do > + VmTask.find(:all).each do |task| > + vm = task.vm > + if vm > + task.vm_resource_pool = vm.vm_resource_pool > + task.hardware_pool = vm.get_hardware_pool > + task.save! > + end > + end > + StorageTask.find(:all).each do |task| > + pool = task.storage_pool > + if pool > + task.hardware_pool = pool.hardware_pool > + task.save! > + end > + end > + HostTask.find(:all).each do |task| > + host = task.host > + if host > + task.hardware_pool = host.hardware_pool > + task.save! > + end > + end > + end > + end > + > + def self.down > + remove_column :tasks, :vm_resource_pool_id > + remove_column :tasks, :hardware_pool_id > + end > +end > -- > 1.5.5.1 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel Works for me. ACK! From slinabery at redhat.com Wed Aug 27 19:17:27 2008 From: slinabery at redhat.com (Steve Linabery) Date: Wed, 27 Aug 2008 13:17:27 -0600 Subject: [Ovirt-devel] [PATCH] Added Tasks tab/pane to HW and VM pools pages In-Reply-To: <1219764285-32230-1-git-send-email-sseago@redhat.com> References: <1219764285-32230-1-git-send-email-sseago@redhat.com> Message-ID: <20080827191727.GK13694@redhat.com> On Tue, Aug 26, 2008 at 11:24:45AM -0400, Scott Seago wrote: > A tasks pane is now available on both VM and HW pools page. In both cases, the tasks show up as a flexigrid panel showing tasks for the pool. By default, tasks are filtered for 'pending' state, but they may be filtered on any state (or to show all). For the VM page, all tasks for all VMs in the VM pool are available. For the HW pool page, there are 3 types of tasks available: > 1) VM tasks: tasks for all VMs contained in VM pools within this HW pool > 2) Host tasks: tasks for all hosts in this pool > 3) storage tasks: tasks for all storage pools in this HW pool. > By default all task types are shown, but the pulldown menu lets the user filter on task type as well. > > Signed-off-by: Scott Seago > --- > wui/src/app/controllers/application.rb | 8 ++- > wui/src/app/controllers/hardware_controller.rb | 61 +++++++++++++---- > wui/src/app/controllers/resources_controller.rb | 54 +++++++++++---- > wui/src/app/models/hardware_pool.rb | 3 +- > wui/src/app/models/host_task.rb | 6 ++- > wui/src/app/models/storage_task.rb | 5 +- > wui/src/app/models/task.rb | 14 ++++ > wui/src/app/models/vm_resource_pool.rb | 2 +- > wui/src/app/models/vm_task.rb | 5 +- > wui/src/app/views/hardware/show_tasks.rhtml | 77 ++++++++++++++++++++++ > wui/src/app/views/layouts/_navigation_tabs.rhtml | 2 + > wui/src/app/views/resources/show_tasks.rhtml | 61 +++++++++++++++++ > wui/src/app/views/task/_grid.rhtml | 50 ++++++++++++++ > 13 files changed, 313 insertions(+), 35 deletions(-) > create mode 100644 wui/src/app/views/hardware/show_tasks.rhtml > create mode 100644 wui/src/app/views/resources/show_tasks.rhtml > create mode 100644 wui/src/app/views/task/_grid.rhtml > > diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb > index b27ddbe..b95577a 100644 > --- a/wui/src/app/controllers/application.rb > +++ b/wui/src/app/controllers/application.rb > @@ -90,7 +90,7 @@ class ApplicationController < ActionController::Base > end > > # don't define find_opts for array inputs > - def json_list(full_items, attributes, arg_list=[], find_opts={}) > + def json_hash(full_items, attributes, arg_list=[], find_opts={}) > page = params[:page].to_i > paginate_opts = {:page => page, > :order => "#{params[:sortname]} #{params[:sortorder]}", > @@ -114,7 +114,11 @@ class ApplicationController < ActionController::Base > end > item_hash > end > - render :json => json_hash.to_json > + json_hash > + end > + # don't define find_opts for array inputs > + def json_list(full_items, attributes, arg_list=[], find_opts={}) > + render :json => json_hash(full_items, attributes, arg_list, find_opts).to_json > end > > > diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb > index 019fdd8..00fe06b 100644 > --- a/wui/src/app/controllers/hardware_controller.rb > +++ b/wui/src/app/controllers/hardware_controller.rb > @@ -24,19 +24,13 @@ class HardwareController < ApplicationController > :redirect_to => { :action => :list } > > before_filter :pre_json, :only => [:vm_pools_json, :users_json, > - :storage_volumes_json] > + :storage_volumes_json, :show_tasks, :tasks] > before_filter :pre_modify, :only => [:add_hosts, :move_hosts, > :add_storage, :move_storage, > :create_storage, :delete_storage] > > > def show > - set_perms(@perm_obj) > - unless @can_view > - flash[:notice] = 'You do not have permission to view this hardware pool: redirecting to top level' > - redirect_to :controller => "dashboard" > - return > - end > if params[:ajax] > render :layout => 'tabs-and-content' > end > @@ -104,14 +98,47 @@ class HardwareController < ApplicationController > @hardware_pools = HardwarePool.find :all > end > > + def show_tasks > + @task_types = [["VM Task", "VmTask"], > + ["Host Task", "HostTask"], > + ["Storage Task", "StorageTask", "break"], > + ["Show All", ""]] > + @task_states = [["Queued", Task::STATE_QUEUED], > + ["Running", Task::STATE_RUNNING], > + ["Paused", Task::STATE_PAUSED], > + ["Finished", Task::STATE_FINISHED], > + ["Failed", Task::STATE_FAILED], > + ["Canceled", Task::STATE_CANCELED, "break"], > + ["Show All", ""]] > + params[:page]=1 > + params[:sortname]="tasks.created_at" > + params[:sortorder]="desc" > + @tasks = tasks_internal > + show > + end > + > + def tasks > + render :json => tasks_internal.to_json > + end > + > + def tasks_internal > + @task_type = params[:task_type] > + @task_type ||="" > + @task_state = params[:task_state] > + @task_state ||=Task::STATE_QUEUED > + conditions = {} > + conditions[:type] = @task_type unless @task_type.empty? > + conditions[:state] = @task_state unless @task_state.empty? > + find_opts = {:include => [:storage_pool, :host, :vm]} > + find_opts[:conditions] = conditions unless conditions.empty? > + attr_list = [] > + attr_list << :id if params[:checkboxes] > + attr_list += [:type_label, :task_obj, :action, :state, :user, :created_at, :args, :message] > + json_hash(@pool.tasks, attr_list, [:all], find_opts) > + end > + > def quick_summary > pre_show > - set_perms(@perm_obj) > - unless @can_view > - flash[:notice] = 'You do not have permission to view this Hardware Pool: redirecting to top level' > - redirect_to :action => 'list' > - return > - end > render :layout => 'selection' > end > > @@ -352,10 +379,16 @@ class HardwareController < ApplicationController > @pool = HardwarePool.find(params[:id]) > @perm_obj = @pool > @current_pool_id=@pool.id > + set_perms(@perm_obj) > + unless @can_view > + flash[:notice] = 'You do not have permission to view this Hardware Pool: redirecting to top level' > + # FIXME: figure out the return type and render appropriately > + redirect_to :action => 'list' > + return > + end > end > def pre_json > pre_show > - show > end > def pre_modify > pre_edit > diff --git a/wui/src/app/controllers/resources_controller.rb b/wui/src/app/controllers/resources_controller.rb > index 20defca..5a8276c 100644 > --- a/wui/src/app/controllers/resources_controller.rb > +++ b/wui/src/app/controllers/resources_controller.rb > @@ -23,7 +23,8 @@ class ResourcesController < ApplicationController > render :action => 'list' > end > > - before_filter :pre_json, :only => [:vms_json, :users_json] > + before_filter :pre_json, :only => [:vms_json, :users_json, > + :show_tasks, :tasks] > before_filter :pre_vm_actions, :only => [:vm_actions] > > # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) > @@ -44,16 +45,10 @@ class ResourcesController < ApplicationController > > # resource's summary page > def show > - set_perms(@perm_obj) > - @is_hwpool_admin = @vm_resource_pool.parent.can_modify(@user) > @action_values = [["Suspend", VmTask::ACTION_SUSPEND_VM], > ["Resume", VmTask::ACTION_RESUME_VM], > ["Save", VmTask::ACTION_SAVE_VM], > ["Restore", VmTask::ACTION_RESTORE_VM]] > - unless @can_view > - flash[:notice] = 'You do not have permission to view this VM Resource Pool: redirecting to top level' > - redirect_to :action => 'list' > - end > if params[:ajax] > render :layout => 'tabs-and-content' > end > @@ -64,12 +59,6 @@ class ResourcesController < ApplicationController > > def quick_summary > pre_show > - set_perms(@perm_obj) > - @is_hwpool_admin = @vm_resource_pool.parent.can_modify(@user) > - unless @can_view > - flash[:notice] = 'You do not have permission to view this VM Resource Pool: redirecting to top level' > - redirect_to :action => 'list' > - end > render :layout => 'selection' > end > > @@ -90,6 +79,38 @@ class ResourcesController < ApplicationController > show > end > > + def show_tasks > + @task_states = [["Queued", Task::STATE_QUEUED], > + ["Running", Task::STATE_RUNNING], > + ["Paused", Task::STATE_PAUSED], > + ["Finished", Task::STATE_FINISHED], > + ["Failed", Task::STATE_FAILED], > + ["Canceled", Task::STATE_CANCELED, "break"], > + ["Show All", ""]] > + params[:page]=1 > + params[:sortname]="tasks.created_at" > + params[:sortorder]="desc" > + @tasks = tasks_internal > + show > + end > + > + def tasks > + render :json => tasks_internal.to_json > + end > + > + def tasks_internal > + @task_state = params[:task_state] > + @task_state ||=Task::STATE_QUEUED > + conditions = {} > + conditions[:state] = @task_state unless @task_state.empty? > + find_opts = {:include => [:storage_pool, :host, :vm]} > + find_opts[:conditions] = conditions unless conditions.empty? > + attr_list = [] > + attr_list << :id if params[:checkboxes] > + attr_list += [:type_label, :task_obj, :action, :state, :user, :created_at, :args, :message] > + json_hash(@vm_resource_pool.tasks, attr_list, [:all], find_opts) > + end > + > def vms_json > json_list(@vm_resource_pool.vms, > [:id, :description, :uuid, :num_vcpus_allocated, :memory_allocated_in_mb, :vnic_mac_addr, :state, :id]) > @@ -208,6 +229,12 @@ class ResourcesController < ApplicationController > @vm_resource_pool = VmResourcePool.find(params[:id]) > @perm_obj = @vm_resource_pool > @current_pool_id=@vm_resource_pool.id > + set_perms(@perm_obj) > + @is_hwpool_admin = @vm_resource_pool.parent.can_modify(@user) > + unless @can_view > + flash[:notice] = 'You do not have permission to view this VM Resource Pool: redirecting to top level' > + redirect_to :action => 'dashboard' > + end > end > def pre_edit > @vm_resource_pool = VmResourcePool.find(params[:id]) > @@ -218,7 +245,6 @@ class ResourcesController < ApplicationController > end > def pre_json > pre_show > - show > end > def pre_vm_actions > @vm_resource_pool = VmResourcePool.find(params[:id]) > diff --git a/wui/src/app/models/hardware_pool.rb b/wui/src/app/models/hardware_pool.rb > index 3678420..a4c921b 100644 > --- a/wui/src/app/models/hardware_pool.rb > +++ b/wui/src/app/models/hardware_pool.rb > @@ -19,7 +19,7 @@ > > class HardwarePool < Pool > > - has_many :tasks, :dependent => :nullify, :order => "id ASC" > + has_many :tasks, :dependent => :nullify > def all_storage_volumes > StorageVolume.find(:all, :include => {:storage_pool => :hardware_pool}, :conditions => "pools.id = #{id}") > end > @@ -98,4 +98,5 @@ class HardwarePool < Pool > return {:total => total, :labels => labels} > end > > + > end > diff --git a/wui/src/app/models/host_task.rb b/wui/src/app/models/host_task.rb > index ae597b0..0aea41b 100644 > --- a/wui/src/app/models/host_task.rb > +++ b/wui/src/app/models/host_task.rb > @@ -18,11 +18,15 @@ > # also available at http://www.gnu.org/copyleft/gpl.html. > > class HostTask < Task > - belongs_to :host > > ACTION_CLEAR_VMS = "clear_vms" > > def after_initialize > self.hardware_pool = host.hardware_pool if self.host > end > + > + def task_obj > + "Host;;;#{self.host.id};;;#{self.host.hostname}" > + end > + > end > diff --git a/wui/src/app/models/storage_task.rb b/wui/src/app/models/storage_task.rb > index e57b6de..db604d5 100644 > --- a/wui/src/app/models/storage_task.rb > +++ b/wui/src/app/models/storage_task.rb > @@ -18,11 +18,14 @@ > # also available at http://www.gnu.org/copyleft/gpl.html. > > class StorageTask < Task > - belongs_to :storage_pool > > ACTION_REFRESH_POOL = "refresh_pool" > > def after_initialize > self.hardware_pool = storage_pool.hardware_pool if self.storage_pool > end > + > + def task_obj > + "StoragePool;;;#{self.storage_pool.id};;;#{self.storage_pool.display_name}" > + end > end > diff --git a/wui/src/app/models/task.rb b/wui/src/app/models/task.rb > index a7faaf0..efbed64 100644 > --- a/wui/src/app/models/task.rb > +++ b/wui/src/app/models/task.rb > @@ -20,6 +20,13 @@ > class Task < ActiveRecord::Base > belongs_to :hardware_pool > belongs_to :vm_resource_pool > + # moved associations here so that nested set :include directives work > + # StorageTask association > + belongs_to :storage_pool > + # HostTask association > + belongs_to :host > + # VmTask association > + belongs_to :vm > > STATE_QUEUED = "queued" > STATE_RUNNING = "running" > @@ -50,4 +57,11 @@ class Task < ActiveRecord::Base > Task.find(:all, :conditions => conditions) > end > > + def type_label > + self.class.name[0..-5] > + end > + def task_obj > + "" > + end > + > end > diff --git a/wui/src/app/models/vm_resource_pool.rb b/wui/src/app/models/vm_resource_pool.rb > index fff6dac..d6acf80 100644 > --- a/wui/src/app/models/vm_resource_pool.rb > +++ b/wui/src/app/models/vm_resource_pool.rb > @@ -19,7 +19,7 @@ > > class VmResourcePool < Pool > > - has_many :tasks, :dependent => :nullify, :order => "id ASC" > + has_many :tasks, :dependent => :nullify > def get_type_label > "Virtual Machine Pool" > end > diff --git a/wui/src/app/models/vm_task.rb b/wui/src/app/models/vm_task.rb > index ab96e6f..31c4ac8 100644 > --- a/wui/src/app/models/vm_task.rb > +++ b/wui/src/app/models/vm_task.rb > @@ -18,7 +18,6 @@ > # also available at http://www.gnu.org/copyleft/gpl.html. > > class VmTask < Task > - belongs_to :vm > > ACTION_CREATE_VM = "create_vm" > > @@ -114,6 +113,10 @@ class VmTask < Task > end > end > > + def task_obj > + "Vm;;;#{self.vm.id};;;#{self.vm.description}" > + end > + > def self.valid_actions_for_vm_state(state, vm=nil, user=nil) > actions = [] > ACTIONS.each do |action, hash| > diff --git a/wui/src/app/views/hardware/show_tasks.rhtml b/wui/src/app/views/hardware/show_tasks.rhtml > new file mode 100644 > index 0000000..e49086c > --- /dev/null > +++ b/wui/src/app/views/hardware/show_tasks.rhtml > @@ -0,0 +1,77 @@ > +
> +
    > +
  • > + <%= image_tag "icon_move.png", :style => "vertical-align:middle;" %>  Type    <%= image_tag "icon_toolbar_arrow.gif", :style => "vertical-align:middle;" %> > +
      > + <% @task_types.each_index { |index| %> > +
    • + <% if (index == @task_types.length - 1) or @task_types[index].length == 3 %> > + style="border-bottom: 1px solid #CCCCCC;" > + <% end %> > + > > + > + <%= @task_type == @task_types[index][1] ? "X" : "  " %> > + <%=@task_types[index][0]%> > +
    • > + <% } %> > +
    > +
  • > +
  • > + <%= image_tag "icon_move.png", :style => "vertical-align:middle;" %>  State    <%= image_tag "icon_toolbar_arrow.gif", :style => "vertical-align:middle;" %> > +
      > + <% @task_states.each_index { |index| %> > +
    • + <% if (index == @task_states.length - 1) or @task_states[index].length == 3 %> > + style="border-bottom: 1px solid #CCCCCC;" > + <% end %> > + > > + > + <%= @task_state == @task_states[index][1] ? "X" : "  " %> > + <%=@task_states[index][0]%> > +
    • > + <% } %> > +
    > +
  • > +
> +
> + > + > + > +
> +<% if @tasks[:rows].size != 0 %> > +
> + <%= render :partial => "/task/grid", :locals => { :table_id => "tasks_grid", > + :task_type => @task_type, > + :task_state => @task_state, > + :pool => @pool, > + :checkboxes => false, > + :on_select => "tasks_grid_select" } %> > +
> + > +<% else %> > +
> +
> + <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> > + > +
> + No tasks found.

> + <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   > +
> +
> +
> +<% end %> > diff --git a/wui/src/app/views/layouts/_navigation_tabs.rhtml b/wui/src/app/views/layouts/_navigation_tabs.rhtml > index 771958c..629ab93 100644 > --- a/wui/src/app/views/layouts/_navigation_tabs.rhtml > +++ b/wui/src/app/views/layouts/_navigation_tabs.rhtml > @@ -13,6 +13,7 @@ > > > > + > > <% elsif controller.controller_name == "resources" and @vm_resource_pool != nil %> > > + > +
> +<% if @tasks[:rows].size != 0 %> > +
> + <%= render :partial => "/task/grid", :locals => { :table_id => "vm_tasks_grid", > + :task_type => nil, > + :task_state => @task_state, > + :pool => @vm_resource_pool, > + :checkboxes => false, > + :on_select => "vm_tasks_grid_select" } %> > +
> + > +<% else %> > +
> +
> + <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> > + > +
> + No tasks found.

> + <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   > +
> +
> +
> +<% end %> > diff --git a/wui/src/app/views/task/_grid.rhtml b/wui/src/app/views/task/_grid.rhtml > new file mode 100644 > index 0000000..cab99e5 > --- /dev/null > +++ b/wui/src/app/views/task/_grid.rhtml > @@ -0,0 +1,50 @@ > +<% tasks_per_page = 40 %> > +
> +<%= "
" if checkboxes %> > + > +<%= '
' if checkboxes %> > +
> + > -- > 1.5.5.1 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel Tested and it works like a dream. ACK! --Steve From mmorsi at redhat.com Wed Aug 27 20:08:00 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 27 Aug 2008 16:08:00 -0400 Subject: [Ovirt-devel] [PATCH] small fix to broken test cases cause be recent commit Message-ID: <1219867680-22818-1-git-send-email-mmorsi@redhat.com> --- wui/src/test/fixtures/hosts.yml | 17 +++++++++-------- wui/src/test/fixtures/nics.yml | 14 ++++++++------ wui/src/test/fixtures/pools.yml | 10 ++++++---- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/wui/src/test/fixtures/hosts.yml b/wui/src/test/fixtures/hosts.yml index bf6803c..44397da 100644 --- a/wui/src/test/fixtures/hosts.yml +++ b/wui/src/test/fixtures/hosts.yml @@ -1,11 +1,3 @@ -mailservers_managed_node: - uuid: '182a8596-961d-11dc-9387-001558c41534' - hostname: 'mail.mynetwork.com' - arch: 'i386' - memory: 16384 - is_disabled: 0 - hypervisor_type: 'kvm' - hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> one: id: 1 @@ -98,3 +90,12 @@ ten: hypervisor_type: 'kvm' hardware_pool_id: 1 state: "available" +mailservers_managed_node: + id: 11 + uuid: '182a8596-961d-11dc-9387-001558c41534' + hostname: 'mail.mynetwork.com' + arch: 'i386' + memory: 16384 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> diff --git a/wui/src/test/fixtures/nics.yml b/wui/src/test/fixtures/nics.yml index 1eeb5c2..8726fb6 100644 --- a/wui/src/test/fixtures/nics.yml +++ b/wui/src/test/fixtures/nics.yml @@ -1,9 +1,3 @@ -mailserver_nic_one: - mac: '00:11:22:33:44:55' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - one: id: 1 mac: '00:11:22:33:44:55' @@ -32,3 +26,11 @@ four: usage_type: '1' bandwidth: 10 host_id: 10 +mailserver_nic_one: + id: 5 + mac: '00:11:22:33:44:55' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + + diff --git a/wui/src/test/fixtures/pools.yml b/wui/src/test/fixtures/pools.yml index 91dd6e2..1862e40 100644 --- a/wui/src/test/fixtures/pools.yml +++ b/wui/src/test/fixtures/pools.yml @@ -1,7 +1,3 @@ -prodops_pool: - name: 'Production Operations' - type: 'HardwarePool' - one: id: 1 name: 'master pool' @@ -73,3 +69,9 @@ ten: parent_id: 5 lft: 14 rgt: 15 +prodops_pool: + id: 11 + name: 'Production Operations' + type: 'HardwarePool' + parent_id: 5 + -- 1.5.4.1 From mmorsi at redhat.com Wed Aug 27 20:41:32 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 27 Aug 2008 16:41:32 -0400 Subject: [Ovirt-devel] [PATCH] small fix to broken test cases cause be recent commit Message-ID: <1219869692-24722-1-git-send-email-mmorsi@redhat.com> --- wui/src/test/fixtures/hosts.yml | 19 ++++++++++--------- wui/src/test/fixtures/nics.yml | 11 ++++++----- wui/src/test/fixtures/pools.yml | 10 ++++++---- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/wui/src/test/fixtures/hosts.yml b/wui/src/test/fixtures/hosts.yml index 64707da..f54516a 100644 --- a/wui/src/test/fixtures/hosts.yml +++ b/wui/src/test/fixtures/hosts.yml @@ -1,12 +1,3 @@ -mailservers_managed_node: - uuid: '182a8596-961d-11dc-9387-001558c41534' - hostname: 'mail.mynetwork.com' - arch: 'i386' - memory: 16384 - is_disabled: 0 - hypervisor_type: 'kvm' - hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> - one: id: 1 uuid: '1148fdf8-961d-11dc-9387-001558c41534' @@ -88,3 +79,13 @@ nine: is_disabled: 0 hypervisor_type: 'kvm' hardware_pool_id: 3 +mailservers_managed_node: + id: 10 + uuid: '182a8596-961d-11dc-9387-001558c41534' + hostname: 'mail.mynetwork.com' + arch: 'i386' + memory: 16384 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + diff --git a/wui/src/test/fixtures/nics.yml b/wui/src/test/fixtures/nics.yml index c37f3d4..c5d2d19 100644 --- a/wui/src/test/fixtures/nics.yml +++ b/wui/src/test/fixtures/nics.yml @@ -1,8 +1,3 @@ -mailserver_nic_one: - mac: '00:11:22:33:44:55' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> one: id: 1 @@ -25,3 +20,9 @@ three: usage_type: '1' bandwidth: 10 host_id: 2 +mailserver_nic_one: + id: 4 + mac: '00:11:22:33:44:55' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> diff --git a/wui/src/test/fixtures/pools.yml b/wui/src/test/fixtures/pools.yml index 91dd6e2..1862e40 100644 --- a/wui/src/test/fixtures/pools.yml +++ b/wui/src/test/fixtures/pools.yml @@ -1,7 +1,3 @@ -prodops_pool: - name: 'Production Operations' - type: 'HardwarePool' - one: id: 1 name: 'master pool' @@ -73,3 +69,9 @@ ten: parent_id: 5 lft: 14 rgt: 15 +prodops_pool: + id: 11 + name: 'Production Operations' + type: 'HardwarePool' + parent_id: 5 + -- 1.5.4.1 From lutter at redhat.com Wed Aug 27 21:03:20 2008 From: lutter at redhat.com (David Lutterkort) Date: Wed, 27 Aug 2008 21:03:20 +0000 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <48B5A50A.4030909@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> <48A97841.4090906@redhat.com> <1219710210.4728.109.camel@localhost.localdomain> <48B40F28.10005@redhat.com> <1219861722.4728.247.camel@localhost.localdomain> <48B5A50A.4030909@redhat.com> Message-ID: <1219871000.4728.268.camel@localhost.localdomain> On Wed, 2008-08-27 at 15:03 -0400, Perry N. Myers wrote: > David Lutterkort wrote: > > On Tue, 2008-08-26 at 10:11 -0400, Scott Seago wrote: > >> I'm assuming the path-based pool lookup is just an alternate method of > >> getting this from your API, as the id-based ones will all still work. I > >> just realized that full path-based lookup will only work for users that > >> have read permissions on the whole hierarchy. A user with lower-level > >> permissions only (i.e. only read permissions for pools under > >> '/default/engineering/qa' and write permissions for subpools below that) > >> won't even see the top level pool. > > > > I think that permissioning scheme is fundamentally flawed; at the very > > least, any user that has permission on some pool should at least be > > allowed to know about the existence of pools above "their" pools - they > > may not be able to view any info about them, but at the very least, they > > should know that they are there. > > Not necessarily. Consider the cloud computing model... The admins might > know about the fact that there are hardware pools, but should a user of a > VM even know that there is such a thing as a hardware pool? To them the > hardware pools should be completely hidden in the UI, including the tree view. Those are two separate things: whether the UI should show those pools or not is separate from whether they are allowed to know that they exist. If the UI does not show the HW pools, what does a user see if they have permissions on two completely separate VM pools ? Do they appear as separate root pools to them ? What if both pools are called 'mypool' ? How would a user in that world tell an admin that they need to do something in the user's VmPool ? Does the admin need to manually keep track of where the 'mypool' VmPool is for user X and for user Y (which might be completely different) How should user X address their VmPool through the API ? If they can't use an absolute path to the pool, do they need to divine the internal ID of the pool ? Smart pools might be able to help, but we really need a unique user-visible name for each pool; whether and when the UI will show that name is a completely separate issue. David From lutter at redhat.com Wed Aug 27 21:04:14 2008 From: lutter at redhat.com (David Lutterkort) Date: Wed, 27 Aug 2008 21:04:14 +0000 Subject: [Ovirt-devel] OVirt API for hosts and hardware/storage pools In-Reply-To: <1218757432-30330-1-git-send-email-dlutter@redhat.com> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> Message-ID: <1219871054.4728.270.camel@localhost.localdomain> On Thu, 2008-08-14 at 16:43 -0700, David Lutterkort wrote: > This is a revamp of the API I posted previously. The most important change > is that the REST specific controllers have been folded into the existing > controllers, so that the WUI and the API share the same business logic, and > in particular perform the same permission checks. I just committed this, with a small change to HardwarePool.find_by_path to avoid hardcoding the root pool's name. David From jguiditt at redhat.com Wed Aug 27 21:11:33 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Wed, 27 Aug 2008 17:11:33 -0400 Subject: [Ovirt-devel] [PATCH] Sprint 6 - remove svg bar graphs, replace with css. Message-ID: <1219871493-12887-1-git-send-email-jguiditt@redhat.com> NOTE: This is only for graphs in a flexigrid, as they did not have the arrow, which will be more problematic if not impossible to do with css (though maybe with js). Load average for host is based on 5 (apparently this is some agreed-upon temporary scale). There is no real load for vm pools in the database, so these are stubbed out for now. Signed-off-by: Jason Guiditta --- wui/src/app/controllers/hardware_controller.rb | 2 +- wui/src/app/views/host/_grid.rhtml | 6 +++++- wui/src/app/views/resources/_grid.rhtml | 7 ++++++- wui/src/public/javascripts/ovirt.js | 7 +++++++ wui/src/public/stylesheets/components.css | 6 ++++++ wui/src/public/stylesheets/flexigrid/flexigrid.css | 2 +- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index 019fdd8..5d6f07b 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -139,7 +139,7 @@ class HardwareController < ApplicationController attr_list << :id if params[:checkboxes] attr_list << :hostname attr_list << [:hardware_pool, :name] if include_pool - attr_list += [:uuid, :hypervisor_type, :num_cpus, :cpu_speed, :arch, :memory_in_mb, :status_str, :id] + attr_list += [:uuid, :hypervisor_type, :num_cpus, :cpu_speed, :arch, :memory_in_mb, :status_str, :load_average] json_list(hosts, attr_list, [:all], find_opts) end diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml index ac24e0c..553adf9 100644 --- a/wui/src/app/views/host/_grid.rhtml +++ b/wui/src/app/views/host/_grid.rhtml @@ -54,7 +54,11 @@ } function <%= table_id %>_load_widget(celDiv) { - load_widget(celDiv, "host"); + var loadAvg = getAverage($(celDiv).html()); + var loadCss = (loadAvg >50) ? "load_graph_high" : "load_graph_low"; + $(celDiv).html('
'); }; diff --git a/wui/src/app/views/resources/_grid.rhtml b/wui/src/app/views/resources/_grid.rhtml index 301a7aa..908eac7 100644 --- a/wui/src/app/views/resources/_grid.rhtml +++ b/wui/src/app/views/resources/_grid.rhtml @@ -35,7 +35,12 @@ } function <%= table_id %>_load_widget(celDiv) { - load_widget(celDiv, "resource"); + //FIXME: there is no real data save for vmpool load, so stub out for now + var loadAvg = getAverage(2.3); + var loadCss = (loadAvg >50) ? "load_graph_high" : "load_graph_low"; + $(celDiv).html('
'); }; diff --git a/wui/src/public/javascripts/ovirt.js b/wui/src/public/javascripts/ovirt.js index 829738f..3a0d83a 100644 --- a/wui/src/public/javascripts/ovirt.js +++ b/wui/src/public/javascripts/ovirt.js @@ -28,6 +28,13 @@ function validate_selected(selected_array, name) } } +function getAverage(val) { + if (isNaN(val)) return 0; + //FIXME: currently using a magic number of 5 which should be replaced + //with comething more meaningful. + return (val/5 < 1) ? ((val/5) * 100) : 100; +} + function add_hosts(url) { hosts= get_selected_checkboxes("addhosts_grid_form") diff --git a/wui/src/public/stylesheets/components.css b/wui/src/public/stylesheets/components.css index 56cadf2..16eaf62 100644 --- a/wui/src/public/stylesheets/components.css +++ b/wui/src/public/stylesheets/components.css @@ -253,8 +253,14 @@ /* load graphs in flexigrid */ .load_graph { + padding: 0px !important; + height: 10px; + border:#F5AFA8 solid 1px; } +.load_graph_low {background: url(../images/chart_horiz_blue.png) repeat-x;} +.load_graph_high {background: url(../images/chart_horiz_orange.png) repeat-x;} + /************************* * details pane style * *************************/ diff --git a/wui/src/public/stylesheets/flexigrid/flexigrid.css b/wui/src/public/stylesheets/flexigrid/flexigrid.css index f8f8037..cd1902f 100644 --- a/wui/src/public/stylesheets/flexigrid/flexigrid.css +++ b/wui/src/public/stylesheets/flexigrid/flexigrid.css @@ -469,7 +469,7 @@ div.colCopy { .flexigrid div.bDiv td { border-bottom: 1px solid #fff; - vertical-align: top; + /*vertical-align: top;*/ white-space: nowrap; } -- 1.5.5.1 From mmorsi at redhat.com Wed Aug 27 21:16:55 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 27 Aug 2008 17:16:55 -0400 Subject: [Ovirt-devel] [PATCH] removed fixed environment from taskomatic, now can be configured via /etc/sysconfig/ovirt-rails Message-ID: <1219871815-26980-1-git-send-email-mmorsi@redhat.com> --- autobuild.sh | 1 - wui/src/dutils/active_record_env.rb | 2 +- 2 files changed, 1 insertions(+), 2 deletions(-) diff --git a/autobuild.sh b/autobuild.sh index 95395d0..385c80f 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -80,7 +80,6 @@ fi echo "Running the wui tests" $ssh_cmd \ "sed -i \"s/#RAILS_ENV=production/RAILS_ENV=test/g\" /etc/sysconfig/ovirt-rails && \ - sed -i \"s/development/test/\" /usr/share/ovirt-wui/dutils/active_record_env.rb && \ service ovirt-taskomatic restart && \ service ovirt-mongrel-rails restart && \ service httpd restart && \ diff --git a/wui/src/dutils/active_record_env.rb b/wui/src/dutils/active_record_env.rb index 9b7b416..a9412f8 100644 --- a/wui/src/dutils/active_record_env.rb +++ b/wui/src/dutils/active_record_env.rb @@ -40,7 +40,7 @@ require "#{OVIRT_DIR}/vendor/plugins/acts_as_xapian/lib/acts_as_xapian" def database_connect $dbconfig = YAML::load(ERB.new(IO.read("#{OVIRT_DIR}/config/database.yml")).result) - $develdb = $dbconfig['development'] + $develdb = $dbconfig[ENV['RAILS_ENV']] ActiveRecord::Base.establish_connection( :adapter => $develdb['adapter'], :host => $develdb['host'], -- 1.5.4.1 From mmorsi at redhat.com Wed Aug 27 21:19:12 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 27 Aug 2008 17:19:12 -0400 Subject: [Ovirt-devel] Re: [PATCH] vm creation / start / stop tests via selenium In-Reply-To: <1219788982.4728.183.camel@localhost.localdomain> References: <1219785949-3389-1-git-send-email-mmorsi@redhat.com> <1219788982.4728.183.camel@localhost.localdomain> Message-ID: <48B5C4D0.7080900@redhat.com> David Lutterkort wrote: > On Tue, 2008-08-26 at 17:25 -0400, Mohammed Morsi wrote: > >> --- >> autobuild.sh | 5 +- >> wui/src/test/fixtures/cpus.yml | 68 ++++++++++++++++---- >> wui/src/test/fixtures/hosts.yml | 10 +++ >> wui/src/test/fixtures/nics.yml | 13 +++- >> wui/src/test/fixtures/quotas.yml | 8 +- >> wui/src/test/fixtures/storage_pools.yml | 9 +++ >> wui/src/test/functional/interface_test.rb | 98 +++++++++++++++++++++++++++- >> wui/src/test/unit/cpu_test.rb | 2 + >> 8 files changed, 189 insertions(+), 24 deletions(-) >> > > ACK. Some small nits: > Committed. > >> diff --git a/autobuild.sh b/autobuild.sh >> index 6c95cb1..95395d0 100755 >> --- a/autobuild.sh >> +++ b/autobuild.sh >> @@ -80,7 +80,10 @@ fi >> echo "Running the wui tests" >> $ssh_cmd \ >> "sed -i \"s/#RAILS_ENV=production/RAILS_ENV=test/g\" /etc/sysconfig/ovirt-rails && \ >> - service ovirt-mongrel-rails restart && service httpd restart && \ >> + sed -i \"s/development/test/\" /usr/share/ovirt-wui/dutils/active_record_env.rb && \ >> > > Why is it necessary to patch active_record_env.rb ? Shouldn't it be > enough to set the RAILS_ENV environment variable ? > Just sent a follow up patch addressing this issue. > >> diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb >> index 6563b44..eadeb74 100644 >> --- a/wui/src/test/functional/interface_test.rb >> +++ b/wui/src/test/functional/interface_test.rb >> > > >> + # click the button >> + @browser.click "//form[@id='vm_form']/div[2]/div[2]/div[2]/a" >> > > Ultimately, we should fix up the templates/views so that the tests can > reference the elements they are really interested in by their id, > instead of depending on the exact structure of the HTML ... that will > become rather painful to maintain. > > If it works now, that's fine, but as soon as it breaks, the fix should > be to annotate the generated HTML, rather than adapt the tests all the > time. > Agreed, hopefully these tests will bring light to the fact that we should set the id attribute on more elements. This probably can be done as we go along from here on out, when writing a test for some components, make sure those have ids. > David > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From sakaia at jp.fujitsu.com Thu Aug 28 02:05:53 2008 From: sakaia at jp.fujitsu.com (Atsushi SAKAI) Date: Thu, 28 Aug 2008 11:05:53 +0900 Subject: [Ovirt-devel] Trouble on oVirt installation. Message-ID: <200808280205.m7S25rLd010097@fjmscan503.ms.jp.fujitsu.com> Hi. My Colleage try to install oVirt. oVirt installation itself seems done. But oVirt does not control a domain. Would you give me a advice? ================= I made the procedure from Chapter4 to 5.2.3 in http://ovirt.org/docs/Using_oVirt/ but I can't start a virtual machine. Error message "Action invalid for these VMs:" came out in the Virtual Machine Action Results display, when I click the Actions option on the toolbar and select start. Why is it happen? Would you give me a advice to starting a VM? ================= Thanks Atsushi SAKAI From slinabery at redhat.com Thu Aug 28 05:37:02 2008 From: slinabery at redhat.com (Steve Linabery) Date: Wed, 27 Aug 2008 23:37:02 -0600 Subject: [Ovirt-devel] [PATCH] resubmitting stats package updates Message-ID: <20080828053702.GM13694@redhat.com> These are the changes to the stats package files that I submitted previously on behalf of Mark Wagner (as part of a patch for review/comment). I believe these are pretty straightforward & would appreciate some ack-age on these, since the stuff I'm working on now depends on them. Thanks, Steve -------------- next part -------------- >From 417cd9ff0f24f1abf6f2c1349bea1c7e6b1244a5 Mon Sep 17 00:00:00 2001 From: Steve Linabery Date: Wed, 27 Aug 2008 21:02:30 -0500 Subject: [PATCH] Add changes to stats package components On behalf of Mark Wagner --- wui/src/app/util/stats/Stats.rb | 77 +++++++++++++++++++++++++++---- wui/src/app/util/stats/StatsDataList.rb | 74 +++++++++++++++++++---------- wui/src/app/util/stats/statsTest.rb | 50 ++++++++++++-------- 3 files changed, 146 insertions(+), 55 deletions(-) diff --git a/wui/src/app/util/stats/Stats.rb b/wui/src/app/util/stats/Stats.rb index f6ced4b..2e2e966 100644 --- a/wui/src/app/util/stats/Stats.rb +++ b/wui/src/app/util/stats/Stats.rb @@ -29,6 +29,8 @@ require 'util/stats/StatsRequest' def fetchRollingAve?(rrdPath, start, endTime, interval, myFunction, lIndex, returnList, aveLen=7) final = 0 + my_min = 0 + my_max = 0 # OK, first thing we need to do is to move the start time back in order to # have data to average. @@ -55,7 +57,6 @@ def fetchRollingAve?(rrdPath, start, endTime, interval, myFunction, lIndex, retu value = 0 value = vdata[lIndex] value = 0 if value.nan? - roll.push(value) if ( i >= aveLen) @@ -65,19 +66,34 @@ def fetchRollingAve?(rrdPath, start, endTime, interval, myFunction, lIndex, retu final += rdata end final = (final / aveLen ) + + # Determine min / max to help with autoscale. + if ( final > my_max ) + my_max = final + end + if ( final < my_min ) + my_min = final + end returnList.append_data( StatsData.new(fstart + interval * ( i - indexOffset), final )) # Now shift the head off the array roll.shift end end - + + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) + return returnList end def fetchRollingCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIndex, returnList, aveLen=7) + my_min = 0 + my_max = 0 + # OK, first thing we need to do is to move the start time back in order to have data to average. indexOffset = ( aveLen / 2 ).to_i @@ -120,12 +136,24 @@ def fetchRollingCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIn final += rdata end final = (final / aveLen) + + # Determine min / max to help with autoscale. + if ( final > my_max ) + my_max = final + end + if ( final < my_min ) + my_min = final + end returnList.append_data( StatsData.new(fstart + interval * ( i - indexOffset), final )) # Now shift the head off the array roll.shift end end + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) + return returnList end @@ -137,6 +165,9 @@ def fetchCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIndex, re # We also need to handle NaN differently # Finally, we need to switch Min and Max + my_min = 0 + my_max = 0 + lFunc = "AVERAGE" case myFunction when "MAX" @@ -155,13 +186,26 @@ def fetchCalcUsedData?(rrdPath, start, endTime, interval, myFunction, lIndex, re data.each do |vdata| i += 1 value = vdata[lIndex] - value = 100 if value.nan? - if ( value > 100 ) - value = 100 - end - value = 100 - value + value = 100 if value.nan? + if ( value > 100 ) + value = 100 + end + value = 100 - value + + # Determine min / max to help with autoscale. + if ( value > my_max ) + my_max = value + end + if ( value < my_min ) + my_min = value + end + returnList.append_data( StatsData.new(fstart + interval * i, value )) end + + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) return returnList end @@ -169,6 +213,9 @@ end def fetchRegData?(rrdPath, start, endTime, interval, myFunction, lIndex, returnList) + my_min = 0 + my_max = 0 + (fstart, fend, names, data, interval) = RRD.fetch(rrdPath, "--start", start.to_s, "--end", \ endTime.to_s, myFunction, "-r", interval.to_s) i = 0 @@ -177,9 +224,21 @@ def fetchRegData?(rrdPath, start, endTime, interval, myFunction, lIndex, returnL # Now, lets walk the returned data and create the ojects, and put them in a list. data.each do |vdata| + value = vdata[lIndex] i += 1 - returnList.append_data( StatsData.new(fstart + interval * i, vdata[lIndex] )) + if ( value > my_max ) + my_max = value + end + if ( value < my_min ) + my_min = value + end + + returnList.append_data( StatsData.new(fstart + interval * i, value )) end + + # Now add the min / max to the lists + returnList.set_min_value(my_min) + returnList.set_max_value(my_max) return returnList end @@ -294,7 +353,7 @@ def getStatsData?(statRequestList) counter = request.get_counter? tmpList =fetchData?(request.get_node?, request.get_devClass?,request.get_instance?, request.get_counter?, \ request.get_starttime?, request.get_duration?,request.get_precision?, request.get_function?) - + # Now copy the array returned into the main array myList << tmpList end diff --git a/wui/src/app/util/stats/StatsDataList.rb b/wui/src/app/util/stats/StatsDataList.rb index d6de29c..0944cbc 100644 --- a/wui/src/app/util/stats/StatsDataList.rb +++ b/wui/src/app/util/stats/StatsDataList.rb @@ -21,7 +21,7 @@ #define class StatsData List class StatsDataList def initialize(node,devClass,instance, counter, status, function) - # Instance variables + # Instance variables @node = node @devClass = devClass @instance = instance @@ -29,41 +29,63 @@ class StatsDataList @data=[] @status = status @function = function - end + @min_value = 0 + @max_value = 0 + end - def get_node?() + def get_node?() return @node - end + end - def get_devClass?() + def get_node?() + return @node + end + + def get_devClass?() return @devClass - end - - def get_instance?() + end + + def get_instance?() return @instance - end - - def get_counter?() + end + + def get_counter?() return @counter - end - - def get_data?() + end + + def get_data?() return @data - end - - def get_status?() + end + + def get_status?() return @status - end - - def get_function?() + end + + def get_function?() return @function - end - - def append_data(incoming) + end + + def append_data(incoming) @data << incoming - end - + end + def length() return @data.length end -end + + def set_min_value(min) + @min_value = min + end + + def set_max_value(max) + @max_value = max + end + + def get_min_value?() + return @min_value + end + + def get_max_value?() + return @max_value + end +end diff --git a/wui/src/app/util/stats/statsTest.rb b/wui/src/app/util/stats/statsTest.rb index baedbc0..1da370c 100644 --- a/wui/src/app/util/stats/statsTest.rb +++ b/wui/src/app/util/stats/statsTest.rb @@ -33,11 +33,20 @@ require 'util/stats/Stats' # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::Load, 0, LoadCounter::Load_15min, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node7.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_rx, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::NIC, 1, NicCounter::Octets_rx, 0, 0, RRDResolution::Long ) - requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Medium ) +# requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Long, DataFunction::Average ) +# requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Long, DataFunction::Peak ) +# requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_tx, 0, 604800, RRDResolution::Long) # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::Disk, 0, DiskCounter::Octets_read, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::Disk, 0, DiskCounter::Octets_write, 0, 0, RRDResolution::Long ) # requestList << StatsRequest.new("node3.priv.ovirt.org", "cpu", 0, "idle", 1211688000, 3600, 10 ) -# requestList << StatsRequest.new("node4.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 3600, RRDResolution::Short ) + + requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::CalcUsed, 0, 300, RRDResolution::Default, DataFunction::Average ) + requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::NIC, 0, NicCounter::Octets_rx, 0, 0, RRDResolution::Default ) +# requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 300, RRDResolution::Default, DataFunction::RollingAverage ) +# requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 300, RRDResolution::Default, DataFunction::Average ) + requestList << StatsRequest.new("node3.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::CalcUsed, 0, 300, RRDResolution::Default, DataFunction::RollingAverage ) +# requestList << StatsRequest.new("node4.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::Idle, 0, 3600, RRDResolution::Short, DataFunction::Average ) +# requestList << StatsRequest.new("node4.priv.ovirt.org", DevClass::CPU, 0, CpuCounter::CalcUsed, 0, 3600, RRDResolution::Short, DataFunction::Min ) # requestList << StatsRequest.new("node5.priv.ovirt.org", "cpu", 0, "idle", 1211688000, 3600, 500 ) # requestList << StatsRequest.new("node5.priv.ovirt.org", DevClass::Memory, 0, MemCounter::Used, 0, 3600, 10 ) @@ -52,27 +61,28 @@ require 'util/stats/Stats' # puts statsListBig.length statsListBig.each do |statsList| - myNodeName = statsList.get_node?() - myDevClass = statsList.get_devClass?() - myInstance = statsList.get_instance?() - myCounter = statsList.get_counter?() - myStatus = statsList.get_status?() + myNodeName = statsList.get_node?() + myDevClass = statsList.get_devClass?() + myInstance = statsList.get_instance?() + myCounter = statsList.get_counter?() + myStatus = statsList.get_status?() - case myStatus - when StatsStatus::E_NOSUCHNODE - puts "Can't find data for node " + myNodeName - when StatsStatus::E_UNKNOWN - puts "Can't find data for requested file path" - end - if tmp != myNodeName then + case myStatus + when StatsStatus::E_NOSUCHNODE + puts "Can't find data for node " + myNodeName + when StatsStatus::E_UNKNOWN + puts "Can't find data for requested file path" + end + if tmp != myNodeName then + puts + end + list = statsList.get_data?() + list.each do |d| + print("\t", myNodeName, "\t", myDevClass, "\t", myInstance, "\t", myCounter, "\t",d.get_value?, "\t",d.get_timestamp?) puts end - list = statsList.get_data?() - list.each do |d| - print("\t", myNodeName, "\t", myDevClass, "\t", myInstance, "\t", myCounter, "\t",d.get_value?, "\t",d.get_timestamp?) puts - end tmp = myNodeName + print("\tmin_value is: ", statsList.get_min_value?(), "\tmax_value is: ", statsList.get_max_value?()) + puts end - - -- 1.5.5.1 From clalance at redhat.com Thu Aug 28 08:08:16 2008 From: clalance at redhat.com (Chris Lalancette) Date: Thu, 28 Aug 2008 10:08:16 +0200 Subject: [Ovirt-devel] [PATCH]: Rearrange the ovirt-managed-node subdir In-Reply-To: <48AE9FC5.8000702@redhat.com> References: <48AE9FC5.8000702@redhat.com> Message-ID: <48B65CF0.6040002@redhat.com> Chris Lalancette wrote: > All, > The attached diffstat shows the rearrangement I want to make to the > ovirt-managed-node subdirectory. This makes the layout a little more sane, and > is the preparation work for the code that I will add later on for having a WUI > appliance manage the hardware it is running on. I didn't include the full diff > because it is uninteresting; it is just moving things around and fixing > Makefiles and spec files to compensate. I'm mostly looking for confirmation > from mcpierce that this is acceptable to him, since it will affect him the most. FYI; I've now pushed this change, so if you have outstanding patches against either the ovirt-managed-node scripts or the ovirt-identify-node source, you'll have to rebase them. Chris Lalancette From clalance at redhat.com Thu Aug 28 15:20:06 2008 From: clalance at redhat.com (Chris Lalancette) Date: Thu, 28 Aug 2008 17:20:06 +0200 Subject: [Ovirt-devel] VM Installation problem (and proposed solution) Message-ID: <48B6C226.80601@redhat.com> Hello all (Ian especially), apevec pointed out a problem with installation of guests under oVirt. What currently happens is that after you finish the installation of (say) Fedora in a VM, the VM reboots, but then immediately PXE boots again. This is because we haven't killed the guest and re-defined the XML to have the boot device be the hard drive, like it should. Now, we could solve this by having a flag in the database for "install", setting "on_restart" = "destroy", and then creating the domain. Once the install finished, the guest would die, host-status would detect this, and then restart the guest with the hard drive as the boot device. This would be similar to how virt-install manages the problem. However, that is kind of hacky, and seems a little fragile since we are then going to depend on host-status to update the status and restart the guest. The better way to do this is to have the remote node take care of the details. That is, with the messaging stuff, we would have an "install_vm" remote call; then this entire operation would happen over on the node in question, and we wouldn't have to have hacky model changes to support it. First, I wanted to be sure this was an approach people agreed with (or if they had other ideas how to approach it). Second, I wanted to make Ian aware that this is one of the "ovirt-specific" methods that we are going to need in the AMQP model, once that is in place. Chris Lalancette From berrange at redhat.com Thu Aug 28 15:26:39 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 28 Aug 2008 16:26:39 +0100 Subject: [Ovirt-devel] VM Installation problem (and proposed solution) In-Reply-To: <48B6C226.80601@redhat.com> References: <48B6C226.80601@redhat.com> Message-ID: <20080828152639.GD26373@redhat.com> On Thu, Aug 28, 2008 at 05:20:06PM +0200, Chris Lalancette wrote: > Hello all (Ian especially), > apevec pointed out a problem with installation of guests under oVirt. What > currently happens is that after you finish the installation of (say) Fedora in a > VM, the VM reboots, but then immediately PXE boots again. This is because we > haven't killed the guest and re-defined the XML to have the boot device be the > hard drive, like it should. You don't have to wait for installation to finish before re-defining the XML with hard drive as the boot device You can define the post-install XML config the moment the guest has booted. When it shuts down, libvirt will automatically switch over to the newly defined config. This is how virt-install handles it. 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 pmyers at redhat.com Thu Aug 28 18:45:25 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 28 Aug 2008 14:45:25 -0400 Subject: [Ovirt-devel] Trouble on oVirt installation. In-Reply-To: <200808280205.m7S25rLd010097@fjmscan503.ms.jp.fujitsu.com> References: <200808280205.m7S25rLd010097@fjmscan503.ms.jp.fujitsu.com> Message-ID: <48B6F245.3000109@redhat.com> Atsushi SAKAI wrote: > Hi. > > My Colleage try to install oVirt. > oVirt installation itself seems done. > But oVirt does not control a domain. > Would you give me a advice? > > ================= > I made the procedure from Chapter4 to 5.2.3 in > http://ovirt.org/docs/Using_oVirt/ > but I can't start a virtual machine. > > Error message "Action invalid for these VMs:" came out in the Virtual > Machine Action Results display, > when I click the Actions option on the toolbar and select start. > > Why is it happen? > Would you give me a advice to starting a VM? > ================= Hello. The VM could fail to start for a few reasons. It will fail to start if there are not enough resources on a running oVirt Node to host the VM. So... How big is the VM you are trying to start (RAM, vCPUs) How many oVirt Nodes do you have running and how much RAM/CPU does each Node have? Are the oVirt Nodes in the hardware pool that the VM Pool is a child of? (are the nodes in default hardware pool, and your vms are in a subpool of default?) If it's not a resource problem, can you provide me with the log files in /var/log/ovirt-wui on the appliance server? Thanks! Perry From dpierce at redhat.com Thu Aug 28 19:48:19 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 28 Aug 2008 15:48:19 -0400 Subject: [Ovirt-devel] [PATCH] Added the current date/time stamp to the host-browser log. Message-ID: <1219952899-23188-1-git-send-email-dpierce@redhat.com> The format of the log is "MMM DD HH:MM:SS [ip address] " for all log entries. Signed-off-by: Darryl L. Pierce --- wui/src/host-browser/host-browser.rb | 21 ++++++++++++--------- 1 files changed, 12 insertions(+), 9 deletions(-) diff --git a/wui/src/host-browser/host-browser.rb b/wui/src/host-browser/host-browser.rb index 881b2ae..5286b1b 100755 --- a/wui/src/host-browser/host-browser.rb +++ b/wui/src/host-browser/host-browser.rb @@ -45,14 +45,17 @@ class HostBrowser def initialize(session) @session = session - @log_prefix = "[#{session.peeraddr[3]}] " @keytab_dir = '/usr/share/ipa/html/' end + + def prefix(session) + "#{Time.now.strftime('%b %d %H:%M:%S')} #{session.peeraddr[3]} " + end # Ensures the conversation starts properly. # def begin_conversation - puts "#{@log_prefix} Begin conversation" unless defined?(TESTING) + puts "#{prefix(@session)} Begin conversation" unless defined?(TESTING) @session.write("HELLO?\n") response = @session.readline.chomp @@ -62,10 +65,10 @@ class HostBrowser # Retrieves the mode request from the remote system. # def get_mode - puts "#{@log_prefix} Determining the runtime mode." unless defined?(TESTING) + puts "#{prefix(@session)} Determining the runtime mode." unless defined?(TESTING) @session.write("MODE?\n") response = @session.readline.chomp - puts "#{@log_prefix} MODE=#{response}" unless defined?(TESTING) + puts "#{prefix(@session)} MODE=#{response}" unless defined?(TESTING) response end @@ -73,7 +76,7 @@ class HostBrowser # Requests node information from the remote system. # def get_remote_info - puts "#{@log_prefix} Begin remote info collection" unless defined?(TESTING) + puts "#{prefix(@session)} Begin remote info collection" unless defined?(TESTING) result = Hash.new result['HOSTNAME'] = @session.peeraddr[2] result['IPADDR'] = @session.peeraddr[3] @@ -114,7 +117,7 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value @session.write("ACK #{key}\n") @@ -142,7 +145,7 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value @session.write("ACK #{key}\n") @@ -171,7 +174,7 @@ class HostBrowser key, value = info.split("=") - puts "#{@log_prefix} ::Received - #{key}:#{value}" unless defined?(TESTING) + puts "#{prefix(@session)} ::Received - #{key}:#{value}" unless defined?(TESTING) result[key] = value @session.write("ACK #{key}\n") @@ -331,7 +334,7 @@ class HostBrowser # Ends the conversation, notifying the user of the key version number. # def end_conversation - puts "#{@log_prefix} Ending conversation" unless defined?(TESTING) + puts "#{prefix(@session)} Ending conversation" unless defined?(TESTING) @session.write("BYE\n"); end -- 1.5.5.1 From pmyers at redhat.com Thu Aug 28 20:16:30 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 28 Aug 2008 16:16:30 -0400 Subject: [Ovirt-devel] VM Installation problem (and proposed solution) In-Reply-To: <20080828152639.GD26373@redhat.com> References: <48B6C226.80601@redhat.com> <20080828152639.GD26373@redhat.com> Message-ID: <48B7079E.6050203@redhat.com> Daniel P. Berrange wrote: > On Thu, Aug 28, 2008 at 05:20:06PM +0200, Chris Lalancette wrote: >> Hello all (Ian especially), >> apevec pointed out a problem with installation of guests under oVirt. What >> currently happens is that after you finish the installation of (say) Fedora in a >> VM, the VM reboots, but then immediately PXE boots again. This is because we >> haven't killed the guest and re-defined the XML to have the boot device be the >> hard drive, like it should. > > You don't have to wait for installation to finish before re-defining the > XML with hard drive as the boot device > > You can define the post-install XML config the moment the guest has booted. > When it shuts down, libvirt will automatically switch over to the newly > defined config. This is how virt-install handles it. > > Daniel If the guest does a soft reboot does the libvirt configuration get reloaded at all? (I would think not) In this case the process would look like the following: 1. Message sent to ovirtd on Node saying 'install_vm' 2. ovirtd defines vm to boot PXE/ISO or whatever and sets on reboot to destroy 3. ovirtd starts vm 4. ovirtd redefines vm to boot hd and turns off destroy on reboot 5. When guest soft reboots, the vm is destroyed causing the new libvirt configuration to be loaded when it is started next 6. ovirtd restarts the domain when it detects the domain has been destroyed Does that sound reasonable? Perry -- |=- Red Hat, Engineering, Emerging Technologies, Boston -=| |=- Email: pmyers at redhat.com -=| |=- Office: +1 412 474 3552 Mobile: +1 703 362 9622 -=| |=- GnuPG: E65E4F3D 88F9 F1C9 C2F3 1303 01FE 817C C5D2 8B91 E65E 4F3D -=| From berrange at redhat.com Thu Aug 28 20:19:50 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 28 Aug 2008 21:19:50 +0100 Subject: [Ovirt-devel] VM Installation problem (and proposed solution) In-Reply-To: <48B7079E.6050203@redhat.com> References: <48B6C226.80601@redhat.com> <20080828152639.GD26373@redhat.com> <48B7079E.6050203@redhat.com> Message-ID: <20080828201950.GE19746@redhat.com> On Thu, Aug 28, 2008 at 04:16:30PM -0400, Perry N. Myers wrote: > > > Daniel P. Berrange wrote: > >On Thu, Aug 28, 2008 at 05:20:06PM +0200, Chris Lalancette wrote: > >>Hello all (Ian especially), > >> apevec pointed out a problem with installation of guests under > >> oVirt. What > >>currently happens is that after you finish the installation of (say) > >>Fedora in a > >>VM, the VM reboots, but then immediately PXE boots again. This is > >>because we > >>haven't killed the guest and re-defined the XML to have the boot device > >>be the > >>hard drive, like it should. > > > >You don't have to wait for installation to finish before re-defining the > >XML with hard drive as the boot device > > > >You can define the post-install XML config the moment the guest has booted. > >When it shuts down, libvirt will automatically switch over to the newly > >defined config. This is how virt-install handles it. > > > > If the guest does a soft reboot does the libvirt configuration get > reloaded at all? (I would think not) In this case the process would look > like the following: That depends on the 'on_reboot' setting in libvirt XML - you can either allow it to be a soft-reboot or force the poweroff. Typically we do the latter, to guarentee that the second boot uses the new XML. > 1. Message sent to ovirtd on Node saying 'install_vm' > 2. ovirtd defines vm to boot PXE/ISO or whatever and sets on reboot to > destroy > 3. ovirtd starts vm > 4. ovirtd redefines vm to boot hd and turns off destroy on reboot > 5. When guest soft reboots, the vm is destroyed causing the new libvirt > configuration to be loaded when it is started next > 6. ovirtd restarts the domain when it detects the domain has been > destroyed > > Does that sound reasonable? Yes, that's the right approach. 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 pmyers at redhat.com Thu Aug 28 20:50:34 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 28 Aug 2008 16:50:34 -0400 Subject: [Ovirt-devel] [PATCH 4/6] hardware_pool: search by path In-Reply-To: <1219871000.4728.268.camel@localhost.localdomain> References: <1218757432-30330-1-git-send-email-dlutter@redhat.com> <1218757432-30330-5-git-send-email-dlutter@redhat.com> <48A97841.4090906@redhat.com> <1219710210.4728.109.camel@localhost.localdomain> <48B40F28.10005@redhat.com> <1219861722.4728.247.camel@localhost.localdomain> <48B5A50A.4030909@redhat.com> <1219871000.4728.268.camel@localhost.localdomain> Message-ID: <48B70F9A.7070306@redhat.com> David Lutterkort wrote: > On Wed, 2008-08-27 at 15:03 -0400, Perry N. Myers wrote: >> David Lutterkort wrote: >>> On Tue, 2008-08-26 at 10:11 -0400, Scott Seago wrote: >>>> I'm assuming the path-based pool lookup is just an alternate method of >>>> getting this from your API, as the id-based ones will all still work. I >>>> just realized that full path-based lookup will only work for users that >>>> have read permissions on the whole hierarchy. A user with lower-level >>>> permissions only (i.e. only read permissions for pools under >>>> '/default/engineering/qa' and write permissions for subpools below that) >>>> won't even see the top level pool. >>> I think that permissioning scheme is fundamentally flawed; at the very >>> least, any user that has permission on some pool should at least be >>> allowed to know about the existence of pools above "their" pools - they >>> may not be able to view any info about them, but at the very least, they >>> should know that they are there. >> Not necessarily. Consider the cloud computing model... The admins might >> know about the fact that there are hardware pools, but should a user of a >> VM even know that there is such a thing as a hardware pool? To them the >> hardware pools should be completely hidden in the UI, including the tree view. > > Those are two separate things: whether the UI should show those pools or > not is separate from whether they are allowed to know that they exist. Agreed, but I'm stating that in some environments we will want both of those things. i.e. the UI should hide the pools from the user and the user should not even be aware of their existence > If the UI does not show the HW pools, what does a user see if they have > permissions on two completely separate VM pools ? Do they appear as > separate root pools to them ? What if both pools are called 'mypool' ? I would think that they would appear as multiple roots. And if the names are the same is that a problem? Sure it makes it confusing for the person to identify which pool is which, but they can always rename them to mypool1 and mypool2. > How would a user in that world tell an admin that they need to do > something in the user's VmPool ? Does the admin need to manually keep > track of where the 'mypool' VmPool is for user X and for user Y (which > might be completely different) The admin knows where the pools are, since they see the full tree. So the user might see: mypool1 mypool2 The admin would see default hwpool1 mypool1 hwpool2 mypool2 As for the user telling the admin to do something, we should probably have links on a pool page that provide a way to contact the parent pool's admin person(s). > How should user X address their VmPool through the API ? If they can't > use an absolute path to the pool, do they need to divine the internal ID > of the pool ? Yes, unless we want to enforce that pool names must be unique (which I don't think we want to do). So we should provide a way in the API to reference a pool by hierarchy name and a way to refer to it by some sort of UUID. > Smart pools might be able to help, but we really need a unique > user-visible name for each pool; whether and when the UI will show that > name is a completely separate issue. Yeah, I'm not sure how smart pools factor into this... We need to discuss more. Perry From dpierce at redhat.com Thu Aug 28 21:35:42 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 28 Aug 2008 17:35:42 -0400 Subject: [Ovirt-devel] [PATCH] Fixed a bug that caused ethtool_cmd to be accessed while uninitialized. Message-ID: <1219959342-28257-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- ovirt-managed-node/ovirt-identify-node/gather.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/ovirt-managed-node/ovirt-identify-node/gather.c b/ovirt-managed-node/ovirt-identify-node/gather.c index 39be6fd..a513b75 100644 --- a/ovirt-managed-node/ovirt-identify-node/gather.c +++ b/ovirt-managed-node/ovirt-identify-node/gather.c @@ -214,6 +214,7 @@ get_nic_data(char *nic, nic_info_ptr nic_info) ifr.ifr_addr.sa_family = AF_INET; strncpy(ifr.ifr_name, interface, IFNAMSIZ - 1); + ifr.ifr_data = (caddr_t)&ecmd; ioctl(sockfd, SIOCETHTOOL, &ifr); close(sockfd); -- 1.5.5.1 From mwagner at redhat.com Fri Aug 29 00:11:40 2008 From: mwagner at redhat.com (Mark Wagner) Date: Thu, 28 Aug 2008 20:11:40 -0400 Subject: [Ovirt-devel] [PATCH] resubmitting stats package updates In-Reply-To: <20080828053702.GM13694@redhat.com> References: <20080828053702.GM13694@redhat.com> Message-ID: <48B73EBC.4000900@redhat.com> Steve Linabery wrote: > These are the changes to the stats package files that I submitted previously on behalf of Mark Wagner (as part of a patch for review/comment). I believe these are pretty straightforward & would appreciate some ack-age on these, since the stuff I'm working on now depends on them. > > Thanks, > Steve > > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel Steve ACK but not sure if its legal since its my code that you are submitting. However, if I can't ACK it then you should be able to providing you haven't changed the code, just tested it (extensively). -mark From terashima.naoya at np.css.fujitsu.com Fri Aug 29 00:33:51 2008 From: terashima.naoya at np.css.fujitsu.com (Naoya Terashima) Date: Fri, 29 Aug 2008 09:33:51 +0900 Subject: [Ovirt-devel] Trouble on oVirt installation. Message-ID: <006301c9096e$e9257b10$b16f210a@FMAFC020FC868C> Hi Perry Thank you for your reply. >How big is the VM you are trying to start (RAM, vCPUs) The VM I'm trying to start has 502MB of memory and 1 vCPU. >How many oVirt Nodes do you have running and how much RAM/CPU does each Node have? I'm running three nodes and each one has 1 CPU and 502 MB of memory. >Are the oVirt Nodes in the hardware pool that the VM Pool is a child of? >(are the nodes in default hardware pool, and your vms are in a subpool of default?) I have three nodes running now.one is in the default hardware pool. The others are in another hardware pools under defult hardware pool. The VM pool I made is a child of the default hardwar pool. >If it's not a resource problem,can you provide me with the log files >in /var/log/ovirt-wui on the appliance server? I looked around for ovirt-wui file in the /var/log/ directory but I couldn't find it. I downloaded ovirt-developer-appliance-0.91-1-i386.tar and installed it not ovirt-appliance-0.92-1-i386.tar. Is it a problem? Thank you -------------- next part -------------- An HTML attachment was scrubbed... URL: From terashima.naoya at np.css.fujitsu.com Fri Aug 29 02:34:10 2008 From: terashima.naoya at np.css.fujitsu.com (Naoya Terashima) Date: Fri, 29 Aug 2008 11:34:10 +0900 Subject: [Ovirt-devel] Trouble on oVirt installation. Message-ID: <017d01c9097f$b8121860$b16f210a@FMAFC020FC868C> ----- Original Message ----- From: Naoya Terashima To: pmyers at redhat.com Cc: ovirt-devel at redhat.com Sent: Friday, August 29, 2008 11:28 AM Subject: Re: [Ovirt-devel] Trouble on oVirt installation. Sorry,I found the /var/log/ovirt-wui directoy on Admin Node. I attach them to this mail except rails.log. Thank you -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: taskomatic.log Type: application/octet-stream Size: 48762 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: host-browser.log Type: application/octet-stream Size: 23857 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: host-status.log Type: application/octet-stream Size: 1632 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: mongrel.log Type: application/octet-stream Size: 7777 bytes Desc: not available URL: From pmyers at redhat.com Fri Aug 29 02:57:42 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 28 Aug 2008 22:57:42 -0400 Subject: [Ovirt-devel] Trouble on oVirt installation. In-Reply-To: <006301c9096e$e9257b10$b16f210a@FMAFC020FC868C> References: <006301c9096e$e9257b10$b16f210a@FMAFC020FC868C> Message-ID: <48B765A6.9080702@redhat.com> Naoya Terashima wrote: > Hi Perry > Thank you for your reply. > > >How big is the VM you are trying to start (RAM, vCPUs) > The VM I'm trying to start has 502MB of memory and 1 vCPU. > > >How many oVirt Nodes do you have running and how much RAM/CPU does > each Node have? > I'm running three nodes and each one has 1 CPU and 502 MB of memory. Are they fake nodes running on the same host as the oVirt Appliance? Or are these real physical boxes? In either case, if the Node has 502 MB free, you can't use all of it for your virtual machines. You need to leave some for the hypervisor. We're estimating about 200MB right now, and working to decrease that number. So on a Node with 502MB of RAM, try creating a virtual machine with only 256MB of RAM and see if that succeeds. We need to change the 'RAM available' on the Node view in the WUI to account for the amount of RAM that the HV is using and deduct it from the total. This will help to get around this confusion. If you want real guests (not just toys) you need to use a physical host to run the oVirt Node with at about 2GB of memory. This will allow you to have guests with HW accelerated virt and enough memory to be useful. The fake nodes are not all that useful for running guests as they are fully emulated. > >Are the oVirt Nodes in the hardware pool that the VM Pool is a child of? > >(are the nodes in default hardware pool, and your vms are in a subpool > of default?) > I have three nodes running now.one is in the default hardware pool. > The others are in another hardware pools under defult hardware pool. > The VM pool I made is a child of the default hardwar pool. > > >If it's not a resource problem,can you provide me with the log files > >in /var/log/ovirt-wui on the appliance server? > I looked around for ovirt-wui file in the /var/log/ directory > but I couldn't find it. > I downloaded ovirt-developer-appliance-0.91-1-i386.tar and installed it > not ovirt-appliance-0.92-1-i386.tar. > Is it a problem? It shouldn't be a problem to use 91-1, but I definitely recommend downloading 92-1 and using that instead since it is much better :) Perry From pmyers at redhat.com Fri Aug 29 03:00:04 2008 From: pmyers at redhat.com (Perry N. Myers) Date: Thu, 28 Aug 2008 23:00:04 -0400 Subject: [Ovirt-devel] Trouble on oVirt installation. In-Reply-To: <017d01c9097f$b8121860$b16f210a@FMAFC020FC868C> References: <017d01c9097f$b8121860$b16f210a@FMAFC020FC868C> Message-ID: <48B76634.7030406@redhat.com> Naoya Terashima wrote: > > ----- Original Message ----- > *From:* Naoya Terashima > *To:* pmyers at redhat.com > *Cc:* ovirt-devel at redhat.com > *Sent:* Friday, August 29, 2008 11:28 AM > *Subject:* Re: [Ovirt-devel] Trouble on oVirt installation. > > Sorry,I found the /var/log/ovirt-wui directoy on Admin Node. > I attach them to this mail except rails.log. As I mentioned in my other email, it is indeed a memory problem. Here's what taskomatic log is saying when you try to create the VM: > Could not allocate physical memory > start_vm > Task action processing failed: Libvirt::Error: Call to function virDomainCreate failed > /usr/share/ovirt-wui/task-omatic/./task_vm.rb:332:in `create' > /usr/share/ovirt-wui/task-omatic/./task_vm.rb:332:in `start_vm' > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:100 > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:88:in `each' > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:88 > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:67:in `loop' > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:67 So try lowering your guest memory down to 256 and see if that works. Perry From terashima.naoya at np.css.fujitsu.com Fri Aug 29 04:03:21 2008 From: terashima.naoya at np.css.fujitsu.com (Naoya Terashima) Date: Fri, 29 Aug 2008 13:03:21 +0900 Subject: [Ovirt-devel] Trouble on oVirt installation. References: <017d01c9097f$b8121860$b16f210a@FMAFC020FC868C> <48B76634.7030406@redhat.com> Message-ID: <003f01c9098c$2d3c7ca0$b16f210a@FMAFC020FC868C> Thank you for your precious advice. The VM got running afer I lower the memory. Thank you very much. ----- Original Message ----- From: "Perry N. Myers" To: "Naoya Terashima" Cc: Sent: Friday, August 29, 2008 12:00 PM Subject: Re: [Ovirt-devel] Trouble on oVirt installation. > Naoya Terashima wrote: > > > > ----- Original Message ----- > > *From:* Naoya Terashima > > *To:* pmyers at redhat.com > > *Cc:* ovirt-devel at redhat.com > > *Sent:* Friday, August 29, 2008 11:28 AM > > *Subject:* Re: [Ovirt-devel] Trouble on oVirt installation. > > > > Sorry,I found the /var/log/ovirt-wui directoy on Admin Node. > > I attach them to this mail except rails.log. > > As I mentioned in my other email, it is indeed a memory problem. Here's > what taskomatic log is saying when you try to create the VM: > > > Could not allocate physical memory > > start_vm > > Task action processing failed: Libvirt::Error: Call to function virDomainCreate failed > > /usr/share/ovirt-wui/task-omatic/./task_vm.rb:332:in `create' > > /usr/share/ovirt-wui/task-omatic/./task_vm.rb:332:in `start_vm' > > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:100 > > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:88:in `each' > > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:88 > > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:67:in `loop' > > /usr/share/ovirt-wui/task-omatic/taskomatic.rb:67 > > So try lowering your guest memory down to 256 and see if that works. > > Perry From clalance at redhat.com Fri Aug 29 06:08:04 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 29 Aug 2008 08:08:04 +0200 Subject: [Ovirt-devel] VM Installation problem (and proposed solution) In-Reply-To: <20080828152639.GD26373@redhat.com> References: <48B6C226.80601@redhat.com> <20080828152639.GD26373@redhat.com> Message-ID: <48B79244.6090102@redhat.com> Daniel P. Berrange wrote: > On Thu, Aug 28, 2008 at 05:20:06PM +0200, Chris Lalancette wrote: >> Hello all (Ian especially), >> apevec pointed out a problem with installation of guests under oVirt. What >> currently happens is that after you finish the installation of (say) Fedora in a >> VM, the VM reboots, but then immediately PXE boots again. This is because we >> haven't killed the guest and re-defined the XML to have the boot device be the >> hard drive, like it should. > > You don't have to wait for installation to finish before re-defining the > XML with hard drive as the boot device > > You can define the post-install XML config the moment the guest has booted. > When it shuts down, libvirt will automatically switch over to the newly > defined config. This is how virt-install handles it. Yes, true. The difference is because of the asynchronous nature of taskomatic, we basically kick off the guest on the node, and then go on to do other things. The next time we would hear anything about this guest is when host-status eventually polled it and noticed it "shutoff" (even though we think it is running). So then we would need to perform the heuristic of "if this was install time, and the guest is now dead, restart it". Just doing all of this out on the node will reduce our round-trips to the server, and do the whole thing in a more timely fashion. That's why I think it is better to do this whole thing remotely on the node. Chris Lalancette From terashima.naoya at np.css.fujitsu.com Fri Aug 29 07:01:24 2008 From: terashima.naoya at np.css.fujitsu.com (Naoya Terashima) Date: Fri, 29 Aug 2008 16:01:24 +0900 Subject: [Ovirt-devel] Touble on updating oVirt Message-ID: <00da01c909a5$0cfa3db0$b16f210a@FMAFC020FC868C> Hi I have difficulty updating oVirt from 91-1 to 92-1. I did the instructions in http://ovirt.org/install-instructions.html and succeeded in creating the appliance. (I finished executing "./build-all.sh -a") and now I did #virsh start ovirt-appliance and #virt-viewer ovirt-appliance but the appliance didn't boot because of error such as written in attached file. Please teach me how to boot the appliance. I'm using oVirt in a one PC not network connected situation. Thank you. -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: Screenshot-ovirt-appliance - Virt Viewer.png Type: image/png Size: 78838 bytes Desc: not available URL: From terashima.naoya at np.css.fujitsu.com Fri Aug 29 10:59:41 2008 From: terashima.naoya at np.css.fujitsu.com (Naoya Terashima) Date: Fri, 29 Aug 2008 19:59:41 +0900 Subject: [Ovirt-devel] Trouble on loading the VM onto the node Message-ID: <018201c909c6$569db340$b16f210a@FMAFC020FC868C> I could get the VM running in the Virtual Machine Pool(on oVirt 0.91). As next step,I wanted to operate the VM on the fake node to see how it works but I couldn't it since the VM and the node are separated. How can I load the VM onto the node. I searched for the way in http://ovirt.org/ but I couldn't find it. If you know how or any instructive web page,please let me know. Thank you -------------- next part -------------- An HTML attachment was scrubbed... URL: From clalance at redhat.com Fri Aug 29 11:13:55 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 29 Aug 2008 13:13:55 +0200 Subject: [Ovirt-devel] Trouble on loading the VM onto the node In-Reply-To: <018201c909c6$569db340$b16f210a@FMAFC020FC868C> References: <018201c909c6$569db340$b16f210a@FMAFC020FC868C> Message-ID: <48B7D9F3.5080207@redhat.com> Naoya Terashima wrote: > I could get the VM running in the Virtual Machine Pool(on oVirt 0.91). > As next step,I wanted to operate the VM on the fake node to see how it works > but I couldn't it since the VM and the node are separated. > How can I load the VM onto the node. > I searched for the way in http://ovirt.org/ but I couldn't find it. > If you know how or any instructive web page,please let me know. You'll want to look at: http://ovirt.org/quick-tour.html to see how to get started. I'm not exactly sure what your problem is, but you don't generally load a VM onto a node; you just start a VM. The backend taskomatic process will then pick an appropriate node for your VM to start on, and start it there. Chris Lalancette From terashima.naoya at np.css.fujitsu.com Fri Aug 29 12:01:34 2008 From: terashima.naoya at np.css.fujitsu.com (Naoya Terashima) Date: Fri, 29 Aug 2008 21:01:34 +0900 Subject: [Ovirt-devel] Trouble on loading the VM onto the node References: <018201c909c6$569db340$b16f210a@FMAFC020FC868C> <48B7D9F3.5080207@redhat.com> Message-ID: <01c401c909ce$fbdb8370$b16f210a@FMAFC020FC868C> Hi Thank you for your reply. You mean, a VM is automatically loaded onto appropriate node? So, could you tell me how to confirm which node are picked. I coudn't confirm which node are chosen by the command #virsh start node3 #virsh start node4 #virsh start node5. I attach Screenshot.png which describe the situation. a VM named aaaa are running but seem separated from every node. I would appreciate it if you could give me an adivice about it. Thank you ----- Original Message ----- From: "Chris Lalancette" To: "Naoya Terashima" Cc: Sent: Friday, August 29, 2008 8:13 PM Subject: Re: [Ovirt-devel] Trouble on loading the VM onto the node > Naoya Terashima wrote: > > I could get the VM running in the Virtual Machine Pool(on oVirt 0.91). > > As next step,I wanted to operate the VM on the fake node to see how it works > > but I couldn't it since the VM and the node are separated. > > How can I load the VM onto the node. > > I searched for the way in http://ovirt.org/ but I couldn't find it. > > If you know how or any instructive web page,please let me know. > > You'll want to look at: http://ovirt.org/quick-tour.html to see how to get > started. I'm not exactly sure what your problem is, but you don't generally > load a VM onto a node; you just start a VM. The backend taskomatic process will > then pick an appropriate node for your VM to start on, and start it there. > > Chris Lalancette -------------- next part -------------- A non-text attachment was scrubbed... Name: Screenshot.png Type: image/png Size: 152317 bytes Desc: not available URL: From clalance at redhat.com Fri Aug 29 12:05:31 2008 From: clalance at redhat.com (Chris Lalancette) Date: Fri, 29 Aug 2008 14:05:31 +0200 Subject: [Ovirt-devel] Trouble on loading the VM onto the node In-Reply-To: <01c401c909ce$fbdb8370$b16f210a@FMAFC020FC868C> References: <018201c909c6$569db340$b16f210a@FMAFC020FC868C> <48B7D9F3.5080207@redhat.com> <01c401c909ce$fbdb8370$b16f210a@FMAFC020FC868C> Message-ID: <48B7E60B.5040309@redhat.com> Naoya Terashima wrote: > Hi > > Thank you for your reply. > You mean, a VM is automatically loaded onto appropriate node? > So, could you tell me how to confirm which node are picked. > I coudn't confirm which node are chosen by the command > > #virsh start node3 > #virsh start node4 > #virsh start node5. > > I attach Screenshot.png which describe the situation. > a VM named aaaa are running but seem separated from every node. > I would appreciate it if you could give me an adivice about it. OK, yes, everything seems to be up and running. What you have to realize is that node3, node4, and node5 are running as managed nodes; think of them as actual, physical hardware. That means that VM aaaa is actually running *within* one of those nodes; you have to look on the nodes themselves to find out which one it's running on. Chris Lalancette From hbrock at redhat.com Sun Aug 31 13:08:25 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Sun, 31 Aug 2008 09:08:25 -0400 Subject: [Ovirt-devel] [rich@annexia.org: Flash / haxe] Message-ID: <20080831130824.GD16710@redhat.com> ----- Forwarded message from Richard Jones ----- > Hugh: > > http://haxe.org/doc/intro > > Never heard of this before now, but this is some open source language > thing which can generate Flash (.swf) as in this rather cute example: > > http://blog.haxe.org/entry/33 > > I know zip about it apart from this, but thought it might be > interesting to investigate for oVirt. > > Update - well what do you know, written in OCaml :-) More on Wikipedia: > > http://en.wikipedia.org/wiki/HaXe > > Rich. > > -- > Richard Jones > Red Hat > ----- End forwarded message ----- Thought I would forward this on to the group. My understanding is that we already have some version of open tools for generating .swf, but it's always worth looking around. Steve? Take care, --Hugh From mmorsi at redhat.com Sun Aug 31 20:50:45 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Sun, 31 Aug 2008 16:50:45 -0400 Subject: [Ovirt-devel] [PATCH] Fixes to broken tests (1/2) Message-ID: <1220215846-28759-1-git-send-email-mmorsi@redhat.com> --- wui/src/test/functional/interface_test.rb | 18 +++++++++++------- 1 files changed, 11 insertions(+), 7 deletions(-) diff --git a/wui/src/test/functional/interface_test.rb b/wui/src/test/functional/interface_test.rb index dbee928..478e765 100644 --- a/wui/src/test/functional/interface_test.rb +++ b/wui/src/test/functional/interface_test.rb @@ -35,7 +35,7 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' @site_url, 15000) @browser.start - @browser.set_speed(1500) # ms delay between operations + #@browser.set_speed(1500) # ms delay between operations @browser.open(@site_url) end @@ -114,12 +114,14 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' "selenium.isElementPresent(\"//div[@id='vm_action_results']/div[3]/div/div[2]/a\")", 10000) @browser.click "//div[@id='vm_action_results']/div[3]/div/div[2]/a" + sleep 5 # give vm time to start @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" @browser.wait_for_condition( - "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\")", + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") && " + + "selenium.getText(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") == \"running\"", 20000) - assert_equal("running", - @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) + #assert_equal("running", + #@browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) # stop / destroy vm @browser.click "//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[1]/div/input" @@ -128,12 +130,14 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' "selenium.isElementPresent(\"//div[@id='vm_action_results']/div[3]/div/div[2]/a\")", 10000) @browser.click "//div[@id='vm_action_results']/div[3]/div/div[2]/a" + sleep 5 # give vm time to stop @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" @browser.wait_for_condition( - "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\")", + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") && " + + "selenium.getText(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") == \"stopped\"", 20000) - assert_equal("stopped", - @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) + #assert_equal("stopped", + #@browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) end -- 1.5.4.1 From mmorsi at redhat.com Sun Aug 31 20:50:46 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Sun, 31 Aug 2008 16:50:46 -0400 Subject: [Ovirt-devel] [PATCH] Fix to broken tests (2/2) In-Reply-To: <1220215846-28759-1-git-send-email-mmorsi@redhat.com> References: <1220215846-28759-1-git-send-email-mmorsi@redhat.com> Message-ID: <1220215846-28759-2-git-send-email-mmorsi@redhat.com> --- wui/src/app/models/vm.rb | 3 ++- wui/src/test/functional/vm_controller_test.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wui/src/app/models/vm.rb b/wui/src/app/models/vm.rb index 80c7efb..9520a4c 100644 --- a/wui/src/app/models/vm.rb +++ b/wui/src/app/models/vm.rb @@ -29,7 +29,8 @@ class Vm < ActiveRecord::Base end has_and_belongs_to_many :storage_volumes validates_presence_of :uuid, :description, :num_vcpus_allocated, - :memory_allocated_in_mb, :memory_allocated, :vnic_mac_addr + :boot_device, :memory_allocated_in_mb, + :memory_allocated, :vnic_mac_addr acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], :terms => [ [ :search_users, 'U', "search_users" ] ] diff --git a/wui/src/test/functional/vm_controller_test.rb b/wui/src/test/functional/vm_controller_test.rb index 0817754..a626430 100644 --- a/wui/src/test/functional/vm_controller_test.rb +++ b/wui/src/test/functional/vm_controller_test.rb @@ -56,7 +56,7 @@ class VmControllerTest < Test::Unit::TestCase def test_create num_vms = Vm.count - post :create, :vm_resource_pool_name => 'foobar', :hardware_pool_id => 1, :vm => { :uuid => 'f43b298c-1e65-46fa-965f-0f6fb9ffaa10', :description => 'descript', :num_vcpus_allocated => 4, :memory_allocated => 262144, :vnic_mac_addr => 'AA:BB:CC:DD:EE:FF' } + post :create, :vm_resource_pool_name => 'foobar', :hardware_pool_id => 1, :vm => { :uuid => 'f43b298c-1e65-46fa-965f-0f6fb9ffaa10', :description => 'descript', :num_vcpus_allocated => 4, :memory_allocated => 262144, :vnic_mac_addr => 'AA:BB:CC:DD:EE:FF', :boot_device => 'foobar' } assert_response :success -- 1.5.4.1 From apevec at redhat.com Sun Aug 31 21:21:07 2008 From: apevec at redhat.com (Alan Pevec) Date: Sun, 31 Aug 2008 23:21:07 +0200 Subject: [Ovirt-devel] [PATCH] Fixes to broken tests (1/2) In-Reply-To: <1220215846-28759-1-git-send-email-mmorsi@redhat.com> References: <1220215846-28759-1-git-send-email-mmorsi@redhat.com> Message-ID: <48BB0B43.2070807@redhat.com> > @@ -114,12 +114,14 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' > "selenium.isElementPresent(\"//div[@id='vm_action_results']/div[3]/div/div[2]/a\")", > 10000) > @browser.click "//div[@id='vm_action_results']/div[3]/div/div[2]/a" > + sleep 5 # give vm time to start Why is sleep needed here at all if wait_for_condition below is expecting 'running' to show up? In general, using sleep for synchronization is wrong, although it might seem to work. > @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" > @browser.wait_for_condition( > - "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\")", > + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") && " + > + "selenium.getText(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") == \"running\"", > 20000) > - assert_equal("running", > - @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) > + #assert_equal("running", > + #@browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) What happens if wait_for_condition times out? Will selenium raise an exception, failing the test or it just continues? If latter, you need to keep the assert. In general, if removing lines, just delete them instead of commenting, we have git to keep the history. > # stop / destroy vm > @browser.click "//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[1]/div/input" > @@ -128,12 +130,14 @@ if File.exists? File.dirname(__FILE__) + '/../selenium.rb' > "selenium.isElementPresent(\"//div[@id='vm_action_results']/div[3]/div/div[2]/a\")", > 10000) > @browser.click "//div[@id='vm_action_results']/div[3]/div/div[2]/a" > + sleep 5 # give vm time to stop same questions as above > @browser.click "//div[@id='side']/ul/li/ul/li[1]/span/a" > @browser.wait_for_condition( > - "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\")", > + "selenium.isElementPresent(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") && " + > + "selenium.getText(\"//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div\") == \"stopped\"", > 20000) > - assert_equal("stopped", > - @browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div")) > + #assert_equal("stopped", > + #@browser.get_text("//table[@id='vms_grid']/tbody/tr[" + (num_vms.to_i + 1).to_s + "]/td[7]/div"))