From apevec at redhat.com Wed Oct 1 11:06:59 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 1 Oct 2008 13:06:59 +0200 Subject: [Ovirt-devel] [PATCH ovirt-node-image] use rawhide repos for Fedora 10 In-Reply-To: <1222811813-20710-1-git-send-email-apevec@redhat.com> References: <1222811813-20710-1-git-send-email-apevec@redhat.com> Message-ID: <1222859219-23982-1-git-send-email-apevec@redhat.com> for experimental builds, until it gets released Signed-off-by: Alan Pevec --- ovirt-node-image.spec.in | 36 ++++++++++++++++++++++++++---------- 1 files changed, 26 insertions(+), 10 deletions(-) diff --git a/ovirt-node-image.spec.in b/ovirt-node-image.spec.in index dbe8834..5a874f7 100644 --- a/ovirt-node-image.spec.in +++ b/ovirt-node-image.spec.in @@ -36,22 +36,38 @@ The PXE boot image for oVirt Node network boot from oVirt Server. %setup -q %build -if [ -n "%{?fedora_url}" ]; then - cat > repos.ks << EOF -repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os -repo --name=f%{fedora}-updates --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch} -repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey +%if 0%{?fedora} == 010 + # XXX current rawhide + %if "%{?fedora_url}" == "" +cat > repos.ks << EOF +repo --name=rawhide --mirrorlist=%{fedora_mirror}?repo=rawhide&arch=%{_arch} +EOF + %else +cat > repos.ks << EOF +repo --name=rawhide --baseurl=%{fedora_url}/development/%{_arch}/os EOF -else - cat > repos.ks << EOF + %endif +cat >> repos.ks << EOF +repo --name=ovirt-org --baseurl=%{ovirt_url}/development/%{_arch} +EOF +%else + %if "%{?fedora_url}" == "" +cat > repos.ks << EOF repo --name=f%{fedora} --mirrorlist=%{fedora_mirror}?repo=fedora-%{fedora}&arch=%{_arch} -repo --name=f%{fedora}-updates --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}&arch=%{_arch} repo --name=f%{fedora}-updates-newkey --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}.newkey&arch=%{_arch} EOF -fi - + %else +cat > repos.ks << EOF +repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os +repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey +EOF + %endif cat >> repos.ks << EOF repo --name=ovirt-org --baseurl=%{ovirt_url}/%{fedora}/%{_arch} +EOF +%endif + +cat >> repos.ks << EOF repo --name=ovirt-local --baseurl=%{ovirt_local_repo} EOF -- 1.5.5.1 From fujimura.toshifumi at np.css.fujitsu.com Wed Oct 1 11:18:40 2008 From: fujimura.toshifumi at np.css.fujitsu.com (Toshifumi Fujimura) Date: Wed, 01 Oct 2008 20:18:40 +0900 Subject: [Ovirt-devel] =?iso-2022-jp?b?GyRCJWEhPCVqJXMlMCVqJTklSCROGyhC?= =?iso-2022-jp?b?GyRCSjhMTBsoQg==?= Message-ID: <48E35C90.9070508@np.css.fujitsu.com> ?????????????????? ??????Question for oVirt-0.9.4???????????? Hello! I've installed oVirt-0.9.4. But I have some questions about oVirt WUI. (1) Host's UUID of node3 is same as that of node4 in WUI. And each UUIDs in WUI is different from that in "virsh list". What does these UUID intend? (2) Node4 isn't contained UUID's host list in spite of starting. Why? Works for me. Toshifumi Fujimura. From fujimura.toshifumi at np.css.fujitsu.com Wed Oct 1 11:34:48 2008 From: fujimura.toshifumi at np.css.fujitsu.com (Toshifumi Fujimura) Date: Wed, 01 Oct 2008 20:34:48 +0900 Subject: [Ovirt-devel] Question for oVirt-0.9.4 Message-ID: <48E36058.60205@np.css.fujitsu.com> I have some questions about latest oVirt WUI. (1) Node4 can see from virsh list command. But it isn't displayed on WUI interface. Why? (2) Would you explain the UUID difference between the output of virsh dumpxml and WUI's? I successfully run the oVirt. But I saw that the UUID is same between node3 and node5. So I compare the virsh dumpxml UUID with WUI's UUID. I raise this question. Works for me. Toshifumi Fujimura. From apevec at redhat.com Wed Oct 1 11:46:23 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 1 Oct 2008 13:46:23 +0200 Subject: [Ovirt-devel] [PATCH ovirt-node-image] use rawhide repos for Fedora 10 In-Reply-To: <1222859219-23982-1-git-send-email-apevec@redhat.com> References: <1222859219-23982-1-git-send-email-apevec@redhat.com> Message-ID: <1222861583-11511-1-git-send-email-apevec@redhat.com> for experimental builds, until it gets released Signed-off-by: Alan Pevec --- ovirt-node-image.ks | 2 ++ ovirt-node-image.spec.in | 36 ++++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ovirt-node-image.ks b/ovirt-node-image.ks index f5695d8..32bd055 100644 --- a/ovirt-node-image.ks +++ b/ovirt-node-image.ks @@ -8,6 +8,8 @@ %end %post +# cleanup rpmdb to allow non-matching host and chroot RPM versions +rm -f /var/lib/rpm/__db* %include common-post.ks touch /.autorelabel diff --git a/ovirt-node-image.spec.in b/ovirt-node-image.spec.in index dbe8834..5a874f7 100644 --- a/ovirt-node-image.spec.in +++ b/ovirt-node-image.spec.in @@ -36,22 +36,38 @@ The PXE boot image for oVirt Node network boot from oVirt Server. %setup -q %build -if [ -n "%{?fedora_url}" ]; then - cat > repos.ks << EOF -repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os -repo --name=f%{fedora}-updates --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch} -repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey +%if 0%{?fedora} == 010 + # XXX current rawhide + %if "%{?fedora_url}" == "" +cat > repos.ks << EOF +repo --name=rawhide --mirrorlist=%{fedora_mirror}?repo=rawhide&arch=%{_arch} +EOF + %else +cat > repos.ks << EOF +repo --name=rawhide --baseurl=%{fedora_url}/development/%{_arch}/os EOF -else - cat > repos.ks << EOF + %endif +cat >> repos.ks << EOF +repo --name=ovirt-org --baseurl=%{ovirt_url}/development/%{_arch} +EOF +%else + %if "%{?fedora_url}" == "" +cat > repos.ks << EOF repo --name=f%{fedora} --mirrorlist=%{fedora_mirror}?repo=fedora-%{fedora}&arch=%{_arch} -repo --name=f%{fedora}-updates --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}&arch=%{_arch} repo --name=f%{fedora}-updates-newkey --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}.newkey&arch=%{_arch} EOF -fi - + %else +cat > repos.ks << EOF +repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os +repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey +EOF + %endif cat >> repos.ks << EOF repo --name=ovirt-org --baseurl=%{ovirt_url}/%{fedora}/%{_arch} +EOF +%endif + +cat >> repos.ks << EOF repo --name=ovirt-local --baseurl=%{ovirt_local_repo} EOF -- 1.5.5.1 From dpierce at redhat.com Wed Oct 1 14:17:24 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 1 Oct 2008 10:17:24 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1222870644-15710-1-git-send-email-dpierce@redhat.com> Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS URL for where the virtual image will go to mount it; i.e., nfs://hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 13 +++++-- src/app/models/vm.rb | 31 +++++++++++++++- src/task-omatic/task_vm.rb | 63 ++++++++++++++++++++++++++++------ src/test/unit/vm_test.rb | 57 +++++++++++++++++++++++++++++-- 4 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..8b16c94 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..2e2ddb5 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,12 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +247,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + self.provisioning.include? COBBLER_PREFIX + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..101878b 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -25,7 +25,7 @@ gem 'cobbler' require 'cobbler' def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, - macAddr, bridge, diskDevices) + macAddr, bridge, diskDevices, bootdrive) doc = Document.new doc.add_element("domain", {"type" => "kvm"}) @@ -65,16 +65,28 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = [ 'hda', 'hdb', 'hdc', 'hdd', 'hde', 'hdf' ] + which_device = 0 diskDevices.each do |disk| diskdev = Element.new("disk") diskdev.add_attribute("type", "block") diskdev.add_attribute("device", "disk") diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_element("target", {"dev" => devs[which_device]}) doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 + end + + if bootdrive + bootdisk = Element.new("disk") + + bootdisk.add_attribute("type", bootdisk[:type]) + bootdisk.add_attribute("device", bootdisk[:device]) + bootdisk.add_element("source", {"file" => bootdrive[:source]}) + bootdisk.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + bootdisk.add_element("readonly") if bootdrive[:readonly] + + doc.root.elements["devices"] << bootdisk end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +166,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -268,12 +277,44 @@ def start_vm(task) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + # if the VM is an image, then prepare things + if vm.uses_cobbler? && (vm.cobbler_type == Vm.IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, protocol, sharepath, filename = details.filename(/(.*):\/\/(.*)\/(.*)/) + + raise Exception.new("Only NFS mounts are supported currently") unless protocol == 'nfs' + + # if the file's an .ISO then we'll have to mount as a CDrom, otherwise + # we'll mount as a hard drive + bootdrive[:type] = 'file' + bootdrive[:source] = filename + bootdrive[:path] = sharepath + if details.file[/.iso/] + bootdrive[:readonly] = true + bootdrive[:device] = 'cdrom' + else + bootdrive[:readonly] = false + bootdrive[:device] = 'disk' + end + + # mount the filesystem + mountpoint = `mktemp -d` + bootdrive[:mountpoint] = mountpoint + + unless system("mount #{sharepath} #{mountpoint}") + raise Exception.new "Unable to mount #{details.file}" + end + end + 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) + vm.vnic_mac_addr, "ovirtbr0", storagedevs, bootdrive) dom = conn.define_domain_xml(xml.to_s) dom.create diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..c226c63 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,59 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_With_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From mmorsi at redhat.com Wed Oct 1 18:14:48 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 1 Oct 2008 14:14:48 -0400 Subject: [Ovirt-devel] oVirt Server Help Document Patch Set Message-ID: <1222884890-12367-1-git-send-email-mmorsi@redhat.com> The following two patches integrate the "Using oVirt" document provided by the docs rpm into the oVirt server appliance and rails application. Overall this consists of the following steps * add ovirt-doc rpm dependency to ovirt-recipe rpm * use recipe to make "Using oVirt" doc available to rails * add help_sections table to db * set application.rb to always lookup appropriate help section based on controller / action being requested. if none is found, a default is used * integrate correct help link into wui, requires various template / css tweaks From mmorsi at redhat.com Wed Oct 1 18:14:49 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 1 Oct 2008 14:14:49 -0400 Subject: [Ovirt-devel] [PATCH] integrate docs into appliance / server In-Reply-To: <1222884890-12367-1-git-send-email-mmorsi@redhat.com> References: <1222884890-12367-1-git-send-email-mmorsi@redhat.com> Message-ID: <1222884890-12367-2-git-send-email-mmorsi@redhat.com> --- appliances/ovirt/ovirt.pp.in | 4 ++++ ovirt-recipe.spec.in | 1 + 2 files changed, 5 insertions(+), 0 deletions(-) diff --git a/appliances/ovirt/ovirt.pp.in b/appliances/ovirt/ovirt.pp.in index 1ee8b48..e24c046 100644 --- a/appliances/ovirt/ovirt.pp.in +++ b/appliances/ovirt/ovirt.pp.in @@ -168,6 +168,10 @@ single_exec {"ovirt_installation": require => [Service["postgresql"],Single_exec["ovirt_appliance_installation"]] } +single_exec {"ovirt_docs_accesible_from_server": + command => "/bin/ln -s /usr/share/doc/ovirt-docs-${appliance_version}/Using_oVirt/ /usr/share/ovirt-server/public/help" +} + # # Cobbler Configuration # diff --git a/ovirt-recipe.spec.in b/ovirt-recipe.spec.in index 7013b3b..cb134c4 100644 --- a/ovirt-recipe.spec.in +++ b/ovirt-recipe.spec.in @@ -29,6 +29,7 @@ Requires: iscsi-initiator-utils Requires: ovirt-server Requires: ovirt-node-image-pxe Requires: ovirt-release +Requires: ovirt-docs Requires: rhpl Requires: cobbler Requires: rubygem-cobbler -- 1.5.4.1 From mmorsi at redhat.com Wed Oct 1 18:14:50 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 1 Oct 2008 14:14:50 -0400 Subject: [Ovirt-devel] [PATCH] integrate 'using ovirt' doc into a wui help section In-Reply-To: <1222884890-12367-2-git-send-email-mmorsi@redhat.com> References: <1222884890-12367-1-git-send-email-mmorsi@redhat.com> <1222884890-12367-2-git-send-email-mmorsi@redhat.com> Message-ID: <1222884890-12367-3-git-send-email-mmorsi@redhat.com> this will work as is, but until help_sections table is populated all pages will link to main document body (and not particular sections) --- src/app/controllers/application.rb | 7 ++++++- src/app/controllers/pool_controller.rb | 2 +- src/app/models/help_section.rb | 2 ++ src/app/views/layouts/_header_redux.rhtml | 1 - src/app/views/layouts/help-and-content.rhtml | 4 ++++ src/app/views/layouts/redux.rhtml | 4 ++++ src/app/views/layouts/tabs-and-content.rhtml | 6 +++++- src/db/migrate/023_create_help_sections.rb | 13 +++++++++++++ src/public/stylesheets/layout.css | 8 +++++++- src/test/fixtures/help_sections.yml | 7 +++++++ src/test/unit/help_section_test.rb | 8 ++++++++ 11 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/app/models/help_section.rb create mode 100644 src/app/views/layouts/help-and-content.rhtml create mode 100644 src/db/migrate/023_create_help_sections.rb create mode 100644 src/test/fixtures/help_sections.yml create mode 100644 src/test/unit/help_section_test.rb diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb index 6dcf6f8..0d5521a 100644 --- a/src/app/controllers/application.rb +++ b/src/app/controllers/application.rb @@ -31,7 +31,7 @@ class ApplicationController < ActionController::Base before_filter :pre_edit, :only => [:edit, :update, :destroy] before_filter :pre_show, :only => [:show] before_filter :authorize_admin, :only => [:new, :create, :edit, :update, :destroy] - before_filter :is_logged_in + before_filter :is_logged_in, :get_help_section def is_logged_in redirect_to(:controller => "login", :action => "login") unless get_login_user @@ -40,6 +40,11 @@ class ApplicationController < ActionController::Base def get_login_user (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" end + + def get_help_section + help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ]) + @help_section = help ? help.section : "" + end def set_perms(hwpool) @user = get_login_user diff --git a/src/app/controllers/pool_controller.rb b/src/app/controllers/pool_controller.rb index 02ef290..1a574b1 100644 --- a/src/app/controllers/pool_controller.rb +++ b/src/app/controllers/pool_controller.rb @@ -35,7 +35,7 @@ class PoolController < ApplicationController respond_to do |format| format.html { render :layout => 'tabs-and-content' if params[:ajax] - render :layout => false if params[:nolayout] + render :layout => 'help-and-content' if params[:nolayout] } format.xml { render :xml => @pool.to_xml(XML_OPTS) diff --git a/src/app/models/help_section.rb b/src/app/models/help_section.rb new file mode 100644 index 0000000..a891383 --- /dev/null +++ b/src/app/models/help_section.rb @@ -0,0 +1,2 @@ +class HelpSection < ActiveRecord::Base +end diff --git a/src/app/views/layouts/_header_redux.rhtml b/src/app/views/layouts/_header_redux.rhtml index 18014ab..a12d567 100644 --- a/src/app/views/layouts/_header_redux.rhtml +++ b/src/app/views/layouts/_header_redux.rhtml @@ -6,7 +6,6 @@ " title="Search" type="image">  | - <%= image_tag "icon_help.png" %>
Resource Pools
diff --git a/src/app/views/layouts/help-and-content.rhtml b/src/app/views/layouts/help-and-content.rhtml new file mode 100644 index 0000000..67989b7 --- /dev/null +++ b/src/app/views/layouts/help-and-content.rhtml @@ -0,0 +1,4 @@ + +<%= yield %> diff --git a/src/app/views/layouts/redux.rhtml b/src/app/views/layouts/redux.rhtml index 01540d4..f6b4657 100644 --- a/src/app/views/layouts/redux.rhtml +++ b/src/app/views/layouts/redux.rhtml @@ -109,11 +109,15 @@
+
+ <%= yield %> <%# the rest of the center and right hand side %>
diff --git a/src/app/views/layouts/tabs-and-content.rhtml b/src/app/views/layouts/tabs-and-content.rhtml index 8aa4183..e55b5ea 100644 --- a/src/app/views/layouts/tabs-and-content.rhtml +++ b/src/app/views/layouts/tabs-and-content.rhtml @@ -1,3 +1,4 @@ +
<%= render :partial => '/layouts/side_toolbar' %>
@@ -6,5 +7,8 @@
\ No newline at end of file + diff --git a/src/db/migrate/023_create_help_sections.rb b/src/db/migrate/023_create_help_sections.rb new file mode 100644 index 0000000..963e8bd --- /dev/null +++ b/src/db/migrate/023_create_help_sections.rb @@ -0,0 +1,13 @@ +class CreateHelpSections < ActiveRecord::Migration + def self.up + create_table :help_sections do |t| + t.string :controller, :null => false, :limit => 25 + t.string :action, :null => false, :limit => 25 + t.string :section, :null => false, :limit => 100 + end + end + + def self.down + drop_table :help_sections + end +end diff --git a/src/public/stylesheets/layout.css b/src/public/stylesheets/layout.css index 9471cdc..013c959 100644 --- a/src/public/stylesheets/layout.css +++ b/src/public/stylesheets/layout.css @@ -128,7 +128,7 @@ a { color:#000000; text-decoration: none;} /* ----- Right side of Header -------- */ .header_info { - padding: 10px 20px 0px 10px; + padding: 10px 30px 0px 10px; margin: 0; float: right; text-align: right; @@ -140,6 +140,12 @@ a { color:#000000; text-decoration: none;} .header_info a:visited { color: #B5B5B5; text-decoration: none; } .header_info a:hover { color: #FFFFFF; text-decoration: none; } +#help-div { + position: fixed; + top: 10px; + right: -1px; +} + #textfield_effect { border-width: 1px; border-style: solid; diff --git a/src/test/fixtures/help_sections.yml b/src/test/fixtures/help_sections.yml new file mode 100644 index 0000000..5bf0293 --- /dev/null +++ b/src/test/fixtures/help_sections.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +# one: +# column: value +# +# two: +# column: value diff --git a/src/test/unit/help_section_test.rb b/src/test/unit/help_section_test.rb new file mode 100644 index 0000000..a0e6a08 --- /dev/null +++ b/src/test/unit/help_section_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class HelpSectionTest < ActiveSupport::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end -- 1.5.4.1 From mmorsi at redhat.com Thu Oct 2 01:09:01 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 01 Oct 2008 21:09:01 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. In-Reply-To: <1222704330-11127-1-git-send-email-dpierce@redhat.com> References: <1222704330-11127-1-git-send-email-dpierce@redhat.com> Message-ID: <48E41F2D.6020809@redhat.com> Hey Darryl, I tried applying and running this patch today but ran into some issues. Comments embedded below. Darryl L. Pierce wrote: > A change to the identify protocol was overlooked. When an empty value was > submitted, host-browser would throw an exception. But, a change allowed > empty values to get by without notice. Fixed. > > Also added a BuildRequires dependency on ruby-flexmock so that the RPM is > correctly installed when building the server RPM. > > Signed-off-by: Darryl L. Pierce > > --- > ovirt-server.spec.in | 1 + > src/host-browser/host-browser.rb | 9 +++++++-- > src/test/unit/host_browser_awaken_test.rb | 2 +- > 3 files changed, 9 insertions(+), 3 deletions(-) > > diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in > index a65e628..f351e77 100644 > --- a/ovirt-server.spec.in > +++ b/ovirt-server.spec.in > @@ -35,6 +35,7 @@ Requires(preun): /sbin/chkconfig > Requires(preun): /sbin/service > BuildRequires: ruby >= 1.8.1 > BuildRequires: ruby-devel > +BuildRequires: ruby-flexmock > BuildRequires: ruby-gettext-package > BuildRequires: rubygem(rake) >= 0.7 > Provides: ovirt-server > git-am reported some conflict with this file when I applied the patch. Luckily this file contained a simple one line change so I was able to manually copy it over and remove the file from the patch. > diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb > index 3adea03..a33e657 100755 > --- a/src/host-browser/host-browser.rb > +++ b/src/host-browser/host-browser.rb > @@ -46,10 +46,15 @@ class HostBrowser > def initialize(session) > @session = session > @keytab_dir = '/usr/share/ipa/html/' > + set_peeraddr @session.peeraddr[3] > + end > + > This previous line, though blank, has trailing whitespace. > + def set_peeraddr(peeraddr) > + @peeraddr = peeraddr > end > > def prefix(session) > - "#{Time.now.strftime('%b %d %H:%M:%S')} #{session.peeraddr[3]} " > + "#{Time.now.strftime('%b %d %H:%M:%S')} #{@peeraddr} " > end > > # Ensures the conversation starts properly. > @@ -113,7 +118,7 @@ class HostBrowser > nic_info << nic > else > > - raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]*/ > + raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]+/ > > key, value = info.split("=") > > diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb > index 5340e01..55a3458 100644 > --- a/src/test/unit/host_browser_awaken_test.rb > +++ b/src/test/unit/host_browser_awaken_test.rb > @@ -64,7 +64,7 @@ class HostBrowserAwakenTest < Test::Unit::TestCase > # Ensures that the server is satisfied if the remote system is > # making a wakeup call. > # > - def test_get_mode_with_awaken_request > + def test_get_mode_with_awaken_request > Same issue with trailing whitespace here. > @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } > @session.should_receive(:readline).once().returns { "AWAKEN\n" } > > After applying the patch, I tried running the test cases but to no avail, many were still broken. Doing a bit of debugging myself, I see that when you recently added tests for the stuff fixed with this patch, you added a few references to a variable named "@first_id" to the code. Looking at this variable I see that it is null everywhere I looked. I'm not sure if I'm doing something wrong, but I tried googling around for it but could not find any reference to a preset @first_id provided by ruby's unit test suite. Adding a "@first_id = 1" to the "setup" function of one of the problematic functional tests seemed to mitigate those errors there (though there were others, not sure the cause). Perhaps you could provide some insight into this issue. cc'ing Scott on this email because I also noticed during this process that some of the tests were still broken due to the recent smart pool changes. More specifically with the recent commit updating the pool test fixture, I believe several of the other fixtures, specifically those with foreign key references to the pools table, are not valid. Grepping "test/fixtures/" for "pool_id" brings these forward. In any case, feel free to shout out via email or irc if you want to figure out / debug this together. Thanks alot! -Mo From fujimura.toshifumi at np.css.fujitsu.com Thu Oct 2 09:05:41 2008 From: fujimura.toshifumi at np.css.fujitsu.com (Toshifumi Fujimura) Date: Thu, 02 Oct 2008 18:05:41 +0900 Subject: [Ovirt-devel] Question about managing strage server on latest oVirt Message-ID: <48E48EE5.8060508@np.css.fujitsu.com> Hello! I attempt to install a guest OS with WUI follows "Using oVirt". But I cannot add Strage Pool to Hardware Pool. I can't oparate in the way of stage 6 in "Using oVirt"(http://www.ovirt.org/docs/Using_oVirt/Add-Storage.html). Because "Using oVirt" is a little bit old. Would you suggest me a latest instruction of the way to add Strage Pool to Hardware Pool. P.S. I think Smart Pool causes this problem. Please tell me how to use Smart Pool. Works for me. Toshifumi Fujimura. From dpierce at redhat.com Thu Oct 2 13:03:55 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 02 Oct 2008 09:03:55 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. In-Reply-To: <48E41F2D.6020809@redhat.com> References: <1222704330-11127-1-git-send-email-dpierce@redhat.com> <48E41F2D.6020809@redhat.com> Message-ID: <48E4C6BB.8050404@redhat.com> Mohammed Morsi wrote: >> BuildRequires: ruby-devel >> +BuildRequires: ruby-flexmock >> BuildRequires: ruby-gettext-package >> BuildRequires: rubygem(rake) >= 0.7 >> Provides: ovirt-server >> > git-am reported some conflict with this file when I applied the patch. > Luckily this file contained a simple one line change so I was able to > manually copy it over and remove the file from the patch. Weird. I only added the ruby-flexmock dependency since it's come up a few times. >> diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb >> index 5340e01..55a3458 100644 >> --- a/src/test/unit/host_browser_awaken_test.rb >> +++ b/src/test/unit/host_browser_awaken_test.rb >> @@ -64,7 +64,7 @@ class HostBrowserAwakenTest < Test::Unit::TestCase >> # Ensures that the server is satisfied if the remote system is >> # making a wakeup call. >> # >> - def test_get_mode_with_awaken_request >> + def test_get_mode_with_awaken_request >> > Same issue with trailing whitespace here. I made sure to remove trailing whitespaces in these files since they'd creeped in previously. > > >> @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } >> @session.should_receive(:readline).once().returns { "AWAKEN\n" } >> >> > > > After applying the patch, I tried running the test cases but to no > avail, many were still broken. This patch was only to fix the HostBrowser tests, none others; such bugs as the one to do with @first_id are outside of its scope. The tests that I fixed with this run without error. I'm sending an updated patch now. -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 2 13:05:04 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 2 Oct 2008 09:05:04 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. Message-ID: <1222952704-4877-1-git-send-email-dpierce@redhat.com> A change to the identify protocol was overlooked. When an empty value was submitted, host-browser would throw an exception. But, a change allowed empty values to get by without notice. Fixed. Also added a BuildRequires dependency on ruby-flexmock so that the RPM is correctly installed when building the server RPM. Signed-off-by: Darryl L. Pierce --- ovirt-server.spec.in | 1 + src/host-browser/host-browser.rb | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 114b52d..5650e01 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -36,6 +36,7 @@ Requires(preun): /sbin/service BuildRequires: ruby >= 1.8.1 BuildRequires: ruby-devel BuildRequires: rubygem(gettext) +BuildRequires: ruby-flexmock BuildRequires: rubygem(rake) >= 0.7 Provides: ovirt-server BuildArch: noarch diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 3adea03..2241614 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -46,10 +46,15 @@ class HostBrowser def initialize(session) @session = session @keytab_dir = '/usr/share/ipa/html/' + set_peeraddr @session.peeraddr[3] + end + + def set_peeraddr(peeraddr) + @peeraddr = peeraddr end def prefix(session) - "#{Time.now.strftime('%b %d %H:%M:%S')} #{session.peeraddr[3]} " + "#{Time.now.strftime('%b %d %H:%M:%S')} #{@peeraddr} " end # Ensures the conversation starts properly. @@ -113,7 +118,7 @@ class HostBrowser nic_info << nic else - raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]*/ + raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]+/ key, value = info.split("=") -- 1.5.5.1 From dpierce at redhat.com Thu Oct 2 13:24:15 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 2 Oct 2008 09:24:15 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1222953855-6319-1-git-send-email-dpierce@redhat.com> Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 13 +++++-- src/app/models/vm.rb | 31 +++++++++++++++- src/task-omatic/task_vm.rb | 63 ++++++++++++++++++++++++++++------ src/test/unit/vm_test.rb | 57 +++++++++++++++++++++++++++++-- 4 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..8b16c94 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..2e2ddb5 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,12 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +247,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + self.provisioning.include? COBBLER_PREFIX + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..101878b 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -25,7 +25,7 @@ gem 'cobbler' require 'cobbler' def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, - macAddr, bridge, diskDevices) + macAddr, bridge, diskDevices, bootdrive) doc = Document.new doc.add_element("domain", {"type" => "kvm"}) @@ -65,16 +65,28 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = [ 'hda', 'hdb', 'hdc', 'hdd', 'hde', 'hdf' ] + which_device = 0 diskDevices.each do |disk| diskdev = Element.new("disk") diskdev.add_attribute("type", "block") diskdev.add_attribute("device", "disk") diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_element("target", {"dev" => devs[which_device]}) doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 + end + + if bootdrive + bootdisk = Element.new("disk") + + bootdisk.add_attribute("type", bootdisk[:type]) + bootdisk.add_attribute("device", bootdisk[:device]) + bootdisk.add_element("source", {"file" => bootdrive[:source]}) + bootdisk.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + bootdisk.add_element("readonly") if bootdrive[:readonly] + + doc.root.elements["devices"] << bootdisk end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +166,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -268,12 +277,44 @@ def start_vm(task) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + # if the VM is an image, then prepare things + if vm.uses_cobbler? && (vm.cobbler_type == Vm.IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, protocol, sharepath, filename = details.filename(/(.*):\/\/(.*)\/(.*)/) + + raise Exception.new("Only NFS mounts are supported currently") unless protocol == 'nfs' + + # if the file's an .ISO then we'll have to mount as a CDrom, otherwise + # we'll mount as a hard drive + bootdrive[:type] = 'file' + bootdrive[:source] = filename + bootdrive[:path] = sharepath + if details.file[/.iso/] + bootdrive[:readonly] = true + bootdrive[:device] = 'cdrom' + else + bootdrive[:readonly] = false + bootdrive[:device] = 'disk' + end + + # mount the filesystem + mountpoint = `mktemp -d` + bootdrive[:mountpoint] = mountpoint + + unless system("mount #{sharepath} #{mountpoint}") + raise Exception.new "Unable to mount #{details.file}" + end + end + 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) + vm.vnic_mac_addr, "ovirtbr0", storagedevs, bootdrive) dom = conn.define_domain_xml(xml.to_s) dom.create diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..c226c63 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,59 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_With_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From imain at redhat.com Thu Oct 2 17:42:30 2008 From: imain at redhat.com (Ian Main) Date: Thu, 2 Oct 2008 10:42:30 -0700 Subject: [Ovirt-devel] [PATCH node] Add libvirt-qpid to the node Message-ID: <1222969350-29823-1-git-send-email-imain@redhat.com> This patch adds libvirt-qpid to the node image and sets it up so it will connect to the server in the given DNS SRV record. Signed-off-by: Ian Main --- ovirt-node.spec.in | 1 + scripts/ovirt | 10 ++++++++++ 2 files changed, 11 insertions(+), 0 deletions(-) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index be8d8f5..3dc4e1d 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -14,6 +14,7 @@ BuildRequires: libvirt-devel BuildRequires: dbus-devel hal-devel Requires: augeas Requires: libvirt +Requires: libvirt-qpid Requires: hal Requires: collectd Requires: cyrus-sasl-gssapi diff --git a/scripts/ovirt b/scripts/ovirt index d81a72e..3c120a0 100644 --- a/scripts/ovirt +++ b/scripts/ovirt @@ -76,6 +76,16 @@ start() { else echo "skipping collectd configuration, collectd service not available" fi + + find_srv qpidd tcp + if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then + libvirt_qpid_conf=/etc/sysconfig/libvirt-qpid + if [ -f $libvirt_qpid_conf ]; then + echo "LIBVIRT_QPID_ARGS=\"--broker $SRV_HOST --port $SRV_PORT\"" >> $libvirt_qpid_conf + fi + else + echo "skipping libvirt-qpid configuration, could not find $libvirt_qpid_conf" + fi } case "$1" in -- 1.5.5.1 From imain at redhat.com Thu Oct 2 17:44:25 2008 From: imain at redhat.com (Ian Main) Date: Thu, 2 Oct 2008 10:44:25 -0700 Subject: [Ovirt-devel] [PATCH recipe] Add qpidd to the wui Message-ID: <1222969465-29898-1-git-send-email-imain@redhat.com> This patch adds qpidd to the wui, configures a DNS SRV record for its address, opens up the firewall port, creates a config for qpidd etc. Signed-off-by: Ian Main --- Makefile.am | 1 + appliances/ovirt/files/ovirt-server-appliance | 1 + appliances/ovirt/files/qpidd.conf | 5 +++++ appliances/ovirt/ovirt.pp.in | 10 ++++++++++ ovirt-recipe.spec.in | 4 ++++ 5 files changed, 21 insertions(+), 0 deletions(-) create mode 100644 appliances/ovirt/files/qpidd.conf diff --git a/Makefile.am b/Makefile.am index 0b4e2b9..37751af 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,7 @@ EXTRA_DIST = \ appliances/ovirt/ovirt.pp \ appliances/ovirt/ovirt.pp.in \ appliances/ovirt/files/collectd.conf \ + appliances/ovirt/files/qpidd.conf \ appliances/ovirt/files/ovirt-cfgdb \ appliances/ovirt/files/ovirt.repo \ appliances/ovirt/files/ovirt-server-appliance \ diff --git a/appliances/ovirt/files/ovirt-server-appliance b/appliances/ovirt/files/ovirt-server-appliance index 0f48402..778d0ce 100644 --- a/appliances/ovirt/files/ovirt-server-appliance +++ b/appliances/ovirt/files/ovirt-server-appliance @@ -19,6 +19,7 @@ start() { -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 _qpidd._tcp,management.priv.ovirt.org,5672 \ -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 \ diff --git a/appliances/ovirt/files/qpidd.conf b/appliances/ovirt/files/qpidd.conf new file mode 100644 index 0000000..a7e34bb --- /dev/null +++ b/appliances/ovirt/files/qpidd.conf @@ -0,0 +1,5 @@ +# Configuration file for qpidd. Entries are of the form: +# name = value +# Using default settings: "qpidd --help" or "man qpidd" for more details. +auth=no + diff --git a/appliances/ovirt/ovirt.pp.in b/appliances/ovirt/ovirt.pp.in index 1ee8b48..5d7e9e8 100644 --- a/appliances/ovirt/ovirt.pp.in +++ b/appliances/ovirt/ovirt.pp.in @@ -83,6 +83,10 @@ file {"/etc/collectd.conf": source => "puppet:///ovirt/collectd.conf" } +file {"/etc/qpidd.conf": + source => "puppet:///ovirt/qpidd.conf" +} + file {"/var/www/html/ovirt-cfgdb": source => "puppet:///ovirt/ovirt-cfgdb" } @@ -109,6 +113,7 @@ firewall_rule {"nfsd": destination_port => '2049'} firewall_rule {"rpcbind": destination_port => '111'} firewall_rule {"rpcbind-udp": destination_port => '111', protocol => 'udp'} firewall_rule {"host-browser": destination_port => '12120'} +firewall_rule {"qpidd": destination_port => '5672'} firewall_rule {"rpc.mountd": destination_port => '892'} firewall_rule {"rpc.mountd-udp": destination_port => '892', protocol => 'udp'} firewall_rule {"rpc.statd": destination_port => '662'} @@ -152,6 +157,11 @@ service {"nfslock": require => [Service["network"]] } +service {"qpidd": + ensure => "running", + enable => true +} + file {"/usr/sbin/ovirt-server-appliance-setup": content => template("ovirt-server-appliance-setup.erb"), mode => 755 diff --git a/ovirt-recipe.spec.in b/ovirt-recipe.spec.in index c257e84..9fef6af 100644 --- a/ovirt-recipe.spec.in +++ b/ovirt-recipe.spec.in @@ -37,6 +37,10 @@ Requires: augeas Requires: syslinux Requires: lokkit Requires: curl +Requires: qpidd +Requires: qpidc +Requires: qmf +Requires: python-qpid %description Thincrust oVirt Server Appliance Recipe -- 1.5.5.1 From imain at redhat.com Thu Oct 2 17:55:11 2008 From: imain at redhat.com (Ian Main) Date: Thu, 2 Oct 2008 10:55:11 -0700 Subject: [Ovirt-devel] [PATCH recipe] Add qpidd to the wui In-Reply-To: <1222969465-29898-1-git-send-email-imain@redhat.com> References: <1222969465-29898-1-git-send-email-imain@redhat.com> Message-ID: <1222970111-30065-1-git-send-email-imain@redhat.com> This patch adds qpidd to the wui, configures a DNS SRV record for its address, opens up the firewall port, creates a config for qpidd etc. Amended: Seems I missed restarting qpidd so it will take configuration changes. Signed-off-by: Ian Main --- Makefile.am | 1 + appliances/ovirt/files/ovirt-server-appliance | 1 + appliances/ovirt/files/qpidd.conf | 5 +++++ appliances/ovirt/ovirt.pp.in | 11 +++++++++++ ovirt-recipe.spec.in | 4 ++++ 5 files changed, 22 insertions(+), 0 deletions(-) create mode 100644 appliances/ovirt/files/qpidd.conf diff --git a/Makefile.am b/Makefile.am index 0b4e2b9..37751af 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,7 @@ EXTRA_DIST = \ appliances/ovirt/ovirt.pp \ appliances/ovirt/ovirt.pp.in \ appliances/ovirt/files/collectd.conf \ + appliances/ovirt/files/qpidd.conf \ appliances/ovirt/files/ovirt-cfgdb \ appliances/ovirt/files/ovirt.repo \ appliances/ovirt/files/ovirt-server-appliance \ diff --git a/appliances/ovirt/files/ovirt-server-appliance b/appliances/ovirt/files/ovirt-server-appliance index 0f48402..778d0ce 100644 --- a/appliances/ovirt/files/ovirt-server-appliance +++ b/appliances/ovirt/files/ovirt-server-appliance @@ -19,6 +19,7 @@ start() { -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 _qpidd._tcp,management.priv.ovirt.org,5672 \ -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 \ diff --git a/appliances/ovirt/files/qpidd.conf b/appliances/ovirt/files/qpidd.conf new file mode 100644 index 0000000..a7e34bb --- /dev/null +++ b/appliances/ovirt/files/qpidd.conf @@ -0,0 +1,5 @@ +# Configuration file for qpidd. Entries are of the form: +# name = value +# Using default settings: "qpidd --help" or "man qpidd" for more details. +auth=no + diff --git a/appliances/ovirt/ovirt.pp.in b/appliances/ovirt/ovirt.pp.in index 1ee8b48..9340e8b 100644 --- a/appliances/ovirt/ovirt.pp.in +++ b/appliances/ovirt/ovirt.pp.in @@ -83,6 +83,11 @@ file {"/etc/collectd.conf": source => "puppet:///ovirt/collectd.conf" } +file {"/etc/qpidd.conf": + source => "puppet:///ovirt/qpidd.conf" + notify => Service["qpidd"] +} + file {"/var/www/html/ovirt-cfgdb": source => "puppet:///ovirt/ovirt-cfgdb" } @@ -109,6 +114,7 @@ firewall_rule {"nfsd": destination_port => '2049'} firewall_rule {"rpcbind": destination_port => '111'} firewall_rule {"rpcbind-udp": destination_port => '111', protocol => 'udp'} firewall_rule {"host-browser": destination_port => '12120'} +firewall_rule {"qpidd": destination_port => '5672'} firewall_rule {"rpc.mountd": destination_port => '892'} firewall_rule {"rpc.mountd-udp": destination_port => '892', protocol => 'udp'} firewall_rule {"rpc.statd": destination_port => '662'} @@ -152,6 +158,11 @@ service {"nfslock": require => [Service["network"]] } +service {"qpidd": + ensure => "running", + enable => true +} + file {"/usr/sbin/ovirt-server-appliance-setup": content => template("ovirt-server-appliance-setup.erb"), mode => 755 diff --git a/ovirt-recipe.spec.in b/ovirt-recipe.spec.in index c257e84..9fef6af 100644 --- a/ovirt-recipe.spec.in +++ b/ovirt-recipe.spec.in @@ -37,6 +37,10 @@ Requires: augeas Requires: syslinux Requires: lokkit Requires: curl +Requires: qpidd +Requires: qpidc +Requires: qmf +Requires: python-qpid %description Thincrust oVirt Server Appliance Recipe -- 1.5.5.1 From mmorsi at redhat.com Thu Oct 2 18:02:50 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 02 Oct 2008 14:02:50 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. In-Reply-To: <48E4C6BB.8050404@redhat.com> References: <1222704330-11127-1-git-send-email-dpierce@redhat.com> <48E41F2D.6020809@redhat.com> <48E4C6BB.8050404@redhat.com> Message-ID: <48E50CCA.1010509@redhat.com> Darryl, I haven't tried the latest patch that you sent yet, but noticed a few things while doing some further testing. Darryl Pierce wrote: > Mohammed Morsi wrote: > >>> BuildRequires: ruby-devel >>> +BuildRequires: ruby-flexmock >>> BuildRequires: ruby-gettext-package >>> BuildRequires: rubygem(rake) >= 0.7 >>> Provides: ovirt-server >>> >>> >> git-am reported some conflict with this file when I applied the patch. >> Luckily this file contained a simple one line change so I was able to >> manually copy it over and remove the file from the patch. >> > > Weird. I only added the ruby-flexmock dependency since it's come up a > few times. > First off, shouldn't this just be a 'Requires'? I think BuildRequires implies that this needs to be on the machine hosting the appliance when the rpms are being build where as 'Requires' implies it needs to be on the appliance for this rpm to be installed which is what we want (since its on the appliance that 'rake test' requires ruby-flexmock). > >>> diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb >>> index 5340e01..55a3458 100644 >>> --- a/src/test/unit/host_browser_awaken_test.rb >>> +++ b/src/test/unit/host_browser_awaken_test.rb >>> @@ -64,7 +64,7 @@ class HostBrowserAwakenTest < Test::Unit::TestCase >>> # Ensures that the server is satisfied if the remote system is >>> # making a wakeup call. >>> # >>> - def test_get_mode_with_awaken_request >>> + def test_get_mode_with_awaken_request >>> >>> >> Same issue with trailing whitespace here. >> > > I made sure to remove trailing whitespaces in these files since they'd > creeped in previously. > > >> >>> @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } >>> @session.should_receive(:readline).once().returns { "AWAKEN\n" } >>> >>> >>> >> After applying the patch, I tried running the test cases but to no >> avail, many were still broken. >> > > This patch was only to fix the HostBrowser tests, none others; such bugs > as the one to do with @first_id are outside of its scope. The tests that > I fixed with this run without error. > > I see here http://git.et.redhat.com/?p=ovirt-server.git;a=commitdiff;h=0453e0e65bb0232acadbe3bf0e59cabf0df2f3f0 that you removed where @first_id is set in the nic_controller_test. I was able to get all the tests running successfully (see below) except for test_show in the nic_controller_test which depends on @first_id being set (and thus reports an "Couldn't find Nic without an ID" error). > I'm sending an updated patch now. > > I discovered the reason everything was foobar was that the pools table wasn't being populated in the ovirt_test database. This was caused by an error earlier in the test process which in return caused 'rake test' to abort and skip over the remaining test db population stuff. The error was a path issue, eg test/unit/host_browser_identify_test.rb was unable to find dutils.rb and host_browser.rb. Adding the following to host_browser_identify_test.rb solves the problem: $: << File.join(File.dirname(__FILE__), "../../dutils") $: << File.join(File.dirname(__FILE__), "../../host-browser") As previously mentioned, once this is done the only remaining error is that due to the missing nic id. If we can get these fixes in today and everything works, I'll uncomment the 'rake test' thats in the appliance autobuild so that our continuous autobuild process will report any new errors caused by subsequent changes immediately. -Mo -------------- next part -------------- An HTML attachment was scrubbed... URL: From imain at redhat.com Thu Oct 2 18:11:16 2008 From: imain at redhat.com (Ian Main) Date: Thu, 2 Oct 2008 11:11:16 -0700 Subject: [Ovirt-devel] libvirt-qpid patches Message-ID: <20081002111116.7efb0b82@tp.mains.net> I just wanted to say a few words about the last few patches I posted. These patches integrate libvirt-qpid into ovirt. libvirt-qpid provides an interface with libvirt using QMF (qpid modeling framework) which utilizes the AMQP protocol. The Advanced Message Queuing Protocol (AMQP) is an open standard application layer protocol providing reliable transport of messages. QMF provides a modeling framework layer on top of qpid (which implements AMQP). This interface allows you to manage hosts, domains, pools etc. as a set of objects with properties and methods. It is very nice because it gives you a single source of information representing the whole distributed system, and is updated in a fairly timely manner (currently every 5 seconds). Note that at this time, only the node is qpid/amqp enabled. We are presently working on a ruby qpid client to allow us to make use of this on the WUI. Also note that qpidd authentication is disabled for now. The qpid folks are working on having gssapi integrated into qpid in a few weeks and we will switch to that when it's ready. With this patch in place, you can log into the WUI, and use qpid-tool (a generic qpid QMF client) to view and manipulate nodes/domains/pools etc. Here is an example session: [root at management etc]# qpid-tool Management Tool for QPID qpid: list Waiting for next periodic update (sometimes it takes a few seconds to get an update, keep trying) qpid: list Management Object Types: ObjectType Active Deleted ==================================================== com.redhat.libvirt:node 2 0 org.apache.qpid.broker:agent 2 0 ... (more qpid stuff) qpid: show node Object of type com.redhat.libvirt:node: (last sample time: 17:56:03) Type Element 101 133 ============================================================================== property hostname node136.priv.ovirt.org node123.priv.ovirt.org property uri qemu:///system qemu:///system property libvirt_version 0.4.6 0.4.6 property api_version 0.4.6 0.4.6 property hypervisor_version Unknown Unknown property hypervisor_type QEMU QEMU (after firing up a storage pool and a vm) qpid: list Management Object Types: ObjectType Active Deleted ==================================================== com.redhat.libvirt:domain 1 0 com.redhat.libvirt:node 2 0 com.redhat.libvirt:pool 1 0 com.redhat.libvirt:volume 3 0 ... qpid: show domain Object of type com.redhat.libvirt:domain: (last sample time: 18:06:16) Type Element 138 ================================================================= property uuid c922d718-4caf-bc56-3358-62d62bee4c5d property name test_vm property node 137 property state running property num_vcpus 1 property active true statistic maximum_memory 524288 statistic memory 524288 statistic cpu_time 149060000000 And a demo of calling methods: qpid: call 138 destroy qpid: Call Result: destroy 0 (OK) {} qpid: show domain Object of type com.redhat.libvirt:domain: (last sample time: 18:06:56) Type Element 103 ================================================================= property uuid c922d718-4caf-bc56-3358-62d62bee4c5d property name test_vm property node 102 property state shutoff property num_vcpus 1 property active false statistic maximum_memory 524288 statistic memory 524288 statistic cpu_time 0 From dpierce at redhat.com Thu Oct 2 18:17:04 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 02 Oct 2008 14:17:04 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. In-Reply-To: <48E50CCA.1010509@redhat.com> References: <1222704330-11127-1-git-send-email-dpierce@redhat.com> <48E41F2D.6020809@redhat.com> <48E4C6BB.8050404@redhat.com> <48E50CCA.1010509@redhat.com> Message-ID: <48E51020.1060204@redhat.com> Mohammed Morsi wrote: > Darryl, > > I haven't tried the latest patch that you sent yet, but noticed a few > things while doing some further testing. > > > Darryl Pierce wrote: >> Mohammed Morsi wrote: >> >>>> BuildRequires: ruby-devel >>>> +BuildRequires: ruby-flexmock >>>> BuildRequires: ruby-gettext-package >>>> BuildRequires: rubygem(rake) >= 0.7 >>>> Provides: ovirt-server >>>> >>>> >>> git-am reported some conflict with this file when I applied the patch. >>> Luckily this file contained a simple one line change so I was able to >>> manually copy it over and remove the file from the patch. >>> >> >> Weird. I only added the ruby-flexmock dependency since it's come up a >> few times. >> > First off, shouldn't this just be a 'Requires'? I think BuildRequires > implies that this needs to be on the machine hosting the appliance when > the rpms are being build where as 'Requires' implies it needs to be on > the appliance for this rpm to be installed which is what we want (since > its on the appliance that 'rake test' requires ruby-flexmock). I didn't realize we were running the tests in a live environment, and thought these were only being run when the RPM was being bundled. >>>> diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb >>>> index 5340e01..55a3458 100644 >>>> --- a/src/test/unit/host_browser_awaken_test.rb >>>> +++ b/src/test/unit/host_browser_awaken_test.rb >>>> @@ -64,7 +64,7 @@ class HostBrowserAwakenTest < Test::Unit::TestCase >>>> # Ensures that the server is satisfied if the remote system is >>>> # making a wakeup call. >>>> # >>>> - def test_get_mode_with_awaken_request >>>> + def test_get_mode_with_awaken_request >>>> >>>> >>> Same issue with trailing whitespace here. >>> >> >> I made sure to remove trailing whitespaces in these files since they'd >> creeped in previously. >> >> >>> >>>> @session.should_receive(:write).with("MODE?\n").once().returns { |request| request.length } >>>> @session.should_receive(:readline).once().returns { "AWAKEN\n" } >>>> >>>> >>>> >>> After applying the patch, I tried running the test cases but to no >>> avail, many were still broken. >>> >> >> This patch was only to fix the HostBrowser tests, none others; such bugs >> as the one to do with @first_id are outside of its scope. The tests that >> I fixed with this run without error. >> >> > I see here > http://git.et.redhat.com/?p=ovirt-server.git;a=commitdiff;h=0453e0e65bb0232acadbe3bf0e59cabf0df2f3f0 > that you removed where @first_id is set in the nic_controller_test. I > was able to get all the tests running successfully (see below) except > for test_show in the nic_controller_test which depends on @first_id > being set (and thus reports an "Couldn't find Nic without an ID" error). Okay, that was in a different patch. I deserve a halibut for that one. > > >> I'm sending an updated patch now. >> >> > > I discovered the reason everything was foobar was that the pools table > wasn't being populated in the ovirt_test database. This was caused by an > error earlier in the test process which in return caused 'rake test' to > abort and skip over the remaining test db population stuff. The error > was a path issue, eg test/unit/host_browser_identify_test.rb was unable > to find dutils.rb and host_browser.rb. Adding the following to > host_browser_identify_test.rb solves the problem: > > $: << File.join(File.dirname(__FILE__), "../../dutils") > $: << File.join(File.dirname(__FILE__), "../../host-browser") > > As previously mentioned, once this is done the only remaining error is > that due to the missing nic id. If we can get these fixes in today and > everything works, I'll uncomment the 'rake test' thats in the appliance > autobuild so that our continuous autobuild process will report any new > errors caused by subsequent changes immediately. Okay, I've taken these into consideration and am sending out a patch that addresses those issues. -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 2 18:19:56 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 2 Oct 2008 14:19:56 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. Message-ID: <1222971596-23395-1-git-send-email-dpierce@redhat.com> A change to the identify protocol was overlooked. When an empty value was submitted, host-browser would throw an exception. But, a change allowed empty values to get by without notice. Fixed. Also added a Requires dependency on ruby-flexmock so that the RPM is correctly installed when building the server RPM. Finally, reintroduced a line that was deleted by mistaken from NicControllerTest. Signed-off-by: Darryl L. Pierce --- ovirt-server.spec.in | 1 + src/host-browser/host-browser.rb | 9 +++++++-- src/test/functional/nic_controller_test.rb | 2 ++ src/test/unit/host_browser_awaken_test.rb | 3 +++ src/test/unit/host_browser_identify_test.rb | 3 +++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 114b52d..1339b52 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -19,6 +19,7 @@ Requires: rubygem(mongrel) >= 1.0.1 Requires: rubygem(krb5-auth) >= 0.6 Requires: rubygem(cobbler) >= 0.0.2 Requires: rubygem(gettext) +Requires: ruby-flexmock Requires: postgresql-server Requires: ruby-postgres Requires: xapian-bindings-ruby diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 3adea03..2241614 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -46,10 +46,15 @@ class HostBrowser def initialize(session) @session = session @keytab_dir = '/usr/share/ipa/html/' + set_peeraddr @session.peeraddr[3] + end + + def set_peeraddr(peeraddr) + @peeraddr = peeraddr end def prefix(session) - "#{Time.now.strftime('%b %d %H:%M:%S')} #{session.peeraddr[3]} " + "#{Time.now.strftime('%b %d %H:%M:%S')} #{@peeraddr} " end # Ensures the conversation starts properly. @@ -113,7 +118,7 @@ class HostBrowser nic_info << nic else - raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]*/ + raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]+/ key, value = info.split("=") diff --git a/src/test/functional/nic_controller_test.rb b/src/test/functional/nic_controller_test.rb index 74a80f4..1fa34ff 100644 --- a/src/test/functional/nic_controller_test.rb +++ b/src/test/functional/nic_controller_test.rb @@ -30,6 +30,8 @@ class NicControllerTest < Test::Unit::TestCase @controller = NicController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + + @first_id = nics(:one).id end def test_show diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb index 5340e01..53711ee 100644 --- a/src/test/unit/host_browser_awaken_test.rb +++ b/src/test/unit/host_browser_awaken_test.rb @@ -18,6 +18,9 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. +$: << File.join(File.dirname(__FILE__), "../../dutils") +$: << File.join(File.dirname(__FILE__), "../../host-browser") + require File.dirname(__FILE__) + '/../test_helper' require 'test/unit' require 'flexmock/test_unit' diff --git a/src/test/unit/host_browser_identify_test.rb b/src/test/unit/host_browser_identify_test.rb index 87c6f0b..86f5a73 100644 --- a/src/test/unit/host_browser_identify_test.rb +++ b/src/test/unit/host_browser_identify_test.rb @@ -18,6 +18,9 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. +$: << File.join(File.dirname(__FILE__), "../../dutils") +$: << File.join(File.dirname(__FILE__), "../../host-browser") + require File.dirname(__FILE__) + '/../test_helper' require 'test/unit' -- 1.5.5.1 From mmorsi at redhat.com Thu Oct 2 18:56:36 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 02 Oct 2008 14:56:36 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. In-Reply-To: <1222971596-23395-1-git-send-email-dpierce@redhat.com> References: <1222971596-23395-1-git-send-email-dpierce@redhat.com> Message-ID: <48E51964.408@redhat.com> Darryl L. Pierce wrote: > A change to the identify protocol was overlooked. When an empty value was > submitted, host-browser would throw an exception. But, a change allowed > empty values to get by without notice. Fixed. > > Also added a Requires dependency on ruby-flexmock so that the RPM is > correctly installed when building the server RPM. > > Finally, reintroduced a line that was deleted by mistaken from NicControllerTest. > > Signed-off-by: Darryl L. Pierce > --- > ovirt-server.spec.in | 1 + > src/host-browser/host-browser.rb | 9 +++++++-- > src/test/functional/nic_controller_test.rb | 2 ++ > src/test/unit/host_browser_awaken_test.rb | 3 +++ > src/test/unit/host_browser_identify_test.rb | 3 +++ > 5 files changed, 16 insertions(+), 2 deletions(-) > > diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in > index 114b52d..1339b52 100644 > --- a/ovirt-server.spec.in > +++ b/ovirt-server.spec.in > @@ -19,6 +19,7 @@ Requires: rubygem(mongrel) >= 1.0.1 > Requires: rubygem(krb5-auth) >= 0.6 > Requires: rubygem(cobbler) >= 0.0.2 > Requires: rubygem(gettext) > +Requires: ruby-flexmock > Requires: postgresql-server > Requires: ruby-postgres > Requires: xapian-bindings-ruby > diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb > index 3adea03..2241614 100755 > --- a/src/host-browser/host-browser.rb > +++ b/src/host-browser/host-browser.rb > @@ -46,10 +46,15 @@ class HostBrowser > def initialize(session) > @session = session > @keytab_dir = '/usr/share/ipa/html/' > + set_peeraddr @session.peeraddr[3] > + end > + > + def set_peeraddr(peeraddr) > + @peeraddr = peeraddr > end > > def prefix(session) > - "#{Time.now.strftime('%b %d %H:%M:%S')} #{session.peeraddr[3]} " > + "#{Time.now.strftime('%b %d %H:%M:%S')} #{@peeraddr} " > end > > # Ensures the conversation starts properly. > @@ -113,7 +118,7 @@ class HostBrowser > nic_info << nic > else > > - raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]*/ > + raise Exception.new("ERRINFO! Expected key=value : #{info}\n") unless info =~ /[\w]+[\s]*=[\w]+/ > > key, value = info.split("=") > > diff --git a/src/test/functional/nic_controller_test.rb b/src/test/functional/nic_controller_test.rb > index 74a80f4..1fa34ff 100644 > --- a/src/test/functional/nic_controller_test.rb > +++ b/src/test/functional/nic_controller_test.rb > @@ -30,6 +30,8 @@ class NicControllerTest < Test::Unit::TestCase > @controller = NicController.new > @request = ActionController::TestRequest.new > @response = ActionController::TestResponse.new > + > + @first_id = nics(:one).id > end > > def test_show > diff --git a/src/test/unit/host_browser_awaken_test.rb b/src/test/unit/host_browser_awaken_test.rb > index 5340e01..53711ee 100644 > --- a/src/test/unit/host_browser_awaken_test.rb > +++ b/src/test/unit/host_browser_awaken_test.rb > @@ -18,6 +18,9 @@ > # MA 02110-1301, USA. A copy of the GNU General Public License is > # also available at http://www.gnu.org/copyleft/gpl.html. > > +$: << File.join(File.dirname(__FILE__), "../../dutils") > +$: << File.join(File.dirname(__FILE__), "../../host-browser") > + > require File.dirname(__FILE__) + '/../test_helper' > require 'test/unit' > require 'flexmock/test_unit' > diff --git a/src/test/unit/host_browser_identify_test.rb b/src/test/unit/host_browser_identify_test.rb > index 87c6f0b..86f5a73 100644 > --- a/src/test/unit/host_browser_identify_test.rb > +++ b/src/test/unit/host_browser_identify_test.rb > @@ -18,6 +18,9 @@ > # MA 02110-1301, USA. A copy of the GNU General Public License is > # also available at http://www.gnu.org/copyleft/gpl.html. > > +$: << File.join(File.dirname(__FILE__), "../../dutils") > +$: << File.join(File.dirname(__FILE__), "../../host-browser") > + > require File.dirname(__FILE__) + '/../test_helper' > > require 'test/unit' > ACK, with a few caveats. Applying this patch once again complains about whitespace issues. It didn't affect my testing it as it applied fine, and figure you'll catch this as if you try to push with extraneous whitespace our repo won't let you. This takes care of all the outstanding errors I noticed up to this point. Unfortunately it seems that since these nic / browser issues are fixed, other errors are now apparent, particularly that pertaining to bonding. I attached the output of my 'rake test' run, so as to help in debugging. It seems the most prominent issue is 'type_id' which is attempted to be set in the bonding_test "setup" method, but which doesn't exist in the bondings database table. The last three errors (6,7,8) seem to indicate another problem which I'm not sure the cause of. -Mo -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: rake-test-output URL: From dpierce at redhat.com Thu Oct 2 19:01:36 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 02 Oct 2008 15:01:36 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed the tests for host-browser. In-Reply-To: <48E51964.408@redhat.com> References: <1222971596-23395-1-git-send-email-dpierce@redhat.com> <48E51964.408@redhat.com> Message-ID: <48E51A90.7050902@redhat.com> Mohammed Morsi wrote: > ACK, with a few caveats. > > Applying this patch once again complains about whitespace issues. It > didn't affect my testing it as it applied fine, and figure you'll catch > this as if you try to push with extraneous whitespace our repo won't let > you. > Gah, I'm starting to hate this editor (Netbeans) for the whitespaces. I've removed the whitespaces and will push without them. > This takes care of all the outstanding errors I noticed up to this > point. Unfortunately it seems that since these nic / browser issues are > fixed, other errors are now apparent, particularly that pertaining to > bonding. I attached the output of my 'rake test' run, so as to help in > debugging. It seems the most prominent issue is 'type_id' which is > attempted to be set in the bonding_test "setup" method, but which > doesn't exist in the bondings database table. The last three errors > (6,7,8) seem to indicate another problem which I'm not sure the cause of. I'll fix that test shortly. -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From mmorsi at redhat.com Thu Oct 2 20:28:58 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 02 Oct 2008 16:28:58 -0400 Subject: [Ovirt-devel] RRD Data Gen Script Message-ID: <48E52F0A.40308@redhat.com> -------------- 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 sseago at redhat.com Fri Oct 3 05:45:37 2008 From: sseago at redhat.com (Scott Seago) Date: Fri, 3 Oct 2008 01:45:37 -0400 Subject: [Ovirt-devel] [PATCH] initial model work for lvm storage Message-ID: <1223012737-31092-1-git-send-email-sseago@redhat.com> This includes the model classes w/ migrations for the lvm storage pools and volumes, and some changes required to support Storage Volume tasks. This patch does not include all of the necessary model API methods to support lvm, but it's a starting point -- some changes to clalance's taskomatic bits will be required to support this, and we will need additional model enhancements when the UI work is done, and when the taskomatic back end is finalized. Signed-off-by: Scott Seago --- src/app/models/host.rb | 2 +- src/app/models/host_task.rb | 11 ++- src/app/models/iscsi_storage_pool.rb | 2 +- .../models/{host_task.rb => lvm_storage_pool.rb} | 30 ++++-- .../{nfs_storage_pool.rb => lvm_storage_volume.rb} | 12 +-- src/app/models/nfs_storage_pool.rb | 2 +- src/app/models/storage_pool.rb | 11 ++- src/app/models/storage_task.rb | 11 ++- src/app/models/storage_volume.rb | 15 +++- .../{host_task.rb => storage_volume_task.rb} | 22 +++- src/app/models/task.rb | 15 ++- src/app/models/vm.rb | 2 +- src/app/models/vm_task.rb | 15 ++- src/db/migrate/023_add_lvm_storage.rb | 106 ++++++++++++++++++++ src/dutils/active_record_env.rb | 2 + 15 files changed, 219 insertions(+), 39 deletions(-) copy src/app/models/{host_task.rb => lvm_storage_pool.rb} (62%) copy src/app/models/{nfs_storage_pool.rb => lvm_storage_volume.rb} (82%) copy src/app/models/{host_task.rb => storage_volume_task.rb} (69%) create mode 100644 src/db/migrate/023_add_lvm_storage.rb diff --git a/src/app/models/host.rb b/src/app/models/host.rb index de5c5ee..546da19 100644 --- a/src/app/models/host.rb +++ b/src/app/models/host.rb @@ -31,7 +31,7 @@ class Host < ActiveRecord::Base def consuming_resources find(:all, :conditions=>{:state=>Vm::RUNNING_STATES}) end - has_many :tasks, :class_name => "HostTask", :dependent => :destroy, :order => "id DESC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end diff --git a/src/app/models/host_task.rb b/src/app/models/host_task.rb index 0aea41b..82298db 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/host_task.rb @@ -22,11 +22,20 @@ class HostTask < Task ACTION_CLEAR_VMS = "clear_vms" def after_initialize - self.hardware_pool = host.hardware_pool if self.host + self.hardware_pool = task_target.hardware_pool if self.task_target_type=="Host" end def task_obj "Host;;;#{self.host.id};;;#{self.host.hostname}" end + def vm + nil + end + def storage_volume + nil + end + def storage_pool + nil + end end diff --git a/src/app/models/iscsi_storage_pool.rb b/src/app/models/iscsi_storage_pool.rb index 1280834..7802a90 100644 --- a/src/app/models/iscsi_storage_pool.rb +++ b/src/app/models/iscsi_storage_pool.rb @@ -19,7 +19,7 @@ class IscsiStoragePool < StoragePool - validates_presence_of :port, :target + validates_presence_of :ip_addr, :port, :target validates_uniqueness_of :ip_addr, :scope => [:port, :target] def label_components diff --git a/src/app/models/host_task.rb b/src/app/models/lvm_storage_pool.rb similarity index 62% copy from src/app/models/host_task.rb copy to src/app/models/lvm_storage_pool.rb index 0aea41b..08b8938 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/lvm_storage_pool.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,16 +17,32 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class HostTask < Task +require 'util/ovirt' + +class LvmStoragePool < StoragePool + + has_many :source_volumes, :class_name => "StorageVolume", + :foreign_key => "lvm_pool_id", + :dependent => :nullify do + def total_size + find(:all).inject(0){ |sum, sv| sum + sv.size } + end + end - ACTION_CLEAR_VMS = "clear_vms" + validates_presence_of :vg_name + validates_uniqueness_of :vg_name - def after_initialize - self.hardware_pool = host.hardware_pool if self.host + def display_name + "#{get_type_label}: #{vg_name}" end - def task_obj - "Host;;;#{self.host.id};;;#{self.host.hostname}" + def size + source_volums.total_size end + def size_in_gb + kb_to_gb(size) + end + + end diff --git a/src/app/models/nfs_storage_pool.rb b/src/app/models/lvm_storage_volume.rb similarity index 82% copy from src/app/models/nfs_storage_pool.rb copy to src/app/models/lvm_storage_volume.rb index 2d05305..7dde2d1 100644 --- a/src/app/models/nfs_storage_pool.rb +++ b/src/app/models/lvm_storage_volume.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,12 +17,8 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class NfsStoragePool < StoragePool - - validates_presence_of :export_path - validates_uniqueness_of :ip_addr, :scope => :export_path - - def label_components - "#{export_path}" +class LvmStorageVolume < StorageVolume + def display_name + "#{get_type_label}: #{storage_pool.vg_name}:#{lv_name}" end end diff --git a/src/app/models/nfs_storage_pool.rb b/src/app/models/nfs_storage_pool.rb index 2d05305..a27944b 100644 --- a/src/app/models/nfs_storage_pool.rb +++ b/src/app/models/nfs_storage_pool.rb @@ -19,7 +19,7 @@ class NfsStoragePool < StoragePool - validates_presence_of :export_path + validates_presence_of :ip_addr, :export_path validates_uniqueness_of :ip_addr, :scope => :export_path def label_components diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb index bc98f8e..c49e090 100644 --- a/src/app/models/storage_pool.rb +++ b/src/app/models/storage_pool.rb @@ -19,7 +19,7 @@ class StoragePool < ActiveRecord::Base belongs_to :hardware_pool - has_many :tasks, :class_name => "StorageTask", :dependent => :destroy, :order => "id DESC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end @@ -33,14 +33,17 @@ class StoragePool < ActiveRecord::Base has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags - validates_presence_of :ip_addr, :hardware_pool_id + + validates_presence_of :hardware_pool_id acts_as_xapian :texts => [ :ip_addr, :target, :export_path, :type ], :terms => [ [ :search_users, 'U', "search_users" ] ] ISCSI = "iSCSI" NFS = "NFS" + LVM = "LVM" STORAGE_TYPES = { ISCSI => "Iscsi", - NFS => "Nfs" } + NFS => "Nfs", + LVM => "Lvm" } def self.factory(type, params = nil) case type @@ -48,6 +51,8 @@ class StoragePool < ActiveRecord::Base return IscsiStoragePool.new(params) when NFS return NfsStoragePool.new(params) + when LVM + return LvmStoragePool.new(params) else return nil end diff --git a/src/app/models/storage_task.rb b/src/app/models/storage_task.rb index db604d5..785f0ea 100644 --- a/src/app/models/storage_task.rb +++ b/src/app/models/storage_task.rb @@ -22,10 +22,19 @@ class StorageTask < Task ACTION_REFRESH_POOL = "refresh_pool" def after_initialize - self.hardware_pool = storage_pool.hardware_pool if self.storage_pool + self.hardware_pool = task_target.hardware_pool if self.task_target_type=="StoragePool" end def task_obj "StoragePool;;;#{self.storage_pool.id};;;#{self.storage_pool.display_name}" end + def host + nil + end + def vm + nil + end + def storage_volume + nil + end end diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb index ef9cd6e..378b58f 100644 --- a/src/app/models/storage_volume.rb +++ b/src/app/models/storage_volume.rb @@ -23,12 +23,23 @@ class StorageVolume < ActiveRecord::Base belongs_to :storage_pool has_and_belongs_to_many :vms + belongs_to :lvm_storage_pool, :class_name => "LvmStoragePool", + :foreign_key => "lvm_pool_id" + + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do + def queued + find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) + end + end + def self.factory(type, params = nil) case type - when "iSCSI" + when StoragePool::ISCSI return IscsiStorageVolume.new(params) - when "NFS" + when StoragePool::NFS return NfsStorageVolume.new(params) + when StoragePool::LVM + return LvmStorageVolume.new(params) else return nil end diff --git a/src/app/models/host_task.rb b/src/app/models/storage_volume_task.rb similarity index 69% copy from src/app/models/host_task.rb copy to src/app/models/storage_volume_task.rb index 0aea41b..78f2895 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/storage_volume_task.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,16 +17,26 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class HostTask < Task +class StorageVolumeTask < Task - ACTION_CLEAR_VMS = "clear_vms" + ACTION_CREATE_VOLUME = "create_volume" + ACTION_EDIT_VOLUME = "edit_volume" def after_initialize - self.hardware_pool = host.hardware_pool if self.host + self.hardware_pool = task_target.storage_pool.hardware_pool if self.task_target_type=="StorageVolume" end def task_obj - "Host;;;#{self.host.id};;;#{self.host.hostname}" + "StorageVolume;;;#{self.storage_volume.id};;;#{self.storage_volume.display_name}" + end + end + def host + nil + end + def vm + nil + end + def storage_pool + nil end - end diff --git a/src/app/models/task.rb b/src/app/models/task.rb index efbed64..f231c18 100644 --- a/src/app/models/task.rb +++ b/src/app/models/task.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -20,13 +20,20 @@ class Task < ActiveRecord::Base belongs_to :hardware_pool belongs_to :vm_resource_pool + belongs_to :task_target, :polymorphic => true # moved associations here so that nested set :include directives work # StorageTask association - belongs_to :storage_pool + belongs_to :storage_pool, :class_name => "StoragePool", + :foreign_key => "task_target_id" + # StorageVolumeTask association + belongs_to :storage_volume, :class_name => "StorageVolume", + :foreign_key => "task_target_id" # HostTask association - belongs_to :host + belongs_to :host, :class_name => "Host", + :foreign_key => "task_target_id" # VmTask association - belongs_to :vm + belongs_to :vm, :class_name => "Vm", + :foreign_key => "task_target_id" STATE_QUEUED = "queued" STATE_RUNNING = "running" diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..73e78f8 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -22,7 +22,7 @@ require 'util/ovirt' class Vm < ActiveRecord::Base belongs_to :vm_resource_pool belongs_to :host - has_many :tasks, :class_name => "VmTask", :dependent => :destroy, :order => "id ASC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end diff --git a/src/app/models/vm_task.rb b/src/app/models/vm_task.rb index 31c4ac8..2d93af2 100644 --- a/src/app/models/vm_task.rb +++ b/src/app/models/vm_task.rb @@ -107,9 +107,9 @@ class VmTask < Task :popup_action => 'migrate'} } def after_initialize - if self.vm - self.vm_resource_pool = vm.vm_resource_pool - self.hardware_pool = vm.get_hardware_pool + if self.task_target_type=="Vm" + self.vm_resource_pool = task_target.vm_resource_pool + self.hardware_pool = task_target.get_hardware_pool end end @@ -144,4 +144,13 @@ class VmTask < Task def self.label_and_action(action) return [action_label(action), action, action_icon(action)] end + def host + nil + end + def storage_volume + nil + end + def storage_pool + nil + end end diff --git a/src/db/migrate/023_add_lvm_storage.rb b/src/db/migrate/023_add_lvm_storage.rb new file mode 100644 index 0000000..697df4d --- /dev/null +++ b/src/db/migrate/023_add_lvm_storage.rb @@ -0,0 +1,106 @@ +# +# 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 AddLvmStorage < ActiveRecord::Migration + def self.up + #LVM pool does not use ip_addr + + # VG Name + add_column :storage_pools, :vg_name, :string + + # LV name + add_column :storage_volumes, :lv_name, :string + # LV capacity==existing size attr + + # LV + # FIXME: do we want to make these user-determined, or should + # these be defined by the model itself? + add_column :storage_volumes, :lv_owner_perms, :string + add_column :storage_volumes, :lv_group_perms, :string + add_column :storage_volumes, :lv_mode_perms, :string + + # VG pool ID + add_column :storage_volumes, :lvm_pool_id, :integer + execute "alter table storage_volumes add constraint fk_storage_volumes_lvm_pools + foreign key (lvm_pool_id) references storage_pools(id)" + + # use polymorphic tasks association + add_column :tasks, :task_target_id, :integer + add_column :tasks, :task_target_type, :string + begin + Task.transaction do + HostTask.find(:all).each do |task| + task.task_target_type = 'Host' + task.task_target_id = task.host_id + task.save! + end + StorageTask.find(:all).each do |task| + task.task_target_type = 'StoragePool' + task.task_target_id = task.storage_pool_id + task.save! + end + VmTask.find(:all).each do |task| + task.task_target_type = 'Vm' + task.task_target_id = task.vm_id + task.save! + end + end + remove_column :tasks, :vm_id + remove_column :tasks, :storage_pool_id + remove_column :tasks, :host_id + rescue + puts "could not update tasks..." + end + + end + + def self.down + remove_column :storage_pools, :vg_name + + remove_column :storage_volumes, :lv_name + remove_column :storage_volumes, :lv_owner_perms + remove_column :storage_volumes, :lv_group_perms + remove_column :storage_volumes, :lv_mode_perms + remove_column :storage_volumes, :lvm_pool_id + + add_column :tasks, :vm_id, :integer + add_column :tasks, :storage_pool_id, :integer + add_column :tasks, :host_id, :integer + begin + Task.transaction do + HostTask.find(:all).each do |task| + task.host_id = task.task_target_id + task.save! + end + StorageTask.find(:all).each do |task| + task.storage_pool_id = task.task_target_id + task.save! + end + VmTask.find(:all).each do |task| + task.vm_id = task.task_target_id + task.save! + end + end + remove_column :tasks, :task_target_id + remove_column :tasks, :task_target_type + rescue + puts "could not update tasks..." + end + end +end diff --git a/src/dutils/active_record_env.rb b/src/dutils/active_record_env.rb index c6f37eb..bcec565 100644 --- a/src/dutils/active_record_env.rb +++ b/src/dutils/active_record_env.rb @@ -78,10 +78,12 @@ require 'models/vm_task.rb' require 'models/storage_pool.rb' require 'models/iscsi_storage_pool.rb' require 'models/nfs_storage_pool.rb' +require 'models/lvm_storage_pool.rb' require 'models/storage_volume.rb' require 'models/iscsi_storage_volume.rb' require 'models/nfs_storage_volume.rb' +require 'models/lvm_storage_volume.rb' require 'models/smart_pool.rb' require 'models/smart_pool_tag.rb' -- 1.5.5.1 From jeffschroed at gmail.com Fri Oct 3 05:35:31 2008 From: jeffschroed at gmail.com (Jeff Schroeder) Date: Thu, 2 Oct 2008 22:35:31 -0700 Subject: [Ovirt-devel] [PATCH] Add trivial error() function and change code to use it In-Reply-To: References: Message-ID: <1223012131-10557-1-git-send-email-jeffschroeder@computer.org> Remove the multiple echo ... && exit 1 references and replace them with a simple function. This makes the code a bit cleaner. Signed-off-by: Jeff Schroeder --- wui/scripts/ovirt-wui-install | 17 +++++++++++------ 1 files changed, 11 insertions(+), 6 deletions(-) diff --git a/wui/scripts/ovirt-wui-install b/wui/scripts/ovirt-wui-install index 44798fc..1f5e1eb 100755 --- a/wui/scripts/ovirt-wui-install +++ b/wui/scripts/ovirt-wui-install @@ -31,6 +31,11 @@ usage() { exit 1 } >&2 +error() { + echo $* >&2 + exit 1 +} + find_srv() { local dnsreply @@ -112,7 +117,7 @@ find_srv ldap tcp if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then SRV_HOST=${SRV_HOST%.} srv_base=$(find_ldap_base) - [ $? != 0 ] && echo "Failed to determine base for ldap" && exit 1 + [ $? != 0 ] && error "Failed to determine base for ldap" sed -i -e "s/host: .*/host: $SRV_HOST/g" \ -e "s/port: .*/port: $SRV_PORT/g" \ @@ -121,7 +126,7 @@ if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then else # FIXME: Eventually this script should prompt for things that can't # be found in DNS SRV records. - echo "Failed to get ldap host/port" && exit 1 + error "Failed to get ldap host/port" fi # setup an NTP step-ticker @@ -153,7 +158,7 @@ echo "local all all trust" > /var/lib/pgsql/data/pg_hba.conf echo "host all all 127.0.0.1 255.255.255.0 trust" >> /var/lib/pgsql/data/pg_hba.conf service postgresql stop > /dev/null 2>&1 service postgresql start -[ $? != 0 ] && echo "Failed to start database" && exit 1 +[ $? != 0 ] && error "Failed to start database" wait_for_service 'psql -l -U postgres' 10 2 || exit 1 if [ -z $PASSWD ]; then @@ -168,7 +173,7 @@ su - postgres -c "/usr/bin/dropdb $DATABASE > /dev/null 2>&1" # create new DB su - postgres -c "/usr/bin/createdb $DATABASE" -[ $? != 0 ] && echo "Failed to create database $DATABASE" && exit 1 +[ $? != 0 ] && error "Failed to create database $DATABASE" su - postgres -c "psql --dbname $DATABASE < References: <1223012131-10557-1-git-send-email-jeffschroeder@computer.org> Message-ID: <48E61F83.7040605@redhat.com> Jeff Schroeder wrote: > Remove the multiple echo ... && exit 1 references and replace > them with a simple function. This makes the code a bit cleaner. No problem with this patch in concept, but this patch appears to be against the old repository. See the instructions on: http://ovirt.org/build-instructions.html for getting access to the new repositories. The file you're patching is now located in the ovirt-server repository and it's location is: scripts/ovirt-server-install Thanks! Perry > Signed-off-by: Jeff Schroeder > --- > wui/scripts/ovirt-wui-install | 17 +++++++++++------ > 1 files changed, 11 insertions(+), 6 deletions(-) > > diff --git a/wui/scripts/ovirt-wui-install b/wui/scripts/ovirt-wui-install > index 44798fc..1f5e1eb 100755 > --- a/wui/scripts/ovirt-wui-install > +++ b/wui/scripts/ovirt-wui-install > @@ -31,6 +31,11 @@ usage() { > exit 1 > } >&2 > > +error() { > + echo $* >&2 > + exit 1 > +} > + > find_srv() { > local dnsreply > > @@ -112,7 +117,7 @@ find_srv ldap tcp > if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then > SRV_HOST=${SRV_HOST%.} > srv_base=$(find_ldap_base) > - [ $? != 0 ] && echo "Failed to determine base for ldap" && exit 1 > + [ $? != 0 ] && error "Failed to determine base for ldap" > > sed -i -e "s/host: .*/host: $SRV_HOST/g" \ > -e "s/port: .*/port: $SRV_PORT/g" \ > @@ -121,7 +126,7 @@ if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then > else > # FIXME: Eventually this script should prompt for things that can't > # be found in DNS SRV records. > - echo "Failed to get ldap host/port" && exit 1 > + error "Failed to get ldap host/port" > fi > > # setup an NTP step-ticker > @@ -153,7 +158,7 @@ echo "local all all trust" > /var/lib/pgsql/data/pg_hba.conf > echo "host all all 127.0.0.1 255.255.255.0 trust" >> /var/lib/pgsql/data/pg_hba.conf > service postgresql stop > /dev/null 2>&1 > service postgresql start > -[ $? != 0 ] && echo "Failed to start database" && exit 1 > +[ $? != 0 ] && error "Failed to start database" > wait_for_service 'psql -l -U postgres' 10 2 || exit 1 > > if [ -z $PASSWD ]; then > @@ -168,7 +173,7 @@ su - postgres -c "/usr/bin/dropdb $DATABASE > /dev/null 2>&1" > > # create new DB > su - postgres -c "/usr/bin/createdb $DATABASE" > -[ $? != 0 ] && echo "Failed to create database $DATABASE" && exit 1 > +[ $? != 0 ] && error "Failed to create database $DATABASE" > > su - postgres -c "psql --dbname $DATABASE < DROP ROLE $USERNAME; > @@ -180,7 +185,7 @@ su - postgres -c "psql --dbname $DATABASE < VALID UNTIL 'infinity'; > GRANT ALL ON DATABASE $DATABASE TO $USERNAME; > EOF" > -[ $? != 0 ] && echo "Failed to run database setup" && exit 1 > +[ $? != 0 ] && error "Failed to run database setup" > > touch $EXISTS_FILE > > @@ -190,7 +195,7 @@ rake db:migrate > cd - > > ${OVIRT_DIR}/script/grant_admin_privileges ovirtadmin > -[ $? != 0 ] && echo "Failed to grant ovirtadmin privileges" && exit 1 > +[ $? != 0 ] && error "Failed to grant ovirtadmin privileges" > > ovirt-add-host $(hostname) ${OVIRT_DIR}/ovirt.keytab > -- |=- 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 mwagner at redhat.com Fri Oct 3 13:56:22 2008 From: mwagner at redhat.com (Mark Wagner) Date: Fri, 03 Oct 2008 09:56:22 -0400 Subject: [Ovirt-devel] RRD Data Gen Script In-Reply-To: <48E52F0A.40308@redhat.com> References: <48E52F0A.40308@redhat.com> Message-ID: <48E62486.60600@redhat.com> Mohammed Morsi wrote: > ------------------------------------------------------------------------ > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > Looks like the exact same patch I NACK'd back in August Will look at fixing this over the weekend NACK From jguiditt at redhat.com Fri Oct 3 20:39:14 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 3 Oct 2008 16:39:14 -0400 Subject: [Ovirt-devel] [PATCH server] Version 1 of Revamped Tree Navigation. Message-ID: <1223066354-21940-1-git-send-email-jguiditt@redhat.com> The new javascript tree widget contains the following features/changes from previous implementation: * The html for the list is dynamically generated using a javascript template system. This will allow us to plug in different layouts per tree as the widget matures. * Updates to the tree are now incremental, rather than a full rip and replace as earlier. We have 2 states we currently look for - 'new' and 'changed'. The first generates new html and appends it to the DOM, the second just does a replacement of the content of existing nodes. * Vastly simplified the markup and css. * Added calls where appropriate to refresh the tree before next planned call (for instance, if you add a new hardware pool). * Added slide effect when opening and closing a node of the tree. * Clicking the plus/minus opens/close the node only, does not load main content area. * Clicking anywhere to the right of that on a given node will load content area. * Added interim icons for 'smartpool' and 'add smartpool' Note that aside from the nav area, this should not impact the existing trees which have not been converted yet (all popups that have one), as this is a completely separate codebase with it's own js and css files. Related, but not technically part of the tree, I added a choose_layout method to allow testing of javascript components as we are building them to help eliminate possible side effects from other code. When not in a production environment, you can pass in ?component_layout=[name] where [name] is the name of a shell rhtml file you have put in views/layouts/components. As our UI is growing increasingly complex, I think this will be a very useful way to facilitate building components. Signed-off-by: Jason Guiditta --- src/app/controllers/application.rb | 19 +- src/app/controllers/tree_controller.rb | 70 ++++- src/app/models/pool.rb | 18 +- src/app/views/layouts/_side_toolbar.rhtml | 2 +- src/app/views/layouts/_tree.rhtml | 148 ++++++-- src/app/views/layouts/components/tree.rhtml | 61 +++ src/app/views/layouts/redux.rhtml | 40 +-- src/public/images/icon_add_smartpool.png | Bin 0 -> 1341 bytes src/public/images/icon_smartpool.png | Bin 0 -> 641 bytes src/public/javascripts/ovirt.js | 28 +- src/public/javascripts/ovirt.tree.js | 71 ++++ src/public/javascripts/smart_nav_test_data.js | 151 ++++++++ src/public/javascripts/trimpath-template-1.0.38.js | 397 ++++++++++++++++++++ src/public/stylesheets/ovirt-tree/tree.css | 83 ++++ 14 files changed, 994 insertions(+), 94 deletions(-) create mode 100644 src/app/views/layouts/components/tree.rhtml create mode 100644 src/public/images/icon_add_smartpool.png create mode 100644 src/public/images/icon_smartpool.png create mode 100644 src/public/javascripts/ovirt.tree.js create mode 100644 src/public/javascripts/smart_nav_test_data.js create mode 100644 src/public/javascripts/trimpath-template-1.0.38.js create mode 100644 src/public/stylesheets/ovirt-tree/tree.css diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb index 6dcf6f8..a751768 100644 --- a/src/app/controllers/application.rb +++ b/src/app/controllers/application.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base # Pick a unique cookie name to distinguish our session data from others' session :session_key => '_ovirt_session_id' init_gettext "ovirt" - layout 'redux' + layout :choose_layout before_filter :pre_new, :only => [:new] before_filter :pre_create, :only => [:create] @@ -33,6 +33,13 @@ class ApplicationController < ActionController::Base before_filter :authorize_admin, :only => [:new, :create, :edit, :update, :destroy] before_filter :is_logged_in + def choose_layout + if(params[:component_layout]) + return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux' + end + return 'redux' + end + def is_logged_in redirect_to(:controller => "login", :action => "login") unless get_login_user end @@ -40,7 +47,7 @@ class ApplicationController < ActionController::Base def get_login_user (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin" end - + def set_perms(hwpool) @user = get_login_user @can_view = hwpool.can_view(@user) @@ -91,8 +98,8 @@ class ApplicationController < ActionController::Base # don't define find_opts for array inputs def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) page = params[:page].to_i - paginate_opts = {:page => page, - :order => "#{params[:sortname]} #{params[:sortorder]}", + paginate_opts = {:page => page, + :order => "#{params[:sortname]} #{params[:sortorder]}", :per_page => params[:rp]} arg_list << find_opts.merge(paginate_opts) item_list = full_items.paginate(*arg_list) @@ -102,7 +109,7 @@ class ApplicationController < ActionController::Base json_hash[:rows] = item_list.collect do |item| item_hash = {} item_hash[:id] = item.send(id_method) - item_hash[:cell] = attributes.collect do |attr| + item_hash[:cell] = attributes.collect do |attr| if attr.is_a? Array value = item attr.each { |attr_item| value = value.send(attr_item)} diff --git a/src/app/controllers/tree_controller.rb b/src/app/controllers/tree_controller.rb index 1aed544..bc423b7 100644 --- a/src/app/controllers/tree_controller.rb +++ b/src/app/controllers/tree_controller.rb @@ -1,5 +1,4 @@ class TreeController < ApplicationController - def get_pools # TODO: split these into separate hash elements for HW and smart pools pools = HardwarePool.get_default_pool.full_set_nested(:method => :json_hash_element, @@ -8,11 +7,78 @@ class TreeController < ApplicationController :privilege => Permission::PRIV_VIEW, :user => get_login_user, :smart_pool_set => true) end + def fetch_nav @pools = get_pools end - + def fetch_json render :json => get_pools.to_json end + + def return_filtered_list + @ids = Array.new + @clientHash = {} + if (params[:item]) + params[:item].each { |item| + tempItem = item.split("-") + itemHash = { + :id => tempItem[0].to_s, + :name =>tempItem[1] + } + @clientHash[tempItem[0]] = itemHash + } + end + @serverHash = {:pools => build_json(HardwarePool.get_default_pool.full_set_nested(:method => :json_hash_element, + :privilege => Permission::PRIV_VIEW, :user => get_login_user)) + } + @serverHash[:smart_pools] = adjust_smart_pool_list(build_json(DirectoryPool.get_smart_root.full_set_nested(:method => :json_hash_element, + :privilege => Permission::PRIV_VIEW, :user => get_login_user, + :smart_pool_set => true))) + @ids.each { |item| + if @clientHash.has_key?(item.to_s) + @clientHash.delete(item.to_s) + end + } + @serverHash[:deleted] = @clientHash + render :json => @serverHash.to_json + end + + private + def build_json(list) + list.each {|listItem| + process_list_item(listItem) + if listItem.has_key?(:children) + build_json(listItem[:children]) + end + } + list + end + + def process_list_item(list) + if @clientHash.has_key?(list[:id].to_s) + unless @clientHash[list[:id].to_s][:name] == list[:name] + list[:state] = "changed" + else + list[:state] = "unchanged" + end + else + list[:state] = "new" + end + @ids = @ids.push(list[:id]) + end + + def adjust_smart_pool_list(list) + adjustedList = Array.new + list.each {|listItem| + if (listItem[:name] == get_login_user) + listItem[:children].each {|item| + adjustedList.push(item) + } + else + adjustedList.push(listItem) + end + } + adjustedList + end end diff --git a/src/app/models/pool.rb b/src/app/models/pool.rb index eb71be8..d189649 100644 --- a/src/app/models/pool.rb +++ b/src/app/models/pool.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -45,7 +45,7 @@ class Pool < ActiveRecord::Base has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags - # used to allow parent traversal before obj is saved to the db + # used to allow parent traversal before obj is saved to the db # (needed for view code 'create' form) attr_accessor :tmp_parent @@ -168,10 +168,10 @@ class Pool < ActiveRecord::Base end end - RESOURCE_LABELS = [["CPUs", :cpus, ""], - ["Memory", :memory_in_mb, "(mb)"], - ["NICs", :nics, ""], - ["VMs", :vms, ""], + RESOURCE_LABELS = [["CPUs", :cpus, ""], + ["Memory", :memory_in_mb, "(mb)"], + ["NICs", :nics, ""], + ["VMs", :vms, ""], ["Disk", :storage_in_gb, "(gb)"]] #needed by tree widget for display @@ -195,7 +195,7 @@ class Pool < ActiveRecord::Base found = false open_list.each do |open_pool| if pool.id == open_pool.id - new_open_list = open_list[(open_list.index(open_pool)+1)..-1] + new_open_list = open_list[(open_list.index(open_pool)+1)..-1] unless new_open_list.empty? pool_children = pool.children unless pool_children hash[:children] = pool_hash(pool_children, new_open_list, filter_vm_pools) @@ -210,7 +210,7 @@ class Pool < ActiveRecord::Base end def json_hash_element - { :id => id, :type => self[:type], :text => name, :name => name} + { :id => id, :type => self[:type], :text => name, :name => name, :parent_id => parent_id} end def hash_element @@ -272,7 +272,7 @@ class Pool < ActiveRecord::Base obj = args.shift method = args.shift obj.send(method, *args) - end + end def display_name name diff --git a/src/app/views/layouts/_side_toolbar.rhtml b/src/app/views/layouts/_side_toolbar.rhtml index e1958f1..4b92bcf 100644 --- a/src/app/views/layouts/_side_toolbar.rhtml +++ b/src/app/views/layouts/_side_toolbar.rhtml @@ -25,7 +25,7 @@ <% end -%> <%if pool -%> diff --git a/src/app/views/layouts/_tree.rhtml b/src/app/views/layouts/_tree.rhtml index 0e6e138..a6bde14 100644 --- a/src/app/views/layouts/_tree.rhtml +++ b/src/app/views/layouts/_tree.rhtml @@ -1,32 +1,124 @@ -
<%= image_tag("icon_dashboard.gif")%>
- <% selected = "selected" if controller.controller_name == "dashboard" && params[:action] == "index" %> -
- <%= link_to "Dashboard", { :controller => "dashboard" }, { :id => "dashboard", :class => "#{selected}" } %> -
-
-<%= javascript_include_tag "jquery.ovirt.treeview.js" -%> - -
    -
- + + + + + + + + diff --git a/src/app/views/layouts/components/tree.rhtml b/src/app/views/layouts/components/tree.rhtml new file mode 100644 index 0000000..063a6df --- /dev/null +++ b/src/app/views/layouts/components/tree.rhtml @@ -0,0 +1,61 @@ + + + + + + + <%= yield :title -%> + <%= javascript_include_tag "jquery-1.2.6.min.js" -%> + <%= javascript_include_tag "jquery.livequery.min.js" -%> + <%= javascript_include_tag "smart_nav_test_data.js" -%> + <%= javascript_include_tag "jquery.form.js" -%> + <%= javascript_include_tag "ovirt.js" -%> + + + <%= yield :scripts -%> + + + +
+ + <%= render :partial => '/layouts/tree' %> +
+
+ <%= render :partial => '/layouts/side_toolbar' %> +
+ +
+
+
+ +
+
+
+ + diff --git a/src/app/views/layouts/redux.rhtml b/src/app/views/layouts/redux.rhtml index 01540d4..a985cdd 100644 --- a/src/app/views/layouts/redux.rhtml +++ b/src/app/views/layouts/redux.rhtml @@ -16,20 +16,18 @@ <%= stylesheet_link_tag 'flexigrid/flexigrid.css' %> <%= stylesheet_link_tag 'facebox' %> <%= stylesheet_link_tag 'jquery.jgrowl.css' %> - + <%= javascript_include_tag "jquery-1.2.6.min.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.async.js" -%> <%= javascript_include_tag "flexigrid.js" -%> <%= javascript_include_tag "facebox.js" -%> - <%= javascript_include_tag "jquery.timers.js" -%> + <%#= javascript_include_tag "jquery.timers.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svg.pack.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svggraph.js" -%> - <%= javascript_include_tag "jquery.cookie.js" -%> - <%= javascript_include_tag "jquery.livequery.pack.js" -%> + <%= javascript_include_tag "jquery.livequery.min.js" -%> <%= javascript_include_tag "jquery.form.js" -%> <%= javascript_include_tag "jquery.jgrowl.js" -%> @@ -40,37 +38,9 @@ <%= javascript_include_tag "ovirt.js" -%> + <%= yield :scripts -%> + + + +
+ + <%= render :partial => '/layouts/tree' %> +
+
+ <%= render :partial => '/layouts/side_toolbar' %> +
+ +
+
+
+ +
+
+
+ + diff --git a/src/app/views/layouts/redux.rhtml b/src/app/views/layouts/redux.rhtml index 01540d4..a985cdd 100644 --- a/src/app/views/layouts/redux.rhtml +++ b/src/app/views/layouts/redux.rhtml @@ -16,20 +16,18 @@ <%= stylesheet_link_tag 'flexigrid/flexigrid.css' %> <%= stylesheet_link_tag 'facebox' %> <%= stylesheet_link_tag 'jquery.jgrowl.css' %> - + <%= javascript_include_tag "jquery-1.2.6.min.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.async.js" -%> <%= javascript_include_tag "flexigrid.js" -%> <%= javascript_include_tag "facebox.js" -%> - <%= javascript_include_tag "jquery.timers.js" -%> + <%#= javascript_include_tag "jquery.timers.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svg.pack.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svggraph.js" -%> - <%= javascript_include_tag "jquery.cookie.js" -%> - <%= javascript_include_tag "jquery.livequery.pack.js" -%> + <%= javascript_include_tag "jquery.livequery.min.js" -%> <%= javascript_include_tag "jquery.form.js" -%> <%= javascript_include_tag "jquery.jgrowl.js" -%> @@ -40,37 +38,9 @@ <%= javascript_include_tag "ovirt.js" -%> -
    -
- + + + + + + + + diff --git a/src/app/views/layouts/components/tree.rhtml b/src/app/views/layouts/components/tree.rhtml new file mode 100644 index 0000000..063a6df --- /dev/null +++ b/src/app/views/layouts/components/tree.rhtml @@ -0,0 +1,61 @@ + + + + + + + <%= yield :title -%> + <%= javascript_include_tag "jquery-1.2.6.min.js" -%> + <%= javascript_include_tag "jquery.livequery.min.js" -%> + <%= javascript_include_tag "smart_nav_test_data.js" -%> + <%= javascript_include_tag "jquery.form.js" -%> + <%= javascript_include_tag "ovirt.js" -%> + + + <%= yield :scripts -%> + + + +
+ + <%= render :partial => '/layouts/tree' %> +
+
+ <%= render :partial => '/layouts/side_toolbar' %> +
+ +
+
+
+ +
+
+
+ + diff --git a/src/app/views/layouts/redux.rhtml b/src/app/views/layouts/redux.rhtml index 01540d4..a985cdd 100644 --- a/src/app/views/layouts/redux.rhtml +++ b/src/app/views/layouts/redux.rhtml @@ -16,20 +16,18 @@ <%= stylesheet_link_tag 'flexigrid/flexigrid.css' %> <%= stylesheet_link_tag 'facebox' %> <%= stylesheet_link_tag 'jquery.jgrowl.css' %> - + <%= javascript_include_tag "jquery-1.2.6.min.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.js" -%> <%= javascript_include_tag "jquery-treeview/jquery.treeview.async.js" -%> <%= javascript_include_tag "flexigrid.js" -%> <%= javascript_include_tag "facebox.js" -%> - <%= javascript_include_tag "jquery.timers.js" -%> + <%#= javascript_include_tag "jquery.timers.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svg.pack.js" -%> <%= javascript_include_tag "jquery-svg/jquery.svggraph.js" -%> - <%= javascript_include_tag "jquery.cookie.js" -%> - <%= javascript_include_tag "jquery.livequery.pack.js" -%> + <%= javascript_include_tag "jquery.livequery.min.js" -%> <%= javascript_include_tag "jquery.form.js" -%> <%= javascript_include_tag "jquery.jgrowl.js" -%> @@ -40,37 +38,9 @@ <%= javascript_include_tag "ovirt.js" -%> - -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_1_graph', :div_id => 'cpu_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_7_graph', :div_id => 'cpu_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_30_graph', :div_id => 'cpu_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 30 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_1_graph', :div_id => 'memory_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_7_graph', :div_id => 'memory_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_30_graph', :div_id => 'memory_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1162, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 30 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_1_graph', :div_id => 'load_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_7_graph', :div_id => 'load_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_30_graph', :div_id => 'load_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 30 } ) } %> - -
-
-
- -
-
- -
-
- Peak     - Average     - Rolling Peak     - Rolling Average     -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - diff --git a/src/config/routes.rb b/src/config/routes.rb index 6f8e481..8d538cb 100644 --- a/src/config/routes.rb +++ b/src/config/routes.rb @@ -41,6 +41,7 @@ ActionController::Routing::Routes.draw do |map| map.connect ':controller/service.wsdl', :action => 'wsdl' # Install the default route as the lowest priority. + map.connect 'graph/flexchart_data/:id/:target/:days', :controller => 'graph', :action => 'flexchart_data' map.connect ':controller/:action/:id.:format' map.connect ':controller/:action/:id' diff --git a/src/flexchart/README.txt b/src/flexchart/README.txt new file mode 100644 index 0000000..7402112 --- /dev/null +++ b/src/flexchart/README.txt @@ -0,0 +1,8 @@ +Until mxmlc gets packaged and this becomes part of autobuild, +you must obtain the open flex SDK to build the swf movie. + +Once you have mxmlc on your system, run: + +mxmlc flexchart.mxml + +in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public on your appliance. diff --git a/src/flexchart/com/adobe/serialization/json/JSON.as b/src/flexchart/com/adobe/serialization/json/JSON.as new file mode 100644 index 0000000..bfee6d9 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSON.as @@ -0,0 +1,85 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * This class provides encoding and decoding of the JSON format. + * + * Example usage: + * + * // create a JSON string from an internal object + * JSON.encode( myObject ); + * + * // read a JSON string into an internal object + * var myObject:Object = JSON.decode( jsonString ); + * + */ + public class JSON { + + + /** + * Encodes a object into a JSON string. + * + * @param o The object to create a JSON string for + * @return the JSON string representing o + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public static function encode( o:Object ):String { + + var encoder:JSONEncoder = new JSONEncoder( o ); + return encoder.getString(); + + } + + /** + * Decodes a JSON string into a native object. + * + * @param s The JSON string representing the object + * @return A native object as specified by s + * @throw JSONParseError + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public static function decode( s:String ):* { + + var decoder:JSONDecoder = new JSONDecoder( s ) + return decoder.getValue(); + + } + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONDecoder.as b/src/flexchart/com/adobe/serialization/json/JSONDecoder.as new file mode 100644 index 0000000..82ade19 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONDecoder.as @@ -0,0 +1,221 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONDecoder { + + /** The value that will get parsed from the JSON string */ + private var value:*; + + /** The tokenizer designated to read the JSON string */ + private var tokenizer:JSONTokenizer; + + /** The current token from the tokenizer */ + private var token:JSONToken; + + /** + * Constructs a new JSONDecoder to parse a JSON string + * into a native object. + * + * @param s The JSON string to be converted + * into a native object + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONDecoder( s:String ) { + + tokenizer = new JSONTokenizer( s ); + + nextToken(); + value = parseValue(); + } + + /** + * Gets the internal object that was created by parsing + * the JSON string passed to the constructor. + * + * @return The internal object representation of the JSON + * string that was passed to the constructor + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function getValue():* { + return value; + } + + /** + * Returns the next token from the tokenzier reading + * the JSON string + */ + private function nextToken():JSONToken { + return token = tokenizer.getNextToken(); + } + + /** + * Attempt to parse an array + */ + private function parseArray():Array { + // create an array internally that we're going to attempt + // to parse from the tokenizer + var a:Array = new Array(); + + // grab the next token from the tokenizer to move + // past the opening [ + nextToken(); + + // check to see if we have an empty array + if ( token.type == JSONTokenType.RIGHT_BRACKET ) { + // we're done reading the array, so return it + return a; + } + + // deal with elements of the array, and use an "infinite" + // loop because we could have any amount of elements + while ( true ) { + // read in the value and add it to the array + a.push ( parseValue() ); + + // after the value there should be a ] or a , + nextToken(); + + if ( token.type == JSONTokenType.RIGHT_BRACKET ) { + // we're done reading the array, so return it + return a; + } else if ( token.type == JSONTokenType.COMMA ) { + // move past the comma and read another value + nextToken(); + } else { + tokenizer.parseError( "Expecting ] or , but found " + token.value ); + } + } + return null; + } + + /** + * Attempt to parse an object + */ + private function parseObject():Object { + // create the object internally that we're going to + // attempt to parse from the tokenizer + var o:Object = new Object(); + + // store the string part of an object member so + // that we can assign it a value in the object + var key:String + + // grab the next token from the tokenizer + nextToken(); + + // check to see if we have an empty object + if ( token.type == JSONTokenType.RIGHT_BRACE ) { + // we're done reading the object, so return it + return o; + } + + // deal with members of the object, and use an "infinite" + // loop because we could have any amount of members + while ( true ) { + + if ( token.type == JSONTokenType.STRING ) { + // the string value we read is the key for the object + key = String( token.value ); + + // move past the string to see what's next + nextToken(); + + // after the string there should be a : + if ( token.type == JSONTokenType.COLON ) { + + // move past the : and read/assign a value for the key + nextToken(); + o[key] = parseValue(); + + // move past the value to see what's next + nextToken(); + + // after the value there's either a } or a , + if ( token.type == JSONTokenType.RIGHT_BRACE ) { + // // we're done reading the object, so return it + return o; + + } else if ( token.type == JSONTokenType.COMMA ) { + // skip past the comma and read another member + nextToken(); + } else { + tokenizer.parseError( "Expecting } or , but found " + token.value ); + } + } else { + tokenizer.parseError( "Expecting : but found " + token.value ); + } + } else { + tokenizer.parseError( "Expecting string but found " + token.value ); + } + } + return null; + } + + /** + * Attempt to parse a value + */ + private function parseValue():Object + { + // Catch errors when the input stream ends abruptly + if ( token == null ) + { + tokenizer.parseError( "Unexpected end of input" ); + } + + switch ( token.type ) { + case JSONTokenType.LEFT_BRACE: + return parseObject(); + + case JSONTokenType.LEFT_BRACKET: + return parseArray(); + + case JSONTokenType.STRING: + case JSONTokenType.NUMBER: + case JSONTokenType.TRUE: + case JSONTokenType.FALSE: + case JSONTokenType.NULL: + return token.value; + + default: + tokenizer.parseError( "Unexpected " + token.value ); + + } + return null; + } + } +} diff --git a/src/flexchart/com/adobe/serialization/json/JSONEncoder.as b/src/flexchart/com/adobe/serialization/json/JSONEncoder.as new file mode 100644 index 0000000..44469d0 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONEncoder.as @@ -0,0 +1,299 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json +{ + + import flash.utils.describeType; + + public class JSONEncoder { + + /** The string that is going to represent the object we're encoding */ + private var jsonString:String; + + /** + * Creates a new JSONEncoder. + * + * @param o The object to encode as a JSON string + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONEncoder( value:* ) { + jsonString = convertToString( value ); + + } + + /** + * Gets the JSON string from the encoder. + * + * @return The JSON string representation of the object + * that was passed to the constructor + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function getString():String { + return jsonString; + } + + /** + * Converts a value to it's JSON string equivalent. + * + * @param value The value to convert. Could be any + * type (object, number, array, etc) + */ + private function convertToString( value:* ):String { + + // determine what value is and convert it based on it's type + if ( value is String ) { + + // escape the string so it's formatted correctly + return escapeString( value as String ); + + } else if ( value is Number ) { + + // only encode numbers that finate + return isFinite( value as Number) ? value.toString() : "null"; + + } else if ( value is Boolean ) { + + // convert boolean to string easily + return value ? "true" : "false"; + + } else if ( value is Array ) { + + // call the helper method to convert an array + return arrayToString( value as Array ); + + } else if ( value is Object && value != null ) { + + // call the helper method to convert an object + return objectToString( value ); + } + return "null"; + } + + /** + * Escapes a string accoding to the JSON specification. + * + * @param str The string to be escaped + * @return The string with escaped special characters + * according to the JSON specification + */ + private function escapeString( str:String ):String { + // create a string to store the string's jsonstring value + var s:String = ""; + // current character in the string we're processing + var ch:String; + // store the length in a local variable to reduce lookups + var len:Number = str.length; + + // loop over all of the characters in the string + for ( var i:int = 0; i < len; i++ ) { + + // examine the character to determine if we have to escape it + ch = str.charAt( i ); + switch ( ch ) { + + case '"': // quotation mark + s += "\\\""; + break; + + //case '/': // solidus + // s += "\\/"; + // break; + + case '\\': // reverse solidus + s += "\\\\"; + break; + + case '\b': // bell + s += "\\b"; + break; + + case '\f': // form feed + s += "\\f"; + break; + + case '\n': // newline + s += "\\n"; + break; + + case '\r': // carriage return + s += "\\r"; + break; + + case '\t': // horizontal tab + s += "\\t"; + break; + + default: // everything else + + // check for a control character and escape as unicode + if ( ch < ' ' ) { + // get the hex digit(s) of the character (either 1 or 2 digits) + var hexCode:String = ch.charCodeAt( 0 ).toString( 16 ); + + // ensure that there are 4 digits by adjusting + // the # of zeros accordingly. + var zeroPad:String = hexCode.length == 2 ? "00" : "000"; + + // create the unicode escape sequence with 4 hex digits + s += "\\u" + zeroPad + hexCode; + } else { + + // no need to do any special encoding, just pass-through + s += ch; + + } + } // end switch + + } // end for loop + + return "\"" + s + "\""; + } + + /** + * Converts an array to it's JSON string equivalent + * + * @param a The array to convert + * @return The JSON string representation of a + */ + private function arrayToString( a:Array ):String { + // create a string to store the array's jsonstring value + var s:String = ""; + + // loop over the elements in the array and add their converted + // values to the string + for ( var i:int = 0; i < a.length; i++ ) { + // when the length is 0 we're adding the first element so + // no comma is necessary + if ( s.length > 0 ) { + // we've already added an element, so add the comma separator + s += "," + } + + // convert the value to a string + s += convertToString( a[i] ); + } + + // KNOWN ISSUE: In ActionScript, Arrays can also be associative + // objects and you can put anything in them, ie: + // myArray["foo"] = "bar"; + // + // These properties aren't picked up in the for loop above because + // the properties don't correspond to indexes. However, we're + // sort of out luck because the JSON specification doesn't allow + // these types of array properties. + // + // So, if the array was also used as an associative object, there + // may be some values in the array that don't get properly encoded. + // + // A possible solution is to instead encode the Array as an Object + // but then it won't get decoded correctly (and won't be an + // Array instance) + + // close the array and return it's string value + return "[" + s + "]"; + } + + /** + * Converts an object to it's JSON string equivalent + * + * @param o The object to convert + * @return The JSON string representation of o + */ + private function objectToString( o:Object ):String + { + // create a string to store the object's jsonstring value + var s:String = ""; + + // determine if o is a class instance or a plain object + var classInfo:XML = describeType( o ); + if ( classInfo. at name.toString() == "Object" ) + { + // the value of o[key] in the loop below - store this + // as a variable so we don't have to keep looking up o[key] + // when testing for valid values to convert + var value:Object; + + // loop over the keys in the object and add their converted + // values to the string + for ( var key:String in o ) + { + // assign value to a variable for quick lookup + value = o[key]; + + // don't add function's to the JSON string + if ( value is Function ) + { + // skip this key and try another + continue; + } + + // when the length is 0 we're adding the first item so + // no comma is necessary + if ( s.length > 0 ) { + // we've already added an item, so add the comma separator + s += "," + } + + s += escapeString( key ) + ":" + convertToString( value ); + } + } + else // o is a class instance + { + // Loop over all of the variables and accessors in the class and + // serialize them along with their values. + for each ( var v:XML in classInfo..*.( name() == "variable" || name() == "accessor" ) ) + { + // When the length is 0 we're adding the first item so + // no comma is necessary + if ( s.length > 0 ) { + // We've already added an item, so add the comma separator + s += "," + } + + s += escapeString( v. at name.toString() ) + ":" + + convertToString( o[ v. at name ] ); + } + + } + + return "{" + s + "}"; + } + + + } + +} diff --git a/src/flexchart/com/adobe/serialization/json/JSONParseError.as b/src/flexchart/com/adobe/serialization/json/JSONParseError.as new file mode 100644 index 0000000..5aec1e3 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONParseError.as @@ -0,0 +1,87 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * + * + */ + public class JSONParseError extends Error { + + /** The location in the string where the error occurred */ + private var _location:int; + + /** The string in which the parse error occurred */ + private var _text:String; + + /** + * Constructs a new JSONParseError. + * + * @param message The error message that occured during parsing + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONParseError( message:String = "", location:int = 0, text:String = "") { + super( message ); + name = "JSONParseError"; + _location = location; + _text = text; + } + + /** + * Provides read-only access to the location variable. + * + * @return The location in the string where the error occurred + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get location():int { + return _location; + } + + /** + * Provides read-only access to the text variable. + * + * @return The string in which the error occurred + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get text():String { + return _text; + } + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONToken.as b/src/flexchart/com/adobe/serialization/json/JSONToken.as new file mode 100644 index 0000000..258d63c --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONToken.as @@ -0,0 +1,104 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONToken { + + private var _type:int; + private var _value:Object; + + /** + * Creates a new JSONToken with a specific token type and value. + * + * @param type The JSONTokenType of the token + * @param value The value of the token + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONToken( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ) { + _type = type; + _value = value; + } + + /** + * Returns the type of the token. + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get type():int { + return _type; + } + + /** + * Sets the type of the token. + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function set type( value:int ):void { + _type = value; + } + + /** + * Gets the value of the token + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get value():Object { + return _value; + } + + /** + * Sets the value of the token + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function set value ( v:Object ):void { + _value = v; + } + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONTokenType.as b/src/flexchart/com/adobe/serialization/json/JSONTokenType.as new file mode 100644 index 0000000..fceb3f0 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONTokenType.as @@ -0,0 +1,67 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * Class containing constant values for the different types + * of tokens in a JSON encoded string. + */ + public class JSONTokenType { + + public static const UNKNOWN:int = -1; + + public static const COMMA:int = 0; + + public static const LEFT_BRACE:int = 1; + + public static const RIGHT_BRACE:int = 2; + + public static const LEFT_BRACKET:int = 3; + + public static const RIGHT_BRACKET:int = 4; + + public static const COLON:int = 6; + + public static const TRUE:int = 7; + + public static const FALSE:int = 8; + + public static const NULL:int = 9; + + public static const STRING:int = 10; + + public static const NUMBER:int = 11; + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as b/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as new file mode 100644 index 0000000..f5d406a --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as @@ -0,0 +1,547 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONTokenizer { + + /** The object that will get parsed from the JSON string */ + private var obj:Object; + + /** The JSON string to be parsed */ + private var jsonString:String; + + /** The current parsing location in the JSON string */ + private var loc:int; + + /** The current character in the JSON string during parsing */ + private var ch:String; + + /** + * Constructs a new JSONDecoder to parse a JSON string + * into a native object. + * + * @param s The JSON string to be converted + * into a native object + */ + public function JSONTokenizer( s:String ) { + jsonString = s; + loc = 0; + + // prime the pump by getting the first character + nextChar(); + } + + /** + * Gets the next token in the input sting and advances + * the character to the next character after the token + */ + public function getNextToken():JSONToken { + var token:JSONToken = new JSONToken(); + + // skip any whitespace / comments since the last + // token was read + skipIgnored(); + + // examine the new character and see what we have... + switch ( ch ) { + + case '{': + token.type = JSONTokenType.LEFT_BRACE; + token.value = '{'; + nextChar(); + break + + case '}': + token.type = JSONTokenType.RIGHT_BRACE; + token.value = '}'; + nextChar(); + break + + case '[': + token.type = JSONTokenType.LEFT_BRACKET; + token.value = '['; + nextChar(); + break + + case ']': + token.type = JSONTokenType.RIGHT_BRACKET; + token.value = ']'; + nextChar(); + break + + case ',': + token.type = JSONTokenType.COMMA; + token.value = ','; + nextChar(); + break + + case ':': + token.type = JSONTokenType.COLON; + token.value = ':'; + nextChar(); + break; + + case 't': // attempt to read true + var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); + + if ( possibleTrue == "true" ) { + token.type = JSONTokenType.TRUE; + token.value = true; + nextChar(); + } else { + parseError( "Expecting 'true' but found " + possibleTrue ); + } + + break; + + case 'f': // attempt to read false + var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); + + if ( possibleFalse == "false" ) { + token.type = JSONTokenType.FALSE; + token.value = false; + nextChar(); + } else { + parseError( "Expecting 'false' but found " + possibleFalse ); + } + + break; + + case 'n': // attempt to read null + + var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); + + if ( possibleNull == "null" ) { + token.type = JSONTokenType.NULL; + token.value = null; + nextChar(); + } else { + parseError( "Expecting 'null' but found " + possibleNull ); + } + + break; + + case '"': // the start of a string + token = readString(); + break; + + default: + // see if we can read a number + if ( isDigit( ch ) || ch == '-' ) { + token = readNumber(); + } else if ( ch == '' ) { + // check for reading past the end of the string + return null; + } else { + // not sure what was in the input string - it's not + // anything we expected + parseError( "Unexpected " + ch + " encountered" ); + } + } + + return token; + } + + /** + * Attempts to read a string from the input string. Places + * the character location at the first character after the + * string. It is assumed that ch is " before this method is called. + * + * @return the JSONToken with the string value if a string could + * be read. Throws an error otherwise. + */ + private function readString():JSONToken { + // the token for the string we'll try to read + var token:JSONToken = new JSONToken(); + token.type = JSONTokenType.STRING; + + // the string to store the string we'll try to read + var string:String = ""; + + // advance past the first " + nextChar(); + + while ( ch != '"' && ch != '' ) { + + // unescape the escape sequences in the string + if ( ch == '\\' ) { + + // get the next character so we know what + // to unescape + nextChar(); + + switch ( ch ) { + + case '"': // quotation mark + string += '"'; + break; + + case '/': // solidus + string += "/"; + break; + + case '\\': // reverse solidus + string += '\\'; + break; + + case 'b': // bell + string += '\b'; + break; + + case 'f': // form feed + string += '\f'; + break; + + case 'n': // newline + string += '\n'; + break; + + case 'r': // carriage return + string += '\r'; + break; + + case 't': // horizontal tab + string += '\t' + break; + + case 'u': + // convert a unicode escape sequence + // to it's character value - expecting + // 4 hex digits + + // save the characters as a string we'll convert to an int + var hexValue:String = ""; + + // try to find 4 hex characters + for ( var i:int = 0; i < 4; i++ ) { + // get the next character and determine + // if it's a valid hex digit or not + if ( !isHexDigit( nextChar() ) ) { + parseError( " Excepted a hex digit, but found: " + ch ); + } + // valid, add it to the value + hexValue += ch; + } + + // convert hexValue to an integer, and use that + // integrer value to create a character to add + // to our string. + string += String.fromCharCode( parseInt( hexValue, 16 ) ); + + break; + + default: + // couldn't unescape the sequence, so just + // pass it through + string += '\\' + ch; + + } + + } else { + // didn't have to unescape, so add the character to the string + string += ch; + + } + + // move to the next character + nextChar(); + + } + + // we read past the end of the string without closing it, which + // is a parse error + if ( ch == '' ) { + parseError( "Unterminated string literal" ); + } + + // move past the closing " in the input string + nextChar(); + + // attach to the string to the token so we can return it + token.value = string; + + return token; + } + + /** + * Attempts to read a number from the input string. Places + * the character location at the first character after the + * number. + * + * @return The JSONToken with the number value if a number could + * be read. Throws an error otherwise. + */ + private function readNumber():JSONToken { + // the token for the number we'll try to read + var token:JSONToken = new JSONToken(); + token.type = JSONTokenType.NUMBER; + + // the string to accumulate the number characters + // into that we'll convert to a number at the end + var input:String = ""; + + // check for a negative number + if ( ch == '-' ) { + input += '-'; + nextChar(); + } + + // the number must start with a digit + if ( !isDigit( ch ) ) + { + parseError( "Expecting a digit" ); + } + + // 0 can only be the first digit if it + // is followed by a decimal point + if ( ch == '0' ) + { + input += ch; + nextChar(); + + // make sure no other digits come after 0 + if ( isDigit( ch ) ) + { + parseError( "A digit cannot immediately follow 0" ); + } + } + else + { + // read numbers while we can + while ( isDigit( ch ) ) { + input += ch; + nextChar(); + } + } + + // check for a decimal value + if ( ch == '.' ) { + input += '.'; + nextChar(); + + // after the decimal there has to be a digit + if ( !isDigit( ch ) ) + { + parseError( "Expecting a digit" ); + } + + // read more numbers to get the decimal value + while ( isDigit( ch ) ) { + input += ch; + nextChar(); + } + } + + // check for scientific notation + if ( ch == 'e' || ch == 'E' ) + { + input += "e" + nextChar(); + // check for sign + if ( ch == '+' || ch == '-' ) + { + input += ch; + nextChar(); + } + + // require at least one number for the exponent + // in this case + if ( !isDigit( ch ) ) + { + parseError( "Scientific notation number needs exponent value" ); + } + + // read in the exponent + while ( isDigit( ch ) ) + { + input += ch; + nextChar(); + } + } + + // convert the string to a number value + var num:Number = Number( input ); + + if ( isFinite( num ) && !isNaN( num ) ) { + token.value = num; + return token; + } else { + parseError( "Number " + num + " is not valid!" ); + } + return null; + } + + /** + * Reads the next character in the input + * string and advances the character location. + * + * @return The next character in the input string, or + * null if we've read past the end. + */ + private function nextChar():String { + return ch = jsonString.charAt( loc++ ); + } + + /** + * Advances the character location past any + * sort of white space and comments + */ + private function skipIgnored():void { + skipWhite(); + skipComments(); + skipWhite(); + } + + /** + * Skips comments in the input string, either + * single-line or multi-line. Advances the character + * to the first position after the end of the comment. + */ + private function skipComments():void { + if ( ch == '/' ) { + // Advance past the first / to find out what type of comment + nextChar(); + switch ( ch ) { + case '/': // single-line comment, read through end of line + + // Loop over the characters until we find + // a newline or until there's no more characters left + do { + nextChar(); + } while ( ch != '\n' && ch != '' ) + + // move past the \n + nextChar(); + + break; + + case '*': // multi-line comment, read until closing */ + + // move past the opening * + nextChar(); + + // try to find a trailing */ + while ( true ) { + if ( ch == '*' ) { + // check to see if we have a closing / + nextChar(); + if ( ch == '/') { + // move past the end of the closing */ + nextChar(); + break; + } + } else { + // move along, looking if the next character is a * + nextChar(); + } + + // when we're here we've read past the end of + // the string without finding a closing */, so error + if ( ch == '' ) { + parseError( "Multi-line comment not closed" ); + } + } + + break; + + // Can't match a comment after a /, so it's a parsing error + default: + parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); + } + } + + } + + + /** + * Skip any whitespace in the input string and advances + * the character to the first character after any possible + * whitespace. + */ + private function skipWhite():void { + + // As long as there are spaces in the input + // stream, advance the current location pointer + // past them + while ( isWhiteSpace( ch ) ) { + nextChar(); + } + + } + + /** + * Determines if a character is whitespace or not. + * + * @return True if the character passed in is a whitespace + * character + */ + private function isWhiteSpace( ch:String ):Boolean { + return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ); + } + + /** + * Determines if a character is a digit [0-9]. + * + * @return True if the character passed in is a digit + */ + private function isDigit( ch:String ):Boolean { + return ( ch >= '0' && ch <= '9' ); + } + + /** + * Determines if a character is a digit [0-9]. + * + * @return True if the character passed in is a digit + */ + private function isHexDigit( ch:String ):Boolean { + // get the uppercase value of ch so we only have + // to compare the value between 'A' and 'F' + var uc:String = ch.toUpperCase(); + + // a hex digit is a digit of A-F, inclusive ( using + // our uppercase constraint ) + return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) ); + } + + /** + * Raises a parsing error with a specified message, tacking + * on the error location and the original string. + * + * @param message The message indicating why the error occurred + */ + public function parseError( message:String ):void { + throw new JSONParseError( message, loc, jsonString ); + } + } + +} diff --git a/src/flexchart/flexchart.mxml b/src/flexchart/flexchart.mxml new file mode 100644 index 0000000..796329d --- /dev/null +++ b/src/flexchart/flexchart.mxml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/src/flexchart/org/ovirt/ChartLoader.as b/src/flexchart/org/ovirt/ChartLoader.as new file mode 100644 index 0000000..d76bf28 --- /dev/null +++ b/src/flexchart/org/ovirt/ChartLoader.as @@ -0,0 +1,44 @@ +package org.ovirt { + + import mx.containers.Box; + import mx.containers.HBox; + import mx.controls.Text; + + public class ChartLoader { + + private var element:Box; + private var datasourceUrl:String; + + public function ChartLoader(element:Box, datasourceUrl:String) { + this.element = element; + this.datasourceUrl = datasourceUrl; + } + + public function addData(dataSeries:DataSeries):void { + var points:Array = dataSeries.getPoints(); + var maxValue:Number = dataSeries.getMaxValue(); + var scale:Number = maxValue; + if (scale == 0) { scale = 1; } + var size:int = points.length; + element.removeAllChildren(); + element.setStyle("horizontalGap","2"); + for (var i:int = 0; i < size; i++) { + var value:Number = (points[i] as Array)[1]; + var bar:HBox = new HBox(); + bar.percentHeight = ((value / scale) * 90); + bar.percentWidth = (100 / size); + bar.setStyle("backgroundColor","0x0000FF"); + bar.setStyle("left","1"); + bar.setStyle("right","1"); + bar.visible = true; + bar.setVisible(true); + element.addChild(bar); + } + } + + public function load():void { + var dataSource:DataSource = new DataSource(this); + dataSource.retrieveData(datasourceUrl); + } + } +} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSeries.as b/src/flexchart/org/ovirt/DataSeries.as new file mode 100644 index 0000000..c47da97 --- /dev/null +++ b/src/flexchart/org/ovirt/DataSeries.as @@ -0,0 +1,22 @@ +//class to encapsulate the json object representation of a data +//series returned from stats package + +package org.ovirt { + + public class DataSeries { + + private var object:Object; + + public function DataSeries (object:Object) { + this.object = object; + } + + public function getPoints():Array { + return object["vectors"] as Array; + } + + public function getMaxValue():Number { + return object["max_value"] as Number; + } + } +} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSource.as b/src/flexchart/org/ovirt/DataSource.as new file mode 100644 index 0000000..e10cf93 --- /dev/null +++ b/src/flexchart/org/ovirt/DataSource.as @@ -0,0 +1,37 @@ +package org.ovirt { + + import flash.net.URLLoader; + import flash.net.URLRequest; + import com.adobe.serialization.json.JSON; + import flash.events.Event; + import flash.events.IOErrorEvent; + + public class DataSource { + + private var chartLoader:ChartLoader; + + public function DataSource(chartLoader:ChartLoader) { + this.chartLoader = chartLoader; + } + + public function retrieveData(url:String):void { + var loader:URLLoader = new URLLoader(); + loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError ); + loader.addEventListener( Event.COMPLETE, dataLoaded ); + var request:URLRequest = new URLRequest(url); + loader.load(request); + } + + private function dataLoaded(event:Event):void { + var loader:URLLoader = URLLoader(event.target); + var object:Object = JSON.decode(loader.data); + var series:DataSeries = new DataSeries(object); + chartLoader.addData(series); + } + + private function ioError( e:IOErrorEvent ):void { + //FIXME: + //do something useful with this error + } + } +} diff --git a/src/public/javascripts/jquery.flash.js b/src/public/javascripts/jquery.flash.js new file mode 100644 index 0000000..e47cc2e --- /dev/null +++ b/src/public/javascripts/jquery.flash.js @@ -0,0 +1,288 @@ +/** + * Flash (http://jquery.lukelutman.com/plugins/flash) + * A jQuery plugin for embedding Flash movies. + * + * Version 1.0 + * November 9th, 2006 + * + * Copyright (c) 2006 Luke Lutman (http://www.lukelutman.com) + * Dual licensed under the MIT and GPL licenses. + * http://www.opensource.org/licenses/mit-license.php + * http://www.opensource.org/licenses/gpl-license.php + * + * Inspired by: + * SWFObject (http://blog.deconcept.com/swfobject/) + * UFO (http://www.bobbyvandersluis.com/ufo/) + * sIFR (http://www.mikeindustries.com/sifr/) + * + * IMPORTANT: + * The packed version of jQuery breaks ActiveX control + * activation in Internet Explorer. Use JSMin to minifiy + * jQuery (see: http://jquery.lukelutman.com/plugins/flash#activex). + * + **/ +;(function(){ + +var $$; + +/** + * + * @desc Replace matching elements with a flash movie. + * @author Luke Lutman + * @version 1.0.1 + * + * @name flash + * @param Hash htmlOptions Options for the embed/object tag. + * @param Hash pluginOptions Options for detecting/updating the Flash plugin (optional). + * @param Function replace Custom block called for each matched element if flash is installed (optional). + * @param Function update Custom block called for each matched if flash isn't installed (optional). + * @type jQuery + * + * @cat plugins/flash + * + * @example $('#hello').flash({ src: 'hello.swf' }); + * @desc Embed a Flash movie. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { version: 8 }); + * @desc Embed a Flash 8 movie. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { expressInstall: true }); + * @desc Embed a Flash movie using Express Install if flash isn't installed. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { update: false }); + * @desc Embed a Flash movie, don't show an update message if Flash isn't installed. + * +**/ +$$ = jQuery.fn.flash = function(htmlOptions, pluginOptions, replace, update) { + + // Set the default block. + var block = replace || $$.replace; + + // Merge the default and passed plugin options. + pluginOptions = $$.copy($$.pluginOptions, pluginOptions); + + // Detect Flash. + if(!$$.hasFlash(pluginOptions.version)) { + // Use Express Install (if specified and Flash plugin 6,0,65 or higher is installed). + if(pluginOptions.expressInstall && $$.hasFlash(6,0,65)) { + // Add the necessary flashvars (merged later). + var expressInstallOptions = { + flashvars: { + MMredirectURL: location, + MMplayerType: 'PlugIn', + MMdoctitle: jQuery('title').text() + } + }; + // Ask the user to update (if specified). + } else if (pluginOptions.update) { + // Change the block to insert the update message instead of the flash movie. + block = update || $$.update; + // Fail + } else { + // The required version of flash isn't installed. + // Express Install is turned off, or flash 6,0,65 isn't installed. + // Update is turned off. + // Return without doing anything. + return this; + } + } + + // Merge the default, express install and passed html options. + htmlOptions = $$.copy($$.htmlOptions, expressInstallOptions, htmlOptions); + + // Invoke $block (with a copy of the merged html options) for each element. + return this.each(function(){ + block.call(this, $$.copy(htmlOptions)); + }); + +}; +/** + * + * @name flash.copy + * @desc Copy an arbitrary number of objects into a new object. + * @type Object + * + * @example $$.copy({ foo: 1 }, { bar: 2 }); + * @result { foo: 1, bar: 2 }; + * +**/ +$$.copy = function() { + var options = {}, flashvars = {}; + for(var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if(arg == undefined) continue; + jQuery.extend(options, arg); + // don't clobber one flash vars object with another + // merge them instead + if(arg.flashvars == undefined) continue; + jQuery.extend(flashvars, arg.flashvars); + } + options.flashvars = flashvars; + return options; +}; +/* + * @name flash.hasFlash + * @desc Check if a specific version of the Flash plugin is installed + * @type Boolean + * +**/ +$$.hasFlash = function() { + // look for a flag in the query string to bypass flash detection + if(/hasFlash\=true/.test(location)) return true; + if(/hasFlash\=false/.test(location)) return false; + var pv = $$.hasFlash.playerVersion().match(/\d+/g); + var rv = String([arguments[0], arguments[1], arguments[2]]).match(/\d+/g) || String($$.pluginOptions.version).match(/\d+/g); + for(var i = 0; i < 3; i++) { + pv[i] = parseInt(pv[i] || 0); + rv[i] = parseInt(rv[i] || 0); + // player is less than required + if(pv[i] < rv[i]) return false; + // player is greater than required + if(pv[i] > rv[i]) return true; + } + // major version, minor version and revision match exactly + return true; +}; +/** + * + * @name flash.hasFlash.playerVersion + * @desc Get the version of the installed Flash plugin. + * @type String + * +**/ +$$.hasFlash.playerVersion = function() { + // ie + try { + try { + // avoid fp6 minor version lookup issues + // see: http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + var axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'); + try { axo.AllowScriptAccess = 'always'; } + catch(e) { return '6,0,0'; } + } catch(e) {} + return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; + // other browsers + } catch(e) { + try { + if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){ + return (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1]; + } + } catch(e) {} + } + return '0,0,0'; +}; +/** + * + * @name flash.htmlOptions + * @desc The default set of options for the object or embed tag. + * +**/ +$$.htmlOptions = { + height: 240, + flashvars: {}, + pluginspage: 'http://www.adobe.com/go/getflashplayer', + src: '#', + type: 'application/x-shockwave-flash', + width: 320 +}; +/** + * + * @name flash.pluginOptions + * @desc The default set of options for checking/updating the flash Plugin. + * +**/ +$$.pluginOptions = { + expressInstall: false, + update: true, + version: '6.0.65' +}; +/** + * + * @name flash.replace + * @desc The default method for replacing an element with a Flash movie. + * +**/ +$$.replace = function(htmlOptions) { + this.innerHTML = '
'+this.innerHTML+'
'; + jQuery(this) + .addClass('flash-replaced') + .prepend($$.transform(htmlOptions)); +}; +/** + * + * @name flash.update + * @desc The default method for replacing an element with an update message. + * +**/ +$$.update = function(htmlOptions) { + var url = String(location).split('?'); + url.splice(1,0,'?hasFlash=true&'); + url = url.join(''); + var msg = '

This content requires the Flash Player. Download Flash Player. Already have Flash Player? Click here.

'; + this.innerHTML = ''+this.innerHTML+''; + jQuery(this) + .addClass('flash-update') + .prepend(msg); +}; +/** + * + * @desc Convert a hash of html options to a string of attributes, using Function.apply(). + * @example toAttributeString.apply(htmlOptions) + * @result foo="bar" foo="bar" + * +**/ +function toAttributeString() { + var s = ''; + for(var key in this) + if(typeof this[key] != 'function') + s += key+'="'+this[key]+'" '; + return s; +}; +/** + * + * @desc Convert a hash of flashvars to a url-encoded string, using Function.apply(). + * @example toFlashvarsString.apply(flashvarsObject) + * @result foo=bar&foo=bar + * +**/ +function toFlashvarsString() { + var s = ''; + for(var key in this) + if(typeof this[key] != 'function') + s += key+'='+encodeURIComponent(this[key])+'&'; + return s.replace(/&$/, ''); +}; +/** + * + * @name flash.transform + * @desc Transform a set of html options into an embed tag. + * @type String + * + * @example $$.transform(htmlOptions) + * @result + * + * Note: The embed tag is NOT standards-compliant, but it + * works in all current browsers. flash.transform can be + * overwritten with a custom function to generate more + * standards-compliant markup. + * +**/ +$$.transform = function(htmlOptions) { + htmlOptions.toString = toAttributeString; + if(htmlOptions.flashvars) htmlOptions.flashvars.toString = toFlashvarsString; + return ''; +}; + +/** + * + * Flash Player 9 Fix (http://blog.deconcept.com/2006/07/28/swfobject-143-released/) + * +**/ +if (window.attachEvent) { + window.attachEvent("onbeforeunload", function(){ + __flash_unloadHandler = function() {}; + __flash_savedUnloadHandler = function() {}; + }); +} + +})(); \ No newline at end of file -- 1.5.5.1 From slinabery at redhat.com Thu Oct 9 10:19:29 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 9 Oct 2008 05:19:29 -0500 Subject: [Ovirt-devel] [PATCH server] Add a rudimentary flash chart written in flex framework to summary pages. In-Reply-To: <20081009101614.GA10125@redhat.com> References: <20081009101614.GA10125@redhat.com> Message-ID: <20081009101929.GC10125@redhat.com> On Thu, Oct 09, 2008 at 05:16:15AM -0500, Steve Linabery wrote: > First stab at showing real rrd data in flash chart (in this case, memory peak use for last 40 samples of rrd data). Note that to test this, you'll need to get the open flex sdk and compile src/flexchart/flexchart.mxml yourself. Gah, I just realized I didn't add our "preamble" & authorship info to the actionscript files I created for this. But I'll add that stuff if necessary. Thanks, Steve From bkearney at redhat.com Thu Oct 9 13:03:37 2008 From: bkearney at redhat.com (Bryan Kearney) Date: Thu, 09 Oct 2008 09:03:37 -0400 Subject: [Ovirt-devel] Re: [PATCH recipe] Syntax error correction in ovirt.pp (missing comma) In-Reply-To: <48ED7B7C.3010306@redhat.com> References: <1223522965-20298-1-git-send-email-pmyers@redhat.com> <48ED7B7C.3010306@redhat.com> Message-ID: <48EE0129.7050102@redhat.com> Perry N. Myers wrote: > Perry Myers wrote: >> Missing comma in ovirt.pp caused ace service to fail at appliance >> firstboot, >> which caused failures in the appliance. > > For those of you who reported issues with the appliance booting and > seeming like a postgres connection problem, this was the reason. Here's > what happened for future reference: > > syntax error in ovirt.pp caused /etc/init.d/ace to fail during appliance > firstboot configuration > > This prevented the ovirt-server-install script from running which sets > up the database, and then failures cascaded from there > > It's important when writing patches to test the build process end to end > including building an appliance and testing major functionality to make > sure no regressions are introduced. > > To diagnose this problem I simply looked at the log files in: > /var/log/ace In addition, you can use the ace tooling to catch _many_ of the syntax errors (not all). I had not documented this... but I just added the following page: http://www.thincrust.net/docs-acedebugging.html Run this before you attempt to build the appliance to save some time. -- bk From pmyers at redhat.com Thu Oct 9 13:30:15 2008 From: pmyers at redhat.com (Perry Myers) Date: Thu, 9 Oct 2008 09:30:15 -0400 Subject: [Ovirt-devel] [PATCH node-image] Add ovirt-node-selinux policy to the node image Message-ID: <1223559015-24998-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- common-pkgs.ks | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/common-pkgs.ks b/common-pkgs.ks index 0149956..cd4f243 100644 --- a/common-pkgs.ks +++ b/common-pkgs.ks @@ -33,6 +33,7 @@ hal # qemu-img RPM. qemu-img ovirt-node +ovirt-node-selinux -audit-libs-python -hdparm -ustr -- 1.5.5.1 From pmyers at redhat.com Thu Oct 9 13:30:39 2008 From: pmyers at redhat.com (Perry Myers) Date: Thu, 9 Oct 2008 09:30:39 -0400 Subject: [Ovirt-devel] [PATCH release] Add ovirt-node-selinux to list of packages to install to host in update-host Message-ID: <1223559039-25028-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- ovirt.mk | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/ovirt.mk b/ovirt.mk index dc28f6c..4a45f7e 100644 --- a/ovirt.mk +++ b/ovirt.mk @@ -82,7 +82,8 @@ update-host: @$(write_repo_file) @sudo yum install -c $(OVIRT_CACHE_DIR)/ovirt-local.repo -y \ --enablerepo=ovirt \ - ovirt-release ovirt-build ovirt-appliance ovirt-node ovirt-docs + ovirt-release ovirt-build ovirt-appliance ovirt-node ovirt-docs \ + ovirt-node-selinux tar-src: @mkdir -p $(OVIRT_CACHE_DIR) -- 1.5.5.1 From jim at meyering.net Thu Oct 9 13:34:45 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 15:34:45 +0200 Subject: [Ovirt-devel] [PATCH release] Add ovirt-node-selinux to list of packages to install to host in update-host In-Reply-To: <1223559039-25028-1-git-send-email-pmyers@redhat.com> (Perry Myers's message of "Thu, 9 Oct 2008 09:30:39 -0400") References: <1223559039-25028-1-git-send-email-pmyers@redhat.com> Message-ID: <874p3l28p6.fsf@rho.meyering.net> Perry Myers wrote: > Signed-off-by: Perry Myers > --- > ovirt.mk | 3 ++- > 1 files changed, 2 insertions(+), 1 deletions(-) > > diff --git a/ovirt.mk b/ovirt.mk > index dc28f6c..4a45f7e 100644 > --- a/ovirt.mk > +++ b/ovirt.mk > @@ -82,7 +82,8 @@ update-host: > @$(write_repo_file) > @sudo yum install -c $(OVIRT_CACHE_DIR)/ovirt-local.repo -y \ > --enablerepo=ovirt \ > - ovirt-release ovirt-build ovirt-appliance ovirt-node ovirt-docs > + ovirt-release ovirt-build ovirt-appliance ovirt-node ovirt-docs \ > + ovirt-node-selinux > > tar-src: > @mkdir -p $(OVIRT_CACHE_DIR) ACK (20-min build in progress) From jim at meyering.net Thu Oct 9 13:34:56 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 15:34:56 +0200 Subject: [Ovirt-devel] [PATCH node-image] Add ovirt-node-selinux policy to the node image In-Reply-To: <1223559015-24998-1-git-send-email-pmyers@redhat.com> (Perry Myers's message of "Thu, 9 Oct 2008 09:30:15 -0400") References: <1223559015-24998-1-git-send-email-pmyers@redhat.com> Message-ID: <87y70xzybj.fsf@rho.meyering.net> Perry Myers wrote: > Signed-off-by: Perry Myers > --- > common-pkgs.ks | 1 + > 1 files changed, 1 insertions(+), 0 deletions(-) > > diff --git a/common-pkgs.ks b/common-pkgs.ks > index 0149956..cd4f243 100644 > --- a/common-pkgs.ks > +++ b/common-pkgs.ks > @@ -33,6 +33,7 @@ hal > # qemu-img RPM. > qemu-img > ovirt-node > +ovirt-node-selinux ACK From jim at meyering.net Thu Oct 9 13:41:51 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 15:41:51 +0200 Subject: [Ovirt-devel] Re: [PATCH recipe] Syntax error correction in ovirt.pp (missing comma) In-Reply-To: <48EE0129.7050102@redhat.com> (Bryan Kearney's message of "Thu, 09 Oct 2008 09:03:37 -0400") References: <1223522965-20298-1-git-send-email-pmyers@redhat.com> <48ED7B7C.3010306@redhat.com> <48EE0129.7050102@redhat.com> Message-ID: <87skr5zy00.fsf@rho.meyering.net> Bryan Kearney wrote: > In addition, you can use the ace tooling to catch _many_ of the syntax > errors (not all). I had not documented this... but I just added the > following page: > > http://www.thincrust.net/docs-acedebugging.html > > Run this before you attempt to build the appliance to save some time. If there's not too expensive, it'd be nice to automate that process. From xxqonline at hotmail.com Thu Oct 9 14:51:13 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Thu, 9 Oct 2008 22:51:13 +0800 Subject: [Ovirt-devel] I cannot see the NFS storage size afer I virsh start ovirt-appliance Message-ID: Hello folks: I use the virsh 0.4.5. I followed the quick start to set the storage pool. After that I checked the property of that pool. There is no size information. Then I create a vm pool, and try to create a fedora 9 (Cobbler Profile), I failed to start it. Can any one give me a help? Thanks, Qiang -------------- next part -------------- An HTML attachment was scrubbed... URL: From xxqonline at hotmail.com Thu Oct 9 15:09:43 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Thu, 9 Oct 2008 23:09:43 +0800 Subject: [Ovirt-devel] Can any one help me, I cannot start the virtual machine with default ovirt-appliance Message-ID: Hello folks: I use the virsh 0.4.5. I followed the quick start to set the storage pool (nfs). After that I checked the property of that pool. There is no size information. Then I create a vm pool, and try to create a fedora 9 (Cobbler Profile), I failed to start it. Can any one give me a help? I am new to ovirt, I really want to start the virtual machine, can any one give me a help? I am trying to find the error log, but seems there is no error, just told me the vm cannot be started, is there any log that I can use to trouble shooting? Thanks, Qiang -------------- next part -------------- An HTML attachment was scrubbed... URL: From apevec at redhat.com Thu Oct 9 15:10:37 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 9 Oct 2008 17:10:37 +0200 Subject: [Ovirt-devel] [PATCH ovirt-appliance] remove skip_compress_image Message-ID: <1223565037-18581-1-git-send-email-apevec@redhat.com> Signed-off-by: Alan Pevec --- Makefile.am | 2 -- ovirt-appliance.spec.in | 6 +----- 2 files changed, 1 insertions(+), 7 deletions(-) diff --git a/Makefile.am b/Makefile.am index 6631274..91f8fa3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,8 +38,6 @@ RPM_FLAGS += $(if $(OVIRT_URL),--define "ovirt_url $(OVIRT_URL)") # THINCRUST_URL env var can be set to the root of an thincrust.net mirror # FIXME: This is temporary until thincrust RPMS are in Fedora 9 Updates RPM_FLAGS += $(if $(THINCRUST_URL),--define "thincrust_url $(THINCRUST_URL)") -# SKIP_COMPRESS_IMAGE env var an be set to 1 to skip qcow compression -RPM_FLAGS += $(if $(SKIP_COMPRESS_IMAGE),--define "skip_compress_image $(SKIP_COMPRESS_IMAGE)") rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz diff --git a/ovirt-appliance.spec.in b/ovirt-appliance.spec.in index fb19edd..f08e0f6 100644 --- a/ovirt-appliance.spec.in +++ b/ovirt-appliance.spec.in @@ -70,11 +70,7 @@ sudo su - -c "cd $(pwd) && appliance-creator --config ovirt-appliance.ks \ --tmpdir='%{ovirt_cache_dir}/appliance-tmp' \ --cache='%{ovirt_cache_dir}/yum'" sudo su - -c "cd $(pwd) && chown -R $USER ." -if [ -n "%{?skip_compress_image}" ]; then - mv %{name}-sda.raw %{name}.img -else - mv %{name}-sda.qcow2 %{name}.img -fi +mv %{name}-sda.qcow2 %{name}.img %install %{__rm} -rf %{buildroot} -- 1.5.5.1 From dpierce at redhat.com Thu Oct 9 15:12:49 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Oct 2008 11:12:49 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223565169-15368-1-git-send-email-dpierce@redhat.com> The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 50 ++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 83 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++- 4 files changed, 186 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..0ec2837 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +244,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision =~ /Vm::IMAGE_PREFIX at Vm::COBBLER_PREFIX/ + provisioning_type = :system_type if provision =~ /Vm::PROFILE_PREFIX at Vm::COBBLER_PREFIX/ + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..982613f 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,50 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + image_pool.save! + end + vm.storage_volumes << image_volume + vm.save! + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From jim at meyering.net Thu Oct 9 15:16:06 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 17:16:06 +0200 Subject: [Ovirt-devel] [PATCH *all-7*] autogen.sh: run autoheader and libtool only if needed Message-ID: <87bpxtztmx.fsf@rho.meyering.net> This makes it so autogen.sh (all 7 modules) no longer runs autoheader unnecessarily (suggestion from Alan Pevec). Also, it skips the check for libtool when it's not needed. [it's not needed in any of these modules] I tested it by running this: for i in *; do test -d $i/.git && (cd $i && ./autogen.sh); done and ensuring there were no errors. >From b2eaec5c40bb3bd5c63baca9513dade17cae510a Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH appliance] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index 8e77f25..6b0970f 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d >From 9621960dd03f2204f4a968ba9afac49ea3e0dc7a Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH docs] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index 234141b..74fc1e2 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d >From e732750ce270975bc6ae338ff0728dbedfa6193d Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH node] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index d018b64..5fd5c2f 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d >From 82e8c9d6bc8a2b7fd215074f4bccc3b3344bff5d Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH node-image] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index a2b0f89..e4b75c2 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d >From ea981202624fe8600d9adb143e1a0984dc511267 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH recipe] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index 02a4ca2..34b0e47 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d >From a3825ffe53956f46981daa6776943b5d65c71aeb Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH release] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index cca9761..af6ff67 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d >From d8d0db5e0d902e779c89a81426a0340249ae8e8e Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 17:03:56 +0200 Subject: [PATCH server] autogen.sh: run autoheader and libtool only if needed --- autogen.sh | 12 ++++++++++-- 1 files changed, 10 insertions(+), 2 deletions(-) diff --git a/autogen.sh b/autogen.sh index 7f0ca85..68ef0d8 100755 --- a/autogen.sh +++ b/autogen.sh @@ -17,12 +17,17 @@ THEDIR=`pwd` die=1 } - (libtool --version) < /dev/null > /dev/null 2>&1 || { + # Require libtool only if one of of LT_INIT, + # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. + grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ + && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have libtool installed." echo "Download the appropriate package for your distribution," echo "or see http://www.gnu.org/software/libtool" die=1 + } } (automake --version) < /dev/null > /dev/null 2>&1 || { @@ -46,7 +51,10 @@ THEDIR=`pwd` fi aclocal - autoheader + + # Run autoheader only if needed + grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader + automake --add-missing autoconf ./configure "$@" -- 1.6.0.2.304.gc76d From dpierce at redhat.com Thu Oct 9 15:17:58 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 9 Oct 2008 11:17:58 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223565478-15734-1-git-send-email-dpierce@redhat.com> The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 50 ++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- 4 files changed, 184 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..0ec2837 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +244,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision =~ /Vm::IMAGE_PREFIX at Vm::COBBLER_PREFIX/ + provisioning_type = :system_type if provision =~ /Vm::PROFILE_PREFIX at Vm::COBBLER_PREFIX/ + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..ef0f8e2 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From xxqonline at hotmail.com Thu Oct 9 14:59:53 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Thu, 9 Oct 2008 22:59:53 +0800 Subject: [Ovirt-devel] I cannot start virtual machine in virsh 0.4.5 In-Reply-To: References: Message-ID: Hello folks: I use the virsh 0.4.5. I followed the quick start to set the storage pool (nfs). After that I checked the property of that pool. There is no size information. Then I create a vm pool, and try to create a fedora 9 (Cobbler Profile), I failed to start it. Can any one give me a help? I am new to ovirt, I really want to start the virtual machine, can any one give me a help? I am trying to find the error log, but seems there is no error, just told me the vm cannot be started, is there any log that I can use to trouble shooting? Thanks, Qiang -------------- next part -------------- An HTML attachment was scrubbed... URL: From apevec at redhat.com Thu Oct 9 15:33:45 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 9 Oct 2008 17:33:45 +0200 Subject: [Ovirt-devel] [PATCH ovirt-appliance] configure node5 to persist config and node3 stateless Message-ID: <1223566425-23216-1-git-send-email-apevec@redhat.com> People are usually starting node3 and get confused after appliance reinstallation, since persisted old keytab is not valid anymore. NOTE: there's a script ovirt-release/misc-scripts/ovirt-reset-fake-nodes to wipe local storage of all 'fake' Nodes Signed-off-by: Alan Pevec --- ovirt-appliance.ks | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ovirt-appliance.ks b/ovirt-appliance.ks index 0fd5186..28f171e 100644 --- a/ovirt-appliance.ks +++ b/ovirt-appliance.ks @@ -121,11 +121,11 @@ cobbler distro add --name="oVirt-Node-$arch" --arch=$arch \ --kopts="rootflags=loop root=/ovirt-node-image.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 --kopts="ovirt_init=scsi" + --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 --kopts="ovirt_init=scsi ovirt_local_boot" cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ - --name=node5 --mac=00:16:3e:12:34:59 + --name=node5 --mac=00:16:3e:12:34:59 --kopts="ovirt_init=scsi" set +x echo "Add new oVirt Nodes as Cobbler systems to make them PXE boot oVirt Node image directly." echo "oVirt-Node-$arch is also default boot option in Cobbler menu" -- 1.5.5.1 From jim at meyering.net Thu Oct 9 15:44:51 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 17:44:51 +0200 Subject: [Ovirt-devel] [PATCH *ALL*] Makefile.am: set OVIRT_DEV from Release: 0 in spec.in file In-Reply-To: <48ED84AE.9090606@redhat.com> (Perry N. Myers's message of "Thu, 09 Oct 2008 00:12:30 -0400") References: <87bpxv3tii.fsf@rho.meyering.net> <48ED84AE.9090606@redhat.com> Message-ID: <8763o1zsb0.fsf@rho.meyering.net> "Perry N. Myers" wrote: > Jim Meyering wrote: >> I'd been running "make build" without setting OVIRT_DEV. >> That would cause trouble due to using out of date RPMs. >> But in development, we should always simply set OVIRT_DEV=1. >> We know we're in development mode when the spec.in file contains >> has a line that starts like this: >> >> Release: 0 >> >> so I've adjusted all Makefile.am files to automatically do >> what we want. Painful to have to put this same snippet in >> 7 different repositories, but very soon I'll be motivated >> (and have time) to factor out the worst of that duplication. > > These patches seem to work fine, and I would ACK them except... > > I've noticed that occasionally the rpm flags for OVIRT_DEV are not set > properly in the ovirt-appliance repo, causing the version for > ovirt-appliance to occasionally be 0.94-0 even though the Release is 0 > in the spec file. I can't seem to replicate the error with any > consistency. It just happens once and a while with seemingly the same > environment (I know there must be something different, but haven't > pinned it down yet) > > If anyone has seen this behavior w/o these patches applied please let > me know, as maybe this has been a problem all along. It could be that > the bashism used to set rpm_flags is not working properly in the > Makefile. Hi Perry, I've run "make build" several times, both with and without OVIRT_DEV=1 in my environment, (though just for the first few seconds each time, which is enough to see the relevant rpmbuild command), and every time I saw the expected .git... argument: --define "extra_release .$(date --utc +%Y%m%d%H%M%S)git$(git log -1 --pretty=format:%h)" Ah ha. But if I build with "make publish OVIRT_DEV=", *then* I see what you report: no extra_release definition. To reduce the likelihood of that happening, I could change the variable name to e.g., _OVIRT_DEV. But maybe it's not worth worrying about. You choose. From apevec at redhat.com Thu Oct 9 16:06:00 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 09 Oct 2008 18:06:00 +0200 Subject: [Ovirt-devel] Can any one help me, I cannot start the virtual machine with default ovirt-appliance In-Reply-To: References: Message-ID: <48EE2BE8.1030101@redhat.com> xxqonline at hotmail.com wrote: > Hello folks: > I use the virsh 0.4.5. I followed the quick start to set the > storage pool (nfs). > After that I checked the property of that pool. There is no size > information. > Then I create a vm pool, and try to create a fedora 9 (Cobbler Profile), > I failed to start it. > Can any one give me a help? First, which version are you using? Check that firstrun worked fine, see http://ovirt.org/page/TroubleShooting#Debugging_oVirt_Appliance_firstboot for how to modify the fresh image after ovirt-appliance RPM installation and before starting it up for the first time and check logs mentioned there. From jim at meyering.net Thu Oct 9 17:18:04 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 19:18:04 +0200 Subject: [Ovirt-devel] [PATCH *ALL*] Makefile.am: set OVIRT_DEV from Release: 0 in spec.in file In-Reply-To: <8763o1zsb0.fsf@rho.meyering.net> (Jim Meyering's message of "Thu, 09 Oct 2008 17:44:51 +0200") References: <87bpxv3tii.fsf@rho.meyering.net> <48ED84AE.9090606@redhat.com> <8763o1zsb0.fsf@rho.meyering.net> Message-ID: <87r66py9f7.fsf@rho.meyering.net> Jim Meyering wrote: > "Perry N. Myers" wrote: >> Jim Meyering wrote: >>> I'd been running "make build" without setting OVIRT_DEV. >>> That would cause trouble due to using out of date RPMs. >>> But in development, we should always simply set OVIRT_DEV=1. >>> We know we're in development mode when the spec.in file contains >>> has a line that starts like this: >>> >>> Release: 0 >>> >>> so I've adjusted all Makefile.am files to automatically do >>> what we want. Painful to have to put this same snippet in >>> 7 different repositories, but very soon I'll be motivated >>> (and have time) to factor out the worst of that duplication. >> >> These patches seem to work fine, and I would ACK them except... >> >> I've noticed that occasionally the rpm flags for OVIRT_DEV are not set >> properly in the ovirt-appliance repo, causing the version for >> ovirt-appliance to occasionally be 0.94-0 even though the Release is 0 >> in the spec file. I can't seem to replicate the error with any >> consistency. It just happens once and a while with seemingly the same >> environment (I know there must be something different, but haven't >> pinned it down yet) >> >> If anyone has seen this behavior w/o these patches applied please let >> me know, as maybe this has been a problem all along. It could be that >> the bashism used to set rpm_flags is not working properly in the >> Makefile. > > Hi Perry, > > I've run "make build" several times, both with and without OVIRT_DEV=1 > in my environment, (though just for the first few seconds each time, > which is enough to see the relevant rpmbuild command), > and every time I saw the expected .git... argument: > > --define "extra_release .$(date --utc +%Y%m%d%H%M%S)git$(git log -1 --pretty=format:%h)" > > Ah ha. > But if I build with "make publish OVIRT_DEV=", > *then* I see what you report: no extra_release definition. > > To reduce the likelihood of that happening, > I could change the variable name to e.g., _OVIRT_DEV. I've changed it to _ovirt_dev. I'll push these 7 identical (modulo context) changes after I eat: >From 9db4eb4dad24f3b5132944c5c1b916d2a42be615 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH appliance] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 6631274..8cba85d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,12 +25,17 @@ EXTRA_DIST = \ ovirt-appliance.spec \ ovirt-appliance.spec.in +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") # FEDORA_URL env var can be set to the root of a fedora mirror RPM_FLAGS += $(if $(FEDORA_URL),--define "fedora_url $(FEDORA_URL)") # OVIRT_URL env var can be set to the root of an ovirt.org mirror -- 1.6.0.2.304.gc76d >From 96223e17149e0440b616c75d4c189363318ab187 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH docs] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 47fbbab..9d58e23 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,11 +24,17 @@ EXTRA_DIST = \ oVirt_Installation_Guide \ Using_the_oVirt_Admin_UI +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) + git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz -- 1.6.0.2.304.gc76d >From f8eefaa29fbee9ddbc4b4159f765be573cdbc9e4 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH node] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 8ca63dc..1d63310 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,11 +33,17 @@ EXTRA_DIST = \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) + git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz -- 1.6.0.2.304.gc76d >From 4b276c09c8d211c63f6e6dc586c8a66b3dab6f05 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH node-image] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 50d55ef..73a0a7b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,11 +30,17 @@ EXTRA_DIST = \ ovirt-node-image.ks \ ovirt-pxe +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) + git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") # FEDORA_URL env var can be set to the root of a fedora mirror RPM_FLAGS += $(if $(FEDORA_URL),--define "fedora_url $(FEDORA_URL)") # OVIRT_URL env var can be set to the root of an ovirt.org mirror -- 1.6.0.2.304.gc76d >From 1b4e66eca164916440cac9031d174f0c82cc997d Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH recipe] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 37751af..db2962d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,11 +34,17 @@ EXTRA_DIST = \ appliances/ovirt/templates/ovirt-server-appliance-setup.erb \ appliances/ovirt/templates/terminal.erb +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) + git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz -- 1.6.0.2.304.gc76d >From 32e4db1453a2cdbed1ee29fb5f523f1fbbe9709c Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH release] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 0637125..d726dab 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,11 +25,17 @@ EXTRA_DIST = \ ovirt.repo \ misc-scripts +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) + git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz -- 1.6.0.2.304.gc76d >From e84b82128372d6d58327116a22c6a3cd76084afc Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Thu, 9 Oct 2008 19:16:01 +0200 Subject: [PATCH server] Makefile.am: obviate OVIRT_DEV envvar Now, _ovirt_dev is automatically set from Release: 0 in spec.in file. Just like OVIRT_DEV was, it's used to define (or not) "extra_release". --- Makefile.am | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index daf6ef0..71a5e9f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,11 +25,17 @@ EXTRA_DIST = \ conf \ src +# For Release: 0..., set _ovirt_dev=1 so that we get extra_release.GIT- +# annotated rpm version strings. +_ovirt_dev = \ + $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \ + $(srcdir)/*.spec.in && echo 1 || :) + git_head = $$(git log -1 --pretty=format:%h) GIT_RELEASE = $$(date --utc +%Y%m%d%H%M%S)git$(git_head) RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "ovirt_cache_dir $(OVIRT_CACHE_DIR)" -RPM_FLAGS += $(if $(OVIRT_DEV),--define "extra_release .$(GIT_RELEASE)") +RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz -- 1.6.0.2.304.gc76d From jim at meyering.net Thu Oct 9 17:57:26 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 19:57:26 +0200 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <8763o24y4t.fsf@rho.meyering.net> (Jim Meyering's message of "Wed, 08 Oct 2008 22:42:26 +0200") References: <1223492786-1123-1-git-send-email-dpierce@redhat.com> <8763o24y4t.fsf@rho.meyering.net> Message-ID: <87ljwxy7ll.fsf@rho.meyering.net> Jim Meyering wrote: > "Darryl L. Pierce" wrote: ... >> + modconf=$(awk '/bonding=/ { >> + match($0, "bonding=(.*)", data) >> + split(data[1], mod, "|") >> + >> + alias=mod[1] >> + options=mod[2] >> + >> + printf("install %s /sbin/modprobe bonding -o %s %s\n", >> + alias, module, alias, options) > > We need to do some sanitization (perl calls this untainting) here. > I.e., we want to reject malicious parameters like "; rm -rf /" or > "& start-stealthy-daemon...". > > So, match alias, module, and options for bogus shell meta-characters, > or probably-better, accept only a limited alphabet, maybe just > search for anything matching this /[^[:alnum:]=_ at -]/ > >> + }' $CONFIG) > > You'll want to diagnose bogosity from within the awk script > and exit nonzero to tell the shell to fail: > > ... > }' $CONFIG) || exit 1; Hi Darryl, Here's a little stand-alone script to demonstrate what I meant: #!/bin/sh ME=$(basename "$0") warn() { printf "$ME: $@\n" >&2; } cat <<\EOF > in bonding=;rm -rf /|b|c EOF modconf=$(awk '/bonding=/ { match($0, "bonding=(.*)", data) split(data[1], mod, "|") if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { printf "invalid bonding alias: \"%s\"\n", mod[1]; exit 1; } alias=mod[1] printf("install %s bonding", alias) }' in) || { warn "$modconf"; exit 2; } echo "$modconf" -------------------------------- It has to be a little tricky that since awk is run from a subshell, which means its stderr would be ignored, so instead of printing its own diagnostic, it just prints it to stdout, and the invoker knows to use the result in _its_ diagnostic when the awk script exits nonzero. From jim at meyering.net Thu Oct 9 18:51:09 2008 From: jim at meyering.net (Jim Meyering) Date: Thu, 09 Oct 2008 20:51:09 +0200 Subject: [Ovirt-devel] [PATCH *all-7*] autogen.sh: run autoheader and libtool only if needed In-Reply-To: <87bpxtztmx.fsf@rho.meyering.net> (Jim Meyering's message of "Thu, 09 Oct 2008 17:16:06 +0200") References: <87bpxtztmx.fsf@rho.meyering.net> Message-ID: <87fxn5y542.fsf@rho.meyering.net> Jim Meyering wrote: > This makes it so autogen.sh (all 7 modules) no longer > runs autoheader unnecessarily (suggestion from Alan Pevec). > Also, it skips the check for libtool when it's not needed. > [it's not needed in any of these modules] > > I tested it by running this: > > for i in *; do test -d $i/.git && (cd $i && ./autogen.sh); done > > and ensuring there were no errors. I've just pushed those. From slinabery at redhat.com Thu Oct 9 19:06:45 2008 From: slinabery at redhat.com (Steve Linabery) Date: Thu, 9 Oct 2008 14:06:45 -0500 Subject: [Ovirt-devel] flexchart build instructions Message-ID: <20081009190645.GF10125@redhat.com> Hi, If anyone would like to build the flexchart swf (Flash Movie), here's how: 0) download the Open Flex SDK at http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3 (I think I used latest stable build) 1) unzip that archive somewhere and add the bin subdirectory of same to your PATH 2) Apply the patch I sent for review earlier today 3) cd to {ovirt_src_home}/server/src/flexchart in your patched source tree 4) execute 'mxmlc flexchart.mxml' 5) copy the resulting flexchart.swf to /usr/share/ovirt-server/public on your appliance (which you've just rebuilt using my patch) Please let me know if it works for you (or doesn't!). If you see blue bars in your chart area (assumes you have some rrd data stored for memory use), it's working. Thanks, Steve From mmorsi at redhat.com Thu Oct 9 20:44:49 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 9 Oct 2008 16:44:49 -0400 Subject: [Ovirt-devel] [PATCH server] fixes to the bonding tests Message-ID: <1223585089-25209-1-git-send-email-mmorsi@redhat.com> --- src/app/models/bonding.rb | 2 ++ src/app/models/bonding_type.rb | 2 ++ src/test/unit/bonding_test.rb | 9 --------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 941e2cd..eb25d05 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -36,6 +36,8 @@ class Bonding < ActiveRecord::Base validates_presence_of :interface_name, :message => 'An interface name is required.' + validates_presence_of :host_id + belongs_to :host belongs_to :bonding_type diff --git a/src/app/models/bonding_type.rb b/src/app/models/bonding_type.rb index e0d2193..7697ff4 100644 --- a/src/app/models/bonding_type.rb +++ b/src/app/models/bonding_type.rb @@ -23,4 +23,6 @@ class BondingType < ActiveRecord::Base validates_presence_of :label validates_presence_of :mode + + validates_uniqueness_of :mode end diff --git a/src/test/unit/bonding_test.rb b/src/test/unit/bonding_test.rb index 4bdb079..1dbab38 100644 --- a/src/test/unit/bonding_test.rb +++ b/src/test/unit/bonding_test.rb @@ -30,7 +30,6 @@ class BondingTest < ActiveSupport::TestCase @bonding = Bonding.new( :name => 'Bonding1', :interface_name => 'bond0', - :type_id => bonding_types(:failover_bonding_type), :host_id => hosts(:mailservers_managed_node)) end @@ -50,14 +49,6 @@ class BondingTest < ActiveSupport::TestCase flunk 'Bondings have to have an interface name.' if @bonding.valid? end - # Ensures that the bonding type is required. - # - def test_valid_fails_without_type - @bonding.type_id = nil - - flunk 'Bondings have to have a valid type.' if @bonding.valid? - end - # Ensures that a host is required # def test_valid_fails_without_host -- 1.5.5.1 From mmorsi at redhat.com Thu Oct 9 20:49:29 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 09 Oct 2008 16:49:29 -0400 Subject: [Ovirt-devel] Re: [PATCH server] fixes to the bonding tests In-Reply-To: <1223585089-25209-1-git-send-email-mmorsi@redhat.com> References: <1223585089-25209-1-git-send-email-mmorsi@redhat.com> Message-ID: <48EE6E59.2060202@redhat.com> Mohammed Morsi wrote: > --- > src/app/models/bonding.rb | 2 ++ > src/app/models/bonding_type.rb | 2 ++ > src/test/unit/bonding_test.rb | 9 --------- > 3 files changed, 4 insertions(+), 9 deletions(-) > > diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb > index 941e2cd..eb25d05 100644 > --- a/src/app/models/bonding.rb > +++ b/src/app/models/bonding.rb > @@ -36,6 +36,8 @@ class Bonding < ActiveRecord::Base > validates_presence_of :interface_name, > :message => 'An interface name is required.' > > + validates_presence_of :host_id > + > belongs_to :host > belongs_to :bonding_type > > diff --git a/src/app/models/bonding_type.rb b/src/app/models/bonding_type.rb > index e0d2193..7697ff4 100644 > --- a/src/app/models/bonding_type.rb > +++ b/src/app/models/bonding_type.rb > @@ -23,4 +23,6 @@ > class BondingType < ActiveRecord::Base > validates_presence_of :label > validates_presence_of :mode > + > + validates_uniqueness_of :mode > end > diff --git a/src/test/unit/bonding_test.rb b/src/test/unit/bonding_test.rb > index 4bdb079..1dbab38 100644 > --- a/src/test/unit/bonding_test.rb > +++ b/src/test/unit/bonding_test.rb > @@ -30,7 +30,6 @@ class BondingTest < ActiveSupport::TestCase > @bonding = Bonding.new( > :name => 'Bonding1', > :interface_name => 'bond0', > - :type_id => bonding_types(:failover_bonding_type), > :host_id => hosts(:mailservers_managed_node)) > end > > @@ -50,14 +49,6 @@ class BondingTest < ActiveSupport::TestCase > flunk 'Bondings have to have an interface name.' if @bonding.valid? > end > > - # Ensures that the bonding type is required. > - # > - def test_valid_fails_without_type > - @bonding.type_id = nil > - > - flunk 'Bondings have to have a valid type.' if @bonding.valid? > - end > - > # Ensures that a host is required > # > def test_valid_fails_without_host > There are still two tests broken. I traced the problem to host-browser/host-browser.rb line 305 where calls to BondingType.find_by_proto fail to return anything. Checking the database at this time verifies that there is nothing in the table, which is strange as this is only an issue for those two test cases, all other times it works successfully (BondingType table gets populated and everything). I have a feeling some deletion is cascading to deleting the BondingType entries, or a similar issue but I'm not sure where this is happening. Think you could take care of these last two bits as you know this bonding / nic system better than I do (also if something looks wrong in this patch, could you correct it?) Thanks alot, Mo 1) Failure: test_write_host_info_with_duplicate_nic(HostBrowserIdentifyTest) [./test/unit/host_browser_identify_test.rb:226:in `test_write_host_info_with_duplicate_nic' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']: Exception raised: Class: Message: <"Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id"> ---Backtrace--- ./test/unit/../../host-browser/host-browser.rb:317:in `write_host_info' ./test/unit/../../host-browser/host-browser.rb:308:in `collect' ./test/unit/../../host-browser/host-browser.rb:308:in `write_host_info' ./test/unit/host_browser_identify_test.rb:226:in `test_write_host_info_with_duplicate_nic' ./test/unit/host_browser_identify_test.rb:226:in `test_write_host_info_with_duplicate_nic' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run' --------------- 2) Failure: test_write_host_info_with_missing_uuid(HostBrowserIdentifyTest) [./test/unit/host_browser_identify_test.rb:167:in `test_write_host_info_with_missing_uuid' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']: Exception raised: Class: Message: <"Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id"> ---Backtrace--- ./test/unit/../../host-browser/host-browser.rb:317:in `write_host_info' ./test/unit/../../host-browser/host-browser.rb:308:in `collect' ./test/unit/../../host-browser/host-browser.rb:308:in `write_host_info' ./test/unit/host_browser_identify_test.rb:167:in `test_write_host_info_with_missing_uuid' ./test/unit/host_browser_identify_test.rb:167:in `test_write_host_info_with_missing_uuid' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run' --------------- From xxqonline at hotmail.com Fri Oct 10 05:21:55 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Fri, 10 Oct 2008 13:21:55 +0800 Subject: [Ovirt-devel] Can any one help me, I cannot start the virtual machine with default ovirt-appliance In-Reply-To: <48EE2BE8.1030101@redhat.com> References: <48EE2BE8.1030101@redhat.com> Message-ID: Thanks Alan: I am using 32bit Fedora Linux physical.priv.ovirt.org 2.6.25-14.fc9.i686 #1 SMP Thu May 1 06:28:41 EDT 2008 i686 i686 i386 GNU/Linux I start the ovirt in the node model. [root at physical ~]# rpm -qa | grep ovirt virt-viewer-plugin-0.0.3-3ovirt1.fc9.i386 libvirt-python-0.4.5-2ovirt1.fc9.i386 ovirt-appliance-0.93-1.fc9.i386 ovirt-release-0.93-1.fc9.noarch ovirt-node-0.93-1.fc9.i386 libvirt-0.4.5-2ovirt1.fc9.i386 kvm-74-7.fc9.ovirt.i386 ovirt-docs-0.93-1.fc9.noarch I have configured the NFS as described in the doc. But I do not know why the storage show nothing, please refer to the attachement. When I create aovirt-node VM, it always pending, and the other PXE-boot, Fedora 9 cobbler profile and so on always show stopped. I cannot start them. And when I use the # ssh -Y root at 192.168.50.2 virt-viewer -c qemu+tcp://node3.priv.ovirt.org/system VM_NAME It told me the virt-viewer command is not on the 192.168.50.2. Regards, Qiang -------------------------------------------------- From: "Alan Pevec" Sent: Friday, October 10, 2008 12:06 AM To: Cc: Subject: Re: [Ovirt-devel] Can any one help me, I cannot start the virtual machine with default ovirt-appliance > xxqonline at hotmail.com wrote: >> Hello folks: >> I use the virsh 0.4.5. I followed the quick start to set the >> storage pool (nfs). >> After that I checked the property of that pool. There is no size >> information. >> Then I create a vm pool, and try to create a fedora 9 (Cobbler Profile), >> I failed to start it. >> Can any one give me a help? > > First, which version are you using? > Check that firstrun worked fine, see > http://ovirt.org/page/TroubleShooting#Debugging_oVirt_Appliance_firstboot > for how to modify the fresh image after ovirt-appliance RPM installation > and before starting it up for the first time and check logs mentioned > there. > > -------------- next part -------------- A non-text attachment was scrubbed... Name: Storage.png Type: image/png Size: 22479 bytes Desc: not available URL: From xxqonline at hotmail.com Fri Oct 10 06:59:48 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Fri, 10 Oct 2008 14:59:48 +0800 Subject: [Ovirt-devel] libvir: QEMU error : internal error failed to set CPU affinity Permission denied Message-ID: Hello folks: I have encountered the following problem: [root at vmodev01 ~]# virsh start ovirt-appliance libvir: QEMU error : internal error failed to set CPU affinity Permission denied error: Failed to start domain ovirt-appliance How can I solve it? Thanks, Qiang -------------- next part -------------- An HTML attachment was scrubbed... URL: From xxqonline at hotmail.com Fri Oct 10 07:02:41 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Fri, 10 Oct 2008 15:02:41 +0800 Subject: [Ovirt-devel] Re: libvir: QEMU error : internal error failed to set CPU affinity Permission denied Message-ID: I am using root From: xxqonline at hotmail.com Sent: Friday, October 10, 2008 2:59 PM To: ovirt-devel at redhat.com Subject: libvir: QEMU error : internal error failed to set CPU affinity Permission denied Hello folks: I have encountered the following problem: [root at vmodev01 ~]# virsh start ovirt-appliance libvir: QEMU error : internal error failed to set CPU affinity Permission denied error: Failed to start domain ovirt-appliance How can I solve it? Thanks, Qiang -------------- next part -------------- An HTML attachment was scrubbed... URL: From apevec at redhat.com Fri Oct 10 07:06:48 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Oct 2008 09:06:48 +0200 Subject: [Ovirt-devel] Re: libvir: QEMU error : internal error failed to set CPU affinity Permission denied In-Reply-To: References: Message-ID: <48EEFF08.9050602@redhat.com> xxqonline at hotmail.com wrote: > I am using root yeah, but libvirtd and qemu run confined in SELinux domains (virtd_t and qemu_t, resp.) > [root at vmodev01 ~]# virsh start ovirt-appliance > libvir: QEMU error : internal error failed to set CPU affinity > Permission denied > error: Failed to start domain ovirt-appliance > How can I solve it? IIRC that was solved by the selinux-policy update some time ago, is your system fully updated Fedora 9 ? From xxqonline at hotmail.com Fri Oct 10 07:08:11 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Fri, 10 Oct 2008 15:08:11 +0800 Subject: [Ovirt-devel] Re: libvir: QEMU error : internal error failed to set CPU affinity Permission denied Message-ID: I am not sure, I just install from a DVD qxu at vmodev01-2: uname -a Linux vmodev01 2.6.25-14.fc9.x86_64 #1 SMP Thu May 1 06:06:21 EDT 2008 x86_64 x86_64 x86_64 GNU/Linux From: xxqonline at hotmail.com Sent: Friday, October 10, 2008 2:59 PM To: ovirt-devel at redhat.com Subject: libvir: QEMU error : internal error failed to set CPU affinity Permission denied Hello folks: I have encountered the following problem: [root at vmodev01 ~]# virsh start ovirt-appliance libvir: QEMU error : internal error failed to set CPU affinity Permission denied error: Failed to start domain ovirt-appliance How can I solve it? Thanks, Qiang -------------- next part -------------- An HTML attachment was scrubbed... URL: From xxqonline at hotmail.com Fri Oct 10 07:08:47 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Fri, 10 Oct 2008 15:08:47 +0800 Subject: [Ovirt-devel] Re: libvir: QEMU error : internal error failed to set CPU affinity Permission denied In-Reply-To: <48EEFF08.9050602@redhat.com> References: <48EEFF08.9050602@redhat.com> Message-ID: I am not sure, I just install from a DVD qxu at vmodev01-2: uname -a Linux vmodev01 2.6.25-14.fc9.x86_64 #1 SMP Thu May 1 06:06:21 EDT 2008 x86_64 x86_64 x86_64 GNU/Linux -------------------------------------------------- From: "Alan Pevec" Sent: Friday, October 10, 2008 3:06 PM To: Cc: Subject: Re: [Ovirt-devel] Re: libvir: QEMU error : internal error failed to set CPU affinity Permission denied > xxqonline at hotmail.com wrote: >> I am using root > > yeah, but libvirtd and qemu run confined in SELinux domains (virtd_t and > qemu_t, resp.) >> [root at vmodev01 ~]# virsh start ovirt-appliance >> libvir: QEMU error : internal error failed to set CPU affinity Permission >> denied >> error: Failed to start domain ovirt-appliance >> How can I solve it? > > IIRC that was solved by the selinux-policy update some time ago, is your > system fully updated Fedora 9 ? > > From apevec at redhat.com Fri Oct 10 09:16:17 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Oct 2008 11:16:17 +0200 Subject: [Ovirt-devel] Can any one help me, I cannot start the virtual machine with default ovirt-appliance In-Reply-To: References: <48EE2BE8.1030101@redhat.com> Message-ID: <48EF1D61.5030009@redhat.com> xxqonline at hotmail.com wrote: > I am using 32bit Fedora > Linux physical.priv.ovirt.org 2.6.25-14.fc9.i686 #1 SMP Thu May 1 > 06:28:41 EDT 2008 i686 i686 i386 GNU/Linux > I start the ovirt in the node model. please run yum update to get latest F9 updates. > [root at physical ~]# rpm -qa | grep ovirt > virt-viewer-plugin-0.0.3-3ovirt1.fc9.i386 > libvirt-python-0.4.5-2ovirt1.fc9.i386 > ovirt-appliance-0.93-1.fc9.i386 > ovirt-release-0.93-1.fc9.noarch > ovirt-node-0.93-1.fc9.i386 > libvirt-0.4.5-2ovirt1.fc9.i386 > kvm-74-7.fc9.ovirt.i386 > ovirt-docs-0.93-1.fc9.noarch that's all good - you have latest released beta installed > I have configured the NFS as described in the doc. But I do not know why > the storage show nothing, please refer to the attachement. Do you have node3 running? Taskomatic needs at least one Node to be up and running to be able to execute libvirt storage pool commands. > When I create aovirt-node VM, it always pending, and the other PXE-boot, > Fedora 9 cobbler profile and so on always show stopped. > I cannot start them. > And when I use the # ssh -Y root at 192.168.50.2 virt-viewer -c > qemu+tcp://node3.priv.ovirt.org/system VM_NAME > It told me the virt-viewer command is not on the 192.168.50.2. Yeah, this is old instruction, if you have running oVirt managed VM, you should get "Console" link in VM details which starts external vnc viewer via virt-viewer-plugin. Please double check you have node3 running: virsh list on the host machine should show ovirt-appliance and node3 VMs running - in "fake" Nodes modus, oVirt managed VMs are created inside nodeX VMs (yeah, confusing and useless, this is just for demo/quick test in development) From Itamar.Heim at qumranet.com Thu Oct 9 22:49:29 2008 From: Itamar.Heim at qumranet.com (Itamar Heim) Date: Thu, 9 Oct 2008 15:49:29 -0700 Subject: [Ovirt-devel] Ovirt-qpid API In-Reply-To: <20081008211933.089ac7a8@tp.mains.net> References: <20081008211933.089ac7a8@tp.mains.net> Message-ID: Some thoughts: 1. Assuming oVirt is booting with a KVM module, would be valuable to know if KVM is actually enabled (not sure what is the generalization of this for Xen/others). Usually it means VT/SVM was not enabled by bios, or that another bios flag is preventing it from booting (TX for example). 2. memory - need to know more than just physical memory size, also free and cached I guess. (could be already collected by collectd - didn't get a chance to go over it yet) 3. node version - reflects potential capabilities, backward compatibility considerations by management, etc. (not sure if we don't need one for KVM/other hypervisor version, and one for the libvirt version) Itamar -----Original Message----- From: ovirt-devel-bounces at redhat.com [mailto:ovirt-devel-bounces at redhat.com] On Behalf Of Ian Main Sent: Thursday, October 09, 2008 6:20 AM To: ovirt-devel at redhat.com Subject: [Ovirt-devel] Ovirt-qpid API So I've been doing some work on this API. It looks like libvirt hardware enumeration is coming along reasonably, so I'm not going to cover all that in this API. Instead we'll get a version of libvirt built in a few weeks with hardware enumeration and update libvirt-qpid to support that. As always, please take a look and see if I forgot anything or such. Here's a rundown of the info we are gathering now: - Hardware uuid using HAL - architecture - CPU info: - number of cores - processor - core id - cpu cores - vendor id - model - cpu family - cpuid level - cpu MHz - cache size - cpu flags - Network interfaces: - interface name - MAC - ip address - netmask - broadcast - bandwidth (speed - 100baseT full/half etc.) - Memory size Things gathered by libvirt hardware enumeration: The in-development Libvirt API is currently using HAL to enumerate devices. This gives us information on processor, storage, network interfaces etc. but does not include the IP address configuration of a network interface, nor the speed capabilities. Otherwise we have: - Kerberos key download. This will remain a separate step. - Various statistics pushed via collectd. This can remain in place, but I think we should consider pushing the load avg (or maybe cpu usage? I know there are some graphing issues with load) which would eliminate the need for host-collect.rb. - Network configuration. - Shutdown/reboot. - Beeps and flashes to identify machine and nics. So the attached API deals with all the network configuration, IP address information etc. as well as the various other functions we want to see supported beyond what libvirt provides for us. On the network API, I wasn't entirely positive I should take this approach or if I should create another Bonding class that bonds networkInterfaces. In the end I chose this model as it essentially mirrors what the configuration is doing. I can easily be convinced to do the other though.. or even to just mock it up and see how it looks. >From the python qpid client, you would setup the network something like this: s = Session() b = s.addBroker() onode = s.getObjects(name="ovirtNode", uuid=someuuid) # So this call would return a networkInterface class but would be configured # for bonding. This is the part I'm 50/50 on. I could create a separate # Bonding class. bond = onode.createBondingInterface("bond0") # It should basically be setup to be right on creation.. bond.dhcp = true bond.restart() nics = s.getObjects(name="networkInterface", ovirtNode=onode) for nic in nics: nic.master="bond0" nic.restart() # For fun.. onode.beeping = true nic[0].blinking = true The objects will also be created from the configuration on the node if it had persistent storage and came up configured. Ian From berrange at redhat.com Fri Oct 10 12:47:48 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Fri, 10 Oct 2008 13:47:48 +0100 Subject: [Ovirt-devel] Ovirt-qpid API In-Reply-To: References: <20081008211933.089ac7a8@tp.mains.net> Message-ID: <20081010124748.GG12910@redhat.com> On Thu, Oct 09, 2008 at 03:49:29PM -0700, Itamar Heim wrote: > Some thoughts: > 1. Assuming oVirt is booting with a KVM module, would be valuable to > know if KVM is actually enabled (not sure what is the generalization of > this for Xen/others). > Usually it means VT/SVM was not enabled by bios, or that another bios > flag is preventing it from booting (TX for example). libvirt provides you this information via its host capabilities API: # virsh capabilities i686 hvm 32 /usr/bin/qemu pc isapc /usr/bin/qemu-kvm [cut restof XML] If you don't get a 'kvm' domain type, then the module is not active and/or the userspace is missing. > 2. memory - need to know more than just physical memory size, also free > and cached I guess. libvirt also exposes this - and NUMA topology and free memory per NUMA cell. > 3. node version - reflects potential capabilities, backward > compatibility considerations by management, etc. > (not sure if we don't need one for KVM/other hypervisor version, and one > for the libvirt version) Although we expose the libvirt version, applications should really try to drive themselves off capabilities, rather than the version number. If we backport new features into older libvirt during the lifetime of a product, then version number checks fail, but capability based checks will work correctly. libvirt exposes alot of capability info for guest domains types, but we need to increase its info for things like storage, network and the like. ovirt qpid will likely want to expose some form of capability based information wrt the host OS features. eg info on allowed networking configs (eg whether bonding, vlans, etc are allowed), and level of clustering support. We should define somewhere this info can be exposed and then use it when adding new features, betweeen releases. 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 slinabery at redhat.com Fri Oct 10 14:33:54 2008 From: slinabery at redhat.com (Steve Linabery) Date: Fri, 10 Oct 2008 09:33:54 -0500 Subject: [Ovirt-devel] flexchart build instructions In-Reply-To: <20081009190645.GF10125@redhat.com> References: <20081009190645.GF10125@redhat.com> Message-ID: <20081010143353.GG10125@redhat.com> I left out an important additional step: On Thu, Oct 09, 2008 at 02:06:45PM -0500, Steve Linabery wrote: > Hi, > > If anyone would like to build the flexchart swf (Flash Movie), here's how: > > 0) download the Open Flex SDK at http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3 (I think I used latest stable build) > 1) unzip that archive somewhere and add the bin subdirectory of same to your PATH 1a) run dos2unix on bin/mxmlc > 2) Apply the patch I sent for review earlier today > 3) cd to {ovirt_src_home}/server/src/flexchart in your patched source tree > 4) execute 'mxmlc flexchart.mxml' > 5) copy the resulting flexchart.swf to /usr/share/ovirt-server/public on your appliance (which you've just rebuilt using my patch) > > Please let me know if it works for you (or doesn't!). If you see blue bars in your chart area (assumes you have some rrd data stored for memory use), it's working. > > Thanks, > Steve > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From dpierce at redhat.com Fri Oct 10 14:40:04 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Oct 2008 10:40:04 -0400 Subject: [Ovirt-devel] [PATCH server] Fixes broken unit tests for bonding classes and the host browser. Message-ID: <1223649604-8272-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- src/app/models/bonding.rb | 6 ++++++ src/app/models/bonding_type.rb | 4 ++++ src/test/unit/bonding_test.rb | 10 +++++----- src/test/unit/bonding_type_test.rb | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 941e2cd..4b6e8a5 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -33,6 +33,12 @@ class Bonding < ActiveRecord::Base validates_presence_of :name, :message => 'A name is required.' + validates_presence_of :host_id, + :message => 'A host must be specified.' + + validates_presence_of :bonding_type_id, + :message => 'A bonding type must be specified.' + validates_presence_of :interface_name, :message => 'An interface name is required.' diff --git a/src/app/models/bonding_type.rb b/src/app/models/bonding_type.rb index e0d2193..f844df1 100644 --- a/src/app/models/bonding_type.rb +++ b/src/app/models/bonding_type.rb @@ -22,5 +22,9 @@ # class BondingType < ActiveRecord::Base validates_presence_of :label + validates_uniqueness_of :label, + :message => 'Label must be unique' validates_presence_of :mode + validates_uniqueness_of :mode, + :message => 'Mode must be unique' end diff --git a/src/test/unit/bonding_test.rb b/src/test/unit/bonding_test.rb index 4bdb079..a2cdef8 100644 --- a/src/test/unit/bonding_test.rb +++ b/src/test/unit/bonding_test.rb @@ -28,10 +28,10 @@ class BondingTest < ActiveSupport::TestCase def setup @bonding = Bonding.new( - :name => 'Bonding1', - :interface_name => 'bond0', - :type_id => bonding_types(:failover_bonding_type), - :host_id => hosts(:mailservers_managed_node)) + :name => 'Bonding1', + :interface_name => 'bond0', + :bonding_type_id => bonding_types(:failover_bonding_type), + :host_id => hosts(:mailservers_managed_node)) end # Ensures that the name is required. @@ -53,7 +53,7 @@ class BondingTest < ActiveSupport::TestCase # Ensures that the bonding type is required. # def test_valid_fails_without_type - @bonding.type_id = nil + @bonding.bonding_type_id = nil flunk 'Bondings have to have a valid type.' if @bonding.valid? end diff --git a/src/test/unit/bonding_type_test.rb b/src/test/unit/bonding_type_test.rb index 5f2ccb2..c900002 100644 --- a/src/test/unit/bonding_type_test.rb +++ b/src/test/unit/bonding_type_test.rb @@ -29,6 +29,6 @@ class BondingTypeTest < ActiveSupport::TestCase def test_modes_must_be_unique @bonding_type.mode = BondingType.find(:first).mode - assert @bonding_type.save == false, 'A bonding type with a duplicate mode should not save.' + flunk "Modes must be unique" if @bonding_type.valid? end end -- 1.5.5.1 From dpierce at redhat.com Fri Oct 10 14:43:05 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Oct 2008 10:43:05 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223649785-8471-1-git-send-email-dpierce@redhat.com> The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 51 +++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- 4 files changed, 185 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..5e9da06 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..6c4ace6 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From hbrock at redhat.com Fri Oct 10 14:57:55 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Fri, 10 Oct 2008 10:57:55 -0400 Subject: [Ovirt-devel] flexchart build instructions In-Reply-To: <20081010143353.GG10125@redhat.com> References: <20081009190645.GF10125@redhat.com> <20081010143353.GG10125@redhat.com> Message-ID: <20081010145755.GD30422@redhat.com> On Fri, Oct 10, 2008 at 09:33:54AM -0500, Steve Linabery wrote: > I left out an important additional step: > > On Thu, Oct 09, 2008 at 02:06:45PM -0500, Steve Linabery wrote: > > Hi, > > > > If anyone would like to build the flexchart swf (Flash Movie), here's how: > > > > 0) download the Open Flex SDK at http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3 (I think I used latest stable build) > > 1) unzip that archive somewhere and add the bin subdirectory of same to your PATH > > 1a) run dos2unix on bin/mxmlc > > > 2) Apply the patch I sent for review earlier today > > 3) cd to {ovirt_src_home}/server/src/flexchart in your patched source tree > > 4) execute 'mxmlc flexchart.mxml' > > 5) copy the resulting flexchart.swf to /usr/share/ovirt-server/public on your appliance (which you've just rebuilt using my patch) > > > > Please let me know if it works for you (or doesn't!). If you see blue bars in your chart area (assumes you have some rrd data stored for memory use), it's working. > > > > Thanks, > > Steve > > Crazy, is mxmlc a shell script? --H From apevec at redhat.com Fri Oct 10 15:11:33 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 10 Oct 2008 17:11:33 +0200 Subject: [Ovirt-devel] flexchart build instructions In-Reply-To: <20081010145755.GD30422@redhat.com> References: <20081009190645.GF10125@redhat.com> <20081010143353.GG10125@redhat.com> <20081010145755.GD30422@redhat.com> Message-ID: <48EF70A5.6010206@redhat.com> Hugh O. Brock wrote: >>> 0) download the Open Flex SDK at http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3 (I think I used latest stable build) >>> 1) unzip that archive somewhere and add the bin subdirectory of same to your PATH >> 1a) run dos2unix on bin/mxmlc > Crazy, is mxmlc a shell script? yeah, wrapper scripts starting Java. BTW, in 3.1.0.2710 ZIP I got all scripts w/o execute bit, so also chmod +x bin/mxmlc From mmorsi at redhat.com Fri Oct 10 15:17:04 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 10 Oct 2008 11:17:04 -0400 Subject: [Ovirt-devel] [PATCH server] Fixes broken unit tests for bonding classes and the host browser. In-Reply-To: <1223649604-8272-1-git-send-email-dpierce@redhat.com> References: <1223649604-8272-1-git-send-email-dpierce@redhat.com> Message-ID: <48EF71F0.2060709@redhat.com> Darryl L. Pierce wrote: > Signed-off-by: Darryl L. Pierce > --- > src/app/models/bonding.rb | 6 ++++++ > src/app/models/bonding_type.rb | 4 ++++ > src/test/unit/bonding_test.rb | 10 +++++----- > src/test/unit/bonding_type_test.rb | 2 +- > 4 files changed, 16 insertions(+), 6 deletions(-) > > diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb > index 941e2cd..4b6e8a5 100644 > --- a/src/app/models/bonding.rb > +++ b/src/app/models/bonding.rb > @@ -33,6 +33,12 @@ class Bonding < ActiveRecord::Base > validates_presence_of :name, > :message => 'A name is required.' > > + validates_presence_of :host_id, > + :message => 'A host must be specified.' > + > + validates_presence_of :bonding_type_id, > + :message => 'A bonding type must be specified.' > + > validates_presence_of :interface_name, > :message => 'An interface name is required.' > > diff --git a/src/app/models/bonding_type.rb b/src/app/models/bonding_type.rb > index e0d2193..f844df1 100644 > --- a/src/app/models/bonding_type.rb > +++ b/src/app/models/bonding_type.rb > @@ -22,5 +22,9 @@ > # > class BondingType < ActiveRecord::Base > validates_presence_of :label > + validates_uniqueness_of :label, > + :message => 'Label must be unique' > validates_presence_of :mode > + validates_uniqueness_of :mode, > + :message => 'Mode must be unique' > end > diff --git a/src/test/unit/bonding_test.rb b/src/test/unit/bonding_test.rb > index 4bdb079..a2cdef8 100644 > --- a/src/test/unit/bonding_test.rb > +++ b/src/test/unit/bonding_test.rb > @@ -28,10 +28,10 @@ class BondingTest < ActiveSupport::TestCase > > def setup > @bonding = Bonding.new( > - :name => 'Bonding1', > - :interface_name => 'bond0', > - :type_id => bonding_types(:failover_bonding_type), > - :host_id => hosts(:mailservers_managed_node)) > + :name => 'Bonding1', > + :interface_name => 'bond0', > + :bonding_type_id => bonding_types(:failover_bonding_type), > + :host_id => hosts(:mailservers_managed_node)) > end > > # Ensures that the name is required. > @@ -53,7 +53,7 @@ class BondingTest < ActiveSupport::TestCase > # Ensures that the bonding type is required. > # > def test_valid_fails_without_type > - @bonding.type_id = nil > + @bonding.bonding_type_id = nil > > flunk 'Bondings have to have a valid type.' if @bonding.valid? > end > diff --git a/src/test/unit/bonding_type_test.rb b/src/test/unit/bonding_type_test.rb > index 5f2ccb2..c900002 100644 > --- a/src/test/unit/bonding_type_test.rb > +++ b/src/test/unit/bonding_type_test.rb > @@ -29,6 +29,6 @@ class BondingTypeTest < ActiveSupport::TestCase > def test_modes_must_be_unique > @bonding_type.mode = BondingType.find(:first).mode > > - assert @bonding_type.save == false, 'A bonding type with a duplicate mode should not save.' > + flunk "Modes must be unique" if @bonding_type.valid? > end > end > ACK as this patch seems to take care of the bonding problems. As mentioned I'm seeing two last broken test cases, both seeming to stem from the same problem (they are attached). It seems that in host_browser.rb, specifically write_host_info line 305 doesn't succeed in retrieving BootType from proto in those two broken cases. I've verified that the contents of the BootType table is null there where as other tables have data (crudely tested by putting a long sleep immediately before the line in question and then running the psql client manually and selecting records ). I tried debugging it for a while yesterday but was unable to come up with a solution, perhaps we can figure out something together. (a theory I had was that some deletion is cascading and knocking out the BootType records). Perhaps if everything is working locally for you, there is still a bit of code in your local repo that you haven't sent out yet? I'm wondering because I see you mention host_browser in the subject line but don't see the actual changes to any host browser module in your attached patch. Thanks alot, Mo 1) Failure: test_write_host_info_with_duplicate_nic(HostBrowserIdentifyTest) [./test/unit/host_browser_identify_test.rb:226:in `test_write_host_info_with_duplicate_nic' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']: Exception raised: Class: Message: <"Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id"> ---Backtrace--- ./test/unit/../../host-browser/host-browser.rb:317:in `write_host_info' ./test/unit/../../host-browser/host-browser.rb:308:in `collect' ./test/unit/../../host-browser/host-browser.rb:308:in `write_host_info' ./test/unit/host_browser_identify_test.rb:226:in `test_write_host_info_with_duplicate_nic' ./test/unit/host_browser_identify_test.rb:226:in `test_write_host_info_with_duplicate_nic' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run' --------------- 2) Failure: test_write_host_info_with_missing_uuid(HostBrowserIdentifyTest) [./test/unit/host_browser_identify_test.rb:167:in `test_write_host_info_with_missing_uuid' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']: Exception raised: Class: Message: <"Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id"> ---Backtrace--- ./test/unit/../../host-browser/host-browser.rb:317:in `write_host_info' ./test/unit/../../host-browser/host-browser.rb:308:in `collect' ./test/unit/../../host-browser/host-browser.rb:308:in `write_host_info' ./test/unit/host_browser_identify_test.rb:167:in `test_write_host_info_with_missing_uuid' ./test/unit/host_browser_identify_test.rb:167:in `test_write_host_info_with_missing_uuid' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run' --------------- 38 tests, 47 assertions, 2 failures, 0 errors From hbrock at redhat.com Fri Oct 10 15:26:44 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Fri, 10 Oct 2008 11:26:44 -0400 Subject: [Ovirt-devel] flexchart build instructions In-Reply-To: <48EF70A5.6010206@redhat.com> References: <20081009190645.GF10125@redhat.com> <20081010143353.GG10125@redhat.com> <20081010145755.GD30422@redhat.com> <48EF70A5.6010206@redhat.com> Message-ID: <20081010152644.GE30422@redhat.com> On Fri, Oct 10, 2008 at 05:11:33PM +0200, Alan Pevec wrote: > Hugh O. Brock wrote: >>>> 0) download the Open Flex SDK at http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3 (I think I used latest stable build) >>>> 1) unzip that archive somewhere and add the bin subdirectory of same to your PATH >>> 1a) run dos2unix on bin/mxmlc > >> Crazy, is mxmlc a shell script? > yeah, wrapper scripts starting Java. > BTW, in 3.1.0.2710 ZIP I got all scripts w/o execute bit, so also chmod +x bin/mxmlc Awesome. So when we put this stuff in the build system I guess we can fix all that permanently, eh? --Hugh From slinabery at redhat.com Fri Oct 10 15:33:40 2008 From: slinabery at redhat.com (Steve Linabery) Date: Fri, 10 Oct 2008 10:33:40 -0500 Subject: [Ovirt-devel] REVISED: [PATCH server] Add a rudimentary flash chart written in flex framework to summary pages. In-Reply-To: <20081009101929.GC10125@redhat.com> References: <20081009101614.GA10125@redhat.com> <20081009101929.GC10125@redhat.com> Message-ID: <20081010153339.GH10125@redhat.com> Hi, Here's a resubmission of this patch, with git pre-commit hooks enabled this time. Thank you for testing! Steve -------------- next part -------------- >From 2f4c9126262cb9cc2272eb6ca62436c0e2746ab9 Mon Sep 17 00:00:00 2001 From: Steve Linabery Date: Fri, 10 Oct 2008 10:29:53 -0500 Subject: [PATCH server] Add to summary pages a rudimentary flash chart written in flex framework --- src/app/controllers/graph_controller.rb | 30 +- src/app/views/graph/flexchart_data.rhtml | 1 + src/app/views/graph/history_graphs.rhtml | 87 +--- src/config/routes.rb | 1 + src/flexchart/README.txt | 8 + src/flexchart/com/adobe/serialization/json/JSON.as | 85 +++ .../com/adobe/serialization/json/JSONDecoder.as | 221 ++++++++ .../com/adobe/serialization/json/JSONEncoder.as | 299 +++++++++++ .../com/adobe/serialization/json/JSONParseError.as | 87 +++ .../com/adobe/serialization/json/JSONToken.as | 104 ++++ .../com/adobe/serialization/json/JSONTokenType.as | 67 +++ .../com/adobe/serialization/json/JSONTokenizer.as | 547 ++++++++++++++++++++ src/flexchart/flexchart.mxml | 20 + src/flexchart/org/ovirt/ChartLoader.as | 64 +++ src/flexchart/org/ovirt/DataSeries.as | 42 ++ src/flexchart/org/ovirt/DataSource.as | 57 ++ src/public/javascripts/jquery.flash.js | 288 ++++++++++ 17 files changed, 1930 insertions(+), 78 deletions(-) create mode 100644 src/app/views/graph/flexchart_data.rhtml create mode 100644 src/flexchart/README.txt create mode 100644 src/flexchart/com/adobe/serialization/json/JSON.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONDecoder.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONEncoder.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONParseError.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONToken.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenType.as create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenizer.as create mode 100644 src/flexchart/flexchart.mxml create mode 100644 src/flexchart/org/ovirt/ChartLoader.as create mode 100644 src/flexchart/org/ovirt/DataSeries.as create mode 100644 src/flexchart/org/ovirt/DataSource.as create mode 100644 src/public/javascripts/jquery.flash.js diff --git a/src/app/controllers/graph_controller.rb b/src/app/controllers/graph_controller.rb index dbe2afc..6450935 100644 --- a/src/app/controllers/graph_controller.rb +++ b/src/app/controllers/graph_controller.rb @@ -3,7 +3,24 @@ require 'util/stats/Stats' class GraphController < ApplicationController layout nil - # generate layout for avaialability bar graphs + def flexchart_data + + #FIXME: use the stats package aggregation (when it's available) + #instead of the old method + graph_obj = history_graph_data_object + + #FIXME: for this release, the flexchart shows only peak values, + # and only shows a default of the last 40 data points in rrd. + graph_data = { :labels => graph_obj[:timepoints].last(40), + :values => graph_obj[:dataset][2][:values].last(40) } + my_data = graph_data[:labels].zip(graph_data[:values]) + @graph = { :vectors => my_data, + :max_value => graph_obj[:total_peak] + } + end + + + # generate layout for availability bar graphs def availability_graph @id = params[:id] @target = params[:target] @@ -67,6 +84,10 @@ class GraphController < ApplicationController # retrieves data for history graphs def history_graph_data + render :json => history_graph_data_object + end + + def history_graph_data_object history_graphs myDays = params[:days] target = params[:target] @@ -212,9 +233,10 @@ class GraphController < ApplicationController :stroke => @avg_history[:color], :strokeWidth => 1 } - ] + ], + :total_peak => total_peak } - render :json => graph_object + end @@ -261,7 +283,7 @@ class GraphController < ApplicationController } ] } - render :json => graph_object + end diff --git a/src/app/views/graph/flexchart_data.rhtml b/src/app/views/graph/flexchart_data.rhtml new file mode 100644 index 0000000..a79ce06 --- /dev/null +++ b/src/app/views/graph/flexchart_data.rhtml @@ -0,0 +1 @@ +<%= @graph.to_json %> diff --git a/src/app/views/graph/history_graphs.rhtml b/src/app/views/graph/history_graphs.rhtml index 2b6874f..f372e4b 100644 --- a/src/app/views/graph/history_graphs.rhtml +++ b/src/app/views/graph/history_graphs.rhtml @@ -1,76 +1,15 @@ +<%= javascript_include_tag "jquery.flash.js" %> +
- -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_1_graph', :div_id => 'cpu_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_7_graph', :div_id => 'cpu_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_30_graph', :div_id => 'cpu_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 30 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_1_graph', :div_id => 'memory_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_7_graph', :div_id => 'memory_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_30_graph', :div_id => 'memory_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1162, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 30 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_1_graph', :div_id => 'load_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 1 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_7_graph', :div_id => 'load_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 7 } ) } %> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_30_graph', :div_id => 'load_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 30 } ) } %> - -
-
-
- -
-
- -
-
- Peak     - Average     - Rolling Peak     - Rolling Average     -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - diff --git a/src/config/routes.rb b/src/config/routes.rb index 6f8e481..8d538cb 100644 --- a/src/config/routes.rb +++ b/src/config/routes.rb @@ -41,6 +41,7 @@ ActionController::Routing::Routes.draw do |map| map.connect ':controller/service.wsdl', :action => 'wsdl' # Install the default route as the lowest priority. + map.connect 'graph/flexchart_data/:id/:target/:days', :controller => 'graph', :action => 'flexchart_data' map.connect ':controller/:action/:id.:format' map.connect ':controller/:action/:id' diff --git a/src/flexchart/README.txt b/src/flexchart/README.txt new file mode 100644 index 0000000..66eb183 --- /dev/null +++ b/src/flexchart/README.txt @@ -0,0 +1,8 @@ +Until mxmlc gets packaged and this becomes part of autobuild, +you must obtain the open flex SDK to build the swf movie. + +Once you have mxmlc on your system, run: + +mxmlc flexchart.mxml + +in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public on your appliance. diff --git a/src/flexchart/com/adobe/serialization/json/JSON.as b/src/flexchart/com/adobe/serialization/json/JSON.as new file mode 100644 index 0000000..8cd9286 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSON.as @@ -0,0 +1,85 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * This class provides encoding and decoding of the JSON format. + * + * Example usage: + * + * // create a JSON string from an internal object + * JSON.encode( myObject ); + * + * // read a JSON string into an internal object + * var myObject:Object = JSON.decode( jsonString ); + * + */ + public class JSON { + + + /** + * Encodes a object into a JSON string. + * + * @param o The object to create a JSON string for + * @return the JSON string representing o + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public static function encode( o:Object ):String { + + var encoder:JSONEncoder = new JSONEncoder( o ); + return encoder.getString(); + + } + + /** + * Decodes a JSON string into a native object. + * + * @param s The JSON string representing the object + * @return A native object as specified by s + * @throw JSONParseError + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public static function decode( s:String ):* { + + var decoder:JSONDecoder = new JSONDecoder( s ) + return decoder.getValue(); + + } + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONDecoder.as b/src/flexchart/com/adobe/serialization/json/JSONDecoder.as new file mode 100644 index 0000000..83bba1c --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONDecoder.as @@ -0,0 +1,221 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONDecoder { + + /** The value that will get parsed from the JSON string */ + private var value:*; + + /** The tokenizer designated to read the JSON string */ + private var tokenizer:JSONTokenizer; + + /** The current token from the tokenizer */ + private var token:JSONToken; + + /** + * Constructs a new JSONDecoder to parse a JSON string + * into a native object. + * + * @param s The JSON string to be converted + * into a native object + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONDecoder( s:String ) { + + tokenizer = new JSONTokenizer( s ); + + nextToken(); + value = parseValue(); + } + + /** + * Gets the internal object that was created by parsing + * the JSON string passed to the constructor. + * + * @return The internal object representation of the JSON + * string that was passed to the constructor + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function getValue():* { + return value; + } + + /** + * Returns the next token from the tokenzier reading + * the JSON string + */ + private function nextToken():JSONToken { + return token = tokenizer.getNextToken(); + } + + /** + * Attempt to parse an array + */ + private function parseArray():Array { + // create an array internally that we're going to attempt + // to parse from the tokenizer + var a:Array = new Array(); + + // grab the next token from the tokenizer to move + // past the opening [ + nextToken(); + + // check to see if we have an empty array + if ( token.type == JSONTokenType.RIGHT_BRACKET ) { + // we're done reading the array, so return it + return a; + } + + // deal with elements of the array, and use an "infinite" + // loop because we could have any amount of elements + while ( true ) { + // read in the value and add it to the array + a.push ( parseValue() ); + + // after the value there should be a ] or a , + nextToken(); + + if ( token.type == JSONTokenType.RIGHT_BRACKET ) { + // we're done reading the array, so return it + return a; + } else if ( token.type == JSONTokenType.COMMA ) { + // move past the comma and read another value + nextToken(); + } else { + tokenizer.parseError( "Expecting ] or , but found " + token.value ); + } + } + return null; + } + + /** + * Attempt to parse an object + */ + private function parseObject():Object { + // create the object internally that we're going to + // attempt to parse from the tokenizer + var o:Object = new Object(); + + // store the string part of an object member so + // that we can assign it a value in the object + var key:String + + // grab the next token from the tokenizer + nextToken(); + + // check to see if we have an empty object + if ( token.type == JSONTokenType.RIGHT_BRACE ) { + // we're done reading the object, so return it + return o; + } + + // deal with members of the object, and use an "infinite" + // loop because we could have any amount of members + while ( true ) { + + if ( token.type == JSONTokenType.STRING ) { + // the string value we read is the key for the object + key = String( token.value ); + + // move past the string to see what's next + nextToken(); + + // after the string there should be a : + if ( token.type == JSONTokenType.COLON ) { + + // move past the : and read/assign a value for the key + nextToken(); + o[key] = parseValue(); + + // move past the value to see what's next + nextToken(); + + // after the value there's either a } or a , + if ( token.type == JSONTokenType.RIGHT_BRACE ) { + // // we're done reading the object, so return it + return o; + + } else if ( token.type == JSONTokenType.COMMA ) { + // skip past the comma and read another member + nextToken(); + } else { + tokenizer.parseError( "Expecting } or , but found " + token.value ); + } + } else { + tokenizer.parseError( "Expecting : but found " + token.value ); + } + } else { + tokenizer.parseError( "Expecting string but found " + token.value ); + } + } + return null; + } + + /** + * Attempt to parse a value + */ + private function parseValue():Object + { + // Catch errors when the input stream ends abruptly + if ( token == null ) + { + tokenizer.parseError( "Unexpected end of input" ); + } + + switch ( token.type ) { + case JSONTokenType.LEFT_BRACE: + return parseObject(); + + case JSONTokenType.LEFT_BRACKET: + return parseArray(); + + case JSONTokenType.STRING: + case JSONTokenType.NUMBER: + case JSONTokenType.TRUE: + case JSONTokenType.FALSE: + case JSONTokenType.NULL: + return token.value; + + default: + tokenizer.parseError( "Unexpected " + token.value ); + + } + return null; + } + } +} diff --git a/src/flexchart/com/adobe/serialization/json/JSONEncoder.as b/src/flexchart/com/adobe/serialization/json/JSONEncoder.as new file mode 100644 index 0000000..8297b4f --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONEncoder.as @@ -0,0 +1,299 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json +{ + + import flash.utils.describeType; + + public class JSONEncoder { + + /** The string that is going to represent the object we're encoding */ + private var jsonString:String; + + /** + * Creates a new JSONEncoder. + * + * @param o The object to encode as a JSON string + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONEncoder( value:* ) { + jsonString = convertToString( value ); + + } + + /** + * Gets the JSON string from the encoder. + * + * @return The JSON string representation of the object + * that was passed to the constructor + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function getString():String { + return jsonString; + } + + /** + * Converts a value to it's JSON string equivalent. + * + * @param value The value to convert. Could be any + * type (object, number, array, etc) + */ + private function convertToString( value:* ):String { + + // determine what value is and convert it based on it's type + if ( value is String ) { + + // escape the string so it's formatted correctly + return escapeString( value as String ); + + } else if ( value is Number ) { + + // only encode numbers that finate + return isFinite( value as Number) ? value.toString() : "null"; + + } else if ( value is Boolean ) { + + // convert boolean to string easily + return value ? "true" : "false"; + + } else if ( value is Array ) { + + // call the helper method to convert an array + return arrayToString( value as Array ); + + } else if ( value is Object && value != null ) { + + // call the helper method to convert an object + return objectToString( value ); + } + return "null"; + } + + /** + * Escapes a string accoding to the JSON specification. + * + * @param str The string to be escaped + * @return The string with escaped special characters + * according to the JSON specification + */ + private function escapeString( str:String ):String { + // create a string to store the string's jsonstring value + var s:String = ""; + // current character in the string we're processing + var ch:String; + // store the length in a local variable to reduce lookups + var len:Number = str.length; + + // loop over all of the characters in the string + for ( var i:int = 0; i < len; i++ ) { + + // examine the character to determine if we have to escape it + ch = str.charAt( i ); + switch ( ch ) { + + case '"': // quotation mark + s += "\\\""; + break; + + //case '/': // solidus + // s += "\\/"; + // break; + + case '\\': // reverse solidus + s += "\\\\"; + break; + + case '\b': // bell + s += "\\b"; + break; + + case '\f': // form feed + s += "\\f"; + break; + + case '\n': // newline + s += "\\n"; + break; + + case '\r': // carriage return + s += "\\r"; + break; + + case '\t': // horizontal tab + s += "\\t"; + break; + + default: // everything else + + // check for a control character and escape as unicode + if ( ch < ' ' ) { + // get the hex digit(s) of the character (either 1 or 2 digits) + var hexCode:String = ch.charCodeAt( 0 ).toString( 16 ); + + // ensure that there are 4 digits by adjusting + // the # of zeros accordingly. + var zeroPad:String = hexCode.length == 2 ? "00" : "000"; + + // create the unicode escape sequence with 4 hex digits + s += "\\u" + zeroPad + hexCode; + } else { + + // no need to do any special encoding, just pass-through + s += ch; + + } + } // end switch + + } // end for loop + + return "\"" + s + "\""; + } + + /** + * Converts an array to it's JSON string equivalent + * + * @param a The array to convert + * @return The JSON string representation of a + */ + private function arrayToString( a:Array ):String { + // create a string to store the array's jsonstring value + var s:String = ""; + + // loop over the elements in the array and add their converted + // values to the string + for ( var i:int = 0; i < a.length; i++ ) { + // when the length is 0 we're adding the first element so + // no comma is necessary + if ( s.length > 0 ) { + // we've already added an element, so add the comma separator + s += "," + } + + // convert the value to a string + s += convertToString( a[i] ); + } + + // KNOWN ISSUE: In ActionScript, Arrays can also be associative + // objects and you can put anything in them, ie: + // myArray["foo"] = "bar"; + // + // These properties aren't picked up in the for loop above because + // the properties don't correspond to indexes. However, we're + // sort of out luck because the JSON specification doesn't allow + // these types of array properties. + // + // So, if the array was also used as an associative object, there + // may be some values in the array that don't get properly encoded. + // + // A possible solution is to instead encode the Array as an Object + // but then it won't get decoded correctly (and won't be an + // Array instance) + + // close the array and return it's string value + return "[" + s + "]"; + } + + /** + * Converts an object to it's JSON string equivalent + * + * @param o The object to convert + * @return The JSON string representation of o + */ + private function objectToString( o:Object ):String + { + // create a string to store the object's jsonstring value + var s:String = ""; + + // determine if o is a class instance or a plain object + var classInfo:XML = describeType( o ); + if ( classInfo. at name.toString() == "Object" ) + { + // the value of o[key] in the loop below - store this + // as a variable so we don't have to keep looking up o[key] + // when testing for valid values to convert + var value:Object; + + // loop over the keys in the object and add their converted + // values to the string + for ( var key:String in o ) + { + // assign value to a variable for quick lookup + value = o[key]; + + // don't add function's to the JSON string + if ( value is Function ) + { + // skip this key and try another + continue; + } + + // when the length is 0 we're adding the first item so + // no comma is necessary + if ( s.length > 0 ) { + // we've already added an item, so add the comma separator + s += "," + } + + s += escapeString( key ) + ":" + convertToString( value ); + } + } + else // o is a class instance + { + // Loop over all of the variables and accessors in the class and + // serialize them along with their values. + for each ( var v:XML in classInfo..*.( name() == "variable" || name() == "accessor" ) ) + { + // When the length is 0 we're adding the first item so + // no comma is necessary + if ( s.length > 0 ) { + // We've already added an item, so add the comma separator + s += "," + } + + s += escapeString( v. at name.toString() ) + ":" + + convertToString( o[ v. at name ] ); + } + + } + + return "{" + s + "}"; + } + + + } + +} diff --git a/src/flexchart/com/adobe/serialization/json/JSONParseError.as b/src/flexchart/com/adobe/serialization/json/JSONParseError.as new file mode 100644 index 0000000..4040910 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONParseError.as @@ -0,0 +1,87 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * + * + */ + public class JSONParseError extends Error { + + /** The location in the string where the error occurred */ + private var _location:int; + + /** The string in which the parse error occurred */ + private var _text:String; + + /** + * Constructs a new JSONParseError. + * + * @param message The error message that occured during parsing + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONParseError( message:String = "", location:int = 0, text:String = "") { + super( message ); + name = "JSONParseError"; + _location = location; + _text = text; + } + + /** + * Provides read-only access to the location variable. + * + * @return The location in the string where the error occurred + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get location():int { + return _location; + } + + /** + * Provides read-only access to the text variable. + * + * @return The string in which the error occurred + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get text():String { + return _text; + } + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONToken.as b/src/flexchart/com/adobe/serialization/json/JSONToken.as new file mode 100644 index 0000000..0296f13 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONToken.as @@ -0,0 +1,104 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONToken { + + private var _type:int; + private var _value:Object; + + /** + * Creates a new JSONToken with a specific token type and value. + * + * @param type The JSONTokenType of the token + * @param value The value of the token + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function JSONToken( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ) { + _type = type; + _value = value; + } + + /** + * Returns the type of the token. + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get type():int { + return _type; + } + + /** + * Sets the type of the token. + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function set type( value:int ):void { + _type = value; + } + + /** + * Gets the value of the token + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function get value():Object { + return _value; + } + + /** + * Sets the value of the token + * + * @see com.adobe.serialization.json.JSONTokenType + * @langversion ActionScript 3.0 + * @playerversion Flash 9.0 + * @tiptext + */ + public function set value ( v:Object ):void { + _value = v; + } + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONTokenType.as b/src/flexchart/com/adobe/serialization/json/JSONTokenType.as new file mode 100644 index 0000000..adaa0d7 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONTokenType.as @@ -0,0 +1,67 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + /** + * Class containing constant values for the different types + * of tokens in a JSON encoded string. + */ + public class JSONTokenType { + + public static const UNKNOWN:int = -1; + + public static const COMMA:int = 0; + + public static const LEFT_BRACE:int = 1; + + public static const RIGHT_BRACE:int = 2; + + public static const LEFT_BRACKET:int = 3; + + public static const RIGHT_BRACKET:int = 4; + + public static const COLON:int = 6; + + public static const TRUE:int = 7; + + public static const FALSE:int = 8; + + public static const NULL:int = 9; + + public static const STRING:int = 10; + + public static const NUMBER:int = 11; + + } + +} \ No newline at end of file diff --git a/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as b/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as new file mode 100644 index 0000000..9e7a5a4 --- /dev/null +++ b/src/flexchart/com/adobe/serialization/json/JSONTokenizer.as @@ -0,0 +1,547 @@ +/* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.adobe.serialization.json { + + public class JSONTokenizer { + + /** The object that will get parsed from the JSON string */ + private var obj:Object; + + /** The JSON string to be parsed */ + private var jsonString:String; + + /** The current parsing location in the JSON string */ + private var loc:int; + + /** The current character in the JSON string during parsing */ + private var ch:String; + + /** + * Constructs a new JSONDecoder to parse a JSON string + * into a native object. + * + * @param s The JSON string to be converted + * into a native object + */ + public function JSONTokenizer( s:String ) { + jsonString = s; + loc = 0; + + // prime the pump by getting the first character + nextChar(); + } + + /** + * Gets the next token in the input sting and advances + * the character to the next character after the token + */ + public function getNextToken():JSONToken { + var token:JSONToken = new JSONToken(); + + // skip any whitespace / comments since the last + // token was read + skipIgnored(); + + // examine the new character and see what we have... + switch ( ch ) { + + case '{': + token.type = JSONTokenType.LEFT_BRACE; + token.value = '{'; + nextChar(); + break + + case '}': + token.type = JSONTokenType.RIGHT_BRACE; + token.value = '}'; + nextChar(); + break + + case '[': + token.type = JSONTokenType.LEFT_BRACKET; + token.value = '['; + nextChar(); + break + + case ']': + token.type = JSONTokenType.RIGHT_BRACKET; + token.value = ']'; + nextChar(); + break + + case ',': + token.type = JSONTokenType.COMMA; + token.value = ','; + nextChar(); + break + + case ':': + token.type = JSONTokenType.COLON; + token.value = ':'; + nextChar(); + break; + + case 't': // attempt to read true + var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); + + if ( possibleTrue == "true" ) { + token.type = JSONTokenType.TRUE; + token.value = true; + nextChar(); + } else { + parseError( "Expecting 'true' but found " + possibleTrue ); + } + + break; + + case 'f': // attempt to read false + var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); + + if ( possibleFalse == "false" ) { + token.type = JSONTokenType.FALSE; + token.value = false; + nextChar(); + } else { + parseError( "Expecting 'false' but found " + possibleFalse ); + } + + break; + + case 'n': // attempt to read null + + var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); + + if ( possibleNull == "null" ) { + token.type = JSONTokenType.NULL; + token.value = null; + nextChar(); + } else { + parseError( "Expecting 'null' but found " + possibleNull ); + } + + break; + + case '"': // the start of a string + token = readString(); + break; + + default: + // see if we can read a number + if ( isDigit( ch ) || ch == '-' ) { + token = readNumber(); + } else if ( ch == '' ) { + // check for reading past the end of the string + return null; + } else { + // not sure what was in the input string - it's not + // anything we expected + parseError( "Unexpected " + ch + " encountered" ); + } + } + + return token; + } + + /** + * Attempts to read a string from the input string. Places + * the character location at the first character after the + * string. It is assumed that ch is " before this method is called. + * + * @return the JSONToken with the string value if a string could + * be read. Throws an error otherwise. + */ + private function readString():JSONToken { + // the token for the string we'll try to read + var token:JSONToken = new JSONToken(); + token.type = JSONTokenType.STRING; + + // the string to store the string we'll try to read + var string:String = ""; + + // advance past the first " + nextChar(); + + while ( ch != '"' && ch != '' ) { + + // unescape the escape sequences in the string + if ( ch == '\\' ) { + + // get the next character so we know what + // to unescape + nextChar(); + + switch ( ch ) { + + case '"': // quotation mark + string += '"'; + break; + + case '/': // solidus + string += "/"; + break; + + case '\\': // reverse solidus + string += '\\'; + break; + + case 'b': // bell + string += '\b'; + break; + + case 'f': // form feed + string += '\f'; + break; + + case 'n': // newline + string += '\n'; + break; + + case 'r': // carriage return + string += '\r'; + break; + + case 't': // horizontal tab + string += '\t' + break; + + case 'u': + // convert a unicode escape sequence + // to it's character value - expecting + // 4 hex digits + + // save the characters as a string we'll convert to an int + var hexValue:String = ""; + + // try to find 4 hex characters + for ( var i:int = 0; i < 4; i++ ) { + // get the next character and determine + // if it's a valid hex digit or not + if ( !isHexDigit( nextChar() ) ) { + parseError( " Excepted a hex digit, but found: " + ch ); + } + // valid, add it to the value + hexValue += ch; + } + + // convert hexValue to an integer, and use that + // integrer value to create a character to add + // to our string. + string += String.fromCharCode( parseInt( hexValue, 16 ) ); + + break; + + default: + // couldn't unescape the sequence, so just + // pass it through + string += '\\' + ch; + + } + + } else { + // didn't have to unescape, so add the character to the string + string += ch; + + } + + // move to the next character + nextChar(); + + } + + // we read past the end of the string without closing it, which + // is a parse error + if ( ch == '' ) { + parseError( "Unterminated string literal" ); + } + + // move past the closing " in the input string + nextChar(); + + // attach to the string to the token so we can return it + token.value = string; + + return token; + } + + /** + * Attempts to read a number from the input string. Places + * the character location at the first character after the + * number. + * + * @return The JSONToken with the number value if a number could + * be read. Throws an error otherwise. + */ + private function readNumber():JSONToken { + // the token for the number we'll try to read + var token:JSONToken = new JSONToken(); + token.type = JSONTokenType.NUMBER; + + // the string to accumulate the number characters + // into that we'll convert to a number at the end + var input:String = ""; + + // check for a negative number + if ( ch == '-' ) { + input += '-'; + nextChar(); + } + + // the number must start with a digit + if ( !isDigit( ch ) ) + { + parseError( "Expecting a digit" ); + } + + // 0 can only be the first digit if it + // is followed by a decimal point + if ( ch == '0' ) + { + input += ch; + nextChar(); + + // make sure no other digits come after 0 + if ( isDigit( ch ) ) + { + parseError( "A digit cannot immediately follow 0" ); + } + } + else + { + // read numbers while we can + while ( isDigit( ch ) ) { + input += ch; + nextChar(); + } + } + + // check for a decimal value + if ( ch == '.' ) { + input += '.'; + nextChar(); + + // after the decimal there has to be a digit + if ( !isDigit( ch ) ) + { + parseError( "Expecting a digit" ); + } + + // read more numbers to get the decimal value + while ( isDigit( ch ) ) { + input += ch; + nextChar(); + } + } + + // check for scientific notation + if ( ch == 'e' || ch == 'E' ) + { + input += "e" + nextChar(); + // check for sign + if ( ch == '+' || ch == '-' ) + { + input += ch; + nextChar(); + } + + // require at least one number for the exponent + // in this case + if ( !isDigit( ch ) ) + { + parseError( "Scientific notation number needs exponent value" ); + } + + // read in the exponent + while ( isDigit( ch ) ) + { + input += ch; + nextChar(); + } + } + + // convert the string to a number value + var num:Number = Number( input ); + + if ( isFinite( num ) && !isNaN( num ) ) { + token.value = num; + return token; + } else { + parseError( "Number " + num + " is not valid!" ); + } + return null; + } + + /** + * Reads the next character in the input + * string and advances the character location. + * + * @return The next character in the input string, or + * null if we've read past the end. + */ + private function nextChar():String { + return ch = jsonString.charAt( loc++ ); + } + + /** + * Advances the character location past any + * sort of white space and comments + */ + private function skipIgnored():void { + skipWhite(); + skipComments(); + skipWhite(); + } + + /** + * Skips comments in the input string, either + * single-line or multi-line. Advances the character + * to the first position after the end of the comment. + */ + private function skipComments():void { + if ( ch == '/' ) { + // Advance past the first / to find out what type of comment + nextChar(); + switch ( ch ) { + case '/': // single-line comment, read through end of line + + // Loop over the characters until we find + // a newline or until there's no more characters left + do { + nextChar(); + } while ( ch != '\n' && ch != '' ) + + // move past the \n + nextChar(); + + break; + + case '*': // multi-line comment, read until closing */ + + // move past the opening * + nextChar(); + + // try to find a trailing */ + while ( true ) { + if ( ch == '*' ) { + // check to see if we have a closing / + nextChar(); + if ( ch == '/') { + // move past the end of the closing */ + nextChar(); + break; + } + } else { + // move along, looking if the next character is a * + nextChar(); + } + + // when we're here we've read past the end of + // the string without finding a closing */, so error + if ( ch == '' ) { + parseError( "Multi-line comment not closed" ); + } + } + + break; + + // Can't match a comment after a /, so it's a parsing error + default: + parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); + } + } + + } + + + /** + * Skip any whitespace in the input string and advances + * the character to the first character after any possible + * whitespace. + */ + private function skipWhite():void { + + // As long as there are spaces in the input + // stream, advance the current location pointer + // past them + while ( isWhiteSpace( ch ) ) { + nextChar(); + } + + } + + /** + * Determines if a character is whitespace or not. + * + * @return True if the character passed in is a whitespace + * character + */ + private function isWhiteSpace( ch:String ):Boolean { + return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ); + } + + /** + * Determines if a character is a digit [0-9]. + * + * @return True if the character passed in is a digit + */ + private function isDigit( ch:String ):Boolean { + return ( ch >= '0' && ch <= '9' ); + } + + /** + * Determines if a character is a digit [0-9]. + * + * @return True if the character passed in is a digit + */ + private function isHexDigit( ch:String ):Boolean { + // get the uppercase value of ch so we only have + // to compare the value between 'A' and 'F' + var uc:String = ch.toUpperCase(); + + // a hex digit is a digit of A-F, inclusive ( using + // our uppercase constraint ) + return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) ); + } + + /** + * Raises a parsing error with a specified message, tacking + * on the error location and the original string. + * + * @param message The message indicating why the error occurred + */ + public function parseError( message:String ):void { + throw new JSONParseError( message, loc, jsonString ); + } + } + +} diff --git a/src/flexchart/flexchart.mxml b/src/flexchart/flexchart.mxml new file mode 100644 index 0000000..796329d --- /dev/null +++ b/src/flexchart/flexchart.mxml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/src/flexchart/org/ovirt/ChartLoader.as b/src/flexchart/org/ovirt/ChartLoader.as new file mode 100644 index 0000000..4e493a4 --- /dev/null +++ b/src/flexchart/org/ovirt/ChartLoader.as @@ -0,0 +1,64 @@ +/* + 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. +*/ + +package org.ovirt { + + import mx.containers.Box; + import mx.containers.HBox; + import mx.controls.Text; + + public class ChartLoader { + + private var element:Box; + private var datasourceUrl:String; + + public function ChartLoader(element:Box, datasourceUrl:String) { + this.element = element; + this.datasourceUrl = datasourceUrl; + } + + public function addData(dataSeries:DataSeries):void { + var points:Array = dataSeries.getPoints(); + var maxValue:Number = dataSeries.getMaxValue(); + var scale:Number = maxValue; + if (scale == 0) { scale = 1; } + var size:int = points.length; + element.removeAllChildren(); + element.setStyle("horizontalGap","2"); + for (var i:int = 0; i < size; i++) { + var value:Number = (points[i] as Array)[1]; + var bar:HBox = new HBox(); + bar.percentHeight = ((value / scale) * 90); + bar.percentWidth = (100 / size); + bar.setStyle("backgroundColor","0x0000FF"); + bar.setStyle("left","1"); + bar.setStyle("right","1"); + bar.visible = true; + bar.setVisible(true); + element.addChild(bar); + } + } + + public function load():void { + var dataSource:DataSource = new DataSource(this); + dataSource.retrieveData(datasourceUrl); + } + } +} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSeries.as b/src/flexchart/org/ovirt/DataSeries.as new file mode 100644 index 0000000..d63162a --- /dev/null +++ b/src/flexchart/org/ovirt/DataSeries.as @@ -0,0 +1,42 @@ +/* + 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 to encapsulate the json object representation of a data +//series returned from stats package + +package org.ovirt { + + public class DataSeries { + + private var object:Object; + + public function DataSeries (object:Object) { + this.object = object; + } + + public function getPoints():Array { + return object["vectors"] as Array; + } + + public function getMaxValue():Number { + return object["max_value"] as Number; + } + } +} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSource.as b/src/flexchart/org/ovirt/DataSource.as new file mode 100644 index 0000000..1a64f03 --- /dev/null +++ b/src/flexchart/org/ovirt/DataSource.as @@ -0,0 +1,57 @@ +/* + 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. +*/ + +package org.ovirt { + + import flash.net.URLLoader; + import flash.net.URLRequest; + import com.adobe.serialization.json.JSON; + import flash.events.Event; + import flash.events.IOErrorEvent; + + public class DataSource { + + private var chartLoader:ChartLoader; + + public function DataSource(chartLoader:ChartLoader) { + this.chartLoader = chartLoader; + } + + public function retrieveData(url:String):void { + var loader:URLLoader = new URLLoader(); + loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError ); + loader.addEventListener( Event.COMPLETE, dataLoaded ); + var request:URLRequest = new URLRequest(url); + loader.load(request); + } + + private function dataLoaded(event:Event):void { + var loader:URLLoader = URLLoader(event.target); + var object:Object = JSON.decode(loader.data); + var series:DataSeries = new DataSeries(object); + chartLoader.addData(series); + } + + private function ioError( e:IOErrorEvent ):void { + //FIXME: + //do something useful with this error + } + } +} diff --git a/src/public/javascripts/jquery.flash.js b/src/public/javascripts/jquery.flash.js new file mode 100644 index 0000000..2608834 --- /dev/null +++ b/src/public/javascripts/jquery.flash.js @@ -0,0 +1,288 @@ +/** + * Flash (http://jquery.lukelutman.com/plugins/flash) + * A jQuery plugin for embedding Flash movies. + * + * Version 1.0 + * November 9th, 2006 + * + * Copyright (c) 2006 Luke Lutman (http://www.lukelutman.com) + * Dual licensed under the MIT and GPL licenses. + * http://www.opensource.org/licenses/mit-license.php + * http://www.opensource.org/licenses/gpl-license.php + * + * Inspired by: + * SWFObject (http://blog.deconcept.com/swfobject/) + * UFO (http://www.bobbyvandersluis.com/ufo/) + * sIFR (http://www.mikeindustries.com/sifr/) + * + * IMPORTANT: + * The packed version of jQuery breaks ActiveX control + * activation in Internet Explorer. Use JSMin to minifiy + * jQuery (see: http://jquery.lukelutman.com/plugins/flash#activex). + * + **/ +;(function(){ + +var $$; + +/** + * + * @desc Replace matching elements with a flash movie. + * @author Luke Lutman + * @version 1.0.1 + * + * @name flash + * @param Hash htmlOptions Options for the embed/object tag. + * @param Hash pluginOptions Options for detecting/updating the Flash plugin (optional). + * @param Function replace Custom block called for each matched element if flash is installed (optional). + * @param Function update Custom block called for each matched if flash isn't installed (optional). + * @type jQuery + * + * @cat plugins/flash + * + * @example $('#hello').flash({ src: 'hello.swf' }); + * @desc Embed a Flash movie. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { version: 8 }); + * @desc Embed a Flash 8 movie. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { expressInstall: true }); + * @desc Embed a Flash movie using Express Install if flash isn't installed. + * + * @example $('#hello').flash({ src: 'hello.swf' }, { update: false }); + * @desc Embed a Flash movie, don't show an update message if Flash isn't installed. + * +**/ +$$ = jQuery.fn.flash = function(htmlOptions, pluginOptions, replace, update) { + + // Set the default block. + var block = replace || $$.replace; + + // Merge the default and passed plugin options. + pluginOptions = $$.copy($$.pluginOptions, pluginOptions); + + // Detect Flash. + if(!$$.hasFlash(pluginOptions.version)) { + // Use Express Install (if specified and Flash plugin 6,0,65 or higher is installed). + if(pluginOptions.expressInstall && $$.hasFlash(6,0,65)) { + // Add the necessary flashvars (merged later). + var expressInstallOptions = { + flashvars: { + MMredirectURL: location, + MMplayerType: 'PlugIn', + MMdoctitle: jQuery('title').text() + } + }; + // Ask the user to update (if specified). + } else if (pluginOptions.update) { + // Change the block to insert the update message instead of the flash movie. + block = update || $$.update; + // Fail + } else { + // The required version of flash isn't installed. + // Express Install is turned off, or flash 6,0,65 isn't installed. + // Update is turned off. + // Return without doing anything. + return this; + } + } + + // Merge the default, express install and passed html options. + htmlOptions = $$.copy($$.htmlOptions, expressInstallOptions, htmlOptions); + + // Invoke $block (with a copy of the merged html options) for each element. + return this.each(function(){ + block.call(this, $$.copy(htmlOptions)); + }); + +}; +/** + * + * @name flash.copy + * @desc Copy an arbitrary number of objects into a new object. + * @type Object + * + * @example $$.copy({ foo: 1 }, { bar: 2 }); + * @result { foo: 1, bar: 2 }; + * +**/ +$$.copy = function() { + var options = {}, flashvars = {}; + for(var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if(arg == undefined) continue; + jQuery.extend(options, arg); + // don't clobber one flash vars object with another + // merge them instead + if(arg.flashvars == undefined) continue; + jQuery.extend(flashvars, arg.flashvars); + } + options.flashvars = flashvars; + return options; +}; +/* + * @name flash.hasFlash + * @desc Check if a specific version of the Flash plugin is installed + * @type Boolean + * +**/ +$$.hasFlash = function() { + // look for a flag in the query string to bypass flash detection + if(/hasFlash\=true/.test(location)) return true; + if(/hasFlash\=false/.test(location)) return false; + var pv = $$.hasFlash.playerVersion().match(/\d+/g); + var rv = String([arguments[0], arguments[1], arguments[2]]).match(/\d+/g) || String($$.pluginOptions.version).match(/\d+/g); + for(var i = 0; i < 3; i++) { + pv[i] = parseInt(pv[i] || 0); + rv[i] = parseInt(rv[i] || 0); + // player is less than required + if(pv[i] < rv[i]) return false; + // player is greater than required + if(pv[i] > rv[i]) return true; + } + // major version, minor version and revision match exactly + return true; +}; +/** + * + * @name flash.hasFlash.playerVersion + * @desc Get the version of the installed Flash plugin. + * @type String + * +**/ +$$.hasFlash.playerVersion = function() { + // ie + try { + try { + // avoid fp6 minor version lookup issues + // see: http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + var axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'); + try { axo.AllowScriptAccess = 'always'; } + catch(e) { return '6,0,0'; } + } catch(e) {} + return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; + // other browsers + } catch(e) { + try { + if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){ + return (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1]; + } + } catch(e) {} + } + return '0,0,0'; +}; +/** + * + * @name flash.htmlOptions + * @desc The default set of options for the object or embed tag. + * +**/ +$$.htmlOptions = { + height: 240, + flashvars: {}, + pluginspage: 'http://www.adobe.com/go/getflashplayer', + src: '#', + type: 'application/x-shockwave-flash', + width: 320 +}; +/** + * + * @name flash.pluginOptions + * @desc The default set of options for checking/updating the flash Plugin. + * +**/ +$$.pluginOptions = { + expressInstall: false, + update: true, + version: '6.0.65' +}; +/** + * + * @name flash.replace + * @desc The default method for replacing an element with a Flash movie. + * +**/ +$$.replace = function(htmlOptions) { + this.innerHTML = '
'+this.innerHTML+'
'; + jQuery(this) + .addClass('flash-replaced') + .prepend($$.transform(htmlOptions)); +}; +/** + * + * @name flash.update + * @desc The default method for replacing an element with an update message. + * +**/ +$$.update = function(htmlOptions) { + var url = String(location).split('?'); + url.splice(1,0,'?hasFlash=true&'); + url = url.join(''); + var msg = '

This content requires the Flash Player. Download Flash Player. Already have Flash Player? Click here.

'; + this.innerHTML = ''+this.innerHTML+''; + jQuery(this) + .addClass('flash-update') + .prepend(msg); +}; +/** + * + * @desc Convert a hash of html options to a string of attributes, using Function.apply(). + * @example toAttributeString.apply(htmlOptions) + * @result foo="bar" foo="bar" + * +**/ +function toAttributeString() { + var s = ''; + for(var key in this) + if(typeof this[key] != 'function') + s += key+'="'+this[key]+'" '; + return s; +}; +/** + * + * @desc Convert a hash of flashvars to a url-encoded string, using Function.apply(). + * @example toFlashvarsString.apply(flashvarsObject) + * @result foo=bar&foo=bar + * +**/ +function toFlashvarsString() { + var s = ''; + for(var key in this) + if(typeof this[key] != 'function') + s += key+'='+encodeURIComponent(this[key])+'&'; + return s.replace(/&$/, ''); +}; +/** + * + * @name flash.transform + * @desc Transform a set of html options into an embed tag. + * @type String + * + * @example $$.transform(htmlOptions) + * @result + * + * Note: The embed tag is NOT standards-compliant, but it + * works in all current browsers. flash.transform can be + * overwritten with a custom function to generate more + * standards-compliant markup. + * +**/ +$$.transform = function(htmlOptions) { + htmlOptions.toString = toAttributeString; + if(htmlOptions.flashvars) htmlOptions.flashvars.toString = toFlashvarsString; + return ''; +}; + +/** + * + * Flash Player 9 Fix (http://blog.deconcept.com/2006/07/28/swfobject-143-released/) + * +**/ +if (window.attachEvent) { + window.attachEvent("onbeforeunload", function(){ + __flash_unloadHandler = function() {}; + __flash_savedUnloadHandler = function() {}; + }); +} + +})(); \ No newline at end of file -- 1.5.5.1 From dpierce at redhat.com Fri Oct 10 19:55:58 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 10 Oct 2008 15:55:58 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223668558-28139-1-git-send-email-dpierce@redhat.com> The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 51 ++++++++++++--------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- 4 files changed, 184 insertions(+), 38 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..6c4ace6 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From mdehaan at redhat.com Fri Oct 10 20:01:23 2008 From: mdehaan at redhat.com (Michael DeHaan) Date: Fri, 10 Oct 2008 16:01:23 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. In-Reply-To: <1223668558-28139-1-git-send-email-dpierce@redhat.com> References: <1223668558-28139-1-git-send-email-dpierce@redhat.com> Message-ID: <48EFB493.6030101@redhat.com> Darryl L. Pierce wrote: > The NFS export for Cobbler needs to be added as an NFS storage pool. > Otherwise, taskomatic will not be able to locate it. > > Also added a few helper methods to Vm to contain the knowledge > of how Cobbler integration is contained. > > When a user adds an ISO image to the Cobbler server on the appliance, > they will need to do so using the full NFS path for where the virtual > image will go to mount it; i.e., hostname:/path/to/filename.iso > > If the filename ends in ".iso" then the virtual machine will mount the > file as a CDROM device and boot it. Otherwise, it mounts it as a hard > disk device. > Please set the "--type" on the image object in Cobbler if you aren't doing so already. This is much better than paying attention to the filename. There is one for "iso", and another for "virtimage". If specifying virt-image there is also a parameter you can use to track the location of the XML file, though I had some recent discussions with Bryan Kearney and David Huff about that. The basics are that most likely we're going to want the data that goes into that XML file stored in cobbler so that cobbler can generate the XML file if we decide to bump up the RAM later. Cobbler has a live mod_python based templating system so you could just use that (once the code is added to support it) to just wget the XML and use that in conjunction with virt-image. This also prevents from having to keep the XML files around, though we may add a facility in cobbler to "import" from an XML file that came out of virt-convert. TBD, I'm still honestly unclear about some of the details. Anyway, "image add" takes a --type, so be sure to set that and don't use the extension. This will make things work better with koan, even though I know you aren't using it, it should be consistent. (In fact, the path also needs tweaking for how you store it... it should start with nfs://hostname:/path not just hostname:/path) --Michael > To add an image to Cobbler, do the following: > > 1. Download an ISO image, such as the KDE LiveImage from Fedora. > 2. Copy it to the NFS directory on the server: > cp *.iso /ovirtnfs/kde-live-cd.iso > 3. Add that image to your Cobbler instance: > cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso > 4. Create a new VM in your server. > 5. Select "KDE-LiveCD" from the list of operating systems. > 6. Save the VM. > 7. Start the VM. > > It should run the selected ISO. > > Signed-off-by: Darryl L. Pierce > --- > src/app/controllers/vm_controller.rb | 51 ++++++++++++--------- > src/app/models/vm.rb | 34 +++++++++++++- > src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- > src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- > 4 files changed, 184 insertions(+), 38 deletions(-) > > diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb > index f5c0845..bc88760 100644 > --- a/src/app/controllers/vm_controller.rb > +++ b/src/app/controllers/vm_controller.rb > @@ -48,6 +48,7 @@ class VmController < ApplicationController > begin > Vm.transaction do > @vm.save! > + _setup_vm_provision(params) > @task = VmTask.new({ :user => @user, > :vm_id => @vm.id, > :action => VmTask::ACTION_CREATE_VM, > @@ -70,7 +71,7 @@ class VmController < ApplicationController > alert = "VM was successfully created." > end > render :json => { :object => "vm", :success => true, :alert => alert } > - rescue > + rescue Exception => error > # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) > render :json => { :object => "vm", :success => false, > :errors => @vm.errors.localize_error_messages.to_a } > @@ -223,13 +224,18 @@ class VmController < ApplicationController > def _setup_provisioning_options > @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::Image.find.collect do |image| > + [image.name + Vm::COBBLER_IMAGE_SUFFIX, > + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] > + end > + > @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 > + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] > + > + end > rescue > #if cobbler doesn't respond/is misconfigured/etc just don't add profiles > end > @@ -239,24 +245,27 @@ class VmController < ApplicationController > def _setup_vm_provision(params) > # spaces are invalid in the cobbler name > name = params[:vm][:uuid] > - provision = params[:vm][:provisioning_and_boot_settings].gsub( > - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + > - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") > mac = params[:vm][:vnic_mac_addr] > - unless provision == Vm::PXE_OPTION_VALUE or > - provision == Vm::HD_OPTION_VALUE > - found = false > - Cobbler::System.find.each{ |system| > - if system.name == name > - system.profile = provision > - system.save > - found = true > + provision = params[:vm][:provisioning_and_boot_settings] > + # determine what type of provisioning was selected for the VM > + provisioning_type = :pxe_or_hd_type > + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" > + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" > + > + unless provisioning_type == :pxe_or_hd_type > + cobbler_name = provision.slice(/(.*):(.*)/, 2) > + system = Cobbler::System.find_one(name) > + unless system > + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) > + > + case provisioning_type > + when :image_type: > + system = Cobbler::System.new("name" => name, "image" => cobbler_name) > + when :system_type: > + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) > end > - } > - unless found > - system = Cobbler::System.create("name" => name, > - "profile" => provision) > - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] > + > + system.interfaces = [nic] > system.save > end > end > diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb > index ace6fb1..d7beacf 100644 > --- a/src/app/models/vm.rb > +++ b/src/app/models/vm.rb > @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base > PROFILE_PREFIX = "profile" > IMAGE_PREFIX = "image" > COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" > - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" > + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" > > PXE_OPTION_LABEL = "PXE Boot" > PXE_OPTION_VALUE = "pxe" > @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base > end > > def provisioning_and_boot_settings=(settings) > - if settings==PXE_OPTION_VALUE > + # if the settings have a prefix that matches cobber settings, then process > + # those details > + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ > + self[:boot_device] = BOOT_DEV_CDROM > + self[:provisioning] = settings > + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ > + self[:boot_device] = BOOT_DEV_NETWORK > + self[:provisioning] = settings > + elsif settings==PXE_OPTION_VALUE > self[:boot_device]= BOOT_DEV_NETWORK > self[:provisioning]= nil > elsif settings==HD_OPTION_VALUE > @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base > vm_resource_pool.search_users > end > > + # Reports whether the VM is uses Cobbler for booting. > + # > + def uses_cobbler? > + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) > + end > + > + # Returns the cobbler type. > + # > + def cobbler_type > + if self.uses_cobbler? > + self.provisioning[/^(.*)@/,1] > + end > + end > + > + # Returns the cobbler provisioning name. > + # > + def cobbler_name > + if self.uses_cobbler? > + self.provisioning[/^.*@.*:(.*)/,1] > + end > + end > + > protected > def validate > resources = vm_resource_pool.max_resources_for_vm(self) > diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb > index 3588224..6c4ace6 100644 > --- a/src/task-omatic/task_vm.rb > +++ b/src/task-omatic/task_vm.rb > @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, > doc.root.elements["devices"].add_element("emulator") > doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" > > - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] > - i = 0 > + devs = ['hda', 'hdb', 'hdc', 'hdd'] > + which_device = 0 > diskDevices.each do |disk| > + is_cdrom = (disk =~ /\.iso/) ? true : false > + > diskdev = Element.new("disk") > - diskdev.add_attribute("type", "block") > - diskdev.add_attribute("device", "disk") > - diskdev.add_element("source", {"dev" => disk}) > - diskdev.add_element("target", {"dev" => devs[i]}) > + diskdev.add_attribute("type", is_cdrom ? "file" : "block") > + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") > + > + if is_cdrom > + diskdev.add_element("readonly") > + diskdev.add_element("source", {"file" => disk}) > + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) > + else > + diskdev.add_element("source", {"dev" => disk}) > + diskdev.add_element("target", {"dev" => devs[which_device]}) > + end > + > doc.root.elements["devices"] << diskdev > - i += 1 > + which_device += 1 > end > > doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) > @@ -154,15 +164,12 @@ def create_vm(task) > # create cobbler system profile > begin > 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 > + if vm.uses_cobbler? > + if vm.cobbler_type == Vm::PROFILE_PREFIX: > system = Cobbler::System.new('name' => vm.uuid, > 'profile' => provisioning_arr[2]) > system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] > system.save > - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX > - #FIXME handle cobbler images > end > end > end > @@ -231,6 +238,14 @@ def shutdown_vm(task) > setVmShutdown(vm) > end > > +# Find thes storage pool with the given ip address and export path. > +# > +def find_storage_pool(ip_addr, export_path) > + StoragePool.find(:first, > + :conditions => > + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) > +end > + > def start_vm(task) > puts "start_vm" > > @@ -266,6 +281,48 @@ def start_vm(task) > # hosts to see if there is a host that will fit these constraints > host = findHostSLA(vm) > > + # if we're booting from a CDROM the VM is an image, > + # then we need to add the NFS mount as a storage volume for this > + # boot > + # > + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) > + details = Cobbler::Image.find_one(vm.cobbler_name) > + > + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details > + > + ignored, ip_addr, export_path, filename = > + details.file.split(/(.*):(.*)\/(.*)/) > + > + found = false > + > + vm.storage_volumes.each do |volume| > + if volume.filename == filename > + if (volume.storage_pool.ip_addr == ip_addr) && > + (volume.storage_pool.export_path == export_path) > + found = true > + end > + end > + end > + > + unless found > + # Create a new transient NFS storage volume > + # This volume is *not* persisted. > + image_volume = StorageVolume.factory("NFS", > + :filename => filename > + ) > + > + image_volume.storage_pool > + image_pool = find_storage_pool(ip_addr, export_path) > + > + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool > + > + if image_pool > + image_pool.storage_volumes << image_volume > + end > + vm.storage_volumes << image_volume > + end > + end > + > conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") > > storagedevs = connect_storage_pools(conn, vm) > diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb > index 4a5e353..22164e8 100644 > --- a/src/test/unit/vm_test.rb > +++ b/src/test/unit/vm_test.rb > @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' > class VmTest < Test::Unit::TestCase > fixtures :vms > > - # Replace this with your real tests. > - def test_truth > - assert true > + def setup > + @vm_name = "Test" > + @no_cobbler_provisioning = "#{@vm_name}" > + @cobbler_image_provisioning = > + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" > + @cobbler_profile_provisioning = > + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" > + end > + > + # Ensures that, if the VM does not contain the Cobbler prefix, that it > + # does not claim to be a Cobbler VM. > + # > + def test_uses_cobbler_without_cobbler_prefix > + vm = Vm.new > + > + vm.provisioning_and_boot_settings=@no_cobbler_provisioning > + > + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? > + assert_equal @vm_name, vm.provisioning, "Wrong name reported." > + end > + > + # Ensures that the VM reports that it uses Cobbler if the provisioning > + # is for a Cobbler profile. > + # > + def test_uses_cobbler_with_cobbler_profile > + vm = Vm.new > + > + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning > + > + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? > + assert_equal Vm::PROFILE_PREFIX, > + vm.cobbler_type, > + "Wrong cobbler type reported." > + assert_equal @vm_name, > + vm.cobbler_name, > + "Wrong name reported." > + end > + > + # Ensures that the VM reports that it uses Cobbler if the provisioning > + # is for a Cobbler image. > + # > + def test_uses_cobbler_with_cobbler_image > + vm = Vm.new > + > + vm.provisioning_and_boot_settings = @cobbler_image_provisioning > + > + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? > + assert_equal Vm::IMAGE_PREFIX, > + vm.cobbler_type, > + "Wrong cobbler type reported." > + assert_equal @vm_name, > + vm.cobbler_name, > + "Wrong name reported." > end > end > From jguiditt at redhat.com Fri Oct 10 20:56:55 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Fri, 10 Oct 2008 16:56:55 -0400 Subject: [Ovirt-devel] Re: [PATCH server] Add to summary pages a rudimentary flash chart written in flex framework In-Reply-To: <1223668852-16189-1-git-send-email-slinabery@redhat.com> References: <1223668852-16189-1-git-send-email-slinabery@redhat.com> Message-ID: <1223672215.3157.19.camel@localhost.localdomain> Assuming we are pushing this in with all the temporary caveats for generating the swf, this does work for me, but a few questions/comments inline. ACK with suggestions added, I tested locally and they work fine for me. On Fri, 2008-10-10 at 15:00 -0500, Steve Linabery wrote: > --- > src/app/controllers/graph_controller.rb | 30 +- > src/app/views/graph/flexchart_data.rhtml | 1 + > src/app/views/graph/history_graphs.rhtml | 87 +--- > src/config/routes.rb | 1 + > src/flexchart/README.txt | 8 + > src/flexchart/com/adobe/serialization/json/JSON.as | 85 +++ > .../com/adobe/serialization/json/JSONDecoder.as | 221 ++++++++ > .../com/adobe/serialization/json/JSONEncoder.as | 299 +++++++++++ > .../com/adobe/serialization/json/JSONParseError.as | 87 +++ > .../com/adobe/serialization/json/JSONToken.as | 104 ++++ > .../com/adobe/serialization/json/JSONTokenType.as | 67 +++ > .../com/adobe/serialization/json/JSONTokenizer.as | 547 ++++++++++++++++++++ > src/flexchart/flexchart.mxml | 20 + > src/flexchart/org/ovirt/ChartLoader.as | 64 +++ > src/flexchart/org/ovirt/DataSeries.as | 42 ++ > src/flexchart/org/ovirt/DataSource.as | 57 ++ > src/public/javascripts/jquery.flash.js | 288 ++++++++++ > 17 files changed, 1930 insertions(+), 78 deletions(-) > create mode 100644 src/app/views/graph/flexchart_data.rhtml > create mode 100644 src/flexchart/README.txt > create mode 100644 src/flexchart/com/adobe/serialization/json/JSON.as > create mode 100644 src/flexchart/com/adobe/serialization/json/JSONDecoder.as > create mode 100644 src/flexchart/com/adobe/serialization/json/JSONEncoder.as > create mode 100644 src/flexchart/com/adobe/serialization/json/JSONParseError.as > create mode 100644 src/flexchart/com/adobe/serialization/json/JSONToken.as > create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenType.as > create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenizer.as > create mode 100644 src/flexchart/flexchart.mxml > create mode 100644 src/flexchart/org/ovirt/ChartLoader.as > create mode 100644 src/flexchart/org/ovirt/DataSeries.as > create mode 100644 src/flexchart/org/ovirt/DataSource.as > create mode 100644 src/public/javascripts/jquery.flash.js > > diff --git a/src/app/controllers/graph_controller.rb b/src/app/controllers/graph_controller.rb > index dbe2afc..6450935 100644 > --- a/src/app/controllers/graph_controller.rb > +++ b/src/app/controllers/graph_controller.rb > @@ -3,7 +3,24 @@ require 'util/stats/Stats' > class GraphController < ApplicationController > layout nil > > - # generate layout for avaialability bar graphs > + def flexchart_data > + > + #FIXME: use the stats package aggregation (when it's available) > + #instead of the old method > + graph_obj = history_graph_data_object > + > + #FIXME: for this release, the flexchart shows only peak values, > + # and only shows a default of the last 40 data points in rrd. > + graph_data = { :labels => graph_obj[:timepoints].last(40), > + :values => graph_obj[:dataset][2][:values].last(40) } > + my_data = graph_data[:labels].zip(graph_data[:values]) > + @graph = { :vectors => my_data, > + :max_value => graph_obj[:total_peak] > + } > + end > + > + > + # generate layout for availability bar graphs > def availability_graph > @id = params[:id] > @target = params[:target] > @@ -67,6 +84,10 @@ class GraphController < ApplicationController > > # retrieves data for history graphs > def history_graph_data > + render :json => history_graph_data_object > + end > + > + def history_graph_data_object > history_graphs > myDays = params[:days] > target = params[:target] > @@ -212,9 +233,10 @@ class GraphController < ApplicationController > :stroke => @avg_history[:color], > :strokeWidth => 1 > } > - ] > + ], > + :total_peak => total_peak > } > - render :json => graph_object > + > end > > > @@ -261,7 +283,7 @@ class GraphController < ApplicationController > } > ] > } > - render :json => graph_object > + > > end > > diff --git a/src/app/views/graph/flexchart_data.rhtml b/src/app/views/graph/flexchart_data.rhtml > new file mode 100644 > index 0000000..a79ce06 > --- /dev/null > +++ b/src/app/views/graph/flexchart_data.rhtml > @@ -0,0 +1 @@ Don't understand why you are doing this here, rather than doing a render :json in the controller? > +<%= @graph.to_json %> > diff --git a/src/app/views/graph/history_graphs.rhtml b/src/app/views/graph/history_graphs.rhtml > index 2b6874f..f372e4b 100644 > --- a/src/app/views/graph/history_graphs.rhtml > +++ b/src/app/views/graph/history_graphs.rhtml > @@ -1,76 +1,15 @@ > +<%= javascript_include_tag "jquery.flash.js" %> > +
> > - > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_1_graph', :div_id => 'cpu_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 1 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_7_graph', :div_id => 'cpu_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 7 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_cpu_history_30_graph', :div_id => 'cpu_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 10, :scaleY => 110, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu', :poolType => @poolType, :days => 30 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_1_graph', :div_id => 'memory_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 1 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_7_graph', :div_id => 'memory_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 7 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_memory_history_30_graph', :div_id => 'memory_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1162, :ticksY => 50, :scaleY => 756, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 30 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_1_graph', :div_id => 'load_history_1', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20, :scaleX => 173, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 1 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_7_graph', :div_id => 'load_history_7', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 7 } ) } %> > -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false, :includeDiv => false, :methodName=> 'draw_load_history_30_graph', :div_id => 'load_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120, :scaleX => 1200, :ticksY => 2, :scaleY => 23, :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load', :poolType => @poolType, :days => 30 } ) } %> > - > -
> -
> -
> - > -
> -
> -
    > -
  • Last 7 Days   <%= image_tag 'icon_menu_arrow.gif' %>
  • > -
  • Last 24 Hours
  • > -
  • Last 7 Days
  • > -
  • Last 30 Days
  • > -
> -
> -
> - Peak     > - Average     > - Rolling Peak     > - Rolling Average     > -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> - I like how much shorter this is! However, not so keen on having the path hardcoded like this. I did a little poking, and think you might want to add a helper to application_helper.rb like so: def flash_path(source) compute_public_path(source, 'swfs', 'swf') end 'swfs' is a directory under /public. I think we should have all the flash swfs (even if it is only this one) in a dir rather than top level, doesn't have to be called swfs, could be something else. > - > diff --git a/src/config/routes.rb b/src/config/routes.rb > index 6f8e481..8d538cb 100644 > --- a/src/config/routes.rb > +++ b/src/config/routes.rb > @@ -41,6 +41,7 @@ ActionController::Routing::Routes.draw do |map| > map.connect ':controller/service.wsdl', :action => 'wsdl' > > # Install the default route as the lowest priority. > + map.connect 'graph/flexchart_data/:id/:target/:days', :controller => 'graph', :action => 'flexchart_data' > map.connect ':controller/:action/:id.:format' > map.connect ':controller/:action/:id' > > diff --git a/src/flexchart/README.txt b/src/flexchart/README.txt > new file mode 100644 > index 0000000..66eb183 > --- /dev/null > +++ b/src/flexchart/README.txt > @@ -0,0 +1,8 @@ > +Until mxmlc gets packaged and this becomes part of autobuild, > +you must obtain the open flex SDK to build the swf movie. > + > +Once you have mxmlc on your system, run: > + > +mxmlc flexchart.mxml > + > +in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public on your appliance. Don't know AS well enough to comment on quality of code too much, mostly seems reasonable as a first cut. > diff --git a/src/flexchart/flexchart.mxml b/src/flexchart/flexchart.mxml > new file mode 100644 > index 0000000..796329d > --- /dev/null > +++ b/src/flexchart/flexchart.mxml > @@ -0,0 +1,20 @@ > + > + > + > + + > + import mx.containers.Box; > + import org.ovirt.*; > + > + private function populate(chart:Box):void { > + var chartLoader:ChartLoader = new ChartLoader(chart, parameters['flexchart_data']); > + chartLoader.load(); > + } > + > + ]]> > + > + > + > + > + > + > diff --git a/src/flexchart/org/ovirt/ChartLoader.as b/src/flexchart/org/ovirt/ChartLoader.as > new file mode 100644 > index 0000000..4e493a4 > --- /dev/null > +++ b/src/flexchart/org/ovirt/ChartLoader.as > @@ -0,0 +1,64 @@ > +/* > + 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. > +*/ > + > +package org.ovirt { > + > + import mx.containers.Box; > + import mx.containers.HBox; > + import mx.controls.Text; > + > + public class ChartLoader { > + > + private var element:Box; > + private var datasourceUrl:String; > + > + public function ChartLoader(element:Box, datasourceUrl:String) { > + this.element = element; > + this.datasourceUrl = datasourceUrl; > + } > + > + public function addData(dataSeries:DataSeries):void { > + var points:Array = dataSeries.getPoints(); > + var maxValue:Number = dataSeries.getMaxValue(); > + var scale:Number = maxValue; > + if (scale == 0) { scale = 1; } > + var size:int = points.length; > + element.removeAllChildren(); > + element.setStyle("horizontalGap","2"); Maybe I am spoiled by ruby and jquery - no .each in AS? > + for (var i:int = 0; i < size; i++) { > + var value:Number = (points[i] as Array)[1]; > + var bar:HBox = new HBox(); > + bar.percentHeight = ((value / scale) * 90); > + bar.percentWidth = (100 / size); > + bar.setStyle("backgroundColor","0x0000FF"); > + bar.setStyle("left","1"); > + bar.setStyle("right","1"); > + bar.visible = true; > + bar.setVisible(true); > + element.addChild(bar); > + } > + } > + > + public function load():void { > + var dataSource:DataSource = new DataSource(this); > + dataSource.retrieveData(datasourceUrl); > + } > + } > +} > \ No newline at end of file > diff --git a/src/flexchart/org/ovirt/DataSeries.as b/src/flexchart/org/ovirt/DataSeries.as > new file mode 100644 > index 0000000..d63162a > --- /dev/null > +++ b/src/flexchart/org/ovirt/DataSeries.as > @@ -0,0 +1,42 @@ > +/* > + 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 to encapsulate the json object representation of a data > +//series returned from stats package > + > +package org.ovirt { > + > + public class DataSeries { > + > + private var object:Object; > + > + public function DataSeries (object:Object) { > + this.object = object; > + } > + > + public function getPoints():Array { > + return object["vectors"] as Array; > + } > + > + public function getMaxValue():Number { > + return object["max_value"] as Number; > + } > + } > +} > \ No newline at end of file > diff --git a/src/flexchart/org/ovirt/DataSource.as b/src/flexchart/org/ovirt/DataSource.as > new file mode 100644 > index 0000000..1a64f03 > --- /dev/null > +++ b/src/flexchart/org/ovirt/DataSource.as > @@ -0,0 +1,57 @@ > +/* > + 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. > +*/ > + > +package org.ovirt { > + > + import flash.net.URLLoader; > + import flash.net.URLRequest; > + import com.adobe.serialization.json.JSON; > + import flash.events.Event; > + import flash.events.IOErrorEvent; > + > + public class DataSource { > + > + private var chartLoader:ChartLoader; > + > + public function DataSource(chartLoader:ChartLoader) { > + this.chartLoader = chartLoader; > + } > + > + public function retrieveData(url:String):void { > + var loader:URLLoader = new URLLoader(); > + loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError ); > + loader.addEventListener( Event.COMPLETE, dataLoaded ); > + var request:URLRequest = new URLRequest(url); > + loader.load(request); > + } > + > + private function dataLoaded(event:Event):void { > + var loader:URLLoader = URLLoader(event.target); > + var object:Object = JSON.decode(loader.data); > + var series:DataSeries = new DataSeries(object); > + chartLoader.addData(series); > + } > + > + private function ioError( e:IOErrorEvent ):void { > + //FIXME: > + //do something useful with this error > + } > + } > +} From imain at redhat.com Fri Oct 10 21:56:49 2008 From: imain at redhat.com (Ian Main) Date: Fri, 10 Oct 2008 14:56:49 -0700 Subject: [Ovirt-devel] [PATCH server] qpid/qmf python demo Message-ID: <1223675809-3625-1-git-send-email-imain@redhat.com> This is a little demo of the qpid interface in python. This is just a temporary thing to demonstrate how it works. Signed-off-by: Ian Main --- src/qpid_list_demo.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 42 insertions(+), 0 deletions(-) create mode 100755 src/qpid_list_demo.py diff --git a/src/qpid_list_demo.py b/src/qpid_list_demo.py new file mode 100755 index 0000000..3b96c8a --- /dev/null +++ b/src/qpid_list_demo.py @@ -0,0 +1,42 @@ +#!/usr/bin/python + +# This script is temporary! It's just a demo to show how the qpid +# stuff works. This prints out a hierarchy of nodes/domains/pools/volumes +# every five seconds. + +from qpid.qmfconsole import Session +import time + +s = Session() +b = s.addBroker() + +while True: + nodes = s.getObjects(cls="node") + for node in nodes: + print 'node:', node.hostname + for prop in node.properties: + print " property:", prop + # Find any domains that on the current node. + domains = s.getObjects(cls="domain", node=node.objectId) + for domain in domains: + print ' domain:', domain.name + for prop in domain.properties: + print " property:", prop + + pools = s.getObjects(cls="pool", node=node.objectId) + for pool in pools: + print ' pool:', pool.name + for prop in pool.properties: + print " property:", prop + + # Find volumes that are part of the pool. + volumes = s.getObjects(cls="volume", pool=pool.objectId) + for volume in volumes: + print ' volume:', volume.name + for prop in volume.properties: + print " property:", prop + + time.sleep(5) + + print '----------------------------' + -- 1.5.5.1 From dpierce at redhat.com Sat Oct 11 11:29:52 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Sat, 11 Oct 2008 07:29:52 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223724592-4169-1-git-send-email-dpierce@redhat.com> The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 51 ++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 84 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++- 4 files changed, 187 insertions(+), 38 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..72ad9d8 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) @@ -277,6 +334,9 @@ def start_vm(task) dom = conn.define_domain_xml(xml.to_s) dom.create + + # Remove the transient storage volume so that it's not persisted + vm.storage_volumes.delete image_volume defined? image_volume setVmVncPort(vm, dom) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From justin at redfish-group.com Mon Oct 13 06:46:45 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 16:46:45 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool Message-ID: <48F2EED5.6030908@redfish-group.com> Hi, I'm currently trying out ovirt. I've installed the ovirt appliance on a fedora 9 machine and have a seperate machine on a private lan which I am using as a node. I've looked through the documentation but it doesn't seem to have been updated with the current release. I've set up cobbler to have the node boot oVirt-Node-x86_64. I noticed that the nodes that were already set up have a kernel parameter set "ovirt_init=scsi", do I need to have this set for my node too? Once the node is booted, how do I add it in the oVirt web admin tool? When I go to add a hos it doesn't appear in the list. Justin. From xxqonline at hotmail.com Mon Oct 13 06:49:15 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 14:49:15 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2EED5.6030908@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> Message-ID: Did you start the managed host in network bootup? Do you have PXE feature in your managed host? Do you have 2 nics on your management host? -------------------------------------------------- From: "Justin Clacherty" Sent: Monday, October 13, 2008 2:46 PM To: Subject: [Ovirt-devel] unable to add hosts in web admin tool > Hi, > > I'm currently trying out ovirt. I've installed the ovirt appliance on a > fedora 9 machine and have a seperate machine on a private lan which I am > using as a node. I've looked through the documentation but it doesn't > seem to have been updated with the current release. > > I've set up cobbler to have the node boot oVirt-Node-x86_64. I noticed > that the nodes that were already set up have a kernel parameter set > "ovirt_init=scsi", do I need to have this set for my node too? Once the > node is booted, how do I add it in the oVirt web admin tool? When I go > to add a hos it doesn't appear in the list. > > Justin. > > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From justin at redfish-group.com Mon Oct 13 07:20:39 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 17:20:39 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: References: <48F2EED5.6030908@redfish-group.com> Message-ID: <48F2F6C7.4020300@redfish-group.com> Yes, the managed host booted the oVirt-Node dist via PXE. Both the managed host and the management host have 2 nics. One nic is on the private management network (this is the network the managed host has booted from), the other nic is on my local network. Justin. xxqonline at hotmail.com wrote: > Did you start the managed host in network bootup? > Do you have PXE feature in your managed host? > Do you have 2 nics on your management host? > > -------------------------------------------------- > From: "Justin Clacherty" > Sent: Monday, October 13, 2008 2:46 PM > To: > Subject: [Ovirt-devel] unable to add hosts in web admin tool > >> Hi, >> >> I'm currently trying out ovirt. I've installed the ovirt appliance >> on a fedora 9 machine and have a seperate machine on a private lan >> which I am using as a node. I've looked through the documentation >> but it doesn't seem to have been updated with the current release. >> >> I've set up cobbler to have the node boot oVirt-Node-x86_64. I >> noticed that the nodes that were already set up have a kernel >> parameter set "ovirt_init=scsi", do I need to have this set for my >> node too? Once the node is booted, how do I add it in the oVirt web >> admin tool? When I go to add a hos it doesn't appear in the list. >> >> Justin. >> >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> From apevec at redhat.com Mon Oct 13 07:55:23 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 13 Oct 2008 09:55:23 +0200 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2F6C7.4020300@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> Message-ID: <48F2FEEB.10705@redhat.com> Justin Clacherty wrote: > Yes, the managed host booted the oVirt-Node dist via PXE. Both the > managed host and the management host have 2 nics. One nic is on the > private management network (this is the network the managed host has > booted from), the other nic is on my local network. Please check the logs /var/log/ovirt* to find out what's going on >>> I'm currently trying out ovirt. I've installed the ovirt appliance >>> on a fedora 9 machine and have a seperate machine on a private lan >>> which I am using as a node. I've looked through the documentation >>> but it doesn't seem to have been updated with the current release. >>> >>> I've set up cobbler to have the node boot oVirt-Node-x86_64. I >>> noticed that the nodes that were already set up have a kernel >>> parameter set "ovirt_init=scsi", do I need to have this set for my That parameter sets up local disk partition for persisted Node configuration, see http://ovirt.org/page/Local_Disk_Usage For simplicity try w/o it first. >>> node too? Once the node is booted, how do I add it in the oVirt web >>> admin tool? When I go to add a hos it doesn't appear in the list. Nodes register themselves with the ovirt server on boot and show up automatically in WUI. Check ovirt-host-browser service log. From justin at redfish-group.com Mon Oct 13 09:19:06 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 19:19:06 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2FEEB.10705@redhat.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> Message-ID: <48F3128A.7090800@redfish-group.com> Alan Pevec wrote: > Please check the logs /var/log/ovirt* to find out what's going on Can't see anything much in the logs on the management server. I had a look at ovirt.log on the managed host and it looks as though it is having problems talking to the management node, not sure what to do about it though. Here's the relevant part of the log: Resolving management.priv.ovirt.org.... 192.168.50.2 Connecting to management.priv.ovirt.org.|192.168.50.2|:80... connected. HTTP request sent, awaiting response... 500 Internal Server Error 2008-10-13 16:52:33 ERROR 500: Internal Server Error. Failed to retrieve configuration bundle Applying default configuration to eth0 and ovirtbr0 Applying default configuration to eth1 and ovirtbr1 Default config applied No volume groups found mount: you must specify the filesystem type umount: /tmp/tmp.1L8QltORDO: not mounted skipping Kerberos configuration skipping ovirt-awake, oVirt identify service not available skipping collectd configuration, collectd service not available mount: you must specify the filesystem type store config:mount: can't find /tmp/tmp.r0yHe5SGXZ in /etc/fstab or /etc/mtab /etc/ssh/ssh_host_dsa_key /etc/ssh/ssh_host_dsa_key.pub /etc/ssh/ssh_host_key /etc/ssh/ssh_host_key.pub /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key.pub umount: /tmp/tmp.r0yHe5SGXZ: not mounted skipping ovirt-identify-node, oVirt registration service not available > That parameter sets up local disk partition for persisted Node > configuration, see http://ovirt.org/page/Local_Disk_Usage For > simplicity try w/o it first. Thanks, removed it for this node. From xxqonline at hotmail.com Mon Oct 13 09:20:38 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 17:20:38 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> Message-ID: Which appliance are you using? The ovirt-appliance is not running well. You may need to download the developer.img in download page. -------------------------------------------------- From: "Justin Clacherty" Sent: Monday, October 13, 2008 5:19 PM To: "Alan Pevec" Cc: ; Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > Alan Pevec wrote: >> Please check the logs /var/log/ovirt* to find out what's going on > > Can't see anything much in the logs on the management server. I had a > look at ovirt.log on the managed host and it looks as though it is > having problems talking to the management node, not sure what to do > about it though. Here's the relevant part of the log: > > Resolving management.priv.ovirt.org.... 192.168.50.2 > Connecting to management.priv.ovirt.org.|192.168.50.2|:80... connected. > HTTP request sent, awaiting response... 500 Internal Server Error > 2008-10-13 16:52:33 ERROR 500: Internal Server Error. > > Failed to retrieve configuration bundle > Applying default configuration to eth0 and ovirtbr0 > Applying default configuration to eth1 and ovirtbr1 > Default config applied > No volume groups found > mount: you must specify the filesystem type > umount: /tmp/tmp.1L8QltORDO: not mounted > skipping Kerberos configuration > skipping ovirt-awake, oVirt identify service not available > skipping collectd configuration, collectd service not available > mount: you must specify the filesystem type > store config:mount: can't find /tmp/tmp.r0yHe5SGXZ in /etc/fstab or > /etc/mtab > /etc/ssh/ssh_host_dsa_key /etc/ssh/ssh_host_dsa_key.pub > /etc/ssh/ssh_host_key /etc/ssh/ssh_host_key.pub > /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key.pub > umount: /tmp/tmp.r0yHe5SGXZ: not mounted > skipping ovirt-identify-node, oVirt registration service not available > >> That parameter sets up local disk partition for persisted Node >> configuration, see http://ovirt.org/page/Local_Disk_Usage For >> simplicity try w/o it first. > > Thanks, removed it for this node. > > From justin at redfish-group.com Mon Oct 13 09:36:00 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 19:36:00 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> Message-ID: <48F31680.1090807@redfish-group.com> xxqonline at hotmail.com wrote: > Which appliance are you using? > The ovirt-appliance is not running well. > You may need to download the developer.img in download page. Installed it from yum as described on the oVirt website. I presume the yum version of ovirt-appliance is 0.93-1 From xxqonline at hotmail.com Mon Oct 13 09:37:48 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 17:37:48 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F31680.1090807@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F31680.1090807@redfish-group.com> Message-ID: I think you may download a developer.img, it is in download page BTW, I did not run ovirt-appliance successful. -------------------------------------------------- From: "Justin Clacherty" Sent: Monday, October 13, 2008 5:36 PM To: Cc: "Alan Pevec" ; Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > xxqonline at hotmail.com wrote: >> Which appliance are you using? >> The ovirt-appliance is not running well. >> You may need to download the developer.img in download page. > > Installed it from yum as described on the oVirt website. I presume the > yum version of ovirt-appliance is 0.93-1 > From xxqonline at hotmail.com Mon Oct 13 09:48:08 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 17:48:08 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com> Message-ID: I think ovirt-appliance.img is just a toy. The developer.img is the real thing we can use. BTW, the doc of the oVirt has a lot of the fault. But they are not hard to be fixed by ourselves. -------------------------------------------------- From: Sent: Monday, October 13, 2008 5:37 PM To: "Justin Clacherty" Cc: Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > I think you may download a developer.img, it is in download page > > BTW, I did not run ovirt-appliance successful. > > > -------------------------------------------------- > From: "Justin Clacherty" > Sent: Monday, October 13, 2008 5:36 PM > To: > Cc: "Alan Pevec" ; > Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > >> xxqonline at hotmail.com wrote: >>> Which appliance are you using? >>> The ovirt-appliance is not running well. >>> You may need to download the developer.img in download page. >> >> Installed it from yum as described on the oVirt website. I presume the >> yum version of ovirt-appliance is 0.93-1 >> > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From justin at redfish-group.com Mon Oct 13 09:48:38 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 19:48:38 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F31680.1090807@redfish-group.com> Message-ID: <48F31976.8090407@redfish-group.com> xxqonline at hotmail.com wrote: > I think you may download a developer.img, it is in download page Wasn't the developer image just there to allow you to run nodes on the management server instead of needing seperate hardware? > > BTW, I did not run ovirt-appliance successful. bummer. From xxqonline at hotmail.com Mon Oct 13 09:50:31 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 17:50:31 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F31680.1090807@redfish-group.com> <48F31976.8090407@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F31680.1090807@redfish-group.com> <48F31976.8090407@redfish-group.com> Message-ID: The problem is I think the ovirt-appliance .img did not use the second nic. But the developer.img used. I am not the red hat guy, I really think they need to improve doc. -------------------------------------------------- From: "Justin Clacherty" Sent: Monday, October 13, 2008 5:48 PM To: Cc: "Alan Pevec" ; Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > xxqonline at hotmail.com wrote: >> I think you may download a developer.img, it is in download page > > Wasn't the developer image just there to allow you to run nodes on the > management server instead of needing seperate hardware? > >> >> BTW, I did not run ovirt-appliance successful. > > bummer. > > From xxqonline at hotmail.com Mon Oct 13 09:55:34 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 17:55:34 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> Message-ID: Hello Justin: Why not have a try http://ovirt.org/download/ovirt-developer-appliance-0.91-1-x86_64.tar http://ovirt.org/download/ovirt-developer-appliance-0.91-1-i386.tar http://ovirt.org/download.html Not the admin url is not 192.168.50.2, it is the eth0 ip of the admin vm. I said there are a lot of the error in their doc. Regards, Qiang -------------------------------------------------- From: Sent: Monday, October 13, 2008 5:50 PM To: "Justin Clacherty" Cc: Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > The problem is I think the ovirt-appliance .img did not use the second > nic. > But the developer.img used. I am not the red hat guy, I really think they > need to improve doc. > > -------------------------------------------------- > From: "Justin Clacherty" > Sent: Monday, October 13, 2008 5:48 PM > To: > Cc: "Alan Pevec" ; > Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > >> xxqonline at hotmail.com wrote: >>> I think you may download a developer.img, it is in download page >> >> Wasn't the developer image just there to allow you to run nodes on the >> management server instead of needing seperate hardware? >> >>> >>> BTW, I did not run ovirt-appliance successful. >> >> bummer. >> >> > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From justin at redfish-group.com Mon Oct 13 10:29:13 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 20:29:13 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> Message-ID: <48F322F9.6000802@redfish-group.com> I'd prefer to run with the current release. Being in beta I suspect there is quite a difference between 0.91-1 and the current release. Plus I don't want to be reporting bugs that have already been fixed. Justin. xxqonline at hotmail.com wrote: > Hello Justin: > > Why not have a try > http://ovirt.org/download/ovirt-developer-appliance-0.91-1-x86_64.tar > http://ovirt.org/download/ovirt-developer-appliance-0.91-1-i386.tar > http://ovirt.org/download.html > > Not the admin url is not 192.168.50.2, it is the eth0 ip of the admin vm. > I said there are a lot of the error in their doc. > > > > Regards, > Qiang > > -------------------------------------------------- > From: > Sent: Monday, October 13, 2008 5:50 PM > To: "Justin Clacherty" > Cc: > Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > >> The problem is I think the ovirt-appliance .img did not use the >> second nic. >> But the developer.img used. I am not the red hat guy, I really think >> they need to improve doc. >> >> -------------------------------------------------- >> From: "Justin Clacherty" >> Sent: Monday, October 13, 2008 5:48 PM >> To: >> Cc: "Alan Pevec" ; >> Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool >> >>> xxqonline at hotmail.com wrote: >>>> I think you may download a developer.img, it is in download page >>> >>> Wasn't the developer image just there to allow you to run nodes on >>> the management server instead of needing seperate hardware? >>> >>>> >>>> BTW, I did not run ovirt-appliance successful. >>> >>> bummer. >>> >>> >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> From justin at redfish-group.com Mon Oct 13 10:31:24 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 20:31:24 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: References: Message-ID: <48F3237C.2070900@redfish-group.com> Hi Qiang, The docs are not current, they don't reflect how it works in the current release. I imagine with the amount of development going on the docs will be out of sync for a while, that's why I'm turning to the list. Justin. xxqonline at hotmail.com wrote: > Hello Justin: > > http://ovirt.org/docs/Using_oVirt/Steps-installing-the-Bundled-Appliance.html > > You can see they provide us the same url in doc for real physical > machine. > > Thanks, > Qiang > > -------------------------------------------------- > From: > Sent: Monday, October 13, 2008 5:50 PM > To: "Justin Clacherty" > Cc: "Alan Pevec" ; > Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > >> The problem is I think the ovirt-appliance .img did not use the >> second nic. >> But the developer.img used. I am not the red hat guy, I really think >> they need to improve doc. >> >> -------------------------------------------------- >> From: "Justin Clacherty" >> Sent: Monday, October 13, 2008 5:48 PM >> To: >> Cc: "Alan Pevec" ; >> Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool >> >>> xxqonline at hotmail.com wrote: >>>> I think you may download a developer.img, it is in download page >>> >>> Wasn't the developer image just there to allow you to run nodes on >>> the management server instead of needing seperate hardware? >>> >>>> >>>> BTW, I did not run ovirt-appliance successful. >>> >>> bummer. >>> >>> From xxqonline at hotmail.com Mon Oct 13 10:14:56 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 18:14:56 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool Message-ID: Hello Justin: http://ovirt.org/docs/Using_oVirt/Steps-installing-the-Bundled-Appliance.html You can see they provide us the same url in doc for real physical machine. Thanks, Qiang -------------------------------------------------- From: Sent: Monday, October 13, 2008 5:50 PM To: "Justin Clacherty" Cc: "Alan Pevec" ; Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > The problem is I think the ovirt-appliance .img did not use the second > nic. > But the developer.img used. I am not the red hat guy, I really think they > need to improve doc. > > -------------------------------------------------- > From: "Justin Clacherty" > Sent: Monday, October 13, 2008 5:48 PM > To: > Cc: "Alan Pevec" ; > Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > >> xxqonline at hotmail.com wrote: >>> I think you may download a developer.img, it is in download page >> >> Wasn't the developer image just there to allow you to run nodes on the >> management server instead of needing seperate hardware? >> >>> >>> BTW, I did not run ovirt-appliance successful. >> >> bummer. >> >> From xxqonline at hotmail.com Mon Oct 13 11:03:48 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Mon, 13 Oct 2008 19:03:48 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F3237C.2070900@redfish-group.com> References: <48F3237C.2070900@redfish-group.com> Message-ID: Thanks Justin: I am also interested in how to run with the current release. I have tried a lot, in 0.91 release, I can see the nfs storage in GUI, but with the newest, I cannot see that. I think the current beta release is not ready. Regards, Qiang -------------------------------------------------- From: "Justin Clacherty" Sent: Monday, October 13, 2008 6:31 PM To: Cc: "Alan Pevec" ; Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > Hi Qiang, > > The docs are not current, they don't reflect how it works in the current > release. I imagine with the amount of development going on the docs will > be out of sync for a while, that's why I'm turning to the list. > > Justin. > > xxqonline at hotmail.com wrote: >> Hello Justin: >> >> http://ovirt.org/docs/Using_oVirt/Steps-installing-the-Bundled-Appliance.html >> You can see they provide us the same url in doc for real physical >> machine. >> >> Thanks, >> Qiang >> >> -------------------------------------------------- >> From: >> Sent: Monday, October 13, 2008 5:50 PM >> To: "Justin Clacherty" >> Cc: "Alan Pevec" ; >> Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool >> >>> The problem is I think the ovirt-appliance .img did not use the second >>> nic. >>> But the developer.img used. I am not the red hat guy, I really think >>> they need to improve doc. >>> >>> -------------------------------------------------- >>> From: "Justin Clacherty" >>> Sent: Monday, October 13, 2008 5:48 PM >>> To: >>> Cc: "Alan Pevec" ; >>> Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool >>> >>>> xxqonline at hotmail.com wrote: >>>>> I think you may download a developer.img, it is in download page >>>> >>>> Wasn't the developer image just there to allow you to run nodes on the >>>> management server instead of needing seperate hardware? >>>> >>>>> >>>>> BTW, I did not run ovirt-appliance successful. >>>> >>>> bummer. >>>> >>>> > > From apevec at redhat.com Mon Oct 13 11:50:01 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 13 Oct 2008 13:50:01 +0200 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F322F9.6000802@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> <48F322F9.6000802@redfish-group.com> Message-ID: <48F335E9.3040805@redhat.com> Justin Clacherty wrote: > I'd prefer to run with the current release. Being in beta I suspect > there is quite a difference between 0.91-1 and the current release. > Plus I don't want to be reporting bugs that have already been fixed. Yes, please use latest beta builds or even try building from git, 0.91 is definitely out of date! > ovirt-appliance .img did not use the second nic. Image is built always w/ two virtual NICs, difference between demo and bundled is just how you wire the management NIC: create-ovirt-appliance -e eth1 will bridge real eth1 on the host machine with the management NIC (eth1 in the appliance, 192.160.50.2) If that doesn't work, please give us clear steps to reproduce so it can be fixed. From justin at redfish-group.com Mon Oct 13 12:00:15 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Mon, 13 Oct 2008 22:00:15 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F335E9.3040805@redhat.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> <48F322F9.6000802@redfish-group.com> <48F335E9.3040805@redhat.com> Message-ID: <48F3384F.9080501@redfish-group.com> Hi Alan, Alan Pevec wrote: > Image is built always w/ two virtual NICs, difference between demo and > bundled is just how you wire the management NIC: > create-ovirt-appliance -e eth1 will bridge real eth1 on the host > machine with the management NIC (eth1 in the appliance, 192.160.50.2) > If that doesn't work, please give us clear steps to reproduce so it > can be fixed. I had no problems getting the management node up and running using the second nic as a bridge. My problem seems to be with the node image. See my previous email for the output from ovirt.log on the node. Justin. From apevec at redhat.com Mon Oct 13 14:24:33 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 13 Oct 2008 16:24:33 +0200 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F3128A.7090800@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> Message-ID: <48F35A21.6070101@redhat.com> Justin Clacherty wrote: > Alan Pevec wrote: >> Please check the logs /var/log/ovirt* to find out what's going on > > Can't see anything much in the logs on the management server. I had a > look at ovirt.log on the managed host and it looks as though it is > having problems talking to the management node, not sure what to do > about it though. Here's the relevant part of the log: > > Resolving management.priv.ovirt.org.... 192.168.50.2 > Connecting to management.priv.ovirt.org.|192.168.50.2|:80... connected. > HTTP request sent, awaiting response... 500 Internal Server Error > 2008-10-13 16:52:33 ERROR 500: Internal Server Error. Seems that ovirt-appliance firstboot failed and left appliance in bad state, check the mongrel log. You can also reinstall ovirt-appliance image and try http://ovirt.org/page/TroubleShooting#Debugging_oVirt_Appliance_firstboot From dpierce at redhat.com Mon Oct 13 15:30:14 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 13 Oct 2008 11:30:14 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223911814-515-1-git-send-email-dpierce@redhat.com> The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 51 +++++++++++-------- src/app/models/vm.rb | 34 ++++++++++++- src/task-omatic/task_vm.rb | 91 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++- 4 files changed, 194 insertions(+), 38 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..40e0a1d 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,21 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + pool = StoragePool.factory(StoragePool::NFS) + + pool.ip_addr = ip_addr + pool.export_path = export_path + + return pool + + #StoragePool.find(:first, + # :conditions => + # ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +288,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) @@ -278,6 +342,9 @@ def start_vm(task) dom = conn.define_domain_xml(xml.to_s) dom.create + # Remove the transient storage volume so that it's not persisted + vm.storage_volumes.delete image_volume defined? image_volume + setVmVncPort(vm, dom) conn.close diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From dpierce at redhat.com Mon Oct 13 16:45:02 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 13 Oct 2008 12:45:02 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. Message-ID: <1223916302-5623-1-git-send-email-dpierce@redhat.com> Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce --- src/app/controllers/vm_controller.rb | 51 +++++++++++++--------- src/app/models/vm.rb | 34 ++++++++++++++- src/task-omatic/task_vm.rb | 76 ++++++++++++++++++++++++++++------ src/task-omatic/utils.rb | 4 +- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++++- 5 files changed, 180 insertions(+), 41 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @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::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @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 + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..8398e83 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin 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 + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -266,9 +273,52 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename = + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = StoragePool.factory(StoragePool::NFS) + + image_pool.ip_addr = ip_addr + image_pool.export_path = export_path + image_pool.storage_volumes << image_volume + image_volume.storage_pool = image_pool + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") - storagedevs = connect_storage_pools(conn, vm) + volumes = [] + volumes += vm.storage_volumes + volumes << image_volume if image_volume + storagedevs = connect_storage_pools(conn, volumes) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, diff --git a/src/task-omatic/utils.rb b/src/task-omatic/utils.rb index 9e60122..47a4543 100644 --- a/src/task-omatic/utils.rb +++ b/src/task-omatic/utils.rb @@ -67,7 +67,7 @@ def teardown_storage_pools(conn) end end -def connect_storage_pools(conn, vm) +def connect_storage_pools(conn, storage_volumes) # 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 @@ -78,7 +78,7 @@ def connect_storage_pools(conn, vm) end storagedevs = [] - vm.storage_volumes.each do |volume| + 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 diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1 From sseago at redhat.com Mon Oct 13 20:01:26 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 13 Oct 2008 16:01:26 -0400 Subject: [Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image. In-Reply-To: <1223916302-5623-1-git-send-email-dpierce@redhat.com> References: <1223916302-5623-1-git-send-email-dpierce@redhat.com> Message-ID: <48F3A916.8000902@redhat.com> Darryl L. Pierce wrote: > Also added a few helper methods to Vm to contain the knowledge > of how Cobbler integration is contained. > > When a user adds an ISO image to the Cobbler server on the appliance, > they will need to do so using the full NFS path for where the virtual > image will go to mount it; i.e., hostname:/path/to/filename.iso > > If the filename ends in ".iso" then the virtual machine will mount the > file as a CDROM device and boot it. Otherwise, it mounts it as a hard > disk device. > > To add an image to Cobbler, do the following: > > 1. Download an ISO image, such as the KDE LiveImage from Fedora. > 2. Copy it to the NFS directory on the server: > cp *.iso /ovirtnfs/kde-live-cd.iso > 3. Add that image to your Cobbler instance: > cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso > 4. Create a new VM in your server. > 5. Select "KDE-LiveCD" from the list of operating systems. > 6. Save the VM. > 7. Start the VM. > > It should run the selected ISO. > This works for me. only change from this is I created a new nfs mount /cobblernfs so the isos won't conflict with the /ovirtnfs storage pool. ACK Scott From dpierce at redhat.com Mon Oct 13 21:04:21 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 13 Oct 2008 17:04:21 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. Message-ID: <1223931861-1575-1-git-send-email-dpierce@redhat.com> The system now takes an encoded configuration descriptor from the server. It then parses from that a set of aliases for bondings if such exist. It then also extracts configuration details for the various network interfaces on the node. Afterward, it reloads module dependencies and then restarts the networking service. Signed-off-by: Darryl L. Pierce --- Makefile.am | 1 + ovirt-node.spec.in | 2 + scripts/ovirt-early | 12 ++----- scripts/ovirt-process-config | 69 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100755 scripts/ovirt-process-config diff --git a/Makefile.am b/Makefile.am index 1d63310..c55db13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ scripts/ovirt-early \ scripts/ovirt-functions \ scripts/ovirt-post \ + scripts/ovirt-process-config \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index daa3d7f..ea8099d 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -74,6 +74,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake @@ -145,6 +146,7 @@ fi %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake +%{_sbindir}/ovirt-process-config %{_sbindir}/ovirt-identify-node %{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 4723426..e5f1e9b 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -12,6 +12,8 @@ # size of the oVirt partition in megabytes OVIRT_SIZE=64 +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding +AUGTOOL_CONFIG=/var/tmp/augtool-config get_mac_addresses() { macs=$(ifconfig | awk '/HWaddr/ { print $5"="$1 }' \ @@ -43,16 +45,10 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/managed_node/config?host=$(hostname)&macs=$macs" if [ $? -eq 0 ]; then echo "Remote configuration bundle retrieved to $cfgdb" - bash $cfgdb - if [ -f /var/tmp/pre-config-script ]; then - echo "Loading kernel modules" - bash /var/tmp/pre-config-script \ - && echo "Kernel modules loaded" \ - || echo "Failed loading kernel modules" - fi + ovirt-process-config $cfgdb $BONDING_MODCONF_FILE $AUGTOOL_CONFIG if [ -f /var/tmp/node-augtool ]; then echo "Loading remote config" - augtool < /var/tmp/node-augtool \ + augtool < $AUGTOOL_CONFIG \ && echo "Remote config applied" \ || echo "Failed applying remote config" fi diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config new file mode 100755 index 0000000..e9d8101 --- /dev/null +++ b/scripts/ovirt-process-config @@ -0,0 +1,69 @@ +#!/bin/bash + +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} + +case $# in + 0|1|2) warn "too few arguments"; try_help;; + 3) ;; + *) warn "too many arguments"; try_help;; +esac + +CONFIG=$1 +OVIRT_KERNEL_MODULE_FILE=$2 +OVIRT_CONFIG_OUTPUT_FILE=$3 + +modconf=$(awk '/bonding=/ { + match($0, "bonding=(.*)", data) + split(data[1], mod, "|") + + if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { + printf "invalid bonding alias: \"%s\"\n", mod[1]; + exit 1; + } + + alias=mod[1] + + printf("install %s bonding", alias) + }' $CONFIG) + +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE + +# now build the list of module aliases to load and load them +modules=$(awk '/bonding=/ { + match($0, "bonding=(.*)", data) + split(data[1], mod, "|") + + printf("%s ", mod[1]) + }' $CONFIG) + +/sbin/depmod -a + +networking=$(awk '/ifcfg=/ { + match($0, "ifcfg=(.*)", data) + split(data[1], ifcfg, "|") + + mac = ifcfg[1] + iface = ifcfg[2] + + printf("rm /files/etc/sysconfig/network-scripts/ifcfg-%s\n", iface) + printf("set /files/etc/sysconfig/network-scripts/ifcfg-%s/DEVICE %s\n", iface, iface) + + for (line in ifcfg) { + if(line > 2) { + match(ifcfg[line], "(^[^=]+)=(.*)", values) + field=values[1] + value=values[2] + + printf("set /files/etc/sysconfig/network-scripts/ifcfg-%s/%s %s\n", iface, field, value) + } + } + + + printf("save\n") + +}' $CONFIG) + +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 02:06:05 2008 From: pmyers at redhat.com (Perry Myers) Date: Mon, 13 Oct 2008 22:06:05 -0400 Subject: [Ovirt-devel] [PATCH node] Fix ovirt install/uninstall node scripts so reboot is not necessary Message-ID: <1223949965-7931-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- ovirt-listen-awake/ovirt-install-node | 55 ++++++++++++++++++++++++++----- ovirt-listen-awake/ovirt-uninstall-node | 10 +++++- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node index 1d998f4..4cffd6d 100644 --- a/ovirt-listen-awake/ovirt-install-node +++ b/ovirt-listen-awake/ovirt-install-node @@ -1,16 +1,27 @@ #!/bin/bash +PHYS_HOST=physical.priv.ovirt.org +MGMT_HOST=management.priv.ovirt.org + . /etc/init.d/ovirt-functions PATH=$PATH:/sbin:/usr/sbin +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +die() { warn "$@"; exit 1; } usage() { - echo "Usage: ovirt-install-node " - exit 1 + echo "Usage: $ME " } +# first, check to see we are root +if [ $( id -u ) -ne 0 ]; then + die "Must run as root" +fi + if [ $# -ne 1 ]; then usage + exit 1 fi backup_file() { @@ -73,14 +84,27 @@ elif [ "$1" = "stateful" ]; then exit 2 fi + # Always try to uninstall first, that way the original pristine files are + # in place before re-installing, this prevents OVIRT_BACKUP_DIR from + # being overwritten with an older version of the node config files + ovirt-uninstall-node > /dev/null 2>&1 + + # Remove old keytab if it exists in case we have a new appliance to work with + rm -f /etc/libvirt/krb5.tab + mkdir -p $OVIRT_BACKUP_DIR backup_file /etc/sysconfig/network - sed -i -e 's/^HOSTNAME=.*/HOSTNAME=physical.priv.ovirt.org/' /etc/sysconfig/network + if grep "^HOSTNAME=" /etc/sysconfig/network > /dev/null 2>&1 ; then + sed -i -e "s/^HOSTNAME=.*/HOSTNAME=$PHYS_HOST/" /etc/sysconfig/network + else + echo "HOSTNAME=$PHYS_HOST" >> /etc/sysconfig/network + fi + hostname $PHYS_HOST backup_file /etc/hosts - add_if_not_exist "192.168.50.1 physical.priv.ovirt.org" /etc/hosts - add_if_not_exist "192.168.50.2 management.priv.ovirt.org" /etc/hosts + add_if_not_exist "192.168.50.1 $PHYS_HOST" /etc/hosts + add_if_not_exist "192.168.50.2 $MGMT_HOST" /etc/hosts chkconfig ovirt-listen-awake on chkconfig collectd on @@ -93,10 +117,23 @@ elif [ "$1" = "stateful" ]; then ovirt_setup_libvirtd backup_file /etc/sysconfig/iptables - lokkit -n -t ovirtbr0 - - echo "Setup complete. To make the changes take effect, shut down any" - echo "running guests and reboot the host" + lokkit -p 7777:tcp -p 16509:tcp + + service collectd restart + service ovirt-listen-awake restart + + # Check if any domains are active before restarting libvirtd, since it will + # kill them. Header information from virsh list is 2 lines, and 1 line for + # footer. So > 3 lines means domains are running + running_domains=$(( $(virsh -c qemu:///system list 2> /dev/null | wc -l) - 3 )) + if [ $running_domains -gt 0 ]; then + echo "Cannot restart libvirtd because domains are active." + echo "Please shutdown all domains and restart libvirtd with:" + echo "service libvirtd restart" + else + service libvirtd restart + fi else usage + exit 1 fi diff --git a/ovirt-listen-awake/ovirt-uninstall-node b/ovirt-listen-awake/ovirt-uninstall-node index 1f820e4..a40b0ed 100644 --- a/ovirt-listen-awake/ovirt-uninstall-node +++ b/ovirt-listen-awake/ovirt-uninstall-node @@ -3,9 +3,17 @@ . /etc/init.d/ovirt-functions PATH=$PATH:/sbin:/usr/sbin +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +die() { warn "$@"; exit 1; } + +# first, check to see we are root +if [ $( id -u ) -ne 0 ]; then + die "Must run as root" +fi if [ $# -ne 0 ]; then - echo "Usage: ovirt-uninstall-node" + echo "Usage: $ME" exit 1 fi -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 02:11:19 2008 From: pmyers at redhat.com (Perry Myers) Date: Mon, 13 Oct 2008 22:11:19 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Fix ovirt install/uninstall node scripts so reboot is not necessary In-Reply-To: <1223949965-7931-1-git-send-email-pmyers@redhat.com> References: <1223949965-7931-1-git-send-email-pmyers@redhat.com> Message-ID: <48F3FFC7.9090302@redhat.com> One problem still exists with the 'running vms on the appliance host'... The current procedure is: 1. Install ovirt-appliance, ovirt-node and ovirt-node-selinux rpms 2. ovirt-install-node stateful 3. create-ovirt-appliance 4. virsh start ovirt-appliance When the appliance comes up, it will contact the host and will assign it a keytab. However, since libvirt doesn't pick up the keytab until it is restarted (and you can't restart libvirtd while the appliance is running) you need to: 5. On appliance: shutdown -h now 6. virsh destroy ovirt-appliance 7. service libvirtd restart 8. virsh start ovirt-appliance Now when the appliance is up again, it can contact the physical host and create vms on it since libvirt has the correct keytab loaded. Once we move to using libvirt-qpid and ovirt-qpid this should go away since we won't need libvirt to have a keytab, the qpid interfaces will will handle the security for us. Perry From justin at redfish-group.com Tue Oct 14 04:57:08 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Tue, 14 Oct 2008 14:57:08 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F35A21.6070101@redhat.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F35A21.6070101@redhat.com> Message-ID: <48F426A4.3070706@redfish-group.com> Alan Pevec wrote: > Seems that ovirt-appliance firstboot failed and left appliance in bad > state, check the mongrel log. > You can also reinstall ovirt-appliance image and try > http://ovirt.org/page/TroubleShooting#Debugging_oVirt_Appliance_firstboot Thanks Alan. I can't get to it at the moment, but from memory the mongrel log on the management server was empty. I'll reinstall the appliance and debug firstboot. Justin. From jim at meyering.net Tue Oct 14 07:05:55 2008 From: jim at meyering.net (Jim Meyering) Date: Tue, 14 Oct 2008 09:05:55 +0200 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <1223500473-12273-1-git-send-email-dpierce@redhat.com> (Darryl L. Pierce's message of "Wed, 8 Oct 2008 17:14:33 -0400") References: <1223500473-12273-1-git-send-email-dpierce@redhat.com> Message-ID: <87myh7k65o.fsf@rho.meyering.net> Hi Darryl, Ha! I wondered why you didn't do this. Now I know ;-) I've just noticed that I never sent this message from last week: "Darryl L. Pierce" wrote: ... > +networking=$(awk '/ifcfg=/ { > + match($0, "ifcfg=(.*)", data) > + split(data[1], ifcfg, "|") > + > + mac = ifcfg[1] > + iface = ifcfg[2] > + Please factor this out: ifcfg_dir = sprintf "/files/etc/sysconfig/network-scripts/ifcfg-" iface > + printf("rm /files/etc/sysconfig/network-scripts/ifcfg-%s\n", iface) > + printf("set /files/etc/sysconfig/network-scripts/ifcfg-%s/DEVICE %s\n", iface, iface) printf("rm %s\n", ifcfg_dir) printf("set %s/DEVICE %s\n", ifcfg_dir, iface) > + > + for (line in ifcfg) { > + if(line > 2) { > + match(ifcfg[line], "(^[^=]+)=(.*)", values) > + field=values[1] > + value=values[2] > + > + printf("set /files/etc/sysconfig/network-scripts/ifcfg-%s/%s %s\n", iface, field, value) printf("set %s/%s %s\n", ifcfg_dir field, value) > + } > + } > + > + > + printf("save\n") > + > +}' $CONFIG) > + > +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE From apevec at redhat.com Tue Oct 14 07:13:26 2008 From: apevec at redhat.com (Alan Pevec) Date: Tue, 14 Oct 2008 09:13:26 +0200 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F426A4.3070706@redfish-group.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F35A21.6070101@redhat.com> <48F426A4.3070706@redfish-group.com> Message-ID: <48F44696.6000305@redhat.com> Justin Clacherty wrote: > Alan Pevec wrote: >> Seems that ovirt-appliance firstboot failed and left appliance in bad >> state, check the mongrel log. >> You can also reinstall ovirt-appliance image and try >> http://ovirt.org/page/TroubleShooting#Debugging_oVirt_Appliance_firstboot > > Thanks Alan. I can't get to it at the moment, but from memory the > mongrel log on the management server was empty. I'll reinstall the > appliance and debug firstboot. Check which process are running in appliance, empty logs indicates that mongrel didn't start at all. From jim at meyering.net Tue Oct 14 10:14:36 2008 From: jim at meyering.net (Jim Meyering) Date: Tue, 14 Oct 2008 12:14:36 +0200 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <1223931861-1575-1-git-send-email-dpierce@redhat.com> (Darryl L. Pierce's message of "Mon, 13 Oct 2008 17:04:21 -0400") References: <1223931861-1575-1-git-send-email-dpierce@redhat.com> Message-ID: <87skqzv5yr.fsf@rho.meyering.net> "Darryl L. Pierce" wrote: > The system now takes an encoded configuration descriptor from the server. It > then parses from that a set of aliases for bondings if such exist. It then > also extracts configuration details for the various network interfaces on the > node. Afterward, it reloads module dependencies and then restarts the > networking service. ... > diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config ... > +modconf=$(awk '/bonding=/ { Hi Darryl, A couple of nit-picky details: This should be anchored at beginning of line, so as not to match a commented-out line: modconf=$(awk '/^[ \t]*bonding=/ { > + match($0, "bonding=(.*)", data) > + split(data[1], mod, "|") > + > + if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { > + printf "invalid bonding alias: \"%s\"\n", mod[1]; > + exit 1; > + } > + > + alias=mod[1] > + > + printf("install %s bonding", alias) > + }' $CONFIG) > + > +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE > + > +# now build the list of module aliases to load and load them > +modules=$(awk '/bonding=/ { Same here. > + match($0, "bonding=(.*)", data) > + split(data[1], mod, "|") > + > + printf("%s ", mod[1]) > + }' $CONFIG) > + > +/sbin/depmod -a > + > +networking=$(awk '/ifcfg=/ { > + match($0, "ifcfg=(.*)", data) > + split(data[1], ifcfg, "|") > + > + mac = ifcfg[1] > + iface = ifcfg[2] > + > + printf("rm /files/etc/sysconfig/network-scripts/ifcfg-%s\n", iface) > + printf("set /files/etc/sysconfig/network-scripts/ifcfg-%s/DEVICE %s\n", iface, iface) Than factor out the long, duplicated paths above and below, and it should be fine. ... > + printf("set /files/etc/sysconfig/network-scripts/ifcfg-%s/%s %s\n", iface, field, value) Thanks, Jim From dpierce at redhat.com Tue Oct 14 12:53:15 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Tue, 14 Oct 2008 08:53:15 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <87skqzv5yr.fsf@rho.meyering.net> References: <1223931861-1575-1-git-send-email-dpierce@redhat.com> <87skqzv5yr.fsf@rho.meyering.net> Message-ID: <48F4963B.9070700@redhat.com> Jim Meyering wrote: > "Darryl L. Pierce" wrote: >> The system now takes an encoded configuration descriptor from the server. It >> then parses from that a set of aliases for bondings if such exist. It then >> also extracts configuration details for the various network interfaces on the >> node. Afterward, it reloads module dependencies and then restarts the >> networking service. > ... >> diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config > ... >> +modconf=$(awk '/bonding=/ { > > Hi Darryl, > > A couple of nit-picky details: > > This should be anchored at beginning of line, > so as not to match a commented-out line: > > modconf=$(awk '/^[ \t]*bonding=/ { I incorporated that into the processing script, as well as the long string replacement. Thank you for the feedback. :) -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dhuff at redhat.com Tue Oct 14 14:19:20 2008 From: dhuff at redhat.com (David Huff) Date: Tue, 14 Oct 2008 10:19:20 -0400 Subject: [Ovirt-devel] Fwd: appliance-tools-002-4.fc9 successfully moved from dist-f9-updates-candidate into dist-f9-updates by bodhi Message-ID: <48F4AA68.1090409@redhat.com> Package: appliance-tools NVR: appliance-tools-002-4.fc9 User: bodhi Status: complete Tag Operation: moved From Tag: dist-f9-updates-candidate Into Tag: dist-f9-updates appliance-tools-002-4.fc9 successfully moved from dist-f9-updates-candidate into dist-f9-updates by bodhi However it looks like it still needs time to sync to the mirrors before it is available via yum. From bkearney at redhat.com Tue Oct 14 14:17:16 2008 From: bkearney at redhat.com (Bryan Kearney) Date: Tue, 14 Oct 2008 10:17:16 -0400 Subject: [Ovirt-devel] Fwd: appliance-tools-002-4.fc9 successfully moved from dist-f9-updates-candidate into dist-f9-updates by bodhi In-Reply-To: <48F4AA68.1090409@redhat.com> References: <48F4AA68.1090409@redhat.com> Message-ID: <48F4A9EC.7060702@redhat.com> David Huff wrote: > Package: appliance-tools > NVR: appliance-tools-002-4.fc9 > User: bodhi > Status: complete > Tag Operation: moved > From Tag: dist-f9-updates-candidate > Into Tag: dist-f9-updates > > appliance-tools-002-4.fc9 successfully moved from > dist-f9-updates-candidate into dist-f9-updates by bodhi > > > However it looks like it still needs time to sync to the mirrors before > it is available via yum. Very cool! --bk From pmyers at redhat.com Tue Oct 14 14:49:07 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 10:49:07 -0400 Subject: [Ovirt-devel] [PATCH node] Fix ovirt install/uninstall node scripts so reboot is not necessary Message-ID: <1223995747-12847-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- ovirt-listen-awake/ovirt-install-node | 61 ++++++++++++++++++++++++++----- ovirt-listen-awake/ovirt-uninstall-node | 11 +++++- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node index 1d998f4..7fbf997 100644 --- a/ovirt-listen-awake/ovirt-install-node +++ b/ovirt-listen-awake/ovirt-install-node @@ -1,16 +1,27 @@ #!/bin/bash +PHYS_HOST=physical.priv.ovirt.org +MGMT_HOST=management.priv.ovirt.org + . /etc/init.d/ovirt-functions PATH=$PATH:/sbin:/usr/sbin +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +die() { warn "$@"; exit 1; } usage() { - echo "Usage: ovirt-install-node " - exit 1 + echo "Usage: $ME " } +# first, check to see we are root +if [ $( id -u ) -ne 0 ]; then + die "Must run as root" +fi + if [ $# -ne 1 ]; then usage + exit 1 fi backup_file() { @@ -73,14 +84,28 @@ elif [ "$1" = "stateful" ]; then exit 2 fi + # Always try to uninstall first, that way the original pristine files are + # in place before re-installing, this prevents OVIRT_BACKUP_DIR from + # being overwritten with an older version of the node config files + ovirt-uninstall-node > /dev/null 2>&1 + + # Remove old keytab if it exists in case we have a new appliance to work with + rm -f /etc/libvirt/krb5.tab + + rm -Rf $OVIRT_BACKUP_DIR mkdir -p $OVIRT_BACKUP_DIR backup_file /etc/sysconfig/network - sed -i -e 's/^HOSTNAME=.*/HOSTNAME=physical.priv.ovirt.org/' /etc/sysconfig/network + if grep "^HOSTNAME=" /etc/sysconfig/network > /dev/null 2>&1 ; then + sed -i -e "s/^HOSTNAME=.*/HOSTNAME=$PHYS_HOST/" /etc/sysconfig/network + else + echo "HOSTNAME=$PHYS_HOST" >> /etc/sysconfig/network + fi + hostname $PHYS_HOST backup_file /etc/hosts - add_if_not_exist "192.168.50.1 physical.priv.ovirt.org" /etc/hosts - add_if_not_exist "192.168.50.2 management.priv.ovirt.org" /etc/hosts + add_if_not_exist "192.168.50.1 $PHYS_HOST" /etc/hosts + add_if_not_exist "192.168.50.2 $MGMT_HOST" /etc/hosts chkconfig ovirt-listen-awake on chkconfig collectd on @@ -93,10 +118,28 @@ elif [ "$1" = "stateful" ]; then ovirt_setup_libvirtd backup_file /etc/sysconfig/iptables - lokkit -n -t ovirtbr0 - - echo "Setup complete. To make the changes take effect, shut down any" - echo "running guests and reboot the host" + # We open up anything coming from ovirtbr0 to this node, since it + # is only intended for demo purposes. For reference, here are the + # ports that need to be opened: + # 7777:tcp (ovirt-listen-awake), 16509:tcp (libvirtd), 5900-6000:tcp (vnc), + # 49152-49216:tcp (libvirt migration) + lokkit -t ovirtbr0 + + service collectd restart + service ovirt-listen-awake restart + + # Check if any domains are active before restarting libvirtd, since it will + # kill them. Header information from virsh list is 2 lines, and 1 line for + # footer. So > 3 lines means domains are running + running_domains=$(( $(virsh -c qemu:///system list 2> /dev/null | wc -l) - 3 )) + if [ $running_domains -gt 0 ]; then + echo "Cannot restart libvirtd because domains are active." + echo "Please shutdown all domains and restart libvirtd with:" + echo "service libvirtd restart" + else + service libvirtd restart + fi else usage + exit 1 fi diff --git a/ovirt-listen-awake/ovirt-uninstall-node b/ovirt-listen-awake/ovirt-uninstall-node index 1f820e4..3e4d300 100644 --- a/ovirt-listen-awake/ovirt-uninstall-node +++ b/ovirt-listen-awake/ovirt-uninstall-node @@ -3,9 +3,17 @@ . /etc/init.d/ovirt-functions PATH=$PATH:/sbin:/usr/sbin +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +die() { warn "$@"; exit 1; } + +# first, check to see we are root +if [ $( id -u ) -ne 0 ]; then + die "Must run as root" +fi if [ $# -ne 0 ]; then - echo "Usage: ovirt-uninstall-node" + echo "Usage: $ME" exit 1 fi @@ -31,3 +39,4 @@ unbackup_file /etc/libvirt/libvirtd.conf unbackup_file /etc/sasl2/libvirt.conf unbackup_file /etc/sysconfig/iptables unbackup_file /etc/krb5.conf +rm -Rf $OVIRT_BACKUP_DIR -- 1.5.5.1 From clalance at redhat.com Tue Oct 14 14:58:20 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 14 Oct 2008 16:58:20 +0200 Subject: [Ovirt-devel] [PATCH node] Fix ovirt install/uninstall node scripts so reboot is not necessary In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> Message-ID: <48F4B38C.7050602@redhat.com> Perry Myers wrote: > Signed-off-by: Perry Myers > --- > ovirt-listen-awake/ovirt-install-node | 61 ++++++++++++++++++++++++++----- > ovirt-listen-awake/ovirt-uninstall-node | 11 +++++- > 2 files changed, 62 insertions(+), 10 deletions(-) > The cosmetic changes are good. The changes to make it so you don't have to reboot are also good, and seem to work in Perry's test setup, so that makes it a little easier to use. ACK -- Chris Lalancette From dpierce at redhat.com Tue Oct 14 15:38:08 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 14 Oct 2008 11:38:08 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. Message-ID: <1223998688-15991-1-git-send-email-dpierce@redhat.com> The system now takes an encoded configuration descriptor from the server. It then parses from that a set of aliases for bondings if such exist. It then also extracts configuration details for the various network interfaces on the node. Afterward, it reloads module dependencies and then restarts the networking service. Signed-off-by: Darryl L. Pierce --- Makefile.am | 1 + ovirt-node.spec.in | 2 + scripts/ovirt-early | 14 +++----- scripts/ovirt-process-config | 70 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100755 scripts/ovirt-process-config diff --git a/Makefile.am b/Makefile.am index 1d63310..c55db13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ scripts/ovirt-early \ scripts/ovirt-functions \ scripts/ovirt-post \ + scripts/ovirt-process-config \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index daa3d7f..ea8099d 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -74,6 +74,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake @@ -145,6 +146,7 @@ fi %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake +%{_sbindir}/ovirt-process-config %{_sbindir}/ovirt-identify-node %{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 4723426..3c8c494 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -12,6 +12,8 @@ # size of the oVirt partition in megabytes OVIRT_SIZE=64 +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding +AUGTOOL_CONFIG=/var/tmp/augtool-config get_mac_addresses() { macs=$(ifconfig | awk '/HWaddr/ { print $5"="$1 }' \ @@ -43,16 +45,10 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/managed_node/config?host=$(hostname)&macs=$macs" if [ $? -eq 0 ]; then echo "Remote configuration bundle retrieved to $cfgdb" - bash $cfgdb - if [ -f /var/tmp/pre-config-script ]; then - echo "Loading kernel modules" - bash /var/tmp/pre-config-script \ - && echo "Kernel modules loaded" \ - || echo "Failed loading kernel modules" - fi - if [ -f /var/tmp/node-augtool ]; then + ovirt-process-config $cfgdb $BONDING_MODCONF_FILE $AUGTOOL_CONFIG + if [ -f $AUGTOOL_CONFIG ]; then echo "Loading remote config" - augtool < /var/tmp/node-augtool \ + augtool < $AUGTOOL_CONFIG \ && echo "Remote config applied" \ || echo "Failed applying remote config" fi diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config new file mode 100755 index 0000000..caaf82d --- /dev/null +++ b/scripts/ovirt-process-config @@ -0,0 +1,70 @@ +#!/bin/bash + +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} + +case $# in + 0|1|2) warn "too few arguments"; try_help;; + 3) ;; + *) warn "too many arguments"; try_help;; +esac + +CONFIG=$1 +OVIRT_KERNEL_MODULE_FILE=$2 +OVIRT_CONFIG_OUTPUT_FILE=$3 + +modconf=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + split(data[1], mod, "|") + + if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { + printf "invalid bonding alias: \"%s\"\n", mod[1]; + exit 1; + } + + alias=mod[1] + + printf("install %s bonding", alias) + }' $CONFIG) + +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE + +# now build the list of module aliases to load and load them +modules=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + split(data[1], mod, "|") + + printf("%s ", mod[1]) + }' $CONFIG) + +/sbin/depmod -a + +networking=$(awk '/ifcfg=/ { + match($0, "^[ \t]*ifcfg=(.*)", data) + split(data[1], ifcfg, "|") + + mac = ifcfg[1] + iface = ifcfg[2] + ifcfg_dir = "/files/etc/sysconfig/network-scripts" + + printf("rm %s/ifcfg-%s\n", ifcfg_dir, iface) + printf("set %s/ifcfg-%s/DEVICE %s\n", ifcfg_dir, iface, iface) + + for (line in ifcfg) { + if(line > 2) { + match(ifcfg[line], "(^[^=]+)=(.*)", values) + field=values[1] + value=values[2] + + printf("set %s/ifcfg-%s/%s %s\n", ifcfg_dir, iface, field, value) + } + } + + + printf("save\n") + +}' $CONFIG) + +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE -- 1.5.5.1 From dpierce at redhat.com Tue Oct 14 15:38:51 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 14 Oct 2008 11:38:51 -0400 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. Message-ID: <1223998731-16081-1-git-send-email-dpierce@redhat.com> Rather than sending the node a series of scripts that load kernel modules, or are tightly coupled to tools like augeas, this patch introduces an encoding scheme for data. A line that begins with "kmod" describes a kernel module that needs to be loaded. It will containing the module's name, an optional alias for the module, and then the module options if such are required. A line that begins with "ifcfg" describes an network interface. It will contain the mac address and interface name, followed by all needed configuration values to bring the interface up. Signed-off-by: Darryl L. Pierce --- src/app/models/bonding.rb | 1 + src/lib/managed_node_configuration.rb | 72 +++++++++------ src/test/fixtures/bondings.yml | 8 ++- src/test/fixtures/bondings_nics.yml | 8 ++ src/test/fixtures/hosts.yml | 10 ++ src/test/fixtures/nics.yml | 14 +++ .../functional/managed_node_configuration_test.rb | 94 ++++++++------------ 7 files changed, 118 insertions(+), 89 deletions(-) diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 4b6e8a5..859df31 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -44,6 +44,7 @@ class Bonding < ActiveRecord::Base belongs_to :host belongs_to :bonding_type + belongs_to :boot_type has_and_belongs_to_many :nics, :join_table => 'bondings_nics', diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb index 4ade235..fc33560 100644 --- a/src/lib/managed_node_configuration.rb +++ b/src/lib/managed_node_configuration.rb @@ -23,36 +23,57 @@ require 'stringio' +# +ManagedNodeConfiguration+ generates a configuration file for an oVirt node +# based on information about the hardware submitted by the node and the +# configuration details held in the database. +# +# The configuration is returned as a series of encoded lines. +# +# For a kernel module, the formation of the line is as follows: +# +# kmod=[module name]|[module alias]|[module options] +# +# An example would be for loading the +bonding+ kernel module to setup a bonded +# interface for load balancing. In this example, the bonded interface would be +# named +failover0+ on the node: +# +# kmod=bonding|failover0|mode=2 miimon=100 downdelay=200 +# +# For a network interface (including a bonded interface) an example would be: +# +# ifcfg=00:11:22:33:44|eth0|BOOTPROTO=dhcp|bridge=ovirtbr0|ONBOOT=yes +# +# In this line, the network interface +eth0+ has a hardware MAC address of +# +00:11:22:33:44+. It will use DHCP for retrieving it's IP address details, +# and will use the +ovirtbr0+ interface as a bridge. +# class ManagedNodeConfiguration NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' def self.generate(host, macs) result = StringIO.new - result.puts "#!/bin/bash" result.puts "# THIS FILE IS GENERATED!" # first process any bondings that're defined unless host.bondings.empty? - result.puts "cat <<\EOF > /var/tmp/pre-config-script" - result.puts "#!/bin/bash" - result.puts "# THIS FILE IS GENERATED!" - host.bondings.each do |bonding| - result.puts "/sbin/modprobe bonding mode=#{bonding.bonding_type.mode}" + result.puts "bonding=#{bonding.interface_name}" end - - result.puts "EOF" end # now process the network interfaces and bondings - result.puts "cat <<\EOF > /var/tmp/node-augtool" host.bondings.each do |bonding| - result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_addr}" if bonding.ip_addr - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/ONBOOT yes" + entry = "ifcfg=none|#{bonding.interface_name}|BONDING_OPTS=\"mode=#{bonding.bonding_type.mode} miimon=100\"" + + if bonding.ip_addr == nil || bonding.ip_addr.empty? + entry += "|BOOTPROTO=dhcp" + else + entry += "|BOOTPROTO=static|IPADDR=#{bonding.ip_addr}|NETMASK=#{bonding.netmask}|BROADCAST=#{bonding.broadcast}" + end + + result.puts "#{entry}|ONBOOT=yes" bonding.nics.each do |nic| process_nic result, nic, macs, bonding @@ -66,9 +87,6 @@ class ManagedNodeConfiguration end end - result.puts "save" - result.puts "EOF" - result.string end @@ -78,24 +96,18 @@ class ManagedNodeConfiguration 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}" + entry = "ifcfg=#{nic.mac}|#{iface_name}" if bonding - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/MASTER #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/SLAVE yes" + entry += "|MASTER=#{bonding.interface_name}|SLAVE=yes" else - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO #{nic.boot_type.proto}" - - if nic.boot_type.proto == 'static' - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" - end + entry += "|BOOTPROTO=#{nic.boot_type.proto}" + entry += "|IPADDR=#{nic.ip_addr}|NETMASK=#{nic.netmask}|BROADCAST=#{nic.broadcast}" if nic.boot_type.proto == 'static' + entry += "|BRIDGE=#{nic.bridge}" if nic.bridge end - - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE #{nic.bridge}" if nic.bridge - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/ONBOOT yes" + entry += "|ONBOOT=yes" end + + result.puts entry if defined? entry end end diff --git a/src/test/fixtures/bondings.yml b/src/test/fixtures/bondings.yml index c2a47b5..7023e08 100644 --- a/src/test/fixtures/bondings.yml +++ b/src/test/fixtures/bondings.yml @@ -1,6 +1,6 @@ mailservers_managed_node_bonding: name: Production Network - interface_name: bond0 + interface_name: mailbonding0 bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> host_id: <%= Fixtures.identify(:mailservers_managed_node) %> ip_addr: 172.31.0.15 @@ -8,3 +8,9 @@ mailservers_managed_node_bonding: broadcast: 172.31.0.255 arp_ping_address: 172.31.0.100 arp_interval: 0 + +mediaserver_managed_node_bonding: + name: Fileserver Network + interface_name: mediabonding0 + bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> diff --git a/src/test/fixtures/bondings_nics.yml b/src/test/fixtures/bondings_nics.yml index 11a3d1a..607ff8b 100644 --- a/src/test/fixtures/bondings_nics.yml +++ b/src/test/fixtures/bondings_nics.yml @@ -5,3 +5,11 @@ mailservers_managed_node_bonding_nic_1: mailservers_managed_node_bonding_nic_2: bonding_id: <%= Fixtures.identify(:mailservers_managed_node_bonding) %> nic_id: <%= Fixtures.identify(:mailserver_nic_two) %> + +mediaservers_managed_node_bonding_nic_1: + bonding_id: <%= Fixtures.identify(:mediaserver_managed_node_bonding) %> + nic_id: <%= Fixtures.identify(:mediaserver_nic_one) %> + +mediaservers_managed_node_bonding_nic_2: + bonding_id: <%= Fixtures.identify(:mediaserver_managed_node_bonding) %> + nic_id: <%= Fixtures.identify(:mediaserver_nic_two) %> diff --git a/src/test/fixtures/hosts.yml b/src/test/fixtures/hosts.yml index 5b8af15..28282c2 100644 --- a/src/test/fixtures/hosts.yml +++ b/src/test/fixtures/hosts.yml @@ -125,3 +125,13 @@ buildserver_managed_node: is_disabled: 0 hypervisor_type: 'kvm' hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + +mediaserver_managed_node: + uuid: '6293acd9-2784-11dc-9387-001558c41534' + hostname: 'build.mynetwork.com' + arch: 'x86_64' + memory: 65536 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index ccf71d2..5b2cecc 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -78,3 +78,17 @@ buildserver_nic_two: broadcast: '172.31.0.255' host_id: <%= Fixtures.identify(:buildserver_managed_node) %> boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + +mediaserver_nic_one: + mac: '07:17:19:65:03:32' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + +mediaserver_nic_two: + mac: '07:17:19:65:03:31' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index b5a7ec5..e5b9c18 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -36,6 +36,7 @@ class ManagedNodeConfigurationTest < Test::Unit::TestCase @host_with_ip_address = hosts(:ldapserver_managed_node) @host_with_multiple_nics = hosts(:buildserver_managed_node) @host_with_bondings = hosts(:mailservers_managed_node) + @host_with_dhcp_bondings = hosts(:mediaserver_managed_node) end # Ensures that network interfaces uses DHCP when no IP address is specified. @@ -44,15 +45,8 @@ class ManagedNodeConfigurationTest < Test::Unit::TestCase nic = @host_with_dhcp_card.nics.first expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -save -EOF +ifcfg=#{nic.mac}|eth0|BOOTPROTO=dhcp|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -69,19 +63,8 @@ EOF nic = @host_with_ip_address.nics.first expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -save -EOF +ifcfg=#{nic.mac}|eth0|BOOTPROTO=#{nic.boot_type.proto}|IPADDR=#{nic.ip_addr}|NETMASK=#{nic.netmask}|BROADCAST=#{nic.broadcast}|BRIDGE=#{nic.bridge}|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -99,22 +82,9 @@ EOF nic2 = @host_with_multiple_nics.nics[1] expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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/BOOTPROTO #{nic2.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes -save -EOF +ifcfg=#{nic1.mac}|eth0|BOOTPROTO=#{nic1.boot_type.proto}|IPADDR=#{nic1.ip_addr}|NETMASK=#{nic1.netmask}|BROADCAST=#{nic1.broadcast}|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|BOOTPROTO=#{nic2.boot_type.proto}|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -137,30 +107,11 @@ EOF nic2 = bonding.nics[1] expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/pre-config-script -#!/bin/bash -# THIS FILE IS GENERATED! -/sbin/modprobe bonding mode=#{bonding.bonding_type.mode} -EOF -cat <<\EOF > /var/tmp/node-augtool -rm /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR 172.31.0.15 -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/ONBOOT yes -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/MASTER #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/SLAVE yes -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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/MASTER #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/SLAVE yes -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes -save -EOF +bonding=#{bonding.interface_name} +ifcfg=none|#{bonding.interface_name}|BONDING_OPTS="mode=#{bonding.bonding_type.mode} miimon=100"|BOOTPROTO=static|IPADDR=#{bonding.ip_addr}|NETMASK=#{bonding.netmask}|BROADCAST=#{bonding.broadcast}|ONBOOT=yes +ifcfg=#{nic1.mac}|eth0|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -173,4 +124,31 @@ HERE assert_equal expected, result end + # Ensures that the generated bonding supports DHCP boot protocol. + # + def test_generate_with_dhcp_bonding + bonding = @host_with_dhcp_bondings.bondings.first + + bonding.ip_addr=nil + nic1 = bonding.nics[0] + nic2 = bonding.nics[1] + + expected = <<-HERE +# THIS FILE IS GENERATED! +bonding=#{bonding.interface_name} +ifcfg=none|#{bonding.interface_name}|BONDING_OPTS="mode=#{bonding.bonding_type.mode} miimon=100"|BOOTPROTO=dhcp|ONBOOT=yes +ifcfg=#{nic1.mac}|eth0|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +HERE + + result = ManagedNodeConfiguration.generate( + @host_with_dhcp_bondings, + { + "#{nic1.mac}" => 'eth0', + "#{nic2.mac}" => 'eth1' + }) + + assert_equal expected, result + end + end -- 1.5.5.1 From berrange at redhat.com Tue Oct 14 15:45:58 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Tue, 14 Oct 2008 16:45:58 +0100 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <1223998731-16081-1-git-send-email-dpierce@redhat.com> References: <1223998731-16081-1-git-send-email-dpierce@redhat.com> Message-ID: <20081014154558.GR10745@redhat.com> On Tue, Oct 14, 2008 at 11:38:51AM -0400, Darryl L. Pierce wrote: > A line that begins with "kmod" describes a kernel module that > needs to be loaded. It will containing the module's name, an > optional alias for the module, and then the module options > if such are required. This is wrong - module options for bonding are per-NIC. > +# kmod=[module name]|[module alias]|[module options] > +# > +# An example would be for loading the +bonding+ kernel module to setup a bonded > +# interface for load balancing. In this example, the bonded interface would be > +# named +failover0+ on the node: > +# > +# kmod=bonding|failover0|mode=2 miimon=100 downdelay=200 This options must be set against the NIC, not the module 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 Tue Oct 14 15:52:23 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Tue, 14 Oct 2008 16:52:23 +0100 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <1223998688-15991-1-git-send-email-dpierce@redhat.com> References: <1223998688-15991-1-git-send-email-dpierce@redhat.com> Message-ID: <20081014155223.GS10745@redhat.com> On Tue, Oct 14, 2008 at 11:38:08AM -0400, Darryl L. Pierce wrote: > The system now takes an encoded configuration descriptor from the server. It > then parses from that a set of aliases for bondings if such exist. It then > also extracts configuration details for the various network interfaces on the > node. Afterward, it reloads module dependencies and then restarts the > networking service. > > Signed-off-by: Darryl L. Pierce > --- > Makefile.am | 1 + > ovirt-node.spec.in | 2 + > scripts/ovirt-early | 14 +++----- > scripts/ovirt-process-config | 70 ++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 78 insertions(+), 9 deletions(-) > create mode 100755 scripts/ovirt-process-config > > diff --git a/Makefile.am b/Makefile.am > index 1d63310..c55db13 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -30,6 +30,7 @@ EXTRA_DIST = \ > scripts/ovirt-early \ > scripts/ovirt-functions \ > scripts/ovirt-post \ > + scripts/ovirt-process-config \ > logrotate/ovirt-logrotate \ > logrotate/ovirt-logrotate.conf > > diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in > index daa3d7f..ea8099d 100644 > --- a/ovirt-node.spec.in > +++ b/ovirt-node.spec.in > @@ -74,6 +74,7 @@ cd - > %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d > > %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} > +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} > %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} > %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} > %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake > @@ -145,6 +146,7 @@ fi > %files > %defattr(-,root,root,0755) > %{_sbindir}/ovirt-awake > +%{_sbindir}/ovirt-process-config > %{_sbindir}/ovirt-identify-node > %{_sbindir}/ovirt-listen-awake > %{_sbindir}/ovirt-install-node > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > index 4723426..3c8c494 100755 > --- a/scripts/ovirt-early > +++ b/scripts/ovirt-early > @@ -12,6 +12,8 @@ > > # size of the oVirt partition in megabytes > OVIRT_SIZE=64 > +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding > + > +modconf=$(awk '/bonding=/ { > + match($0, "^[ \t]*bonding=(.*)", data) > + split(data[1], mod, "|") > + > + if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { > + printf "invalid bonding alias: \"%s\"\n", mod[1]; > + exit 1; > + } > + > + alias=mod[1] > + > + printf("install %s bonding", alias) > + }' $CONFIG) > + > +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE There is no need to install the bonding module. THis is handled automatically by the networking init scripts. You merely need the aliases. # grep bonding /etc/modprobe.conf alias bond0 bonding alias bond1 bonding alias bond2 bonding Danie -- |: 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 dpierce at redhat.com Tue Oct 14 16:00:31 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Tue, 14 Oct 2008 12:00:31 -0400 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <20081014154558.GR10745@redhat.com> References: <1223998731-16081-1-git-send-email-dpierce@redhat.com> <20081014154558.GR10745@redhat.com> Message-ID: <48F4C21F.2040408@redhat.com> Daniel P. Berrange wrote: > On Tue, Oct 14, 2008 at 11:38:51AM -0400, Darryl L. Pierce wrote: >> A line that begins with "kmod" describes a kernel module that >> needs to be loaded. It will containing the module's name, an >> optional alias for the module, and then the module options >> if such are required. > > This is wrong - module options for bonding are per-NIC. > >> +# kmod=[module name]|[module alias]|[module options] >> +# >> +# An example would be for loading the +bonding+ kernel module to setup a bonded >> +# interface for load balancing. In this example, the bonded interface would be >> +# named +failover0+ on the node: >> +# >> +# kmod=bonding|failover0|mode=2 miimon=100 downdelay=200 > > This options must be set against the NIC, not the module Thanks for the catch, Dan. The documetation is out of date regarding the configuration file. I'll update the rdocs for the class and resend the patch. -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Tue Oct 14 16:03:17 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 14 Oct 2008 12:03:17 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. Message-ID: <1224000197-17453-1-git-send-email-dpierce@redhat.com> The system now takes an encoded configuration descriptor from the server. It then parses from that a set of aliases for bondings if such exist. It then also extracts configuration details for the various network interfaces on the node. Afterward, it reloads module dependencies and then restarts the networking service. This patch also includes updated rdocs for the configuration generator. Signed-off-by: Darryl L. Pierce --- Makefile.am | 1 + ovirt-node.spec.in | 2 + scripts/ovirt-early | 14 +++----- scripts/ovirt-process-config | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100755 scripts/ovirt-process-config diff --git a/Makefile.am b/Makefile.am index 1d63310..c55db13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ scripts/ovirt-early \ scripts/ovirt-functions \ scripts/ovirt-post \ + scripts/ovirt-process-config \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index daa3d7f..ea8099d 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -74,6 +74,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake @@ -145,6 +146,7 @@ fi %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake +%{_sbindir}/ovirt-process-config %{_sbindir}/ovirt-identify-node %{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 4723426..3c8c494 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -12,6 +12,8 @@ # size of the oVirt partition in megabytes OVIRT_SIZE=64 +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding +AUGTOOL_CONFIG=/var/tmp/augtool-config get_mac_addresses() { macs=$(ifconfig | awk '/HWaddr/ { print $5"="$1 }' \ @@ -43,16 +45,10 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/managed_node/config?host=$(hostname)&macs=$macs" if [ $? -eq 0 ]; then echo "Remote configuration bundle retrieved to $cfgdb" - bash $cfgdb - if [ -f /var/tmp/pre-config-script ]; then - echo "Loading kernel modules" - bash /var/tmp/pre-config-script \ - && echo "Kernel modules loaded" \ - || echo "Failed loading kernel modules" - fi - if [ -f /var/tmp/node-augtool ]; then + ovirt-process-config $cfgdb $BONDING_MODCONF_FILE $AUGTOOL_CONFIG + if [ -f $AUGTOOL_CONFIG ]; then echo "Loading remote config" - augtool < /var/tmp/node-augtool \ + augtool < $AUGTOOL_CONFIG \ && echo "Remote config applied" \ || echo "Failed applying remote config" fi diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config new file mode 100755 index 0000000..9af0822 --- /dev/null +++ b/scripts/ovirt-process-config @@ -0,0 +1,68 @@ +#!/bin/bash + +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} + +case $# in + 0|1|2) warn "too few arguments"; try_help;; + 3) ;; + *) warn "too many arguments"; try_help;; +esac + +CONFIG=$1 +OVIRT_KERNEL_MODULE_FILE=$2 +OVIRT_CONFIG_OUTPUT_FILE=$3 + +modconf=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + split(data[1], mod, "|") + + if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { + printf "invalid bonding alias: \"%s\"\n", mod[1]; + exit 1; + } + + alias=mod[1] + + printf("install %s bonding", alias) + }' $CONFIG) + +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE + +# now build the list of module aliases to load and load them +modules=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + split(data[1], mod, "|") + + printf("%s ", mod[1]) + }' $CONFIG) + +networking=$(awk '/ifcfg=/ { + match($0, "^[ \t]*ifcfg=(.*)", data) + split(data[1], ifcfg, "|") + + mac = ifcfg[1] + iface = ifcfg[2] + ifcfg_dir = "/files/etc/sysconfig/network-scripts" + + printf("rm %s/ifcfg-%s\n", ifcfg_dir, iface) + printf("set %s/ifcfg-%s/DEVICE %s\n", ifcfg_dir, iface, iface) + + for (line in ifcfg) { + if(line > 2) { + match(ifcfg[line], "(^[^=]+)=(.*)", values) + field=values[1] + value=values[2] + + printf("set %s/ifcfg-%s/%s %s\n", ifcfg_dir, iface, field, value) + } + } + + + printf("save\n") + +}' $CONFIG) + +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 16:14:26 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 12:14:26 -0400 Subject: [Ovirt-devel] Question about managing strage server on latest oVirt In-Reply-To: <48E48EE5.8060508@np.css.fujitsu.com> References: <48E48EE5.8060508@np.css.fujitsu.com> Message-ID: <48F4C562.7030504@redhat.com> Toshifumi Fujimura wrote: > Hello! > > I attempt to install a guest OS with WUI follows "Using oVirt". > But I cannot add Strage Pool to Hardware Pool. > I can't oparate in the way of stage 6 in "Using > oVirt"(http://www.ovirt.org/docs/Using_oVirt/Add-Storage.html). > Because "Using oVirt" is a little bit old. > > Would you suggest me a latest instruction of the way to add Strage Pool > to Hardware Pool. We are in the process of putting out a new release (0.94) that will have some updated docs in the ovirt-docs rpm and should have integrated online help. That might fix your problem. Try this again when 0.94 is released and if you're still having problems, please let us know. The release should be out in the next day or two. Thanks, Perry From pmyers at redhat.com Tue Oct 14 16:44:49 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 12:44:49 -0400 Subject: [Ovirt-devel] PATCH: make "warn" function more robust In-Reply-To: <87tzbsda8t.fsf@rho.meyering.net> References: <87tzbsda8t.fsf@rho.meyering.net> Message-ID: <48F4CC81.6030100@redhat.com> Jim Meyering wrote: > I noticed that the warn shell function (and hence die, too) would > misbehave when operating on something containing a % (or, far less > likely, a \c sequence that printf recognizes). > > These fix the four definitions I found, > 3 in node-image/, 1 in appliance/. These look good. ACK and please push to next :) Perry From pmyers at redhat.com Tue Oct 14 17:01:49 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 13:01:49 -0400 Subject: [Ovirt-devel] [PATCH appliance] Fix fake node installation options to be a little more sane Message-ID: <1224003709-16110-1-git-send-email-pmyers@redhat.com> Since everyone uses node3 as a smoke test node, I've purposed node3-5 as follows: node3 - No config persistence, always PXE boots and does not use disk image node4 - Uses config persistence, always PXE boots node5 - Installs on first PXE boot to hard disk. Need to toggle to hd boot after initial boot This ordering seems better and will cause less problems with people still transitioning off of the fake nodes. Signed-off-by: Perry Myers --- ovirt-appliance.ks | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ovirt-appliance.ks b/ovirt-appliance.ks index 0497768..ab904ac 100644 --- a/ovirt-appliance.ks +++ b/ovirt-appliance.ks @@ -125,11 +125,11 @@ cobbler distro add --name="oVirt-Node-$arch" --arch=$arch \ --kopts="rootflags=loop root=/ovirt-node-image.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 --kopts="ovirt_init=scsi" + --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 --kopts="ovirt_init=scsi ovirt_local_boot" + --name=node4 --mac=00:16:3e:12:34:58 --kopts="ovirt_init=scsi" cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ - --name=node5 --mac=00:16:3e:12:34:59 + --name=node5 --mac=00:16:3e:12:34:59 --kopts="ovirt_init=scsi ovirt_local_boot" set +x echo "Add new oVirt Nodes as Cobbler systems to make them PXE boot oVirt Node image directly." echo "oVirt-Node-$arch is also default boot option in Cobbler menu" -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 17:07:21 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 13:07:21 -0400 Subject: [Ovirt-devel] Re: [PATCH appliance] Fix fake node installation options to be a little more sane In-Reply-To: <1224003709-16110-1-git-send-email-pmyers@redhat.com> References: <1224003709-16110-1-git-send-email-pmyers@redhat.com> Message-ID: <48F4D1C9.6000402@redhat.com> Perry Myers wrote: > Since everyone uses node3 as a smoke test node, I've purposed > node3-5 as follows: > > node3 - No config persistence, always PXE boots and does not use disk image > node4 - Uses config persistence, always PXE boots > node5 - Installs on first PXE boot to hard disk. Need to toggle to hd boot > after initial boot > > This ordering seems better and will cause less problems with people > still transitioning off of the fake nodes. Ignore this... Alan had already fixed this... Perry From pmyers at redhat.com Tue Oct 14 17:15:37 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 13:15:37 -0400 Subject: [Ovirt-devel] [PATCH server] Added a link from bonding to a boot type. In-Reply-To: <48EC9489.3040902@redhat.com> References: <1223390662-16536-1-git-send-email-dpierce@redhat.com> <48EC9489.3040902@redhat.com> Message-ID: <48F4D3B9.6010802@redhat.com> Chris Lalancette wrote: > Darryl L. Pierce wrote: >> Previously the bondings had one of two paths: it could either >> explicitly set a static IP address or else fallback to an implicit >> DHCP boot. >> >> This change will require that bondings be explicitly configured >> in the same way as a standard network interface. >> >> Signed-off-by: Darryl L. Pierce >> --- >> src/app/models/bonding.rb | 10 +++++++ >> src/db/migrate/023_add_boot_type_to_bonding.rb | 31 ++++++++++++++++++++++++ >> src/test/fixtures/bondings.yml | 1 + >> src/test/unit/bonding_test.rb | 20 +++++++++++---- >> 4 files changed, 57 insertions(+), 5 deletions(-) >> create mode 100644 src/db/migrate/023_add_boot_type_to_bonding.rb > > Yes, this is reasonable. I like things to be a little more explicit, so having > an explicit "DHCP" (as opposed to implicit) makes things better. > > ACK Darryl, Did this ever get committed? If this has an ACK we should get it into the 0.94 release. 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 imain at redhat.com Tue Oct 14 17:22:30 2008 From: imain at redhat.com (Ian Main) Date: Tue, 14 Oct 2008 10:22:30 -0700 Subject: [Ovirt-devel] [PATCH node] Add collectd and libvirt-qpid setup to ovirt-install-node Message-ID: <1224004950-3674-1-git-send-email-imain@redhat.com> This patch adds setup for libvirt-qpid and collectd to the ovirt-install-node script so that they will both work in stateful setup. Signed-off-by: Ian Main --- ovirt-listen-awake/ovirt-install-node | 13 +++++++++++++ 1 files changed, 13 insertions(+), 0 deletions(-) diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node index 1d998f4..f81a4a1 100644 --- a/ovirt-listen-awake/ovirt-install-node +++ b/ovirt-listen-awake/ovirt-install-node @@ -82,6 +82,19 @@ elif [ "$1" = "stateful" ]; then add_if_not_exist "192.168.50.1 physical.priv.ovirt.org" /etc/hosts add_if_not_exist "192.168.50.2 management.priv.ovirt.org" /etc/hosts + collectd_conf=/etc/collectd.conf + if [ -f $collectd_conf.in ]; then + sed -e "s/@COLLECTD_SERVER@/192.168.50.2/" \ + -e "s/@COLLECTD_PORT@/25826/" $collectd_conf.in \ + > $collectd_conf + fi + + libvirt_qpid_conf=/etc/sysconfig/libvirt-qpid + if [ -f $libvirt_qpid_conf ]; then + echo "LIBVIRT_QPID_ARGS=\"--broker 192.168.50.2 --port 5672\"" >> $libvirt_qpid_conf + fi + + chkconfig ovirt-listen-awake on chkconfig collectd on -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 17:42:21 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 13:42:21 -0400 Subject: [Ovirt-devel] [PATCH node] Add collectd and libvirt-qpid setup to ovirt-install-node In-Reply-To: <1224004950-3674-1-git-send-email-imain@redhat.com> References: <1224004950-3674-1-git-send-email-imain@redhat.com> Message-ID: <48F4D9FD.9080108@redhat.com> Ian Main wrote: > This patch adds setup for libvirt-qpid and collectd to > the ovirt-install-node script so that they will both work > in stateful setup. Conceptual ACK, if it's been tested it can be pushed. Otherwise test it first :) Perry From bkearney at redhat.com Tue Oct 14 18:12:42 2008 From: bkearney at redhat.com (Bryan Kearney) Date: Tue, 14 Oct 2008 14:12:42 -0400 Subject: [Ovirt-devel] Patch for the single exec issue Message-ID: <48F4E11A.4040907@redhat.com> Perry found an issue with the ace tools where items marked as a single exec were being execute more then once. This should resolve that. -- bk -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: single_exec.patch URL: From pmyers at redhat.com Tue Oct 14 18:28:33 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 14:28:33 -0400 Subject: [Ovirt-devel] [PATCH node] Add collectd and libvirt-qpid setup to ovirt-install-node Message-ID: <1224008913-18798-1-git-send-email-pmyers@redhat.com> This patch adds setup for libvirt-qpid and collectd to the ovirt-install-node script so that they will both work in stateful setup. Signed-off-by: Ian Main Signed-off-by: Perry Myers --- ovirt-listen-awake/ovirt-install-node | 28 ++++++++++++++++++++++++---- ovirt-listen-awake/ovirt-uninstall-node | 4 +++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node index a4a4b6f..95ba4de 100644 --- a/ovirt-listen-awake/ovirt-install-node +++ b/ovirt-listen-awake/ovirt-install-node @@ -28,7 +28,7 @@ backup_file() { dir=$(dirname "$1") case $dir in /*);; *) die "unexpected non-absolute dir: $dir";; esac mkdir -p "$OVIRT_BACKUP_DIR/${dir:1}" - cp -pf "$1" "$OVIRT_BACKUP_DIR/${dir:1}" + test -f "$1" && cp -pf "$1" "$OVIRT_BACKUP_DIR/${dir:1}" } add_if_not_exist() { @@ -103,12 +103,31 @@ elif [ "$1" = "stateful" ]; then fi hostname $PHYS_HOST + collectd_conf=/etc/collectd.conf + backup_file $collectd_conf + if [ -f $collectd_conf.in ]; then + sed -e "s/@COLLECTD_SERVER@/$MGMT_HOST/" \ + -e "s/@COLLECTD_PORT@/25826/" $collectd_conf.in \ + > $collectd_conf + fi + + libvirt_qpid_conf=/etc/sysconfig/libvirt-qpid + backup_file $libvirt_qpid_conf + if [ -f $libvirt_qpid_conf ]; then + if grep "^LIBVIRT_QPID_ARGS=" $libvirt_qpid_conf > /dev/null 2>&1 ; then + sed -i -e "s/^LIBVIRT_QPID_ARGS=.*/LIBVIRT_QPID_ARGS=\"--broker $MGMT_HOST --port 5672\"/" $libvirt_qpid_conf + else + echo "LIBVIRT_QPID_ARGS=\"--broker $MGMT_HOST --port 5672\"" >> $libvirt_qpid_conf + fi + fi + backup_file /etc/hosts add_if_not_exist "192.168.50.1 $PHYS_HOST" /etc/hosts add_if_not_exist "192.168.50.2 $MGMT_HOST" /etc/hosts chkconfig ovirt-listen-awake on chkconfig collectd on + chkconfig libvirt-qpid on backup_file /etc/sysconfig/libvirtd backup_file /etc/libvirt/qemu.conf @@ -125,9 +144,6 @@ elif [ "$1" = "stateful" ]; then # 49152-49216:tcp (libvirt migration) lokkit -t ovirtbr0 - service collectd restart - service ovirt-listen-awake restart - # Check if any domains are active before restarting libvirtd, since it will # kill them. Header information from virsh list is 2 lines, and 1 line for # footer. So > 3 lines means domains are running @@ -139,6 +155,10 @@ elif [ "$1" = "stateful" ]; then else service libvirtd restart fi + + service collectd restart + service ovirt-listen-awake restart + service libvirt-qpid restart else usage exit 1 diff --git a/ovirt-listen-awake/ovirt-uninstall-node b/ovirt-listen-awake/ovirt-uninstall-node index 3e4d300..1046a37 100644 --- a/ovirt-listen-awake/ovirt-uninstall-node +++ b/ovirt-listen-awake/ovirt-uninstall-node @@ -25,7 +25,7 @@ fi unbackup_file() { # note that $1 will have a / on the front, so we don't need to add our own - cp -pf "$OVIRT_BACKUP_DIR$1" "$1" + test -f "$OVIRT_BACKUP_DIR$1" && cp -pf "$OVIRT_BACKUP_DIR$1" "$1" } chkconfig ovirt-listen-awake off @@ -39,4 +39,6 @@ unbackup_file /etc/libvirt/libvirtd.conf unbackup_file /etc/sasl2/libvirt.conf unbackup_file /etc/sysconfig/iptables unbackup_file /etc/krb5.conf +unbackup_file /etc/collectd.conf +unbackup_file /etc/sysconfig/libvirt-qpid rm -Rf $OVIRT_BACKUP_DIR -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 20:32:23 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 16:32:23 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed regression caused by cb2fad05d45bf51a7c8b7c06c9700b94024ae442 Message-ID: <1224016343-20232-1-git-send-email-pmyers@redhat.com> The above patch to allow iso and disk image provisioning broke profile provisioning because it did not remove some vestigal code from the create_vm method in task_vm.rb. This is now fixed. Signed-off-by: Perry Myers --- src/task-omatic/task_vm.rb | 13 +++---------- 1 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 8398e83..02e41af 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -163,16 +163,9 @@ def create_vm(task) # create cobbler system profile begin - if vm.provisioning and !vm.provisioning.empty? - if vm.uses_cobbler? - if vm.cobbler_type == Vm::PROFILE_PREFIX: - system = Cobbler::System.new('name' => vm.uuid, - 'profile' => provisioning_arr[2]) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] - system.save - end - end - end + # FIXME: Presently the wui handles all cobbler system creation. + # This should be moved out of the wui into Taskomatic. Specifically + # here, and in the edit_vm methods. setVmState(vm, Vm::STATE_STOPPED) rescue Exception => error -- 1.5.5.1 From imain at redhat.com Tue Oct 14 20:34:19 2008 From: imain at redhat.com (Ian Main) Date: Tue, 14 Oct 2008 13:34:19 -0700 Subject: [Ovirt-devel] Re: [PATCH node] Add collectd and libvirt-qpid setup to ovirt-install-node In-Reply-To: <1224008913-18798-1-git-send-email-pmyers@redhat.com> References: <1224008913-18798-1-git-send-email-pmyers@redhat.com> Message-ID: <20081014133419.57ba3c40@tp.mains.net> On Tue, 14 Oct 2008 14:28:33 -0400 Perry Myers wrote: > This patch adds setup for libvirt-qpid and collectd to > the ovirt-install-node script so that they will both work > in stateful setup. > > Signed-off-by: Ian Main > Signed-off-by: Perry Myers Yeah this one works. ACK! Ian From pmyers at redhat.com Tue Oct 14 20:42:59 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 16:42:59 -0400 Subject: [Ovirt-devel] [PATCH node-image] Moved requires that are needed for all Node installations to the Node package Message-ID: <1224016979-20422-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- common-pkgs.ks | 23 ----------------------- 1 files changed, 0 insertions(+), 23 deletions(-) diff --git a/common-pkgs.ks b/common-pkgs.ks index cd4f243..9f9e277 100644 --- a/common-pkgs.ks +++ b/common-pkgs.ks @@ -1,37 +1,14 @@ @core -bash kernel hwdata passwd policycoreutils -chkconfig rootfiles dhclient -libvirt -libvirt-python openssh-clients openssh-server -iscsi-initiator-utils -ntp kvm -nfs-utils -wget -krb5-workstation -cyrus-sasl-gssapi -cyrus-sasl -cyrus-sasl-lib -collectd -collectd-virt -augeas -nc -bind-utils syslinux -cronie -hal -# Stupid yum dep solver pulls in older 'qemu' to resolve -# /usr/bin/qemu-img dep. This forces it to pick the new -# qemu-img RPM. -qemu-img ovirt-node ovirt-node-selinux -audit-libs-python -- 1.5.5.1 From pmyers at redhat.com Tue Oct 14 20:43:08 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 16:43:08 -0400 Subject: [Ovirt-devel] [PATCH node] Added Requires for packages that Node needs that previously were in a kickstart Message-ID: <1224016988-20445-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- ovirt-node.spec.in | 16 ++++++++++++++++ 1 files changed, 16 insertions(+), 0 deletions(-) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index daa3d7f..3fcc166 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -22,7 +22,23 @@ Requires: libvirt Requires: libvirt-qpid Requires: hal Requires: collectd +Requires: collectd-virt +Requires: wget Requires: cyrus-sasl-gssapi +Requires: iscsi-initiator-utils +Requires: ntp +Requires: nfs-utils +Requires: krb5-workstation +Requires: cyrus-sasl-gssapi cyrus-sasl cyrus-sasl-lib +Requires: bash +Requires: chkconfig +Requires: bind-utils +# Stupid yum dep solver pulls in older 'qemu' to resolve +# /usr/bin/qemu-img dep. This forces it to pick the new +# qemu-img RPM. +Requires: qemu-img +Requires: nc +Requires: cronie ExclusiveArch: %{ix86} x86_64 %define app_root %{_datadir}/%{name} -- 1.5.5.1 From mmorsi at redhat.com Tue Oct 14 20:58:47 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 14 Oct 2008 16:58:47 -0400 Subject: [Ovirt-devel] [PATCH] fixes / improvements to test suite Message-ID: <1224017928-26999-1-git-send-email-mmorsi@redhat.com> --- src/app/models/vm_task.rb | 3 --- src/test/fixtures/quotas.yml | 2 +- src/test/fixtures/storage_pools.yml | 6 +++--- src/test/functional/host_controller_test.rb | 2 +- src/test/functional/resources_controller_test.rb | 6 +++--- src/test/functional/storage_controller_test.rb | 2 +- src/test/functional/task_controller_test.rb | 2 +- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/app/models/vm_task.rb b/src/app/models/vm_task.rb index 31c4ac8..6251919 100644 --- a/src/app/models/vm_task.rb +++ b/src/app/models/vm_task.rb @@ -122,13 +122,10 @@ class VmTask < Task ACTIONS.each do |action, hash| if hash[:start] == state add_action = true - print "vm: #{vm}\n user: #{user}\n" if (vm and user) pool = vm.send(hash[:privilege][1]) - print "pool: #{pool}\n privilege: #{hash[:privilege][1]}\n" add_action = pool ? pool.has_privilege(user, hash[:privilege][0]) : false end - print "add_action: #{add_action}\n" actions << action if add_action end end diff --git a/src/test/fixtures/quotas.yml b/src/test/fixtures/quotas.yml index 757c934..037fd67 100644 --- a/src/test/fixtures/quotas.yml +++ b/src/test/fixtures/quotas.yml @@ -46,7 +46,7 @@ six: total_vnics: 3 total_storage: 1024 total_vms: 2 - pool_id: 10 + pool_id: 5 seven: id: 7 total_vcpus: diff --git a/src/test/fixtures/storage_pools.yml b/src/test/fixtures/storage_pools.yml index 0610333..ac042a0 100644 --- a/src/test/fixtures/storage_pools.yml +++ b/src/test/fixtures/storage_pools.yml @@ -45,20 +45,20 @@ seven: export_path: '/tmp/apath' eight: id: 8 - hardware_pool_id: 6 + hardware_pool_id: 9 ip_addr: '192.168.50.2' type: 'IscsiStoragePool' port: 531 target: 'bartarget' nine: id: 9 - hardware_pool_id: 8 + hardware_pool_id: 9 type: 'NfsStoragePool' ip_addr: '1.2.3.4' export_path: '/tmp/somepath' ten: id: 10 - hardware_pool_id: 6 + hardware_pool_id: 9 type: 'IscsiStoragePool' ip_addr: '192.168.50.3' port: 539 diff --git a/src/test/functional/host_controller_test.rb b/src/test/functional/host_controller_test.rb index e1e977d..7939aca 100644 --- a/src/test/functional/host_controller_test.rb +++ b/src/test/functional/host_controller_test.rb @@ -24,7 +24,7 @@ require 'host_controller' class HostController; def rescue_action(e) raise e end; end class HostControllerTest < Test::Unit::TestCase - fixtures :hosts + fixtures :hosts, :pools def setup @controller = HostController.new diff --git a/src/test/functional/resources_controller_test.rb b/src/test/functional/resources_controller_test.rb index fca7aad..49e8d0e 100644 --- a/src/test/functional/resources_controller_test.rb +++ b/src/test/functional/resources_controller_test.rb @@ -58,14 +58,14 @@ class ResourcesControllerTest < ActionController::TestCase def test_destroy pool = nil assert_nothing_raised { - pool = VmResourcePool.find(2).parent.id + pool = VmResourcePool.find(10).parent.id } - post :destroy, :id => 2 + post :destroy, :id => 10 assert_response :success assert_raise(ActiveRecord::RecordNotFound) { - VmResourcePool.find(2) + VmResourcePool.find(10) } end end diff --git a/src/test/functional/storage_controller_test.rb b/src/test/functional/storage_controller_test.rb index ff728b9..a9f7eb9 100644 --- a/src/test/functional/storage_controller_test.rb +++ b/src/test/functional/storage_controller_test.rb @@ -24,7 +24,7 @@ require 'storage_controller' class StorageController; def rescue_action(e) raise e end; end class StorageControllerTest < Test::Unit::TestCase - fixtures :storage_volumes + fixtures :storage_volumes, :storage_pools def setup @controller = StorageController.new diff --git a/src/test/functional/task_controller_test.rb b/src/test/functional/task_controller_test.rb index cc07502..a9578b6 100644 --- a/src/test/functional/task_controller_test.rb +++ b/src/test/functional/task_controller_test.rb @@ -24,7 +24,7 @@ require 'task_controller' class TaskController; def rescue_action(e) raise e end; end class TaskControllerTest < Test::Unit::TestCase - fixtures :tasks + fixtures :tasks, :vms, :pools def setup @controller = TaskController.new -- 1.5.4.1 From mmorsi at redhat.com Tue Oct 14 20:58:48 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 14 Oct 2008 16:58:48 -0400 Subject: [Ovirt-devel] [PATCH] added rake test to appliance autobuild In-Reply-To: <1224017928-26999-1-git-send-email-mmorsi@redhat.com> References: <1224017928-26999-1-git-send-email-mmorsi@redhat.com> Message-ID: <1224017928-26999-2-git-send-email-mmorsi@redhat.com> also removed unecessary chaning of the rails env --- autobuild.sh | 9 +-------- 1 files changed, 1 insertions(+), 8 deletions(-) diff --git a/autobuild.sh b/autobuild.sh index 98a3b46..07ea1db 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -58,13 +58,6 @@ ln -s /etc/init.d/sshd /etc/rc3.d/S99sshd %end KS -# set appliance to run in test mode -cat >> ovirt-appliance.ks << \KS -%post -sed -i "s/#RAILS_ENV=production/RAILS_ENV=test/g" /etc/sysconfig/ovirt-rails -%end -KS - ./autogen.sh --prefix=$AUTOBUILD_INSTALL_ROOT make dist @@ -115,4 +108,4 @@ for i in $(seq 1 10); do sleep 10 done -# $ssh_cmd cd /usr/share/ovirt-server && rake test" +$ssh_cmd "cd /usr/share/ovirt-server && export RAILS_ENV=test && rake test" -- 1.5.4.1 From apevec at redhat.com Tue Oct 14 20:58:42 2008 From: apevec at redhat.com (Alan Pevec) Date: Tue, 14 Oct 2008 22:58:42 +0200 Subject: [Ovirt-devel] [PATCH ovirt-appliance] use rawhide repos for Fedora 10 Message-ID: <1224017922-27412-1-git-send-email-apevec@redhat.com> for experimental builds, until it gets released Signed-off-by: Alan Pevec --- ovirt-appliance.ks | 2 ++ ovirt-appliance.spec.in | 36 ++++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ovirt-appliance.ks b/ovirt-appliance.ks index 82e6a57..b344e47 100644 --- a/ovirt-appliance.ks +++ b/ovirt-appliance.ks @@ -33,6 +33,8 @@ ovirt-recipe lokkit %post +# cleanup rpmdb to allow non-matching host and chroot RPM versions +rm -f /var/lib/rpm/__db* exec > /root/kickstart-post.log 2>&1 # the code to contact the host we are running on and make it configure itself diff --git a/ovirt-appliance.spec.in b/ovirt-appliance.spec.in index f08e0f6..d96e7bf 100644 --- a/ovirt-appliance.spec.in +++ b/ovirt-appliance.spec.in @@ -40,22 +40,38 @@ The oVirt Appliance image and scripts to install on a Fedora Host %setup -q %build -if [ -n "%{?fedora_url}" ]; then - cat > repos.ks << EOF -repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os -repo --name=f%{fedora}-updates --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch} --excludepkgs=%{bad_pkgs} -repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey --excludepkgs=%{bad_pkgs} +%if 0%{?fedora} == 010 + # XXX current rawhide + %if "%{?fedora_url}" == "" +cat > repos.ks << EOF +repo --name=rawhide --mirrorlist=%{fedora_mirror}?repo=rawhide&arch=%{_arch} +EOF + %else +cat > repos.ks << EOF +repo --name=rawhide --baseurl=%{fedora_url}/development/%{_arch}/os EOF -else - cat > repos.ks << EOF + %endif +cat >> repos.ks << EOF +repo --name=ovirt-org --baseurl=%{ovirt_url}/development/%{_arch} +EOF +%else + %if "%{?fedora_url}" == "" +cat > repos.ks << EOF repo --name=f%{fedora} --mirrorlist=%{fedora_mirror}?repo=fedora-%{fedora}&arch=%{_arch} -repo --name=f%{fedora}-updates --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}&arch=%{_arch} --excludepkgs=%{bad_pkgs} repo --name=f%{fedora}-updates-newkey --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}.newkey&arch=%{_arch} --excludepkgs=%{bad_pkgs} EOF -fi - + %else +cat > repos.ks << EOF +repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os +repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey --excludepkgs=%{bad_pkgs} +EOF + %endif cat >> repos.ks << EOF repo --name=ovirt-org --baseurl=%{ovirt_url}/%{fedora}/%{_arch} +EOF +%endif + +cat >> repos.ks << EOF repo --name=ovirt-local --baseurl=%{ovirt_local_repo} repo --name=thincrust-org --baseurl=%{thincrust_url}/noarch -- 1.5.5.1 From sseago at redhat.com Tue Oct 14 21:00:42 2008 From: sseago at redhat.com (Scott Seago) Date: Tue, 14 Oct 2008 17:00:42 -0400 Subject: [Ovirt-devel] [PATCH server] Fixed regression caused by cb2fad05d45bf51a7c8b7c06c9700b94024ae442 In-Reply-To: <1224016343-20232-1-git-send-email-pmyers@redhat.com> References: <1224016343-20232-1-git-send-email-pmyers@redhat.com> Message-ID: <48F5087A.5020501@redhat.com> Perry Myers wrote: > The above patch to allow iso and disk image provisioning broke > profile provisioning because it did not remove some vestigal code > from the create_vm method in task_vm.rb. > > This is now fixed. > > Signed-off-by: Perry Myers > --- > src/task-omatic/task_vm.rb | 13 +++---------- > 1 files changed, 3 insertions(+), 10 deletions(-) > > diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb > index 8398e83..02e41af 100644 > --- a/src/task-omatic/task_vm.rb > +++ b/src/task-omatic/task_vm.rb > @@ -163,16 +163,9 @@ def create_vm(task) > > # create cobbler system profile > begin > - if vm.provisioning and !vm.provisioning.empty? > - if vm.uses_cobbler? > - if vm.cobbler_type == Vm::PROFILE_PREFIX: > - system = Cobbler::System.new('name' => vm.uuid, > - 'profile' => provisioning_arr[2]) > - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] > - system.save > - end > - end > - end > + # FIXME: Presently the wui handles all cobbler system creation. > + # This should be moved out of the wui into Taskomatic. Specifically > + # here, and in the edit_vm methods. > > setVmState(vm, Vm::STATE_STOPPED) > rescue Exception => error > ACK here. this code isn't used anymore and no longer works. Scott From berrange at redhat.com Tue Oct 14 21:17:55 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Tue, 14 Oct 2008 22:17:55 +0100 Subject: [Ovirt-devel] [PATCH node] Add collectd and libvirt-qpid setup to ovirt-install-node In-Reply-To: <1224008913-18798-1-git-send-email-pmyers@redhat.com> References: <1224008913-18798-1-git-send-email-pmyers@redhat.com> Message-ID: <20081014211755.GH5994@redhat.com> On Tue, Oct 14, 2008 at 02:28:33PM -0400, Perry Myers wrote: > This patch adds setup for libvirt-qpid and collectd to > the ovirt-install-node script so that they will both work > in stateful setup. > > Signed-off-by: Ian Main > Signed-off-by: Perry Myers > --- > ovirt-listen-awake/ovirt-install-node | 28 ++++++++++++++++++++++++---- > ovirt-listen-awake/ovirt-uninstall-node | 4 +++- > 2 files changed, 27 insertions(+), 5 deletions(-) > > diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node > index a4a4b6f..95ba4de 100644 > --- a/ovirt-listen-awake/ovirt-install-node > +++ b/ovirt-listen-awake/ovirt-install-node > @@ -28,7 +28,7 @@ backup_file() { > dir=$(dirname "$1") > case $dir in /*);; *) die "unexpected non-absolute dir: $dir";; esac > mkdir -p "$OVIRT_BACKUP_DIR/${dir:1}" > - cp -pf "$1" "$OVIRT_BACKUP_DIR/${dir:1}" > + test -f "$1" && cp -pf "$1" "$OVIRT_BACKUP_DIR/${dir:1}" > } > > add_if_not_exist() { > @@ -103,12 +103,31 @@ elif [ "$1" = "stateful" ]; then > fi > hostname $PHYS_HOST > > + collectd_conf=/etc/collectd.conf > + backup_file $collectd_conf > + if [ -f $collectd_conf.in ]; then > + sed -e "s/@COLLECTD_SERVER@/$MGMT_HOST/" \ > + -e "s/@COLLECTD_PORT@/25826/" $collectd_conf.in \ > + > $collectd_conf > + fi Anyone fancy writing an augeas lens for collectd ? > + > + libvirt_qpid_conf=/etc/sysconfig/libvirt-qpid Doesn't augeas know how to edit all sysconfig files ? Be better to use that than sed/grep IMHO. > + backup_file $libvirt_qpid_conf > + if [ -f $libvirt_qpid_conf ]; then > + if grep "^LIBVIRT_QPID_ARGS=" $libvirt_qpid_conf > /dev/null 2>&1 ; then > + sed -i -e "s/^LIBVIRT_QPID_ARGS=.*/LIBVIRT_QPID_ARGS=\"--broker $MGMT_HOST --port 5672\"/" $libvirt_qpid_conf > + else > + echo "LIBVIRT_QPID_ARGS=\"--broker $MGMT_HOST --port 5672\"" >> $libvirt_qpid_conf > + fi > + fi > + > backup_file /etc/hosts > add_if_not_exist "192.168.50.1 $PHYS_HOST" /etc/hosts > add_if_not_exist "192.168.50.2 $MGMT_HOST" /etc/hosts > > chkconfig ovirt-listen-awake on > chkconfig collectd on > + chkconfig libvirt-qpid on What's our security model for libvirt-qpid / brokers ? Our existing libvirtd setup is kerberos secured, so it'd expect qpid to at least match that before switching. 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 Tue Oct 14 21:22:55 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 17:22:55 -0400 Subject: [Ovirt-devel] [PATCH node] Add collectd and libvirt-qpid setup to ovirt-install-node In-Reply-To: <20081014211755.GH5994@redhat.com> References: <1224008913-18798-1-git-send-email-pmyers@redhat.com> <20081014211755.GH5994@redhat.com> Message-ID: <48F50DAF.8000002@redhat.com> Daniel P. Berrange wrote: > On Tue, Oct 14, 2008 at 02:28:33PM -0400, Perry Myers wrote: >> This patch adds setup for libvirt-qpid and collectd to >> the ovirt-install-node script so that they will both work >> in stateful setup. >> >> Signed-off-by: Ian Main >> Signed-off-by: Perry Myers >> --- >> ovirt-listen-awake/ovirt-install-node | 28 ++++++++++++++++++++++++---- >> ovirt-listen-awake/ovirt-uninstall-node | 4 +++- >> 2 files changed, 27 insertions(+), 5 deletions(-) >> >> diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node >> index a4a4b6f..95ba4de 100644 >> --- a/ovirt-listen-awake/ovirt-install-node >> +++ b/ovirt-listen-awake/ovirt-install-node >> @@ -28,7 +28,7 @@ backup_file() { >> dir=$(dirname "$1") >> case $dir in /*);; *) die "unexpected non-absolute dir: $dir";; esac >> mkdir -p "$OVIRT_BACKUP_DIR/${dir:1}" >> - cp -pf "$1" "$OVIRT_BACKUP_DIR/${dir:1}" >> + test -f "$1" && cp -pf "$1" "$OVIRT_BACKUP_DIR/${dir:1}" >> } >> >> add_if_not_exist() { >> @@ -103,12 +103,31 @@ elif [ "$1" = "stateful" ]; then >> fi >> hostname $PHYS_HOST >> >> + collectd_conf=/etc/collectd.conf >> + backup_file $collectd_conf >> + if [ -f $collectd_conf.in ]; then >> + sed -e "s/@COLLECTD_SERVER@/$MGMT_HOST/" \ >> + -e "s/@COLLECTD_PORT@/25826/" $collectd_conf.in \ >> + > $collectd_conf >> + fi > > Anyone fancy writing an augeas lens for collectd ? I nominate lutter! :) >> + >> + libvirt_qpid_conf=/etc/sysconfig/libvirt-qpid > > Doesn't augeas know how to edit all sysconfig files ? Be > better to use that than sed/grep IMHO. Yes, we should be using that instead. >> + backup_file $libvirt_qpid_conf >> + if [ -f $libvirt_qpid_conf ]; then >> + if grep "^LIBVIRT_QPID_ARGS=" $libvirt_qpid_conf > /dev/null 2>&1 ; then >> + sed -i -e "s/^LIBVIRT_QPID_ARGS=.*/LIBVIRT_QPID_ARGS=\"--broker $MGMT_HOST --port 5672\"/" $libvirt_qpid_conf >> + else >> + echo "LIBVIRT_QPID_ARGS=\"--broker $MGMT_HOST --port 5672\"" >> $libvirt_qpid_conf >> + fi >> + fi >> + >> backup_file /etc/hosts >> add_if_not_exist "192.168.50.1 $PHYS_HOST" /etc/hosts >> add_if_not_exist "192.168.50.2 $MGMT_HOST" /etc/hosts >> >> chkconfig ovirt-listen-awake on >> chkconfig collectd on >> + chkconfig libvirt-qpid on > > What's our security model for libvirt-qpid / brokers ? Our > existing libvirtd setup is kerberos secured, so it'd expect > qpid to at least match that before switching. We're not switching over to qpid until they have integrated with kerberos. It will be a few weeks. qpid is on the node/appliance now just as a toy for people to start playing with. It's not tied into Taskomatic or otherwise used by oVirt at all at this point. Once they've secured qpid upstream, then we can swap out the existing libvirt remote calls for libvirt qpid calls. Perry From sseago at redhat.com Tue Oct 14 21:28:15 2008 From: sseago at redhat.com (Scott Seago) Date: Tue, 14 Oct 2008 21:28:15 +0000 Subject: [Ovirt-devel] [PATCH] fix validation issues for VM destroy Message-ID: <1224019695-14686-1-git-send-email-sseago@redhat.com> Signed-off-by: Scott Seago --- src/app/controllers/vm_controller.rb | 25 +++++++++++++++++-------- src/app/models/vm.rb | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index bc88760..a9ac9a5 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -137,26 +137,35 @@ class VmController < ApplicationController def delete vm_ids_str = params[:vm_ids] vm_ids = vm_ids_str.split(",").collect {|x| x.to_i} - + failure_list = [] + success = false begin Vm.transaction do vms = Vm.find(:all, :conditions => "id in (#{vm_ids.join(', ')})") vms.each do |vm| - vm.destroy + if vm.is_destroyable? + vm.destroy + else + failure_list << vm.description + end end end - render :json => { :object => "vm", :success => true, - :alert => "Virtual Machines were successfully deleted." } + if failure_list.empty? + success = true + alert = "Virtual Machines were successfully deleted." + else + alert = "The following Virtual Machines were not deleted (a VM must be stopped to delete it): " + alert+= failure_list.join(', ') + end rescue - render :json => { :object => "vm", :success => false, - :alert => "Error deleting virtual machines." } + alert = "Error deleting virtual machines." end + render :json => { :object => "vm", :success => success, :alert => alert } end def destroy vm_resource_pool = @vm.vm_resource_pool_id - if ((@vm.state == Vm::STATE_STOPPED and @vm.get_pending_state == Vm::STATE_STOPPED) or - (@vm.state == Vm::STATE_PENDING and @vm.get_pending_state == Vm::STATE_PENDING)) + if (@vm.is_destroyable?) @vm.destroy render :json => { :object => "vm", :success => true, :alert => "Virtual Machine was successfully deleted." } diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index d7beacf..6853157 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -84,6 +84,11 @@ class Vm < ActiveRecord::Base STATE_CREATE_FAILED = "create_failed" STATE_INVALID = "invalid" + DESTROYABLE_STATES = [STATE_PENDING, + STATE_STOPPED, + STATE_CREATE_FAILED, + STATE_INVALID] + RUNNING_STATES = [STATE_RUNNING, STATE_SUSPENDED, STATE_STOPPING, @@ -272,6 +277,24 @@ class Vm < ActiveRecord::Base end end + # whether this VM may be validly deleted. running VMs should not be + # allowed to be deleted. Currently we restrict deletion to VMs that + # are currently stopped, pending (new without any create_vm tasks having + # been run), or create_failed. Also, get_pending_state must equal the + # current state -- so that we won't delete a VM with a current pending task + def is_destroyable? + current_state = state + pending_state = get_pending_state + DESTROYABLE_STATES.include?(current_state) and (current_state == pending_state) + end + + def destroy + if !is_destroyable? + raise "VM must be stopped to delete it" + end + super + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) -- 1.5.5.1 From apevec at redhat.com Tue Oct 14 22:08:52 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 15 Oct 2008 00:08:52 +0200 Subject: [Ovirt-devel] [PATCH] added rake test to appliance autobuild In-Reply-To: <1224017928-26999-2-git-send-email-mmorsi@redhat.com> References: <1224017928-26999-1-git-send-email-mmorsi@redhat.com> <1224017928-26999-2-git-send-email-mmorsi@redhat.com> Message-ID: <48F51874.7050404@redhat.com> Mohammed Morsi wrote: > +$ssh_cmd "cd /usr/share/ovirt-server && export RAILS_ENV=test && rake test" instead "export RAILS_ENV=test && rake test" RAILS_ENV=test rake test doesn't work? From fujimura.toshifumi at np.css.fujitsu.com Wed Oct 15 01:41:36 2008 From: fujimura.toshifumi at np.css.fujitsu.com (Toshifumi Fujimura) Date: Wed, 15 Oct 2008 10:41:36 +0900 Subject: [Ovirt-devel] Question about managing strage server on latest oVirt In-Reply-To: <48F4C562.7030504@redhat.com> References: <48E48EE5.8060508@np.css.fujitsu.com> <48F4C562.7030504@redhat.com> Message-ID: <48F54A50.1060705@np.css.fujitsu.com> Thanks for your reply. I'm looking foward to being out a new release. Thanks, Toshifumi Fujimura. > Toshifumi Fujimura wrote: >> Hello! >> >> I attempt to install a guest OS with WUI follows "Using oVirt". >> But I cannot add Strage Pool to Hardware Pool. >> I can't oparate in the way of stage 6 in "Using >> oVirt"(http://www.ovirt.org/docs/Using_oVirt/Add-Storage.html). >> Because "Using oVirt" is a little bit old. >> >> Would you suggest me a latest instruction of the way to add Strage Pool >> to Hardware Pool. > > We are in the process of putting out a new release (0.94) that will > have some updated docs in the ovirt-docs rpm and should have > integrated online help. That might fix your problem. Try this again > when 0.94 is released and if you're still having problems, please let > us know. > > The release should be out in the next day or two. > > Thanks, > > Perry > > From pmyers at redhat.com Wed Oct 15 02:11:01 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 22:11:01 -0400 Subject: [Ovirt-devel] Issue with ISO provisioning Message-ID: <48F55135.2050100@redhat.com> I tested out end to end ISO provisioning tonight. First off, the good news... I managed to get a VM created and booting Windows XP install ISO. I even got WinXP installed on an iSCSI LUN and booting consistently. So that's a good start. To test I set up an NFS share (/local/isos) on physical.priv.ovirt.org and put the WinXP iso in there. I then used the cobbler image add command to add a Image called WinXP. Then I created a VM using this image to provision. Started the VM and it booted from the ISO and started the installation process. Now here are the issues: Linux installs follow this sort of process: 1. Boot install DVD 2. Install from DVD 3. DVD is no longer needed and can be ejected 3. Reboot from hard disk Windows installs follow this process: 1. Boot install CD 2. Stage 1 install from CD 3. Reboot 4. Boot from hard disk for Stage 2 install using CD in drive 5. Reboot 6. CD is no longer needed and can be ejected 7. Boot from hard disk So Windows installs right now work because the soft reboot in step 3 does not cause the vm to shutdown. If it did that, the restart of the vm would come up without the ISO in the drive (since that is a temporary association) We've talked in the past about changing domains so that on reboot the vm is destroyed and then restarted by taskomatic. This way the boot device can be toggled between reboots. This fixes linux PXE provisioning, which right now has a problem because after OS installation over PXE boot the domain soft reboots and tries to PXE again. So it seems the requirements for Windows and Linux provisioning are somewhat at odds. What we may have to do is make it so taskomatic is aware of the OS type and sets up the semantics of domain rebooting accordingly. Second problem... I tried to provision a second guest using the same ISO image off of the same NFS share. This failed with the following error in taskomatic: > libvir: Storage error : no storage vol with matching name > start_vm > Task action processing failed: Libvirt::RetrieveError: Call to function virStorageVolLookupByName failed > /usr/share/ovirt-server/task-omatic/./utils.rb:124:in `lookup_volume_by_name' > /usr/share/ovirt-server/task-omatic/./utils.rb:124:in `connect_storage_pools' > /usr/share/ovirt-server/task-omatic/./utils.rb:81:in `each' > /usr/share/ovirt-server/task-omatic/./utils.rb:81:in `connect_storage_pools' > /usr/share/ovirt-server/task-omatic/./task_vm.rb:314:in `start_vm' > /usr/share/ovirt-server/task-omatic/taskomatic.rb:99 > /usr/share/ovirt-server/task-omatic/taskomatic.rb:88:in `each' > /usr/share/ovirt-server/task-omatic/taskomatic.rb:88 > /usr/share/ovirt-server/task-omatic/taskomatic.rb:68:in `loop' > /usr/share/ovirt-server/task-omatic/taskomatic.rb:68 Seems that if the NFS share is previously mounted as a storage pool in libvirt, it causes taskomatic to fail. If I manually destroy the storage pool for the NFS mount on the host and undefine it and then try again, the start_vm succeeds and recreates the storage pool. This needs to be fixed... 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 Wed Oct 15 02:12:59 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 22:12:59 -0400 Subject: [Ovirt-devel] [PATCH] fix validation issues for VM destroy In-Reply-To: <1224019695-14686-1-git-send-email-sseago@redhat.com> References: <1224019695-14686-1-git-send-email-sseago@redhat.com> Message-ID: <48F551AB.8070008@redhat.com> Scott Seago wrote: > Signed-off-by: Scott Seago > --- > src/app/controllers/vm_controller.rb | 25 +++++++++++++++++-------- > src/app/models/vm.rb | 23 +++++++++++++++++++++++ > 2 files changed, 40 insertions(+), 8 deletions(-) This seems to work ok, so ACK and I'm going to push this. Perry > diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb > index bc88760..a9ac9a5 100644 > --- a/src/app/controllers/vm_controller.rb > +++ b/src/app/controllers/vm_controller.rb > @@ -137,26 +137,35 @@ class VmController < ApplicationController > def delete > vm_ids_str = params[:vm_ids] > vm_ids = vm_ids_str.split(",").collect {|x| x.to_i} > - > + failure_list = [] > + success = false > begin > Vm.transaction do > vms = Vm.find(:all, :conditions => "id in (#{vm_ids.join(', ')})") > vms.each do |vm| > - vm.destroy > + if vm.is_destroyable? > + vm.destroy > + else > + failure_list << vm.description > + end > end > end > - render :json => { :object => "vm", :success => true, > - :alert => "Virtual Machines were successfully deleted." } > + if failure_list.empty? > + success = true > + alert = "Virtual Machines were successfully deleted." > + else > + alert = "The following Virtual Machines were not deleted (a VM must be stopped to delete it): " > + alert+= failure_list.join(', ') > + end > rescue > - render :json => { :object => "vm", :success => false, > - :alert => "Error deleting virtual machines." } > + alert = "Error deleting virtual machines." > end > + render :json => { :object => "vm", :success => success, :alert => alert } > end > > def destroy > vm_resource_pool = @vm.vm_resource_pool_id > - if ((@vm.state == Vm::STATE_STOPPED and @vm.get_pending_state == Vm::STATE_STOPPED) or > - (@vm.state == Vm::STATE_PENDING and @vm.get_pending_state == Vm::STATE_PENDING)) > + if (@vm.is_destroyable?) > @vm.destroy > render :json => { :object => "vm", :success => true, > :alert => "Virtual Machine was successfully deleted." } > diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb > index d7beacf..6853157 100644 > --- a/src/app/models/vm.rb > +++ b/src/app/models/vm.rb > @@ -84,6 +84,11 @@ class Vm < ActiveRecord::Base > STATE_CREATE_FAILED = "create_failed" > STATE_INVALID = "invalid" > > + DESTROYABLE_STATES = [STATE_PENDING, > + STATE_STOPPED, > + STATE_CREATE_FAILED, > + STATE_INVALID] > + > RUNNING_STATES = [STATE_RUNNING, > STATE_SUSPENDED, > STATE_STOPPING, > @@ -272,6 +277,24 @@ class Vm < ActiveRecord::Base > end > end > > + # whether this VM may be validly deleted. running VMs should not be > + # allowed to be deleted. Currently we restrict deletion to VMs that > + # are currently stopped, pending (new without any create_vm tasks having > + # been run), or create_failed. Also, get_pending_state must equal the > + # current state -- so that we won't delete a VM with a current pending task > + def is_destroyable? > + current_state = state > + pending_state = get_pending_state > + DESTROYABLE_STATES.include?(current_state) and (current_state == pending_state) > + end > + > + def destroy > + if !is_destroyable? > + raise "VM must be stopped to delete it" > + end > + super > + end > + > protected > def validate > resources = vm_resource_pool.max_resources_for_vm(self) -- |=- 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 Oct 15 02:14:50 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 22:14:50 -0400 Subject: [Ovirt-devel] [PATCH] fix validation issues for VM destroy In-Reply-To: <48F551AB.8070008@redhat.com> References: <1224019695-14686-1-git-send-email-sseago@redhat.com> <48F551AB.8070008@redhat.com> Message-ID: <48F5521A.6020109@redhat.com> Perry Myers wrote: > Scott Seago wrote: >> Signed-off-by: Scott Seago >> --- >> src/app/controllers/vm_controller.rb | 25 +++++++++++++++++-------- >> src/app/models/vm.rb | 23 +++++++++++++++++++++++ >> 2 files changed, 40 insertions(+), 8 deletions(-) > > This seems to work ok, so ACK and I'm going to push this. Ugh! My apologies... I didn't realize until after I pushed this that apparently when I ran git am to import Scott's patch it totally messed up the commit message. I think it was because Scott sent me the patch separately as a forward... Sorry about that... Perry\ From pmyers at redhat.com Wed Oct 15 03:08:21 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 23:08:21 -0400 Subject: [Ovirt-devel] [PATCH server] setup symlink on rpm install based on version of installed ovirt-docs Message-ID: <1224040101-27325-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- ovirt-server.spec.in | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 1339b52..71fa2a1 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -31,6 +31,7 @@ Requires: ruby-libvirt >= 0.0.2 Requires: rrdtool-ruby Requires: iscsi-initiator-utils Requires: cyrus-sasl-gssapi +Requires: ovirt-docs Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig Requires(preun): /sbin/service @@ -146,6 +147,11 @@ fi %daemon_chkconfig_post -d ovirt-mongrel-rails %daemon_chkconfig_post -d ovirt-taskomatic +# Set up the symlink for ovirt-docs so that online help works +docs_dir=$(rpm -q --queryformat '/usr/share/doc/%{NAME}-%{VERSION}' ovirt-docs) +%{__rm} /usr/share/ovirt-server/public/help +%{__ln_s} $docs_dir/Using_the_oVirt_Server_Suite_User_Interface/ %{app_root}/public/help + %preun if [ "$1" = 0 ] ; then /sbin/service ovirt-host-browser stop > /dev/null 2>&1 -- 1.5.5.1 From pmyers at redhat.com Wed Oct 15 03:08:48 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 14 Oct 2008 23:08:48 -0400 Subject: [Ovirt-devel] [PATCH recipe] Remove ovirt-docs from recipe since it is now required by ovirt-server instead Message-ID: <1224040128-27354-1-git-send-email-pmyers@redhat.com> Signed-off-by: Perry Myers --- appliances/ovirt/ovirt.pp.in | 4 ---- ovirt-recipe.spec.in | 1 - 2 files changed, 0 insertions(+), 5 deletions(-) diff --git a/appliances/ovirt/ovirt.pp.in b/appliances/ovirt/ovirt.pp.in index da98f2d..66f7a9e 100644 --- a/appliances/ovirt/ovirt.pp.in +++ b/appliances/ovirt/ovirt.pp.in @@ -179,10 +179,6 @@ single_exec {"ovirt_installation": require => [Service["postgresql"],Single_exec["ovirt_appliance_installation"]] } -single_exec {"ovirt_docs_accesible_from_server": - command => "/bin/ln -s /usr/share/doc/ovirt-docs-${appliance_version}/Using_oVirt/ /usr/share/ovirt-server/public/help" -} - # # Cobbler Configuration # diff --git a/ovirt-recipe.spec.in b/ovirt-recipe.spec.in index b98380c..9fef6af 100644 --- a/ovirt-recipe.spec.in +++ b/ovirt-recipe.spec.in @@ -29,7 +29,6 @@ Requires: iscsi-initiator-utils Requires: ovirt-server Requires: ovirt-node-image-pxe Requires: ovirt-release -Requires: ovirt-docs Requires: rhpl Requires: cobbler Requires: rubygem-cobbler -- 1.5.5.1 From jguiditt at redhat.com Wed Oct 15 03:40:13 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Tue, 14 Oct 2008 23:40:13 -0400 Subject: [Ovirt-devel] [PATCH server] setup symlink on rpm install based on version of installed ovirt-docs In-Reply-To: <1224040101-27325-1-git-send-email-pmyers@redhat.com> References: <1224040101-27325-1-git-send-email-pmyers@redhat.com> Message-ID: <1224042013.24332.0.camel@localhost.localdomain> On Tue, 2008-10-14 at 23:08 -0400, Perry Myers wrote: > Signed-off-by: Perry Myers > --- > ovirt-server.spec.in | 6 ++++++ > 1 files changed, 6 insertions(+), 0 deletions(-) > > diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in > index 1339b52..71fa2a1 100644 > --- a/ovirt-server.spec.in > +++ b/ovirt-server.spec.in > @@ -31,6 +31,7 @@ Requires: ruby-libvirt >= 0.0.2 > Requires: rrdtool-ruby > Requires: iscsi-initiator-utils > Requires: cyrus-sasl-gssapi > +Requires: ovirt-docs > Requires(post): /sbin/chkconfig > Requires(preun): /sbin/chkconfig > Requires(preun): /sbin/service > @@ -146,6 +147,11 @@ fi > %daemon_chkconfig_post -d ovirt-mongrel-rails > %daemon_chkconfig_post -d ovirt-taskomatic > > +# Set up the symlink for ovirt-docs so that online help works > +docs_dir=$(rpm -q --queryformat '/usr/share/doc/%{NAME}-%{VERSION}' ovirt-docs) > +%{__rm} /usr/share/ovirt-server/public/help > +%{__ln_s} $docs_dir/Using_the_oVirt_Server_Suite_User_Interface/ %{app_root}/public/help > + > %preun > if [ "$1" = 0 ] ; then > /sbin/service ovirt-host-browser stop > /dev/null 2>&1 ACK, this links properly to the new ovirt-docs dir for me, and I can view the help page by clicking the icon in the web app. -j From jguiditt at redhat.com Wed Oct 15 03:41:13 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Tue, 14 Oct 2008 23:41:13 -0400 Subject: [Ovirt-devel] [PATCH recipe] Remove ovirt-docs from recipe since it is now required by ovirt-server instead In-Reply-To: <1224040128-27354-1-git-send-email-pmyers@redhat.com> References: <1224040128-27354-1-git-send-email-pmyers@redhat.com> Message-ID: <1224042073.24332.2.camel@localhost.localdomain> On Tue, 2008-10-14 at 23:08 -0400, Perry Myers wrote: > Signed-off-by: Perry Myers > --- > appliances/ovirt/ovirt.pp.in | 4 ---- > ovirt-recipe.spec.in | 1 - > 2 files changed, 0 insertions(+), 5 deletions(-) > > diff --git a/appliances/ovirt/ovirt.pp.in b/appliances/ovirt/ovirt.pp.in > index da98f2d..66f7a9e 100644 > --- a/appliances/ovirt/ovirt.pp.in > +++ b/appliances/ovirt/ovirt.pp.in > @@ -179,10 +179,6 @@ single_exec {"ovirt_installation": > require => [Service["postgresql"],Single_exec["ovirt_appliance_installation"]] > } > > -single_exec {"ovirt_docs_accesible_from_server": > - command => "/bin/ln -s /usr/share/doc/ovirt-docs-${appliance_version}/Using_oVirt/ /usr/share/ovirt-server/public/help" > -} > - > # > # Cobbler Configuration > # > diff --git a/ovirt-recipe.spec.in b/ovirt-recipe.spec.in > index b98380c..9fef6af 100644 > --- a/ovirt-recipe.spec.in > +++ b/ovirt-recipe.spec.in > @@ -29,7 +29,6 @@ Requires: iscsi-initiator-utils > Requires: ovirt-server > Requires: ovirt-node-image-pxe > Requires: ovirt-release > -Requires: ovirt-docs > Requires: rhpl > Requires: cobbler > Requires: rubygem-cobbler ACK, since this is just backing out an older version of what was added into the server spec file. -j From xxqonline at hotmail.com Wed Oct 15 08:26:01 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Wed, 15 Oct 2008 16:26:01 +0800 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> Message-ID: Hello folks: I have to provision a redhat to the managed host. Is it possible? Is there doc for me to follow? Thanks, Qiang From clalance at redhat.com Wed Oct 15 11:14:29 2008 From: clalance at redhat.com (Chris Lalancette) Date: Wed, 15 Oct 2008 13:14:29 +0200 Subject: [Ovirt-devel] Re: ovirt-listen-awake In-Reply-To: <20081015100051.GB25659@karma.qumranet.com> References: <20081015100051.GB25659@karma.qumranet.com> Message-ID: <48F5D095.6090209@redhat.com> Dan Kenigsberg wrote: > Hi, > > Please pardon my newbieness; I'm trying to understand what's what in ovirt (did > not get very far yet), and stumbled upon this: No problem, happy to help... > > commit f393d38599283676fbd33c81783fd7164faa4251 > Author: Dan Kenigsberg > Date: Tue Oct 14 12:14:03 2008 +0200 > > ovirt-listen-awake: what's the point of streq? > > diff --git a/ovirt-listen-awake/ovirt-listen-awake.c b/ovirt-listen-awake/ovirt-listen-awake.c > index 7ecd0a7..b00a54d 100644 > --- a/ovirt-listen-awake/ovirt-listen-awake.c > +++ b/ovirt-listen-awake/ovirt-listen-awake.c > @@ -37,14 +37,7 @@ > > static int streq(char *first, char *second) > { > - int first_len, second_len; > - > - first_len = strlen(first); > - second_len = strlen(second); > - > - if (first_len == second_len && strncmp(first, second, first_len) == 0) > - return 1; > - return 0; > + return !strcmp(first, second); > } Heh. Well, the idea was that I wanted to be a little more defensive, so that "IDENTIFY" != "IDENTIFYFOO" (or whatever). I could have sworn I've run into bugs doing that in the past, but a quick test shows that strcmp() properly returns failure in that case. Ah, now I remember. I was taught always to use strncmp(), and just doing strncmp(first, second, strlen(first)) runs into the bug I was trying to avoid. In any case, not a big deal either way; it's not performance critical in the least, but I guess your version is a little easier to understand. If we are going to do it, we can just remove the streq function entirely, since it doesn't really serve any purpose then. By the way, you can send this stuff to ovirt-devel; I've added that as a CC here. -- Chris Lalancette From dpierce at redhat.com Wed Oct 15 11:37:32 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 07:37:32 -0400 Subject: [Ovirt-devel] [PATCH server] Added a link from bonding to a boot type. In-Reply-To: <48F4D3B9.6010802@redhat.com> References: <1223390662-16536-1-git-send-email-dpierce@redhat.com> <48EC9489.3040902@redhat.com> <48F4D3B9.6010802@redhat.com> Message-ID: <48F5D5FC.9030204@redhat.com> Perry Myers wrote: >> Yes, this is reasonable. I like things to be a little more explicit, >> so having >> an explicit "DHCP" (as opposed to implicit) makes things better. >> >> ACK > > Darryl, > > Did this ever get committed? If this has an ACK we should get it into > the 0.94 release. I thought it had but the changes weren't in the repo. I pushed it this morning. -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 333 bytes Desc: not available URL: From dpierce at redhat.com Wed Oct 15 11:46:39 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 07:46:39 -0400 Subject: [Ovirt-devel] Re: Issue with ISO provisioning In-Reply-To: <48F55135.2050100@redhat.com> References: <48F55135.2050100@redhat.com> Message-ID: <48F5D81F.40703@redhat.com> Perry Myers wrote: > I tried to provision a second guest using the same ISO image off of the > same NFS share. This failed with the following error in taskomatic: >> libvir: Storage error : no storage vol with matching name >> start_vm >> Task action processing failed: Libvirt::RetrieveError: Call to >> function virStorageVolLookupByName failed >> /usr/share/ovirt-server/task-omatic/./utils.rb:124:in >> `lookup_volume_by_name' >> /usr/share/ovirt-server/task-omatic/./utils.rb:124:in >> `connect_storage_pools' >> /usr/share/ovirt-server/task-omatic/./utils.rb:81:in `each' >> /usr/share/ovirt-server/task-omatic/./utils.rb:81:in >> `connect_storage_pools' >> /usr/share/ovirt-server/task-omatic/./task_vm.rb:314:in `start_vm' >> /usr/share/ovirt-server/task-omatic/taskomatic.rb:99 >> /usr/share/ovirt-server/task-omatic/taskomatic.rb:88:in `each' >> /usr/share/ovirt-server/task-omatic/taskomatic.rb:88 >> /usr/share/ovirt-server/task-omatic/taskomatic.rb:68:in `loop' >> /usr/share/ovirt-server/task-omatic/taskomatic.rb:68 > > Seems that if the NFS share is previously mounted as a storage pool in > libvirt, it causes taskomatic to fail. If I manually destroy the > storage pool for the NFS mount on the host and undefine it and then try > again, the start_vm succeeds and recreates the storage pool. > > This needs to be fixed... You shouldn't have to create a storage pool at all to provision an ISO from Cobbler. As long as the ISO image is on an NFS exported file system the Image record was given the hostname and export path, a storage pool need never be defined for it in oVirt. But, I'll dig into this problem and see what I find. -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 333 bytes Desc: not available URL: From dpierce at redhat.com Wed Oct 15 11:53:07 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 07:53:07 -0400 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> Message-ID: <48F5D9A3.9040507@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 xxqonline at hotmail.com wrote: > Hello folks: > I have to provision a redhat to the managed host. Is it possible? > Is there doc for me to follow? With our upcoming release of oVirt (0.94) ISO provisioning via Cobbler will be supported. To provision from an ISO file you'll simply: 1. copy the ISO file to an NFS exported file system, 2. create an Image record within the Cobbler server and point it at the ISO file 3. within the oVirt server, create a new VM 4. when setting the OS for this VM you'll see, and select, the Image created in step 2 above 5. attach an additional writable file system 6. boot the VM This will start up a virtual machine with that ISO mounted as a CDROM and boot from it. That will allow you to provision from ISO. HTH. :) - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj12aAACgkQjaT4DmxOfxtDbQCfYybsehyYl9sZ7Et+l6G6glTR zlMAniUw4eWW8IFWacrR8ofnAu5GaNvW =HWWa -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 333 bytes Desc: not available URL: From hbrock at redhat.com Wed Oct 15 13:21:10 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Wed, 15 Oct 2008 09:21:10 -0400 Subject: [Ovirt-devel] Issue with ISO provisioning In-Reply-To: <48F55135.2050100@redhat.com> References: <48F55135.2050100@redhat.com> Message-ID: <20081015132109.GG23882@redhat.com> On Tue, Oct 14, 2008 at 10:11:01PM -0400, Perry Myers wrote: [snip] > > So Windows installs right now work because the soft reboot in step 3 does > not cause the vm to shutdown. If it did that, the restart of the vm > would come up without the ISO in the drive (since that is a temporary > association) > > We've talked in the past about changing domains so that on reboot the vm > is destroyed and then restarted by taskomatic. This way the boot device > can be toggled between reboots. This fixes linux PXE provisioning, which > right now has a problem because after OS installation over PXE boot the > domain soft reboots and tries to PXE again. > > So it seems the requirements for Windows and Linux provisioning are > somewhat at odds. What we may have to do is make it so taskomatic is > aware of the OS type and sets up the semantics of domain rebooting > accordingly. Yes, this is exactly the same song-and-dance we went through with python-virtinst when getting Windows installs to work from virt-manager. What we need to do is figure out a good abstraction for the various possible install modes and either a. allow the user to specify what's desired, or preferably b. figure it out ourselves. What we want is some way to say "OK, the install is finished, now change the VM to boot in the proper permanent configuration." If we always set VMs to halt on reboot and we have a way for taskomatic to detect that a VM has halted in mid-install, we could have the UI pop up a message asking the user if the install finished? It would be difficult to make that clear but it could work, I think... Thoughts? (BTW great news that we can even do this at all) --Hugh From pmyers at redhat.com Wed Oct 15 13:24:59 2008 From: pmyers at redhat.com (Perry Myers) Date: Wed, 15 Oct 2008 09:24:59 -0400 Subject: [Ovirt-devel] Re: Issue with ISO provisioning In-Reply-To: <48F5D81F.40703@redhat.com> References: <48F55135.2050100@redhat.com> <48F5D81F.40703@redhat.com> Message-ID: <48F5EF2B.8090501@redhat.com> Darryl Pierce wrote: > Perry Myers wrote: >> I tried to provision a second guest using the same ISO image off of the >> same NFS share. This failed with the following error in taskomatic: >>> libvir: Storage error : no storage vol with matching name >>> start_vm >>> Task action processing failed: Libvirt::RetrieveError: Call to >>> function virStorageVolLookupByName failed >>> /usr/share/ovirt-server/task-omatic/./utils.rb:124:in >>> `lookup_volume_by_name' >>> /usr/share/ovirt-server/task-omatic/./utils.rb:124:in >>> `connect_storage_pools' >>> /usr/share/ovirt-server/task-omatic/./utils.rb:81:in `each' >>> /usr/share/ovirt-server/task-omatic/./utils.rb:81:in >>> `connect_storage_pools' >>> /usr/share/ovirt-server/task-omatic/./task_vm.rb:314:in `start_vm' >>> /usr/share/ovirt-server/task-omatic/taskomatic.rb:99 >>> /usr/share/ovirt-server/task-omatic/taskomatic.rb:88:in `each' >>> /usr/share/ovirt-server/task-omatic/taskomatic.rb:88 >>> /usr/share/ovirt-server/task-omatic/taskomatic.rb:68:in `loop' >>> /usr/share/ovirt-server/task-omatic/taskomatic.rb:68 >> Seems that if the NFS share is previously mounted as a storage pool in >> libvirt, it causes taskomatic to fail. If I manually destroy the >> storage pool for the NFS mount on the host and undefine it and then try >> again, the start_vm succeeds and recreates the storage pool. >> >> This needs to be fixed... > > You shouldn't have to create a storage pool at all to provision an ISO > from Cobbler. As long as the ISO image is on an NFS exported file system > the Image record was given the hostname and export path, a storage pool > need never be defined for it in oVirt. *I* didn't create a storage pool. Your code did that in Taskomatic. What I'm saying is if you create a vm that uses a cobbler image as provisioning source, your code in taskomatic creates the storage pool on the node so that you can access the iso. If you try to provision another guest on the same node it fails, probably because that storage pool already exists but is not tracked in the database like other storage pools. Perry From berrange at redhat.com Wed Oct 15 13:27:32 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 15 Oct 2008 14:27:32 +0100 Subject: [Ovirt-devel] Issue with ISO provisioning In-Reply-To: <20081015132109.GG23882@redhat.com> References: <48F55135.2050100@redhat.com> <20081015132109.GG23882@redhat.com> Message-ID: <20081015132732.GK25527@redhat.com> On Wed, Oct 15, 2008 at 09:21:10AM -0400, Hugh O. Brock wrote: > On Tue, Oct 14, 2008 at 10:11:01PM -0400, Perry Myers wrote: > > [snip] > > > > > So Windows installs right now work because the soft reboot in step 3 does > > not cause the vm to shutdown. If it did that, the restart of the vm > > would come up without the ISO in the drive (since that is a temporary > > association) > > > > We've talked in the past about changing domains so that on reboot the vm > > is destroyed and then restarted by taskomatic. This way the boot device > > can be toggled between reboots. This fixes linux PXE provisioning, which > > right now has a problem because after OS installation over PXE boot the > > domain soft reboots and tries to PXE again. > > > > So it seems the requirements for Windows and Linux provisioning are > > somewhat at odds. What we may have to do is make it so taskomatic is > > aware of the OS type and sets up the semantics of domain rebooting > > accordingly. > > Yes, this is exactly the same song-and-dance we went through with > python-virtinst when getting Windows installs to work from > virt-manager. What we need to do is figure out a good abstraction for > the various possible install modes and either a. allow the user to > specify what's desired, or preferably b. figure it out ourselves. > > What we want is some way to say "OK, the install is finished, now > change the VM to boot in the proper permanent configuration." If we > always set VMs to halt on reboot and we have a way for taskomatic to > detect that a VM has halted in mid-install, we could have the UI pop > up a message asking the user if the install finished? It would be > difficult to make that clear but it could work, I think... Two ideas - The BIOS boot device is not a single device, but actually a list of devices, so you can do and it'll try 'cdrom' first, falling back to 'hd'. - The installer will try to eject the CDROM media inside the guest, so if we could make sure it stays ejected we wouldn't need to change the XML Alternatively setup boot order and dd 512 zero bytes to the disk before booting the installer so it picks the cdrom first time, and on 2nd boot will find the harddisk bootsector. NB, we didn't do this with virt-install because we didn't want to make changes to their disk by accident, but maybe its more acceptable for oVirts use case where things are more tightly managed 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 hbrock at redhat.com Wed Oct 15 13:56:10 2008 From: hbrock at redhat.com (Hugh O. Brock) Date: Wed, 15 Oct 2008 09:56:10 -0400 Subject: [Ovirt-devel] Issue with ISO provisioning In-Reply-To: <20081015132732.GK25527@redhat.com> References: <48F55135.2050100@redhat.com> <20081015132109.GG23882@redhat.com> <20081015132732.GK25527@redhat.com> Message-ID: <20081015135609.GH23882@redhat.com> On Wed, Oct 15, 2008 at 02:27:32PM +0100, Daniel P. Berrange wrote: > On Wed, Oct 15, 2008 at 09:21:10AM -0400, Hugh O. Brock wrote: > > On Tue, Oct 14, 2008 at 10:11:01PM -0400, Perry Myers wrote: > > > > [snip] > > > > > > > > So Windows installs right now work because the soft reboot in step 3 does > > > not cause the vm to shutdown. If it did that, the restart of the vm > > > would come up without the ISO in the drive (since that is a temporary > > > association) > > > > > > We've talked in the past about changing domains so that on reboot the vm > > > is destroyed and then restarted by taskomatic. This way the boot device > > > can be toggled between reboots. This fixes linux PXE provisioning, which > > > right now has a problem because after OS installation over PXE boot the > > > domain soft reboots and tries to PXE again. > > > > > > So it seems the requirements for Windows and Linux provisioning are > > > somewhat at odds. What we may have to do is make it so taskomatic is > > > aware of the OS type and sets up the semantics of domain rebooting > > > accordingly. > > > > Yes, this is exactly the same song-and-dance we went through with > > python-virtinst when getting Windows installs to work from > > virt-manager. What we need to do is figure out a good abstraction for > > the various possible install modes and either a. allow the user to > > specify what's desired, or preferably b. figure it out ourselves. > > > > What we want is some way to say "OK, the install is finished, now > > change the VM to boot in the proper permanent configuration." If we > > always set VMs to halt on reboot and we have a way for taskomatic to > > detect that a VM has halted in mid-install, we could have the UI pop > > up a message asking the user if the install finished? It would be > > difficult to make that clear but it could work, I think... > > Two ideas > > - The BIOS boot device is not a single device, but actually a list > of devices, so you can do > > > > > and it'll try 'cdrom' first, falling back to 'hd'. > > - The installer will try to eject the CDROM media inside the guest, > so if we could make sure it stays ejected we wouldn't need to > change the XML > > > Alternatively setup boot order > > > > > and dd 512 zero bytes to the disk before booting the installer so it > picks the cdrom first time, and on 2nd boot will find the harddisk > bootsector. > > NB, we didn't do this with virt-install because we didn't want to > make changes to their disk by accident, but maybe its more acceptable > for oVirts use case where things are more tightly managed > > The first idea probably works better for the Windows-multiple-reboot case, since presumably Windows won't eject the install media until it's really done with it. QEMU should deal with "ejecting" a virtual cdrom OK, shouldn't it? --Hugh From xxqonline at hotmail.com Wed Oct 15 14:11:43 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Wed, 15 Oct 2008 22:11:43 +0800 Subject: [Ovirt-devel] Can ovirt talk with Redhat5 In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> Message-ID: Hello guys: I have used the PXE to connect admin and managed host together. Seems it create a ovirt node on the managed host. Now I want to know is it possible to manage the redhat physical machine. The red hat is pre-installed on the managed host. I ask this question, because I saw your architecture picture, that ovirt use libvirt to talk with managed host. Thanks for any feedback. Regards, Qiang From sseago at redhat.com Wed Oct 15 14:21:33 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 15 Oct 2008 14:21:33 +0000 Subject: [Ovirt-devel] [PATCH] initial model work for lvm storage (revised) Message-ID: <1224080493-29248-1-git-send-email-sseago@redhat.com> This includes the model classes w/ migrations for the lvm storage pools and volumes, and some changes required to support Storage Volume tasks. This patch does not include all of the necessary model API methods to support lvm, but it's a starting point -- some changes to clalance's taskomatic bits will be required to support this, and we will need additional model enhancements when the UI work is done, and when the taskomatic back end is finalized. revised to fix a couple controller bugs and to increment the migration version # Signed-off-by: Scott Seago --- src/app/controllers/host_controller.rb | 8 +- src/app/controllers/storage_controller.rb | 35 ++----- src/app/controllers/vm_controller.rb | 34 +++--- src/app/models/host.rb | 2 +- src/app/models/host_task.rb | 11 ++- src/app/models/iscsi_storage_pool.rb | 2 +- .../models/{host_task.rb => lvm_storage_pool.rb} | 30 ++++-- .../{nfs_storage_pool.rb => lvm_storage_volume.rb} | 12 +-- src/app/models/nfs_storage_pool.rb | 2 +- src/app/models/storage_pool.rb | 12 ++- src/app/models/storage_task.rb | 11 ++- src/app/models/storage_volume.rb | 15 +++- .../{host_task.rb => storage_volume_task.rb} | 22 +++- src/app/models/task.rb | 15 ++- src/app/models/vm.rb | 2 +- src/app/models/vm_task.rb | 15 ++- src/db/migrate/025_add_lvm_storage.rb | 106 ++++++++++++++++++++ src/dutils/active_record_env.rb | 2 + 18 files changed, 251 insertions(+), 85 deletions(-) copy src/app/models/{host_task.rb => lvm_storage_pool.rb} (62%) copy src/app/models/{nfs_storage_pool.rb => lvm_storage_volume.rb} (82%) copy src/app/models/{host_task.rb => storage_volume_task.rb} (69%) create mode 100644 src/db/migrate/025_add_lvm_storage.rb diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb index 417d11f..a40d297 100644 --- a/src/app/controllers/host_controller.rb +++ b/src/app/controllers/host_controller.rb @@ -139,10 +139,10 @@ class HostController < ApplicationController def clear_vms begin Host.transaction do - task = HostTask.new({ :user => get_login_user, - :host_id => @host.id, - :action => HostTask::ACTION_CLEAR_VMS, - :state => Task::STATE_QUEUED}) + task = HostTask.new({ :user => get_login_user, + :task_target => @host, + :action => HostTask::ACTION_CLEAR_VMS, + :state => Task::STATE_QUEUED}) task.save! @host.is_disabled = true @host.save! diff --git a/src/app/controllers/storage_controller.rb b/src/app/controllers/storage_controller.rb index bbd7840..fe524f3 100644 --- a/src/app/controllers/storage_controller.rb +++ b/src/app/controllers/storage_controller.rb @@ -111,10 +111,10 @@ class StorageController < ApplicationController end def insert_refresh_task - @task = StorageTask.new({ :user => @user, - :storage_pool_id => @storage_pool.id, - :action => StorageTask::ACTION_REFRESH_POOL, - :state => Task::STATE_QUEUED}) + @task = StorageTask.new({ :user => @user, + :task_target => @storage_pool, + :action => StorageTask::ACTION_REFRESH_POOL, + :state => Task::STATE_QUEUED}) @task.save! end @@ -144,12 +144,14 @@ class StorageController < ApplicationController :location => storage_pool_url(@storage_pool) } end - rescue + rescue => ex # FIXME: need to distinguish pool vs. task save errors (but should mostly be pool) respond_to do |format| format.json { - render :json => { :object => "storage_pool", :success => false, - :errors => @storage_pool.errors.localize_error_messages.to_a } } + json_hash = { :object => "storage_pool", :success => false, + :errors => @storage_pool.errors.localize_error_messages.to_a } + json_hash[:message] = ex.message if json_hash[:errors].empty? + render :json => json_hash } format.xml { render :xml => @storage_pool.errors, :status => :unprocessable_entity } end @@ -181,7 +183,7 @@ class StorageController < ApplicationController @redir_controller = @perm_obj.get_controller authorize_admin @storage_pools = @hardware_pool.storage_volumes - @storage_types = StoragePool::STORAGE_TYPES.keys + @storage_types = StoragePool::STORAGE_TYPE_PICKLIST end def addstorage @@ -226,23 +228,6 @@ class StorageController < ApplicationController end end - def vm_action - if @vm.get_action_list.include?(params[:vm_action]) - @task = VmTask.new({ :user => get_login_user, - :vm_id => params[:id], - :action => params[:vm_action], - :state => Task::STATE_QUEUED}) - if @task.save - flash[:notice] = "#{params[:vm_action]} was successfully queued." - else - flash[:notice] = "Error in inserting task for #{params[:vm_action]}." - end - else - flash[:notice] = "#{params[:vm_action]} is an invalid action." - end - redirect_to :controller => 'vm', :action => 'show', :id => params[:id] - end - def destroy pool = @storage_pool.hardware_pool if @storage_pool.destroy diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index a9ac9a5..585e524 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -49,19 +49,19 @@ class VmController < ApplicationController Vm.transaction do @vm.save! _setup_vm_provision(params) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_CREATE_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_CREATE_VM, + :state => Task::STATE_QUEUED}) @task.save! end start_now = params[:start_now] if (start_now) if @vm.get_action_list.include?(VmTask::ACTION_START_VM) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_START_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_START_VM, + :state => Task::STATE_QUEUED}) @task.save! alert = "VM was successfully created. VM Start action queued." else @@ -105,19 +105,19 @@ class VmController < ApplicationController _setup_vm_provision(params) if (params[:start_now] and @vm.get_action_list.include?(VmTask::ACTION_START_VM) ) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_START_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_START_VM, + :state => Task::STATE_QUEUED}) @task.save! elsif ( params[:restart_now] and @vm.get_action_list.include?(VmTask::ACTION_SHUTDOWN_VM) ) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_SHUTDOWN_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_SHUTDOWN_VM, + :state => Task::STATE_QUEUED}) @task.save! @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, + :task_target => @vm, :action => VmTask::ACTION_START_VM, :state => Task::STATE_QUEUED}) @task.save! diff --git a/src/app/models/host.rb b/src/app/models/host.rb index de5c5ee..546da19 100644 --- a/src/app/models/host.rb +++ b/src/app/models/host.rb @@ -31,7 +31,7 @@ class Host < ActiveRecord::Base def consuming_resources find(:all, :conditions=>{:state=>Vm::RUNNING_STATES}) end - has_many :tasks, :class_name => "HostTask", :dependent => :destroy, :order => "id DESC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end diff --git a/src/app/models/host_task.rb b/src/app/models/host_task.rb index 0aea41b..82298db 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/host_task.rb @@ -22,11 +22,20 @@ class HostTask < Task ACTION_CLEAR_VMS = "clear_vms" def after_initialize - self.hardware_pool = host.hardware_pool if self.host + self.hardware_pool = task_target.hardware_pool if self.task_target_type=="Host" end def task_obj "Host;;;#{self.host.id};;;#{self.host.hostname}" end + def vm + nil + end + def storage_volume + nil + end + def storage_pool + nil + end end diff --git a/src/app/models/iscsi_storage_pool.rb b/src/app/models/iscsi_storage_pool.rb index 1280834..7802a90 100644 --- a/src/app/models/iscsi_storage_pool.rb +++ b/src/app/models/iscsi_storage_pool.rb @@ -19,7 +19,7 @@ class IscsiStoragePool < StoragePool - validates_presence_of :port, :target + validates_presence_of :ip_addr, :port, :target validates_uniqueness_of :ip_addr, :scope => [:port, :target] def label_components diff --git a/src/app/models/host_task.rb b/src/app/models/lvm_storage_pool.rb similarity index 62% copy from src/app/models/host_task.rb copy to src/app/models/lvm_storage_pool.rb index 0aea41b..08b8938 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/lvm_storage_pool.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,16 +17,32 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class HostTask < Task +require 'util/ovirt' + +class LvmStoragePool < StoragePool + + has_many :source_volumes, :class_name => "StorageVolume", + :foreign_key => "lvm_pool_id", + :dependent => :nullify do + def total_size + find(:all).inject(0){ |sum, sv| sum + sv.size } + end + end - ACTION_CLEAR_VMS = "clear_vms" + validates_presence_of :vg_name + validates_uniqueness_of :vg_name - def after_initialize - self.hardware_pool = host.hardware_pool if self.host + def display_name + "#{get_type_label}: #{vg_name}" end - def task_obj - "Host;;;#{self.host.id};;;#{self.host.hostname}" + def size + source_volums.total_size end + def size_in_gb + kb_to_gb(size) + end + + end diff --git a/src/app/models/nfs_storage_pool.rb b/src/app/models/lvm_storage_volume.rb similarity index 82% copy from src/app/models/nfs_storage_pool.rb copy to src/app/models/lvm_storage_volume.rb index 2d05305..7dde2d1 100644 --- a/src/app/models/nfs_storage_pool.rb +++ b/src/app/models/lvm_storage_volume.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,12 +17,8 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class NfsStoragePool < StoragePool - - validates_presence_of :export_path - validates_uniqueness_of :ip_addr, :scope => :export_path - - def label_components - "#{export_path}" +class LvmStorageVolume < StorageVolume + def display_name + "#{get_type_label}: #{storage_pool.vg_name}:#{lv_name}" end end diff --git a/src/app/models/nfs_storage_pool.rb b/src/app/models/nfs_storage_pool.rb index 2d05305..a27944b 100644 --- a/src/app/models/nfs_storage_pool.rb +++ b/src/app/models/nfs_storage_pool.rb @@ -19,7 +19,7 @@ class NfsStoragePool < StoragePool - validates_presence_of :export_path + validates_presence_of :ip_addr, :export_path validates_uniqueness_of :ip_addr, :scope => :export_path def label_components diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb index bc98f8e..7a6f8f0 100644 --- a/src/app/models/storage_pool.rb +++ b/src/app/models/storage_pool.rb @@ -19,7 +19,7 @@ class StoragePool < ActiveRecord::Base belongs_to :hardware_pool - has_many :tasks, :class_name => "StorageTask", :dependent => :destroy, :order => "id DESC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end @@ -33,14 +33,18 @@ class StoragePool < ActiveRecord::Base has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags - validates_presence_of :ip_addr, :hardware_pool_id + + validates_presence_of :hardware_pool_id acts_as_xapian :texts => [ :ip_addr, :target, :export_path, :type ], :terms => [ [ :search_users, 'U', "search_users" ] ] ISCSI = "iSCSI" NFS = "NFS" + LVM = "LVM" STORAGE_TYPES = { ISCSI => "Iscsi", - NFS => "Nfs" } + NFS => "Nfs", + LVM => "Lvm" } + STORAGE_TYPE_PICKLIST = STORAGE_TYPES.keys - [LVM] def self.factory(type, params = nil) case type @@ -48,6 +52,8 @@ class StoragePool < ActiveRecord::Base return IscsiStoragePool.new(params) when NFS return NfsStoragePool.new(params) + when LVM + return LvmStoragePool.new(params) else return nil end diff --git a/src/app/models/storage_task.rb b/src/app/models/storage_task.rb index db604d5..785f0ea 100644 --- a/src/app/models/storage_task.rb +++ b/src/app/models/storage_task.rb @@ -22,10 +22,19 @@ class StorageTask < Task ACTION_REFRESH_POOL = "refresh_pool" def after_initialize - self.hardware_pool = storage_pool.hardware_pool if self.storage_pool + self.hardware_pool = task_target.hardware_pool if self.task_target_type=="StoragePool" end def task_obj "StoragePool;;;#{self.storage_pool.id};;;#{self.storage_pool.display_name}" end + def host + nil + end + def vm + nil + end + def storage_volume + nil + end end diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb index ef9cd6e..378b58f 100644 --- a/src/app/models/storage_volume.rb +++ b/src/app/models/storage_volume.rb @@ -23,12 +23,23 @@ class StorageVolume < ActiveRecord::Base belongs_to :storage_pool has_and_belongs_to_many :vms + belongs_to :lvm_storage_pool, :class_name => "LvmStoragePool", + :foreign_key => "lvm_pool_id" + + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do + def queued + find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) + end + end + def self.factory(type, params = nil) case type - when "iSCSI" + when StoragePool::ISCSI return IscsiStorageVolume.new(params) - when "NFS" + when StoragePool::NFS return NfsStorageVolume.new(params) + when StoragePool::LVM + return LvmStorageVolume.new(params) else return nil end diff --git a/src/app/models/host_task.rb b/src/app/models/storage_volume_task.rb similarity index 69% copy from src/app/models/host_task.rb copy to src/app/models/storage_volume_task.rb index 0aea41b..78f2895 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/storage_volume_task.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,16 +17,26 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class HostTask < Task +class StorageVolumeTask < Task - ACTION_CLEAR_VMS = "clear_vms" + ACTION_CREATE_VOLUME = "create_volume" + ACTION_EDIT_VOLUME = "edit_volume" def after_initialize - self.hardware_pool = host.hardware_pool if self.host + self.hardware_pool = task_target.storage_pool.hardware_pool if self.task_target_type=="StorageVolume" end def task_obj - "Host;;;#{self.host.id};;;#{self.host.hostname}" + "StorageVolume;;;#{self.storage_volume.id};;;#{self.storage_volume.display_name}" + end + end + def host + nil + end + def vm + nil + end + def storage_pool + nil end - end diff --git a/src/app/models/task.rb b/src/app/models/task.rb index efbed64..f231c18 100644 --- a/src/app/models/task.rb +++ b/src/app/models/task.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -20,13 +20,20 @@ class Task < ActiveRecord::Base belongs_to :hardware_pool belongs_to :vm_resource_pool + belongs_to :task_target, :polymorphic => true # moved associations here so that nested set :include directives work # StorageTask association - belongs_to :storage_pool + belongs_to :storage_pool, :class_name => "StoragePool", + :foreign_key => "task_target_id" + # StorageVolumeTask association + belongs_to :storage_volume, :class_name => "StorageVolume", + :foreign_key => "task_target_id" # HostTask association - belongs_to :host + belongs_to :host, :class_name => "Host", + :foreign_key => "task_target_id" # VmTask association - belongs_to :vm + belongs_to :vm, :class_name => "Vm", + :foreign_key => "task_target_id" STATE_QUEUED = "queued" STATE_RUNNING = "running" diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index 6853157..4885b5c 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -22,7 +22,7 @@ require 'util/ovirt' class Vm < ActiveRecord::Base belongs_to :vm_resource_pool belongs_to :host - has_many :tasks, :class_name => "VmTask", :dependent => :destroy, :order => "id ASC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end diff --git a/src/app/models/vm_task.rb b/src/app/models/vm_task.rb index 6251919..d1966dc 100644 --- a/src/app/models/vm_task.rb +++ b/src/app/models/vm_task.rb @@ -107,9 +107,9 @@ class VmTask < Task :popup_action => 'migrate'} } def after_initialize - if self.vm - self.vm_resource_pool = vm.vm_resource_pool - self.hardware_pool = vm.get_hardware_pool + if self.task_target_type=="Vm" + self.vm_resource_pool = task_target.vm_resource_pool + self.hardware_pool = task_target.get_hardware_pool end end @@ -141,4 +141,13 @@ class VmTask < Task def self.label_and_action(action) return [action_label(action), action, action_icon(action)] end + def host + nil + end + def storage_volume + nil + end + def storage_pool + nil + end end diff --git a/src/db/migrate/025_add_lvm_storage.rb b/src/db/migrate/025_add_lvm_storage.rb new file mode 100644 index 0000000..697df4d --- /dev/null +++ b/src/db/migrate/025_add_lvm_storage.rb @@ -0,0 +1,106 @@ +# +# 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 AddLvmStorage < ActiveRecord::Migration + def self.up + #LVM pool does not use ip_addr + + # VG Name + add_column :storage_pools, :vg_name, :string + + # LV name + add_column :storage_volumes, :lv_name, :string + # LV capacity==existing size attr + + # LV + # FIXME: do we want to make these user-determined, or should + # these be defined by the model itself? + add_column :storage_volumes, :lv_owner_perms, :string + add_column :storage_volumes, :lv_group_perms, :string + add_column :storage_volumes, :lv_mode_perms, :string + + # VG pool ID + add_column :storage_volumes, :lvm_pool_id, :integer + execute "alter table storage_volumes add constraint fk_storage_volumes_lvm_pools + foreign key (lvm_pool_id) references storage_pools(id)" + + # use polymorphic tasks association + add_column :tasks, :task_target_id, :integer + add_column :tasks, :task_target_type, :string + begin + Task.transaction do + HostTask.find(:all).each do |task| + task.task_target_type = 'Host' + task.task_target_id = task.host_id + task.save! + end + StorageTask.find(:all).each do |task| + task.task_target_type = 'StoragePool' + task.task_target_id = task.storage_pool_id + task.save! + end + VmTask.find(:all).each do |task| + task.task_target_type = 'Vm' + task.task_target_id = task.vm_id + task.save! + end + end + remove_column :tasks, :vm_id + remove_column :tasks, :storage_pool_id + remove_column :tasks, :host_id + rescue + puts "could not update tasks..." + end + + end + + def self.down + remove_column :storage_pools, :vg_name + + remove_column :storage_volumes, :lv_name + remove_column :storage_volumes, :lv_owner_perms + remove_column :storage_volumes, :lv_group_perms + remove_column :storage_volumes, :lv_mode_perms + remove_column :storage_volumes, :lvm_pool_id + + add_column :tasks, :vm_id, :integer + add_column :tasks, :storage_pool_id, :integer + add_column :tasks, :host_id, :integer + begin + Task.transaction do + HostTask.find(:all).each do |task| + task.host_id = task.task_target_id + task.save! + end + StorageTask.find(:all).each do |task| + task.storage_pool_id = task.task_target_id + task.save! + end + VmTask.find(:all).each do |task| + task.vm_id = task.task_target_id + task.save! + end + end + remove_column :tasks, :task_target_id + remove_column :tasks, :task_target_type + rescue + puts "could not update tasks..." + end + end +end diff --git a/src/dutils/active_record_env.rb b/src/dutils/active_record_env.rb index 3903c87..26caac2 100644 --- a/src/dutils/active_record_env.rb +++ b/src/dutils/active_record_env.rb @@ -80,10 +80,12 @@ require 'models/vm_task.rb' require 'models/storage_pool.rb' require 'models/iscsi_storage_pool.rb' require 'models/nfs_storage_pool.rb' +require 'models/lvm_storage_pool.rb' require 'models/storage_volume.rb' require 'models/iscsi_storage_volume.rb' require 'models/nfs_storage_volume.rb' +require 'models/lvm_storage_volume.rb' require 'models/smart_pool.rb' require 'models/smart_pool_tag.rb' -- 1.5.5.1 From sseago at redhat.com Wed Oct 15 15:07:09 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 15 Oct 2008 15:07:09 +0000 Subject: [Ovirt-devel] [PATCH] model apis for the storage tree view for the new VM form. Message-ID: <1224083229-29533-1-git-send-email-sseago@redhat.com> HardwarePool.storage_tree returns a list of storage pools in the HW pool. For each pool, the :children hash element has a list of storage volumes, filtered to include only available volumes (and ones attached to the current VM for edit operations). For each storage volume, if it supports LVM, then it has a :children element with LVM volumes defined on it. The hash includes fields showing which are available for use (:available) and which support creating new child volumes (currently iSCSI volumes, with NFS Pools soon to be added) Signed-off-by: Scott Seago --- src/app/models/hardware_pool.rb | 6 +++++ src/app/models/iscsi_storage_volume.rb | 6 +++++ src/app/models/storage_pool.rb | 27 +++++++++++++++++++++++++ src/app/models/storage_volume.rb | 34 ++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 0 deletions(-) diff --git a/src/app/models/hardware_pool.rb b/src/app/models/hardware_pool.rb index 160f663..6780329 100644 --- a/src/app/models/hardware_pool.rb +++ b/src/app/models/hardware_pool.rb @@ -101,4 +101,10 @@ class HardwarePool < Pool return {:total => total, :labels => labels} end + def storage_tree(vm_to_include=nil) + storage_pools.find(:all, + :conditions => "type != 'LvmStoragePool'").collect do |pool| + pool.storage_tree_element(vm_to_include) + end + end end diff --git a/src/app/models/iscsi_storage_volume.rb b/src/app/models/iscsi_storage_volume.rb index b254cd8..00d7db2 100644 --- a/src/app/models/iscsi_storage_volume.rb +++ b/src/app/models/iscsi_storage_volume.rb @@ -21,4 +21,10 @@ class IscsiStorageVolume < StorageVolume def label_components "#{storage_pool[:target]}:#{lun}" end + + #FIXME: should also take available free space into account + def supports_lvm_subdivision + return true + end + end diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb index 7a6f8f0..aed2902 100644 --- a/src/app/models/storage_pool.rb +++ b/src/app/models/storage_pool.rb @@ -28,6 +28,9 @@ class StoragePool < ActiveRecord::Base def total_size_in_gb find(:all).inject(0){ |sum, sv| sum + sv.size_in_gb } end + def full_vm_list + find(:all).inject([]){ |list, sv| list + sv.vms } + end end has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy @@ -73,4 +76,28 @@ class StoragePool < ActiveRecord::Base def search_users hardware_pool.search_users end + + def user_subdividable + false + end + + def storage_tree_element(vm_to_include=nil) + return_hash = { :id => id, + :type => self[:type], + :text => display_name, + :name => display_name, + :available => false, + :create_volume => user_subdividable, + :selected => false} + condition = "vms.id is null" + if (vm_to_include and vm_to_include.id) + condition +=" or vms.id=#{vm_to_include.id}" + end + return_hash[:children] = storage_volumes.find(:all, + :include => :vms, + :conditions => condition).collect do |volume| + volume.storage_tree_element(vm_to_include) + end + return_hash + end end diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb index 378b58f..3bd5da0 100644 --- a/src/app/models/storage_volume.rb +++ b/src/app/models/storage_volume.rb @@ -70,4 +70,38 @@ class StorageVolume < ActiveRecord::Base return [] end end + + def supports_lvm_subdivision + return false + end + + def storage_tree_element(vm_to_include=nil) + vm_ids = vms.collect {|vm| vm.id} + return_hash = { :id => id, + :type => self[:type], + :text => display_name, + :name => display_name, + :available => ((vm_ids.empty?) or + (vm_to_include and vm_to_include.id and + vm_ids.include?(vm_to_include.id))), + :create_volume => supports_lvm_subdivision, + :selected => (!vm_ids.empty? and vm_to_include and vm_to_include.id and + (vm_ids.include?(vm_to_include.id)))} + if lvm_storage_pool + if return_hash[:available] + return_hash[:available] = lvm_storage_pool.storage_volumes.full_vm_list.empty? + end + condition = "vms.id is null" + if (vm_to_include and vm_to_include.id) + condition +=" or vms.id=#{vm_to_include.id}" + end + return_hash[:children] = lvm_storage_pool.storage_volumes.find(:all, + :include => :vms, + :conditions => condition).collect do |volume| + volume.storage_tree_element(vm_to_include) + end + end + return_hash + end + end -- 1.5.5.1 From clalance at redhat.com Wed Oct 15 15:07:17 2008 From: clalance at redhat.com (Chris Lalancette) Date: Wed, 15 Oct 2008 17:07:17 +0200 Subject: [Ovirt-devel] Re: ovirt-listen-awake In-Reply-To: <20081015145415.GA5890@karma.qumranet.com> References: <20081015100051.GB25659@karma.qumranet.com> <48F5D095.6090209@redhat.com> <20081015145415.GA5890@karma.qumranet.com> Message-ID: <48F60725.2070209@redhat.com> Dan Kenigsberg wrote: >>> @@ -37,14 +37,7 @@ >>> >>> static int streq(char *first, char *second) >>> { >>> - int first_len, second_len; >>> - >>> - first_len = strlen(first); >>> - second_len = strlen(second); >>> - >>> - if (first_len == second_len && strncmp(first, second, first_len) == 0) >>> - return 1; >>> - return 0; >>> + return !strcmp(first, second); >>> } >> Heh. Well, the idea was that I wanted to be a little more defensive, so that >> "IDENTIFY" != "IDENTIFYFOO" (or whatever). I could have sworn I've run into >> bugs doing that in the past, but a quick test shows that strcmp() properly >> returns failure in that case. >> >> Ah, now I remember. I was taught always to use strncmp(), and just doing >> strncmp(first, second, strlen(first)) runs into the bug I was trying to avoid. > > My point is that your > first_len = strlen(first);second_len = strlen(second);strncmp(first, second, first_len) > runs into that bug, too... Ah, but it doesn't, because of the first_len == second_len guard. -- Chris Lalancette From dpierce at redhat.com Wed Oct 15 15:35:40 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 11:35:40 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... Message-ID: <48F60DCC.4000606@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 http://www.ovirt.org/page/VM_Network_Configuration Please take a few minutes to review this list and provide feedback. The network configuration page is the only part missing now since the backend support and processing code is out for review. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj2DcAACgkQjaT4DmxOfxuQqQCfRw9pq32yW4ZjgCVLh2K13O1w OlsAoKma437gsvUKUUA2hIVMlvRmjtet =ru1j -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From pmyers at redhat.com Wed Oct 15 15:47:38 2008 From: pmyers at redhat.com (Perry Myers) Date: Wed, 15 Oct 2008 11:47:38 -0400 Subject: [Ovirt-devel] Can ovirt talk with Redhat5 In-Reply-To: References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> Message-ID: <48F6109A.5010804@redhat.com> xxqonline at hotmail.com wrote: > Hello guys: > I have used the PXE to connect admin and managed host together. > Seems it create a ovirt node on the managed host. > > Now I want to know is it possible to manage the redhat physical machine. > The red hat is pre-installed on the managed host. > > I ask this question, because I saw your architecture picture, that > ovirt use libvirt to talk with managed host. > > Thanks for any feedback. There are two issues here: 1. We need a way to install the ovirt-node software onto an existing physical host and get the bootstrapping/configuration process working properly. 2. We need to be able to support hypervisors other than KVM like Xen. #1 has already had some work done on it by Chris Lalancette with his patches for 'managing VMs on the appliance host'. But these patches have some things in them that are specific to that environment. They would need to be generalized to allow management of Fedora KVM hosts. #2 needs some patches to remove KVM specific stuff from Taskomatic and just end to end testing on Xen hosts. Neither #1 or #2 are very difficult and they are both on our roadmap. They're just not the top priority items at the current time. We would gladly accept patches for these things though, so if you have the means submit some patches to the list :) Perry From dpierce at redhat.com Wed Oct 15 15:48:23 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 11:48:23 -0400 Subject: [Ovirt-devel] Standalone operations wiki page Message-ID: <48F610C7.4080209@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 http://www.ovirt.org/page/Node_Standalone_Operation This is to capture designs and requirements for working with a node not controlled by an oVirt server. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj2EMAACgkQjaT4DmxOfxugWACeL7K4UOSBBxHlYdXdI6Uc4b0D 7X0AnArogvVXybGi8g7Ef+hB2346wl+c =fbYt -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From danken at qumranet.com Wed Oct 15 14:54:16 2008 From: danken at qumranet.com (Dan Kenigsberg) Date: Wed, 15 Oct 2008 16:54:16 +0200 Subject: [Ovirt-devel] Re: ovirt-listen-awake In-Reply-To: <48F5D095.6090209@redhat.com> References: <20081015100051.GB25659@karma.qumranet.com> <48F5D095.6090209@redhat.com> Message-ID: <20081015145415.GA5890@karma.qumranet.com> On Wed, Oct 15, 2008 at 01:14:29PM +0200, Chris Lalancette wrote: > Dan Kenigsberg wrote: > > Hi, > > > > Please pardon my newbieness; I'm trying to understand what's what in ovirt (did > > not get very far yet), and stumbled upon this: > > No problem, happy to help... Thanks, I will bother you again :-) > > > > > commit f393d38599283676fbd33c81783fd7164faa4251 > > Author: Dan Kenigsberg > > Date: Tue Oct 14 12:14:03 2008 +0200 > > > > ovirt-listen-awake: what's the point of streq? > > > > diff --git a/ovirt-listen-awake/ovirt-listen-awake.c b/ovirt-listen-awake/ovirt-listen-awake.c > > index 7ecd0a7..b00a54d 100644 > > --- a/ovirt-listen-awake/ovirt-listen-awake.c > > +++ b/ovirt-listen-awake/ovirt-listen-awake.c > > @@ -37,14 +37,7 @@ > > > > static int streq(char *first, char *second) > > { > > - int first_len, second_len; > > - > > - first_len = strlen(first); > > - second_len = strlen(second); > > - > > - if (first_len == second_len && strncmp(first, second, first_len) == 0) > > - return 1; > > - return 0; > > + return !strcmp(first, second); > > } > > Heh. Well, the idea was that I wanted to be a little more defensive, so that > "IDENTIFY" != "IDENTIFYFOO" (or whatever). I could have sworn I've run into > bugs doing that in the past, but a quick test shows that strcmp() properly > returns failure in that case. > > Ah, now I remember. I was taught always to use strncmp(), and just doing > strncmp(first, second, strlen(first)) runs into the bug I was trying to avoid. My point is that your first_len = strlen(first);second_len = strlen(second);strncmp(first, second, first_len) runs into that bug, too... > In any case, not a big deal either way; it's not performance critical in the > least, but I guess your version is a little easier to understand. If we are > going to do it, we can just remove the streq function entirely, since it doesn't > really serve any purpose then. > > By the way, you can send this stuff to ovirt-devel; I've added that as a CC here. I was trying to keep my general disorientation lowkey, but hey, we're one happy company now... Dan. From apevec at redhat.com Wed Oct 15 16:03:50 2008 From: apevec at redhat.com (Alan Pevec) Date: Wed, 15 Oct 2008 18:03:50 +0200 Subject: [Ovirt-devel] [PATCH] add flexchart Flash movie blob Message-ID: <1224086630-5890-1-git-send-email-apevec@redhat.com> source is under src/flexchart, but build-requirement (flexsdk) is not RPMized yet see src/flexchart/README.txt if you want to compile yourself --- ovirt-server.spec.in | 4 ++++ src/flexchart/flexchart.swf | Bin 0 -> 165701 bytes 2 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 src/flexchart/flexchart.swf diff --git a/ovirt-server.spec.in b/ovirt-server.spec.in index 1339b52..4719657 100644 --- a/ovirt-server.spec.in +++ b/ovirt-server.spec.in @@ -85,6 +85,10 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/host-status.log # copy over all of the src directory... %{__cp} -a %{pbuild}/src/* %{buildroot}%{app_root} +# move Flash movie to the public folder +%{__install} -d -m0755 %{buildroot}%{app_root}/public/swfs +%{__mv} %{buildroot}%{app_root}/flexchart/flexchart.swf %{buildroot}%{app_root}/public/swfs + # move configs to /etc, keeping symlinks for Rails %{__mv} %{buildroot}%{app_root}/config/database.yml %{buildroot}%{_sysconfdir}/%{name} %{__mv} %{buildroot}%{app_root}/config/ldap.yml %{buildroot}%{_sysconfdir}/%{name} diff --git a/src/flexchart/flexchart.swf b/src/flexchart/flexchart.swf new file mode 100644 index 0000000000000000000000000000000000000000..5079f5039132f7ac82f2149edc087a64a7741c0f GIT binary patch literal 165701 zcmV)$K#spdS5pbwA_V|=+QhvHcvMB!H-4*b-CKP-ou#uA7Na<$jtVZLGiF>Skf0gE zGD%RJ8Q-2xx|6i&bjQ9OAkI7QgdLH62U!%v1rbz~MG#OBML}GU#T9YGUEEjrepUDO zN_6J?p7;O!<8w|`ojSFhI(6z)-D(`Eup^3+b*G{fQ>DV)O;MD8(>asJMqtUjqzI{iG7}00Mg?+-2roQK2a>*rq&%2=S1s9wPj&se9P}Do} z+>qJ(jLT&O6 at IfJ5@?GC!l6=;_ST2nqnDj=hFz<#Azo=)dn72U^fmPL2mP)7P}J;u zexLJEk*}ewF&t_2MlbiawFLtWUQu1&k>{Gt;f9tG-r at dp8-rf6`O>}#Mo}gjhz9+a zm;1u?{?Y+K|H#q{OUo1GCAY;Qn0<+kUY;~euPEK8A>7)xEfV&%H=s|As7e-2E+VRE zYp)Lm%w~V&^7c?mC_KVyB0&{A4H3UL8cyYi(L{dG8)|CzHu*2F93=DNY1v0_)PMQl zhG^;ezdfh)g7eP%z10mty|izdv~3C|@N%Uh=cszA(w~lBbk|>;DrQ&*sAR5Fk$?ZS z`p^$OFyrqyJz{JP&E81V(>l`A>J6dgi0P^AFr)s~fmTx4nt$X{qJUp%jCfo9=PNl% zALT6N1pcy#Q>Wk1FV%e4rAN7MRA*;SXJ=ju=W#*pWSyNq^3G8RN6{0be&i=ksC$l&VJ9}x&X11sLvuE1 zid*)+bHE?1VdVUA;R5k@=n6$SjJiK9zfk_Yb%hnac*WOe{%zXhUmdj5A6{Yc-(#n@ zUt!hroK4?&#Xs^_U-{-uV{Q83E7~TK3%751;ff|19=T#~hN2Iyzro)SRRf`Drc}Z{ zzJS>l^mb%cSfQ1#*G40OP!k&-4*0b4NW|OWw78r6;Z|)(7{z(Na5(7qhIs2p=v&0^ ztm at w!2>Pl6CQ7$O0#U!SI_&lNBUPcsuu+3bp&CKIRvX2vWS~%Ny0WTL=Bu!3K)9jZ zv{U`VSfGKBKVr8Z^^c4?>wpHxbyJwq>o~JMsJ*q`A2I48-jFF)Se{kd+tA=Q%|Lx1 z7>IThl_#f$M8a+UNHpL#9Ru1!4MHVdRYR-#hg;jiA*^lIjxoEeYW%~+V%I7~;H(R@ z`Xe%GU{F?;A(pR|JuuvE`eigTWnhBYjCKV5V*Dbs=@;bnudS7Y3V%b;8e$|+RbOj z3H{nbzMxcvriiz#InZF{)PzCv`{LT-sHv^CdR2)D^&x8j=D46LIaT9tg3d)cvQq2i zRsGtd(QqhTpX~W`SB0Yf2s#`X?zgnZDRfef%*?2=hA31w!-`r{7;D0sZ)*}l{-|?k zO?8d`#&*9M<>+x0gzdP}9+NXsm7_Y`;6=1T$e;~I6#WSXP?MuyAmsDb2mLyXS*Xch zVy~&Plf|t|>+5F$)R!A)i!Z&5PAr%2~b^83VXcm@`8IHs%bNJ9T3HKmxa?5ci&Xsfra0>cmrk*WzPtbBL?#tUmC5{}BX zpBWVDRTsW87_RpQSqS6OMg)A(=B!q~*KCjYeOLJdP0djQg+e~`*zYujK3;NVB;eC! zbeI)fml-Q at ttemQ?b=eJcqAMQiq)Fg;Duc(H?7`hHpwp3g{8vl&9Yg&%^MM`2IB80 zmbsY|Zu4Tz^z$~fG(`kOXjXYcwA~x54cz3{yur3+ud6u_t;4#Ks;9RKYdXvdu4AoU zvxTEN(E%eGv-L~}pGCsqXr^7(k{20CI(TCsh*f7A1H;1uek{JWZpkW|q9?!U(yf9U zbFp-(`nHbj8xS at m_{dGc6BYO>udqfDBZQ7fW$ca~SB?}GI}jZl z8JIFP-9zDMps_>R|NhbxRE1&+Sem5So~{;VhEQp{IZa5y%&=5gE>xEkT7PT*;2vvE za?o*U%Y+{Lv2*4~E=b33S7Z??;QQGG7Z%mPuo;z=W at Awd5MEVmlx-2ewVX2{*LcD- zhQy+Ia{u>Wf4~&SN_k>yE6Z;S#u7(SyoQ952-PU*QiM`h3q4&f`b>{)S}a+3lC6Zr zSd*a0MeRxLiYvK%jwj7fL8?%iDqSoYO1WYNp8t8RVvL+;1a at Xle)<4UWVlf>NQ?4pAOHSy}He2 at H(zGgJ z*@egwGwt^I8~qVTd63xc#gykKmyB8bTx((nCh1yMC{{wQiVSPlY>x(lrY0$l*o+l+ z)tB;URw!JT)RX*(ze%VnOtiI5gyl&YM(PIDAZrl at JI|6;io(0mxrjGehcl8UtJ}{= zx;)wogN`7o^wHf9rCo}`7yJ(d--!|RH>Q1Yr)Om$Zo<-#WBc+z&9 zlCA`2?QFf`m3ZQ at 3V1;FV7qFge>fP1g|N0*XF^j;AejEb`+#7?u!H_+TxP=P^+#dY z!)#CL#+fp!3E!e1RV`xHMb1`@l(ervv>jU(F|XV#PV^||99Uw=_M|F`+mpWRauWW8 zRm6xJ>&)14msX at _LTaM5n6GSivcv}gR?1b28Lmp|q?zSOsVBdyv1dh7`f&Pxa*91I z#gpptgyd>WqGp=OrCrGx~# z(Gk#12tVPBxMMC$ylXj0os~5jt+AT21EGdsyAQi-wYMYO9(BTGTw}RYnUc7St#PLFfM&|<36f(nX z4v(;%!#tmTO2nzKGGy)US%I)T)5V+v>6mcr2*MI%BTUZCMLMm17*27<@<)|482aCisb3b3x-F)8VW;LBepz$sKKAt-qw~*%dM&nh at M${kUYlqNUW at r zGYy#|OtQ;NxOcvSswDM^ujQWc#n at GDw|H*)(L38Z9mdHy=?ulOqUjmVXpCSwD+1Ee zdLteARo425*%~>L6kYC$p<8>Kxa5n?+z^~LFc$-Y-lp8)u-hUpv2OEXBwI`55^9Tt z#fii3i`@{0Bc?0HDBPIrv|EK9$#)KAX_-!OcCzlI;z#BL3GRn=+m#*5vybrIVUr$;x4es_SK3_jWMTbxZxK(Vt(?_yVkU&m?LOi z)sn{NY>x!(D+wlgsUj`wOKYuXir~^OYjQ==I8EL|IOX|GUi~?Qt0C5?pjmetW-rT2 zN^^$>gdY;P&66Svwq%4AFp}(MZdkcEWyRZMwC at oIjqhzu)OBVszIJ1 z!?NuSHDJhnq4?-?+q z4YgukGm@!d+|^=S88K$)SgznrJ-OzIj;8tfZXAED5kL=QCA`{7WQl6+E`!mP+}(nh zf10wj=Xhk1gd`lEj=_zMaBrOT;RyIe(cBcLR=Vxok~|1ZO+}(ZP9Y at QQhMwwrz~rm ziET{G2RD~B2Yh~A^b{vLQAV_sExUtSVrtr!YNfJ0sS20cvpQj8<+Ma#TZNyU*M&Ep zlP*1{Cq5gxXSnd2oF1ujqDMKNG?h;#*r%(sMZyzRM{G8VzM+b`NJf> zE<3JhmLTOY(j6N?OVf}5g9j02*KRKU; zwsth6ozqNzTzrNl6zFM&C!rhJVlGqk2TpUKwY at bvttw5HcEy?&hbY~u(U~}D8gaQh zJR at yAw4^Bb6JAbN_xa+Ir`$Bd4FRZ{FXa;474t_0e-_$FN{UTMgl2wMmG+1=*;S^i zD#>3fOm^my+_(m@;fa05*?>JTBD1>1Hp27|H)c*C)a=KULtSK^nHLgg(Nvnl6dMs@ zIc8dUN0XhC9UEe0W23nB%dDyj+5Ql!DToU)nVJ!Em~#_MnddJSP7WvqvA1>_gZE~?2)`50J~u7fr`BVbC)B!u*vm}U%-+icQwChLMM zT at +(YQ$VNHhPYaEM?2d5wf>-A$Wh*`6{qW at X`fv3Vp^M&cY47554sUBg$|xXcL&UC z1Vtv~dADb*x-R)$Qfq866j?6ZNeeJDnaM2Zk_Qc^NhG$zyk4 zE<4#X#?xhPLBfV4b*OuCe-a7iCu&2cY?MkZKbbHdxe|9A8faN)9!SzhE*~L=; z?zc98d>o2oUR^k$rv^?tkq8WpnPpoq+aU5rE6hshTX{4Z$+!HtR7KB}JcI3;a$+N^ z at i$7(!XBX2eO(?-%!1UDlYM at UZ|_~^=Eo$Fa`%)t0L8xiiw_r;QAI?0gat|G^u!I4 zLuk0Ls}6aHM1i~@GSj>)Zr?%a0aM(46$_TG&iBH$RLRpahRp4R3w&as%i42A(bNNd zZt6gVE}olqppL8U?=LJnnL8_zLP|W9u#et}g;JFA5+SktF8B}HODmGsMUm29x=s-c z*{MA>dnSF*g0&hH4oP=+L^#q?-qzOh7eAI5mNzia+m`EVZ*5B(v)FMhz9O@|tt}jh zrd4jbJV{pw!WFYF_0rDnrUPS=U!5Gnb`M3&9ix=RCgJeL~D`SNF*7Ibuz^>cjAITJ6Ea zdgq8$&aG!Dz8)!pNPDZ}!kWbK-YS~km7i^hkD1c=Sl>0}%ZpgV>51n$ty>u}A$jpB zO3>o>boL7$r at s}W6`vh_;&dLY77uHF>4QnkWs at IFY|qXRGa=cWQE$CXbVO~3&MiOH z4M^h85WeR%s61d at +r%#wZQ4o42TPrG%S|crhde`TPq!GvK50AKg^4#vWl3+4G`Wd! zoT~g@)33?>jlqrZa{INW24AebEz>GtkFz+bu9OUSN=mHeU#C!nz4-0#FDg5UOIEm>kfBCZ7kt at U98CP>)h zhNw7Ci2I!E*nOXUMemXqhjv1 at e2&aeduyC#y1nU7=a{EG64X6Xo~2!mBB>+J`i`jI zx~9lZ3=Z4r$d1$hjRYi<<`I*a+nRV`7Q4NT#JcD!NNkBoFVV!uV>uBCA1pt?m>?D< zZK%oS-(?xaT9{ModqTq6C+GA?PKZJL?)>x0Vz1a-Z4voUsv67R)crTMdxNH{!HWYf zBr0|S-MYJX4EII?UUb1>7cj+Rh5!^d)HJx!i7ne75=+Kp;`OjQ{*VWrjr6N^;X;Q) zS&|Xc>=A5cmeq~e+5NPi?`iVczpJZmYp;!@-dGnUzQCkB)ytM!874*^6#b at CI1<+5 z6)2>|nf!@5R$5+E3-`}T+NI?~QCQtJzb3Ie<@1D~xMJ at J<@OY}hAC9bn22&orX)W_ z0qw7hr~I5vu=BI3Dyl0zbyw9?)?PKZy22gwn$eW6mExpcd+h+}CraL`L3Nci*OXVg zVR%zG&HTh^Gx33(UsG9ATQztPzp&4F;y=wJzLPP5@&uB%C>0PACOXv6kuhj+ou}N> ze{l8Sn!+w0_LznB;&29~X-hme9$HhK;c4>@&Y?Afj at VQ>XtPH?7XfVJKf)S6)|X_(uk?2|`8O{c9 at A>maIl z#W at XWk1tU95b)@hQgLxnST*QMZr_EvtXxrVOMXbW?G%>P>UVzmkRjDo{ZoaRmt^^4@^qoDp_PJVU{zg)yg5czYap6!bvIr(EA<1X zHUC3u249JFQ|n5;qh?q~*Sg~aiCs}!_uMHYS5*=vGxKu8^{_^RSg9iSUyCEaY%tjJP`CphQru-(l!m?1{sB;_sp2}1z`4V%%<`ag zt7?Es8h&=zdFfTuR at S*wYpC@{i at T~o)GSm#{*hFZHE7m+$JD_QyKB-=^UxvsQ2E)! z5z_!i?BhjtNGNRL<7^SkPwdlmzm6esx2qfM{?K4F&?*+0y?0q`IdalNzCp2y3Z?ag zVpa_-uer`s4eK+s{K`s~MXss565}qm5|zX1U^^=-;!L$UNf|;wwRW-E{xwxY>YR-c z9Ll5wvn={GayzsXH`Y1^@?8HFON@#^{})BS at NB&{KPBX$U6oNhCIrvD-AP7 za!QW!V4x}Fs2e=QQ(ZZrPV8Zr7 zf8lgWD$_|nX6gP5mGnsd6;oN-ZIC9NNoK;?id}cf%a#Fk&HmC9&#SZnE_*c6E>1(G zBfPMESSNm8>4;{3s5A=hUS8!hc!Iz*mfOR>Jd6fjHSO_oN(rS`&Y zPP5lcdG)KK at 7Vn^#k9w@#IW2H_&%ghhVXpl(SL9w>1#-vFMkn-t;EzDd^HGM?WRBe z4x at X_b1&;33B!ez8f9N~X0(N|*>((oMwr|eX!4ub)5X27=@18 at CiJ>j%GuBrmdFtPvf8&3CqBQP?+$xviu>O=_0m^Wpemq;>M5Fhl4&n0L=_E zI7PflMjf$BF-_iU81;~A2-6*ONINI4DBz9>LYDX;kbDRrjbzW1rI`LJzaGhVSY=iH zBHJTT?= zU}e9MRV7~=+gUwRvXXN|d#Slqb=D6vVyc+h+tf@^+ST9N=9T`2_&t-bk^V>>Rwl3T zn=P<`-BN3>h|lDo!8n%=WO5^`sk-v2I$B+4h^+-e|Mh>K&!*4mfre%wiArA}YQ2mshs!8Uj`eK9Blg8eIM~nrwpHlCt{bsG z-6!~ZdZKpOYii{$*JAt<<$Id_@|bErptZg+d#sYNf-LcvBZTdemBf`d zP54)JA^Z#v)?UC8Sdrba^y$kJr<3}y`065_eA)Ap#Dxy}{Vf&AGgROS20|_AH5qb| zh|}Cv*rLtSR&PgXI27zCt at oFjZT<%7J(kvYl(w7L^Gkz)dN?m3U#U2}nx$}e?5~LD zl!n65()w_;xsRi2uyq72Ir#}q?ChDJ`~)XP?B2ygoLF9lykEBc9-qGfzUR=IDkcP( zpZGm|pSYkicYAp)WL%gavB`54rUo4zqS71TxSN8<*&jU;Q`Bs)GZG(+nP(EQ4w#Xqm-3~8%v$g z!BRO#rAGXMhvT3$w7R^8^&4DWks*+$s;<1cs=sap!<@NZ=-6nS$~NmYGOj>22dJI4)vZI2tJFl*r!>mjfKwXpgiJ`74y=5R3q4R%*DVkvY) zw?3)nBzo6hUXL>INmzwzkriXM=#JR$q%!2~vUQiH)d#UmSbebFuQiD!!Qg3j=w=`| zT-1c+py6mS+^>xg_fvX at KNt*;&|AHcFr3+tcX)@2WvsWgM_^81HoQUJ*xt}=2D}XW zjhBlzrsx at K=JohBS at VvxCwzoYr-)_<_smL*3S3g5I^Q_W< zwLClg(Ng)VB5~E|_w~`Oo9^zOuAf!Ne|IJJ at 9~(9Sd5mHImKM{7eb5 at 5BV#-{;%wY zFeGq2Yw!nxG&+(-JF=x^w4NCVyJ-JwG4}80a}rM<21tsdB@}LGfjgfY`)#g87e-nB zpti$G!~1LN7!nxi51OQu7$lQ8iA%>hv~pw{{o6n2-~LIOaM;2|*x+|y4VV~4^prHA zN=-WRALsq^tg_O7od3^0q}3JsSBrdQefv6M`(B2LO}jokQW|{mA*QZU{F=h__Yjv3 zwp6{cPFcS-N18m1Wh8x+#nLdXn;czya}GwG7?FLoqr*~^2ZL at i>CK7!w9MNq^(bub z@*0sty<5XkKexC{asz20L3dlc0=s^4E)ASZyyPZwNfx>>ctptdD>|6Cx at 7RKL(Cs- z4lr-TThHqKp(ZTnNRUOFyNn|4%``fKHiT${NgHmWJ}>pPP at kXrgS06^{UO?J(#8O7 zjL>F3g%&oq(|}0>QF?=y-q1!{!n7qqLqQsOO+SsIxd?5K(6$yD at X_`b+U}+Gep=r_P4EP$KT7?68XQi8QQGLGfe0NQpuOy-)Z0jJ^3oPB#mZ?6)8+sTglMZtTRUh=3vIcHwjk0(%@MT4Pup*z z(I{;Lb32WCY1B_!z>L0mX={i!1=W^tiwdz&vxEAE)7IfM6ryH?HgwRYdfFPGp_^!H zRJ{@P)Q4#_L`OE$o7&Zo8CK2K8vtH_2?)`8lh%8wx1M?<)N4|22d#%_o2d^S57WjQ z)W%4JHlkj122BsA%~2XaCxf)5l?J^uSWkl?8Vu9cCfeFeTZ6PUOhaB8^3jl=h9K$= z8g8Xw2+*WAw$n(6Mn+H*BMnp2q-K;x12hU_fqt5FxSx&)(~f}J(B at ZtW<6Rr)h2U< z+Kf1?xeAk|hJ2Q zr%CUzEolATq}CrbJ at U@3Jmiag${jw?W+}zt))Y?F at POIw4VE^<{_%`v?{L^}>~2X8 zzOdo)4zeM32~~P#BI=7 at 3rXKUkrDfBd{&=4I8mYa at 5`-s<7%HieOxds@!yA+#GU}e zERH{x)90L&4 at IT+2NCoY#|`VNh}C5g?ehy_sBN#8KIu5}za|Iajg+p5S za8 at 7Z@Bn;bxfNwxgJ`K0=k;E32jX^pBHd~+ttoSw&-X+jO0Ssjy13+sTJ-A at G1?L_+LDc|WH&3^Xviw|vyvYK zalBU4uVkiHGFvN|ua&IVN;YUEJGEaE2VsN~jdT-E1j))NF4D#mGJ#Aag*TH)#-9mvozl%+uzlWRTO1Gc>XQRgNdWUI>EooFYbU zC5wFPxkTI}`IIHg>f^@fR)~F%(ShfCnr7Yp99VUujd#}~2T5=u9H0~oHUALO7A%DML z6dEnk4~TLN4~p~{l0&lAg4Xm9l8zk2rjtPP!>GefOd~BftRp1H7>nMyiEBJD*MrR6 zGPgatAr>7+hHoUqJ=4hvACh#8z^a+<|yHdk{vb#pC`Yx!I+$YD+ON3t4DLLWJI>-6XY-76xOOfleYm49cbvx^ z=W}ua*DvJkx13$XUBBb5-}B5eZv26>OF6rYyDsPK3a=#K@$^ zZz<3B at ch5?d at s+h=lKmh-^cU)Jin3WH}U*to*&@(H}L!xo*(4-tvo-(^TRy9jpyIU zS;R0^UeL}9hVz0EykI0R=->r6 at q&Nwg8$|P|KtV#!wde!3pxoe7zG#&7y}p!7zY>+ zm;jgvxEU}Ba0_5EUX3kRPc9RFayC%z%0OQz#PDhoX#aigc^B_7cC&Xcp>3M zw-OG0f=Rsp_;pqyoc~^_aa;cxDQ#Y0c!yD9IJ?v`vEs_GL#oT0EPzvYYEpLLijLphH>&Q zPX5e`)**ob7q3Sqbgp*I)e zdGU5|?m+ZfUi<{8I}r`=;wM3U3dvna{)HDmjpQ?gm+V0jaxZ=sS$hG`A$=V$dLBU= zCw^Y^0*EgnwGT`$A>0qRh8G<`@*uJfA$%F(D}Yy#)xybZ2tu5^j+{3 at J&f>8kd7dH z3tQ-1nN%FnnDuo{3i zoNFjQ^8vtv;9E=iS&jTR>kwT}jf+qlqUUU+yyOw|W)tP-J_>*yox26$Rsa^mx!Vvv zM!D;86x)F^J1IZ!DTKQKPg8#WGf3|S>;XKB^j^@P13ZuP3kY8X>_hw|g!?JK;2_`- z;AOxofL8&p0bU2Z0XPhJ6L19Z7T|5bJAiiq?}_q+UvL!R`+yGs9|Ar?-#$kDpHY6% z=YTH&Ujn`YVC7x(HNtNIkmAMPg716EfBysEN6LTq6T;(w6Od at 9YTN;neaUE*UouAJ zWn%&30OJ7 at Km%{tM1(g3CV_SfU@`#pl}$l774+$V8HmqBh?%DfrnK{yxT zJcRQB3lLw3 at Kyk3uxybE^+cJ)NG|~_MgDCFZwD*`EJykdzzXnTd}Viva)7&$Zs!EU zF1rWGm7*Bp_aa<{^nD0dBU}SEh@$L%#2)}Wh&04iwiY2oRrV109tNyK-g>|W#5W>* z1h5IP8Sp4*TL4=T--dD!Z`osrKMvYg%V0|`+pF at +o&!7&cmePtU?1QmzSl!hRiKA0p9-$1YZKa!Wh0*dBwMY?*QKeegOOk_z7?va01ZD zcm>8(F$&>mz!<<-z&OBozy!cVz|DY3fLj2Q0aE}|nenx%^UCRrSI%I3z)Z#+vlzc> z4qz@|9%%C!Ckp|$0u})lGwxghSjvnubbj^ij9RJ1-!=i z-(F{&zJc&C(r+R>g77VbZv)-|ybE{_a8yhc;eUG{@ecqW0zLwK4EiSsKSlT%;27ec zBm5HZHRGOd5PpmBJI1g3o*6wU_x{AV_c-JAof at wn1%R&Ck488KFjnJioW^|<;nCi# zaX(g8<1HF*#1_y9^WHca at hO0*fN22CZX;%?aXR8ClQl!*IkQl9j>ZFX5zf_pxjc#;fJ-{hVXU(+HF~ea5>-(#8)7MnQXZe;ay1I zjS%g&+=Ec`_g>VoN;CR9cxW|}=wfIM;LkjCzsB1h)VQ%$Ua?F5MZ6oharCy*?`1G zz$1W7fX#qM0b5YvR-KO~+mLunL>@_zH1 z!1I6?0Q&$h0ru;BEIEkCArX05pjVK374RD1b-){d!+CpsTbKGS*0F%Uind;$1U=M%_R0(~tIcE}0j8xi|fpzm}} zzX#V3fFBY63E^?T2|y?BZeadk#3li7{2_nGG$J|`FbyzWkO4CRI2GZ at 4|5Kfh4gH| z9Kc+TKFvb}CoZx;ByJUGF*jbK1|q9P>VD)uh$X}g;~|bVA4Yl|V7(t<6PF=iOwBE>Je__GvfzwN}uP*ew<|Q zCD~t*?4u<6F_L|NWFIEkJ4yBnBzp87rx)d#14gCD&5gJ#tCgR2`6 zJ_6X};B+&>M*&*^TLIeuj{zPB{F9Ua;U&#{9)abZN47hFb_fKen at 64i$4&>t^`t;g zA+<;|I#td1mb$sV8|giOX90Tw&pEp18qcWI*rV#kL6v47R?*>9=Y at 2bW=N1m~o z=XK9BF6YKkF#U>k&o}O2`KWk?VLVUrjm1PH_Bo6hM!qo{u+IS^I97H_Eyjmr~R< z+vwf{YfM!8lwo|&N{lP0 at kfT_PI00$y7x53Y1CK(q0P~H8S}JWJO?_E1G|$0i<9$b zp7V^sbH3BNpTfmpGWMX2X90VS?xmP}T0$QC1v((mL4ghl^s+#&2=uBzw+Zx`K(7n*hCtXkfQ;_Fp&`97f_Dw0 zllL~>GeqJ&!}v_^Z5-phyPuBy(+zCy#?`+vp3r_}z~?insj*kXkhf$QFK8vkOPXT` zm;WyiqA>Y?KuP|iAgd}uRgFc76_bboQIW05w2q31Lrts0kaa6WBUVIliiG(8v}D*V zXw+sC-~wdY at hp*&82p4~vT_V#C1+_3;^zlT>RGmE1_~el!5ycDB7W45u`im6XslR^Xr* zuUY4e{J(QI|@h0$PMdd0_V^;ihZ5l7lD z#Zas$Ml2%s3L&zlQ<%e1daB}J85vHe3xBc+2g%4t>1u9r$GTkCN|%c`uD|gz$%U0) zHU(}$aCgZfO;uL%de at SeEq!s=ikvQYqMR19wLfog|07$Q^yktXy^C at Q%gxpC2YI^e zKcbzIke#JWr^j{dSGF<{?bqpgm)u`#L8sW=)6O{Ke__LjO{rxm)3=di#xu`KWB5&1 z#Q5aKjm^pPu2~UjVzn+LdA4mwlea~dy2N0%3dEnNDMgs^znEGu=(kXBH6tTMxrug; zTo&Lw3W zW4%G{&0&KB*)&dVqus1WADN7ohGq8~X2vl)W7wuy!Opwb(C+&f3VV&FbH7Xa8SpWao%jLbH<9nlPaltShhSa-(ezm)t!l+0 zQkflJR7&a3G)8~6K>$rOtR+rj^dvvHIDMpl_z%V?>+N!&7bmLXV%Cd_*hPv%7mHqK zgk;9 at OkVW5%?H&D^#$Vp^40bqGL(IN0iKhIj9da}x0%95s8D&D0wQf#HM6*8fl z=^I9R+S=(I$q7AfPA)r1ms0#{!&*xAa{48;i;)HrWl;55=)OuXCknX&>S7?}&iEr$ z^m3vY{fOe~4=Y_kl*~#Teg+UF`%0o at T}70PDx&Z|5hW)}(bTJ}2mYpT5LJo?Q>A1G zRZbnGD&77}lrw9n(zBK-r_>SUtf5rtbq!TcyOt6b_ zy{h#1FQT0OSE>|TPnGT-qV)bdRZ6{zM(eL`xVV8Tzx7e&kA9+D+enqZO{!AaOchUn zDno9dN?oI>oY$f%e+a6|Wvx{Cdx$F6hgD@@8&$5lkto+hs4^g+Dt|Vqa(!W^yyCx}8h{eums4+%lq0M&f=l z1^6K{75F|f4R{NgPEJ)8lNpF_Br}Q7nOUfC_-wRh&H-wl3x at e*9ucnCd?L*60x(P> z3qhYrZbiWjWD)Q#vKaJ#EFq^UyU9`zo+7t_a0|H|wag&PKzWQTM+J8fWd#yXk~_uv zR_+4Rh`WI%l6ydYgsemjd&s?ruOX{|*OB|s)B>`ah@<}+YdH5Kcz`Gm$_}kX{v7fU z>iE~gDDcm9h)p5uiO64!*8h70*fxr~6(w8QB+G0Dz3ov%;R$R3=WS%G9O5>Vy_q~F zy5 at 4-1*VaYqxQLEJMe6>1Ds396QWo~#!hs!YtpJPUj;*$ezIdCnTg^GF0{qg><##8#6R!G0^51448k2#=DNz_%Y=Ie^A) zIA}F|2s!tVm(jUVRpc=6GV&%kXOSa_Zzpeo zKlC;T^T<0O+)dsEew at 4qyqp}hy0#fS;dN+aDtTX;fDa`85ab&_0-8WRM&?-Z3F70( zr&giQP{T at c40s#)92w)u7epN9zC?T)`3m%<_f0rC^1lghFmgFc3gqVf)8G$okYG4wPgIF<^TDdRwSfQ+ZA za?=Dvw~~oePWO160y}P|LJKBQp^UdsaZ5Xyo at R6>1D_*w2L$sxc^LQwlC5au>O+K0 zK{fly6cl}lOa(qbrlIUXGMy5mC%TUcG%|tQMbDlA&PjA8cutU+h6d^^1rl<9O4 at LakWl;v~@;$!Jj;4vtrDhugt zh|fWtsv8Tjv@%R#@5-T^$3t^l4%??mo+(5bSN-UU1jeNdJ8^lsqE z^d2D%x)OL9N at 0dkiYhbcy~v$SR{@U(C)PA5RG9?|RYuYKKwpB^RpmIj50u690YZ#Z zNcBqUnk)2=ZWs23?iTBjz9 at 8*-Us{ueOuThdK|NOFI|l}I1Sp1ML=H>R*Fs+>x4co z>;PQ at DW9rrpil{?ahkG;=H8E9ZlMpL)G034gGf9{*NW*yK}Q5T at dSQ^cj)!HYK}}dW-%BiKCS40mFOrS at i8}vX_2L>2n}_ zM4w0PFHlq})b|IPu at 7awru$ImH}oaoV{||GzNH6%zo!S0`#I=T`HUU{{*t~7`gfqK z$`_!h%2)IiG|@-tRFzk$)99;=QORrcRAsDs2{F!7CaBr3Q{mvg0bE4X!&dO7CGsOO z>n(}jmiQei+ at 5#Q$j$0|z_+MJCH;Ni-zy)0GEx0d at _dB&B=u}Dj1_9p$4E_AKcVHy z?KlE{if|di&p at 549s{1Oeh$1q{Q`rSs(y+5rO5vZv^k)u$~5(B&~5|m8_;H|-y*eG z6+16R=Iq8mFqtT6Mfil}+jd;78SopggSJ4E%^X36yo}Ex;Sp zalj9PQ&l#Dhbj+&Usbk%M^)B?M^!eelhNW zK*{auOhMArSt8KY+2Fr~%u#o#*Uweu1kFRiPt^IUxRF}`oXgdPB5|W85p{@egE9!k#07*c$zlR_=fb^mIAn*t3THxdA zLt at U=hf(eYbsggGtLxMb-&*3wrmOx)tRQsN1Z* zJSO@=)D at t;t3EErzFj>{d0O28-u>zm;&NRPM&qcuQ?RP)lc?$k^(j%kvJ1stR-d-o zcm|0h>TWBs2gRRPp9SwRbuaMe>T_~z&x5{4eF2nC_9EhY)qO%KF(_4eQGH3&nxXC& zx~m?rB(nw8zN#JsX%ssI{4u(Q%}9M&PQ at O?pHp9vVt5|$-Ri60`AB^c at n_Z7tkG>n zXTUSrF%`?> zbM-Ek^##%^*q5kx0s9K^W$bI<A!^P}7P;O`6gR%rzRhEKIl||rDm0Q^l z$i0pIC{`u=3HVNS9JMQ}89Ra0eXNtg`(mSjSFzE+E7>{7eu$}K7#18G3%r(%1Ac&= zPmDj3 at oXc@oFI;L7>k(xn^EXdHVJqey9Iok*kr`FuqnWgv8j at C8sd+$>A>6B4Df7b zGl3srvq0I(W{WSRPcb@&sme|^2RTo&9w5ES+;frG%jSV?ADa(+h%ErqZZ;SAIkph^ zMRqHA_OM0BeVHvr{CV(DaTA7r-y?+3rCJj-rJ`~X`9{0dtxI`BQ~ zeg{&YvlYOfvO9r~vb(^0gx!sr-eUKF@*P_V{4To}_%pT&_#<{7a^GO95&w#<0se&D z5Bvp6smj~z0mQ#y4+4M5)&hUc9s+(JrBvlG>W3<^he7$4tph&B)&qacHUNJJNn;md zj{twbHUYoGdO|TavlEOyih at 6}Eg&9eTiG^dT&axK)W?t>t33`pPTLMVM%%%NQKd}K z*b_|bBs(R368IuWTv2v0)u_#W+j z;Dy=;z;m<@kvmiC1<6z^_i5Q5G2t?QjI4*XPh_K?BEDMtObXBDq=n01m(=tzXkE9w8_9bwJDnH>r|OB4WwtZ=^8QWl)W080pd(eN!Ev1 zn$U;Y8iX}ed0tcJpu~&XT;P4$Jm43!`6BOHvOqhal`hn%a!^~SsmdX33M*GW*2KIC zkuK7tbzcm^E7~MveWVHXAh}(88sY3Y%SnT?IDQfFUk)ZRD&3QRgP<=>oCMy^z|6xRDA>RSbZbf zdPKv#$}M9v%1zTBwG!*WJVoCEJWAgxN%}U6`!TRh*B=)&s&BVwJHU5BdqU7ueWyix z(yHhwE7$;zG5RiR3b&y3(fZTClk{gS?%haC1PLb^eGlT}^=CD?TkipBoW2`)g1!S8 zo%%Z9$@;l2<9cP5PWD>$_a(-EDRXu992n>8&uc<6UqF1G{-TWUgB1R*EY+0 at U~FE} zR_N+}8>LvvrB&q_$mEuq2u~H zzQGIiN!hPMU7)|s%0XQf ziuJPonN&xi0*CZJAnOL at RlS$cpQHL0DF2pz7_RbR{Y#;*`d6U5u77PQ_c3AZ^>2{z zss1fU-(lwvdZt5vlpi(aCnP at Bk89FCoDjOHKOr^?y;Fy&*GK8HmeD$P4t)&phx%BH zG!BVRbg}B+(Z}m@@;lM++xi3)d`;geb_ac;_$G#VhcBg1vdZ0pa_{RuVx&Rk3tgRT z6%`ikEa-u_pq`>*gso(%{*|61#`V1%S0 at +a`c|K&OHp80-|1o`$Mxwt&O@@lKgj-^ z&}X2H(R?O2|DcFY{RAGIob*{%7es%4)I~=|@rzG4!pb<#EQ>c=7nbEoR6d>yOE`hg z(N8ySR3>pMlDBY+?RTde5oM}Hn`YDI>clXWd0e at a7*S;*&zxs9HXqF_<_mytf0 ziM8CZ0*QM$)L&V}??n7Qeiz~o at VimbgNRe*etr)qYk;Y;ny=LF)r}5i9hXPI^?Vg- zze(A|u`13t{-JE)WVL>(vX!p^{lAsRxxDAjQ_fNR{C-`hxi0kqUC~Zc9 at O2eX|1lS zxtZ!iy27AG59 at BVahtutTtG_`u>hA2L zewe(#+$CR-H<-Kl7&*+``5%*an7jLzWRsp} z93gKr_bK0!mzcZ9SL7|`?)44Xtmmq)k|WIB^K0^`uBcBlWxbxuUL)@^xBCOKMRz;j zA_tkf;1lv8%guP3ywBWuACV83JL^3X>-km;vxw=3n7iBOtheie-Y63&mcl z#%NDKlpoTaDDWGcl$GO at J&E{yIH0KVY$ciP1Rbr$>l&YkPEnP+RBe}R^J(zi9j|N#2y560^h#b`QLI(R0>&78n z&tN#DzpU#HHc=<9=z1QTq?1?0SD48UEP1VWkx}L+P!xrnhi_O%@VO{URX2Tfk zI%imaN1%iZ>+LOF at 5vVG9u+nH_vJf7gjM*WuTHm+0eA(;^+l?=D?=K+he|-Npue0#}?cs1v!) zin7$3x#B2*Vb0SZ)^Fh$Z?-y#=V}k>lew-UF@@)0lvBB`73HYYxMF}dokLNc*2xT> z=UA`LVxqP%;33voj!{z8E9*hu58s6EY}zG_+UT9M82rcMz3Gek6{7r z(+}v%E4r9WOw?Ge&p`(n+}pY66`P`wdFT*(F){M_=o5o~yFj!*Q6o3=c-;#{kpsFY z!bFj|qA04E^6yz at aP3y~X%3GuE`oGt at x_qmT)qVIoXzKR$5O5n%+zftOx^13sEexV zGA``7x*Ro8UA+V47Vs75B+XOrgv6)@e#c#0 at ZXJsFn*{-S5|@_hjCQBjITmX at Tbs$ z<$N`Y!W-sbP#)&-VQ=I#7F~n79^sF2CM%IiFDSERyaYDR~iL?oX(WW6Ldaj8e%fpyZ8H zVV#+hKUpQ0Fs0y5m7L9#!c{66qcWxFewCCkrT8(GYz0%dF^tXzx%(8Ll1Yr5$CMuP z82J#%o_8~H3}G*vypJP11!w7Vm{N*!@A*tQbr&PE8BHPyE%aJ_2QzJc@ z@~e9_bt{s;ej4b^9YAMn(8NQavmVvR4 at mw74tyV`oV`aQkD(vue50w;HKv?9AE at ta zpgu6p4f{2ZTy(!qE at aBZkHGMP`nxSU z*@Z at a|2Pb<#>gel>*8ra*%+=((V23^C*ZmC4@|l2V?_S&Bhcj&Ik}iAf1E6S1IU!} z>74wIDg9P(^-`u(EC;C)ZoSg~9!_S0eZWJUT*j0uH*m5NW zCu at +tdaNej$&xGK8Bs#0^SSe`A6Pm}X+G;p;i1 at Bxv{M3(qW|8xKjLzfG{Sq&K6l2 z$eKt>htcLNkj{za`C at q!kf%0#LGr7__&=<@36vZ~k{}u$het$KNvbLxoPq)+rK3Yg z(A_`}-95ncWUK4VcFv#w#IUoQ`6II+C#DDH^B1I@`TKXLbd>H3-ADpe=|(~qx&`RI z52%QwGC at K&0&ximAta%rzL|MMWLBv}_skBcGCbVP&CT7--ObF+Jt9A8?3-N6t)rT= zmM2&62hQlu3Z7iWqt804cycvQcyd*>C$rRQ4p}maVn!Zq>}#*)Q+t&Iy*=%fd};-g zq~AiI_Wqog86j6hB*}f&dy at 4mGeP>!TxV|lEggyYdzD8ztYeLR|F&1I?{njR)+U0E0?6kzFyzI zlX}%?z54#-1=iKj9#)m|iOtQ1YABWc*gs5Rud+4zTAi=c at hHi9UE&8 zAyccA-ocSCIf@)9nJ7d1UT05B&UKcYh|h_v at pqEFi&fk%;lqAc&ur3Dn{^Ct(KB21 z)HdB-E@!sz%yvC0E$>x9&Af|EtpG_i`627>e8}Ln9N%`r+fHVG$SO(z8|7-mwob0( zjj5G9v4+LgFv%LEXLjhdyvcsZtz0s-7goEcE6`&`H!4ATT^Ur#8lk7Y<%vx!zKKbR zoqBwyE&=+1B9FaGkFq6GD>_P2VvTN%2BP(PYm8oB4b!1w at 74pj-Fjw^J`mkCM9{uH zvsbsrv-k`q10Vc@!XhQV2R1@{_4Yo!J#mnD+po6+ni2=}_yJuidjM+L#1c6ceYQHs znA5~;_;*L+zJ^g+7Ie7 z)o;;dY+waUh49SS{VE~H^FjW%G5-rPyumoL4>9{4)&Md8gfTdTy4VX?tP2AD-$9l^ zGQ7QrMM9S3AlY>WVMYHR9nyad=^wIkzpTIOXJ2OJQK;5sme{}=6W#5Vy0w;7cb7%5 z0fW^WnBrV!019~jTTsdwe0xb&A|>UGeO-8Mcqx}W!(8VKj;@yjumuP-ydMtqd&>u)_$pXCdJtCJb(J zZBOrngieq%n|VKYxUthJVjtcb)%QNC5v>z0`3WQ0&4b`?`q}%$AlO$l2pS86U|~22 z_AvkjG@}?!8g~Jj6|B5Hy2ZclC2_ddVe0`;WtC4Uu0#P%r$8hfnm`m;CG{jcFB&t5rZ?vCCIg+zSj}p4MXJP>;h9 z&wJHyl*PO>kLB=ci`+I9gr?3rnwvc+`!yy*%Z-EeAS&fRxWckjpbnU_VgRg_nY`ZE zsSEZC68uumgH|sE=B?^i_Ov8HhO*xx5!rwS)!r|!s(=iM{c>83?Uz~R08AUz#PL=A z(kjweK{TpFBNjwS+mtNe?!2GaV(Fx|~XlsTZ`$%Um)xhFn&GN$>SeS!E0Znp9m+NwgZi!cz7kh;XIWV8C0P!^Y6wJXW z`M_!jh0kW~$|5DNacqL^Us?N$nhy3(R&h6swzp9d%WPJh zw^?#Jg?A{UCe{3*Kjo_gqyzh^faInCLrpKyj`pZeR%V21Af;!=d(S7KA%AW~Wt> zMD586#v)(VXr3?ldA?BjFMttS+iK9m_O5A%sV|9EEsLWl(9TO^d4g6yJf|o;w(gK(E`PS1Hw!7*vC3cT?jHP*F}bs3h2jliJxA}1yghAo>E{9m=o=9 znSGYoUom?TLk2D(1q8UaSSBe-*r60lG?kUmLZ$JIaw!b^dYbk6;sn*X$Q#9=4Qy)z!aEt at -pTuhvCg~xElWoO36bhI>wdtMAlI4 zEX*dIfse8{n`B`&`A~+z{8FwAtEu|HX|>#KZcdJ7o--Pz`Yx+*!b@~X*96EShWbu7S~zK?$pNhA!+t`r9>U_1_D0dND;DC# zH!Z|Viee5>%$r1L1Yj|r0cFrR z_G;1sRun at mWFgc-CZMhm6bP|Eg3{O*%6^N;`Ew8oVJPCbf|bM-i--s7hdqrUqrk&W z4UysDrSdFsLQmWdO7Uk}4sbAG0Wwz8 za{emw>EPY-0*zSP%YNe%q7 at UuX)5C`4h?vCI2Hs!yqKU6(>4vY%Fe5so3pJ*y0;}s zfUoG4 at B)czh{gU2DeRE5H?oTE(7qd)ixY4JkxisIj4h{z-^c*U%Z!!d(^V-6U=IAj zE0&AT_-qx&)>4YzmP2>FDwt#?Rnk4q6sPViUx17D;mq3*2x}N zR#A|1Hj?a9IlQ*Awle3ru}^h|Gbw~t!`jL6Ss0WXL+$KC9Pe*ajcyY&V(}6J?6d^Z z-(iX&41SfhpXoIm#8=(ToB?Uo{!EX34r?M8J^7C)e|)BAsjp(6L3y9)!1EbT^E&+} zNwPNUnDGlVrg4&DzSIXIan?3SD7y!_>q9eG<{KT}-S{~jZIHd<=XDvMLMjm=Nc>w} zg{g$rwoSx84w;`D8#5r6)M_c{#+D|=6DT;tVCuwCODTh at ML~N>e5ZT%92WmhmpvRZ z`Lg!d4d3Zm`y|Dp60%S4{6%wf;v$P*WLV&4S|9I#0*mBy)LvZ{cL`z($upZ6nR#lv zkx9jzeVPR;=xoIWtiWi)%_%`?w530ED8}~vDnHqVBYl#_RxEB4*3+P zXH$tY at z1)`xnh zzl1gcsE>T8L#?xaq{rUWSY at X>TEZPJhAaEAL`?YDPdHMX;M>WQBjhS)gdEH*M}?-9 z&-zMH)>lKG0!fY35V7+81o;nfW?`W0D2S*q74)jntz(EKt)mQW=>7tC5Sa5*7XOq< ziDnSRnnC&rIdL09^X~ru0R at SuWlewTtOyf>;K;waR2Teoq z>oN!jnG13`NetQeAX at 9UlQHJ+kcqm5I^+pL!g8o1;-ur*Kai4H(Zg2~{fHvP95wn= zRDYG~uNJDggN9`9aPMHh-mYu7TTr|;a6)ViT&0;~C*wfaC7PSNq?1k#dISaP(S=#tpMVmp`w$f4 at aXwY~J6+%EOhMXy=m{cr> zdfw~B?!_6#<@Wb1eqEPa74thNW(R48Fg0%Des~8 at ZRfW#RwPnhEX7*PxdY`g)VxFj z>4icGganw*kf-3`#oqyGY9tdg4GQ=m+50ab-lOUW)oTRep#yf2h}ECezFg5xK_2+v zmxXrEkvQPlH$ZfQ5t+CF=xzYMTD1@!1oDL^pgmOn0Q+txn;>h+$Q7i}=6wA%?`yTd z!9t5g1CiGdmFEtq;lQ^dgnMt9ZKy*VxCM7Z?_}cA0SRUApwf+z&3dO2V)X`$o*zWl z8UZXXn-D|}G6)t5!sveqqPOZxdX>|{Xjot9OHi1kLPYMr3BU#$oyz=_0L3Fg+7L?f zsSx?@4AO=go!WJb-M7W4>IIc~qT zUCSjAU#v#3in#vbv4^RVNJ*nimc+_hCEvYZxI>Ez{27(CW{sJ=1S^_ziP>Mnvr|_l z1%hw;@*oT=wc=*#4B#R_1Z%ijggDAO$Wzimq_KhtJ2`U5FMhv9QZ0tAeC75_$0wxH>Q)u5O z&>BI7;g%R?x1f~^5x;?UY>%tQ2%}}J5gTd9fNdL3bFcG;=H|4K)|VG-Xa=Imrj7V- zL9&JU-Rs;7^cQ4o1!k1a#MpZSDLQC5h}+jj+Yb$;2pY^rN``VGd_ArKj&!}C+f6o0--uon23vIU%!X>33WHgJ&wz_I6zBZ~mK=+T0G30&%Y6*&)*0(xN$q7{e$5d9=hRL at a z#KFpbWb{Rg^<-6W$z%|dW0O_Zn3 at 71sFufi0a?>PuCu0d>w*r8qBTN)n!%E2eWzO& zb!(AoeXm=W^rtmgJnhQNSi_pG0}-(NTbFeUM3WzM>#A;D)2-{ebwjtBb*t8}1{v01 z!x~~(Lk+9Wu!b4daKjp5SR)N3p_%`8I{j)rvtbgH770QwHk zEX4La3QP+uN-=I~p$hnp6$33rpyMromLkxJVxUC`bg~7|A_O``Kq4x>hSbkbiu1jT z`A$>55|JgN?L?fHjW1Sl_vtgHD2iI@$Hq6P*l=fx%Xovz_`Dcnd;zuU z7cELzO{IL;gVjo;?S^7#U{R*_K zs)aMa56G8FIq>Cpno!s?dRJ!x0$v5))if-0ksR=DlUDhR;dFtxH_%ZNafiGMk&9(n zR<#ns-RL^Y5X}+*R5jd;T_)Qr_1XzW<`o0YfHKgg-A3`fa(>E0!*icx2JRg$Z7Y3J zA$=KMgreOp((``$WFzZ(Ps)aZ2E(=xOer8((N_A at Li)>MghZ`w$f)7($~sC-_7#-&g|-}#2#Bmy at QL^7XGx9+Jc?4UT)Fx z8^X-5`I#eKo#ohL8zIFoTXpT*xb8&4-0#SGxfvO&(#Bmo_!Ap-m9I z-!m=tRVY{jK{9Qx(XHu5;KPKrgsRNzhP{UN#9k9z{QSt`-IK$~ABl<<-$ zwCpb0TuPhi_LO*#sxncN zl5|-Sszc4q$t`kXtDG*W-YPR^i`?j at R>{eA=mijskQ2Z<*Jbtve zw3?V_xS9DzY at WeV*VW=0ZkKf^?l7Z%Ce1&wLXNG#Yh7|)W`Xfi-pedBpl7i|qINVh zP4C`4n^vo%8uiM08HRHb)sD2f!k=7JlwQ$ZM?v3BCrgn(6E2bsE*3@=B)-w6+7WrC#20(#A^2WeemWKlw%d4R zY8x&j`*7J&(agUcW!L at T>o$Bn&~lMF7%oza{Y9#@tFr at H_Pw^`kN5FPRjqR%f9#eU zJ!hxfn00nxmPT0t++C3CWP6vKSz=&dCkD_t$dxrs)&884!_{^&thOOIuG_Lm-33cJ z#Jp0YqJ1v4#4uC?#17m+_3(B#?JoqLV!<3hZ-?S`tYH=0nk$_qy z_eYT7E^!mdK at YS;j-Aupuint`9Yn|zmS)mCZ0N3g3=H@>}?TVZGk2aLh zL+fxj2;TGuLA$Qb1{`M}%fT^)6C|R;a7cWsR6f(**@Fy!GE6=lCNBl;vxBoAlRpiU zKMw``_vOlG%A8)PTD3L6zNAPTLc2I_X(e~@#;2szE)MUgX32`KYOe^NXWV}6k+WUd z%`7u&JvpDnZ75ub`NR$5wOYPz4CIw;>o`=ZMaR`K(u2oNmWpxkl3iHk$-jB7rzX}gTNVQ6f zX>yPf(7)2BzhhVDFcLUeX+xGD0%~ETgC&Rp49SbSy;^IG4bqu&Q7S3ky_&q0FDoYqOEus;J2f z9YkTIcqG*VW~bzF8O1;6xSTvDqx|O_lkGEdWk+ONs7(9}iIbxU{!V~D0V4DX+5W at _ z`2;{dA&}9E0wD9S5l+iRGL>^$_A^0QQAv~p&tr08tzn;*<7*A(X4V;0ktJe_*ghqr z4u+%U6clhuW at W#_?QDRe+67=oL$IR&b`-$w_rXvS44_UD6jFN!Qv_>d_l}do1D-z{;s3=#?D=!z?=K@%Btc8X+N8|O%Tc2qFk$qAk!@-i4 zgX_(3aBcLfbX!+v2(A}P6*O&(kc-LQ&#kXCXFpG#Ko;<;=soXiilZUu9f;)$JGRLh!+{y7|Be%ZSoJOAfh+DH1=Odmx#;u9K zMm%|tTjws`h+=6Jb9E`6S#AfC$rqTra4)jJi at JU+&RLNhq*P1 zJBNAlJ#KxgIq&hL$E_bU$K%NlxmCxV4|(zfZr#wF4|wuZNRpjTdGY|a&T7s9p8N#J z;LayJndjCJ?&Nv$IJYKq=QvNE;?@lAoWibJh6^Mow=FOGJ8Yxnipn&Ot#_vl{PVV9 zzhhM2iOswM+ZFpf at h-LN?eOoJ+hLNp6SA!g*=4H|%)3)yWxK=KObQEhkztB?cRTdG z8Mzk#%Bstx+Oj4*WwKs#t;I&_jP6DwsS6qkH9H6{oM9#)bvqTVnvu;gxZKJmy~+z- ztV_fpy9r7Lk2B@`l_sCHHyN2)BemJcR;9M!&sO}|2GYzcN at _bS?5X(>9%0y%6x=c2 zVa)MxpKrVpmFG0z4n=B+Oz9JY^lWB`9NQTtL?th?12Z&aGkGSmH0oKzf{?g_hTBOK zltwfZG-mM^1$vt2I~Q}}8E(ztsWaFuyQ#V8NE0s8d!Qu at 6OXK%;jVz#OVj#KA!?gq zf1)_sl;ljseqC{9D#^o&{ifm^R+7^ddx7FiSCZ=#d#~cGQ^Ww-r}(BJdjIk8%auU4ew3dMC+V8_iAJgxH7> z*$`qQKx{;a4-{0Bo`a}U5Cwl?DcPXlvfzqk8DO4YqJ6gnxbg3_d353FU;Ut%^)`Zz3d9u|2dEOH(cIS-3G z8bX{zh+_nC2fA`~UPT8fPXgpgCGnOLe at j6YI)2j_?%k=aF?@pRg3C)OP+T5;Hd{Uq2D_#4o|t{z6fq>|-#A^=Pv9DHVSW~HSZWN8mzzQUFv4TEtkU)7G=|MvN$BG;O znADUn6%(edIs?^<9y$2S+vL$^XO%u`$2(v|L&$cO(6LRl at VB~ZA5ha~%*`P9e8<=RN{u;ynf1|9w3Mb4}uXdTG>S?<-7n zm*^Y<(O|w01M$5EFJz9g`WE@|Jwy?e8w3_V0Qng%9mQcnhdz33Q1%~(9NK~W1wlF6 zXH at S4S$dy=C!eNMO{Y^$M;Mv?2Jj*lKfSnNrUwlZKVWESXnrH}p3%EY at 7tc1qpTPF zNDnl#r$KCwzmMs+&flWA=|RIzFKXDL+(nh2UR?R&7hZ^ZpP~eP=+beB-iP1{?uYXXojVQz4>uYZH49|}D#nZ-ra)4M1_ghdhcq6moE9~4n9*msRqy#{olHCiup3V}H=&7*&p+60e2T51Qr z$*F_z5dRAf=vAYq)@fHvWMiDMxCj;6U$*8BE8JnkAw%Uq(c`WBhv zo*}md)gZ980?ryZXMUyVrDo*12J>D`2 at Foimp;KLfje(2T zAyyYBx&hcg!YyeXv4`3)pvFK_)&-;ts4=npbwT-2xqd)RBn_>S26-)QrX$Gap5XFq zAH9zOtgQ;>_AnU%aT+ShqSv>|WkgxaTnG_nMSf;N{0CjPxmKi^2jHcyALCjIY+XO8S(x!Xy;{Jy^?rMiNB^u zK2^!%p%ir-g`aaIhY3yaJPBb2%9*E?dUU at iL@c3*3nVK2D at jV5l?UqCQsE*9VnXzv zmsWIwM)?2+qVs`~Y{F-gfhQ*s^c;d30`!5YY-vSbkS10taZprIog;|3B5f|Ja|40t~`*NSC_gebXVKthfua z3FJtmC`qAmULcI2=?*D>UJWgBACe{%M3GHyp&wN+ at nJ2W*AkUvldG;|so{9%jGA~X zq_O%jEL|fj9UJ%xn?m=>Xn8|Yq1X=%yg=q7LsAs!kRi$4q{D`!0q#qR7BM4lqhwv9 z_oy*?z(TMJ at 3M%m0$mL~U2`tZMfpU75^GST0kF!P)YB?*JNcV3^c%&w3eCG|wNJY_ zFjt$vEjTc0^t1^aGbal{!%gccV3 at R#YBGSK`m_fiFRjmMm_t%HELjw}L&>I%Fp|4z z4X at Qma}W|xQ+9=>tfMIGDzMH2uU#h9;!&|{@v#BY`x*tW=70xC*l zFH_`!6}_R1eM6;imLD1;@c1(AudN at FIkSC3U#?N}w}xtZ%~d>m;uHDGzE^N{6xx`Yu2OzUlc?JEfffbf2bg9dFw7cMxlE^8Zy;OUu=U@);a00Lh2TZpVitE{Wx-D~vjAoBGUHvrJSSzlJ$ z7mxUv?%A*4nD0Z(4VP$|m|G(6?D-DCj{G zc=0oe{BUVW`QYYeqtqyQIAWGR4Z(gz*gDu&*{v?A3igB`iBo-;TsICQi|2FzD;}B z(96Gu8XJb51}Hphiij{DUOhuZKSs=s?tM*o?c5i1?Ht`+rDpG`%ejfO+>4#%3|;Z? zj=0m}jyPPMrl`0__ at 9Yj!7Y$Ipjl9x&sIQx0=s=?0OP92Y+gS)!7#W+$4)fy3W|Hs zMP0Ld4)6D{&da}m5_E&(J!MnX7TZD7)L=X4jK3WO6UCP}QA}5ZJ&Q$ZuxIhPQTfb0 z&Us9JBb-N;hsj?+^1aRtOkNQtuL-A_VQS?wm5znSo7FbuhHC(Z&CrRsRY~3Rc$N5K&OM at fFbjJ#fnhh%Yd^`yaJ3{R*IU`iOE>Mv1 zwNHkd3POpqe!xqeH6TM`q#7HkVlH%>RdF{E4{o%C&78&U$hQM7vPYnu`JGn4K?#|! z5%^sK4z54jq at q_7+O{WvXf**O?r#hU#=O;JbfS~QSu1CV^T+7vuvx_oW3+8ogc&MO zvDy+Qs9^#*kZ)Dd^XbV#0eGp&IRmdS@(L&7yP$_6GChsfS=1YLj;biXqhJ78a!d?Xw}-$8OW=Wg7S2ve_yncso&6?Nuf=4;{Lt<{3e z`yu%Orz=V#8g__~Fa~Q}*7z at IK5*?vGE;SXyGkbvuj?8{faJNgMZ^@{-WEonjH1D; z$8n~f!tE&>Fu0kwL1cW#sGh==+9 at 1mL-HNET*S&pNfyb9SkXS!X9U-U#id0C}>-M)*Z;HS;Q1 zFsrO1YL)daJU=q3V708WW at uIY_0&b4yv(gb5O)~C6oq*>_ z_&WuEpTOT~`1=(8K7+qA at b@|VeF1-8!rxc$_ci>Tg}-m$?;QM{H>&#Ik-E&27r6B; zykCI7 at 8EA0{9VK{;O~-w^jzS{OMeXJ5>I}|t;+`fyJAEo>j&e3W at cSAdf#DPGkV`? zT{n8)W!*4(-)%J;y}MeqX7AgrL1yoJtifjQd#xen1GUT=YWA+Q>dfBvS;NfU_gllw z-rcMbX78vq5<{afG}`R_fHlVKU1g0mdv~|SnZ0}Tzb*A0PWmIYK&Jd&$duocO!+;B zcf7Yx!-)kqk`Cg0c2G at b@tIYVhk|3p&LK6K!^oVPJc`ews#w}aYr!0K0OzPN8V+XM z*gK%Q&H?P`u^~hbA;yIeIe^F^#P|^6C_+pKA&vsXQG|F!Q<-osJgQ|z>G(TFFNIYE za9ZQdKU_3=V(2r2LPtn$Ngh${gQ_*b%v?13OO>(j4cR$@$R=tsOkEGPlphYNC_l6- z$PZp1IDAzQ942WvEeW+4q}bRcgIP25j(6j#V3$Ow0oDbQZ5Fe%WAY zwX!OH#gOTykK=R%a+;=y8QlIrb*rETDA-UZmWnt)P=!XQSHsAQJdB(X=Dy5>+%rSK z%LTw$A>f4&@U;-|LILpg5b#n6I6DNqQ~<1}{={=e-+}P=gCThWxPgE>7UCg;?dfmg zJsEQ}bWKI~j~==E2IpP0aJ at lFP3$VL!$h;wLueGSCYf%O#jY8^1r=;SF0gyj$yU0u z^JP+8Z}Vcev6uTHEM}j?{HtIBd{fLNc-M_Ey8cl#MDKN?cQB~Ro_=THhS0=qS%86u ziw{YfOP&8LGV{xz{g6+6q38c1=y^Je)804LVuUc)f&i8{t%``#Dtj4^YEn5J(U62X z!bO8vWF4`{JZe!Bt*U-I&ezcI2UpZH!1l2-DtgNCi&fgM}2bW^tLr>li=~@ksbU$sO2D)1vkeQ`jbknq=r1 zV$#jbb*4Scw1=DVPc*cDyrFTf>F4Q!?HY{?u|F4=YHwCw(D9jLFPQl3{6fSx#Cm9{ z&7z$)t)i{d{#qj&A^m$Qx2t zNs$pIel=mZDXI6BmJTr`GsmQnro=l-&4wf!B_(yHr2SkPWJ>yGRT^eWN at ZzjtqC%g zG}M$Lou$RZvHurNS3d`K%hJSByF&$K?Qy*6VUHR=ronT!TKjY0$M{zoNdMFz*~|b} zK&Zbi6^}R2>r{B%Nw0{a5~|f|T3mwCocdEdFl`E9vPV~EpPCqL2Bx1a>N8cK!fjR) zV|OFV_ at P z<2kyy6ffvxHAdnaShl`V@#?1T(0jCC33DHkvBVFaqo5|L5Zn~ty*U+4991={hvndL z at E>5(i2tC{_JNn}LRZTGE* z=XVF7mU at MERPm08!8vO4Ujru5)_CO=Hnw*MUcq%%MP)sC7Da`#D)z0H8f&6u0-3>} zhx>=ICN(Z$5qgH2XWZ0Z)c6cf(- z3Fk#Z&=^fXDXhIv8p?O8G>LIeSK@`CHJYk(V~|P{wu%#W9nQ2kKht#0a2~}slag+p zv%CA>JOIY0O(#Ws(g5jwff2!X6Tt`qq`?#$P>p!1UQmhlyNUJ!Oi+OU2D)n!p=sGo z4`5MGtkMLv&+8!qe0i#nAI))q0}oj|DL&nR$1I?x5Q%Gmm`m;~6B~ROgIs|lC3r6Y zV%Kr4e7!i0K{Z|^KH^X at HLN)_+7qia(QMD-pB9j15?KaPgt(EU$BIe9wFb8WBg8&k z3A0fY=MxkuP_i|k1R+J0pJ%|1ml(<{{4#2XlZ1?&|Q_xvWp!o*5T zC&4;qNRwerW6~5;k`-wxENPvkX|STH(sWp`ROwY%$~sFkO!{CFNI)9D4`j--s=u%5 z&&UId2O65_V3{M9hRMB+F$VoWqbyH at te~^tT#OisbQV0pO=}sbeuy_VZI#&kO3gPKz2ARE2M6Azx^>30>{Pd3z`tjD zLCvn>O1ueDHQv2=CBDwBb={CnmnxL@=jx~6MwSZcQiN=R~;_O#>-(}Wpi7+QmNkS+*{XmXt9I^(qD zD9v+5X~{Y*>(pt`^o1L**VvrUeq|s$tc>4qClEbywa7`-z zB`gqQwO&n`vD%g#&c2!<8_XW7*~4%REyd|)m- zi^#nnB>bqfRNP(Yip(o2=tdWKaUnOf$L>(yAB(=hd*~G}0wHjS*-Kp6jsScd5W7$F z55Cx=G;!i(l;*}qX{=DM{h{Jk=Y!M%NFCisaUe_^;-?LPv>}vsPzwz7^}al3b#=DV znb9JH*843DT9dRE2Cc>-d!Ku=y>FV<%HB6cYh~}7sukJ$JTlci1{6I?gCo>V9#v`| z{ZnjxSwhg)M=PH!5C<16|YtG0~B$}9R2ouu{GBMqN;eaNl zJPk%jPR`a~CY!A#XX10FCag#wLfe621i2R at M|?yR5PC*02KHX8C6;LMCDg%(G_6GA z<7lVTzqP8a#`Nf7P?Yt;1ODRX_~IzK>k7{S_8caWbS#P{RE^SbZ8C+Ok_Ns{l zcNUoGhzp{h`J~d*h-@=JpwT+-Z*Im*qta^U=pCzr)Mhotb`67}vL$6Qk^>rvs zcO%~q+MX!G8~X4i_;yka5LkXMl!kYk{wNTL^I1{IkHg(2?C$f-Y`rz#toI7Xpb`(I zji;p8L$sS>2HPB8<1Jeh&Zf>9iFsPujL)NC_Aw0$bjg^Z*;BcjSgFNUYEorYY?iJ% zGm!6(0QYfj71V!N17~W1;@Y?iRXP99m9>xNt%aryM1rEfz@&jg zdj7)z-Nra-L&smxwvK-c#7xek_CjCBUnF$=g+j-F!?YKh_L4xyKMt(Sjka|B$(oQI z{~bDhVlsIwRZoTpf#!)Lfmj!6{dz&`f8ywm==d`Ud18X*>-dyfJwa1a4Q3lU{;@wv z$A at tqd6RVfB|^tvD0KX#Bn2%Z6 at CFoN0T*ao=FP-d{XY;Ahmz7(DzT!?9&dvQKMUL znEuAxNez}O-Uq!NhN^WrH&dt9q`9$0nz$C9cAp^;6Yy9x%}pgDgCF;)roi-7~n&xKQC;t)+b{wUs1sX_Q zu?3n^YvKN`Z?k}+hNyl|eFch`#((X at rqux0r!)Z)n-7c^%hm8K?)KUrXlb5}4aG?p z=4(9Et!bdS|NYHH5|*$)XgtIi6C0cKwFbNZ8qEn}+3?Sz8&m=SkI5-XrY+IRhia#=U$5>Bu!GU0tSGTkutZ^(2TWFj;0dCK(DHru0n zTkV1Na4Tn^{rp?X+vAV6CE7K*4M$&E8ppjL!$K{-P?NHi^ktJdTGqw&uhuHVGk?ng zS!_AJ;w@*yBL4t`V3sEC+MlIWIkPl>K+q5s-=-6e3r()I#KhH1ToTibUW4^4=Vg|g zwLI at -R+!}9|CWZf>iAol0!va>EI}7(gwvNZc$0lux85|N61X^qFOYzg;&IHC=F<%K zOwh-c^rR$ad?*J4lD^EAd5Z?(pZNo^8zd#x8eZlVXqt&?e61m)T;jn9LC+E+ at GPmz zqf5zo;ZlNwcC#3?K;I=&dqB8MtSVON`hYSqT}!O?1=!wQoe5fEO)@n&i}0v|GEp~2FSZ{v^kSArGIw*R4ednPx0a)tatFP-9q}t_ at N{-+$@hTm-$SH>c#vT;W*8i1 z*z9M3r?a^z!;mn;Ud%8w%&^zb08eLcQHHuO!#2z?EX=UY&j3$nTTzDLVTQLc!-z1$ z+kOUkI&T+c7#U`G7c-0sGra3(fT#0rQHIfBhCP^JOqgMhp8=lEo}vt6!wkDI!?-ZR zZa)J&o!vzl#^aa6wfA&%-0sk$rmROzd5 at Zk9yJ|%)O6}m)44}Y7yCVpJ^-^-i*MB= z$k`oo;-#``Y=g=024e_oBCOdPG}@%w0IQN5Dk0q7E~2-$h}P{bB6^FK$+Ixpu=k7T z{Vk$#-O=k`!UH^M6P|cjrKFw?B$cMsbVLN(6&+q`4`ld9DWHgf6;IPKPWylc!0C2$ z_MoPNA5&7FE)_9l>Gq&1HKaD0>Gq%@HKg7)U3(XP&FLLeb}M7AgeOIilEbQ+JisqaihI5Sp?Es8Mlk0xW8MF_!Jcgb?SR2k6iSadJ&y+20zAR!>;=x4_b|o?0Zp(h-`a0rbtHPf|?a zkq)Z1Uqd%KWN_dCydfbf+XuBd^@16pe=x3oK$8!%uQklt0!&U=xcqZXOGzk6EaugV zxm3G^=d*OC5EjASx5v-x%zl&O?Q*?bPv8}p;>&eYoE=SU1Zk%V^sSAWTDwt$B`WI= z-1xhuoY-Un3HBzvNwg}p*-VrY-s)1dwzNj4gJL|k#gyRK zX-M$eVx(etr%M}J;4Ven`+eM{gnPdRoZcGue!#sS7pqrzAad;XMUIEMI%_axB5%Vw zHwnaaGN=jL_V3ijwC22z;FEc9qsksaR0{u|c^Cx6-sp0~+Yn!-a&&;Xo5Yu-3yY|$ zC9|6Sr2-1Tmt1zTh;~}I`ntIKP7hzR`1&djyfydw)c>@r(+9H6Xsf`P9It@>JG^hD z6Mp}i^@~b5{B)kVO->BaL07nBu-Fig_(69 at 50*(U1qz1kK`T5dQ}f`SZb-Ra`5)s~$gCh~I97vPc!o7k?$B3mFhKPz3QaA^U|M zxZm_|4m}BaAi%XZ(48FR%{tN*UgLOi-2ZIsi at Q%|RX?+PzcT!!XU^JedUuNN;`|1` zALPF={K9~>)qj)Adf>-~{5J*tw5;v^n~L7_Wf;;(V9*TtuDQ+4T77eK at jr-?q}Mr$ z-2Y1T23;j+UzFuv1|gsMAHRZW4(es|RVY0NFT+uuRtXaC==Tr(po^0n2pvUl=p1g%fTpx1xdp>{stgG_@@>K&5_GK6F z at cuco@|mAI52FJR5BzmkgsF{?`iS#W+(9Ph>R+t&jMjU1a7OFNG58##Cr9YL+dCul z9=@rDdkAq z!dS)q+oh5tbvHE*JM086Idq`V|5o|<70K%o^-2R6WYu*fpS5eTv4Mcs1Y6<;OGXAxABb9 zqqi at 3pqVXtxe3E%BAkP8 at 93hJ^KQ&FB~p_xP>-Q3g`i&Rh_(KjuJ!c|D7l1ZBKKs? zm}jz=^YqGsSJ1}sT at K&1el}UARgZe at pweMp(PV;44Lo) zp9vrB>KsKTd^=>bO+0W5Zvr-Z%-M_j`DR|&2-?EYG6(>T)(HF*!SB#Myp^MYp+jR| zYpQAOGyAhzMsvcpU at jIP40s6sJ@X5gijQNV+j-z@?uNWQj#obOXU at -YyQr;+GfO-O zHSvB|pR#&Rakb|2gnpQrdHLQ;v}&E}aEdO6z9mh;3}t@^AcrB>)78t)Wp*;4tU z3V+CPqo+L(at=3m&iNZWA3>+_l3lED{FsSz$B&C+$Cbi9-VN>AWDifQB3}hEJiH*I z$@CnVOa};805#i9V8A_m`|;rd&+}@gR#+l`?{Dd=^Z_NZpLg?Z-EwFRB9A$8URWi3 zG64L%+7CaH>Yqri(flLH8k%N at 3e#+5Klab_*dn$@zo_vKO^J7tJ~Aa)l at 6JbRw*4e zCG~OXWAqY`j*!Q|AsFHse-PUBoaSbZnnbUEv)tou_2UfQFv&|DH4|gGR}H!=UZ+&l ztREB0v=fW&2<|!~u!E0KV at Gk*O$$EPb5#@Pz^-cI+fnq1S+unt<+#Vzk$~x-&-dEE zGKNMzNjmo9h|a6EJq4nk_zj5Ghz9Wx(TUb+LknpqTc?dGq at 5xH?!SqE5<$Qi?km4& zvFE{F;Rv*^p62K+aRtxt4&uTpX8 zWz9dA?p)UF%bGZpzE`)eYjm%B5Hmx!FBQko+2cB16hr6McdA7(;xfrF3hkES2FXux zGoOza=O3G(y at D3J0WJ75Y_=Q(2j_*P;@ty4$*Y=cU(<#=SLrtPhZVW}6mYj<$U(DR z)#z(m(5C(;X&$j3$-JspKSKK&P|r%JAvAS&Z0b?)7{!xA;W3mazt_4u-{YM18O?rA zK-T~pgfvY>XFMn-bbN%;s6dd0PnkejrB792G>?zwbRiauAl~jY^Y28xtX1UE`ijj` z at n3>a*w80#YUmR;HS~#_8v4Y|4edvbR)z5wL)K^z$|?jq3mC#Doaup$UZ zf;xpsav{lY)88upZ48 at 0v&%xZjJ-vnZEP<)NFnKSYSh2NqfT1&p*;r zUvPAz13?hFL0n^4G;BK3sJf~pu4&cRfU~b+XSu2KYB179!Gn-i6_5t>Ob_S at BBr&D zm=Z+bbzK~{w(2>O6wmtgyheu8rtiyXSDIRa-Ywh*_{aMi);!5^Sil%-aS%-Z82lQ3UemW!h8r z17mQBkuBF0&KdYZHEqF6IG(Tivjt4rFyVwGumTBwE8o>!Whga3MbYwYCtoab&5d2kWDp8sk1#L0Yx4huHbD^ck@?Fu|dXb}7f*rW^ zOyn*#ul))SHo1L|ot6<(T1QN69r0>$1b&DoT)D>M%5{m>bW{<>!-75 at 7xc>nfv!*! zdFCRj%2$eG&f}Kv55+OZ at zliC;+R at QeXTfVB*t8C0fU%t at L(ewY8?N895k9~C0S1^ z$s>74h&RIoGl}Ds75FxUOUX&l3zN7YkiP!zxBS$g<;w;VFwj;}BSIs2MgIa|$hm^t zia&#-aAJUBvlxFtL&pq1L_C#kbI!o68mxfluZ6Wb3KIBy(zSQ(L~i8oc5`Pu*|SqHpgF(NV1bJN$yWuSh)J)%jXW zoGIR$`z6TvXSKxV#c{uaxW^M;_?dp))w!l6zAOg#bG&Z?D>amA{flDNcB*cT)tsq% za++>sxid{qPSIh$ai-|W>AE#RbEfObS9NQI=Ddo^Po2&UuHVtx`|&z?KTgInhCvxz z2kpe3%;`+QWZuuNGwh$^#_n+4fW)peSG016ysp~2)#^u3Zy2Emj*wrqaD;pU9U&jV zZ6W<8pUoM%mFLb3{pJec?HW3V|2y7HHSQScI*{a_SG|d*>M0zD#TcRps-5_{1w~IH zMN_caWB-jLjr$Rj#uG_*l55?~6g`RrO$eLlYzu;(LV_Me%b(tcrZ-W?^WK*Ijp=62 znNzGux^Ngb?Qcy#68 at q82fqE`9C at uw(pAvP&Z|XkN?fR63^k*uguX at aYtXMrzY%hB zFF~MRzR=+ozDrL`)N4Dx(Ed4w9w~^CJiY;C;&q|ay^c!z3#CDX>_ at aqyoRss=$@z7 zbXk;+UL~e~4yhFVl%OW~NQuXm=h#ok%?bbT^Gx{p at ql{7hL*Y)B4vFTVzGbe7oY={ z*gPnfs_}gLXN%&WWIcnZ4zVM$1n;O5nyn)#SkEOCoo|Vns_-ky?Wbb}s12X*KzT|K zIQAN+y9Rb~@XSwLUAdqsGkL{7f%$JL z{GOtg3CeF9dycTf+SB0zI?qQU-+_R_rHiK2;g{0)pfIbYrI$>peP`(#(5jhq*_2AT zbRM*7E?qGtU6y_ at B}103nvy9?*GwrQOW%T|!=>w{RH8~ZOsO50ChKS}?%mke_lL^U znIA7P6OS)K&&<^6PLOR%7H!4BEd*0w1kr`@bl%fGU|J_kYq$B7Y^@-JC0^>Gqgm|d z4GQoOd0pPklHA;kcf7ddb%hpjy at dcWorW<=Mzi&(M z2@{@?#;2s(U*_!D`b&BD=E~rM3}nPxLq8<7BC7EpA;Z8*c4 z#PB=1UR7!uFY8G$^(KDm*7Gv6xpmU4Pt7K|mEO|^zS<2?e%Z at R!p$cArCv2ANa$#u zNnw;YK#k1}1ZM$uneQ`VMwKOa*PNG`1BkHwFod2yN+%6JY>Zg%VNw=Cg*@sK{7RCS zHc~UVU5_AFB><^Mka7VcFY&)HMzD5X#v!Fl at HZq~b}t_fb< z*q{ijcn~OsFp!uT9DyD#74Ls&HhY#;h_Ig?8xoO#Tr at Xq@XZZ#xIKr*hDLmH?3vF5gs+;ow&c$G8ZI4t6@&GKOPYFM`#t{^>#bnJ1~j0%rc7#a&d^S zBdabfr2Z8yM`T>MT>PP9 at _OJBGaEW*Hh!bS6m8>XW^jad?OJXv^T)$%-gWrf9K z*HRzhmRpp?Khfo?e#9v?8r72Hwf*9QJw?@2U|`fW;km4n;#`)CpKJCR#NS$rGQ2V? zV4|vi*=%tc(3k}Zh-F%&Qm_0K&JzKjz!x+Xw3dOOe+y#pwV-U+lt?*i(gcLROVu0Ub*9-uLLFHjk+>|g1; zsV5hMW)g{NFT!tsW{r$Q*^AaF42}li7p*an??r2Dq`z#9qqOmuHi3e#U~nP at Ct`3? zBx<~9O~&99ik*tVQ3aIKC^!v+(llYDMWffcfpM6hMzGb4KR8~giZonlpi;wFwIwj~;9#X;Y7Huhicw0;NbEyg zN5j<`jH$>bG-RzI_&OAS>hK49!WxDl>|kp+hHyL+H_ at oFMqnHcY+_m(9ME4;F~VtV zTO&M%pwP2xFg6lNP;0&^d7290)W491PcpKnpLdV at pU%-Y>dFPjB^T&dg~I0pL$Hd$;E2G&>Z~135LcMjSm_b zG}BmfOQ7TDAqcXdH6KGb?^p{kgcA(SJ=Q{e#VNvCgrW093pOnBsI?g5mQcC)vy_U& z9~?l|a;*Le!i+y};m at iPSUZrZt<_Wz{;b8Hb@;O$e>PABOQLu8AHkeOdUBZ#s|G9< zu+%`^yW-+;kM(v5{6Ih4(_e$pi>dcwss`ilqXwy!2!2H}r&@R%0 at Ql-P&}?qkl;(eVR>aeI-E zA?LM#_ga9 at r2=s;ZE;Yb{iZHTs!_9*{!Qy?JoWFBQ7E#m7oGv>Nxn|=S}LOR(cq-u zp^=dhVQBn|uFh1R80EkHrmOSI#AyHJFS|N(Q9NFt2Obb_=z#~sm`LR_F=rwgx)+DZ ztM$NFVJsy7*7+6Qs6d9qRu-(a&>BX&f8X~7*8xJ^X!(7?^;j^kL??+z(S^NBIQ~7; zkFM%nj0JCK%bvDT=N%Mvg3{O*9bN~3dYabi(N|JUYfHtBZ&aQ$0KKfz_$iP9=y}c4 zrAz4m0Uv;y<>}J at pS16QZ{xV~)}rmA7m^@ExzLG|*s?6kPA<9VN@BaR8$`$}}^80<19;VMuee>ST zn>X+U2PS=0+gAnKCVVy9SDOe*-=O6So_V|YiH$;ZBDh at wX6(L+&_sA5cmjL&DC=ch39iyxT)oym>#-zD7c2-r)8J50vI9sk8Pv z_rCpBd2WU0B!$eI*BjmDS??0hf2}$BvAr%nzT+3WP8xR|@h4^NRw4hHcl>Tvy!)|(f ztN-rUVqW(VcmCHd^Y&iiN$>kVkALUmYdijH*V6P|C!MJLkN7I3*{avORbc0z-_h9Q z+0F1Q3#tK%uM%AO(xQV;?7#A*GnJ>VcqDM= zkrxEsdg==G$O~@vef-u{-lwkkIB?~MPy2V>^$Ydmx1UkJzQS|C%cuK~yx=9 at DVH7P z1GBHbe9v*}V;8&}xaGj(0;k=7Y2f*r_E%rJ^GNT}7u at f=^TVV{dWdGX)B&;DNc$x%nr-(w>E zD?hpGPWpRMxS#zAkN3b&CfVOxKlyFv**iY>n>~d4C6WFz8UGsYzD=aN;U`CLbHZLn z?-BU7lhX72eYIMC&bO{wDh7rpeq|Kufzf)cRGCtjRjXwqUs2IZg=)>{8qY1S&dgRT zMy1|wn1xztwqC8Jaw{;{`%C6*IX}nK2a&*7hgfFUPWf`Fn6H$deWoIUgV$mqBs8;GD##G6y*XE); zMUj^z?JCi7#LJf|MlCSZK+;+mC6LoMmg6H%%-?8kMYYf{g)4%HHN&hnY6ZhgQAH&Z8 z+F;N??-tC!U>@cM|EellJDEdn6P z=P3Ub)fzg|*Fct3>Zw|Ob{gMGj8q#HbeGjAVaHI4i!5>#e4|n at f>c93PJU+0O;q!> zB6EdUzwiPLR at WKK7DkVhe#7k%=cw>6N6k%bM^S<>Z-^PASjxvnsWfA?e8rqZc_UQt z(Mo>SoUWqU#VOfB^BwKJ7{k3}WGNLW9+YDN2`f z)Hy1$W at FOyE~{?vt;A2rZo?287NgUKft1Y7dz7+au}Vg{sO82mMaN3B)FTt6N|DW4 zZU%U(#@vmY;?A*rZOT9%W%PP7=k!}yHS3}NT7GJj4Rq8`7BgvoSB&F?{Ja?dJH&H+ z6PSt!MM=X z)O20=Y8#|QjFh4eGk3jjx>Pg*HHHqlWx~NyW#>|{J}n~J{`n0~|LC;y1L;zwP;L~B zTty5S?j2e0NRe at T!Bsl(i5CA*sZy%51~XD1c4}yHhD&*HBwwDL&Worqqe5+0Z244v zgDcBGqXOho?3Cfo8im)DjP)`e$6etPMu+g)bR8yI32jXa0!L<}D47hMuGUJwp+R5f zVXLb6fNbr}?a;QVBjx;rQ5KE`@%uOHVyR{@VnW2~Kwts!oQ)WtaSxODjC(jGW|}&=Q)J at uOw2U(Tclp9mu0aN z%#pLZCQ2)9!<18iHRh^vzF^D{eiL!()u|~fi!g9w!vGeVDb at W2H1OI@T+7SiM!i%n zq2?SPBy%1RZ!)D5u~o0}JnvhfYsg^Cu*K%{|K?5Z_0mD@?$2iQ7g)OuYJmbOL+JFskOokf6; zHYV8O-My}fCSRGa6w9=}+a1l9uK8B$HHV~3!?BX?MB^_t)#BCFL?7_9TR3R%rEO!V zV%6B(#V0In;S0@&li8yFzbwb zB5q*ebiF>ibi3`=uU{|d3Q!o^?XYCY54WQ|l?WuFy_W9j6f(dEW7~S zmlbQ at aBa}Yn+;5Gj%qs)tvo=Ckc}=h#AgK$Y{{1LV1Y0k4Ux25iNG!tsZyLbiqj}} zw00C35HZCZX3)a^p^`b=sIfYw7+xqkIgzBJ+&q3)Ns_aM;NEEJs^DpAu*Zl}7 at I~n z0lQXf0Zt(G$l+5MK at qE*4&AXeGG^Cw-gHEgi)O97Zgz2K`C397Ec6>IZ*913f#&pxgY3GZ at VcuL3=fa(AAX~iKGnnL55+(e*G8$M1i!Rt5i3j$ zgVM0M$Q8Bf3>)%hKxbpVLvI}to%V3FJrku0rc8+#E}ns1AWlxoQK7f4FrFjqo=+zhB0H}mU^Wz|}d7$=PHgtfgQDWYcLy at O*I9xR(&Ocs_!mvC6i>vAyo^=|hl zT!w0o;?u=VYwk at Pz2M9^>KbP;;*v$ZQHkh`a~mgN4bL1ZiVA2Lm23D}1sPeFG zFYpoFW(A5`QOvpEmNGGo1AF=>npu{pKIs_D9N+Yr%S!c`{4A??fR9j3600=c<*pF1 zkzF<8*A3ufFc{AH$ZBe}R`u_*A(%kuQ%`1rr}cgR-%{+On-l=`D;=rrk)UPes`T zWn3pd(M9u{6Kbe|BsL>#yohxv>X^}ebyd{g4{T`Ex{-Vcv_%8_hjXx`?61 at OWZ!K# z7pzY3AgCvJHhYK2RCSKvg}ElFn at t;nT_Rc5)yAm;Blw+t7;j=+JBniMb8M2)pmhy@ zO)@zhdY*m2DB5k;q08}Um$;)%cXsLRsussmGv%1- at dEG7vFeVjd&N+eTdlf-ruNZt zruuijAsiPnVGyn3k^&{?;JZ34!O z8cv)Y%FoW0=U5Y~I>*@w-uiY+MP00)e?4C;cw|+qXAx0WhP{(k$t9i(V`y7;*~mlH z^_Wf6<*Pu?*MruIn3SDs?S;m4*G at pNSj(?x%@?0^?->V6G^xQ0^(P%=;3W^Ez2rQJ zH{rLm)$p8Vtr96%$9gR`ECPI!xZAOG(Ik>oV7xI)HChhJY$9J+Yo_#;wK>r|_EW`b zV}g;Ig|#9MIC+EGb*#!-WI=`4!#PfoGEAPcsOhK}aBLTLwac}J5DBkx#5rrDvRPS^ zcIvHrHN^y{@$6Mru0QUjdcLTxXr)zrH6*N{>KsK!^YuEi5{MG>uZ8nS@&VL0Oa011 zqPmmbiR*4&CswW&9B4D@=s;sbLT{o)LLAjl!Z7bDR?Q^HVmGBt6OlD=(8321-N9uh z9qXMKMkUjtlta2j{;Oi7M4W3_hnu$RF98`x<=&5C6;s=IxK_@`p%j$MVlfty%~z2xf|!DY{;z-O_F&@Oj41Jm?N4l zxRRvaOc7J*UF&VE;bQaRf)AEiCG}>?HtM3 at 1kEAPLD1SZj5t@xP@!z(Ypeh%=R6hM zj^08p7MzEhDZK^ZYZ6EJ^hT+uZ7Hgok(kdOXS`v0^o`;*5bBj8?L$^8*5r>cqHcsw zsMwY+vZ~u2l6?iD9!e8z=P}tr*hTo#$Q?loYbEril`Y;x++^f*Th at dpItn!E%6;YX zaygUDl-|NUcDd^+nGe%mY{2iiR{k`hLI-y#>1kSw*(oSdmp*EQu;>ax6&g~p?&^|E zHs5a%sbjOQ-9me7oJB51UF-fP82=8U3rkei#?&!4jhf{3b5L?A=boQ9^_ni#`|zch zxgIQr_;g>LI0oigHb;G-$-~|#R#$2$etH#UYrIUw_YjD_;242$W2Ejle~nJLCNEr^ z^D at VY&0uHMG1GN?8?H2FWcHfa#WvH;#}2&LqeWvf-zeAl?n%U1pG at nS2Tz01hS^K| zDiKx{v1o;4LHp|AdVazx0qm^kd=rK`I|MJ0h at Hxa)8$YRD*!)p4Nb7}pv#S3babwg z7qg*kwA4PCqh2CDPVS^9Wsg}M)|DH{ua_mVd^_76#e~s>1%son?+UDHcTHcV$RkhW zYi4e8$S^Rfduw*)dbt%(IP?zGX-Eh7-+o_kz( zf2>m+0PqPSP5~mk_Iz?i zqf@?gQ}8m7g=>x47Hz8P-p$!m4gt|Uw0~r`c2aL{{tzaFXvc`|Xtazq>Kr4H+RCrf zxlgy={^&2={#Y{N?74LN!^xE1(n#IBYBr at MHzN6lPm!^$_El!ak}Yv> zd}-@}urbOu_(!MnvqrLITBqB$euvJG0eG$1SvK5}R~HtO{ucUl^Om-D52IMYH4&|S z2p*jk+0PC(qkQ^Gfg62eXS|Uu9atg)yEV~LZO}&(f^rgrju5$4B#Ab+rXTf3Tz8kh z+nLNk!MHx;qB_&Q*VQ%l8#

ImP8j{YPuyM z)a0MiTLc!w;w!{md3;spo|0C#tbtxZCSq6F;pCN@#sAihMbddju=!*;Pl;FQbN zd=a0q7Ic<0uDb;$lHf+xbm3J zfnyA8 at 9b5%Dg>=`U5-?v))A*RTb*r`QH9PnTl at MJI)=qGairN|2_9r4){hIL(oaIS z>&-mUdLE4~s1!Tirg~kZ-O0k at b|{d%K|`URHnNEQBhQG=PkqF9>eXU?&Q6lj*Mp3% zuIE)@ze#6nFrTO{*GHQ3qa zDm0(NGrwc4RCE+6uFuj=KXom0_sEKZe*_`AVb0kGeA{X)KVf@@b-NI8-9lkaEkFWd zB8t0ldvx%$Sld`8mbh+HhpcSVA(;DRe2Lk+MZuc8v;$acW}?~;dK#suh(2?~LIDk9 zZNIbEy7Y3XvbH&=1vbmtA~GG|BLBYn+^nJ1t9;uctj}6heA2dx4a$)1vuPUv7CY}K zKk66BRIxp`xpiG(Y1``xYK_ at Vbq4qXgT_Bejp{Fyk$b}GB7UT0W?Hy$5s+xkdo#Bj zS97ptiqrHU8;Z-TMMEu_Y%3?idq4*&Q@!6YkhI} zsX&3WAJOR$&a=i-m`OMl*vt(T at -?H**7gu61wOP^YM={8(oRHp z3m!Guo#E%|>0sGU%(bof~bs zKEu#Pus)5|p4uVhj5a(;Z49Oj(|JbpqE at 3lhEjoT-8tIGbh^S*K$9yzsN7qy!GgP#kRRn#IT!UPqiVUCc at EZtZ@sm5VVNw1}rSDax! z`xs at ubqQ!`Q%Hx6kF2xZ9+%LhYY9u(TnO_vJH^gDcdp^BDPq|d?B+KrBSL6(tU7Kw zLP8`-&#iG+R=NJ}TKgo_+l~zAWQkjYT!ap`h+rX^!(GJHyoeF8cJmt;b|W~wV`48& z)P25v?`5ewB+5 at Kge%@8IqbNEveXqZ#9c!`eVe-iNR;viKl;&9J6xUQ%ZMh=uu_gp z#hsdTAwOGh)PPEB20H~}qMv32<7R3~T=P)tY!jG72U%Q4yeRbqmr0==Hkg=q{MJsw zXu7nM5XpfPc;W!hS1UI7*%pP?HvO_Zmi2!IQ2yg40qZaX2FuV3Yd+~4Y&Za zl;VoZV+tYE;6*8K_QBTxTg&}C4wUTbCvu|sn9$Eo!-eD zyLFSFVZxZBYxzv(Yh-hXwKZ>D%L`g^_EC1OfTm^0Cl0=Nu28lPO2T4-P#ygR+KG!0 zwTTa`!;m0Rqviu~DHqH)y6n~8MzmU?Iy* z4LZzXmM%N8Ljq}2+qcd2Fv^9EUmx19sL5L!lqd5J at bkT%*10A}S%gLzQQgGGv1>Ox zJj4%j43Zh;5;fxLB6sk14D-`a7AwF%5N7Fk+_?J|i7BG9tN;~M_n7oX-tU-KlXi6jklH>y0a(6gmH at G{X zE9aEbl~kfdLbnYKPdW}YSuU$;rE0BIpR?|HwD^jTJ8qN;c|i-ESQkwBs8UI+qc~g% zl3)!e2g8u8&fZvdqnG;%YNXblS4C*qxU zxouCCm=^dc3xktP{Fe3$Tr2i`=Y(^GSfse)8mbrg)K at kp>j6%8`D;YybUOCZ`{iAx z%^b6{P4KrjdZBfxeW6viZbpHZb?raL%C7CnFwX%MFRTr44lhW2VIP>2^~o79ldi2m z8GX7jGZ7xO?LBl~bS?MbbI>a90xLV6pO8AF(NlWM7dCMeh>Mmv;z!wMtV3O1HPj-c zTVnjkQt~D;Ud(+s#9ClgxncGjK;q?OOZ;S=s>Ie)zD%3n!uXgn^p>Ix+Tt-$x*Xcb zmw71?j`m=8LB-W&Mo#*fkJ^}JtrFMWJDkEswX6u at rNppv?wZExJX`g4Qf(Z4gU%h{QH!+xE1?vn~k)bt#a& z$u|uK)+zQ7i- at ryBs%5H2-$l$Y3I^nd-t~NLo}VlCY>09eQQB`?7_mit!&(9?3=fI z#vLAU9yM50iPJN*Q~Rpe-fE7YEyb;ciMvxdp-+HrWO1(^2f@`8)HUHgcx8JDW@{FiY6cq+M+rI#6US?FXe@&ug<%pwVhremc85bJP;4N#B1;oIMstvqSr ztVwX0JVE*HH>R?pg-WJ8l<#J2k35#@D}%4+*XrOeDf#<7XX~>afFJy5X&dzuniNB}d46M9y%byhOZbm6;g%haO&LSvc2!#`c1Uskg)^W at yW z57cu-n=WvdO65en5SiTT+u!gZsZ=9?YQP?Z!4rjL1He~XCo+%l`fx{(I}q+)SHN#f zrr^QfY4_EZj!oEy*2 at i||6}sdmOGOU-u at rCfd-%i)=y{)7W-T zoBj=wv^WdtbP1Npvvj?kZ&=iTj)FpzK_5fVsn#JD<3mtZL6b#`lxX;fPVBZtVO&DlWF2a>pPwOkI}B zo}~_TIX;qJh|(?`-*y?Vl~v4GQU#zId|%bi&Rsj)IWoUZTvuG0SOY>8E7>tICnMNF zv8r=LAYt7WD4 at d)Cfq^71-es>NUAQpZL-x at Gp1J4&zf+dTs6gUZp7L-#5(9_sRBHw zq|Dqp=Zn={P9dAlkDu*Gx?5ohJNq)~LgUx+YKGoA zl5y0)B_-MI2yijIR#HB}BJy(l6skR6Hdf0nfhI%ZOo|MoUnV>2{EFhj^QK6T{fdKng_ at G@qJy#kAg* z*X(Ta2i%J%x*E< z4!_|xf=0T|PVx1EY3eMJshjI3NtZ0&+~;?-bo(S#-~LBC?C>MN5B at o1`!;p99iUBP zWug%inkiKpoGwT*f_McO;T zT$$&mZHc1z#0b|JVTM7Pv#%jlykxQwMLIs%$!L9jb!Sq^(V!7Gi`6b at 5%N-qw!bOA z`BcPNtZbr`qYZZTX|hoc=~m)Yqhyh>JM$VaYL*pI6*p}_*Z^sXrbxD&?Oq{NHl9r0)^)g6G-aKC;naXWM5vrF3*87NI6s&SQnV3hyg1AtJheSXVlqrqRH-6 zGa^w|Eg`iZSkeo;yqvJ28?)SdId8wBr8w~vvESf_qay0jFge*8I#yLlOGT-&I$}q$ zJl7<}s2GvEv}V*wN*T>>8ECTENMa(-n~hE(*s1gg7kVhB$gy*&K0Q?J7p)T_0+pYC z#rS1584{c=i%AtR9JP+a)Teh0c*ZU)I~ zkb;6Gc1+X(2U2Xlv zgb}yKxx+>iNz%td2w4`;wC>>AdU#`#OoT&pt{G&yYM7iIF{CTlHDA&iOfFD&MXn-C zL|1uYQ`3cIlW$PLw?o{P@#4CL%|fj)!8(&khVjOU3XYA(Vf!yEN>pAO=R5qw1o5O~ z6Do;i6rMIBb;J_7=&%$iI_ at kN`7K5fUnNNrm??L;b0DKP+qfhP1&4I2IPKUW=Z2bY z*EDWhvqT-Uy7H4*mMmw|;b>U5n<=V0tCSXw!KPzF*$O0X$+^oJc8}E6IDU2!f_DLr zKwr$5sUT{^EaQrLa4>8LlX+vz$P51O0g z8A^?)uyU~o&ffH6%5$9!%FT$`?luBSKSSxa_(l;Xim}zkv|KkOF#QaX26Mwg(xt-| zDY5(l9JTq`LcEoWt)O8|yW~Io;SZNKsf*TTg$PcVvlV=Pal5OLwStiDOtxgb=|&}0 zC^SL6!?O}Hp0tZ8Wkpj$cLH;+G|H&;d?5pB%Qi`b7UE^#x}@64uf}CLNe8kylKHMj86B7Tz82QJU)?qJo*Ah^ycxC(3_-XJGr8?71f%f z#$5$Rb_2#FQEF30yx_jonWo!tt<4lAfhc=JPH!hhOEPLqE4WCsKx{QRyIkg%Xc0e{ zu$W2ei!kS07*UwA4&<$K%tilzK#>)>J4ft%uF|Z(V7razEnm8k_*z-$GEX_h1vop_ zEwSKE2D_Xl)t!XFc+X$lsuZUrvxd|mb`XuCCKJ7kuMsGd1-La at VEZBB95cdYf%wEpIXgip z8Y){?N!eXqU>lcWtO!Y5$R{+z~VYopeOGb5)Hg-m^r8Q37eF?=V%FRvD$EQ?_EiqXvm)PnOmyxpZhHnVTts zd}N6 at H|AaW%N!ETo_ at BWqRF|W$tf3>5m9%3+nkT!gj4elyFn}?4m&EDX!Z>15@)#L zw-ifyvMVWk%bWEim4c6A3N<%rJ2$1!L^BF<4?i7l(%*)HHae-Ev*&9taIQeiGXZOE)qiT{`>ReW9;+Li>EJ}w|GRh9k`Itz#QZt>-n{706791poewO-=zbV2N z=}!C9()J7&>;p`-ROQzPYqUpea9){lj4Zs^oB?~OBuICt=w6f=<^l;^3ct!We?shh zi)%`Bh+SKrA$zMh<31F%1ESQe*1{RIi5tlTyTZecYlt09mmJ7%LEUxf)pg1oZ&c(p zE_N}lODgtpkw)?w8|T((GYg|@HP`58DcW+Y=9LQnG+o at 943tdri|MZ0W=zjI?}`97 zQqDSZFLtKGvNzkD>F at yFG-YuKP!8V&5lKR61MBDSwvD#5Ia-+1R`Mrb%#dv*&571q zFiHM8%u at Sx7(c^T0e&^MGrJBPAm5#~Vr=f%xo#J(ia?h%yz8((v_4;|_pzgk&1|hZ zd|%wH-?yi&l3~UQIkR6}wlyrEZ{-~Y7O%st>W%qjn!hUb`gXY0sIkaNzGav%uaH^- z=C at Nv%;F|2{gI+4y7l(*3vnwV-)`+HR@=l1b8eT3*YvCe$2e-S?aocC?{AbT-FTV> zeMKSL*BbVNjpp|aiM$wB4rF1!eB@`(m~=R{dgU(UqK$s1O=MakjkUBc^X+yP#j$%G z7Ti5~``KP{s9NFqREkkykZ5xYx4X6yTMggrDt_(WdxwHI&)>Y_4^Wl+dawV)ONuKj5PIw~7zRX12bnC(^zb(iQ zLFpyV0Tm87q0bWNXf55ppZ zHF>HQLN4O0gdd#9i;Z!cPv#MeRrZGCI#R2Sb;pqSPGh%jkTOiYn5`60&)Qahx4T8e zhykoHYw(SD-bG_VCr6M8J&`R$sl;AACk$mAlJKUYNH;4(zwr;2K|~BQ33fiFLd?!; z7Gxrm^xRyqPQMt^wW#$39l89>Uz10SbZF-47$Yn4^pZT at abJ1!XQDwnR&q$TN6sJ^mV zT^pLruQj+5cK^@W^E>_76 at B9aWBOpWZ*+Vl+pq7O>mOU0w7mOPbDsq*5eIWa`pRr> z$CYDSh{vFuiiByCnju-PM{9I!%|KQkAIc5o#&UfFxnE at a?Xs-yG}=5Wn{t+cf2g~q z5Zu3q-+ym+-rA+N%Uj(kJ2ILZ9#X%*cnSUMX)w*DZ+vXnGd$#}R=mS2R(PwElS%%%%`$dPfwYh85Ux#443Z8MP&tX_ z(C at evldq`O9Jh5xrIKh8t9QK9z+P99gotl&c&BV^YU_IyjLV#e<8+}7 z-IBE6OR88S#fwm+x{@RS_-%P1YC_aUhpkN=eZ!f6O_<8_;o5kGS-7XvfF37#1!fcP z$L1 at g8RD_fjgrPq8_r@<##}sx_Mdnvp$0TA3Y(LHBW%faW>3-qEHP!`%yquuY at kuz zXiU&TR9QU5f=U5jcJPVsYSeDd-ZW=y@Dy87$28YSjRl>7 zM48v};X%yxu`EehPFd>SSWtF#Zj8mI+wmdpH8hqT2{Jz-3%Dm6Szu$Huby@|Ar}+{R z1Fh8ACT=z`k{tzVjtfk;MtBP~ww!EZZJin%9v{u>{g|`*@c7s$CYpUSX1Gx|={S|g z*?DC)J0K%Dv?YmHs=&PJvfNNV3LBT07+>xm$c_ZED^_Hek0nvu(cuxS1w(y1W(QI5 z2oK;58+J%ug+u`6c+isTiY#B58|c?ZvV?WQj%#yq>Vtv4H3G@>$K>l)d?W{Xy1xE? zOqfuQhlG at P%viMCXtB4h~c_1Gs}gv zGi{!`U>}^XZ!DuQyFw#a@}Xg!H=RE_xN2-oXyx!o?iZjq`UXPwMJ{b8TGy8M4Ftzl z=9d3#C_6f;4&;WgV6S4&C6CW)ePj9vEwyUCK0=Ftx(fY3;dE(wd^X6U(|FJ*0S$v7 zi^04o4tc4_Wgxp^ERe6$@oR|1VA(R|nJnYj*vj$2W%{b&QBVrQLs53EUtaPKAcL{t zRsK9(Y2x3c{Ma`s#~{L&4Udft4{G`PGIrZ7#A5M}u-kD=RTclp*ht^%ru!;b?IJ6)ef`-HtU|dJV_~jwaCEvf zSr3RQ#Sbr1d>pZ>OJY`Wh3by0OM%hlBf|p&THmtK;eqk7td^fJ!A92&rIyh*a at is# zKEDbh6tgF~Y<3~*INsj=+pF-~3KwYzR&?`1rntWNx9xEfDGUl}9C7$pm7qw<{j9r}lnK2h-;$CuxI5 at U*HakSvN2`mitqAwx zEf%iZyIf%&z))DIZta%&R-#;i!EAqSe9${Eyt5ZW-pA$*AKQL5nte32d~~GgBOK-< z#O1++EHc(DU@$Jy&(6ohGckFq)0B0_n?WI%UUvL*oMjzOj+*7YFE706R zJc(*QhuSy#ef}isL<1A9RB;^}@GA$MTuF#fMJVneZ0YtCJL7yYu*mSu-I3TP0`C+a zaltoavI!59`hDCw*4(%FTy^xblfjU^Jsux%zuV%+Y{>Sje5LHyRtzs69|hhDatqdR z?Pu9F46O&qGQMh4S4o;5icYe16^Ps-qCz$gXunULljT9L`iP?U5%F&8Lu&V`hU2{y z3Zh-XZ+*kbpyYjO)oR_(B!8S5HZA5Q>S``h?KHaciV>vg-T2i=enJs4g(e+(WhQT~ z^_R?{{E!#mH^B`WVHz{yXqw$ULTx#-EIh-4E7iZRFY)aBt3ckQ9Ya#9 at bd;*XR>7Q ztx#gEJWH3VC4NsfU^XT&(v|=8{PI72zp9oh$}dAgaVcs2tXmIS&fjS3_&{t-gZjhr zG!ka?tG;5SZ!nv-FID6m4-aqR^lpBYIJ#;iYj<&Xiyk)dmNH8rKHpt3=`6=N2)N>0 zdC9kJ=$10naqiR-$v4|DrvoOj3gCYo3X+XQI~WdX-3=TdTAW ziR4l7mYjI2QNGB at p78QBW}d%sLlOi00pKyIG&E%2&9gPK(7a4Q3L%#m( zcutM0m^Eo)bz<#d!St-4+);>dtiol5ojL{jt1}1=(NJdZG?`KUUX%Dfdsy5ws|6Bk zm3))CB?g@@tocgdmZcD*$eZg*CpJ2nZ~m|Md(DuKayL8UoPmNh}>X={bKtc{b`?jy8tCuycVsy;RwX};LtAE)H%*;{41{lF- zDrU&~v>BuboDif7+^%QUf(_ZnQ7J^n22#w at dds!)itGF=4Wke>w!@0`D>5Z?4Ko1= zzCS`EKSZkQR^(?&2 z4%0qMBy^=7C@~{Q-u$WY5;&VuL0t>_0mV?upv`F?e3pv at Fb0HFZVcvYn6**R62#JR z5yOax#F+)Q>&2sKEPGPFLtmy{a&c3HnyyCtMUrClIraX=$!AAf3vZO=A+;Sdmoc+ at 4$*ge}hOJS&v7ti&vhkyeGV z49OE&!r(+U=co8qG8K*qE43r7BI4mY)S5`6eY{uZC{Y>cMjN2Ief({gY5Qfyy-S=f~&)~cd|qL z at -1O{p+TFq_9ks!YO%Imds^NIvVy#z?gE^)`-ZBFHYOOx=x`w_{wB+Ow`h#xzOzzL z);TUq_pujNnQn&3(gvgGpDb6awKxqm#|XppoFfJ68W<_2mfN>-crfb)C6aMHwdZ(e zVS$s3X;xAph|vw4Ye;5=h0)fNz}vz6x_pVf0Hg8qvRr?hx2wH`mQ1NIC9*s}n`bl{ zDLyZm{frGHtuM62!WpHs)ZXE~Cf?JghlDPUOiTZWfVg}n8$*(I-v71WcBLg9TxOy7U zC(8NC+72Ew?>sgV{Z|z2ET{l3J{9E- at gl5<$!bKWpVC=HxKR_O*f3KLvSp9X`#RdZ z*%7l3XGoZ!!+oqli>SHD;%|7Eqr+?^ZDR5!8pm&bM(KDbC$KD+ir}P^i<_VA$SmH- ziyZ+jG96#+ezSwFGnW}s36eZ8UzI6~5$CcUpLQ#0i<#^`|8jb)o>VtbCPuNd#x)Oc zlq$7aROq%^nbVfb${uq7+a z^6zS?`?7MQX1}~3YaO4K7aw>#hZoz=)>AV0IG zo1a4Ffr)1C=p@&}#Di7gipdv_xwj^raqECYoHkkRsJH_o@)53(EyNdfod~MJCEh*~ zXIa7_kNtv{JIQUMwkqwg!zU`PK*+4QD< zA^Un$r){+t&B$WMOF&V1fsqm5!Nt-UU+jJjD8AVJ5Kw5b^#)Ma;&#vf#233?{)sr> z^$|*UEMiF&h`gij){(*P<1`TspcpjZ46|BZXJ<-9;+mYSJUg4xu?luHc at l484`HT? ziXBo2MSn~&uGnp^OH~|ETLUrCic*m;yHX()aKx-B!)My!c25 at Bi&4{SMS;cap`z5{ zmN$xGi(StXq3Djsh!TsN-XZc4iP72SjUYYU_URx!)wDn2LkM;fY^`9pa{5u-d9n z6yXK!lbUdxaGUIuF15~azRbOXe7lZ>x?Jb!iBbjpQ_c2J3-wwVWN{#0uEWq68Afil zJG7b`YkKVM at 3E%iV*7Q3=8#m$T){Z?|y~QD0$#WB>?h-8Zv#G?#GQUp7^@PidK-?v7Da*zgcz7US0J;rS&kx6IM at k$KuWzg at Yq*(PhmX025ta>>+u zR&9j)OTvq z9^hNH(Xump2c18dH`glP3E3a=W1%h;1Jy}>)KGYLabkx zi+{8fL)NPU)%C>un1-)jYZx6epJn!gXH4>dmz15MR+(7ESk*t7FPnx|6wHP{PiLB1 zz07z1w7S>=)F!0Qgz#Z1cIi`g+9^!ks at 1@Ck(<9bR`?TYObX=pOoPiuvd76N%odu?S~z5mu5TGY!L0#=n-a{Y-?)3vM+ zyB5i+b&uTc8c%dcc1>m)sQEbk(+F&@T9nkjLaw-9^3kfEkP zg$WsJ;#9$U-;cJoLL1-zHa2ba z(zTdgPHyyqVCuyh-aCPnn^PJ_v3FvwH{WY>95Q5a+ at ebO2Gl}s^h(u2y)zAyWtHz` zBcT^f%Nu)fM(&9A=F22cJ=bf{gD~de$TZ1;n*4HOT#9uH)U3&Is$rhecFdfg2?@u9 zDh>Hc6Whe%`&Zhkk>MRjvZJF>i_W1D>dy_W8Xt=g5x5R4FcH%MPPzt3&W20haT}ts z^@cXt2XkW~+Ew8gj%~1T4f_)_agxh&T#*l#?WgDx&DVJ~$=QbNTk{d;b?1olO16J= zRp0V#f{WAiP1c3#u?0ib-sT4Z%-%_w?29w5Fk0tDz-BIAPL{DA=fPVvXV?-WI6g8U zF0OL=)uabRxP-e#Q=86o5<)7lQUce;v_KY}M~w2`K}Q?0n$C7tMTHy%1ueDuQ6-c^ z$2xV|4%DSpgmRM9eP?X9LkM?PUnq9Ya6 at GwMiMK)~hRM&pc^0 zM4x!qQUm2qv%>II8s)NQmv4D?*~PQVglCsY&n^|uE;Y|CKrhpI_GoEe-Lqtg7sQ2U zmxl5!Wfw(LB1%*VYU}c~659gjd#}}+@>}J9gKW#67Dw+|V{QRxMA~lw()=aO=|#OD z>-q5Lb%gzIb6DCATiQMe=Y1Su-QyL5g#rx$3-?whdl`ilcK9oGM-JbkW%hfH9F)mT z9HDGYbp=JSSzzxBIw_P%+KAlC6gcUnpaJ?p>l}m2I{gEzoja znk*G)cCty-%U6tEx*KT%qjFNam(5L;Us>sm^_EOFo!JoEVpAr at zbzrF-U7c$!$KKx zSlrTC``)b#yQQ}K9`)|`7JjdHYvsqFbH=gkF*---Q&ybdODkzL9ZE;i at pMNzot~fG zB)xh1+v)G6w at q)C{(kx=>E-EM`se9i`sby81u)a=)4xq$plBB<+GUD%rJ`M at q;FTW zI~DCdMSDom9#OOx6#u-;OG at T7nA*FFc9bu3sSjS~`tfrOe$r3-GtZLaWq;-sf96$x z<~4ujb${j!f96ep<}H8bZGYw+f973(<~@JreShWyf96Af<|BXRV}Irof96wv<}-ih zbARRwf96Yn<|}{ZYky|fK<0OW%x;0q?t#o6fy|zP%w7R};ebHqpg`u2fy|!*nLh`# z;{)1>f%H>>%rk+^bAinB0quoA=G{Q%{Xpg;ivCGJ`yA2L%rR=_&#HF3s-2)_PFE3f zmYO+7&77}hE>N}0RP81;bE}%UQ`PQPGxw>P`_;^Y6zg#{^OTx-M$NpaX8xvTURN`3 zz&pLCmN`YsoT?$l>00JYEpxV(Iakxp*R%_@%w<~UDlK!prroGzZl)-A(9hlU^APi-nE8D$b5JmIND!@iQ!sN|Fmp#Rb0=Jx`|*?hDwz3Q2 z>F3Kx=Br5h>qus|sJ2H`+b5dYKML=I=;!cg=7?zKsA%RGa-9~rY=uj%)nshhTOq>Bmy($5ZJiQt2mC>8DcZr&H-?Qav za^ov*>>6OkZrs?N8+&kLPZ&LWF?)BY=b%8(>m9Id-t!#!zaQw{uID2j5z}qYP6Tb+ zLo=y+dCwba_XSGN6I#z5nsxz&qUq4g=Lw3xd#HzIK+k at mo_)xq835z2WWR{pFDLue^m8r!Tu(nVb_*9Pw|iLd9iG{^p1^}1|DB$PJi)|0o`*e;cy9DWcGyFC)YB3A{$rl@>C at Wdo+o$? zKYS8y?J3XG-1(zt$oZ`2Iqv-N^W=QN^CEXH{Tn%7^1RHQ|MChsU-i7ko&WkeIp6TS z$({f97CGPcyu+RU{w|!M_dLq`9&+xX-01nq2NdHtkMbe&`sqjH{MhpePtf-%IY0A! z&YjD?Am^8!uefvh*W}#Q`#Udl_V4C}b9e6^+?m~zoO^lq=FS!SkaJ(}e%!g^{^b0< z_WO8Be`?nC~_X{J%&4njwR=x zy~lCqs^iIdg7-x3{P{`bJlXpf?i~3mIZyGP%AKR9!5JJs-K(&!A3Foi(3xK4EU))$ z?^&Lm&+(oMAH}QuGIYFWVMsa8dp^AN3n&T|Wgf4fkLUg^CGe!sbz$BQO+R~9PC-IQy**TVZcZ_5Br zDyh&hin5^VdW0XX+~B>D^6Cnmgmmi7-do^%qH-(zQn!1R)E(YC;qh1HF7Msm*0C5o zRZ;Hortd|RQ zS5Y4Go~=BNpmUTbysqH0mBa-|{E+uaBtFY?zVej!X;+NoMasfwyb4c#q4KP^IYR6? zukyUe{$k|?clJu^8sz?>2)ciqdMWIB<+Y}i at lz@E zbryPq@e8w%EOt8#+pZG^vrmJZ*cEPU6i#AuM*uDr*3KYR~j zo#lzW at 4eW2xAFn=3x4QT?pIRKr zb%rn+x}Q?W^GW3k>e}w)bIAT#CH$rLEBJohGPHub`jiD{d4A`+-ut4mn-57|Qi5+P z%I-ckLf%mJfM5JQ1sHNqA5D{YmA!m>`}Xl!8Jq!6WnUk)?gz?#l*5Ng_zNoD{yr+n z=gRMWtT_(wDV=}t9q2pD^ObUtkE#_q7+!}kudkIunO9^Fk8+r=le~8K9Paz0kGF`j zpGWBl9_%^7r+lgW34sTBj-*e0se}*pu=YF3cd_>n&(Z8t!NWbuF}|K-5$`b1pAj#3 zgr^6Qj`JPs`=jT0cgXY^(Ry%sd`dspi@=4 zRJB`GGpg33steTMrmAN%HPovHwopCaQo~!Sf$ylE at 2Zil)Zo@?aG at I5M)fXIql?wp z64kT4s_dY~f1rB)Nsawb_5Mip|Fi1k2OH8P+^2G!7z>KRr&tJIDWH8!fo#?;uj8rw*{zqMKp`vbbG2jwK^<1i`n_dp{3Ps)QDwtO* z!D|#Hcm-M$Q!BXAqi%k^qHcZz;6}hrfSUog0B!}`rl`GlDC!n>0qzFegDmb<)NkDn zctBCr2bEyZqkj8gMcwjIz+-^N0Z#y)1UvN--q7^Fh7L*BbXlpJ^_3R|Ic84j<{dIM5VU=5^=tQ9VOrT|EkK@ zaPI0+7yb^}?*?;sm}oX-514xb_5$n;*axsL0BwSyu5PtI;P-GJ0P_zp5A>*89|ZGY zz#)J`0f)iwaKIk{MoZ}V1vnew=K#({*m;2SVZQ+W=)Z**;u{yij{a6I zhW{l9zZ7s8?3cqI9l7-tFt3E)RWPrH|1~hLg?SzPu7`O8+&9906X0gpZ-L*fFfop{ zz76K>fI9%_aOF-4hyPtLF(4M+O=Sh#i!}F9nUKc=fCpiJ2XKya4x$aQ_YAF9BW#yaIR?@EZJIhxrEJP1xT;{I>z`!2T}$ z-h=&p*gpVZ&~E)9{62#H6PTaE{0!#jFuwqNiMU_E#2it+hX1Z!b?e^&JM9M89riuE z>cTy}>Q;Nfy*FSVxc7y*AHw#BiHWiG?_oax_CEj)1RMnS!GJ?xKNN5n?1#hrBhnrL z^H0d{NcbHE`_V9uf&Z~E{|xtWfa3ut08Rv)1UMOCe}VZ|z$vhw3OEgLI^1Uj1+Rq($Wa1HF&0K8zEXd&1lcetRQqAHcqV{ouDh!hR3)0Kgw$ zKM-&b;9$59LHt8uKMdyKF#ia at BVhgs?jwEbHb(=F0UQg!xY*{;FpmSE?YB7|<_Ulk z0Ve at Y2B1y0`3uay0!{&(3OEgLI^YZd>b%XFFwX*<4LApIF5o->=HNEKjoX|L`vrgt z0T%(#AKL&|Y;&Et^-^TzZ>Ab5&k#9ycuqka@$*A-U_%4fKqLHJHj!dl{;X+6MlEWeK$`%)V15YmBf!U$zgOM%6NG;X_zdtl;0uJI_ZEH$_zHEv=-&2g*mw0~gaLN* ztK04lN@)+kp0Mu)b8ndYz}y$+eu%R_>>v)d{XN_V0R8~?fq;WxKN#VMz&sRi7~pWg zAK`Zd%s;_A5^xmYXv9ATe#Zj-3_C{9w#UJKJm3U`pXgT?0Sj(>66`0#?=KVw_ETU# z73OI$PltI1%rg-eqj%w1aG&j0J?Hp?7b+T7Cq>=vJiog5e80Nj0+{H>?Jh(<7Xv`# zEXE43-6in9)UR%L8Q^lj6 at V+@cNGA6dag#?YXR2*uJ^0k-{4o5Al>#i!hRFJcQede z0LW+iTM_3rn70Fv-}ZL^kf(Ad;@w5 at z3TRN!+sC)Kv|S~5q2N!_ap29m{?Vp5axdn z?uQWeFyIluqkzW{j`D2(IKogy1+!@TCtyAa_fvj#hi71a*028HIhfA_Uch%=g!^xR zm*9RG at Cx8nz-xfl0dD}_1iS@!8}JU`UBG*Q_W>UOJ_LLO_!#gB;8Vb7fX at M60KNo# z1^61UYd}?g2iOg;J75pMo`Agp`vCS0sN?&=+#m4!fVvQ?weknp4}=}7*g~vS%E53S z0yq?K7|8X*18VOPupbFH3UD;w82BFxI4+?6=Lvz}aRHCI%Si!s7htSiK+x at SGVFf= z{1tEt;8ehAfYSkI0Dup6ITPktfU^PT0L}%VjJuo%I3I8U;6n08+>2md47dbvDc~}| z<$x;yR|2jATn)Gea4q0E!1aI|09fUBxe?|~fSUog0B!}`2Dlw?2jEV?U4Xl(Jctj< zVV8SgzZY;H;C{dZfCm8&Q5jLDhpGH9AA$KOl^OQOU_K6b0`MdNgzPV$4yd7L(PmiL zz;dhk7XxbkZ-AEoF9Tiyyb5>?@H*fPz?*=#0B;9W?>hmtMTUjuek)v4bBb_47V*aNU9U at ySlfPDa)d~nx1zoG4$rqjhHxbb2Dg|9r!PKsRwy%IdP|rmEXX9Jv z0?q>vQG^9lxe)e?02c!;0bHu8=H&pigNgc^R{*XApbgBc09OO90bC2X4uJMB(Jm(H zYhDjPdzfeg6K!DL06?8h)ZM%hfOe?g47df~xm8uQ+f=o2JIZwj>V220uD=K7y$E|i zRTqMIP##j%--6&&9)|l7z at rFz4DbZvJ*leEr(s7qlxG3YtE&0}%$EQ!16~2Vio9P_ z)!mgh>E|u at yp3GmQPuFfFyDjweV8ApYVt#vSZsDzKBAZ()6XaL^C|s&Mn9h;#usGy z63M=T<7=3^YU*yv?=<-Crh!i)%O0A#hq4!3dy{J)im)%)_5%vvAMkqw9YDTVt9DoZ zKtTt>>mb0vfJ4aRQ2IHHA{|b)Kavf7x$p?2{}cHfNk2!?&(TP83|Wq)pFhKO99fQs zvNNH&~ISD=|BmG}AHT+ji-IFrhn^*<~q>uMBP4%1sI18cYXzKnH>N!tSe@{!V z=K?Kwp5{>xP%hH&b20r~qN(0Xk=^A03>D=HxUYo$YQQysYmwP?fEzURU}72;+ at u9> zKpqOl^r6Zv8h$`pF1(d2=Mue377+c1Dj(C&?c at YMakz3PGTVdtm>5N$dYA$Valu`h zdW3QhYIHB)KEVBe2Q+oTgPM9I<$k0B;_*o3VX_ at S#0*)$I~}Qj7(bG7PCbg~k8A2N z%9Hf-6#YC+KhMz5v-I;E{X9=UFVN46^z%3Rd5L~rrjCAvY_HPKYxMIv{k%axZ_>|O zDEm8rcLDDK-UoaD_z>_B;A6lifKLIR1HPcf1bhYf8n9~+OAuf;z#f1-0eb=V2J8da z7qA~-f4~8NKLphU2L at I3;9xN0Q%_P3h2LR8SPrM3KL#;^$nq!pIg);kqMxJb=NS4q zmVUqyoTU7jY at n=^ zDA%jVay9+X&ciiixfYSGBg^&l^E{=y0gZEGP(7L0&6Aaz$#x6nz}C-yi4_{rz;PjEglTQf&`~455e^? z;1Tk96qd(=>S;9RPgfo%uP5jSB>{c_G$8d=Kpc`Ch%1pSGssrb>G{! zy8CwDdx6+N&SLC%A&DokC${4_PK=$5C(I;q_Q{MhnRt>(X36a9n>PbmkOV?IXhT8* zBmn|RNFW4~5JEx%iG340hoB#5ZoUc!vsyelusybEO7bE#m z5_f?)Mui!W9;c`iB&Ma{B!x~P{xmUX2;k@~A`faYKTE;=6g-C%=PAzx5_J=IkpM at p zMf?)UE)!fKfO9#qha%y%O}t9XHRQfdQ8x%~q9(V9xeewHF?T5u^-(V|_oPwAA4o$? zG38uz$r6fM%A1M1#lhjMy_D~wV1fX*f`SbMJtRw^#P=y!M!{&)DCe=JQOT#7Mg^%V ze(aP7OH(H5lGmSP5x3P_G>&9#} zjg7nmjo1X*Y#JN*79`w?z&4864yHwb*#TxJXcuTVQtZLAlTz#@*oV-5NDolpAeckM z95&_Wq|w2TLIB6AgLi>}XTjkyaCli0kC{e0KMo<>st$ev415O;zkW>P8pA;)TaC<6 at r()KSqKv;&p8U6C%Aw*c0&vohNn{0 zMFilJ at p#g5iK=oLNv;sn1Li8|8U?N+-3<_Y8$N^5-b5gsm|I{nh`CJ#+%XO3u4!!I z_o%(ZK#lXjG&U2%r&_2vCAu<_E3Nfh!X$0vIWe|g=@+Q(i&CMjqECSj~;m=K+O at ST4zhOgW&~P5>mfiPq@|O$60{L)K-5N1JEBnX#10Z}0<#$c6gF`S3ATdS20BLz z2%g&!xsoDwAg~hw)O_MD&~EUk9aXNA?}4C`n7v^3f%bzAAhwEx2MG?*s6q;_XyRc! zkC5;vglO@^F3>U1anK3SNz2eqA?~bY1kYKQ z>Ol1fXHwz at 1lADK2xcv49jKXf!`qs;o)~y7M|mryX(QN3a8QSg{ET)a?*MHAZ3b@xTkqlr5IP7tL>dnh93ePL321BYqR??9I6;zA6grL2 z8Dh>7oI~h5G33%*K%kQPteYfDeAMq$3S5GT7ZG;}bj3D~^J at gx32xZNF>2>=ev^VT z32qVGCb)wPcWoo87kzc#HcrwqbBa$1Vr>lKd=)g#@Hm2L2*n4DvpkUigY=QR^#cF?eMf;d|T<+p^LE{#$CRiRc#;gd+f594t at EyJqvCE0c2+9^oH^6 zZqSIjhwuZ?6bDugiU!4iVjW|A9G(fFM9_4{NE0&%k`S6nOfr}hP%0>m1f{x>Au=3e zB7B7mk?8=|bHFU(W)ox~X*MW_0=Wcv1ak=Tk+A at Ab16_rP((0~U_Rm&fQl(lLQsnO zEp&`oVlhD(@+~1|DM2|w1rk&`MwYIoVM z)(|ujtVOaWV%C9aCZ>g8J;73fR)RKy4UUl~+6g*P$R^Mh$0!inka0UP-DE?K@%&B* zc7b+-_JBIkyuF}(h}#c30NFv%A<$ujk09K+K-N|aD#N~kit6SNn!A9Mh85Of%H6m$%90(1&=26PT| z0dx^`8Po&12D$;d1-b+31>FZt31R#|v7oq+F^9*8a2ie^NDScsi#o30(?d9_%^*k$ z8Kq(-L2}4gNZdl=77 at 3IxDK8|kO~DNEd+1y4!t*k^2`wVQ5f{u2xNh>K{=pYP#$Pb z2-7)4+ZF*?Zy_-lr!vyci$L>2Sbs_5d<2R?rJzNi#h^0K63|jmIj90u3914u2dx0r zgs>E>L`k*8tOB!|m^v`^#554BLBdART2K>c9jF=90$LAh1+{@TfHs2KLx$)8Z3!91 zHiWl_qAa6E><4=wWCRZ4xeV4=1*!lYLijM~2c(nOPEev7zDD}c#gCkcKBG?1d+ZA#!`~e;>IHmc;}UXS4jB#N z5{Cq*-Wq_c7nUp*R6!hag1P=(J!*UP4$&G>X%jVP4?IT9Kq9G*5$^?!=SAMw8er z0Eq;t8Xy}?fiAPdmdpXghb)TXNAdGFh7;z<_QqV%sqA*%Djk*j_Pq0?RQSGt( ztP`d5DGpLqfGWcfq|#{7habxzsY3S{meDM*!|)oa`^vDqU|F);qN7n5Mi-VTehb>l zg)B!^C)PoT6|qG$gpKv05lYsEjaIP^Obci|DQQKTHexmqYz$*u+7Z_Q+5*}dHkQyt zSt4$7z%~kQC%Ekg>>zF@!7hT`1bd*coIc7y?!BM`VR;=x$`67+gnl|4mPe>>y2A1p zJ&%Xw33{Fk%TwsL)5M=b+B0E!miTjFd7hpZ!m^v5-C=o=o)^RN5fQ9h3?+4Oeg+cSSA at 5SH$f@&#e at tn!Jy|l!%3vOpO5M zb94)xB%>pSFD8OD4HOrVbC7;D`5N_t-VscL^gK*79?yh`(I%!x(9<&_axYB!Fz5iN z3v>u{1oQxyOOC1BN9V0JB#=;uM%1d}&23-O5 zfbv0CLDvy?4bMAx-oW!F=oaWU$v}5Oy`X!bM*mop2OUUzAI}G%Yv8Ah87rrPE`dJ* ziUz+J&lo%};yIV};Tel?E%-P*3-PRizMCWmB|w%4nhu%)>V+%`Y0lCU;hCT~@F{qv zg3>_gpbXG4$d7<#floo6*?8VUI2+F#P%fy^7>n6CR_1{(!*dRvr}69o6;ORZMWA`0 zQ;^NabAjT=LH{_k>m#W5V`%?la)fgPmd$*<4F5B`u}mHf80%Wqdp4D8Y9<_882HwRsJYsjFL at bqGU5@ zD(FP`2`GL7il0FI6R6Y^Q1}ECJ^_VKK;aWm_5?coW61k4bn?fL=i{JHpz@!<^OK+_ z<+d>spl1T~On{yVNILiA->@ zY;T7*3%yUFUQf&YV?G7_pMw5R$phdIf)0Ut!=I9e at w|j*3gVA|?*U&Jc?M~pkw?co zgZexJ<9r6~dIss9fdM`PLxc=`a^zXWKZ}lk79IaA;-7_{XA$=-4B|Po?>W@*IT+k? zNcS8J>N%A89P0WU(mscDl;(Npc^>lT(J#-#2%d+rKQBA&=aJ at l=zAXNo=3Wgh at XhG zpN9O?kbfHTPe*+QGy2RJENGvRE#fos>p})Z)MsU$|Fc+SKZ_NA3|9Lw(s}`#=OSIs z@(Y>e_se`d5Bq-$^Z$Xs#`ifetsJmZi~1ZE!eSwh`hD`X7vyFC3-Z#K&&$hWJ|FdY zd1cJ!QC`m&z|}GGkokGp<9{*gC3(mHlI+l5!e;f7%*3-^yd+B^FBvr&j&3#D^)Y}O z1UCs{4Ztnp68wPMV`QuW9b;aScgMVh(ywUH-%IiL#>i5ry9f0NpgZCvER69&Ci-5I zv#pn83g{lny5^VirvJBb4n}jQjKw(^6b*{O6f|Ctd4`Dkg3JduK^S!!PT0K0Po4yl zG{Sj3NT1$}m7f=KiJ{AKr2Enf@{eASPrnSGXsqmjV4ET2XI_YUMZWN={P_#=AcBXD zSLJb}pCsft;{}5!Lt7T8aI7p1e-ZvM_<10@`6btLKKKP=<=d~xlEBOEZQU*NxmiAU ziqDLs6ue5VQM3R|CrF at RyO5iaa~EiD;7d`jyBh*-nQ)hAZn_WA=>qQB z>)4uJhs*Xlwx`#jXEyD4X+qu$ypEw+jCPcb1yeCr?uVK``Lg`km*x9kmVf$XnIZ$& z0>{GnttVHlYOJwdtF@!vkd^uy#zyUy0MMtmHPEpVv;tH+7Q#jiP>H>C6=_~gkmxts zv|<6c7eFL=>uZsG3S0!OX)FM~cN@$Hu#SSw1TAAx)m|zl4|d{z1Ge%8Dw2uKvVp!_ zfr1U7jbn|8?RajY3U1P5n$+btzT{Ta9ZDjv-=i$?=AG)TX2Bif&=`Pd?4S3NBK4RKzt2` zhONc?x{Np9#(sYYcM5x6 z=y&9U(C--jv&eT6`<3~2)H`VQJMzldcTk0QP=$9;rFT%JcW|aas_UQ|V^P(2(8X7< z#UR0a&^*wBFcRHDg4>`wpu3=6&^^%ou`<*AderaAV)N_9ZV^3BmV)6iQLtI%jx5OU(Ns}fo6h|K at EucCStxRQ$aI8GeOCqM#Ovzp>HAd zE!hNpjqt%`v+0u?%@>U#(K0sb+wcv?!6yV|fwDn4;}DfgusjIJBQ7I|`hQ#IkNY+P z1tgg}PJZXbsCVUA>s@%j?_%+KS9Y84%IOGS=kLl|?OmA7yRuZg3yXOd%UJ0+K#d{4 zE9G5N$_Jo(;yYL|zk`lih2uT9V~Zl3f(hM8RwdUc#xN4ca%1lUIzF<#pq=s2|Iz&W~j*C>j(4 z>a_!MO7IV5Oi-8cK^DSG>-IQ?$d0A&PMpiU#v z9RWBgSIzTgiG`^-3bF8H#%j6VAZL*5Afsl1Kg zUC_N?K*l*gkNO1;YrlX6|3Yq#`~ruyU&w~wpGEyr?hgJ^?g8!d|5E-N*2c52KL4+1 za~~J=D`%haE6IL^GxM*Ed>vbOzK+?OuTS|1U at 8HQLHT;iSh)~I{JE at jeol7+9|078 zMAkSzm;e5Axe1Rg&d+PdF#6;8zu<$J!G9VFwP8Y?ggSfV9~V9^74uWTKPU?q#sK(O zfScyXG6&Q@%oG?_07Z$BRT#Bm15Fr+rIuZL3gF zNjzeM^^Ig4VjPcQE(P2V%k=9K!q?xC?r3Fsp at G45KBRiqCw-_P1PW6Cz#^bA6d2<% z)RA16gNJ^g{|DOv>+5GlV?pC6V%*^51I$N=`N$A^pxloU^Vq1}``_!)|2bx$V&>qE ze0*?VNX$Slxqb79gdZj0M at Kd2!OT!I9%1+oqX(p7aMz9>55f5HW*^Im6M~S^jT)^Y zgfi*zBVb at gs6!OUhe_{hA<0F85qOUsR2WH*)A at xV0RC&wdKE1y+p9DNf71DL8 zDt}TX)B90%h?t=FcME62Q~j_JUJ_E>H?B0=PYqjJg?Rc?19j2FGlN20SVLBdXZyg5 zd#=wMIUl-kYbF$+lPHm9Pyw3Z=@Lp|U(V|0m{RlgVDsu*EnXg2Jf0iAPz-kfhdZr99E*Wo?W^M;SJ3lMUl`&|3~U*~9BcsE z;JE&+^WHoo z15T7s#QIBkwXYAA?28om;?Qj5Yl&gL?Eim-p6AH^Z*JoMg at ZQW|MxkdLnHLTTN8GC z9S)&xnzTh at j!^8NnIOa`J~8~b<|DTXlS}=Ly5)^}TpZ~i4_;clsHcf}dQ{1X?eR5Y zUi)Ch=I~9+9CfQVN7XVoqsI)%Fl1l-5;0#IoO__+BOK`w&GweIvE-+iYN<1avK~A! z!0G**cnuh!f1XNHIy5*tdcV)34UQeJ4{G#>^Mghndq(E!KLU6@^3#2W36Ff>4Bu~0 zPe1Jr!l#GdZ at dnrn)bRX^UK5S2gZs%@r;%Gxqq;sKCIM8f24nRG+&@T4WIyl|9Pp0 z_zt68%IH+=8_H8rUhJSUp+x_R+K+r*fb#338h>PPF|YI=GsIUl9j>{9S4goVGp!wbOMwuG=W+Uv$UrqzGy5pVW^8s>pDg4921qOwVpWzyjOdW)F1MxCHT z9KMmx!axTONCvi6b>8vLp8Q#@&(Xq>NO1a>QH5=LPTSA{d3Ml=iVwO0>id&4Hm|&` zlOq%en5JcUr%F@(F>CZjYm`eJN`|EoBTHak8 at _^Qqt2H8Btm at 6H;NXz!TLJg5slF` zbrjaslLdnIBmV|&Zp0IPN8ABMd%`=gdFM8773}}}o&Lizc~Nf=^Twz*IPVbi&ZzTc zAgcc)po#IiKJZW)eQ%Jf`HC7j8p$_CnXI%;Jlt8V-*hVLzpMI&1(Xi2pNxJreSKpM zGva}|PatN(U?U!K{`v+5zA at _JK5~^G80Mi)qWBGH$fEX%J{ZLk2mUOtOFltPJyvaH zz_d#B8#9H5w0hiV-r4YV>)BD)ts%xPhGiK)dX|yf!K3~{ee^%KAgha|fG#xef9g!} zK{w{Z_Vs=zNFTN-e?UP0;A=>2&^AAqA98 at vg;#Ih*V4ZALY>!qPY*qyy*w1GQMi|f zH&h?$YxGw@>3-+|PVO6-|HirW!~PAe((tN`7&SWAim`(Zw1fT(WtpyPv4Gz*POc7j zgH2SW(Ounrn8r`^sBHk-_GqtKevhj0d&6!&KK#|Rr`?kc{+mp~$wQ8=!)9fll!_K5Ki&HqaZ5a~V2$=17|y|+DcE-&-f4UD|GUI|ckl`}aP{^b zV!kuTum2tizBl^q>Ig68dsy0q&l%r;ePe#_dwt#H5+_VViHB>!cpo$Ac1fl64a_!d zg+8C&U)uNjuAtQEOCMakQZVEWXXKU6;A%fVe6e_V>iGU(@GAHHL2m*cd8&JOC)01K zBd7hqIJ~N|{a_TQXy`qtHmXDU%HW-D)Yme@?kxK*PDflK4ZBF{XZzkAh=I2){aeu3 z5tp^E^mimhKLh%5AHDJcEB43+51)g~9rZx==;POr3%Frdp2Nmxh?!Dn4SzY~Q_sO~ zX+AdeEzMu at f%!Ms-Fo!9#1Zd<240X2x^Er%YWl^ImjweGf>J3SzVvcG_zLsIAw%q; zzi)-<4+r{J9JN5X{a(R~BL|GRqps6K3wh*U|M zF={(>?dgXH at c(8CP5Unr2===6NShg8Uhg~lKYSyt4S0Y0hqR`#zrlADI)m>p^ae)5 zqD<$QzH2kg1Y2VcM8fyOANJ2FpZRbR6YBEI1LmD)|G#jY{!P!+!pF6KN#Ds@<)RC3 z)!WoCom>Nr=H4g!ZWsn%B at Qa{MNa{QH0ZAg}Hd{wJ#9y>lB<9pCuh~4v1^R z56?rdQbvC7G{X51LmChqd_-^tUC4~`J(WA$Zy5C}wBh7MjQqqz{|$`T_lU1GKEQEz z2iyMW??oQ*#o9ocfv^1B;h$0t`RHTt>l~fl^LSII at 0HKsPqrRTE8)G78S;_UU|-4^ z{B_uCgI?VXEH8bpaE6~R)pgLox0YibUPY^~g8qw7Kp$xxuswJjBi{UxcL2H-^C3sXvB6tRHEjQHtJm}_r~5g^pf!5iry#m{R~?H?Feef zy{I3J*0w%<)`&~fevJc1^^r%?7e`&YAMx?Uz-IL2M}6s}>#ryyRoAk^y>LIGO(alP zpdacYQN37W`lxq!{qs%x{$Qs6b4TC!@qWj8H)5eIJalp`QWM z`Iig(p;oT-ujIow@!>b>d|-uEChWby<6euz$Tj}`zAV~ko51^nNFO;(2gA at C7&r!G zLxa7LQNKQa_OeKgWh#`n2h$EOr5KoXmkVr6{ zU{K!39<;X333Q>3GxW$5abgS5X>bgBq$=72bj+n z5L--8LQqPukYEwPVuCV)B?Lox{U^?z_-Bnh{?CFv_AgNKtnXhn_SC-#_VoYa?3sVl*r)zgunGT; zn9m5*=hOee*~I^Z+9~|=Vk#Hxx29sL_?$%io55&|-WOvad^r~I_ at iNryv$kCYVWy}?#-n@@AH)7}IpjXmTmgBssNr;xw-S7VsO3LnKjmT-rwOnc z8TRoy)cBgH=TEXryaAGYz6K at z??$B06Kf$z=1mZsDPLxo at cOm|a*bR(9+ckEI?*X48I?>pF-V1i0*vG at Hhwq0vS7^2TDfTDp z0Pn9x>izrxl5~rMz)Ru~G`5Mus`Mjh`wx!-u8S^2&K1Xi$HZ}9vS>io0)7H~qc}-= zQ7mUC#A%iF4EQPHER<#PbKsAP^Q!3=Fou8A4fwl at D%~Z-<%-MHWXrmOzz)#^Y!X+| zS`3PL4WUzF6Ds at j>zqEQxdAkVcGDZfji|*s-j3)$xCQnE?*RLUw~^_jxC229zYD$u zou^?|_o6=O{GKZLK8Exhf1qkUMbO4HRVbCwLSU`hi4602j0m&KJQhCK8WATZu{zGJ zX+l*t9?6n;f>5k+BIvmy<-*Yk9w zT*x!Lax)Rpj{eu!1~ChKi;j)5#)%j zR~Ou^h}|eIK*8U(fvpxBROCkR(?vU4fb~LlV7&f_JDWs={q#C8>?6Czk9oFr9656M3Go#B)k(;AVs)E at y@$J}5ULla zq5R*@0RGq6zAl1Y|9c!V)$?fJbbb!$W{4(;&+zjY{;T2wX=d?=oyofeod_-p>a0tU ze6JG|@}DlNrd)xvS?oZ0t2hrOq6&Z9Bj_WWt0?cXxP}f);XHg at CB8wuZ`&9_HecLA zf)0M$Yv3Ja5O)z#C3;obd*D}#`$~Qb6+6!F0HgT>(!u!@jS@}Ou=$E;O--s8O=Eu% z3n=7qn$kKQrDKUVryL~{swop#=sgyxsxC%M zrzk@~i}@0b*1Dyr%?@4;MHhJmB()fL!MTp+4|9irN+%W_q{6<(kQnKQ&n zr8-8N#QvO*tyMi7i-@^=mG%_-%hkxeUeuw;Ip}f$$E+TF4R3%vN2~!qg*Sq)Lx%}? zg=>|9b?BZf-sJI+|3@b!@tQ4DpxA_(=%&wwplNfgYaa$ql<=ar^8opgsVhvRP z=MD(=@Mf at w`A&^~8 at 3A>rgHv~-N-Oa>;Wc-PL*LV_)TITFjnkWl8st~{mTIwOWQsO zdA>LVED%MQ^LzO&RI(jKPGSwHz+uG95<5`RZhpjT?oo)&i!LNc#XLKP&;z~~nRfBx zsMcD30+Q3k~_-O3{Vy9}? z!6#`qAkWlpf?ufJf_$EKnoD-0b*0NDY{@e z+Eg5i=4huOxi2mP)3s=vmg1okzK#|HzE{K|O*S+OR)X|`W1zxb*A_01)p+t at 4YKh=uwdueVZHCT#{v`b`r@({F<$qnSvB}TK z&-iBQNjm)}D^vAl+G?JLY!|h3U71$~@-5ag^(WbVZ5H@>`fNm3Ygxb+EgO)3M~g( zTeTvcPVMs$yGuI*;E!tykn*Nh>{YA;f$4fF at QSujy+ON)q|@|8s8Y4Q7+9>A z>5~|Y;qi|y0kemgrI0Vs=c1T(TDh)FvH>pODy;&d4O%72xuh)ve at -g{UeKzLVX0n? zVwUU6p=H0u$FG2Fp6WVIfOUbJ3UOI9nCAJLYexK6DN z6)4p=AnP`bf2;xXW3{#svPF73aJAl{x_uM)75ZjfZ9Q9%y+hlo)1tl&xLs#E5MQD1 zgvK-4E=t9ALo!R>qf=8lkzu2 at 9I*$qy^t)^_n|yY|Bt8Pi7EDp{YcTM9YEYly#&>d z(`i}1t{wE+d`OS5I_)qL9n+3LO_w$sd=;!nux9NjvK8tT$dIUap9oY((M~Gs$$=zRKZP`N^wYp1y%CyEX=lI}=oQf1rk(Zda2Wz;wR6DB z8hOT7wev{2Q_F^mRJ{rOKJ5as)#%;8Ed8R_&9e|aL%)Rbd$h}5L#`k`U++PVE!tHt z6*-_cv}*{wVB6ObU!vbY{2Fa1`sTF8KY3GslVKV?c?)q%^a3PL({FpjdPj}%UDe^e zUJ3WS#}QQasCFM&^7IG59DRxphZKFP52I$=)zC5(qpq>-S{pj0SBv(+Mb%@FC{wRN zY>XbOe(-r7wb-HY37e5BUEhKno3uEevV$H(VdHpenr{ND*W!JidHU3xy at B!@v_wy~ z4|(JD=|0b&o#A_uJ*mda{zG_^`ETohYeYOZ7_Hhrl5%4V5d`Pa^3| zJspX*Y8gJ&g_(%R)o1yXuQVI#qxCFmCr*}6vJ^cV(X;g&-(>j$&-FFvWAl)CjXp=U zFCSRogON^PE&5#KZPWA66^(i!saxUWMF=(P^N?k&J|FSx^#wlF`NckZRafE#O6mTY zEmSHODO{|g%aEg0FY--d>vaC~Jm_lDm-upg@^h?1e{Ctk`}OUZblX(By7Y3Tr-I66 zm0s(XLB(NZ*a!3~A5HXXH0ZLvTqQ*f*=e1|@Sxs~g>g_q5%Np=QZ#y>PF=HCuY#i8`dSs+gxHHZjp|W- z9cp({Z}zI%;yuHO!ZNheD}I-czBt+q z+~Z at Nz9l~S61$}b_WJ1eX#0 at suD%}wa!==<+K&OauOEP{SM}j-{UG=|`XL`3)(-nL z`2`l^V at JTe%wXC_!Ms9N+Xd!TmhNN6zV2Jjlrl+fcX+D^0AX(UMB-N1?I~z z1$G+D8#JzGzJ{&_GsN&46YFmJI9KGqH9ZMNCRE`s?Q+R840 z`8t}$E`#|UcF at PJfO!YCVm)Af7bUW*V7>uu>>8NKNXf2)`8^=J0p|DF9Ur?1=9}z+ zkKF?EEf(u%x50dyCHfJo$#+?@pWOxX9hTu|yz<2uRf=@IG!5{Y*0h|5vfZhK2!1MkEh`r-4 z2H%Lh8aw7Mfuz at 83dwcL%F9V+SF99YSOM#dC<%r$nuK>T%UkNHHru^jT#{uRKJs0rLIe+}>eN{7enUkTiTy1{4j*CNds|0?kN z{Hvkow!aRNI at B4ijK3b3kJ2@E+1~*9P5&CCx#w>Lza4ecSc%>mf-nTETDgw}CG-Hh|ADHiDmr9)L55Hex66w}bET zcR)VXsD$J&+KXKh{QzeKEya1#*aR#vHbcG+?GkJ=T8%9o?Gw3mQe?+_iu)z$-f&Z at A~&Z zGSk=s$u9qPU at B4yHX9n?pc|dQo#-)~?2!_O0rZl_uKD*O?*son;C!PIk{$m2;HMY| zfOCz5u;`z##RdjelYh)A4fbhE{*+Z4+F?Jffkzb5G+1tgVA8FIdlVU)j4rC4aSVL3 zaU8hbI00N^oJ7$pjZ@%TjMKn%#u;D(jN=gH)t2SX1Y17}?DTk>aY zqk&@@eceK(wj153*!ygU0mHfEmw(F6888e@{(@aF#1+);s~9iF-em9Kxb>Hp3>-Oq z{Hi}I at TqGkWme!i^d|&vK!1GTCM21GTaer}Zj%WacgVnu>xfN+Tw}e)U8I>E=!HBs zaGN^NxJQ%4xDUK<+yG7s+ at Vf39)OPyOfhi!LVAtG1*RG>9wQof&xkQNn;E!A=4Zr$ zpAm=yW(4j-^1zq|J}nRrObsMJ!yO|Ld{SUKFgY*-m;%i>86p-ZB_j!v^uSEu^uPmX zhzTScFUw!D+<=dIDKC&>&|;Wss0$N=Hl}po+psK6{)!a at u)tV>KW9Y&PU+_bW*HdF z(twx^mBoQ9U`gO{h*k!I*(h;wAP2Qt4iv09kPE&lkO#gzFb7x$iD1hD`N&oiC_rpw zU at rI-fkMcaLLyiNQfh2T;HlT-U$OdtQ)JLB`aFZoiEiH31m>&CE|NLwp8gTH~Lra zV}Z|D@^9J6fJUKHfeNL!QsM7f^6%KWfPb0lyegHaTJg&jQjYV16^efros%jj)SziM z11o{CawQrVCu_l953E8(r^(fjJP6c5ay3v-Ly1 at zK%jvJRIUMkFVKkCD}l9;#LFh| zmjdg6(Z~w}3pCRZ%Q~dI7H9$9MtY6)1lA+=VxS59Wu%7z$W}5i*+!-%TM&CIumSSB zfsJHHaswoH0`1 at vQLbRUfetcMxe1a4*+HfyKZ+*Jl43L2rQ8C{lv|B$hWtmCE%|oi zNtPpbz{>uK&5_D#^5sslnt4*}LjFRz3mS{$ZbP&Fg%wNoNlX4KTPOu3T_pFQ*Pdlh zVtoFMEs;hi)Gw8LRTu9wlvB8${y7JB01*}PAh28>Qqk{P^50pN6o(D^){V4P%Of!3 z|IJoQb`;Eium-7h8FagJjB<1t{CMB<1fu_wZI|ptO-|u#m-L-fBYjHYX at zGDZ%N~9 zw>*o=PvvZ{WG_773y?B z76&DkI(A%631BqMsR3A^>?Ny^(E)~~04tTMP#hkvUz>&mM`e7#J0~H~EfWK<8|c;8 zX*nHQ?#megc%d>0e2h60Y0k)G at Kekb@Le($cwVLfPs()26U+?oH)JO88nnS`0kX}7 z0s3`NGGcShMW|JgSx8q(<^o9a%ms9|HWvppD*;QViN#Q}5;713BSOQ;? zGhorCuN?KLQuSM=>Qt at jSE*`NVO9ia$u)T;T3TZ+Lx@)e)|h%V!mG{YzrRu=i0DV~NO_WuE{+)WAxfj&9NpGR{m>t0VCSBz3Hu-b(vY^xC&wDQ?TRn52w{|Y_4z~ql at R5{ue(o2+Gh at ZrcnB3Q)WSdl*3UGeNk-Q z3#xz0+=42eHn*aLGv+qnS#x`UUY+d-6!_$9oFL7>PKYm=yMR4N at +7-x?goF=+ylH~ zb^pUzGd#RhUshe(i-E`C3r at F~|jYHjX%j}>x>DGQ#!$j2jj(I at w2fg;w zrMz{B=9qO at N$54xREtj>)LHjTx>k?1jtq2JyhU%(Vl4jYL_BYs+@|aF2PXdvy at QXo zsC(|C=t(Tj;{Kz7{x{xtO|H}H+Flb|rIm}l!@>@UF37_MWsN(Idd#*?0F$kg^v_da z!^C{CP6P9-Gr%nCEU>^j2h6ajJJPK4;Pb5uUVXQ~uv4vWmH8s at QUE&^{TSd1Qq8h@ zfZ5hnV6H`-oNiqMpJ`nO&ar-9llhpFmUAQUaMix4YJW at NZG|TXt>br;e2rWmQ-s3?q6IL%8GuL7PPuiS5GaxP~J zEGGd`djr^J)yp`QV!9{t^mvt>As?2KBp+@@US(%`8Irw6ii-cXCJW(}S;h_ZPeW2Q znaaeJN(OI)FiqhZB}tb%0usLSB8yYsF19kHCg*XUDVJJr&!TSu*=z}S%gO?7x3J5z zBUX;o*h(uG{AMc;xXQwI&-Ph#$lh(`gFj{!05 at 85r5fQv1R5-QU0i1sN%}e8JV at HD z`M?%yfuvvY6@%Ykl>qlzrK;eC;CEPyfV(WZ-dkoZ2EWOo_g<^5GVra|5 at 4gX6u93i z2kx;dfK65<@UXQExYeRh9#&XY;E!6>z-`uY=r;q}3Ix_$HNZ}^3G1n~68u4{R?@#+ zw+eiRwHoqTtIlgjy{dbIRF^q)*<5L at L8`S at BXEti7P!-*&rOzFO_Hwp)=Bz3Vl(=y z+G_E#tw*9RtCa$*O_Hr`fTZ5qh(r}uJNP!M1Gvs=M$uK)Ch%LV&AFX*ZXJN=sztl#6^lRf#aCqs&fONLZRn2m6HS(48 at Kd>Nc+G# zq#ANb?kvF{+d z!lui=Ec-6_V!IbuWZ#o?({x{1?E^JorzMBY3**;Fk^LBwJ^6}Z4o16JGV6agnlV`+A#muQwbiDlaSg$z7X zZT?%ck#L!v1wHw8Hu!n=EF_<0=a|$_xn`azmvG+{^~qQ+pJT4HpQF#ccG~$!yw#@9 z!`9mLNm!Fz;FXXFFQ(R}_t!1VIC6gvguof zRW^O-(rnK+>Gx*zrOGCIfvGOwi at mh;0YQUJ at 006nde2;M(+4Syb_sNJ+NGv40{U!V zolUQa+idzWYNJgrfH&IWYBy-qag3do?FQn^en?5b+vX at d}%Q!r~3pS at WQ{8sC zxzv}|UaC111a!QZkQRIW89xjv0%xha=p-rEtfxd--Im9+`Urv%q2z8RUO23u67 z^~%gzmAnn|nBWFrY;YrRTCg1$9qa(c2R8xZf}4?Yi^=w565O}9Dg)nU(!#Qx+Fb)@ zKd7Z+lx76WFoBYSJ5cV-;7+R4O3qS)KFXOE+@*5vMr1~is+k_#<0ufb;r;VY(b zxr%H1%qi%~oS;G0lNThr$qnvTRX?DRjHDn)29O`5?w%7oXzn#-9p-!xZY;FaV^Rfu zhfVda at rd_0ipG=%yMV>PV>Eq($IV7luE8`922Lo&RIR1KW>m2}cv4lDs=g%H21!Nm zlxL6ND}$%0>1#1*gZvBx%Y$cWz?(Q*>Gege>WfvubBJ6WJWrA9uoroejVf|&@B$*6 zg54C^j4jWLY*mqM!HbC85WGZ at EtptdT)i4 z9e6%?lLq>7kbOmy8{m%zKXQvEL-4jqSNeAp-X%j1_5yo at _mq*{r%4igpc*s9BFmj> zDQ7fV1!5G&0>1(eS!36O(=5!7V7!tiC``2IFhAW=H$jNK5sb5N{0%07zZ?82T>o~? zXIfL8x05Y()=ok6Y$pZkvYb>)tz>BkBspn_nBk-&Uy+jmEO9b{i=0`&#SR@!W;xV% z(avn7PjRv=`t at oyV&^$IkW6!OsV;sk52Crw98Y&X0t=l2ORb!95h!*FQBaIi1dMm) z(GNh_d}L2^7I-;|k*L%u0Twu=NR;T*qU;=Jq2>8NIKgD1K*8diMHcm9A#%r}Qi3Hz zE4DmmG5DEI8B~-zODsBlF16?vqvc)+6<%%TBUP$XiBxl(Wx%CQ6)@MShK>vric_|; zT-9#{m|R(o3OEZULl5wQR&1WR`sz~?z at sNbd5h(IA4gk9BH z>*Z*IXuh*fsclx+VtrL^!Y=4&>k(J&v_fjzcAJIsxwFA)vE*jXY8<%{f?B7YG{awe zlC5+)6u(K~W`$c6ZdF#YP4RES8n$r0-Kuk5-+>yosxfbIcB(Wq@&}wJ#>Oktf<0YzJ!gWlNeMWEd|gha=nNn at Lw!(PM at h>jy4&hpMtPi>c_ z?w80)_Q29Kwhh&UFQAN}*&&13=^RsLa2&b!q9nmKJ13M$oV4g#p3G^D(`O#1EV{Hi zt)|o&WaxCxdJ$w2yB!+g!_GOcIy8ibobz6HUGQYx7XAGAqBrc9RNvCz?{_X++YGr4 zE0M#mV9-uGZz1ZUqxT^8oO2a;-njI|32b+9rBWII}d0{aHiNKpK2qw6KxB2%ZZ^Mtgzpq zjmmv277=M~93o6>C%H4g&v27~GZBjk z>dpjCMP5vG at Hq3k$>3Am6zEBJ;q%N(3 zdtZ^z1MaHo+AL&gce9arp_^mVZ-#RrS?lK6${yw*qQuQddHHSuA}Zavl+V-(ZJMY> z5Vg25^pTr8&!%-`J|qq90xx&5O)qy!AgXsup`*-Q=tbm1ZGpQ85moMD;A*!FxY1pL zL`CjWo1C9=@Bm^!oxgc*h2kp_+vqNXWWHMkzQd)jRSVr}@as`~!Ajlb;455p`|Pd& zzs8*dd9hogTD20AYE(jFOWa!U>)chqRqkqFt6K+L?$!g#-3H(?)I?+R+%@2v+;Z at 9 z+(z)V?poVhbr5KC*8w-U&D0~7)`C=vQAdp}aMvSZh1&{T>9(L2i`+Kw&F%(Zox2fO zeehmgZA*a6szVu9y)wN`HioLx)ixu`E_VxXkGmDP+x at X74`4pH z{%uHZYT9;Neebiwrk5`}Z8eG7)m>bN!dp?z7dl zv!Alt_5l=e#yyCb!|oyQH{HX)OYRY6GBU_#A-o~lqjg_hX7z)LgN zdlUw7j1KY7Wg&lJsK^t}gW~ege9D0NMW2(+2rYnYVW^m7m`^42#4Msic576;V;l%ask5Pxcks`653SrRH&dAx)g{D(Wh(E(P%oyr-U{lPjQG&3-i%cIN68VAz2in at 0H?19n=nZ z?5Z`Jy}sK5y>mlbsgz?j-xj3b at NcIO=FyHIo!xh;T)UuWX=pdFA+!fr7V7l$>_uR8 zXrC9 at -*1y}2}1|Gz`>xpHaUcX=7kO;bzSI)7ju+iF5A2eHJugeqEHWvz_ at 70W7rEq z)-j}R4jorxctYVxXlV(ZQv7Mewub5;X$qYIzdm$U$ zlUVLT{35dK2we(Z4$9M5(n9PCrXG(8?hlC``il0N?o zrGdW{S}LAmH$%}*j3X~|c0a`FyQ2r8SSQ+%SGbYRAAS%N=fs2)_%z4+Ws{?^xna6^ zSrJZf;OT`E9ok8!gFh0ctC*T_IbxTE={I^?!*rn99VYK_W_SkD90(_&UYo)*9rfxw z8KP5R`dvy&IK`o#nA1=362m)yv%{$l{kVj#I&;HmNVz|p?qD{BGgOiEJGL!h`ngDV zIMbo-nWd0^{Wd*JKOkue(+O`+csuyI at J9N9SD1bZk`$&5IW0ULC%>vN{rqD_n0_YH z8m2RPWte`&*A}KPk3kfLxd zni?CXAFf>t=OO*EF#R~BB22GIGQwA(KPyacqw2%)j$j30`u?CgJjbE$59rrz>%#et znlN!FW>%Pfxt1I*P(3tPAzhMf2-6SG&V&oS(Iwv~HC*Hc=;vT3!}NQnOJO=M*P>#A z6^7{t80BI5wZ+0P{UjqHOm`Y(VY)qA6P}06PwW2qo({T=-yWu4V{8u7yQ0-$`k~jk zFrC`xhZlGiEJo{ggiC-&!*rom94lh|CbY{`?!5!`iaK+FkRE^1dl@$iW6*3_&BmddX&9kfF<&Gw=VdoFC6%H1Z8*tOZT8*Plo6y=Du0?O$ z3;z^rNpwW4LSSlSHE>F#4modgUhl+3^ae=cBWoPGEocNkEwWaP?4Q7mzRP)&Gb0kU z4t+8+(u~;bNQ>9s>k*h8X$7W5+92BCsIL{1BOARyyO*rP8Tch^YGjiawHcW*B3qpP zpS{hBZ1qxY^WwLAp&j01r}x<9&`tAhWKECk0cJ)zY4Z5|dlATrtV4s7BGh#$k$ui2 zc^^mUh}e(uo)bCXjmANUA82lOQe+*?Ui|nu_utkyM;4331fK`!tB&vus0hdP9*_)k2hM%!c>nXM{k`4Ki z2u^QodE_*(I?@Qq;>a2B<&m>q`_4H>9T@||j68APd3X-Lp!ja*;VJ#1;=Pl(cY431 zB)g!qK5`j4*F?_HIX~i^;jgIJ9>^ObSAh)?@BDua{MyKM6`P0jt0La{{}ei>E^^*uJeq<%Fyd{$>vCy9D!DK-MTq)j}ox^G~Xf*)Ji+Wr7fKt zz-^JK?vrd|BpTQeA at 6E^BpnvE0Vr5oB*w*}ABhESiNqnzrik~um1!;}OeEgbtau#F zBkVm*CJ44CV$hF__eK(2bs7`&Qe&U~Rq*^N`iXUCB+*ruT?%Ko at 5vd09fqksRQqNUrPsMo0aNDRUsY8p(InmQaAe#mHRXl}I75CsG8w z7Wo-{yCB%z$dmJ2H3aj~DZP;es%W~3Nf?tu!)R-C at p3OxtQuWHX>njwV at EeH_ak)k zk~n4|{hL8JtKl>dS*-Xnm&~`%D3_?fQm>?Pq>3HWiriDiRH*VQA(=X6nMzcpa##N! z-rhVujv_l8 at 9M6eo*9ia-90w2W&!Q4kc`o?Ua|=}Alcn4gVzuyy9s1-%%o?OotzLzY%W_#p!R00wt;TD61t1b|MMd}g}Sf;KJftBhSRmQ+t z^)smQA!(gR-=J<3fz9eR5!kNo5`lNry&_<%`$V8!-7f;~s~?EK5%stToK(+gc;h`=b{7!erfn@0=jyiw4_*QxsQK|g|5o%8jLc(Tr7MY3&T#Z(kHF; zN%J&sux^A6zCgPPvtgmu325hQi;J0A-B6ZjgLH#0)duT^x=dT4dBZ@#)n10W z_Gqu at 639a?&`90TY;BZo_;T84XvMqQKJDIDA<(Xk(T(s?ZLBW&O;Gub$VqLyE(I?5 zK|PmH)X)v{gw~*g(j`rT^6zPrb)({lcBsrSj%zOdDY{X1NSmq~f%mm(x`bDbbxHd^ zXu-xEXSA8RapzgBC1mtCrOncf;6bes+OuD4(hdEX)~riDV12W7!+$`VqZ>U>Yjc4_ z9ojry0^M}JZiGJ27U)L#VQryqDs9?14P5{(Y0?zGwg{;)JIq>;T64o}F;Z)Om at PqS z0heBi)M+VFYZ+2^Ntn&>v&%5*iqz;YP8Gkj&@a92SC>Q2%cWMR>l~(E| zU+3TEzk3yQY`=eqV)}Q(ZzS-Je|E_D+Cl%~P^9c#|Kw1_*ayF+UhuCD8NCkqr-#f6 z+dn^K_UQ1h267zmuhEUW-uEvJMbsVst)WPy-TxX?_%>iLgM0jIp$X$cQfnwuo`b at X zaNa*7Wcqf(Z$#S#1x&LI3Y&bpzcplr_ImA{AByyR&;L5K_6g~TUwH%RvntHi(Hvu5 zUr+eG>9-!UhB0==ueeDYFli2BZ^BeM>p$<8#s#EF0cl!5c?)x3zuAa**B9wJD%W15uortDEZDKpw~h1q(1&$pgr%9#J45z9xSC?U_Vs6F>nB?*%y+Ihm7z$fm0zv-xD|&GRk%Y zJ`5S5oq_j5M#b*Hdm&TX8fXuhfwv(X at ox(h0^S(C4;4Htbp(`y*z5gab_l70{xA-} zfg#AL+g_ zeziDo^MmSnMEM0IVb#~d>;j^?Q3P?B?xNtY<-bdceOeuqUJptegZv_*?F_R^*rGwY z2XGmy9ip?XL61nEAjl-fb_DrBELB3%Pcds~vGnesw6|E8%!6B844x>HE)-**7lYT!q(LDLB8n1{t`MX2wl^IA zh_WW+RrWblc8|0_RBU-Mc%c})UM%qi?H(h_y}Vb8!HMP4ZK zwLUyQxq71hD;a6qL?*TP>plWvBkAvy}E za!49sNJ?1qd915AbAfff4E^4zzhW4^cl42lq3zMV$!HkrZhbURWS5 at PrG3R>9lCT- zmyYYwS>SWJN1#`+J2P}xO&;WpVsMb5jR1fkSH?hhgFTe7hQvVzHzZ$CFdlD6Dn6 at Z zn95+|l2Kef2A at b{+5pYHVz|kZpmA4?FN<)UhV+tIlzhxCWP9113^SIs3bBw^-T z3Nz$O)02__-{*OiqKX(RKozvGfr z#(u#irnA52k{n_Gz$K-Uy}%{joBb=7)Nix@a1neD8TnWgo)#-mNM-2jBa4s_Y~9R=%sU^YHyf0hTj-zxkfZF2MI&hg5bE zzE#Imb_u at spH|sr_*Q?UvQOY!a|yO4`1Zb{vd`fAz%`X!fp4Fzk6nfDgY`c4Iecqh z_OUPE`|VeK>`VAQG{MKN!S_2;eC#@W`_A&Q-{#VHXZx6sOAjybF+Z1nYq5`wgy{E{ z`PeA<{`LwV8wcOtdCkYh!}pQ78heaOzuTg*gAo0_R*fBk at 9(eH*n?d9gY6pAx%B96 zjeP{Ej}FKEfwpZgUHZ#ET0Q;x21FQhwpUexeHu(N at QGiu)>Ccu2 at K2Hd`Kkc> z374K(7hs1W^Vv58>YSzzW*??j2(sVbMwp8 zKjqRt9fR=mf5N2~p61d&#<=u|9rKK{VtaVTq|S0 z$EAVU5KD0BUsi|M5y<@4_d;xHkW2seVF<-9>EAyNfsDna|F{-nOX2&U!^_!Q at cnEnn;rzluoD&R2V5G<&Q!3AWt@#;7b at 5vb7?&L ztb+ZJOA{Dq*gxXZL^dhRp5Rggdne4sg}5|{?FH2W0+U%j%=&X_3VRQ9Ye<+1(y6=; z0 at K-FacLUp`>YdUGuU9AWy`rV6SO7vG0c)#pvbVx5NKq3b at mZtHL-%u&X;r6%-VH! z0a9jzL=WBJ(i{-G*{2Yg3-UC(3W0g-yw0BG(tHqOStlec0O^QjE4Z`}1RpjS0*ly1 zgS`ZS7WS#ZRzP4e`@#S!aA^sLmkogFmU zE0FL8J8ZI15Lm~KnQSx!*0Yl)dldp3*oP)-fWVvVGn4HAG;gt&BP_*%=Z}l9*CA&U zn;KyoA+VV(h_JUIu!Sv)usSYnWh){e7;$MEdo98aLf~z-0AKD`6!rxE;TXo`vRvQz zRJHPFQs(cTKr^3{SLWu8bD^$GjlqJzx1VIrH~V;FE`pbIgjk#U``L zpDCGtcmn;Wna_uLm2Tby$m5w8An6+}%HD3?49HV6{|ZSDxOv}p^JYPwFY}*IphFZ> zRm^+X&6^H+TIRo>z*lvgazAwQ#zCGx6LkGjj z%L-TZT;JS}rJS94mF4o8F$mJ1vq13qT;IgU;o74WnUBMX|{+aUopQJ98nN zI2W$EBXcgCIEmkr;lzpX{bQcaoCqgAq`-&a#Od%eQu1^-aV8u;7Pi(KweRTtkA>yr znQ-E$o3uwH9SuXJPhzFt%=NX-hmD?+lsF%bms=Z*+P!+TO=r<^mOLMZynd4Ol&l1T zcL3yJtxsEex4yC6&L?;4e%X(W{}Y)9f`6IoTbYft0R-e%Nfl8^Lg;pgZR*Gu5L)re zZg!6v$bQpEzhzWO>5T>yvuWmwC-LJy>O8`c(9jJ{)7?}_Lo7Q4hG zYo7v`FX`47tfoq;j_onj#7UOpyXDMDmbfVA_y{?35wsa8#(*w97?wf!M?(Hok%MIq z0+9yEb_V{LUx((rC0i?%>eySd96u+=dt1HJ?;8Dkv)DNqNA4#sf>ns%r-+~u5Uc_O z at Xs^=fm3R4NvS_`OYK&m)St^x3Knf*w_;~@E0F&T=HCVRTV*o<|;Oq#La}lm)vDFN_Z#^{(3guy{134bBmdFFmpd{sEIRk!d zLxOR(VUR@&MwYZ7;kMu+4%FY4wqPd{Er4%kCv#hHQT|`H01Cq75-oVSqy?7Sf(6)u zBvP<1#RLHp5XdZ$-TFi;1eRu`G14Kp?a5wpa$0VEH>2%SxPpKga2W z?D#F&hKY^jFqMd749=~ z(R~I>w;2GS!+ei_FBknDgGiOsq2Db at fqWs_ZZPY9jU`u7Mrz2A6| zx>N;Sf_MQqz4e|k48X8gA?#J{Al>?u#SR$Mx1Q~tor!TUHsh546`DOoG&z7&8?mSV z=#9={Z1n%>%1ZtT>EI_i>_X`O+m%(kSj^sy+5cS3MxqZW$wrb5^s+}__P-RfM_~59 zdfD$F at _#F4zk|sC-5t<%nEM}Y>r?KpR$uQ-Agp*EhM at HML zO@}=;U|Wx at 6dP;p=Zd`wsbxb5_guX_-_K4HsBP@)?6gj3xT6C0|3>V;3w5oPO;G;f z)6YTKi%|Y_B`%lvPwnVWS^9mWh~;Q!XC;)+GPikaVJ@!4o)2Pf`ol?^6xyS>b|BEvF!nf?># zk$E>I#Sa;F^pL at -r05*Zoc!-P8&A9bO5gO&JEId)W* z1aC3A at fO`*WG#RfIme5HTK^-YM2ZuWd1hGlnLc%@Sq|$+g{m5pM-3fB2RHw*VcEy} z)cKGDqiq7eiIC zy1x%?iqGK%8af;#SlkB8KK*`N;OS!qly2!`KrimI`Ov1sO{n-L zR{V-r at d~VXq!-ElLLX|r=Z z?neRjgpodJSk=QIFOT%gryU4g0j(sfASfUiLr8u|9fr9cX-DimYH40lPupzVjcw=@ zt}~do0F%e0JYefr*-yvKX)v~VEGkIIww)P=>(EZ0GO|5Hl|2AvoY&1JoLu9H+);ZB5?J9s+ zg%AxM#7u;kj9$^$$32&MandKv>OT(8H%)P z@*s{OM6(BR3?Pmn#B2{DhY)i-h#WxV5Mr(eu@!qT&x60wmvj6?Q#Ng zGoQ&t5x1*c3wW0l+lH-Q>ft at 0+y?Im1 at Vqh5|EoYp!^EF%ZO=!z60g0PZ(jqUGo#x z&zgZXEvG(4pr%h8bt3_R%qK{W*3!cHV&Oa#K7id{;T6uig&~m1W8sxl_#RQXUZ%pp z^C37uMgyij4n+Khhq%K<41r9?Z-#hO3F37g;!TKn zy at z;{ix>i#O}`o95haK>c!;Ot$i3+yp6=2e0-5RZZ-jV!3F5ar#O;W9BOy*w^foj1 zGg=X!$*?qRSQxj|_LaJysCZ&Y#hbi}MZR_ zD7({LbO;zGK(7K=bk9mu!!ECecVWH1iyheQb>If04%}ey|1BMOhlqE#04pajz%F&c zzQ at CU1F+vf?C(-BwDNrU zrEHyLWP%jSW0_JNTc)u1Yb^E}XyN;n+Ik+_4+*Da2vx^U$w<|A{oY(tlKJ`NLnS%0hVuup%RHL1$l-Z#`8lXJDJXJHf z11IbIR7YPGDC39ob`(5?~td(wmG#KxVX?*BGLfyA9qNvG_P zc(tx1RDNv|+ZO<%)6OGOs=IZ!0WhpOYF6T-_rnZ3F-vr29%HZ*7FpI)lqjK4ta{@buvL{=%^4)A5Iw@}--S z7%x|6#yc6#SJG!?voS zUd80gRX-x3n*ngL~qc?_1**NsZCXdFmNLg0smp#brR z2|vW#8XAygivlI-P|CInnq&C08>nmrmMv&@w4kx{J`Gw2DQ*I`YoCU_$Igr1=3+Mt z!1K&XYSc4!^ciN&W{!dAPNeE%E3rlK$L#21Zq{UkwR>J%2=zXj at c|xXv zLHI}Q=%cJUcGF;J+d(uA$q7+$ri3mO? zON=+K;`Gb4AIyP;&0C zF4;yU2YV$OM9Bsy+3>4N7O3P9x!ARrWF*~UV1_xdvI00^x3lPWCJjXLIbF6v93TRM zc;^_l^%n2GT1mXybC1-Xk&Aw=90z5C9jAr~(xY(a!sO?btE8WtC zX`-I`UO at -<)G?bnHdMw9;ZJjYd$;w*!vX^|hnjxYZZuXob72IA&2Iw6yd2BRQoNl- z+nF?=Ek2J$=Ru%7ew4vR3WAygQ4L$hFd1b9R0lp_ at zIdqe>9V0BTYu;O4`S$)>Y^RJfwFYJ?0K80nJSIETTOeCg<;cI%jxHjzqoYj*geqEt zaO?Smq4lRZ^9jR@U+?+Yda*3%jRMV#; zGgU756?HAM7RuH%uLTqlEod(>DIvZ*gIK;flcyH!EN1Lv5QB`Jti%e0894=Fkdado z7iH_B48egSQPOWJ}rUYxq?pGY!fqe{;20T&^G{4Vih}JS1 zo;r?^%}zv3 at hTR%3yCgUIXT5=XL54l0J9D-48yPLg8oFP`&H`<-1^NQ6NEJqF|;) zDbAY`pq#SI1A2C9+D% zLc;u=PWcwVpH-^c^i at I?ynPb}z-+bUNPt>efCg2!eRX>XSP{Sq04oCc_Kic}PDa3; ztN=|Fz_)KH0%ry0EMUfVqqZ}O_~5rdwwQr5o}nZTAk7cRFf-sgPKI%XZwo5dqh&lb zSr5y!cLz-G0c|j?2ejMB7=KD^$@bV{2T;KD_+jCz_+bJ0AxuEiV}u1_gi}F)p9)JF z7~YeP5;e`koEMmh&lQl*K{rj0!4;6f!Avwg&Q>7KHcie0Cxc$RDwo*8Z% zs)EC*#(PZ|As0CfHeo`SCQS31FhaRa6H1J_`CE;;zq$z%NoWcKtxmA$2 at sCZ_y-MW zxFvlerB9-U<(P0KL+nP5)f!%dYoA$&+Y~}Lk=3jvgsVw zXkB?_meo~MX78yBS7wzu-5PIZzf%_~zy{6f#J7Rek(96UYbujcK8vk1&%jowVW3Rj;ECmfrg=5nI%bjl4^joreU=~#GgX-a!(pr`ON0e^r5Zav?1>bWhDj at 9Z|t&Y{|ZnZGPc&s0a%_XgN zCau6&u5V3;jSfwW7Ksl50;d`qmVzqGHaoyU4_y`p=dW&ivXhZW%TvSb7+I&cxPif6Es_n1}aF3f^ z8ks|p-bH5~5a=rV;riL5lf9Wj{ZNHSFEF9NT+%^)etR9{_eFs``Z`C1B9cokFd?ln zxktwd2*Df#iz>-bauL=IFDpJ?!Nl?8SU!(B^BB}7EWfS-nO+ErIyDu8>7lB_(Lh>8 z{{~DH?oioY(B}Cv2l4fgf*iI2W$B0NR8#)`$boNeRRK7EN zXNT=_``Jo^`5gv|8`@_MpHf#g&|Z^ABQSKdJ&$Jgl=?$#e7<&} zkvyb9=1s=(s1G34XY#^)l{^Z39c at V=vL)rwmXu2_HqjOP!SoQ_nyXa9FeoK)*HgPT zyM5X&`8$Z2m3G>_*rY8fD?(Qv;Ck z3&qMnD=mpuLtMeQS9#aC3+`18*)Ula$eKS`wpBr9j~Ys4_TD;Q?>fy!{+`SBLUGVe z at 0J}!rCcZ~c6x+JA?{D}3 at A%eLvtoaRAX5Umf$Zym09i?P?ob=G;5>(nRi?P`i)*L z66)OqjI=lrwcwcp3(-ee?P3G?>Jn2fV7sbNI(5lP=oDPdT1hq7A`q#2-Eg(3oB$sP8DC5axnup*pl#?@umBd*Y#G%Yt za>*g_lCXz3-2{-em-_hmJ`(4%nnwhBnh=nk=1SyVx`+fvF)<#G-}-~jq2Xv zl1ACcriD!~mJ3LUU5cH7e^zCByKXg;AGIBOnI_*-**y{5rBnhCIy{jDEQLa+d;00k z1+f*OWd>b^0U)2=Xr$Meb`NZei1tIH%waq(?fHIZr?pS1U1`*=GVEtr^fgnd?})E8 z3;oxc42E*K%z=@OlJVjf)IcNE0l~a8XuMQrRj`-+pecp)dzv#dD at 5Z`+)Z+!masx} z32ZE^PK7XROd{f|Wp$d~2igu2-Rq`QU#F#fF$jR9w%>rt&^ik17{Yj>ioHy!l!rbT z#12F-vc9vE(xDM1mMMYa=|ftJa9oEFRDrJAR1$yfxHbpL{lpg3Oh>#Dv?Uw(*_zHot=0xxq?>G-{V4Jb)ZxL35wLUg%VgW zqo~cjuR+BJv at C;79v$)PR6T;k0YMg9my5k&0+-xW2fZ^+cXlG>^ATiW*JKLg#70jw zs__ at Urp6y(FI@!N9Rx7NMGHhZJ?vCZwXZYTW~e%aZR>do>W>|yS-Xk|^kC_4TCkCe9H0jVLewkr7cA4P at Y&|+G`zS~#cjH`oi&Zb6(14+r>+mD${l^`{3$IL-DJvGBIVCJ;danuH=85|TxJx;o^1w>kWzcT+1Y8E z55RU*Qq=ZJ+E`Ff#CFOh&N6F}jDrdvuMk?QQd(BYoMnm4G6IANz{P~SD9|di8Q1P> zGFQs<3!o$(i}^gmFMFQjr`PiV{M_+;@aN3hYUZNbV8nVpFaMl9Cs#&5 zJ^ne1Z!s|zecP03w;0g>=oWxMJjiRIm~&@B;Ce2AE9_aR-_IFF(%a4S4%4aC^Z$BY zp>?2?t>pRz`PRsD;mQ+`;h+2$5qxuR33!^VOWMUq1pv^i at diKqL@S zF&5no9RN&v2ep4VL5sPHba*a^+{^(E#@;cppbue?N5facxD)mxoCtSq9x6m#%fZ>;*ix)xp#~%(LE3~uHNkY zb*KTq4PK9UOpnKDLrB z<30u%~MV*TN+3=BV%M9A}X)~ zqZ$w`lH+&uzeCC_LLOn~u{5aoNTX#+0UaCh_F_qv<7Spp2NGrXE4Jh8Xcrt;$8}^h zsSt)v>yYV~I at ePt{u+m<)2LIO%ZqqfMNt0{am)ww$LlJc#xMZG1G;dr`zLnaRr#nyI8eF_WK)&}U}yipafcCO;QxUzo`+ zMcOqpIX+Bj*Uj21fa!)vziB4tX%y--le0xA8%Z{c(4a_iz6NX4hP8<%(~UBBJRBPo z*A~>EqzB6bsXG*e?%*lOQ3Yo=kjb@$O|l9j`7Bbjj@#Zv-$!w~0Ct5p zeUX9HgBV3S7Aaqxj7 at jYTMH=;aOp#3HCCIJ-m1sub557j-lP at 3nNCNV(1UReGoHf( zHD+f%V~I5^UfsW1%B*3KQJa;MhcpOOCwD6Gi86$Mc}$d*_$4`Z2?h}@|DM6`W0`yv z*D$)MCBD0vV~!fPX_GRdTcQ!NUTW)WYDujV5)1oK(q2^DbO%(DXKF z`ldek&Ihzy_A_z~BHQm8Si~T-#V_#MVGQYUfy+4{;^S{}T4eG4)PwyD>0AjkhHBoH zxwu0s=xcq(Dv`pFvt4!_kiS63)E%C=;R~2%U*NjiNz47;#3Zp^gFOXg5Ig;r79FLb z`inE>yL4$DrVE0!!cK&D#1EN0aLr@%SqX#`WYD)tN(pa;=7P zaALMnotdq~JLDS2NqU#;bB+c+W>mZ5>?JHHR_}Cu#ClJ0AM3!28l-+!)71l<)oo}H z!9UyqakPNMhDKm==Ix#z(L8-ecJA(XMTocJ<1I2&8f%f6`(O#c?*Z(=vbSY)i|MG$ zl2c4g=|)yx=Z9}V)IR>jcKrjW8|kzQ?{6cWvNe5(nAn;gD}YKOh-ffa#bTrI(95H; zvz`1~aXW!Jkf44+=|~(;{^|l`I&sre0kU at V=pjZyBmwGS5gDxrn*%8iG(9}hb6aO; zNwiAp5pc@=a-;D;aohRN&Q3Ie!2lx1==p8fg*LV4tE9W`rlAy_hPjNhwOVJAR0D%c zvWZ|HFwEqN2jg4=tHa;V-y!8`Fdgi^bfMS~Gw5Jr;mC(SL~E8HX?%4Ehqb6x^0zErKES$nz7jisxl8BinBiHW zF&A)&Ws7kxQ`GcI4^g<5bAEb*0veI-5xZNMYTbbN~AFZ#5Tjy^#{1>Am~ z;VVoX>AddjLwOzdA!Iarb=ue_D5y}zCV(&>?HYXqHOM?_kR6T&8K0olb{hRBXw1R? zocwe7IhK4SVtt94069R$zn_eKyobyP$g(PfO$%?WWZ z=b6E-iubbIOFh;|pPl|d%f&tqW330NvG>8w+Z~lCFi6L!36Ml{CfDaAF$7%Qe2KMG%e=gGXY`l1C z3y>#BG-(nlL1lK7k9Gq)eN(mk>8s%$s6yi5Xyj`!OE8dG&fN2yt6}#%=P*ERVkF=a z-`qsUsjl6AxNo5O9Br;#|1;daA%(;ktgetA?F0B8zl+w+F+MsKPmlH0(_<&ULDj$j zP(?2GD}h6q~7 at hApklOopSNWKuA5|LZz(qb#?muO}2Z`=7o z|8FaqR)#5(be1sP37F1?wWQ9)B8%c zCuv^SS+-~VqG6-aG8(99?aLVV9Dcchzn)j6!)b!8O z4Cx{6d7t}3 zudaY9$^yR!E44XdDSfRAzP?tkzSezxt%|>lx7raC^G;FO;Q at 5zkD zReV);pXOYwxNl{S;*~!OWgf?OI21Q`nc}{d`6}d=XI|luxLiR^xM%5Gm>0vfCn_=* z at y*Is$Sn)qO)qFB-ZrZ7nZ|8K4L;iVIjhNhj%ZgX3}8^DoXep( zNMpYoAFllRi|^KQo<6BHSMf%=-~N^N-vwNi+~{6|Urk-ZIX_6H$vH?(!xYK!)5u at e z+$Q%3=AOaa*A!VT3q4hQt<8Oz4xt|w(C+I%_n;OxNENRG-S!!}o`*;lT1&)zklrvG zqFBwG>TL!vh_#AXE5#h08SUHN`0T~;c|(zTSqQnJHCG9}z!LzZGyBH$C&~AoEFm&2LOmpSD zsI?&6cGpo$>A|+4D!vs$@vjL2mw6;3$L}%fivY2~>)R*Tw>K3TCfe_bhbt1FKttgl z-4O3`HSSluF7p;m_=oX65_c7=8Qg013%h5lA7ar~KT9w7XCKDB&U!fgzMg#;&2wH` z)?!;WdC>(J-Rv!k1<Ma$5G20K zwi_UKa}azhlV2c{*y91NN5FR#1pq<4Ppk*P_1qOt7DQYp9Ir!ry^{6Vp_~_;hKOuJ zgl{Zv;nkTfJTVRMP2*kOSe#aJ6+7=0KZwN(-HRWD;s?7HZ!9Uk&nx~V7H{)t_a&73 z5^2{?lf!vEXte6R9yD6*XN|xPbWoLlh?f9V=M5SNWJaioZP0{m9ACynQ^;sY9IbX~ zNkd6X-t$`WGGg6NSbI~{jsUMnz&G=9*YZL5 z^a at PG3J-Y&CPGCMRZ(DiNr4X(1qz_W4Ombav7q!#Z2&Qa&kbuSw{Gwz;M)_zbc^G# z2i_{cTf2Y{Q?2Wz;77dXOvUye^+sYUw0|m& zabr%r!Q<j_y$CM zrhD-X(C`gir{2tx;%B|$o3Z$ZUWYeBxy{(&bEQ*nWO3?^gsFEC%Y5Wby^GwLdJxE5 z>h)Jme~PkCi;H?S9rqj;55vMs7!6 at kyDD zTg9QzC1s|G_k(|Q_o|7jIkbIKA5;iaDz;tq^Db?#DYm`FY5OO%e(?N#G_-oC8Wrz>e5!C*B?Er#i!1Ic zDk%Syll)`nV}OEOy*J7>Y at 7MUF0ZT5+Y@EQcQH1yfuuT}_u194(K?+YenxAw*?UOX zwq9@^#)!XQM_*v+e;H{0Hk6sd>2CKF?wu5V4dv7+ypV2<*u8?3R}g0xB%>bf>~vpr z`Be?6!G>U$LZVC9Q!+K&<`tMLL_ibmMT+|nOjJCfa+R{~5z=8mpu+5aIUtMO_&5e7 z>2nfypCWG*>_aK=n+R;BM(R$0cpW`L!BP3ao`G6As`oDHNM7gt?{QX>;2dGS$2&dR-T>w0GAz4 zzV1iceeyLwur*1 zjpxe^<`1FxR3$ywH at L(~93)x-;KHrY&!RJti|P*` z@~KQ_x5X-iqKj1qoziuYuvkrB4m)YsS374dbr-*X9z0B!SMg8`Z{PH$ zyEold6kcZk0x0tZOr(`EhHD^9d~^vHhy1Z6+|_{N<9T#EmrQ&#xGSE>ZSeqn4B+Eq z)#zANqEVU5W0N^98*?yT-l6S%4a0{Ix;r!A0sTBVxtmVv*C@#mGRPHfovl!38AE~D zu at IL?6CeZGwkN|hp}f=VfoW?_i3oPlue23wP0BlX5(h$61;(bL2z zI88H!pB5TzIXZml!Wk+ahDjNA-j%2juz8<=t>dY$1ceFv?Ryaf3rshit61n=MG`!+ z$nOLo1)AkHe3I8EF*-=0#-;&vK8L(WU5sl0nI3PVBZ>&ctDxd4mQwNltVpVo`kkVQu4dIcBUH zg9+}^bQ?^5&@8ra`qKe0-X?vl9X$CFPBQBpPmb{8^e{>1v)-Z%2ox{_l%pC at j?h at - zSX#(3G|)T&(7$4AItobduuJcPNAB{jBSY5w84lIC^)U+Mx0VtMtx(GVXD=btmn^;p zXCHfAX}~5Td2mZ6n`IC&yAO{*!ZNCNpa(}cPO4ahi#QtilB**40fi??k5mugxXs?( zb(_WOv?cdyU%DL(Uau{=XIoD&_YsWqG|C#;j;@he_TIJKHT1YN7i{+c4{C6dXG5f`Aq4HlB<(p0C6bTWu5&{st7Mu8*5?4T> zS;$=XTiOzZ^mb2*gE+Tju%i=G39I+?;#Ut>(wvC;lFTEa#;<&y$z$c*_4D4AuUL#VkU*GDe|ybfzTj2?a|4lFi>R)1i(DKrQ)QBo6d+Z5UXnkcg= zr3wc?^pk}xsp~~v^!>XfMed$5;+B*lk>d4V*R#+^o`pW)S?K!>JPUn}XW>YW28zf3 zm(HO!xhE{-c5EU2{kKsZ6`aS2Pjq9>LTQ7b_eT)2J=*im`3D{lz_d6i)03Q|IjRVE z5X4ZefpwS_6_K|idj}M1-wb%o@>P=SvDha&|6ZN!e zGWBYFjM}FoJw|o>ZN{kNZ$qsn#(jl1;KKNIV(}Pi>JOk9T~#4+AKj(*{|U?fq&<%+ z6aeJuQm42k>Ke77)ssmu9ZQ%VMr*~>GhMJXp6wg)?YJF2C_0Y&Kvx}1mX4=E0lt+6 zc$5JhZif!jA3lKY;-7iDl`Q{53f!syj^}73cu?GVZ1et(g^tJm?%F0tFkF8hc6Oq& zK7kfsuUuceo)w5U$yHLUNoI7IcjtLpqZ5fbe=VH4=Zkk3trfB>wXTpsO(flr{30k^ zq94xft{2<$;q7oVNLQon zaMaBf^17!Hu1W3-Bo1TM)-~RgIn0ZXR!-(zFAI%Q$;DTz%-)Sp$2?kB2C)$QE06o^ zsE=jQ-DR*3rB}d!e^@|{I$iH@;6%Pk=Ne_a6J|vxcP}N2W>iBn3hA3__939- z52fr2K)XVEn4Ekuhk|t}u>ucKXBq3W2Q5LzYjD zSFtl_ at q)a&SE3#qzZ>%IX4$*@)CDm!`I4D}z))XuCz`BUpe}RU?qWL6apo{e*b)X6jgVmn*uFqM61u_ z<1O62xN;sTE?nG5aN)k-iV!cr(nZUr-6H&##Dd)kYiSmz*FMDolE8*$&iD)1We&B8 zay?3l9qH^u7nG^QBL5g4j#tIIGx2)l&Gjn%(VBYV&CYH%M7Co?++Cs at w?)=DjhM#2 z(Mf22iE!b|JK|myYg}S}O>q8OwmS43K)~{zqWTW3UI5e+fYSi?9qzAe!F1a4d%_NQ zgxk?0oW0l{zr^#=OJwbvLG7Eu#1qzJFwNz0e~Z7X*s*sNmeBQ+&W`Si^C;|^CH>e`+K;ZAVYd#Tv at w%(n}FLwNSRfV at +PG;mZTh|lqQ;W|KX;* z*x}e!(V)7- at ri`sa at qi(U3HuqCJq6hWzaVLL5b|I%ZuPk=}#EB;2XPu- at 4Z}m$Yi3 zA6qrMBt at 88=g`JkMUDxiqwM6dog7sw(DLz-69}O#*`&W5#L=+#3T;6TwLwmU^MdYY zJo*`z2AVGjC1sY-sUK>yDqUHMbQ*p?r7+fsnFnof(HmhuC>6qK)u)@ z6%}WT8%jZi#_xf)bOCK?=*RBp^ALGlZ-_|jA?eMCBrDQ<5{mFJw+Zy!COphtKx4Vp z`*VDeb~Jb3U$>g1nvP%1B$agJNor+glBzdQp7AP-(IhoKmh~UYBuIy}+9fJpr<$Zz zC*O)V1?NT3jzouK)-FyLsNSSM!2(0`rgH0FFKD9+J$9}Kdez^?9oR3Nek+p;O!3H- z^Y;gLk-W2z`++sQ&1h;`t^Q%zRzGBtWOsuX-#ETbr)B7hT$x8axJK1X#JoaqLlZnxJ;%JfZ9cwXrfS_o{dmsXY0 at l&L2ZyRq zoh(Tg4hjWyHW1DSuxCJ6z~b{gq)Gid4o;7 at ns)1BZUTb%aUMO+B}j^Y4*c; zQvuXqCx+z#ZTRdWM0blQ-rdBQcoVrNK^F*`QX9!VqeB1*Z5Hpr;3H3*kWjLKs^iWJ z3Q5>uN$u9+J^J?mQh=~yCr{x649S!5TS%UPUtsd5HS2Ybb_t02Z#nIz#O{aNDL?+b zGOm`Fg-)VBKa?8Kj>Ls|GY{JU(`7&%Syt&FPds0F5+eiad``3Qy-J3EP!3IXC2xl| zV)@N(o+|P-dwKN86_0L;I4=u>ctK%I&r^^ducT0iFfxU at nB^P9Ysap2916C2Kx4ZC zan~Y`re=_3fqI%_6^2~}!Y467n1$714R!t^qPjrJKhSx)C*THr1D%&a){r;6usCT% ztz}*e$+c`?T`Mv?9iie1!8K7MnySp at PWn8Vur*O at o2n|8qZdbY zoj;`(TvTJbq3Tw|L)EPc7ZvnMxQ3(xU5-5nVaaI;#0NWzt(gyYgRPt%;};i=97m8m zy-O0JTlJ0)NMSj$&YC`Tp at C3g$PXXC{zIIo&h`61LpO}&U0UKHEY^W-uBr0_uYidi zNCk;q)Rh5S%6n?o4H#_KIF~9L=ol+en1T;pjLN5YlXZ^ACiz_-p9Gf4+~B}%^1y0< zwG}*c*I}x3N7pLQwL(^vR7j~!R-p?zER(s8EUN(gDpiKBf^ii%({hdO%YbrSU*{7_ zGu#GkI_&k6Ad44I=jAuKwJ2-4(C5Bhf z6T2B^!$Q^&#@Lk46E>R3pp1Lotw#?Kc*CFmM7P$6Z4lxhzmVrFz6mFu at CO9qRXA6+ z6t-gg^PR2Sy^TzNM}8$?!Ha}A6jQ}%hgSjKk|}8sZr{zYt8PRu_O_Nd^1? z7$j>15Iur^Oc*Ho2;SNVynsafp+T&Ml<4*bOzb z^7s;e|0O_dI)kgtyTopGiLG_BVmrik4A+`R>{gH1w;bnSK+vLH5WCeSc54Z-Tfdsv z&9@^qM$sk5dPmTHxtO;L#etGnpGW&vkM^xV`&OL9Fn~fCf2W3it;&Nu at xqpPDwBd!dTr+^cTG zJM at rjy{cA}Q96Vuqh#}f%CBnxBrG*J_eZEnNI at bJHiw!Hwu{_)n-ew)QAWuw*Gkwf zatB*0jtXEyU33{&{AHoz%(-#>h;W!8r^W^ts0I>%+8jAJduMyTb}CB2`Re4`sEp8Y zWKj``ZjRs|%XzWG@#J>?uusvKdzGIwg3a_U2>J-k=J^6DFJuk)L$WQPQ_bP}2QgVfR8)&(jpZ<8W-E&=MXJU|=&kRzb2Q}D(L29+Q)wZ1b zw6ai67Kd^qHq-I6EAe$brYEVUE63Yh3G9p~QH_trvuW z@&%AHUto4 at fUy4-NaSSjr4FzhW%vd(JhLwbFO z)q;brcS2=}77j7s-;J3T9)Az5EEr$pPao4`?{RL&wnQZTA6W at xQ+LS2C6JF7Aq%%c z9xZ{qyA<-4+K-n&UUJ$AY~rHU9q at Ds;Ep2T&07G^mH^HIKz%CeuUjLqY_|P>ti1`G z6jhcloROK45t&()8M#yzVi%NcS%sJ`V=wm1^i115-KFAmSNCW;%zH0$GBWL)_~vzg zGxHq${l0JB?|mgZ2q+-9YZVBHYzppz0xl>ZR%B9Dh#(^S4vM={-}#>#5gAzv%(wi0 zRT+0VaqcRdLEenIXm!^`N2M+-9u`#U z!sfxtJa~zR*Zcas|I*j at U|-+s!knW&5O5zV6_$`8f-`XWn94MZYgG&h0wa4*y`#r< zA>g1p!i{R>J9EC~`KP8$0%zg-HDvXoBZTRJS- zlfXaG`aJKyjQ3Vs06Ff51(56<8lLID$92ae%Xd6zj;rCj8#KqwV!ND_i1{+eshBTk z$pdD~S+ejNhZ%JiW>id at voLKo_u#FgG28^H9O}>Vp?<3 at 1dJJ?tQj(9(;%l}Hk~04 zm`i8aWzl>)L#Fu_lj;nstQlO^47ubwo(6M+yor4kjgfmWe(e$C*B&0f at TtdB8?}t$ zrdP)+_lsZj2dE1F5=Nm+8d}%HuOt#lTzepX%(;+0A<8AnqA}QPrQx^uifT2(OM~H9 z1{|U{f2+7#w?tV3alGu8NUKq4^$!ul9Wp;Il!BkPhrCYNOn+k|4fc)7n_&@+cj*%D zYmjH6E&3VUZ(2urHMPHlZx%UIQpc1?kA76nJ|*WqR*k3R>>@c=QjJA&_DT6RkMX3O zeFV=(jqWo(qC739ymlA#Urvw4iHYEF8z}z z?*CHj%n6Q}6Xezr^$Xdb6NK!4DK(4hz+0(D)<(nu<9CVP+6c}<_@{WS-SRGRuGzAP zo7<8oZcXqm0fO%)ZUiW95xUU4M at yI5NR)5AZgzc9=yIi-u4{#+>)nzoT=vWU+Q?)G z>1vs8;H;L#4V=|7rI60u<>y^#ERA0j2M2rkI*z!5!#9ceE{?c>!}p1D+np0K+vVc= z1Uok~p`n*M0d87b&fDeIZ3z$0Wqer&CS|C}Y9r`F&!P`?Gdr5&&#}w=&1$ww)-y0; z5oh!|2wnw(9bDZ5r1^aZYCWKKGB$jWSWi84J>=>GPQtN3W=4FB+b%D|K2=lSjn2oi zT_oJKk<*f}=_nj=WM7rd;(OF|vhk{%eO5%a%Eq&D<~iAHdrp?J?cPSC-J5&J7r%q* zyw@*o0~EUwMlTNCH`<;Xd;OMA=NbVfPR-l!&~1xmqLJ6_Tf7Jx^!R5pae1j&veKt{ z5jN=Y&!q%zA~fpX$(dK=wpU~iUm*~_BR0iyugTa?Fk8zBk97?qpPBbs$gL6HJ z+zaw$yTWZfd_s1&KR|Uj6W>37bGPitJ}p<=RvAyr*|J=5P-T?m+|#msh_o!T at 5Rg- zITNOm*J>Dj*2q%sWf|v9sS-Igw??)P<78i!n~j%ceAy)++%Iwgv5xYx+(H1jhw1lI zgZvjvp^Do_-sn%H6LZPy~aLP_A+LC z at c%s)E at oG97M8JFytf+F at L}sr9 zk9x~|I9^U_2&qoX{iI63ZJNJwRUWovDp%z>)+J(H9Lu~XTlWFplf5Mp{R3V}-<`l` znU9&^JG-tabtMbz|H>pGG)dsB1M+S}r+voQNY9o`RDvPSAg53417bMS}-Dd2cgq_n3abXy_%DAaz ze^jp6AC+D9N5 at wThq4{e at dFKssxCvK?6Ph at PcPp`k2piC%x;-4PX)D48gR-|r$(LG at 1?N20Q9JC-1%kkZzR`jc>oA9*nO>~=WAEU$WOEl~I6Et|of8v~k+i0DH%XZ6|9=X&!rbqS~RQL4v zUyBD4xi`h=Ry>%XB%IsHev~kcj}lg~HI-sb6zcID@%w9QBdgfRp)hAi{~`LX$(gb+ z4u at V^2((~#%hV4g_%7LDKBskreoOk%1c8UOML5H3%K32dSfY3|aYvyNJDgw{sYHv% z6ZQ$XLMR^~@TR^5yfXxw`j%PWppcpmuHa8$;X4Xyb3xH=qV?!svgLe2S!)H{$Z7-z zad0e at uc>~WI^o7}KAe~9Zt3gGE4aG>qWFMQvt at 3c;#3WkHZu-)D)tWU^Rn=y`jXuC zl8jOCD9 at c2({%bg4cpJ-+Ez1ZWbh`nS+=}M9ixFGk~k+-Y`Xh|N%!miJ#8yfA#zr;*>h8FJwc+uFXO;O at le9D$eSDQwGyae+pmM6Xe-`+9ZJC&1)O+8 z2{*HC_?xBXwiABI$`}p#z1+zvI)}G-3raAHFYtWUVRbx5DeuPEM`+anjSP{#S at txj zUOt)mJk3*FBdnuJW4fg65+9H_nece8^PEa}B%kNF-zV2bZst*~;7?24g6VO*G`T_u zQyE-Qu9 at zNmpjsCyaT7R6CLRihmIyXIwp1VlI1<#jX1%5^B!2~rk>3ACB?`fwkG*_ z{0Me4upQ^))8EG0PA4QMT8<-{-A?)u!S;XglByS*Jn*CBdbOG3)e8^tL4gQ6Nz6tP zoh%8x*RaLFKS(9(5X+4)cjL%FU7aoK2U^ zmIS+GiEl8vAn+x?!6Uix&`XVYBQvW~D)BSR=e at x@Y*ZRemHXF!=N3CdEhc+Dp%$Zd zW0Akqve;%F|FlJQRdx3`?Wchyvd=3#_>x`}pDMhxnyf4F<;UR|iH1wXE#7fG+SNoT zln_%A1|dkZx8t|tm;%KqoO=`C3z}|UC>$?aj+|>PFQpWVC^i5VmqmakHVuw z($H+0HhTxqG|fA%*P&^%*P>~&*NzLCHhU=ynx+YwdR?xaMZsl6=UE3RY_GF+R!Pv* z%QT at xXhF*?_cvuqSvH at 0ZndBHy;6y<(p45dXISj(X`%3GB6Ai~#+b7SDZL}!_9<(z zpJ43?`#71;F0l4|P{#jQt)a34T at HowQ(j_V@$e}vJU_+4^DHH4TBIvY%c9Q{T}>YS z3rr}V`Bh3zrKk9sZcjf*6_viv+njzZe&vYY<8##Ke$A`JeuD9^AIR(eOdvfqko)uW zn^Jn8oSqf9{;WTJci{TZ{J-~)2}rj|jNl7 at z#G*@G6MURkAi^v-2Z#8fa43`1Q&_^ zJyE<{7EsG&Kz$*gjNQCHvo_ZLWg_!cLIk>WJCyl4F}hm>i(erLOeecl0A~2ub+&UiQB-fws}<^B%#^v zw=`QZUSj$52Z_&BBS`=tqou7c0lTW+VtcX?_aYF!{w`T>Pu}41-j(!tQ=aijk5uQG zK*jsz0NXdCawp2flTWjG@{Nr*7$1VnNiH%c6OiA3B?Ns!vMaqy)+Z*rnnL;{iX4gS zlQ}Ylf2Z>AG%D3IWeAq$*It07`L#o{GTn5 zpg+;62IFH8{h*8Jhh&+EzRTZrIqepF^T8 z>2s4^&H8yJefFMU)c`5xj-R9RHq%2i`h|(_8AEw`C{Siuu z>5o#lMSm>Wb=lXF{x}NlU?iRVyCB(hbx28o5ruhH)n`g#JJmC!c` z3D`(*wfgJy&ZYVrg5x(ut-eK%`x5#luIaZK={rp2X3Drfp>IL?TTu<~;%6JZeAcJG zCs^-dc$3L3(QiO66P~obozuGs=+T7ULyyN3dN1W%sqdg1pZ+lvB=v;Z;M;oEMtr zkDh0mUpWZFPggViOn-(iZZHmk;ngmN*T at aB&%eNr^DF_JFcCW8XUV)jy)==2D)Dpg z<*I*R-m*k``A~T)66uvg<*iDjpB^gj*>HNYG(_GriS+8B-dmGMKRZ<3bBXl5{vq<7 zPo!TM>b(~e=_y0e@=_xG@=))+l1Q%|DsNpP{pwJ8uO-s!hst{?oSr0TNq-SbPmHBc z#L^GP(|0G*r{n1(vGk-^dUhiHSuDLFk^VZC{y3K26HCuaq`!=%&&1Nl2sn{G8%rOG zrT53u3liy*vGl=M`m0!aVlWkul9TzQ!#Q~#g`FgIu ze0{IMI1KrEo-%y?8d_d`7R~Jn>7QXq_=2VTE at n1x3;c{Hn?4$=-%jZ5br_}Y zs_VM)2m1IrFgT$OquRtejB1lOcQXG@;oqrs_{={9lkZ%J$#>4f8_$3gdI z7u{Q2n!pTDyLAX^w_J$YE$5+j%OKRgTTSh@{?uO5V4MK8 at 42Y$vKKO>XwIx-Wpx&a zHisa3>xGEkdLE*?1|hmsO?0_G(cf<{PJ-y|E~2|#ReTSq?HPjFcP~WkyXT>{dk|`S ztEt`5pV~_sj8mZYeHXPmUDVzSYCjl)+HDu2cH4QV-8l%gyQ-<(-JjZ~2IDk;Xm(M% zhpC+?b*0}+=(9;8%!EFNyWU*thCkHr(aM6GY_{X6|X8#}}bFiA$L;Y!OZZJLpt%qH-90 zCqnuv7WAh@5H`crP9zviuw=j*V4 at dEu`r at zRZ`6W(ynSWm)eq?=Z9d>Zm)nUQ(DyO^#4$ltJ zuzN4muzSzbu%8aX<7d at 8e%_zQ%NvZ(z~dJ#9=~*U4!;_L%#SZb=EvtD^UFcV{JNUV zZ~Buty21DyWPa-+^E+;eG^A!d*Zu|?2PJ)@n0sGmae9M)-{jx7Q1R!6Qmrq<(Z2I= z^xYsF^;L7!9_Yu>6%EE0;OMSEg`@ESyW%GVhS=G^5SjbWL+1FvKr$x=s>qzwpUiZF z at g>Nd>>_hY!0H3X~Ep=D`Y~_ at 9`F9&j|9kw~g{GJ~1dj(U z#N&bU at Hk}<9;a3FIK4lQS2h at 5fycXDJkDSquV+=m z-pe!Y4o-TX!#ly{%ptfubD_1|ne(jWW(>mRtZFXr>Cfd=4aV1O!pp_w>_BDn{R6?- zE)YFu2%-;OsA&$Kr)g#nLiF5fqVMZZ^wkZG(5b__WCjY_y^wjst$#FJHsQo#=fWIUiqO zL&?FszdV8xOJ=FKx6asGcdg$SJKWdD{t7*wM*=>-UmnMfFT2HI^;~;^t>(7}Zp_^k zXiUJKa(jS+x$)lCkLx6ZA_VdS1}W|%m95*v0gcMx_K5Joy0?zr(D&EzA(7*KeK0sq zn11gO7+~TC)i2}Ic6*?aQnBT1C#8=ha#Or;3G}#K6`2EdqAvcd!T1sOo*A}ZRdfct zUO9kotmaK@>4kvg01zIv5iSHmf07U$WQ0GWzp>XgG#DV at 2}U}?ec=1^X!=;h_&l2Z zBAPxCF}^@Civs?D9HqVzKjsafK)HCR zu6Vc(2CqyrEwMq(v@{-epVP+^<2xpGn>N&uI{TQ-8^Yq-l0bm?!GQxccTUb7t;-#& z%S{gu{1_GAl%I5%JI>67>2K^0h&fz^Sn87frvg6J=YQ5K?v)1`+ixi-rS_4%CAdxE z8=%4p6q~9nE#pt!Y%MeIFYb|qnFN?68QH6`xz&-qdA3}G(@WRo{no9`GJ94UZ=Db% zh+nO-Z|7%zMNq$zieLJK>vi_N6nm_%FE=Al{3QI-;HCa5oP2ee#=U{Ak})d) zA(|cNDjWCU#Z?ZA;^8$@yuQTQ%`^A9m*KCnudp(;JX&n0# zs`QT)^u{@8Vmb-%1hTYOoc^kk`nesMO(@<3x>KWkD4&1QQa$R9LQE;nh5 z^8O}u(bBxU6kR~^QK9eB`gD|aDX!GyNwV!-3e;TV at ig6D>fnP+4nYgX^*W_5paM-h zB;$%-sS3CPY6F4 at dX_s&3p&P8KcH~l&k@)QK};4g7%M4T^fT8C%Q4HSi4z^b)>6e6 z^!amL*PNG*lAVdm#dfYz5>K_94=kYILPnieM3_s+s~kZ=PbpRMvP`k%<%}gSf8Jp1 zf{*ceVpU0I{A(;88SzI1xD}6m?Zupe;;k~8eCv|M^d%k0H7dB~^u|1}# z@#4BT=N3P$!!b8Ak5Jp$I;m8AjdNuCJ=uCMRC%u;o2{SKv72Qcc8T at ena~>86N1Cz zB(B!orRDvlme#v89~%lk6DqZy2}#B06hR(N<7#7tA8 at t!c^!^*2lH~dbwk{X0`R_E zYPQ~&^O$0-Jbypj`CxHlTrhzUnAoW1)w}{nq^4PXF at ckBcslqonC9hnpZ;3B8o=)c z at Dso%7>8CW{#u{^ZL7A!_^Dxz9S1%*T4%HPX{g;NY)~(y$_JLUt58h}F(;}iY~qMA(MjmL^I?nm5{H=iAk~&yx4Eyc zL_&eK48{MA*a^)`&#g^fjZiKnOkt$*q1KcHcKF)aU7DHsDJA_>DvQeP=KJiVXuTNA zhdP8ZR;bC>Ko!Z?RMAo6&{1R2Q9{LPu(9=G!VSun5EPk4k7Io1KhHGC4Sj9khJH{Y@<|=C at SR9- zRc#SeZEBcVNqCI%`S;baeuBfOk9|m%J z0%NX#>hhdot1e$vRF}Ub)#VC&^-}D)X_GVgbk{VF{i%V1-)6FY;U;U6mhOufleFwq zEj=k}Ox3bewDg3iF-6Nx*3x%HjmcVeqL%(HVocPs)3o%Is4)$w-ckIr9EiGk=tQxb z6`80{L9q%{$nTWK91q}d16(E zaJcuII(8vXIp5Ub_uD$8mCE{e6!^9-GgB+I&eWu4isNH~;w4cX%i22)TmKHd?Pt`s zxi8TFh1ZvJb$3EnM<6FnvEj57JkHa|`dwZ7&uBy~>65~F|LlK<76sSgFt~}25{i## z+s5}OXtaVD^ZW=Xgw2?*nVY{XO2?3-IXTv!_qS0%K4i* zX0Y6LS4z4zAjfFTRRby(QC*5p_W5t18Yh%^`;H!y5OJutR2I4d&MucR7vBML`jR!` zI5XcTyn`m(CL*czlNhc}nu^cs^OH#C?$%7JaZ1%)s_b;B at hQ=z{<^_90omBD`1;H4 zecEjv{XQ-GFl6LmExQ=c#aebg(&ua01scw2Y1z42=9Jnz=9DVwb0JCH+zsHOceaM3 z&e_>o?)6w}x8~6)G+QH5dlaHnh8Qo@eQqQQVJK)cPg?hhu-ws2l^Y^uliE} zkL&-Wh>ISo`;>p~VT~8yREG80KuU(~uM6p0I{qZSLGWjU*2yUtoAguIFPu{$l)H+3 z6wm_yDnwhal0=Dr#J}_lNdWoTdy#cZWJ*eq at kbT?nTX)qy&?^%c!MYc$r__31lRM# zN2OfPrCcuwhW at Bvrot1L^=)`pxe>Ep^(}=4)o)QK6{n`sXR-g_?`WNtk_5px-orj! z at w5nUyiuAieJ9=J#f1ZPddlPXdG1bml{2C8*B} zU5!<*(IiN$T(DVbKj~l-Qly^->0y}!h8Cf~_Zcfwfx2zNI`A&V at Ab<##{B^G!sfOI zG;DF~cBi)3sUKFswpgiqs$lKZkCZ^rFQ4!UGszgSaBn7%o6piOQnX7U{_{yQg#6D* zneanp#{tbZpk!FQsp}@KDu6Fs>4}P&{yNd*Nq<8>zxP>K#jU7Pd=iZsFszghKv;e`9Sf$lw&>!9_4VQR``=^*8f%e8~erkQsYM3uinS6R=qVku2uZd z{g2$704(Ss*KE<}rL?mk;>Ud7Yc3U+NLA>WTSZSb1uaCu?fe#qO#DNIn7N||(h?H4 z&QJjL{Ipe?UA0MeeBX~ht1kDWF2#$EO(d?C_+N7UyISb`{aYw%wUF?q=W3uH9-{Ut zej1|w9;-+*EhTQI at rZ_wE5%ONI2y|WtAyb}lY*VC^HWm#7QV?$GlP`^Q`+<@MB!nr zWa<-A8172+#3x6ZTIJAnuB}DNWOr4IHSWa|Qa3{P?YgFfZr;niJT2YWpwbX_qZDtr z1iexB;0NZMKe<2ydzQA;98^E6yq*{{f& zUq0ur3?lYWNvc(l{zDi)!}zJePc42Tm}aAyg(N#vH?0dbZ~OJVWp2fC?(OgzyBew9 zt|(5>mSLL8E!EQZ1k&dcd|j0m;HPWzL3Z6xDn6WQNw%>vD3gvA5-(Yc=}<0RTQCU74~VXYXp{SAbzn`*aF%~_*=4JT6X zq-7b;0^Ccl6r(>+i$Po%#pqv?S1|h91#VPm014E6NE3kQuv_~2%0ga*aZzd%M?UV1 zKT#>Omdlt5<(B~RwT!;?ajyhoBjVzxAu5QggJp)vxdG%vm=3fFLx?Kki;DSZ at ksbi z7+<3hQGzTA?GCp+n)2qOE&7pgJ_;>}X-7$QD=qSC5&08wVCpZbQNlLobCSq-i` zWgpSXI{mjk9OlKh>Kf245*kzBVzQ`|jT+dZ16WR`LJFtbQ&Tckl~^M2D3*@KFo#*}@+Xf`Ij2nLcKrM;!c()@bM zbBP%3GC_#wa1&D{9$68$Mpj%G_HQV#4b@@gzP^mF)asLpkENQEjaYs=y>P-A>0E7mFFtJq%cT+HyPI#k z*5{3#p_PVD^+G8*O3MH_;4A};iv+6~PH!OI}88)S0qxdYuZ4 z{TZz$PX;mzto6h74aR9epH%Emy{Da(@uz{tu|GF%#8CvwmH9r~1FD at nGu$}#7smJT znMuXhk*c-FGA+F*YAn;TPig6eQR68syFyDZjT$So>~bx=Bx)?zva7W8im0(l%dXVY z%c90g^p1yAe<+|_>0UR_RiuPdF&8g&sDpiFQb`5^L7Q28 zXHv0k at C*I~m$|RP%b5EVU;BLuiArx*^*1 at Q)Kw}zmC~O|xxUGPk%g+gm9Q-3Y$g1P zn79x0JfYefAy2CICjBF!#<8u&JY+9*vR`!CdwHsH>vX$?lm zvrlW8T9_(&Ph(R>wUYj{MtMZqD-P&t0lHetv;uTul0jP~eYHk;1iDs at MCHJxs3N0N z=@M2a**gpyV5XtHhQ{B>O|>}Kt6H5T_mo1fzN+FI9=;1crI^N3VCyy2?~?=2%%4=u zmQw4JijM%-yHlI()D5bv%7OW`Kr~?zIW2X!z_75ES)}BS$8(3{xzq91MT#%?tB}=G}ZCE$I(MG{p+e ziR1%a`pOiQ0bfekRwTU$+arXq;!uU^qIFeDx>J8T#h<(aEfmTJa*GvxG0keZg^Ioq z&jpIU0M8|gzCq+T^hvhPvjm*l*+9SCcMIh`cmb_>{2CjB}x03 zD}BaN1t}iS>)cSVkCHteH;c#P#_ at P|E=`VeqsH85_86rdiyOz{*~1h%95)Wfv!^L^ zI&PefXTPJ+cX8vpc=lb{gfXLcxYfLn&{om>oi#V(QatuzBbPnhBuIRX{TVw8afH z2|!z`fF=RxT{qA)0Bx%Rng*cv+(1(R)KvvE1wf`7Xfl9GRX~#gRCWX14WR8+Kz9SE z+YK}WKs{AJGXT`<2ATz+9aTWH0QA0!wNDd_~n=liJ_%+Y7I7#ce|lm at 2$aw^bgglId~6?3HG6%bx*H1k)SNYSo4uO;0VAg zTKZnecty*;tfilg8ZTqQ+p7-P>|V(e-U=Z3SjA>HW~1eYyWtEzdB%}eXW+BA{lE at _JM(AKh0&Y!n+4md!s#FDf($8w at Mk4M#+bqe)4>! z&Me^z0uzR%FZ z)Ssi at lD>w1p$Vz}pd4s%)4ZB#Uc(B)v#cOIqd-A;4qO~lgTzH6QF&8^i`7bQjgos- z$vvawo>Ru$1cwyI9d=%*u=BUX&P~vzocVBe(4G%X(tmz!wBU47(9WIj$UXSsIeY({YbE;CZJp-Dy;wPc~ck)O|V7^ zC|3~CC6|ybPUV)QavxT-r`$`a+*)-^Nr69s$AY$+^m3J&^joM&C6M>HQz;9BwuZ4c z*f{ogMwu0mAiib%$W8s5+GTG`#+zDpqtie ze6E2|G*)V+M;X(Ik2OyPZHZZ1k(gVE|3-Z84h}L=EaxVRP~uMIHjlAW5#+B3`bYr& zk_}Yl-dAE*(G>E&(rmo1Py$S{2Qyo1ayu2XxGvSYwMJ at tx5l$kGAX6{epr5`4vDMyk$s`6QTeNHm z&ytokwM at g929IuP*=<^jzD>)P=}`t5&j+EULmAwLdG`hCV?IBW!EH2qYy-rL4n!Fc zFF6opf?ymkS4-I|j+Cvpr0li^V-I*+8yw^a!@BCXUv=KzV7>kK4aP^Tl?DfV*6W4j z_kxD?K|f_ex9AbNMUR%zpj*rox`ig`JsRafV|5sMAJL5*4RW-204L>Mw^^hVQ$K)Z z-y3eAV*q-y3g{Ss-f{!&1<1{{(e9%_+>mTv!@4Ax?and$-(#M?iUKO%~jI66FZ6BwZ)h$$VT4~5hCw5}%4=+RA{U-*p=&`{fhRj08I(qhFppmddu!wUMqA>iqzKH!%j z=GW#9D!Btn?y!Q;1RnQZ(Pbj`_75@(n#eqsMtw4p>UPFeW*2Q#)rW20bvQ(I9PTCVW(M4Ub}@Z*)rFGX)WiVU5i(IyRKjPQ at wC1Ik_Xc6lnbv!`MQX0E zrnp;JGV5m&{IE6&W*vuy{kQ^qlwtQdG)}@~zn at t_XP=~+#w46-JTNr2BNc2PF}8ya zwr{}kAqU$x#PK(%DEX8FaSRZr9f)HDaSRY=9Ed5X*-wK0K)7a;Bvf<%gP^sJ1SaJh at W}9vz*O z{TahzEE0LJm*sx_3#YE0#}>nvPINK8|9?e8eYoarA1re&_jz(Bl-Pf#syd-$PAX$g zDjws6LTN4fL?1oUyAt{vwFumk)Su+Qy-9r|2WBVrXE-n?sXvN<+vnp6#rFA_IVVvJ zo>1_3kV>4W#yY6PiK<=VL~z*xUqBPpX8rzJj!i@?J(r86zFk)c!x!!uNvYvv>Q=+!0 zV30Z4A at h`|zEg at I^E1V=G4?gh8{wM!B-<`zhhC78`sYw#n7D)u~guT~;J}-4wI8hVbwPkcK_13cLLuZ|67hs`gOS*1W at DYiC zo&cQH=bAj|Q}{$mX>fl^u^bCEBf3i+fCML}34oBCusg248R7~(5Dj5-FBlu}J{)77 z;`dM4 at 1y4m=uW04$skRhe(9O})!bB-h7ycA#}sz2_N_u=obfH{?%N>zxU8UlJ*psg znfg?fMnrs>h+renKMwXx&2cnB!rov>95U8VbF6H|&M7oBQoO5p8hF`q^Ay!iJI87B zI1MpCb;7})FlRc!cV~P$SAMfC_7gBH;2~UZElcFSP%56dRwQyW)Jk+^BKN51OlOo> zky`kSlKDg#^9gk(8Vb*F_sPsct7rE5Q7?P_629;t0kYqx*s^~{Nk77Ui~^5xAEUry z+{Y;Jcv63z1ADlS-AB#7!XB$H5`(?No!POF02W~E9XAd;0yJ2!lxlv4&p at gb33IoA zy=R=25Q*a$6keHfOnY&TS7bmOa7L|vZeC_VIb(N=CQpZ}^{sgWB!RTHW3}A!e0Dtd zEvKPfkk4)Qt-_>Oq|it&)KIzi$XwE;c4|AbrR0>=Dt%V5#G$FaGx%> zeX3v>I>)8{UzJjik-R7H)-h!qzcp1I_x}?e_gZu;R0q=07lhA}LHs)PmP`68DiG`I z_+i4aQ<*dveGwcxl`U1VJp>0ZhVVSX{(F)wObD?vOi z)i(Y=0V`Rs6Bu^1V~JH9dc-H+6Us7izTzR3MQ6O)wxxz`9EPBYW3#f7GlxIMP71;C z?UUZ~V9P$FHX9GAup<5A;;KaMaTONpeImX*k?VBg#SJl$)GmexkrFO$O6BH=@neKp zT%0nCOHyM-c-u$xpqx_c+bM~{nI-X3>ykKv*)Nq+af at VpsSHsa*|Xdmw at 9MDbpU#W zZoK-T@)!bR7ZMrH^6~smn%JFJuf(nK1H3bS?lQd?6>NUoz zl%M;D;ybC_1A-T)E+>buMEN(W%d-kie06ql7Ad(e6+YR5+`eSQ$^BBP;d3Ky0&FK| zA(ksF}m+rM@{uaac~kUfUFIcx({PQO&kFEDTe*{pR8O$3tpM|7P0R zubFlZXr at yGn&}iX>&Q>aV+liox#AXBHK4clkn~et3DS>NIvS%^f+b2Oxn4+DZflB> zWIBobP9}dkcgi2&`~HX?2q$$g2+6x7B-<<@8B{QE!!=}k{dq5SUavp@)iFQ%16+m; zVU5#ozh!0!PF5Pl0vs!!U7EZ|&Auo#^%*Zp*;VnT#l|Xp;F(1|sisz}Y3~Wy?r^@w z`sQ9I+JVK)Y$wVJ`Wz?9>ib+S5ME_|Gf(xvDZ|$aoG~o((c8u{e08{w>aM2tM=)p2 zuDWn$)EP6HZ#0FwE<05csa at u;X0bQg6`5#pB7l!ODm|fNuOs2tj1*ean;MSQgx%Xg>Xz4RikmfjmR_G;OKTKZVjIH+ZhY3a|R#xdSg z4^{7}JH+p{h|e|{PeEdqF!9$6dUNoEmi{VgoY1l-we&Yp@ zP6;1cCH!N9v5Z~0hyI`4OhAh1BL<`0mM*1V*(EaYj`Not)XDiphLmJ{zHK&JQ;H8biht`QujHjl1O!i zjc1)<8+v8fSOJ!w3pocrLiSgi)oSC|ys-+|FNN$=La#a5t3r)q3&u0ZULP9p zt>BE7zDqOCXxUG+^aRcLM9ZGm(tT0mGz4Np$gj$QORB#W;A7rPDn91DpyFfRi@>}w zq)KvNBtAB(?#~Cgm(<(~YVJjK%=0RrB?#G{L-$rbB-~EiJr68zga&+-{8UTdFBzYL znl}eh^NI>;)-g4Un3}b~^Oi%+%PwkOQFH55?7FR0Nv{Ppn;dFBu2Ay_qUL2_d3&Ig zpViWnHREh$U)R~`#ddnNly7#Ve4i!d#RlV5Al~9M;x?z1Hc})0lkpm|-wO@$X{sw! z^>y2{qq#RA?xm1 at mgWuBG~NJ~vIDUd5ZfJytpu?Z5Zw;MW`cQNw6<)|r69v_LB>_`9#@g`Qa zHmO<4HQqTN1M^&E>~_l7gfc#K%Gfkm8Siiz at 2FImNkyHfjK!`p_Bds1K^Y%$8JA!L z+oIx7WOfT-+oFmWC%9hR8}j-50V?T?vI2ByA?j_^t}UC-}~PhjK^>(?ATzP>v(nNIuY_k<-oms%>L%?QH9Vb z9NE6EP&2AfJE{=r@^4cqJyTX&%c|#2b|y{#IIlYn2foMQ0PSS?j?o3hzCqi^o`}tS z(B+7 at rno+V#?Px83aa)2F0{gt*tkM11-2m&DaCK+Js7x)swo~!vAp1nSWWRziuWD! zwd2ap<$9cSwroL2ShAjUFP}fTwkVF&8w}wsx+<;%Jd-IgK*d^=R+O(qdqh5&xbqo zonDiQq?9>cT-~J!}iuv?iATN#Hq;{Y;h%f$u$_8$}?<>E7VMsX}Vd546v`b{2} z)YVfXk1Gtz*Ha6QD_oQx7Qc=~j1mr|#Pet>KdkBYC^kxK#=+xJz9v76-LGI at O|3}Q&c_HWrF(R3B+J8AYTuh z!&^5bP-Xdx5r8)_Y*pv$^TYEOBbJZy`O6xv=-K|t+uhffJ6>C+Vqg5AH8|S at wmyL=E1#g?sZ?I; z0Ah=qMSXo*5S8&|{Qpa3)Yz3#+dBP1mEmg3p(`V9S4NzMm^d*B7Kg5kT4v290~f4} zBv+rPjAW%U at ZC0Koiu_z%0714rReDpPMlwlQ!y$JP(ARHT4J}7y=IfNO6+D?64tT| zXL-v6Yt^hdm$!!r(HTbcvnoUf6u4G(5CtoU?sV`7ajoF_gw at 6t#*;q7g-q%;)5qgU z8t<>Jz0ph`Q4nmBZfwHm)KA^;GaUFM75Kwd at Mj&J;WSUwkIDES`_DL~h6(<&!KUla ztEcNP95v{Ds0m*>n(q6gCcFY};~j}|g*Hh~-oduh!bVSH=lQTLbzz&}VA}<36T_hXuQ)nL z4ej9?+M`lcnN(4OmVsS$2k8euIyvkpzaI$Y_XB$U155dx=%QvyxLWz0R#AR8d;2NB zQ*Ep)8YxFK21>!Sur0`E+{*8NCqdo;Ow+^vsY60{hpX0*Gs3nW`-!E;{;|Q>3w$%f z=os~Ge4lBVu$1z< z;$|)P8Si!@^0t;cCL++pposPI^}o#UQkjR@;Syg}%pGB0pEVO$QtM z;_Qdb9Dr?!3tN{dv%mP+VPA;q*(-GXMyP^~?MYZjyRM)#wr@}iYGeBjwGgzleNyWy zioRKVnw3Y6Xd`fQnG|0tHFw<p=9RX6s+9N7hNkTYk##%z`os%A0-d*;t+z|X zKfvx$ii#Uuuy=RQrg-kKqSP1k&r&uX`z at R+eR&XcMS*Syp$k@{3s%s5-XC4jLYFzL zwjEYU#??mSw3<1iww_Tvs1#DciP=;hRRSOM)|Wz^Eq=Y;(=a$q`Nr0+#_ z`nftoT%BQ2oudmfH(MYt3%XkLFH(ert5vZRn<>%ED^Pvu_b~KawM3x<96y9OF-~h9 zt4g3}$M~MRl~}BJPRt&sK7Cxx9arNU at S#db3oiUR&E`k5`O$2UmH?y$aDT?tYn4*A zTPsvuy&-pN(d%xq>Rug)s93h!DO`;x>~1F)qN0V!Y9rtqF_3|?DxVzpx1Cjef{71U z-IyEp(~5SZxR+dO=KklD_Wx;JG0vS-nU-JE+6ec%sS?aqs)P1lLqe$Tc{NG3Pb(yP z_NlZyqUQmMt8f-dPZ$gQR>gwucAEB?ZQOEpVny5ohO9>TGVuH+Pvk*%44GGW)W`Ku ze7xU=gD55x&L;0d(y?&TK;JBBh5(iUJip>VIlytOrAm33;7TP3X`n;j;zgfuUWtWl z2vnK#W-JDiZ=QZH{<)oqiV`vs$!t*b;e0LCQtJj4{xM0Ni)^eVg%IqZic9$jE at IVG za~JB*-8ACvl;AETxZC1zr}pD6=;BT#I3kjITd+f)=5VK0xC4aEUC_;4^^1SaCp!ue zk>h4 at hl-neU~z{Ecbg82H7*vX;grCAVgEI9;PVK}QgT$GZd4&PikIAtld$Ig{;0yG zqY6!<3YU#4G`AF5S_-3D3YYU3FuJ91MN1*wQn<3Ea21aSSC?59A$zO{>{H7^_%K2o zWSNs$_TVuZvZwNZ1DTUq_CoZ8?BNdi=PK4Ua`8)bQURB~zoBP at o?p>3O3$z93Cj at Q z(UWAOc#fWNdVWFAL`z|K3#~z;mNCqHtsRKWzq9bYeB6#|b|7X45(LnIY8_#YD~v>- zu{5r5N&b7WpGdS4!GWM=jks&Mv(S={xTB-;QFrumRH)XDVPbij5PEk{IN%*vx57TJ;168X#FpEn;B5DA9ZLM!o0Ei9K& zQ*BJ4A_(~FYn#k3&4O=^81?xPmZoT7#Om{YZqkBYB?i3|pFc{NOQ z5EzZR4AtIn2X0AHm^pC4V8t~uKcbUQBXw7XgGvWdLgo z at Ek`Ay}#xzQ?_@$T99)v)pYwpa{*;%_N#e*rCdCkYTd8WqO-2KFdP at b%VO)JF5fU; z+^DRKcGjdbg&JRTH}4hsm^+%z$KBB@^9gtKDwc&uAPYMxvQQMVKwXqOPyPy)1ucKI zEelr{w@@oKQa{V5Sr%deyFR$qCO-zau%h!qvV{CiB{0GLWu at i46vGWi?EEFqo^VVG at JM(ocwHv{8JJHku!= z$k@`sW$coCqmZ$l%Aj14v5+HUrF^h6AA*dLupF^u>0EW4fM7$Hc8oWJ85Y9Eb}aMF<>I15 zXTF9I4`akN6aZk@;URAG at cq1)4#OS!CX3c#77ryS%TB-5J->kmjd^IK$;~b*h(Ovs z{ep)W^Kc2Z58(s^&zW`%u=V!&r*iR2YcsokoW= zkPa?R>x)yX$JIOI)2Z at YYFuxX7RP1-Nx`|4v6T^wfev@*?zNI%0x!yd)_v>EvdV)lPS^M1#Ll$)1)ql&J=Q=Kd$+mr6yYA|NPOhtJ%8L_|cFUp=FX9!g=Fi z>`-%34_m2s;-D6k5h^yQ=>rh{&3O>I6A!yeu~SJ^VKmi+49>yf^KfurP!10D;{c;o zsKUYF^KfurFb)n4%E7s64!DrqXIfstL2-IQbHy`A3N)S}(lRqgso^Nnc2q?gYbFW; zn0izWcu^4)z=RcYMl7ok76E6}v=W6pC{PulCpsgEm3kJ!K;wex|x<3XOn6e0A z=bVHS`uu5?+Dv?lAsMeE9#UvTMB*W3qhyxquK%tNJ+r&yIR%x^sH zzrjK%i8V|eep2|UhvaF`@a~1PCW+e{AtNgL7c-zxke-+iB`;y6afCiOm`{}TDfF9< zk1kw-sqhl at 68)gkTu2JoFb;e(Y&)R*`UD1Uk~$bZ#qftK at HGznT>zBzNmcOm4Bw{` z{Kj}bUcHX_De0%l_~Urr)W_3%#mT|(w(+t=u{}6{yk*RVW3H^pB1;jmyMlM}2c$%k zq)!a;SI9&@MH&&sTCfD9`r_T0L~(pDcR=OZD)yydcc!^8623D)9OZzj9-?X__fdjm z4`?TX0x$YH+;ec4Am at CbiON++P%tO2*V6PfIZ? zIxFZj=CI;)t1*XF8gp2Fc>baRjX4aBd1A=MJkhT)333=3GxO^qj3Uu0TpRQ2Nc*n~ z4Y`x5nQzcPP30SCqPRcouag6tBF^qQ5Bs$|=0`>qqN57hC_ehoi2mA0Qj&D5Y~5~^ zt*0dRq^u8qrWHfQWRI&Y#&PuqYiqP}dL?r+-oIJ0wquZfup%&9_ZPAoiVTVc|&ex*qVBS>ZZSh*VaV_u7I3hlu zR{$XkJjq|UIc&o;>u0fziqh-u7W=S`VuK|gD!cYoYN>5d4A{cXX~=HO8bLR1=W%zX z at K_F~`_#7fdWo%4UMqRHpY!hfZ$=jq#fd>0g~O7EM++bq#b~AkS?Dzmb_m!Bm9z<*=B|5e zDDoL2h=C?nEgBZD8YNAqA*T4WS1eyCA+NQ)txB#cMK8&GLc<%WWS^*BR5B_4)&RKi zT%Y~X$=)Z=*El=vK!MdBmDS|ovYI^o3POYNKINO#V5Ho|fkwBQIb7Y$lvf)R+n^e3 zcYvojOCJuWh^6L*`O<7T;T_<4La;6qC7P}RM`I(BG+nj;$-8i}EsdqVzJa}@BqZ#n zN-rr{UkVBlv__=3+3V^h5qB?%zyg at 7mqZ5il8CF9aM*^idP%8&FWF)D5^KjPLgN>T zK^GsammHToVjQIK{Tz1pl7B-lIbrpZw$X*8=p|#Qmn3OSX7S&lmP#D6<=QS;gA0iy zjn;0k=>x&WFxb>8u&D#VE at iNpqBB`wGY5cCB6TKSIL0V^kLXMyNNpL at u4goNSJLkm z>7zJ(n&?n54t59}Gb(8_IL+O3b|*tT9}}G{24Rfxc^3Lv>l4C=({-`7-6V{a7U52I zP|YW4VRI0jEXbWqOiUA~lUZMM(Y!ygnzzQ)yfBVB&DGaXq}|t?@|?c*Dm8o1-Pd;5 zea)#oS6_=&`Wj at o($_A`Ud)y at cV8Rf>Ka5hsWe+8oM;r=n*m#x-{;R?4}|>@IWSMg zu|aHzj4ITcG{RE<>@{f!#lYYEz%w9z6cYnt5O!iG1no=S)ay-*ot!+yl^iTv zt`N%9&F|z-d4aO%@d51 at xO04`}#3>x^ z!1Ci)9uHpTFB1OwFy`$pNLlmw;1SGX$^}p!#`hE|d>MqT<<^SFyat{trbtjMlNh6bp=$ z3|@^}U>pWE#}h?6>2rDHWuhhxFtqwvI1a;bup=L+sTcd4VU(8-cDjqKwO0AH*icm% zQo;A65cIZAcd51FEeLKH=RC{K6cHvN1+%4mxN}?&0BWcbXcqB1Ae4}=5im8dm4T~V zgNf(VAW1ff=S(~Y7td*y98Rki&ly(ojKRfoT17mkT~IuWCMk55c??SkS-WgM`@b4Rs!PIHOpw1MI|ZJ>D400M#AJwQCC4JMw`ybiS3A9TcX>LB77 zbcttBi08~f#8Y*Nrz*sAYDGL}4k4cE0P(biuEr&FVHVT=;%N(V*d at p{w)D}s>UYLf zA$_}T>9a(5rBrqxScG at mA`J0-C=5S`|AeDe_*QFgHD!J~=C_{q-}aVE>Cf#Zc8L!wKEkK9hIL(&8E(R8ef1F_SkhyFO9unM zm0VhcQR+W`$`+s><6~k!wXW4(w}xG8U1wT`&(|k}84hvmsf5fDtSFUWL&-Wo10#Fk ziY?-fus6z{23!WH4#NOdpNgwY2l!&uA-0nEEnj8&T!odyFIAXP{)T=-##i(kHom6c z8sj_qtu at ZkZ^ZZlWp%pBT2OtAY at sXbiR!FHuB^q?Sxfjx+s#y46Y5=OGyl}kV0^4* zCf17sY5#kJ@$1YaEAlT5#&0r{t;oMN7;Tv;R^;CrjNjslVc07SEV|EgE~RnLeiIsNaioc?!>v3zO3y2J5X=r~H*y2GJJ?FtPR#V=ENe~tKH1zW&*q;y)h9w_oe zA}`z7u$hSzD_gPrq&*4e7I2AS?MNu>9^*RpcM#NC29|0S8|V=Dl at 5G^vXGgq?<_;h z$={=vo9eV&NyY!z|ADK7bAq^q86M29Ul>&W={<9);*-8e-cf=2?xP$ zi{;*RV9mC>>v0@&r7g<2$AfV97PhYrJx)|V4)&jRAl3rnnJ~<>-$vaXYo*LBX6r3d z`z^wa-YxC7(0H>pnD`+zIz_CLW7)OAlCd_J?W70&l(xp9=MAFg4bbzf1F-=R&p8kq2x0>uo_8Rg0K^LpglGxe3FSkfu$299u=U}f zhbC26P7K2tz)jqpNUFPY^Ma1A+RI_oe_pU$oLOI-Rgdj?OqTWvlRspN8$H53tts5o zzRX3#1J#2;s`TQ9Sn%L?x{zwUK_ at B?g!YqTF zf_!S2Qow3$#m8aR5Dt?dI$|!DizfaL864HU^v9?qxCF-~x;b zz?Le&77K7=I6#1JQSIhl39_FRr)hGdi?i!<4+LA9Jk2x@%I=Ig^|{sV4E6?-dpjts zjc|N(EZ5=0trx?^x%Ii%2EiCE-dCSnUWJh at 9X-k&6w%9zTVlC41;Xqr#P%!94XKiu zm%XUMm-#sL>tUGeepGUan9T=I)%|&}5-arY>_`93LFli>WYKP8vgzL0kM5mJcMH*t z3(MRQe%N&lkJD!ER?AsS=2o-yR;f6zo(q75p+x>#)PFNreFYD`}}W zEfxIfSCR!)Oh4_z3pi&kLLu#DOsWfL#-xg&nRP)B-v8t#t at XirX_D|M+vKmo!6%yf zoBVksu%{=QbP0c6DR)fj<+p)2AG8|<7mauhN7kLO2mAW8Ut2|mP*mvuMNxS_J^lPX zXs2~U|H49C*!_Kd+(aQy}Gv2Q#t?11KY0Hr`5IJz=5%d(ZIWFPNt*Cp<$b8$BSkz9*L%FfJzH2j z{sZDMa!~R3wM#rkl6aJ4Xm81ie3WEcK5UQUNV7g_WEKV676obebBoOiAvTZMVzV_2 zXN++gTsni9AE#GKV}2~PO-mr_U9KOv*s}c&-U{D=jqMRQ3~chi!(Lo8aUGw>hkJQ^L>DC~FZ4HWrEU}I_XwR;r at o^nT z)9aGCZ9yz!IQD8X_kt74Mb^akbAb`Y6p;k61ov7p_p}Y?FTNFvujS%JTi(v^RJEpH z3|wkIfZ#q9v>YJHtvxYT;)ZZZ_(ysDAEYaU`$4=??BxwYjoIyokHMkNR^gM*htH4u z6NK*u?I#Y<}!RF^0a!=&pkt zpRMi9EF;ZlSPes~@097CK6o(=%Qb#^ zr&suUQb4`Euo{j_8Was%R1{?MjzU0dkvyc#(m-Nv=!TWAjZzuj`$|@&uxU9QBJfF` z(tIl|FSAP#?G*hdabQsH$jeXM(VY*NNu>8u-8=*0O(LFD%x6gLdyUeDlh$XYLha)w z8eLGhJ|E@!%qv!fTFbKk5S2hfE{zw%aWMn11pynfU`H3iRQY+8er#7>;o|4Dcfx;Q zW+Z`)lxPT{kRn3P*s2oLeD(r;e^A)NzCY-{F(166o4 at -~v7)Z({tsxzTK75Dk4yGq zLX2zHmGjJc;%iZ=b$yUVgq{-i*-E)HMC|wByt}s4dZtza07_0DRxoC#4=Qb+)JoXa zA@!(aDM#h79Q4aCOG5Svs#6MYm*mz3*_P}}!M2xz9vVYhXbh=R)Q9(QJy*1A(GzIe zEOF%}xbjL)-J#kh^=8%tORZ~y66pdhxDtZfJJ1o}x*S4h3yr_IAX9CT2;y=IRo5K_ zMYL*Ov6>b3NW$>M^>3s({kI`Br>gj3#My^oe`ROn}xf0nHQ9;zt4 zCmgI z3wB5Pf3x at IadH*cz4*Po_g#9O(KG_YV7F(m+#o_6uh=hk>_(pW=XRchtw++GJ3Voy ze|dI3`Nc>{elN=)p^Y7FAV4r92_YD5Ff2msk`My1Om{OgXcwTBMG`_FAtd$hd{5nb z`}WL8U?HcQE)k03IIYsS2afHZkH&C>=&6{F zO=%e~i#0sJJy_0D?AjW`9wi0@=l^Bs_aDWL)EUm8f>FBeXGQyUt3#zuYYVDEt@(|2d4 zhlTT9+*3rbOi8pvP_~MHQ}qTs-s{<*%IHCO8Mjn-jD5Fo at U0Tsp zYe&NviAC#85UochaT|iMcktPcrnBkC`-0|;jF&AGvFPBSNe3%VI>@p^g$ZE;6T*s3 z2>l?09Uh{D6+sD^(0M_$TgB4w8`yp!5`G!G2J?nnsMV|Ms4w-8=sQ!qqOOyu>muVs z4!!lPIf9&nx}CBZ>w?0rf%VBNn|dsV?8d7ei#aA9m&hca)?gpxfna78PpG#T2MdIQ z7+Xe%?ESR~k-k4D*dxYFvF}hOVt{>0E|&K>O!O!N>BKs@#+Ci^GLdYoRT!{W%)OAe z?W at cWnp?uZIlnj?DZ5q6XxTy4GKckCsCmNG#n+I0NFBi at ++o!;=$6|Y%$5Uf<$zS& z9PIm%WYBLr-|+oFg$X$JiV-Z_%eL=n&C&E$jw!)%?PqvC_>fBoTh_rh2Q#k)fX+v$ z=QKUehRA+0&+9^Q*gFg+M_nhG+ougo(5=68bGuV)vN z>{qik5fHxM0?oZ#M4f>lf>HJzt zz6pcij&qJvYMo2Yox#3xaXV1$wCX0tYAu{2fHVGPN1&8GsY{*mKq-Anr?IO{6c&WM zEM#|h%7RTcb_O$3yb?W9$788i8#1R{E*{%oLrZ~V`bV1#d3sW&Cp3b;+4fDT^P9p$ zwr5fDs)g+ at xk&u~>%4bSdr8g*Qe|K2H7^lt+ zCggQqkD!6-^UQFM&-T01{cZ`@M2vT$ z<8imeU|i!hO~@G6x#2whWZ1GP14F`P9}bTAR6SevWXc|?=6q!;7x~AgQx>X%QO?LYG0KZuSMPDv+xD;OqcN|pD z`#=xwzTLs*c9>QU8czobMg7W*sq;45y`%ylWZhZ?^pm^(&H*s)-Czns}`WnbaQ zKFiL21lebEcDQOgLfMZ5bNea#emlF_mKQvk7d&QR%N&Kxu?ste!sc>emr!A+sIXJP z+#IE)I7b;$7?-l#QOZ2cS~|MDqN{o7xJ~l~HC-@%mkkWN_R{BphH-!0jQtB)GOkmg z2$#D~$zCR$IJI5oDPE^gJVA?01DJTL#fj}8-rq0{ZFvx=m;o;WgJ!^oz(9j(;;n=$ zGWKZ~n5K%bcjuYh)xHu>?o2*_CW#uDHLC^-&l1NM;q-k- zfpQCq!S2NEJgJ&_%2{eD^eTM7PV>T0?VEh)FrNn%-gI^a3wy)O?2=$turISWj4{7M z^LRCVfOnEE>2L15e4x3r8XfHW$!-soqeNjRXqu93X={;+(-ew_>R{C at 2$YjowDZ9}HSk3l-Yd-y;RtJ2EDw_E^6qdzX9$uxPWAYc!c&B<-8~0X at 46fu&*0D zUPh(H6gON?9K;dR{c)pH^aFLv!619DIY=aA5m?DPc`81 at ha3HUKe^sZlUPo_AWN~o zaMc$)9qBHFaea4e6=I{>%jU_JQQFrE0&}Ga~Vt+xPbCg!162=q-r<9_aDX0d{ z*mF~q=He8E=nF2vQYd>W*mf!?AUzAw)CJ&=`2r)I5GoKg1lI zPq6K~nQhO08ccs0lsX&wOSn8plJZO#&2&(n<-Cctg=h;q;kvA_HPU%m=Vip?u^AM$ zfzs7AaUkg>x>C-2ldgpn!YAW4F+B^9HzDF6%cf;|!t+hihdj9jxJ+I)7O-|&ae*5q zs1Y%X70`+l8a8_`Vxj at 1Nv^+3G=RvM$V*v;c}>a;)g}!x$wf7pWVb1k7z;eSGA;15 zw6AHR4sQ{zPVUy+M4I2i?48OYIXsWhM21X$Ik?|yl-S>SV#lDAn(FD9G|*T2$!51) zo at 7N1#C!M#{ZT%KXY$HZcj_QTRcI2{K>Ols$q|UyC_wuJD9J+e0Bt~$zm>eF4tTJk zhqoG>eZoGn&J$S&8$?zw9A;4;Rikp=TUSDvs=JmkG4`+ZL}hG;FZQ>Tav)ekRY4_y zst1D2L}}=e_tnuHlKokS-aiu6Ax=KVve*#1la%0+2WnuNca)|-7ywoVn>)u at 7qf*u zS?7%&Va)WRjz$dQa-s_Y4F?^o9j$POqcoi9!% zB{Nfz8P%Q;823yZ9QW*4=eV&QN at g;lv~uwn>YfELS6-nd9=c1JyJMcblDRjALBVY) zc0Wb0NP|D113~ztdMM_~`q96BnNwEKNDwW(Nda}eNr`=znPzkL;bHlmB2F4goZQT$ z(rrR?=L04Xj18d5q%yHX#gB%$eF+suQ~SxLiQq7}|&(&rx^h2NKaT}ddeMgentR6eS; za^pbXDCQu=h?MSb(<|4(QK%-`etcBqd@;YlJ>=yLRYx5+VaW2-?t+^D$8` zw#!t$H7Wq?aD!-t9c~nj>0z$0CLeWa9um54;Qb^w at mBAHxAG<>>;z`7QJOQ?DDoA& zeMHf-?G2=Xve6mpjkVE2RCp^&IZ1?th6z1{9t5ZzjuNtq-L;5vrTwiav1Dcpab;9R z8CCEJlvSN1b!#44ZT<;s#{i8Z`$0S!%zh`kUZ!zQub1WYcjV$x at D8o*kN8T-DsCh@ zm=&z{kw{%s3B=N|hM?dw2r3ox0WbMSnRi+fhk~o-Cf4*T#F|c1EFt}9a8iw=AGJyP zQ6lL_vAeT~8$Cr9t*+&pugr_#1LiA3KY)>V*ays4hJ64>*&onneZYK$KY)svt>6Rb z#rlB7BYePo#qk02ZT4wCu}||A=;~@dVz}2aE_`vYx%0dZPqJZRe=;-?ePu0`Oj{-~ z;W`@3^u{crLs=>!v6W`LtMfc!Q^5-LvmX3JwALzu$-<=Af8)8xMF at JqMR1~71l##8 ziOU~zOoyOlTd#iNK(>KMHrtsLjyubEwl%<=?h=+lQhk at A$2JS8tlu&O%Bz;(*r+Vy zSfooBsl1PcRRxNzyd+W7Qq4u3@(ppE4Pl}rqztxgf?Wwd$(g3Goje>6djP=O5_@^0 z?0!azXJzPNRfBv|nK7~Xoskvt!w(1=G%0PJtJ7c>16dVkrN|oqCDf=(Y}?5gTk z#Z;W{(LB`eSHPW$Blngexwj0jUKLW~G$5naS6K+))=mIdxWC z%5HaaZ at nAr=6-rF*lqfjeTb_{2$yl9DClTsuT?VFD(Lav&jNUuWw<8c*}bY8RlRU? zw5_UJ`zDWQmM at s6KXY&WpK3eWyI3EX^7;dHCSZ at 74%d%k=k+m44+IlJm3yf|0z+}B zlIv289(S<|^kg-4Y#_)!bI{L^)5ipYQ7W%XDG@{svDVmtO9A+V4Y-s5mpTBSWI!(@ zR)Tw~F7&ir$lWO987>5evv*S=ccVSe+7OEY at tj@FVuD!gsAjDVcrO6g*+tz;fcK)P z^)|$PfOy`9xQ`(21H=X!Vi6!-pn(bm&*NYzj@>7E66;@F#+mBBndDKmlgH!muEOc2TeLaDN$FaEhtEVl0C10rn8vO*E`qF?RY3 zFGu<^71myOO_SYLg?)~OlpOD6evfjg-1J^orq_k~D%_{%aF6lnJ9Sw4nI?HMbcC8G zL&t;GE6$s}Ut#4L;^FRBywJfImU5MuGNAc!P6rq$ujlp5Hn%VG z1?tj!VpgoT at N5(PJcpmP_*vJKSsWwj0NV0-wXnXa(><2Hp9&tw1&194Q~L8w^s at m! zFW_fmlU>grnDvYZ6h^gSR2xRMVXIo|OGvyL#5L%hfQw2_Q*Se4>X1{xEre)6#l8fz)e5Gr#q`|9K^*3m-G!H$C}um&fS at 9fbcZu_6H<3N zQg=~xK?Ob?M^Sou6t*_sbfmwfDY2k3*$-42Dw0DCrx}Mu{9|Fwo<;G~F~?@}vZ!OT z`DE0w+1wMg{E1NDZganbeNRp`Q%JUO9^S at G8+M8zWMvhya_;4(O7Z2Ud?2-eC3{9| zA13x`neSG!ZC#xWyW%Gd;h9Z31XA5mTxm~AVH6y}{l7qJ;gGlx#Vr~VMjynaX2&~N2B_y at KR%edzGzx%@@ud)dUvy}oN_(m=b z{$x?!WF`PnYpA06C}uo?n$aUvW5I~N9a6k5+1)*~h7mgGx2_IaBDAOkm2-(RqiNN{ zx>P;NWNgUxm3~Mw2FuEBT7`p3Ou*pALav7v*B(%oJ)FJ4ObT)mm`+aI%T$8ck)MMd z*++|?MFI9ZsKEip`#T_O0qC5)G7?$4ga1wVe@&+B0aWttNVM&}dfN6r2LnW|u)9ZX zIcV4O>RIafUzQ_4)N_c5+EA?+;u;k73l#aZVmZ@#S`i2B;=bL(7DxX9DPXGPW^SDrYmpklv$zZmoli}4GQncx`leYv08~2Whwix(wuo%k#nmR#1_TFRBUFr zOQui4>Pcl>f^hm087;^@3P8{IrOcy>9JxT2%xnQGo2NInUF6C>8P9q+t4B^h8JEox z&ZOJ!f#&HiWl=i5QdH&Til%Xq4~5zu3Q6Xt|H at bBYoh)xH5qFZQ~REy{;E)JGM=o9 zsx%c|uf=6wYifI~Niw_cSN`m}n6WNa=x=IU7n9pJ#*iy}wAT2xqj5CLpV=04!R#rb zaa!_A*?3w>ZPilOH6Y5gi~H+9_OMRj0N>7_foFzpm6BOSvWonuT$1}`+}8*UG)bbCcv9Ep2I%+-_-WlO%_wZB0I3Z%EqSB>AbuA88)1qHcl1)Ludt z`e*cpdZ8wsdrHanhDzz)kWAlO*yJicrQ{x|i<{{xKB7PYj$o-(HVoc5t>loFOOTam z9f!2MQ(R<-^Q9CD*|!+ at Qqoru^HsU?tDRpR$lj^6-Kj{OCC^|bwXdOlyT8I#-YddH zdIfu3F^bh62RP3vJ%?)<*(E0(fOWzagnKwvNjdy$=Q`n-V2QSzW_QL!QG5k2A?kIF#-#3maSB>_bZWA%zWg*$2 at fp_t@PvI#+$Ch(1mESv}nDV~MKQ_3RfdWG`d zg~lUz58zAv?W<)x1MO?EM>9^(dk7Nbd7&`T5)`ps%99V+u^1k-4|Y89w#QFBnJsxR zYWt(WcZ5PBoFB_ZAh2FU_Z?CW7Aa;mjGtE at _Ns_*q51!7ED)|4=BPHkD-N6&WCY4 z0i$9#o(P-E)G^KDQPdx?nl+3s_A^f>PMF*S*3F1ANDj>Zr}j6bcQnbr8LT9$abQQd zoRli*olP#-X5^i%udI{TcSfzB+)p{zJm3&p%|4sN8YsX>*@}1P8GJjZY$Kn&XA5KcCcN&s%YFa$$O1;}aKkqdp-KqES zb`S}N=_?%<@62+?bGswb~0(6tn+b>t`pbeq>M;xH9qXOSt3Y-ZU6 zz;_xN?i2@*=J8!^kJG~}Uq5Fo(SITvH`P2vt)%XW$0xu{!9G9p;zDKkHHE0wUVjA! zG={|Pq1Xe%V)s+*{$a8E{4i5RU05!>-SnC5LN`iMRfDtn9}t7p+~YVJwqCnp_Ur1} zYgfF7U9h}H`16{Bk?7Rnnf_P2d&X5y64R1`WwC9?Ha18EdqQ#LHYnK_mGp~>RNSEC zo>y9n&nvk;g(yR at Psy#NqSq?9_4HlE^-Atp3O=i3*D0lPW*v!dFW}_`-Z{BOKd(jA zo*xY=?oCQp!_vdm9scI2tgMT7rL{xFQ^U4`*jqp_jGqX8qWFom!t|mWhqadb3kscc zQOCI#b)47S`NNL$;Zz8=dN1l|Mj;m>*B4sZIfertS)N=hNQaE8HI1h1HyUK95}U6{ zk&UVpjNGD0LBKx5+J at 9*V;gw!H`1rrfVc>qBSK9gbdd-pj13BQ2hM3;W^Rg{laiP5 zk+mOf&c3ANoARUbiEKE34pF4~N9HBP7m0tK%J=>5l_&C}&RqFC`uw^X`meH7TNEaC zoQuFUmdY29;+i;+V!cw6$bl8lb6D)$r*}6=={-$S>PSNmVUqE@!otRSr6!7+pgz1EES9%GxjkQW)ZxbKC^Q8s~?n5xv zPIaD}_r||ORAwWC?Qg>GfhLi~f&?u?^Yf##VHy(Z+x)pK*^NF^9$xWAW5vVVvj?B1 zU&Nybboj7Apo3<0RnkL`qz4scmx!oTcQolqEu_y&Eym9x3SQ~C_!~(%vqbTK2Gp2~ z$Nbzijv{{U%HAU&)AuMI%E{%+V at tD1lGLgdy#U zY##@+aQB7*h&en%lk?g*-LCY%qIonp%@~uv4-A>2R at WwJrkOQu>$^L938P^U69c+X-t2lT5_tfN^|$yg z&G5 at bO!{)3>CZG_raL34aTH07!x7DT+qk>6R5(G2G7zYPGQ^#L=*^^DD$m9cNn(f= zp-37i`lXQ%<(r2hspto|+l;bFPX2ncLx}DCM-!pWTCSJ`dC!OwfbfQgQ(%rW`)rYa z_Q(yhhV@%Mfi=fs<_e at cDXX`BablU>3SG#GwhTC_L(L{ZRFZ^uUl>YvsS#0UM{cw} zSkP7R$CQ=Yw9HbppAkcWJ7UBIT{S zcvk~Rpye>%K#BPzrO}Tgsr`LyU=uq#AJ$&$V6gUL7c^<@DFSha#&HS5tFEovLo(M# zilzn92b-j+V><#hcKX|}(+Uo`Dc_K9prsFDCH*!O-lsLQ{gC~3xXE}{;TZ8e+(eul zebL^bB^?kY?c)imhFG)VPO;SC*)f*h96_ zC|Ka{FYqT6xHW~tO-{anVz6#CH#g at SFgGYX1bIal4vpjzfdazpnlsHp2l3UP0du4Y z)+_3Hz#6TxTe-fzl{6Qrz}j at Q%cXf4d4)u|jxUkGoBz^i7|)R+vn7e7UNb01uBG6+ z4Q9|4nMlF+te`tmpkT=Y_C$U`!S}6HFZPj1ST=(`*l;1S*$nz4V=1`JYX$>wyG??t z84N~<;zGhrDmc4MDuFMZJNeV9a_fiHc^_8eN2|B+-xofvK6VOo_DB}gk7*c4Z{6~X5Z!LkWz zr4k?GNUd0@`M~TElKJR@)5ngK>aFEQy(2*Q?Al#s+mfDcv%M~q>2XGxdqY$7^E6ev z+iPtTLJag_i_miPf}`S#I#g at 5V-{zFLP8O{n9xmS-rJdaBgVRBHeal at v414#$7JCr zc6gG!Om9-sn-mE(i4*YJ%@)(qfv7=9U|chA6~g5I#be;DMdwWlqfapxenAT630{!o zfl09q*i2-fivi4L!p)I{rr>ZIO$*Cn`e7PO(}o=&u at 4~wUN=R6l`dzxI65Docbfd3 zyw_SysR5W=>`LwPmau-r622v@$@1XSpw@~iaT(nl(Ou>*?dAkAx_*qVQ*5tOq8993 z1=~rYiz~dz6tf2NIbN-sdty_?UeIEji&lhbcIFGY7srLD7gdK?bOu%;!KbFlt0sc{WM77&w~~HYapC)6FEg0FDaM{_&aY2)q$Xh#IB2}0 zVA|wfQL?WpZLca)@fC&Q^1=2GoA6ZIkK&2C at 8p)XzZ;|QI0gI3lXeRqYpjm%^-V!#R>fqRT4GgD*{&C&HV~m)?{oj*nc9 z-M<6=hJ*=#PrX>AKjEVECP(~LBK{^9EObrd^8bt#eh09-0MkDGCEI}Tn)tj}i|ENa zW7du{QxZk+&1Jzg6D2Q?uU${Yol#wuRgJy6Lj!Y$?viEYt9_SdPn>NRcLo2O at UOP= z*)!P6ovDv-&Uu|)cg}geZXUQMar^#;y3mcf?hh&tvZa!%JA9^|1rsmc5vw*hg^s0` z(BaK{qT|gA9VTz=IQ<|5y?6}8z?TAF)W%MfFBwi(i7)uH31>PYW}g+~g{u}?A6S9M zTfvr_Lu~d8mosn&Rku!SPoCwp_TS9jDxtS-)^XZ8q_=LdhC}q$t-6oK{T1A0eO+u^ zzh1X--OpC`bFX8YIZR8Edn`6)Usob??PxcmlVLOdUmfv!4ZKhfZbfH^1^83K$$`|y zc-iDmE+Q_Wv$1S4C!;BJjTv&;EQ6a_26w=9Zcnx9{$;iLFV*UtYLx-!n{JeaJ=(hdvHV zP at Qp+5*QZ)*>K!P$#uy;mx^6-BQUXa;OTGoVmh z(hWjG&#ORa2Ea}eff+Cf%m5ffR~KlE_4;w`^;qroI9{nyRJUZ+4Vn{KVYW#{yejr? z+_>tGHnFL2uX&S3P5OB|1(K at H`zeTv+izwn at 9A~P*;7qPSMsW)G)`$d)#UlPj1Kso zSs70O{Y(`Fk!DsEMuW^M8aZCPJ7CUMVV`7Hm{iYNV^X#1Gq;MUHi{E(ij{h{nt=J3 zM3oLTH6Z6z6U)IVZ;YEknK|XoMlb_f{E?V5Gj at 7TG1h;{mn9ycQPi3p-5RM-L?hmS)QW$)%#sFZASm5Q5OxtXqVk$sBagU<|J zOrP1AIFlOmt at 4CxWUHIrzz22l7l<>{?s{Unkh>o?ORhO%fo~}IH(|1Dcm6h%x at e@% zU95Zjiuyj^bsk5{)sG}vXj5Q)RJC^ z8gH`xi>;;fmZhb1ukLm!YK(@FlTJj9T~^W(JLym@=@2K~$4M9B{2cvSSQ$%dUF|Dl z27e`jGJ-HiZ{ywMv*Fzo8}GIQL+;mMk*L9WjQwfkN~!%*98W)>djeYcm<;WxflFEc z$pvkwDG6vfu^v?Ru&}v#I4V=3 at o5A`Lkrg?k}@p#wTySfNWx$wf~l#E4Y+XR0Pp#7 z3ngWzCb+n%3E8X#7R=bn=(NN?O(t3H7b*GsvhYupCl2b1G_+$o{O1|d6I3W`*+IqF z*M4o{{D42;yUgo>jpBee;Hd=MoxfmXqMr1O1X0U|klh4Y=B`b&TCIimyhO`*$ulz{ zxjfQ!3CZ&d>H37^o|2GmNDw`fdUW$1hnS+vbg!oA8-zN|aHB26qGnG-ppkGQB2Vfs zv3+Em3Z&g~VQ;YW>%uns*E_#HP?wf|J18+b4ZVzsgJqL%F7usg`qHv+=LE+{yA`O@ zo^M_i*{uk1**4N3#Am*bm)k?^$sVO*?By)ynGQ(I4Ay0ny~?m0#vZ}S?NP?#lSn}K zIur{auRC{k{Z2y`0jD90pwp0r;xuHTVvRISSqOKxel{Axwj%;;%OWps^42`wIv|2< z&VnI?#R_F8gu)gr91!frJN6A>0o3ZOV9laMa599OGqi^vDx0*(?Nutpy^7V5?9$A~ zB6kIC%IzirzPMYdS)|uZfPFBEi~{x$gCIH?mxy%S#hHXP2e;IJ8wG_*+<$h; z><;o_cW~sx?wb>Jhuya%tix_k-C_5w3H;7Vh%6SQy;tqG54(jLv;RzaYMVSm54&k> zSL)|^HGMT-yp&sKF4N{=RdgF zvr304moehDrQN_9o5%E4k{-l*sD6vUW+y&_Ykh|QO}JLB-Aa2Kef7{t8w0)d8v_sP z=P5zuluHPA=KTs2!v~#Go2Tyl(Oc_p$oD=RK>LXgBUGJ}5RyIbrV(3In47p79enYa ztCQ~uMHGAc8S-8^>E^kY_s5QcWD$!JsPK_Ay_T8Rf&C2dEJ5o;+A&Odocq&{qpS12 z%7{mf;uLY at Jmztu*Kyo9H1C-*=DmbPa}%G579(V~k>U~F$oLA4_PoTIH!`N0BV62v z;eB)@?)WiFln}$anuFNs!J)+Cb_jb=YxG89r1#2oL%-FUA2gU1b1Z9fPz)eR`WRtP z at +}ea(r{uVvH$Hew)<1FUooLH{!(BKDY&G zLcf`M9kvcdg$i0IqTGv;u7!wAFSIv{o?s61QtIQ|5@%PHoMtnb2hh<^a!1!OnJ2+y zo&=NmlRXgHBWv-4Wg(^aQ`Vt3E*N~vk* zP=2af!VVYmCj)3e=lSpv%Cpjua1RoOv<>yvT1Vr-q;6R4vn^Uz-@|#2-5zR7%xue8 z(F at dwkl6^6Gz_c#EInfp2M-em_%O}KYTs)Og-Ic5-|I}^8d-HH47+i5(>*ZAB1n$8 zYy>R2BWlCqVbYz*1T&*EF(0z>QOV~yT4a}OHmb>NR9(q-vm&cJKuoALxk9)`>rc7_ zj=C*;jI)d3;kX-T?8Nz at C-$pK|fVb&9Zd zwHJ>O at sYogq(}#`DBJxVjS;aG|6NH+-PMq9G`Hf%QE-73bm2}^>TWCO#%;aS&5dSI z?8i%i#&b6vuX^Yo{r&Z))$UW(VzmkjG(^Ox3F+tJ?B9Kfl8?Y1O)WO*Mcc=YQ_MEk z(p_gy|BTFCXHWMGPltgEn`^xLGq}d-HrMzrxW;wExyIAr8c&03T+dwN|2zZNcp8k~ zJ&EWa(~z9DxyF0W$~Cgae|uM=j*09}SY^TTMm-a`frGgE`iF%b;3Hoc8nIDtBvx{x z$x8C|i!gp7zbj{XL!J0&Gm}1BaI9k{{Z3{wK)6ZlU*{+}b!%gf*n~S26KP7e8lk1s z!wc3uV!^s6k*=CN=I7!FwS3U>iLA}TCML5cU1HhOVP1-Z^wVv|nKEpM`NpB_q*Lx0 z8|C^S7c~l{z#iTuwI{8`XFdWxQ;QlF*<|WQYd##no0WIfi1IykAR|alNUX_DNsmKv z3f6ZOe^8j^Sc>!3;vx=FO((*n{q8*4=MmuY;XEt{B3#?9$9uCd#kVe~dsRLbk)^xymF zf&KqoNhhtP-KIW{!dCT%@sn3X){V$#UJ>ar*~Zjtiyf3$S^D-G?+9h-IW^u9%F^>| zyd#vQ=Qh~9BUC`{u!3Gz0hwn7eXIgUU07pFdgcKUMwl=_;kc9F))`j(rVRpqLl+ at g_69d#P)DLT4tF^w!=@D0+`vX652k zJD*5JF6+&P;gG42{G!cF-uoHM_o at OT(GT&L}aHov?VP!nJ{kgGn`=mOfKBeM64m7k(ti7c`Fj3*&-M+gP3X@ z5jM9HBX at Xe6DAKkzN+Ke;Lxj+=4>VO6b&{XjvNkJ%XD!s_I~O<(qaijoVON-MfSPc ztRA^B4$|YYFH2d%tA~YGd}NLKxJ*G37KyQvqcGR7cGg3h$1h=Q-G)ave(_toT`uQt z7k}>>=Lb~s%30cf2GU7mHWKX=TZ8o#k32eN>qBUL=(lVAH^5yC;qjIT9&d?rr*8Ow z#)tBFO9YR%#JN*9e9Rj at _h|pty+td{@42 at W*Y7lSf`$7M^t05SHuCQe9sfoiar!k1 zav;WoSOL%kD at v|KG3=-z_&iSk&l;h|{HT#XKe7sXm*$e)lzK$VH#wH!_~ZaVkxY02JtqDp_?`n0|7R?lGwo&b5SGmnWpsOn9FdlFhtIKc at k6DrRD1+mJr!EiJYE>$I-(RF zz%ux{4hN;p^`5NQlQ%83Y)$B>(&>g3P2KWuc~mJCkD~4YXKByGU?@|U2}meOzQHAb zacIfZI{-h at NX4^K6$;<%$hU<{WrssJ?b~YCdxYvef_k?(>s=j(eMSb-IL3I})xH{5 zuXpHhyqywY%VN034JPpeT*Ky=^yRYgAv<3gFCt*e(W;JOO6|*_x3`OtFmsq)h3 at 9|i#pODHNrsLp1Qp0Yho^n+*_CTbQF2_ z)m4175f$$T^0=X$*pm?6)vlAzFMfcoI-pCk&&Tc#b<5r1$x!oz>*eBiVc5;q{r$k! z{au!5o^XR)JcjH?M#8QSb&uEMZL>VuRTV<96G~sD_=(b&_Y_YlxfyCpafX_kthN>> ztGOxkn4;!7>Cvg?W~#|*ai(g#?E8ET12DzWXb zX=-Mg3SRAFYJgW;X=^=}ek~hQ)o7z|H{Kjq3HZ3ej?IrN@)gEpb<)@hxg2D)XqbJ2tMUdpr!3lK&D`k(@QPwD-!T`sKY7v<;)cKbH0n&2Qa-( z9>ivPuu>jFN!jbwQs#P9zRc~4zC)DA9dJ=a%~Tl7Kj|L7H*~^(%_t zSD at sX_x#$En at -;|U1i&J>YlupZQJ?Sw%spXzml_0 zg!-T;+V(`qm02B<2?Yw*M~qWSVMT)dm#<8u<}^~cD>b*V3pu(nD--hh0Utbgivne< zz~3VC9ad(Svo(cPi7y at WL%|aF=B$_xhQ%Bi7IRNvXv}ho0ahXGhNtw;- at 4}U?y*1q z=_8>B6JvkeEkBg#rRNU$uzq)w_ZC_5$kM~C&iY6~^17u*6Ov1l9!p4W!krU3ybJ$C z+-dTZ(yJ5F)q|BXp16|Dv9M)H7Db`6{ah+;RN%Wet z?m9L7ctV;q*)$n_omyHpnL6{^B4GTYcXA!zWZQfjr^KQ3c3NS`=@d6TeOhr%o`;<( z;}bNtlzFSsm7`LN*QwU5mZX!q$LG~1S|%iI^N>?izEMa7s)Yy?O%otCW(u|~Y}<^O z^B$V>Uo_)z*^4H$=iM zIE|{A5|U-F4=dYkSlO=ST5&7oHbT$ufh-7vkcMoY4kyPqKMS0hASg4BTQwou!)kW}P1 z<8d{;EhLvxFY7(+J3^6#Cd}qg+hzf?nPD~qW~$uNzBLs2HxuUjZQln3B@<6i9R1H- z?I+ZTF+6pKJN1&8`u*Xl55!xpl-p0Lk(S|Uk6CHT at U-qmGwq->EN!8cRu*Z^6d&0@ zjQU!>h4`{=-9o$$RuEL{7UI9aVuEblLi{@lO*KPqxKd8t;whUF!hfgGbTiEhN1v%h z=Iupah%IS9hPTO4aIl z)#^8@)jwwe;(B|`Z^4+~U}MECG}gBOD{c(A2m%fk*Qu6$qjjpVoJ2WpvJ1Eg1>9^G za1#}96AHM6AsPu{q009R7OLa352@*gRB*7j+8Gv7hK0y5%Pw*)ikxjjtR;xGfSAK= zf?5BEMN2|(Nb;~s!sSzm at y&1%8gf&n(F~E4GB?KLQp|!WwLb+uXr5io!>H!A5OLDJ zKZ45+_=+vh{>>mZhl3 at MjAmFQEa5ov#+a+}++ZasHN9P}(p)dmRL5;MyRc;_Y`)#* zWdyMd5O*+yW!d at 8klXL|{XZa;tsTA+%pdd>ZdN}2eK_4-t0Xp1xo3ARM#Xh1+lrbkhf-^P)lu3 zsB*fM8Z?l7Of6>~Q{}uD7cU5$9Zz!=u<{zDn7kiE_Srx|SW{mf7xxv9MB at 7r4>j&*@!Y5qtM}lPBRlA!4R|OJxfi znoW=O)IZI{ArgeKSjEgU+F>p`Q+7$l8kOD8KB{KtU}Rd;b5vLHQ56wWq7;#y5|t^O zU5!+rR%W&8%05Ly_>{_8{yaDWyS at nQYOM(5!ccbjvhKW(`mEf(HbJQLte^RSyr;3h zTCGqIck$ZL#$G=mIy|6So^760=fPNM?pa!Xo>k4R0F3aw+=^in)ns0xQfYC8=Ve at u z;gatSd8la#A~}z%?AwHR0>bNZ`cu_Cz6IW^7~pU0w%=^h%0FeiAtr^;WibuY zVpSxG?-?a#YPO9c at kwoMGLsT~(r%(87RI$$7`KM{bPXuneIb%7eOIzl3_ITz)e)b` zG!4 at s24C~pHm0do!R`w4S9 at sc&r!=M=;ygaQitLE8u|!s z!Tlkh>h;~=76(2aKJ~Gv9Q_N>zaVuJ>?s(Fh}fb%*~w9Qr`W;?H9a}Xep3c3reaLR zv=I1jrVDYCn*9y4%O)*DC1qT!F=L*l7zp5I at EHoCF=E1T6BwmQddde0AUjtjBF(Sq zxvGoSrnNyhjcnXnt(sKv)@s#ib+4n{gHMP|RJ5yz-ydZZ6Ae_<7v$yu!^T zf*@Yf>k at LU-P@e)1~2i3{?0ax# zvx=t^4-0OmVix&uY%IJYsrkSVI1zNlWFApHW at Srh?0TIm;kPlrdetFS#lC=>YQy5K z2nby36E3sNr>KVoIez~%D8VvfCVc8T+1`v9VHd{Qe0exFpU`NjT5~!e8~$F~ae<3C z>>g}Am>V^6;dxx#b!RptT#+B0J?9L!GpCz#zmVXZVLKX)a1*isiHB_VoFDLAA%A(9 zyowF{dp0H{k6XHKl;oDA7x}*KO{1`--^R+G_JJ%D<4Md zy+i`Do9x#AG5t-r8JqjX^?Y9{orIXO>ij_c>%;v&@Y7%{W{0NCAYVd`czb-zFGry#rUgy4bs z1?r7=s5jmT$%rW%eQJB3S~fNjPeqTH)b^L?@rv613O!y{+h3-~t7`kJ^mt8ee+^YU zj*s7j205 z0r8Rz at jgMk4~R`R#125bY(wlIh#i1<#fEqf5U<)0?-9g%fOyS at cn1)DHpDvw at eUwL zHpDJKlx>Jz1hESc6&qq7AYQj2_7TKBK=j)XZvkS!hIor0-U7rRqsv`zFtR#OB6$nC z;z2?LEZw>J>X%N)#rbOFpAq?Lprs}AO2U;pK*N7PrLb{Pp&T`~tC?4cU|xyi$e&{M zKJ6VyJ!xAz!V%({xV6s!XXO_aD$z6BJ9 at HObx)~Q>F at Sx^-1O%-{6s-MBj1H*Oyrw zkx_R`FMFzQq4#u)+?Uzll4+Frb~B^!AhGqZtNJF17XhTmZBqiS;+tx&U(LRjNWYel ziv6nbrfU3;!i%GfokB!=Q_XDBB%$v0{nm5%#DKXh$^q^lA zH~$k<4z+2PD&^j_noWt_#dk4$TkWa)CSyA{f0UHlLL;&_UZul(kM#n7WN$I!Wf4^6O;KFnxU;UL)nw7HATw_KBQ$5 z at I@X69sp|aJ5g#jVHf+8RcmO!+vYqfPR`?2a30 at -SFjL{GR0B$UPc01(E3s{8&Ws> zeuT>Qg*-0aMoXQJrygib^)#lIHC~AW7A!mat>S+vq4YjK{!N&L(hmb=4uq@|+e0Di zK=<`T^Mu)Q at lW8CD>TA^?FYQB5}M^cR*hq7 at nbc20%GF{HTMagpQu6y?g+_!;>7-A zmM~onFe>!1TB at W!R$b%qR>mt%>^~${)#IH2_tqUA^N*1FMree32H&7rwlA z45gN%Y*vP^e;id^1d;a?`V(=Ez0Bv0R;ty2FW_w%pFNJcj;lEQJ{s}_J>L6yFQpBJ zP34;SXYHzWcq0GCk`T)4Y$KkT#V_|5vq&qqC1f)|t+Ld8RW* zKEL>VoLG-EEXR0Q#!zIZcBNG8)N-d(dY)2qQ#2ZtDVi9S<00#8e2PYud=i4DK%9eO z->g$Bm)Zxh6MAA;+9}ma`!s~p;&GJLsg(+Y2^ew|W*Z3z5s)D1b?hJlmG4ObH*dh6 z9sn(|i at Ar2xd#yU*%0#p zvDAi`M-cM>alZ}mC?FoND|%E^^eBLOY at jXxEwhX1qGGxLu{?~m;qT!oS&Ncvo?EOD zsfT%bru2(7iWkKFIgN??odWF6ILfG& zJb0?F;SQW;~(MC($Jkvl>;pI_G zl3K^(weW<@7S&ZVsjv%SGu;F*<6F|36CmRch9Pf%fyTDSU$|YHI6%*&)PB2`eT=Go zOp_^GrXYmmuqCcPrZpo8!Y#*L4C3Gx&cmLdY)??O6`HYJi^E0z^IGP4P0B6TD6Y61 zB|XfDcmt94$UlI58~@ZB`zm3=LM{7GrSw0^g?}zC)Qp8%bC1P|EYwmm5nrNExfO(# zE418PLXx at K6(!@}dtsm!dH=nX{&%@>jcjI)*Fm>3bV2ydi_WZ8DMvFw5=1jWK;Y(Y zd6pNP_k8`vvl at F>AM|Gi{StkQwFSLEV~3F0_k6~CzQXiK+j~A48A_DW-YpX(i}~3l zT4sqR(MQMr7H4Jj1?)WD%s11{M-ZIBxLsp&$Na%dHR3`=JuhmR7d0unLCb8Q$KzV& zae6$UWgZ~@2`1WT^4+e1_gt)*yyv4~h%X&9QlhS@!|Ey(y4ihSZh?l^;sOnd3>3=V zr?uUuNfbd})7rkKDf=|WE&a46cg6?Gg)NEfXi7rI6Yt-7l8iu#xUle zh5+Pf9?gtlDv8C05ihOkFKT?Xq>S{?A? zD#ZZUc(6X_=9MT|u+ at x!Nt`SFSXfT{aVh;{Inx`H zN~QE*Tq?nCTV at 4o@I4;(XkOo|Fdf%_(Li%&1DKI9Fu3(y%gXvi!p8ant?)7XRva{6 zXR at 2cxmxToS<0^Fj$BP0*#Jgmzdz~en%K|A(q>N_0QM9Pj_Rs}+GMsS*Ad{~Alq9b zA%0 at N1w#2Mp$?LCOWjFNH*B}Ljr%lMs at s}?@isG*543MhjL_d*u~aAbH-%HneB!sT zEz!O$VP_3e*2pEI>pp*WwC~WUq^pZR3a;(=#g6WtI1u?2NA80S+KB_U*)~{P`yB1Q z=1lFjY{J!*gO8};RCY|M+&0FAk)s%lU3PTdPl#OD28+)35vMo?oZ?F|4T`U at Nv6jB zgnD4Mzi=d6*q%sYFUbt;NF+y5^Tl{f>`+R1g@(ij%Z3UKl=$F at 9cnqRcI7=Gzqo7L z%{fZt%tMSUS*&=P(M9s4e5y+w=tSi z{uY|QG#!|Xt+?`F3bBwX2m at M_;1=e%pKgYCwWrd!VQb}Gc_9}5zoL6s`_fC24|VuDD)r(H*;GAjV6i^=9vQNvxD5Yy`uxMfd;ank+|(1;7QeHxNFKMi$|Y=d={ za2d7WxW&Ycor%tPXMAjjpX$x~Kqd1YNS}G1N$F0xT=d=*%X`FYi3lX2DZvt!=6&T# zX1klBjqgs!E+mgu0#YsNF)47#{1zBW;#DhaEn3UyD;mK0ZqzaxX at Q_2pg3b!Lc^yK z1GbDm?N0tQj9qQquRRm)VJnks`I=dawY1ft&5Sdl4P}3hv!4%o`*q>5_Uone*JaUl ztOO1v?zLereQS!kc}!9o%X4+0R8B9!%pAxzlxb>~XqT-eIvM&^;~U;)2RRaX7uxC$+BVOEH8v*if+MXu*T+W&ax;I_(ldNCfXbZY at l+% ze}I7;@E4x(6DspKiFt`DwN}D|T0`Buc*!nIbUXH1hey2(dCEhGl6}*;EpZz*4rYP9 zc_j=Hbd2*nVOlgbJ5rn$%}tM{=0=LsagOk6*pxPf at cCNUQZ0YW6yjOzp3>OZGEJ0W at RBuO04Q(Epc zo~K!)oDns-_tLx}vL&kl(ARBYE~?KY1bY$`flWTEea1luSUbbkeTIX?n;rzjE*s)w zK)h)~d`u7 at 1L7?kVizFZwjp*A#4bSWW+^#}O>IG?-y-t=GP-il at RU}OvN!Awdc0Gh z>bqG3#iHM5I|A^N)4o{?1Ooowba=B at wOu83UNsgJuf3vyUX|##+}=mO75G~Au!mZCDG;g7BKPx_MQ-DimOZMK zjp^aS-O=<>&4n+fjPHhV83lsYDNU$T;1=>$EvryO`tg{QR^-f9CN}R89rSp2c<^~w zi3NJ6nddn1hDS7dlX7oq*<0ndTV<*EmPSLGI;p~(HpcductxRIv7vQ5 at D;!8rRanNdpJhD3JEJT|Qd)Lrlp at kQqcV%UFUjn_ zyDokg77`hLyqV3T?4x^Qmx|n at 0W^AOUMtp-vw`6OBw&yr=0f4QC~-T>;>Jm>#pw4J zo{tdA=CKn<)ZgPw$ZgXw9K~%~?hP)l_=c7n;PQ$C8vJGC-Xj_IJ7VQGU z%nJsLLt6AVVr<@y8W&8bXmllB25MepcEu;b`8r;VzbL$uFb>px`ys;CLt4#vwh3N7 zD>!z*c{2V(U29^-=W0!KNNezc^$my)X#gOo>Q+(p;V>@le4D71RV=Nxy*MH&8Gl)L zH(?ypGVdi^B||Ngnv4(3z+V;0Rsh at bpQDWTon^f5EaQDH<4730vtOYxJUwK0_Ow=^ zsb3oHqGi?Ioh5oINgsy2E~d1EQY}C!3n`b!jrW~Bjf+`~BV#Nav4PMV*^|WJon(^r z5idy0-R$r~>rr at 5)lBUrcq~6mbsg5S?`WBKoJja_7+co&!=>9{4O{R8*b2aRwamMk zWK+Hx*|KK(Wy5E>@rE|iL^lRBo~>>6Jilb-+>jG+lcKu~l$*ih)%4j3P4 z^Gvicri6OMsbRTrFku`@6b>hPAj0N8qHq65%MH?FP>cK_755QHV&aUs=JDptN7NIP zO^i}@yO!C`YtS)XDKQIM`^B)NRyU at rjWAq4&%EenMH3379>?#bNdx8B=83~Ck3y8V5)&l{+-0{<)O#K6rL&W67P`qmAq5j zxo*9s*sW*x%bESMRI_|Po#9)c=j82{OYAiIF#>%|2ggaTt at K(0pAoUVkiD!^Z8IZo z%1yYMds#0P=^w6?ThLEC~=@nka>;hJ!fa at b}kE*SwuDx*bz=e|s zbE~PG)w*bmRJPFEsioNA~(KMWn9}%Y_ at lg^v at H2U7CnK`4JvG}bs{wvJCA zP=1n&INwDEJ zGkky@@0>(=x7g|1P~NTF+uwn<5*1X at DYnGdT>NG2$DP^ZwuTtZR8x1! zZK~DCS$3hTQ0Qzfln^Gjib`2EV)m7e>~rkweaJqS+YHgEkFxh+i09c5j{@R0yB8iM zh)0K1fKy5vekb7PbA#e=G(_LCNzcyGOD&mMI%eV>JQM$sK}&I(c{zF(cUoOqH!qePmzvS5=eAIb zw+xwNq6=3!x^O`RE@&V7fd&BH$DdM5zZxI^&WSLu{sU5`_m-Xy- zvg>7{h3WOOoc at knEbElk*S;*4JuTBJTh_7dLL5AfRk?^=Fx38&7hO{J1v&kKELl=R zS;y at O+}&U7wlflAOLa7hQrT7X=daNuF6QSOPr6{gF#DpOc~O_LFX@?=bjj$}nZuwW zrnuYJ#EqwPt2s~U*u_GQuhV<_>#~5LLR+a&V~dVOl!go2n>0~dF-y15V+)Y2E8=r& z(T99c@$Kj9)|WxEm#FlwgTIt7`n7@|6+aq&bo_++ezMRd!PCw}=mX>7B5IARJHM2P z*wVWo$SC5M@~?t8g8(GI&!l(PFWH>XAz^?Bl^}S;45}h{lW+loiV?io4C-7C{^3f! z+J&?OoU^OTss+lxlSLP2;yT)&p#4{+?*q?_H+{Mvnf z01aHMrx)u|ryn}bxLOXT2v6*>TOLGkHN7tFkR#0}`o=FF0dv{v8nTQnq-E at 9$ZdX= zw~MpcAdTZu^OJo^Ul-d%`gI`k$H*#@xKt7Ex4Xwj znFE|hiZ__mvt6>gccY?V{_|;GU1~3a-m?g63;MfjKKVxPThkKZP;0AAr;`@WBc;3 at c8&?(mp# z4t&Oymx)k%f~V^<= zD!udAI`ql_1=A}7a$!}V^D=Qxa9QVNa0m1SzAqVoJLQ0w9H@}q;f`5MijMzW{0 z?Q`|K&+IS4QyDYETmfh}j5?$cXzyBJp*P znTP^JaVB?%3x3QTk&xL(<8 zU1xt+Z|YN7BY1&6b!tB%i491tZA2NVb&XR8BKN^426z9x(FRAxd(DhXof+4Qj2kt| z_;+^3#dR4=X2!M7jL(aVy%EazM|Q?V(T0m^88?_2A9QBiATmBk8M8MMs$_0tR(46m zO-MTE7Dy_5%~zu|4KAVCU1I-Vm&l!$3|6B5H+3B%jrDph+Ssf^IH5SIX}(@lr1~~N z`zzmkf@;G3<+k5%Xrj{`9tqN?lncKCtZBpoc%FD z3PllcHF<8W0|e>^bNvJ}*Dq&o^GiH2`OM>K?p%7{>^%20MHh+IZMMZm3gBCTb~gYz zeS<8Q+i!GaYLN-GgrUYe+v$LM&4hZEp}yd3Z(|MWITLCn zpr9DNl$(b;Q`7bIbX~#*hKrVj@!9M3Qu=xw0_pt>)8K$XVuljoD>LHusoEX9zWZTB zrFm+HcL??ll%oX;d)L*lcfc4F*t-tcd$)zX|7pY at kbJR7W-$P4mSv2k~hFm+Jp zycI^>yD8%Cp{P4pX>M2~N#*AFg8DLnx)s7)aqd!$R?^j|Y-4W4L|pow3Y3{38UDCp z;%E!45VX8;vlxBn`YRS{Qd-4SX{$h0#o0pl4tMHAq?VZzDm=Fy&}%$qBg%P;%bD6SO0Y~8L3%LI z%Z%8A%!oZWlo9J_DoyN2l=G49e6)LF$2s{X=F1X!xNy$HYjYPxC-Rg4z6 at TQ5pfRn zH+IG8!P-*+yg*$lcoru2nDA0*ie(*IKES$?m}q(UHvk@^>okNyUlB~o at hCMW(4E)1 z^P%p1*t`cfwqp!DvW^LiX+JoM_x8*GUvFOm=G2v)sV6-xwdg%PsY>{P=_YM8<J@?#m&pq$1=LBP}id3txj;m}D zYyO^?V- at u>(7REGy1Get@|&sUIrfvm at ED3l4D#}x9Eo%OgO-&VEmI3k> zo0Mu4pN*uDFwCk1gyGf>QI*`vjjFb|<440u%7OKpJmaOVtzw&@&qt+9sCB$>ccy2& z6^VY#KQew{B-GBN22mz*Gc|S&aWT4#Om zShW at -4KA%Zzy?28cHUV z$!{3n$W9E6{)W{2jS)>lF>WtM)35q699$AfpYTEVUxr^_>U`i7(Hf32-ptU(DG{F4 zK(nmDe%?+ at vW*A5vm4jYl4nO*&W?(ld|6JuJO?l5L*B2{)b6gf6gRZ?%@r$nEC+gn zFazEj^!&+}R_IVEEMWh3g&rSBq4Pe0rMT^l0 at B|%52U|K`Cp{QNiPq|lj{AqF0^%4 z=z}sk6uNFH^g$%*IuyEYDfFBEH46QE|00F{yY|S2Gz}`(>6tJ_udUgKVzdqo46W4c zWvPySh=@Ty6oVOAG?=f`*?&-}vj>%HR|Xi`KV5ug;Qo%cBubwMU2Is8KE)U>X>r_^ zDbut>7!6_mpaKi4R7&&qOY?_*GO%LwiygqY)K4 at 1$kB|0DnEq-vBsu=RLKBrD$|XX zg{>5aoeH#~!_#W!X*H~72%Zi>qSKw!k at iQf8a@FDp3^HCTBcfWXEk_}r3P(!1%t#_ zC1Yt){*ZhF-i>Yy4&a(hl?CoE)1A7dJN1(8)JwV(^h15x>Q1CUi1fQF7Z%OK6~jVoV~Cap)#7D?SJ8T_f!14i zx5WqVf7U%RWv>i!4?E|eFGQ_lGGlHKd$`5|*73Xi7*V(*y&x!FtF`d4Iz_G9;OqFT z>c0|oS0r%`|6*`>sR+?uKUM?(pkyzU9W9XVO`Z;x)HS5I7N4b z;^phPuR3k%a3H`oKeNq#(8N{zwNu3s-YO`kLtwtMY!TRLZObeUwuDQSEE%YhS^Thj zOGu_lzEfgNMgJbYu77d$LhzOG;aL%rIiiH2yi%LBPY1D?Y8TmNUw=Rmm{e+<6``7+ z4)QaqSwULHwlvNPN;dq{+<&kW-zqzI*AuO>RL<+AW%bzww{t~NXDpP%;PD0H9b5L0 zZ=hlFU8_@~1Bf&R&#XxQ!PC=k at a*7vZvLEl2xup++Ek;F59?*d at V`3JP(~VqMjA7v zk;ZWPH)gTp4?cGR7XEheL3O{o- at 8NbMqkgDeNuTTR-MaBsl~zJxy6v)Jv8QOgDMW5 zi4(~;f{kx5L-vN;-rC?ze7E6DusfeV8SE~k-wJkT{dmAMvx@@8%0TvHsPRL;+azt6rUVwIRT1MBLU-3G{P)DMI+!8?}@z- zlKr*8EqJ|mCTN at tiWiDsF|&uGR3a+gU?i7^^WuF}FWz4mZ_-kxuV5UMTJduxSc^-; zXM*GVu)SCyEDE;DPEtat0VA5^;l!KNJ5nL{j+Bw9HQ0Wn3EkPC at m8>nun~&3b}EKn zUOZAduX*K+Fa4Pxj?}@`bAqduSnq?V)chXx!Xuw2Di{3t8`J8@%h22h5Saa;0Uiq+ z2>DcxyhavIA$`M|r>n4LcBe3%c5743txdJJHU)2OQu6jH0(SA5d}j7R?>MX+nyNzI zk?3=Erib%7}lFL=~*fto}G%O=ZMhRX!Xij!QmEiS2gni8>%%7dD+O+v;ZRL;` zV)Bs9>3ohR2x=)7E8L}3VXIy<^No*c?hKwHb+Oz$$#U}>6q%aO5t&n#$efL4YAQvh z<_03u=ZH-0jYOvQdXcHMMdpknGUptTId}t+Ir61MCTMqy(obZ98RdGBdG|k3WDdGZ zA|u47LS)cxO8)|pc?2T!h$S)yLtYY at 18(>f^EEX2e``Wh75!DyUk&}$(qEANl#I;u zd-G-i{n$opNRlkPIL*^4X?$-;Vgv1 at Er&Px^M#HH*u)?_p#zqkM{4VY5luDOsd}cW zokF#ln(Q>a_K(@=>olFJp`}SMJzZy`ki at z@Bd9tmm3~xbPS#L>q=*oZ^3P*8tC#2v z{k_W-8Vh%Ylr=+F1il$MNUq5!Uml-g;rlJZr}A2 at 7KMFcS2a^TlNv*ay!E#iiy425 z7GgikF&E!y at UhQi$TbX)h2)?|J|+xNdT)jPzSDl8&7%!RKl##rPm>o7;DP5Y8+cVD zWw_~pdp#s7*v^2BOpHRT-m6>j?)nT~4&wsA5HSFK)HnI|#D=Iu9aNL4Ry?q|EDP(+ z3t)Gj=R6o5*bN-sR2!O0bvIy-iRgG>KsqX_cB- at oD(G~Kg;MNXbanWy8&LB#-b}0_ zJ)5bHNv%AZ{Idli*pK9|zaVxz5nEfnRS7HlctsfU{06vt!D`~k|9I|JRQ-kAJ^O{+ z1!GxW{x#e^5~_TB8M5!C&(y`e^fqZ|?mT9n-Jz0|DKk`VWol_1Tbas-0&X^o6JK5z zKTsckvM%0LA74=yKUg1MRu?~1f3Iv=oGOF{vN+X_hokSHs$-!6jPmq^@SN2z%uT`- zRLFjQI*UntFNnV}9l-J~u=Isok}L!CW#=OOY<<|6B~tEv?2$!--wIhiki?_X%gBE_&MtdXDaDo}>5X`LRrQ_7k2C=fyjkPsPgZ zqlh{Bqlnb}QKTubTT-}iH z4*Yz1`cY0-k^WA|eoH&gs%kk^bsyepaf$l!mkHCg_`*?1h at YqgYk+McmJn**yB at 7;tlTGtHz->+4H>}T-Fd5O3lR0KOe4!J!+r2(_AyL*k*ZZmQd<6G^9*UP&nQ)(Vt z0{O;$GQV574%;VX*sd_uL$EG<+v6e(d_aCT)-!iY7KTql9;%Voi1~Y7ta2N_^759# zue`(3o8W=$Gq9`n+r+K~pSL=>d!+Gg`Zwmu((+_zu35p$6hhz+^ke8XsT$pExZa(3LYH#K{g{qa+_oJWaQ}%^HE0MfPP&SOFi-{eVJNXs$E}HGji~g0ov2*Y zkIIP({>H2nbmb<1q+e;ka7*QjmzXy;%@pG$`)MCXLo;{`J7AvVm zR&bGm0TY+Jvy{|xT0`zREwyq`LvH1u)bkX3UNdHEe9dC9PHTMKn5p6JX=#K$T49O4v~d zFg~Pt2qTPw%}`9EYI1G{I(Z5M;o0A8g-hQE^Dl!%PkyQ_Q9o z%|^*bxFno}l)&2oejf_ZQpr+LDS3=az@=|$lT}VdEZSwOYmuspJ)BEG>P(ss(ZpIx)wzja?vpC;!)AH zXv8q935y)aPCzbpD(NK1PE^wBK(qp)jr;qX=YWBcXA1!>w;2E% zg)7P!@FZslSI=khAy`?K at D>s}85v~AdMkG*ipv+q65W`AXK(boRL?Hcv&;2%PI0+b zF%B;#$`o5&R^6J4oV8^+>uylZ3LWRfqwp)Ld6KG`!qu#=K(?VGw$WkI6JXLNhe=Nm zlb!&RHaie4fOv`_;EpgRt+igew2)S5!7b_(g>o&5aopG4I3XmZRx87EtCf;s+2Y_@ z1YBF4+7?l{MW}6CrN};A*7EI at SfOn^Zw}bF at 0Pijb;xRz%W{5aP&B44c zPJ!HGkT^c4wpxJEIZ3uedk;(MNJ#3Py#||!6VB9(J10|$m^ox$)kZlsdxZL*;hEH%n&B~XGdxD8 zzdLU>Zell?FO(1AePtEzcQ7ph2M#zqT0#w90v>f~vRqY-PjRbmeI)XQd!AH0;hu7ro4ZOpJ7ha1CKn6xiClJ%)}3AfS37xT z!k;>WDS1nBnv$5NxJKV1HQ&->8lOq&&m=RsQctYZU5!ipl4&gSrpr3Ys zx}AQO`_s$)yi($dze*KnyCgG$`|W1BOX|)Zl=2+Qrw at XjV+==YFXJH1LNOJg-yAM` zxbqyP-`o_e(t4Q2F>FpG`*PUKcItcEu$+a5JGAS#BV?2IpUzS!a zf;0#y217lJ^bWY6V|_sL4GpjO+bGti#1DChpiO-D6d`zTqTX1ggH11KGRaxdjWKsu z&#>m(dWSXF_YG@~z?O?7PKO+6X%W|0x at BVHo1RWK0p3lan5@`xgu5{Ec<(`XNqU at 8 zQ{0q7r7&-xkeA}CmGFl|)=VWaQ*jXiX7+qIoTQX_3W8?#H2u97PRvt0M4IuKmM4ix zEQq*?$O1c=O|6R#&#mJd5te9yc1q32Y`T)S4P-O1HY)uBa3Lti@&XPrcZ})4;_z~ z+Z71zY8_Ag8S11cb1sZm)HH$=1Qjdul$~iBZ-kL>Lg0K&OT0xh^7Eo1aj7+ZLB!4p zkn>dSdm`l>k^g~+T at bO42#sW)EJvl`d>Gd(xK#@cRPz at _@h}*uYl=@J9l8 zw#u=Kf{sf>$JKCZI;mCqgL`V0f7sS;r?hs(8U~j9m%fg#u#SRC7CA5xe|^%cWh(Q8 z=JQomvooTV&b!|8U%`~FP zOJ5%NDTPID-Z(8-P#y5S)6U!3=QKC_jmpR|SFD(zC_#6V47Z{%zbwSWpt`Jzwc62Q zarh|Asa|tFJ7VntL+TOErGDl!%|0|=>e8z#uS2v6IKdfj{mUI_%NFb zd6V(^uw&3kHNM6Cg6phx3a^;fDYRk&r>q6j+YU+VsC(9--jmGsI6q?NVan5-&mT%p+2Bgz;RF*f!lM%}bxV?^B_v3Tx=8O133pc8``<=vZp4 at u$rlpyBkoRK zGEG%RrKT#$+vxi^`ZiB)s)7`k>oikTCl*Lmyg)J&YiJ=34xZs}y7hrS%Mshr;JNaw z^A+>FckF2aoP}K|n-;uVK3{kb%lrT)mum-D=1<_iF`d>HMD78w>V018_uGE>fYwvh z>c4LL;X~$q<@UozOc(5jth;`bWF!BLS!I*80c2g&aB5puz6CKyyVsSTgFS~!#9xn0 zRhZB#A8M%LV@(ctI@{@>l)GIPp{WA z{(zrzj8}a5*iREh)t%m;XGC-}M>pEhQykqSqJ@~6xJuE at B1(fv#GkUE at 8#$gJNjLY zZnX>F%F%63VG(~?H#5HM2AxPiV53fzBd|%QY7yA1Q-ugTrBh7^Y|*I>1h(pg9f55+ z;YHwSoe*LZOsm*&{zS8PF1K6TiL0fdxlLgD6>WfR;7_&6ZQ#!|$Kp6-{VHb<<|rm| zsizcpt5YayT6WQ{Y4*nPGvyn{Yp|!d8TA%5dt-QF*ftsej52J at S}A8>OC~(P3I3!o zOwxqxozW~vTq@?yak$sBj z6XKKY0th^v8g>ich{KwXYrT|aM((0&={HwUjGbC`mlktnck0<)Isi`45<%t-2r{Xh`I{iH(jxOFq<2v^8qT=2jW3Lj1y`!TFb}*7JD2*>DmUTj!5x>PV;^%Y;{+dbB7^X*Q z>?wnJ+kqKIFkK3K%_k2ljfcx%PC76nL|y(;U8aaxSi3~bDt}b5Usjt=s7v_;W+5e! z2>k;!Z!a}(uR_w%K+-|HI8fe0{~6rNKCidf2JT)UnZzB#;pyjflW*5eVxvD(B9hr4 zn0cV0!*&*n_C35wW#)nAge{x3F&8)qvy&9UeUhpq5!Xvi$Sc-+F>}Q3p4d-UYj5&F zW=j}ekq`4#n0xu9I-C&qDdXs4+NT)R*|~%8`Y0vFU9L|FZIIx^-plf8CKjs_)7BTd z9qt?<;)RlE6bhDT6dVyRD^k!7m1JCTWSlfj18Eu-Rx>f*sUXD_kbK{;0^>OazGAPd z2Y09`$pNM&^257+H59akrSYIRBDWC;M zKr2{yfW>Mqe@%z*cWS_)gN<;8!6)7sLU1Azw!Jp()62{wug5eajm`0azU_}QDovbR zUa5iGL at 9R#UYMQ;yM12Y4=vwBW$uWETkeSV>m_$YAvP2K49Ziw at v_o6N|d{t*eYQ>LKG*h3`1Q(-EPe zY}jv9ZC^-sQp>0ch!$~|zXHT+)jZLvHm6kwORd_7R=t8&wR5W)%IEO&wfK at yZoZa! zT#K&=)_C$grgUC?LK!^<$JI-jhI+W3EFD*G z0u5tuLJ%InN9gh}+;x^_v at 19sWqWgdHOAYDZ~&>8={|N;OgMagEI=uzmUFTlYPX!P zX&uFDvz0V^6XoyORpTm^Z`{R~&O1xlvnsO1o%B_u?0FU0^Ei7o*Zvh`@2SY9ST|h# zd6~#H+=ij$M4FAK6^Sj~y-pLy*TKoPjO;dIhj2ev>5~pc6 at h%6>Po#$3;efHYF*gp z_xW?g;tcN=XS|~`-sTKeP3$lAEj~`o_=Km_veij^j1i{aPn0+D}N)e!_<*VK{hw3rn?o9<^-nk~62kXWTR&0Qqk4Bus*4ypq9 at l?V+N!g#Sg-p zW%h`kJ6MxC%;BSY?l3>5B5*5@%UO9{C_|u(<}jlHY$41q5uy% zK9_!Y_3J7T=hWI&Q>qa!Day$WH3+1ldd%lgb$3lns$Sx zAt55Vu19hhsBaI!f>a6tB7kbP5)hzjsGOC60AVMzRssTqBXv=+=)b6>E-4lnmz31I zO6ojEyV3ab3K~yo=aketoNOZb9Zo){fZ#)dU>pE^%n*4!=I+RStgr(hN;*_yTvXaA z0nkTE(3csyz|fZkG?HqJO9Xvcp`uCE_*jWaiDSByP$j}Z0F-vQEREpaXFNT6?tQM| zeT7eXYT0q6peLQzQx_PjSI=Ews0;m}3VOPr)1XbhsdN*AiG%mKiLb=C`{4UF at unhU zI3KNHlipN at t*d^dG&J_<9udFTFTN^b$MMc4?DhEri{0#8wQ+ig?K{0|ak?!rEiBz0 zR&bV0+&_<2at1Z at MQ$j8wzX1spwv05Bg#ur=e%Q;GRsPxLn-b!lh*Jr>FFanhb4rM z>LQW4r(=AtNF?TWjPE11=iA|F=(19vUoS}oC1vC`BCS=317&5rZB%J1$pPh4-S3w5 z3&T*_xw15-8*M(PH2zw5lZrK-(V8zuhNoZF!IY&TepeLs$o>EU z?i}9>Jnt*%9yBKVif)Z#5y>9c8#>v83;{9zeEMRvtj{ex1)bVSAQzN$@0Y_0qMuZq z=qFVa at 4iR)Hr)}HiDL!&5H<8R^cgSKG>`5v!+Rtbq9*236Wr+9<6-?rtTf70S&d-E z5nY^zcGrv-db&)Xsd#&5BN1Q+YoN at A3*--%Bkw9#aTfBUR;ey3#k#dj&)LWyE+Ic$ z10^?{mJ;GQA>}^gv8woSfBNCi={-yBB;He`8#g1$kR@~_Xsb27oY&WVTvcSW4jiu8*&l}{NUik-ZiyP}8}hO;6l)If!B zzaeaUVSlZB`tq8BzcEhu-dr{AZ at x(4D79P7I4Y^&o z==dC`;>3HQ%w}U0VK!!=Y?+N2W!#Z{8785g4_BBFej(h?gz!Emzlhz={b9 at 8bE-)5 z)S{BQN?f?A*cNO1o8~YN2fYz?CCX7s_l{ zNnyiEptjt!65_(RQt|;cR&0ELt=)Eo at i=*k#JeKHn(yiz$YO1?3X8ShB($%~gs54? zE6vwsX^9Oj z#LS-Zmzis%uSs4Qmn;bTkW_s`dIzY;cKI^CR^wA8yUwjdnQ9Tq_}X9*pjjPGfT>w- zz+Q+FY;G3pA0qm0G~;j0b>d5VC at fdk)IK7KTeL8G2-^D!{b+BJ3;l?1@)r72?<@4D z9?u4ScukP;X7FggiaJ$TM?5f(&Xvj9J{Vfs`kLcg)dbU+dulF%C3qEGmQzEw6SK=@v z(To-A at K&D%lH~{jiBq=ZG>@HRHcsQW`$xlg at IH(QDA`LErC=|a#F8k$^8sg+HRF1l z8MYtXzVZ1FZnu;k+k)QaZHw`cSSzOD)mj8$-DzT572l-(Qa3+A6+-icbgR$#Chh!>O>_!)Lb1ZK^qXn<_PL zQ+xA?XH;yGoOUvIQ|4~U+>OlroO!0QzOzn!XRP{mQhhC`@7zG-p63o#DDiRiK3DE> zRiJq%3}5GIoJ6bn>{%UWcL?6+N}tuOG_&!XE*T3{W1(6bg?m&rwLr~}=*ulo3jt4V zp^6()dF1*zH&01+ps07VyofD2)`XxYHlT|GSnO%TCW7n>!mN-j`G^kx^Rd;0Q) z^a3^D3An8a*|s4OLQx{6WrP;OhsBO(&+9Q)r?#V5bdnP9=q{=NhQvMxEgoIn!cF~L z#M_HS+-`X{EC*!oQAs at ag10S#8rO$S`*BTjXcZjIJgyd~oNe4g#2Cxk at L1s!9iTuXVqs5T=r%kzU!c|vg1bBw z&O52ar1E_kKN{^1_z0?yk-5?AgtHL+vtlui%MhR2DGR~9h3al_Z=v8GLnuzCEbPa- zh1Yk=!s|NahG@$=kD}IdJ1-H1ETBf?)ZQQ>CHSEh08S>PBEII zVZ_CXVjz9UK-HLJf@^DWcZ9p~yXeK=?K6#IG?&Ba#WBrH9Mhz1k4DLbJUobvG7Uu- zxm3)ABIRK-W0aZ4C8CRtMtRrLD7#3b?8o=*5?2{7SFN2IsfbQ3wNi^!r&nsJ722@$ z3N5u-gPn)f(61kd;g;%`nBjM7*_X8RQk4RH;lWs;X(QnCTuV+27%Mfn9HG*bEX-xB z(S&Do4dyZ+vQ`kQmDL)rRM)&9r&J1O&nWP8KD;;M-P1(sBIdg3c_OcJJJ&WF at o5+y zU6brK!eBl|J?>RGMmRQ1L`mE`K5_$BH!7 z$Ga1ngv`gfGUF}J_i{c=_H0$Ze%hb$cI39wRK=HDMgRMA>s8#f!xS&I6q3GFO|Db1 z#m>E->26%bT>H$q1hNTJnyXH93!>NfpcXTnEi}W~0vRasHIX1Mtf;1ZdOcE`BQ{_s z0d^wkR}lb?;R6)x%8wo+HIL~@ZdS34wwXS~q+asr6N03!naN(7^kujVOpAf-;R5$Msk+SL=);tDUYfO{3p(nLOrT)*+|I1bT7;m-u zi|}GN5Y~*fYTNkU)F%3pZc>S&yr8EqPo%B0%=@fXTiEKdVA}8=*cvd}RgJ#P+ at Qxr zgPOcVKh5 at W{k5I?wO!@h1tOP3tst71k-3eltTwJv3yrIsHWnH`(52LBwIR1!g(999 z33$D}Mf?$w;CYSRP~upk4!Z{%)KSzue1Cf0YD_hGj2xPo6p_wN}lW zZMn57wS73X9T%x0cyoLw!7W$A^yx}%B(7{!lcyD`mSnUI=Qbpb#DEqONWM0{Q8hMF zY$M7~j^OMKg1DRtE=STsoP?KS14+>ot}xC8hEoBe2@*mz5nY6r*YJC4aPno*g3DPE z`_%WN@>Ad4n4M?vSS?bTpgtObH at -Ttv9Uu+6y0HUc8dquGW<82zlPLrVG zW${&cnmR$AoOoKrmqy(PPj<uF6t7y$4p$(;hM|kil40?6}lVuqrDr$by$AE zQt=0MocD3vC|r6l4PiYxxt$P8lM~w+vBxqtZen`@)+BZkl{+a5C|$1U5jb>x50_si z^$nZU(JAZrtVz2+So6O at a8O`b=Q357U-V7QC^eQs}QJdfAS;v9lEa z#EyDGL_qv}#EQzemJ`2X=X&8ZA^xcy_2K?E9$CixAvgXH1cv;6vFN{Wt at z2cqFgMh z#p2*%@$1Fn$YPN)Fcr zn}v`9;^vh_BikSm(h7+g|n%yZm``*_TU>^1Vxk8C6G+kIh^+f1QXgNLWO>qI zUo}LNsN`>Dqjn>1FT?gF9M^~@*p6e|Xb0*pq}SJkyr<4~U4);j6$+;_+x zZDz3;kOQ7j4--M+=L2#7?TY@(2~B?Euj0}VB$CMA$Axp6wcj$LDKMg`>gMF9dg4pXn~ym{0oZ9%D>PqcVlh3M& zXH{2j4+_t5bkrW`s1`1dZQOmT at w}SoR&gl1Pc;MBcb&_*|4c+kqb2$t^;k%aeJVDS zUskglv^p9N=BUu06Z>(z5o9T4i^jgq1t2Up5&`iEQ2?A!S{w89MO`Ug&&`X# z733*?HL}2)kR?LVzvyDvd(wjdl7HLs;U**4)X#4_4^dGQ9e=mvtZoX?(Ci+iA zuyVLrh<*=ERD0A^7nXxvYVxSscvQs{eqjVYz(4e|ltsu^>`nN76jgZ0^Ub&Oef8zX*%K{+{g%rl_3lYgj1s#SzsIi z%V$T_#1R#}7aD at -A=NmnCJw1^*~AVB4y*BL(N3P{y{IN$6rd1F%8=@WE08>oki%+g ze0o%%KFR=1Uh4ad*BTzP>HA-_qY?}QQU6*|cW69CC)!aD&pTeTqcYDsR at GTiFV8z3 zids=0&pRHrqkf)uJZeV+Jny(<*IVU`pK2aI)dBC2nqsl~FRvE={%UdB)nez>;{L0} zx33o6*NT6Bt+?`9 at w02i8nFemDB>mIe^BO$9+nWcIICurA-{jz at 2;Ktm zki^1f1Qc4IYKcaoPnFoQ5_Qu|?k03m0f9+v1`Q>*NSJa6)#>Zx>zHM7Z_-{ z-A(ABOZB)}X^Xqm*`odMT2Tmiwcy)h{H3sp at WLGOus8zi0R;p4{70)ueO3sc)7fQGHw z7t-*h at EygaIh$MaNICp1upcd9NBaC|=XDz7hKMa17JNAxl4_n-+T;6ZrB}c*&$6|j zJfjtO3ka(nv%q}>tdElhjXxn26F!Hn5wGx^ojR-7r at buSk=%uXr&~z&{{o0hx_XhNtG~-* zx`$BO6B+u3P>uF1#vW&G9%sazA=`^Z_wN*onPPD}Ul8wzV83=~_ek+b!RoUD_gR7Z z>?}hq;rYdHY+993TJ{e*_t3dOB19Q zFhP2l~z;w6G$ z9BU)?`RGFy9nTRRO}OtH!5Lc$TKIEmoD=sHL`PvFhA5EwxUI)#TP; zBYl0O;^c3G!<;D=QO^@mFN5BVk&-#QO%ZC1?@C#eV8T;Q^aP??BKEVyhXkkR<{|DEGyCRFq9{d0R literal 0 HcmV?d00001 -- 1.5.5.1 From xxqonline at hotmail.com Wed Oct 15 15:16:21 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Wed, 15 Oct 2008 23:16:21 +0800 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> Message-ID: Can ovirt talk with the managed host which has redhat5 by using libvirt. When can we get the ovirt 0.94? Thanks, Qiang -------------------------------------------------- From: "Darryl Pierce" Sent: Wednesday, October 15, 2008 7:53 PM To: Cc: Subject: Re: [Ovirt-devel] How do I provision a redhat ISO > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > xxqonline at hotmail.com wrote: >> Hello folks: >> I have to provision a redhat to the managed host. Is it possible? >> Is there doc for me to follow? > > With our upcoming release of oVirt (0.94) ISO provisioning via Cobbler > will be supported. To provision from an ISO file you'll simply: > > 1. copy the ISO file to an NFS exported file system, > 2. create an Image record within the Cobbler server and point it at the > ISO file > 3. within the oVirt server, create a new VM > 4. when setting the OS for this VM you'll see, and select, the Image > created in step 2 above > 5. attach an additional writable file system > 6. boot the VM > > This will start up a virtual machine with that ISO mounted as a CDROM > and boot from it. That will allow you to provision from ISO. > > HTH. :) > > - -- > Darryl L. Pierce : GPG KEYID: 6C4E7F1B > -----BEGIN PGP SIGNATURE----- > Version: GnuPG v1.4.9 (GNU/Linux) > Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org > > iEYEARECAAYFAkj12aAACgkQjaT4DmxOfxtDbQCfYybsehyYl9sZ7Et+l6G6glTR > zlMAniUw4eWW8IFWacrR8ofnAu5GaNvW > =HWWa > -----END PGP SIGNATURE----- > From dpierce at redhat.com Wed Oct 15 16:38:23 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 12:38:23 -0400 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> Message-ID: <48F61C7F.1070404@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 xxqonline at hotmail.com wrote: > Can ovirt talk with the managed host which has redhat5 by using libvirt. Can you tell me what you mean by "the managed node which has redhat5"? Do you mean a node which is built from the installation instructions[1] but which is uses RHEL5 and not Fedora 9 as the foundation? Or do you mean a virtual machine provisioned with RHEL5 as in your previous question? > When can we get the ovirt 0.94? We're working on that now. I'll leave it to someone with a better idea to answer when it'll be available. [1] - http://www.ovirt.org/install-instructions.html - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj2HHoACgkQjaT4DmxOfxusyACg4LW+90FYYIsJIw9fzraxxGKr A7QAnRtcqJB6LDOa/gelaej/QuX5FGvh =jGmW -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 333 bytes Desc: not available URL: From mmorsi at redhat.com Wed Oct 15 17:53:01 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 15 Oct 2008 13:53:01 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <48F60DCC.4000606@redhat.com> References: <48F60DCC.4000606@redhat.com> Message-ID: <48F62DFD.5020409@redhat.com> Darryl Pierce wrote: > http://www.ovirt.org/page/VM_Network_Configuration > > Please take a few minutes to review this list and provide feedback. The > network configuration page is the only part missing now since the > backend support and processing code is out for review. > Hey Darryl, I was looking into this and the only question I had was about the dropdown that should appear when 'enslaved to bonding' is checked. According to your interface requirements and the 'enslave a nic to bonding interface' this should be a single select drop down box which a user can select one bonding to associated with the nic, but this association is stored in the bondings_nics bridge table and the relationship itself is a many to many one. Should this be a multiple-select box, in which a user can select more than one bonding to be associated with the nic? -Mo From dpierce at redhat.com Wed Oct 15 17:59:13 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Wed, 15 Oct 2008 13:59:13 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <48F62DFD.5020409@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F62DFD.5020409@redhat.com> Message-ID: <48F62F71.6070107@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Mohammed Morsi wrote: > Darryl Pierce wrote: >> http://www.ovirt.org/page/VM_Network_Configuration >> >> Please take a few minutes to review this list and provide feedback. The >> network configuration page is the only part missing now since the >> backend support and processing code is out for review. >> > Hey Darryl, > I was looking into this and the only question I had was about the > dropdown that should appear when 'enslaved to bonding' is checked. > According to your interface requirements and the 'enslave a nic to > bonding interface' this should be a single select drop down box which a > user can select one bonding to associated with the nic, but this > association is stored in the bondings_nics bridge table and the > relationship itself is a many to many one. Should this be a > multiple-select box, in which a user can select more than one bonding to > be associated with the nic? No. A NIC can only be enslaved to one bonded interface. The bondings_nics table has a uniqueness constraint for the pair (bonding_id, nic_id). - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj2L2kACgkQjaT4DmxOfxsErwCfco0MEaoX3u87wsaFwWKEn5jh BJAAoL37SOr6/PRtcmRCP2i8w6S2vpaN =/u5g -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Wed Oct 15 20:45:17 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 15 Oct 2008 16:45:17 -0400 Subject: [Ovirt-devel] [PATCH server] This patch hardcodes support for ovirtbr0 bridge. Message-ID: <1224103517-5003-1-git-send-email-dpierce@redhat.com> Currently the node configuration has no explicit way of configuring a bridged interface. This patch provides a hardcoded bridge and associates each nic with it Signed-off-by: Darryl L. Pierce --- src/host-browser/host-browser.rb | 2 +- src/lib/managed_node_configuration.rb | 22 ++++++++++++-- .../functional/managed_node_configuration_test.rb | 30 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 79d34e8..d228a59 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -302,7 +302,7 @@ class HostBrowser end # iterate over any nics left and create new records for them. - boot_type = BootType.find_by_proto('dhcp') + boot_type = BootType.find_by_proto('static') nic_info.collect do |nic| puts "Creating a new nic..." diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb index 4ade235..93f8448 100644 --- a/src/lib/managed_node_configuration.rb +++ b/src/lib/managed_node_configuration.rb @@ -59,10 +59,19 @@ class ManagedNodeConfiguration end end + has_bridge = false host.nics.each do |nic| # only process this nic if it doesn't have a bonding + # TODO remove the hack to force a bridge into the picture if nic.bonding.empty? - process_nic result, nic, macs + process_nic result, nic, macs, nil, false, true + + # TODO remove this when bridges are properly supported + unless has_bridge + macs[nic.mac] = "ovirtbr0" + process_nic result, nic, macs, nil, true, false + has_bridge = true + end end end @@ -74,7 +83,7 @@ class ManagedNodeConfiguration private - def self.process_nic(result, nic, macs, bonding = nil) + def self.process_nic(result, nic, macs, bonding = nil, is_bridge = false, bridged = true) iface_name = macs[nic.mac] if iface_name @@ -92,9 +101,16 @@ class ManagedNodeConfiguration result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" end + + if bridged + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE ovirtbr0" + elsif is_bridge + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/TYPE bridge" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/PEERNTP yes" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DELAY 0" + end end - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE #{nic.bridge}" if nic.bridge result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/ONBOOT yes" end end diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index b5a7ec5..d0d8aa3 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -50,7 +50,15 @@ cat <<\EOF > /var/tmp/node-augtool 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 #{nic.boot_type.proto} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes +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 #{nic.boot_type.proto} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes save EOF HERE @@ -80,6 +88,16 @@ set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes +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 #{nic.boot_type.proto} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_addr} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes save EOF HERE @@ -108,10 +126,22 @@ set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BOOTPROTO #{nic1.boot_type.p set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes +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 #{nic1.boot_type.proto} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_addr} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes 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/BOOTPROTO #{nic2.boot_type.proto} +set /files/etc/sysconfig/network-scripts/ifcfg-eth1/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes save EOF -- 1.5.5.1 From xxqonline at hotmail.com Thu Oct 16 02:27:16 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Thu, 16 Oct 2008 10:27:16 +0800 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> Message-ID: Hello Darryl: I mean a admin ovirt appliance talk with a physical redhat host, that redhat host is pre-installed. We use libvert to talk with the kvm on the redhat to create VM. Is it possible? Thanks, Qiang -------------------------------------------------- From: "Darryl Pierce" Sent: Thursday, October 16, 2008 12:38 AM To: Cc: Subject: Re: [Ovirt-devel] How do I provision a redhat ISO > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > xxqonline at hotmail.com wrote: >> Can ovirt talk with the managed host which has redhat5 by using libvirt. > > Can you tell me what you mean by "the managed node which has redhat5"? > Do you mean a node which is built from the installation instructions[1] > but which is uses RHEL5 and not Fedora 9 as the foundation? Or do you > mean a virtual machine provisioned with RHEL5 as in your previous > question? > >> When can we get the ovirt 0.94? > > We're working on that now. I'll leave it to someone with a better idea > to answer when it'll be available. > > [1] - http://www.ovirt.org/install-instructions.html > > - -- > Darryl L. Pierce : GPG KEYID: 6C4E7F1B > -----BEGIN PGP SIGNATURE----- > Version: GnuPG v1.4.9 (GNU/Linux) > Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org > > iEYEARECAAYFAkj2HHoACgkQjaT4DmxOfxusyACg4LW+90FYYIsJIw9fzraxxGKr > A7QAnRtcqJB6LDOa/gelaej/QuX5FGvh > =jGmW > -----END PGP SIGNATURE----- > From xxqonline at hotmail.com Thu Oct 16 02:37:51 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Thu, 16 Oct 2008 10:37:51 +0800 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> Message-ID: Yes, a node which is built from the installation instructions[1] > but which is uses RHEL5 and not Fedora 9 as the foundation. -------------------------------------------------- From: "Darryl Pierce" Sent: Thursday, October 16, 2008 12:38 AM To: Cc: Subject: Re: [Ovirt-devel] How do I provision a redhat ISO > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > xxqonline at hotmail.com wrote: >> Can ovirt talk with the managed host which has redhat5 by using libvirt. > > Can you tell me what you mean by "the managed node which has redhat5"? > Do you mean a node which is built from the installation instructions[1] > but which is uses RHEL5 and not Fedora 9 as the foundation? Or do you > mean a virtual machine provisioned with RHEL5 as in your previous > question? > >> When can we get the ovirt 0.94? > > We're working on that now. I'll leave it to someone with a better idea > to answer when it'll be available. > > [1] - http://www.ovirt.org/install-instructions.html > > - -- > Darryl L. Pierce : GPG KEYID: 6C4E7F1B > -----BEGIN PGP SIGNATURE----- > Version: GnuPG v1.4.9 (GNU/Linux) > Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org > > iEYEARECAAYFAkj2HHoACgkQjaT4DmxOfxusyACg4LW+90FYYIsJIw9fzraxxGKr > A7QAnRtcqJB6LDOa/gelaej/QuX5FGvh > =jGmW > -----END PGP SIGNATURE----- > From pmyers at redhat.com Thu Oct 16 02:47:07 2008 From: pmyers at redhat.com (Perry Myers) Date: Wed, 15 Oct 2008 22:47:07 -0400 Subject: [Ovirt-devel] [PATCH appliance] create real ovirtbr0 bridge for -e Message-ID: <1224125227-14209-1-git-send-email-pmyers@redhat.com> From: Alan Pevec NOTE: distro specific networking scripts are used Signed-off-by: Alan Pevec Signed-off-by: Perry Myers --- create-ovirt-appliance | 119 +++++++++++++++++++++++++++------------------- ovirt-appliance.spec.in | 1 + 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/create-ovirt-appliance b/create-ovirt-appliance index 88d0188..9454377 100755 --- a/create-ovirt-appliance +++ b/create-ovirt-appliance @@ -13,6 +13,7 @@ IMGDIR_DEFAULT=/var/lib/libvirt/images NAME_DEFAULT=ovirt-appliance NET_SCRIPTS=/etc/sysconfig/network-scripts BRIDGENAME=ovirtbr0 +NET_TYPE=network NODE_DISK_SIZE=128M NODE_DISK_FMT=raw @@ -84,9 +85,9 @@ gen_fake_managed_node() { - + - + @@ -130,8 +131,8 @@ gen_app() { - - + + @@ -147,6 +148,18 @@ gen_app() { EOF } +net_debug() { + # Some output for debugging problems + echo "Currently active bridges:" + echo "-------------------------" + brctl show + echo + echo "Currently defined libvirt networks:" + echo "-----------------------------------" + virsh net-list --all + echo +} + # first, check to see we are root if [ $( id -u ) -ne 0 ]; then die "Must run as root" @@ -193,33 +206,33 @@ chkconfig libvirtd on sed -i "/# ovirtbr$/d" /etc/rc.d/rc.local } > /dev/null 2>&1 -# Some output for debugging problems -brctl show -virsh net-list +net_debug # If we're bridging to a physical network, run some checks to make sure the # choice of physical eth device is sane if [ -n "$bridge" ]; then + NET_TYPE=bridge # Check to see if the physical device is present ifconfig $bridge > /dev/null 2>&1 ; bridge_dev_present=$? test $bridge_dev_present != 0 \ && die "$bridge device not present, aborting!" - # Check to make sure that the system is not already using the interface - test -f $NET_SCRIPTS/ifcfg-$bridge \ - && die "$bridge defined in $NET_SCRIPTS, aborting!" - # Check to see if the eth device is already tied to a non oVirt bridge attached_bridge=$(brctl show \ | awk -v BRIDGE=$bridge '$4~BRIDGE {print $1}') test -n "$attached_bridge" -a "$attached_bridge" != "$BRIDGENAME" \ && die "$bridge already attached to other bridge $attached_bridge" - # Check to see if the eth device does not have an active inet address - ip address show dev $bridge \ - | grep "inet.*$bridge" > /dev/null 2>&1 ; bridge_dev_active=$? - test $bridge_dev_active == 0 \ - && die "$bridge device active with ip address, aborting!" + # Check to make sure that the system is not already using the interface + if test -f $NET_SCRIPTS/ifcfg-$bridge ; then + echo "$NET_SCRIPTS/ifcfg-$bridge is present, are you sure you wish to" + echo "overwrite this file? [y/N]? " + read yesno + + if [ "$yesno" != "y" -a "$yesno" != "Y" ]; then + exit 2 + fi + fi fi mkdir -p $imgdir @@ -238,16 +251,6 @@ for i in `seq 3 5` ; do rm $TMPXML done -virsh net-dumpxml $BRIDGENAME >& /dev/null -RETVAL=$? -if [ $( brctl show | grep -c $BRIDGENAME ) -ne 0 -a $RETVAL -ne 0 ]; then - # in this case, the bridge exists, but isn't managed by libvirt - # abort, since the user will have to clean up themselves - echo "Bridge $BRIDGENAME already exists. Please make sure you" - echo "unconfigure $BRIDGENAME, and then try the command again" - exit 1 -fi - # Remove old bridge device if it exists sed -i "/# $BRIDGENAME/d" /etc/rc.d/rc.local old_bridge=$(brctl show \ @@ -256,36 +259,54 @@ if [ -n "$old_bridge" ]; then echo "Removing old bridge $old_bridge" ifconfig $old_bridge down brctl delif $BRIDGENAME $old_bridge + rm -f $NET_SCRIPTS/ifcfg-$old_bridge fi -virsh net-destroy $BRIDGENAME > /dev/null 2>&1 -virsh net-undefine $BRIDGENAME > /dev/null 2>&1 -TMPXML=$(mktemp) || exit 1 -gen_bridge $BRIDGENAME > $TMPXML -virsh net-define $TMPXML -rm $TMPXML -virsh net-start $BRIDGENAME -virsh net-autostart $BRIDGENAME +{ +echo "Removing $BRIDGENAME" +virsh net-destroy $BRIDGENAME +virsh net-undefine $BRIDGENAME +ifconfig $BRIDGENAME down +brctl delbr $BRIDGENAME +rm -fv $NET_SCRIPTS/ifcfg-$BRIDGENAME +echo "Done Removing $BRIDGENAME" +} 2> /dev/null if [ -n "$bridge" ]; then - # FIXME: unfortunately, these two can't be done by libvirt at the - # moment, so we do them by hand here and persist the config by - # by adding to rc.local - echo "Adding new bridge $bridge" - TMPBRCTL=$(mktemp) || exit 1 - cat > $TMPBRCTL << EOF -brctl setfd $BRIDGENAME 0 # $BRIDGENAME -brctl addif $BRIDGENAME $bridge # $BRIDGENAME -ifconfig $bridge up # $BRIDGENAME + # real external bridge, use distro networking scripts + ifconfig $bridge down 2> /dev/null + TMPAUG=$(mktemp) || exit 1 + cat > $TMPAUG <> /etc/rc.d/rc.local - - $TMPBRCTL - rm $TMPBRCTL + augtool < $TMPAUG + ifup $BRIDGENAME + ifup $bridge +else + # internal bridge only, use libvirt networking + TMPXML=$(mktemp) || exit 1 + gen_bridge $BRIDGENAME > $TMPXML + virsh net-define $TMPXML + rm $TMPXML + virsh net-start $BRIDGENAME + virsh net-autostart $BRIDGENAME fi +net_debug + # Cleanup to handle older version of script that used these domain names { virsh destroy developer diff --git a/ovirt-appliance.spec.in b/ovirt-appliance.spec.in index d96e7bf..91cc562 100644 --- a/ovirt-appliance.spec.in +++ b/ovirt-appliance.spec.in @@ -27,6 +27,7 @@ BuildRequires: wget Requires: libvirt >= 0.4.4-2ovirt2 Requires: kvm >= 72-3ovirt3 Requires: /usr/bin/qemu-img +Requires: augtool %define app_root %{_datadir}/%{name} -- 1.5.5.1 From pmyers at redhat.com Thu Oct 16 02:53:32 2008 From: pmyers at redhat.com (Perry Myers) Date: Wed, 15 Oct 2008 22:53:32 -0400 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> Message-ID: <48F6ACAC.80809@redhat.com> xxqonline at hotmail.com wrote: > Hello Darryl: > I mean a admin ovirt appliance talk with a physical redhat host, > that redhat host is pre-installed. > We use libvert to talk with the kvm on the redhat to create VM. Is it > possible? Please see this earlier response: https://www.redhat.com/archives/ovirt-devel/2008-October/msg00235.html The short answer is that presently you cannot manage a RHEL5 host with the oVirt Server. We will eventually support managing RHEL5 hosts via the oVirt Server but there are a few development steps that need to happen first. The above email outlines the steps that need to happen. Thanks, Perry From xxqonline at hotmail.com Thu Oct 16 03:34:14 2008 From: xxqonline at hotmail.com (xxqonline at hotmail.com) Date: Thu, 16 Oct 2008 11:34:14 +0800 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> <48F6ACAC.80809@redhat.com> References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> <48F6ACAC.80809@redhat.com> Message-ID: Thanks, Perry: I have downloaded the src package, it has a lot of the rpm package. Which one shall I read firstly? Regards, Qiang -------------------------------------------------- From: "Perry Myers" Sent: Thursday, October 16, 2008 10:53 AM To: Cc: "Darryl Pierce" ; Subject: Re: [Ovirt-devel] How do I provision a redhat ISO > xxqonline at hotmail.com wrote: >> Hello Darryl: >> I mean a admin ovirt appliance talk with a physical redhat host, >> that redhat host is pre-installed. >> We use libvert to talk with the kvm on the redhat to create VM. Is it >> possible? > > Please see this earlier response: > https://www.redhat.com/archives/ovirt-devel/2008-October/msg00235.html > > The short answer is that presently you cannot manage a RHEL5 host with the > oVirt Server. We will eventually support managing RHEL5 hosts via the > oVirt Server but there are a few development steps that need to happen > first. The above email outlines the steps that need to happen. > > Thanks, > > Perry > From pmyers at redhat.com Thu Oct 16 03:38:12 2008 From: pmyers at redhat.com (Perry Myers) Date: Wed, 15 Oct 2008 23:38:12 -0400 Subject: [Ovirt-devel] How do I provision a redhat ISO In-Reply-To: References: <1223995747-12847-1-git-send-email-pmyers@redhat.com> <48F5D9A3.9040507@redhat.com> <48F61C7F.1070404@redhat.com> <48F6ACAC.80809@redhat.com> Message-ID: <48F6B724.6080403@redhat.com> xxqonline at hotmail.com wrote: > Thanks, Perry: > I have downloaded the src package, it has a lot of the rpm package. > Which one shall I read firstly? It would be better to work from the source git repositories instead of from the SRPMS. Instructions for accessing the git repositories is located at: http://ovirt.org/build-instructions.html Once you've followed these instructions, the places to look for adding in support for Xen dom0 hosts would be in the ovirt-server and ovirt-node repositories. Perry From pmyers at redhat.com Thu Oct 16 04:25:44 2008 From: pmyers at redhat.com (Perry Myers) Date: Thu, 16 Oct 2008 00:25:44 -0400 Subject: [Ovirt-devel] [PATCH appliance] create real ovirtbr0 bridge for -e Message-ID: <1224131144-15539-1-git-send-email-pmyers@redhat.com> From: Alan Pevec NOTE: distro specific networking scripts are used Signed-off-by: Alan Pevec Signed-off-by: Perry Myers --- create-ovirt-appliance | 119 +++++++++++++++++++++++++++------------------- ovirt-appliance.spec.in | 1 + 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/create-ovirt-appliance b/create-ovirt-appliance index 88d0188..9454377 100755 --- a/create-ovirt-appliance +++ b/create-ovirt-appliance @@ -13,6 +13,7 @@ IMGDIR_DEFAULT=/var/lib/libvirt/images NAME_DEFAULT=ovirt-appliance NET_SCRIPTS=/etc/sysconfig/network-scripts BRIDGENAME=ovirtbr0 +NET_TYPE=network NODE_DISK_SIZE=128M NODE_DISK_FMT=raw @@ -84,9 +85,9 @@ gen_fake_managed_node() { - + - + @@ -130,8 +131,8 @@ gen_app() { - - + + @@ -147,6 +148,18 @@ gen_app() { EOF } +net_debug() { + # Some output for debugging problems + echo "Currently active bridges:" + echo "-------------------------" + brctl show + echo + echo "Currently defined libvirt networks:" + echo "-----------------------------------" + virsh net-list --all + echo +} + # first, check to see we are root if [ $( id -u ) -ne 0 ]; then die "Must run as root" @@ -193,33 +206,33 @@ chkconfig libvirtd on sed -i "/# ovirtbr$/d" /etc/rc.d/rc.local } > /dev/null 2>&1 -# Some output for debugging problems -brctl show -virsh net-list +net_debug # If we're bridging to a physical network, run some checks to make sure the # choice of physical eth device is sane if [ -n "$bridge" ]; then + NET_TYPE=bridge # Check to see if the physical device is present ifconfig $bridge > /dev/null 2>&1 ; bridge_dev_present=$? test $bridge_dev_present != 0 \ && die "$bridge device not present, aborting!" - # Check to make sure that the system is not already using the interface - test -f $NET_SCRIPTS/ifcfg-$bridge \ - && die "$bridge defined in $NET_SCRIPTS, aborting!" - # Check to see if the eth device is already tied to a non oVirt bridge attached_bridge=$(brctl show \ | awk -v BRIDGE=$bridge '$4~BRIDGE {print $1}') test -n "$attached_bridge" -a "$attached_bridge" != "$BRIDGENAME" \ && die "$bridge already attached to other bridge $attached_bridge" - # Check to see if the eth device does not have an active inet address - ip address show dev $bridge \ - | grep "inet.*$bridge" > /dev/null 2>&1 ; bridge_dev_active=$? - test $bridge_dev_active == 0 \ - && die "$bridge device active with ip address, aborting!" + # Check to make sure that the system is not already using the interface + if test -f $NET_SCRIPTS/ifcfg-$bridge ; then + echo "$NET_SCRIPTS/ifcfg-$bridge is present, are you sure you wish to" + echo "overwrite this file? [y/N]? " + read yesno + + if [ "$yesno" != "y" -a "$yesno" != "Y" ]; then + exit 2 + fi + fi fi mkdir -p $imgdir @@ -238,16 +251,6 @@ for i in `seq 3 5` ; do rm $TMPXML done -virsh net-dumpxml $BRIDGENAME >& /dev/null -RETVAL=$? -if [ $( brctl show | grep -c $BRIDGENAME ) -ne 0 -a $RETVAL -ne 0 ]; then - # in this case, the bridge exists, but isn't managed by libvirt - # abort, since the user will have to clean up themselves - echo "Bridge $BRIDGENAME already exists. Please make sure you" - echo "unconfigure $BRIDGENAME, and then try the command again" - exit 1 -fi - # Remove old bridge device if it exists sed -i "/# $BRIDGENAME/d" /etc/rc.d/rc.local old_bridge=$(brctl show \ @@ -256,36 +259,54 @@ if [ -n "$old_bridge" ]; then echo "Removing old bridge $old_bridge" ifconfig $old_bridge down brctl delif $BRIDGENAME $old_bridge + rm -f $NET_SCRIPTS/ifcfg-$old_bridge fi -virsh net-destroy $BRIDGENAME > /dev/null 2>&1 -virsh net-undefine $BRIDGENAME > /dev/null 2>&1 -TMPXML=$(mktemp) || exit 1 -gen_bridge $BRIDGENAME > $TMPXML -virsh net-define $TMPXML -rm $TMPXML -virsh net-start $BRIDGENAME -virsh net-autostart $BRIDGENAME +{ +echo "Removing $BRIDGENAME" +virsh net-destroy $BRIDGENAME +virsh net-undefine $BRIDGENAME +ifconfig $BRIDGENAME down +brctl delbr $BRIDGENAME +rm -fv $NET_SCRIPTS/ifcfg-$BRIDGENAME +echo "Done Removing $BRIDGENAME" +} 2> /dev/null if [ -n "$bridge" ]; then - # FIXME: unfortunately, these two can't be done by libvirt at the - # moment, so we do them by hand here and persist the config by - # by adding to rc.local - echo "Adding new bridge $bridge" - TMPBRCTL=$(mktemp) || exit 1 - cat > $TMPBRCTL << EOF -brctl setfd $BRIDGENAME 0 # $BRIDGENAME -brctl addif $BRIDGENAME $bridge # $BRIDGENAME -ifconfig $bridge up # $BRIDGENAME + # real external bridge, use distro networking scripts + ifconfig $bridge down 2> /dev/null + TMPAUG=$(mktemp) || exit 1 + cat > $TMPAUG <> /etc/rc.d/rc.local - - $TMPBRCTL - rm $TMPBRCTL + augtool < $TMPAUG + ifup $BRIDGENAME + ifup $bridge +else + # internal bridge only, use libvirt networking + TMPXML=$(mktemp) || exit 1 + gen_bridge $BRIDGENAME > $TMPXML + virsh net-define $TMPXML + rm $TMPXML + virsh net-start $BRIDGENAME + virsh net-autostart $BRIDGENAME fi +net_debug + # Cleanup to handle older version of script that used these domain names { virsh destroy developer diff --git a/ovirt-appliance.spec.in b/ovirt-appliance.spec.in index d96e7bf..f42982d 100644 --- a/ovirt-appliance.spec.in +++ b/ovirt-appliance.spec.in @@ -27,6 +27,7 @@ BuildRequires: wget Requires: libvirt >= 0.4.4-2ovirt2 Requires: kvm >= 72-3ovirt3 Requires: /usr/bin/qemu-img +Requires: augeas %define app_root %{_datadir}/%{name} -- 1.5.5.1 From clalance at redhat.com Thu Oct 16 08:11:24 2008 From: clalance at redhat.com (Chris Lalancette) Date: Thu, 16 Oct 2008 10:11:24 +0200 Subject: [Ovirt-devel] [PATCH] initial model work for lvm storage (revised) In-Reply-To: <1224080493-29248-1-git-send-email-sseago@redhat.com> References: <1224080493-29248-1-git-send-email-sseago@redhat.com> Message-ID: <48F6F72C.8010301@redhat.com> Scott Seago wrote: > This includes the model classes w/ migrations for the lvm storage pools and > volumes, and some changes required to support Storage Volume tasks. This > patch does not include all of the necessary model API methods to support lvm, > but it's a starting point -- some changes to clalance's taskomatic bits will > be required to support this, and we will need additional model enhancements > when the UI work is done, and when the taskomatic back end is finalized. > > revised to fix a couple controller bugs and to increment the migration > version # Well, this version is better, but still not working. I downloaded this patch, applied it with git-am (thanks Jim), then rebuilt an RPM with it. Then I did "rpm -Uvh ovirt-server", followed by "cd /usr/share/ovirt-server ; rake db:migrate". Then I shut down all of the services (including mongrel-rails), and restarted them. Now, I was able to successfully add the iSCSI storage pool, but taskomatic barfed when trying to do the scanning. In particular, the error I got in the database was: Couldn't find StoragePool without an ID And a more complete stack trace from /var/log/ovirt-server/taskomatic.log is: refresh_pool Task action processing failed: ActiveRecord::RecordNotFound: Couldn't find StoragePool without an ID /usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:1364:in `find_from_ids' /usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:541:in `find' /usr/share/ovirt-server/task-omatic/./task_storage.rb:26:in `refresh_pool' /usr/share/ovirt-server/task-omatic/taskomatic.rb:106 /usr/share/ovirt-server/task-omatic/taskomatic.rb:88:in `each' /usr/share/ovirt-server/task-omatic/taskomatic.rb:88 /usr/share/ovirt-server/task-omatic/taskomatic.rb:68:in `loop' /usr/share/ovirt-server/task-omatic/taskomatic.rb:68 Any ideas? -- Chris Lalancette From apevec at redhat.com Thu Oct 16 10:33:00 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 16 Oct 2008 12:33:00 +0200 Subject: [Ovirt-devel] Re: [PATCH server] This patch hardcodes support for ovirtbr0 bridge. In-Reply-To: <1224101338-2773-1-git-send-email-dpierce@redhat.com> References: <1224101338-2773-1-git-send-email-dpierce@redhat.com> Message-ID: <48F7185C.6000700@redhat.com> Darryl L. Pierce wrote: > --- a/src/host-browser/host-browser.rb > +++ b/src/host-browser/host-browser.rb > # iterate over any nics left and create new records for them. > - boot_type = BootType.find_by_proto('dhcp') > + boot_type = BootType.find_by_proto('static') I'll revert that, since this breaks default network config: DHCP on ovirtbr0 and eth0 bridged to it. Any objections? From apevec at redhat.com Thu Oct 16 11:03:23 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 16 Oct 2008 13:03:23 +0200 Subject: [Ovirt-devel] [PATCH] revert part of 98b63b405a3803295e517690c6572ca66b24e052 In-Reply-To: <48F7185C.6000700@redhat.com> References: <48F7185C.6000700@redhat.com> Message-ID: <1224155003-28928-1-git-send-email-apevec@redhat.com> restores default Node network config: DHCP on ovirtbr0 and eth0 bridged to it Signed-off-by: Alan Pevec --- src/host-browser/host-browser.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index d228a59..79d34e8 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -302,7 +302,7 @@ class HostBrowser end # iterate over any nics left and create new records for them. - boot_type = BootType.find_by_proto('static') + boot_type = BootType.find_by_proto('dhcp') nic_info.collect do |nic| puts "Creating a new nic..." -- 1.5.5.1 From dpierce at redhat.com Thu Oct 16 11:47:55 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 16 Oct 2008 07:47:55 -0400 Subject: [Ovirt-devel] [PATCH] revert part of 98b63b405a3803295e517690c6572ca66b24e052 In-Reply-To: <1224155003-28928-1-git-send-email-apevec@redhat.com> References: <48F7185C.6000700@redhat.com> <1224155003-28928-1-git-send-email-apevec@redhat.com> Message-ID: <48F729EB.10300@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Alan Pevec wrote: > restores default Node network config: DHCP on ovirtbr0 and eth0 bridged to it > > Signed-off-by: Alan Pevec > --- > src/host-browser/host-browser.rb | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb > index d228a59..79d34e8 100755 > --- a/src/host-browser/host-browser.rb > +++ b/src/host-browser/host-browser.rb > @@ -302,7 +302,7 @@ class HostBrowser > end > > # iterate over any nics left and create new records for them. > - boot_type = BootType.find_by_proto('static') > + boot_type = BootType.find_by_proto('dhcp') > > nic_info.collect do |nic| > puts "Creating a new nic..." ACK. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj3KeYACgkQjaT4DmxOfxs1PQCg6LfUjhnC0biqQI8gqDbDbLGE DeYAoLHP6C9OrT3MvOyIHMq7L2bCcPpj =LoAK -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 16 14:58:02 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 16 Oct 2008 10:58:02 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <48F60DCC.4000606@redhat.com> References: <48F60DCC.4000606@redhat.com> Message-ID: <48F7567A.40107@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Darryl Pierce wrote: > http://www.ovirt.org/page/VM_Network_Configuration > > Please take a few minutes to review this list and provide feedback. The > network configuration page is the only part missing now since the > backend support and processing code is out for review. I've posted a screenshot for what the interface could look like. It captures all of the needed elements for configuring network interfaces and also bonded interfaces. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj3VnUACgkQjaT4DmxOfxuicACg79uAaYCy6qO4Yu2/7+R56Op0 tBgAn3+qM213y4LDkaNkhGuk77bCFM89 =rhDd -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From pmyers at redhat.com Thu Oct 16 15:01:58 2008 From: pmyers at redhat.com (Perry Myers) Date: Thu, 16 Oct 2008 11:01:58 -0400 Subject: [Ovirt-devel] [PATCH node] Fix install script to start ntp and iptables in case they are not already Message-ID: <1224169318-20108-1-git-send-email-pmyers@redhat.com> time sync is necessary or kerberos won't work correctly iptables needs to be started so that the lokkit -t ovirtbr0 command is persisted across reboots Signed-off-by: Perry Myers --- ovirt-listen-awake/ovirt-install-node | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diff --git a/ovirt-listen-awake/ovirt-install-node b/ovirt-listen-awake/ovirt-install-node index 95ba4de..2ca4cf7 100644 --- a/ovirt-listen-awake/ovirt-install-node +++ b/ovirt-listen-awake/ovirt-install-node @@ -128,6 +128,9 @@ elif [ "$1" = "stateful" ]; then chkconfig ovirt-listen-awake on chkconfig collectd on chkconfig libvirt-qpid on + chkconfig iptables on + chkconfig ntpdate on + chkconfig ntpd on backup_file /etc/sysconfig/libvirtd backup_file /etc/libvirt/qemu.conf @@ -144,6 +147,8 @@ elif [ "$1" = "stateful" ]; then # 49152-49216:tcp (libvirt migration) lokkit -t ovirtbr0 + service iptables restart + # Check if any domains are active before restarting libvirtd, since it will # kill them. Header information from virsh list is 2 lines, and 1 line for # footer. So > 3 lines means domains are running @@ -159,6 +164,7 @@ elif [ "$1" = "stateful" ]; then service collectd restart service ovirt-listen-awake restart service libvirt-qpid restart + service ntpd restart else usage exit 1 -- 1.5.5.1 From berrange at redhat.com Thu Oct 16 15:16:49 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 16 Oct 2008 16:16:49 +0100 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <48F7567A.40107@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> Message-ID: <20081016151649.GD1821@redhat.com> On Thu, Oct 16, 2008 at 10:58:02AM -0400, Darryl Pierce wrote: > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > Darryl Pierce wrote: > > http://www.ovirt.org/page/VM_Network_Configuration > > > > Please take a few minutes to review this list and provide feedback. The > > network configuration page is the only part missing now since the > > backend support and processing code is out for review. > > I've posted a screenshot for what the interface could look like. It > captures all of the needed elements for configuring network interfaces > and also bonded interfaces. It feels like there's a bit of disconnect here between the concept your trying to present & the way you're making users configure it. eg, you're talking in terms of defining networks for virtual machines, but making the user work in terms of individual interfaces. I particularly don't like the duplicated forms for IP / DHCP config, and the way you end up picking interfaces to go with your bonding device, or the fact that you're asking user's to name it. An approach that maps more closely to the conceptual model would be to have a single form, with three parts. - First define the IP address configuration. - Select one or more physical devices to associate with the network. - Optionally specify bonding mode - Optionally specify a VLAN number If they pick only one physical device, then simply put that straight into a bridge. If they pick two or more physical devices, then ask whether they want 'bandwidth aggregation' or 'failover' mode and automatically put the devices into a bond device. No need to tell them your creating bond devices - that's not what they care about - only care about whether the network being define is aggregating bandwidth or providing failover. Use of bonding is a mere implementation detail. Automatically create the name for the bond device starting with bond0, and incrementing, then put this bond in the bridge. If they enter a VLAN, then instead of putting the bond device / physical device in the bridge, then configure a VLAN device ontop of them, and put the VLAN device in the bridge. Again you're not asking them for naming of the VLAN device - just whether there's a VLAN number they want to use Note that whatever they choose for physical device, bonding, vlan configure has no impact on the IP address configuration part - IP details are logically associated with the network as a whole, only being assigned to physical device behind the scenes. Or to put it anther way - there's no need to ask the user whether they want to put the IP address on the bond device vs the physical device - that choice is implicitly driven from the configuration they choose for devices. 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 Oct 16 15:31:11 2008 From: apevec at redhat.com (Alan Pevec) Date: Thu, 16 Oct 2008 17:31:11 +0200 Subject: [Ovirt-devel] [PATCH] masquerade all protocols Message-ID: <1224171071-11884-1-git-send-email-apevec@redhat.com> not just default tcp --- appliances/ovirt/ovirt.pp.in | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/appliances/ovirt/ovirt.pp.in b/appliances/ovirt/ovirt.pp.in index da98f2d..eb0776d 100644 --- a/appliances/ovirt/ovirt.pp.in +++ b/appliances/ovirt/ovirt.pp.in @@ -100,7 +100,8 @@ firewall_rule {"ovirt_nat": table => "nat", chain => "POSTROUTING", out_interface => "eth0", - action => "MASQUERADE" + action => "MASQUERADE", + protocol => '' } firewall_rule {"ssh": destination_port => '22'} -- 1.5.5.1 From dpierce at redhat.com Thu Oct 16 16:00:26 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 16 Oct 2008 12:00:26 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <20081016151649.GD1821@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> <20081016151649.GD1821@redhat.com> Message-ID: <48F7651A.7010507@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > It feels like there's a bit of disconnect here between the concept > your trying to present & the way you're making users configure it. > > eg, you're talking in terms of defining networks for virtual machines, > but making the user work in terms of individual interfaces. I particularly > don't like the duplicated forms for IP / DHCP config, and the way you > end up picking interfaces to go with your bonding device, or the fact > that you're asking user's to name it. > > An approach that maps more closely to the conceptual model would be > to have a single form, with three parts. > > - First define the IP address configuration. > - Select one or more physical devices to associate > with the network. > - Optionally specify bonding mode > - Optionally specify a VLAN number > > If they pick only one physical device, then simply put that straight > into a bridge. > > If they pick two or more physical devices, then ask whether they want > 'bandwidth aggregation' or 'failover' mode and automatically put the > devices into a bond device. No need to tell them your creating bond > devices - that's not what they care about - only care about whether > the network being define is aggregating bandwidth or providing > failover. Use of bonding is a mere implementation detail. Automatically > create the name for the bond device starting with bond0, and incrementing, > then put this bond in the bridge. > > If they enter a VLAN, then instead of putting the bond device / physical > device in the bridge, then configure a VLAN device ontop of them, and > put the VLAN device in the bridge. Again you're not asking them for > naming of the VLAN device - just whether there's a VLAN number they > want to use > > Note that whatever they choose for physical device, bonding, vlan > configure has no impact on the IP address configuration part - IP > details are logically associated with the network as a whole, only > being assigned to physical device behind the scenes. Or to put it > anther way - there's no need to ask the user whether they want to > put the IP address on the bond device vs the physical device - that > choice is implicitly driven from the configuration they choose for > devices. Thank you for the feedback. I've redone the mockup using the input you've provided. Can you take a look and tell me if this is closer to what you're thinking? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj3ZRUACgkQjaT4DmxOfxtKKQCg8ZDWPc9nfekxzhBMVv87aE0D msgAniIcli+p5olGkh0J7Nzkkq91oFIU =3NT0 -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From berrange at redhat.com Thu Oct 16 16:10:43 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 16 Oct 2008 17:10:43 +0100 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <48F7651A.7010507@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> <20081016151649.GD1821@redhat.com> <48F7651A.7010507@redhat.com> Message-ID: <20081016161043.GI1821@redhat.com> On Thu, Oct 16, 2008 at 12:00:26PM -0400, Darryl Pierce wrote: > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > Daniel P. Berrange wrote: > > It feels like there's a bit of disconnect here between the concept > > your trying to present & the way you're making users configure it. > > > > eg, you're talking in terms of defining networks for virtual machines, > > but making the user work in terms of individual interfaces. I particularly > > don't like the duplicated forms for IP / DHCP config, and the way you > > end up picking interfaces to go with your bonding device, or the fact > > that you're asking user's to name it. > > > > An approach that maps more closely to the conceptual model would be > > to have a single form, with three parts. > > > > - First define the IP address configuration. > > - Select one or more physical devices to associate > > with the network. > > - Optionally specify bonding mode > > - Optionally specify a VLAN number > > Thank you for the feedback. I've redone the mockup using the input > you've provided. Can you take a look and tell me if this is closer to > what you're thinking? Yes, this does more closely align, though I see this approach actualy introduces a slightly different problem - the VLANs don't actually work very well in this context. If you want to create lots of VLANs, its forcing you to re-specify the pair of bonded devices each time which isn't very nice. I can't think of a good answer for this yet Also, your heading 'For network device bond0' isn't applicable - we're trying to model this as configuring a network,rather than configuring a network device. Which reminds me that we'd want to have a way to provide a name for the network eg 'Super secure guest network', 'web server network', or some such. We also want to be able to indicate whether the network is to be used for guests, storage, or management 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 dpierce at redhat.com Thu Oct 16 16:45:02 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 16 Oct 2008 12:45:02 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <20081016161043.GI1821@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> <20081016151649.GD1821@redhat.com> <48F7651A.7010507@redhat.com> <20081016161043.GI1821@redhat.com> Message-ID: <48F76F8E.7080906@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > Yes, this does more closely align, though I see this approach actualy > introduces a slightly different problem - the VLANs don't actually > work very well in this context. If you want to create lots of VLANs, > its forcing you to re-specify the pair of bonded devices each time > which isn't very nice. I can't think of a good answer for this yet Another thing is if we're going to have multiple networks defined on a node, we'll need a dropdown for that as well; i.e., select an existing network to edit. Regarding the vlans, I'm not experienced enough with them to come up with a good design. I'll do some research, but if you can point me to a site that can give a great high level explanation, I can try using that to tweak the design. > Also, your heading 'For network device bond0' isn't applicable - we're > trying to model this as configuring a network,rather than configuring > a network device. Which reminds me that we'd want to have a way to > provide a name for the network eg 'Super secure guest network', > 'web server network', or some such. We also want to be able to indicate > whether the network is to be used for guests, storage, or management I've dropped that static text and replaced it with a text field for entering a description. That will get used in the dropdown list. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj3b4oACgkQjaT4DmxOfxt5bACeMI5/K8cFiEQmplUpKJGp48wK HSgAn0EN1zhuFOtNFAAJOYB7+lD6aHaC =DnfQ -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From sseago at redhat.com Thu Oct 16 19:14:30 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 16 Oct 2008 19:14:30 +0000 Subject: [Ovirt-devel] [PATCH] initial model work for lvm storage (revised2) Message-ID: <1224184470-7912-1-git-send-email-sseago@redhat.com> This includes the model classes w/ migrations for the lvm storage pools and volumes, and some changes required to support Storage Volume tasks. This patch does not include all of the necessary model API methods to support lvm, but it's a starting point -- some changes to clalance's taskomatic bits will be required to support this, and we will need additional model enhancements when the UI work is done, and when the taskomatic back end is finalized. revised to fix a couple controller bugs and to increment the migration version # and to fix some additional model problems in taskomatic and host-status Signed-off-by: Scott Seago --- src/app/controllers/host_controller.rb | 8 +- src/app/controllers/storage_controller.rb | 35 ++----- src/app/controllers/vm_controller.rb | 34 +++--- src/app/models/host.rb | 2 +- src/app/models/host_task.rb | 11 ++- src/app/models/iscsi_storage_pool.rb | 2 +- .../models/{host_task.rb => lvm_storage_pool.rb} | 30 ++++-- .../{nfs_storage_pool.rb => lvm_storage_volume.rb} | 12 +-- src/app/models/nfs_storage_pool.rb | 2 +- src/app/models/storage_pool.rb | 12 ++- src/app/models/storage_task.rb | 11 ++- src/app/models/storage_volume.rb | 15 +++- .../{host_task.rb => storage_volume_task.rb} | 22 +++- src/app/models/task.rb | 15 ++- src/app/models/vm.rb | 12 +- src/app/models/vm_task.rb | 15 ++- src/db/migrate/025_add_lvm_storage.rb | 106 ++++++++++++++++++++ src/dutils/active_record_env.rb | 2 + src/host-status/host-status.rb | 5 +- src/task-omatic/task_host.rb | 2 +- src/task-omatic/task_storage.rb | 4 +- src/task-omatic/task_vm.rb | 48 +++------ src/test/fixtures/tasks.yml | 32 ++++-- 23 files changed, 296 insertions(+), 141 deletions(-) copy src/app/models/{host_task.rb => lvm_storage_pool.rb} (62%) copy src/app/models/{nfs_storage_pool.rb => lvm_storage_volume.rb} (82%) copy src/app/models/{host_task.rb => storage_volume_task.rb} (69%) create mode 100644 src/db/migrate/025_add_lvm_storage.rb diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb index 417d11f..a40d297 100644 --- a/src/app/controllers/host_controller.rb +++ b/src/app/controllers/host_controller.rb @@ -139,10 +139,10 @@ class HostController < ApplicationController def clear_vms begin Host.transaction do - task = HostTask.new({ :user => get_login_user, - :host_id => @host.id, - :action => HostTask::ACTION_CLEAR_VMS, - :state => Task::STATE_QUEUED}) + task = HostTask.new({ :user => get_login_user, + :task_target => @host, + :action => HostTask::ACTION_CLEAR_VMS, + :state => Task::STATE_QUEUED}) task.save! @host.is_disabled = true @host.save! diff --git a/src/app/controllers/storage_controller.rb b/src/app/controllers/storage_controller.rb index bbd7840..fe524f3 100644 --- a/src/app/controllers/storage_controller.rb +++ b/src/app/controllers/storage_controller.rb @@ -111,10 +111,10 @@ class StorageController < ApplicationController end def insert_refresh_task - @task = StorageTask.new({ :user => @user, - :storage_pool_id => @storage_pool.id, - :action => StorageTask::ACTION_REFRESH_POOL, - :state => Task::STATE_QUEUED}) + @task = StorageTask.new({ :user => @user, + :task_target => @storage_pool, + :action => StorageTask::ACTION_REFRESH_POOL, + :state => Task::STATE_QUEUED}) @task.save! end @@ -144,12 +144,14 @@ class StorageController < ApplicationController :location => storage_pool_url(@storage_pool) } end - rescue + rescue => ex # FIXME: need to distinguish pool vs. task save errors (but should mostly be pool) respond_to do |format| format.json { - render :json => { :object => "storage_pool", :success => false, - :errors => @storage_pool.errors.localize_error_messages.to_a } } + json_hash = { :object => "storage_pool", :success => false, + :errors => @storage_pool.errors.localize_error_messages.to_a } + json_hash[:message] = ex.message if json_hash[:errors].empty? + render :json => json_hash } format.xml { render :xml => @storage_pool.errors, :status => :unprocessable_entity } end @@ -181,7 +183,7 @@ class StorageController < ApplicationController @redir_controller = @perm_obj.get_controller authorize_admin @storage_pools = @hardware_pool.storage_volumes - @storage_types = StoragePool::STORAGE_TYPES.keys + @storage_types = StoragePool::STORAGE_TYPE_PICKLIST end def addstorage @@ -226,23 +228,6 @@ class StorageController < ApplicationController end end - def vm_action - if @vm.get_action_list.include?(params[:vm_action]) - @task = VmTask.new({ :user => get_login_user, - :vm_id => params[:id], - :action => params[:vm_action], - :state => Task::STATE_QUEUED}) - if @task.save - flash[:notice] = "#{params[:vm_action]} was successfully queued." - else - flash[:notice] = "Error in inserting task for #{params[:vm_action]}." - end - else - flash[:notice] = "#{params[:vm_action]} is an invalid action." - end - redirect_to :controller => 'vm', :action => 'show', :id => params[:id] - end - def destroy pool = @storage_pool.hardware_pool if @storage_pool.destroy diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index a9ac9a5..585e524 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -49,19 +49,19 @@ class VmController < ApplicationController Vm.transaction do @vm.save! _setup_vm_provision(params) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_CREATE_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_CREATE_VM, + :state => Task::STATE_QUEUED}) @task.save! end start_now = params[:start_now] if (start_now) if @vm.get_action_list.include?(VmTask::ACTION_START_VM) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_START_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_START_VM, + :state => Task::STATE_QUEUED}) @task.save! alert = "VM was successfully created. VM Start action queued." else @@ -105,19 +105,19 @@ class VmController < ApplicationController _setup_vm_provision(params) if (params[:start_now] and @vm.get_action_list.include?(VmTask::ACTION_START_VM) ) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_START_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_START_VM, + :state => Task::STATE_QUEUED}) @task.save! elsif ( params[:restart_now] and @vm.get_action_list.include?(VmTask::ACTION_SHUTDOWN_VM) ) - @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, - :action => VmTask::ACTION_SHUTDOWN_VM, - :state => Task::STATE_QUEUED}) + @task = VmTask.new({ :user => @user, + :task_target => @vm, + :action => VmTask::ACTION_SHUTDOWN_VM, + :state => Task::STATE_QUEUED}) @task.save! @task = VmTask.new({ :user => @user, - :vm_id => @vm.id, + :task_target => @vm, :action => VmTask::ACTION_START_VM, :state => Task::STATE_QUEUED}) @task.save! diff --git a/src/app/models/host.rb b/src/app/models/host.rb index de5c5ee..546da19 100644 --- a/src/app/models/host.rb +++ b/src/app/models/host.rb @@ -31,7 +31,7 @@ class Host < ActiveRecord::Base def consuming_resources find(:all, :conditions=>{:state=>Vm::RUNNING_STATES}) end - has_many :tasks, :class_name => "HostTask", :dependent => :destroy, :order => "id DESC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end diff --git a/src/app/models/host_task.rb b/src/app/models/host_task.rb index 0aea41b..82298db 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/host_task.rb @@ -22,11 +22,20 @@ class HostTask < Task ACTION_CLEAR_VMS = "clear_vms" def after_initialize - self.hardware_pool = host.hardware_pool if self.host + self.hardware_pool = task_target.hardware_pool if self.task_target_type=="Host" end def task_obj "Host;;;#{self.host.id};;;#{self.host.hostname}" end + def vm + nil + end + def storage_volume + nil + end + def storage_pool + nil + end end diff --git a/src/app/models/iscsi_storage_pool.rb b/src/app/models/iscsi_storage_pool.rb index 1280834..7802a90 100644 --- a/src/app/models/iscsi_storage_pool.rb +++ b/src/app/models/iscsi_storage_pool.rb @@ -19,7 +19,7 @@ class IscsiStoragePool < StoragePool - validates_presence_of :port, :target + validates_presence_of :ip_addr, :port, :target validates_uniqueness_of :ip_addr, :scope => [:port, :target] def label_components diff --git a/src/app/models/host_task.rb b/src/app/models/lvm_storage_pool.rb similarity index 62% copy from src/app/models/host_task.rb copy to src/app/models/lvm_storage_pool.rb index 0aea41b..08b8938 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/lvm_storage_pool.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,16 +17,32 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class HostTask < Task +require 'util/ovirt' + +class LvmStoragePool < StoragePool + + has_many :source_volumes, :class_name => "StorageVolume", + :foreign_key => "lvm_pool_id", + :dependent => :nullify do + def total_size + find(:all).inject(0){ |sum, sv| sum + sv.size } + end + end - ACTION_CLEAR_VMS = "clear_vms" + validates_presence_of :vg_name + validates_uniqueness_of :vg_name - def after_initialize - self.hardware_pool = host.hardware_pool if self.host + def display_name + "#{get_type_label}: #{vg_name}" end - def task_obj - "Host;;;#{self.host.id};;;#{self.host.hostname}" + def size + source_volums.total_size end + def size_in_gb + kb_to_gb(size) + end + + end diff --git a/src/app/models/nfs_storage_pool.rb b/src/app/models/lvm_storage_volume.rb similarity index 82% copy from src/app/models/nfs_storage_pool.rb copy to src/app/models/lvm_storage_volume.rb index 2d05305..7dde2d1 100644 --- a/src/app/models/nfs_storage_pool.rb +++ b/src/app/models/lvm_storage_volume.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,12 +17,8 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class NfsStoragePool < StoragePool - - validates_presence_of :export_path - validates_uniqueness_of :ip_addr, :scope => :export_path - - def label_components - "#{export_path}" +class LvmStorageVolume < StorageVolume + def display_name + "#{get_type_label}: #{storage_pool.vg_name}:#{lv_name}" end end diff --git a/src/app/models/nfs_storage_pool.rb b/src/app/models/nfs_storage_pool.rb index 2d05305..a27944b 100644 --- a/src/app/models/nfs_storage_pool.rb +++ b/src/app/models/nfs_storage_pool.rb @@ -19,7 +19,7 @@ class NfsStoragePool < StoragePool - validates_presence_of :export_path + validates_presence_of :ip_addr, :export_path validates_uniqueness_of :ip_addr, :scope => :export_path def label_components diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb index bc98f8e..7a6f8f0 100644 --- a/src/app/models/storage_pool.rb +++ b/src/app/models/storage_pool.rb @@ -19,7 +19,7 @@ class StoragePool < ActiveRecord::Base belongs_to :hardware_pool - has_many :tasks, :class_name => "StorageTask", :dependent => :destroy, :order => "id DESC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end @@ -33,14 +33,18 @@ class StoragePool < ActiveRecord::Base has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy has_many :smart_pools, :through => :smart_pool_tags - validates_presence_of :ip_addr, :hardware_pool_id + + validates_presence_of :hardware_pool_id acts_as_xapian :texts => [ :ip_addr, :target, :export_path, :type ], :terms => [ [ :search_users, 'U', "search_users" ] ] ISCSI = "iSCSI" NFS = "NFS" + LVM = "LVM" STORAGE_TYPES = { ISCSI => "Iscsi", - NFS => "Nfs" } + NFS => "Nfs", + LVM => "Lvm" } + STORAGE_TYPE_PICKLIST = STORAGE_TYPES.keys - [LVM] def self.factory(type, params = nil) case type @@ -48,6 +52,8 @@ class StoragePool < ActiveRecord::Base return IscsiStoragePool.new(params) when NFS return NfsStoragePool.new(params) + when LVM + return LvmStoragePool.new(params) else return nil end diff --git a/src/app/models/storage_task.rb b/src/app/models/storage_task.rb index db604d5..785f0ea 100644 --- a/src/app/models/storage_task.rb +++ b/src/app/models/storage_task.rb @@ -22,10 +22,19 @@ class StorageTask < Task ACTION_REFRESH_POOL = "refresh_pool" def after_initialize - self.hardware_pool = storage_pool.hardware_pool if self.storage_pool + self.hardware_pool = task_target.hardware_pool if self.task_target_type=="StoragePool" end def task_obj "StoragePool;;;#{self.storage_pool.id};;;#{self.storage_pool.display_name}" end + def host + nil + end + def vm + nil + end + def storage_volume + nil + end end diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb index ef9cd6e..378b58f 100644 --- a/src/app/models/storage_volume.rb +++ b/src/app/models/storage_volume.rb @@ -23,12 +23,23 @@ class StorageVolume < ActiveRecord::Base belongs_to :storage_pool has_and_belongs_to_many :vms + belongs_to :lvm_storage_pool, :class_name => "LvmStoragePool", + :foreign_key => "lvm_pool_id" + + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do + def queued + find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) + end + end + def self.factory(type, params = nil) case type - when "iSCSI" + when StoragePool::ISCSI return IscsiStorageVolume.new(params) - when "NFS" + when StoragePool::NFS return NfsStorageVolume.new(params) + when StoragePool::LVM + return LvmStorageVolume.new(params) else return nil end diff --git a/src/app/models/host_task.rb b/src/app/models/storage_volume_task.rb similarity index 69% copy from src/app/models/host_task.rb copy to src/app/models/storage_volume_task.rb index 0aea41b..78f2895 100644 --- a/src/app/models/host_task.rb +++ b/src/app/models/storage_volume_task.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -17,16 +17,26 @@ # MA 02110-1301, USA. A copy of the GNU General Public License is # also available at http://www.gnu.org/copyleft/gpl.html. -class HostTask < Task +class StorageVolumeTask < Task - ACTION_CLEAR_VMS = "clear_vms" + ACTION_CREATE_VOLUME = "create_volume" + ACTION_EDIT_VOLUME = "edit_volume" def after_initialize - self.hardware_pool = host.hardware_pool if self.host + self.hardware_pool = task_target.storage_pool.hardware_pool if self.task_target_type=="StorageVolume" end def task_obj - "Host;;;#{self.host.id};;;#{self.host.hostname}" + "StorageVolume;;;#{self.storage_volume.id};;;#{self.storage_volume.display_name}" + end + end + def host + nil + end + def vm + nil + end + def storage_pool + nil end - end diff --git a/src/app/models/task.rb b/src/app/models/task.rb index efbed64..f231c18 100644 --- a/src/app/models/task.rb +++ b/src/app/models/task.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -20,13 +20,20 @@ class Task < ActiveRecord::Base belongs_to :hardware_pool belongs_to :vm_resource_pool + belongs_to :task_target, :polymorphic => true # moved associations here so that nested set :include directives work # StorageTask association - belongs_to :storage_pool + belongs_to :storage_pool, :class_name => "StoragePool", + :foreign_key => "task_target_id" + # StorageVolumeTask association + belongs_to :storage_volume, :class_name => "StorageVolume", + :foreign_key => "task_target_id" # HostTask association - belongs_to :host + belongs_to :host, :class_name => "Host", + :foreign_key => "task_target_id" # VmTask association - belongs_to :vm + belongs_to :vm, :class_name => "Vm", + :foreign_key => "task_target_id" STATE_QUEUED = "queued" STATE_RUNNING = "running" diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index 6853157..2eff87a 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -22,7 +22,7 @@ require 'util/ovirt' class Vm < ActiveRecord::Base belongs_to :vm_resource_pool belongs_to :host - has_many :tasks, :class_name => "VmTask", :dependent => :destroy, :order => "id ASC" do + has_many :tasks, :as => :task_target, :dependent => :destroy, :order => "id ASC" do def queued find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end @@ -231,11 +231,11 @@ class Vm < ActiveRecord::Base def queue_action(user, action, data = nil) return false unless get_action_list.include?(action) - task = VmTask.new({ :user => user, - :vm_id => id, - :action => action, - :args => data, - :state => Task::STATE_QUEUED}) + task = VmTask.new({ :user => user, + :task_target => self, + :action => action, + :args => data, + :state => Task::STATE_QUEUED}) task.save! return true end diff --git a/src/app/models/vm_task.rb b/src/app/models/vm_task.rb index 6251919..d1966dc 100644 --- a/src/app/models/vm_task.rb +++ b/src/app/models/vm_task.rb @@ -107,9 +107,9 @@ class VmTask < Task :popup_action => 'migrate'} } def after_initialize - if self.vm - self.vm_resource_pool = vm.vm_resource_pool - self.hardware_pool = vm.get_hardware_pool + if self.task_target_type=="Vm" + self.vm_resource_pool = task_target.vm_resource_pool + self.hardware_pool = task_target.get_hardware_pool end end @@ -141,4 +141,13 @@ class VmTask < Task def self.label_and_action(action) return [action_label(action), action, action_icon(action)] end + def host + nil + end + def storage_volume + nil + end + def storage_pool + nil + end end diff --git a/src/db/migrate/025_add_lvm_storage.rb b/src/db/migrate/025_add_lvm_storage.rb new file mode 100644 index 0000000..697df4d --- /dev/null +++ b/src/db/migrate/025_add_lvm_storage.rb @@ -0,0 +1,106 @@ +# +# 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 AddLvmStorage < ActiveRecord::Migration + def self.up + #LVM pool does not use ip_addr + + # VG Name + add_column :storage_pools, :vg_name, :string + + # LV name + add_column :storage_volumes, :lv_name, :string + # LV capacity==existing size attr + + # LV + # FIXME: do we want to make these user-determined, or should + # these be defined by the model itself? + add_column :storage_volumes, :lv_owner_perms, :string + add_column :storage_volumes, :lv_group_perms, :string + add_column :storage_volumes, :lv_mode_perms, :string + + # VG pool ID + add_column :storage_volumes, :lvm_pool_id, :integer + execute "alter table storage_volumes add constraint fk_storage_volumes_lvm_pools + foreign key (lvm_pool_id) references storage_pools(id)" + + # use polymorphic tasks association + add_column :tasks, :task_target_id, :integer + add_column :tasks, :task_target_type, :string + begin + Task.transaction do + HostTask.find(:all).each do |task| + task.task_target_type = 'Host' + task.task_target_id = task.host_id + task.save! + end + StorageTask.find(:all).each do |task| + task.task_target_type = 'StoragePool' + task.task_target_id = task.storage_pool_id + task.save! + end + VmTask.find(:all).each do |task| + task.task_target_type = 'Vm' + task.task_target_id = task.vm_id + task.save! + end + end + remove_column :tasks, :vm_id + remove_column :tasks, :storage_pool_id + remove_column :tasks, :host_id + rescue + puts "could not update tasks..." + end + + end + + def self.down + remove_column :storage_pools, :vg_name + + remove_column :storage_volumes, :lv_name + remove_column :storage_volumes, :lv_owner_perms + remove_column :storage_volumes, :lv_group_perms + remove_column :storage_volumes, :lv_mode_perms + remove_column :storage_volumes, :lvm_pool_id + + add_column :tasks, :vm_id, :integer + add_column :tasks, :storage_pool_id, :integer + add_column :tasks, :host_id, :integer + begin + Task.transaction do + HostTask.find(:all).each do |task| + task.host_id = task.task_target_id + task.save! + end + StorageTask.find(:all).each do |task| + task.storage_pool_id = task.task_target_id + task.save! + end + VmTask.find(:all).each do |task| + task.vm_id = task.task_target_id + task.save! + end + end + remove_column :tasks, :task_target_id + remove_column :tasks, :task_target_type + rescue + puts "could not update tasks..." + end + end +end diff --git a/src/dutils/active_record_env.rb b/src/dutils/active_record_env.rb index 3903c87..26caac2 100644 --- a/src/dutils/active_record_env.rb +++ b/src/dutils/active_record_env.rb @@ -80,10 +80,12 @@ require 'models/vm_task.rb' require 'models/storage_pool.rb' require 'models/iscsi_storage_pool.rb' require 'models/nfs_storage_pool.rb' +require 'models/lvm_storage_pool.rb' require 'models/storage_volume.rb' require 'models/iscsi_storage_volume.rb' require 'models/nfs_storage_volume.rb' +require 'models/lvm_storage_volume.rb' require 'models/smart_pool.rb' require 'models/smart_pool_tag.rb' diff --git a/src/host-status/host-status.rb b/src/host-status/host-status.rb index 6330d5c..291369e 100755 --- a/src/host-status/host-status.rb +++ b/src/host-status/host-status.rb @@ -92,11 +92,10 @@ def kick_taskomatic(msg, vm, host_id = nil) 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.args = host_id ? [msg,host_id].join(',') : msg task.created_at = Time.now task.time_started = Time.now - task.vm_id = vm.id + task.task_target = vm task.save end diff --git a/src/task-omatic/task_host.rb b/src/task-omatic/task_host.rb index 5f42da3..3d039fb 100644 --- a/src/task-omatic/task_host.rb +++ b/src/task-omatic/task_host.rb @@ -25,7 +25,7 @@ require 'task_vm' def clear_vms_host(task) puts "clear_vms_host" - src_host = findHost(task.host_id) + src_host = task.host src_host.vms.each do |vm| migrate(vm) diff --git a/src/task-omatic/task_storage.rb b/src/task-omatic/task_storage.rb index ff9271e..5cddf2b 100644 --- a/src/task-omatic/task_storage.rb +++ b/src/task-omatic/task_storage.rb @@ -23,14 +23,14 @@ require 'libvirt' def refresh_pool(task) puts "refresh_pool" - pool = StoragePool.find(task[:storage_pool_id]) + pool = task.storage_pool if pool == nil raise "Could not find storage pool" end if pool[:type] == "IscsiStoragePool" - storage = Iscsi.new(pool.ip_addr, pool.target) + storage = Iscsi.new(pool.ip_addr, pool[:target]) elsif pool[:type] == "NfsStoragePool" storage = NFS.new(pool.ip_addr, pool.export_path) else diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 02e41af..b5f888d 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -118,10 +118,10 @@ end def findVM(task, fail_on_nil_host_id = true) # find the matching VM in the vms table - vm = Vm.find(:first, :conditions => [ "id = ?", task.vm_id ]) + vm = task.vm if vm == nil - raise "VM id " + task.vm_id + "not found" + raise "VM not found for task " + task.id end if vm.host_id == nil && fail_on_nil_host_id @@ -135,7 +135,7 @@ def findVM(task, fail_on_nil_host_id = true) # can mark it either as off (if we didn't find it), or mark the correct # vm.host_id if we did. However, if you have a large number of hosts # out there, this could take a while. - raise "No host_id for VM " + task.vm_id.to_s + raise "No host_id for VM " + vm.id.to_s end return vm @@ -196,10 +196,7 @@ def shutdown_vm(task) setVmState(vm, Vm::STATE_STOPPING) begin - # OK, now that we found the VM, go looking in the hosts table - host = findHost(vm.host_id) - - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + conn = Libvirt::open("qemu+tcp://" + vm.host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) # FIXME: crappy. Right now we destroy the domain to make sure it # really went away. We really want to shutdown the domain to make @@ -357,10 +354,7 @@ def save_vm(task) setVmState(vm, Vm::STATE_SAVING) begin - # OK, now that we found the VM, go looking in the hosts table - host = findHost(vm.host_id) - - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + conn = Libvirt::open("qemu+tcp://" + vm.host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) dom.save("/tmp/" + vm.uuid + ".save") conn.close @@ -400,13 +394,10 @@ def restore_vm(task) setVmState(vm, Vm::STATE_RESTORING) begin - # OK, now that we found the VM, go looking in the hosts table - host = findHost(vm.host_id) - # FIXME: we should probably go out to the host and check what it thinks # the state is - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + conn = Libvirt::open("qemu+tcp://" + vm.host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) dom.restore @@ -442,10 +433,7 @@ def suspend_vm(task) setVmState(vm, Vm::STATE_SUSPENDING) begin - # OK, now that we found the VM, go looking in the hosts table - host = findHost(vm.host_id) - - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + conn = Libvirt::open("qemu+tcp://" + vm.host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) dom.suspend conn.close @@ -482,10 +470,7 @@ def resume_vm(task) setVmState(vm, Vm::STATE_RESUMING) begin - # OK, now that we found the VM, go looking in the hosts table - host = findHost(vm.host_id) - - conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + conn = Libvirt::open("qemu+tcp://" + vm.host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) dom.resume conn.close @@ -507,27 +492,24 @@ def update_state_vm(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 + vm = findVM(task, false) + new_vm_state, host_id_str = task.args.split(",") + if (vm.host_id == nil) and host_id_str + vm.host_id = host_id_str.to_i end vm_effective_state = Vm::EFFECTIVE_STATE[vm.state] - task_effective_state = Vm::EFFECTIVE_STATE[task.args] + task_effective_state = Vm::EFFECTIVE_STATE[new_vm_state] if vm_effective_state != task_effective_state - vm.state = task.args + vm.state = new_vm_state if task_effective_state == Vm::STATE_STOPPED setVmShutdown(vm) end vm.save - puts "Updated state to " + task.args + puts "Updated state to " + new_vm_state end end diff --git a/src/test/fixtures/tasks.yml b/src/test/fixtures/tasks.yml index 73daaef..404543c 100644 --- a/src/test/fixtures/tasks.yml +++ b/src/test/fixtures/tasks.yml @@ -9,9 +9,8 @@ one: time_ended: message: type: 'VmTask' - vm_id: 5 - storage_pool_id: - host_id: + task_target_type: 'Vm' + task_target_id: 5 two: id: 2 user: 'admin' @@ -19,7 +18,8 @@ two: state: 'queued' created_at: '2008-01-13 15:40:13.417883' type: 'StorageTask' - storage_pool_id: 1 + task_target_type: 'StoragePool' + task_target_id: 1 three: id: 3 user: 'luser' @@ -30,7 +30,8 @@ three: time_ended: '2002-12-14 05:12:23.417885' message: 'finished!' type: 'VmTask' - vm_id: 7 + task_target_type: 'Vm' + task_target_id: 7 four: id: 4 user: 'buser' @@ -39,7 +40,8 @@ four: created_at: '2015-11-23 15:12:43.417883' time_started: '2015-11-23 15:12:43.417885' type: 'VmTask' - vm_id: 2 + task_target_type: 'Vm' + task_target_id: 2 five: id: 5 user: 'admin' @@ -48,7 +50,8 @@ five: created_at: '2015-11-23 15:12:43.417883' time_started: '2015-11-23 15:12:43.417885' type: 'VmTask' - vm_id: 3 + task_target_type: 'Vm' + task_target_id: 3 six: id: 6 user: 'buser' @@ -59,7 +62,8 @@ six: time_ended: '2001-01-03 01:05:19.417885' message: 'task cancelled' type: 'HostTask' - host_id: 9 + task_target_type: 'Host' + task_target_id: 9 seven: id: 7 user: 'yuser' @@ -70,7 +74,8 @@ seven: time_ended: '2001-01-10 13:49:15.417885' message: 'task failed, insufficient permissions' type: 'StorageTask' - storage_pool_id: 4 + task_target_type: 'StoragePool' + task_target_id: 4 eight: id: 8 user: 'yuser' @@ -81,7 +86,8 @@ eight: time_ended: '2001-01-10 14:04:15.417885' message: 'completed' type: 'StorageTask' - storage_pool_id: 4 + task_target_type: 'StoragePool' + task_target_id: 4 nine: id: 9 user: 'kadmin' @@ -91,7 +97,8 @@ nine: time_started: '2008-04-20 20:00:00.417885' message: 'paused' type: 'VmTask' - vm_id: 12 + task_target_type: 'Vm' + task_target_id: 12 ten: id: 10 user: 'kadmin' @@ -99,4 +106,5 @@ ten: state: 'queued' created_at: '2008-04-20 20:05:30.417883' type: 'VmTask' - vm_id: 12 + task_target_type: 'Vm' + task_target_id: 12 -- 1.5.5.1 From berrange at redhat.com Thu Oct 16 19:42:24 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 16 Oct 2008 20:42:24 +0100 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <48F76F8E.7080906@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> <20081016151649.GD1821@redhat.com> <48F7651A.7010507@redhat.com> <20081016161043.GI1821@redhat.com> <48F76F8E.7080906@redhat.com> Message-ID: <20081016194224.GB6745@redhat.com> On Thu, Oct 16, 2008 at 12:45:02PM -0400, Darryl Pierce wrote: > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > Daniel P. Berrange wrote: > > Yes, this does more closely align, though I see this approach actualy > > introduces a slightly different problem - the VLANs don't actually > > work very well in this context. If you want to create lots of VLANs, > > its forcing you to re-specify the pair of bonded devices each time > > which isn't very nice. I can't think of a good answer for this yet > > Another thing is if we're going to have multiple networks defined on a > node, we'll need a dropdown for that as well; i.e., select an existing > network to edit. > > Regarding the vlans, I'm not experienced enough with them to come up > with a good design. I'll do some research, but if you can point me to a > site that can give a great high level explanation, I can try using that > to tweak the design. Thinking about it some more, I wasn't far off - we just need some dynamic handling of the devices list depending on what you select. Basic properties Name: free text name for the network IPv4 properties Mode: DHCP | Static | None Address: Netmask: Broadcast: Gateway: IPv6 properties Mode: DHCP | Static | Autoconfig | None Address: Prefix: Gateway: Physical devices Shared: yes | no Mode: raw | failover | aggregation | vlan VLAN: Devices: eth0: 00:11:22:33:44:55:66 (or name of network if its used) eth1: 00:11:22:33:44:55:66 (or name of network if its used) eth2: 00:11:22:33:44:55:66 (or name of network if its used) bond0: bond1: So, to explain this last section - If you select shared = 'yes', then the resulting device (either ethX or bondX) will be attached to a bridge. This enables its use for a guest. If not shared, then its a storage or mgmt network where the device is used directly. - If mode is 'raw' - Devices list is filtered to only show unused ethXXX devices - Allows picking of one device - Configures the ethXXX device or the bridge if its shared - If mode is 'failover' or 'aggregation' - Devices list is filtered to only show unused ethXXX devices - Allows picking of multiple devices based on mac addr - We put them all in a bondXXX device - Configures the bondXXX device or the bridge if its shared - If mode is 'vlan' - Devices list shows all ethXXX, all bondXXX devices in networks which are not shared - Allows picking of one device based on network name - Creates a VLAN device ethXXX.YYY or bondXXX.YYY against this device, where YYY is VLAN number - Configures the ethXXX.YYY/bondXXX.YY device or the bridge if its shared Some examples 1. Want device eth0 for mgmt traffic - Create network with * device mode = raw * pick eth0 based on mac addr * Configure IPv4/6 * Shared = no Results in eth0 2. Want devices eth0 and eth1 bonded for guest traffic - Create network with * device mode = failover * pick eth0 & eth1 based on mac addr * Optional IPv4/6 config if desired * Shared = yes Results in bond0 + br0 3. Want devices eth0 and eth1, bonded, with two VLANs available for guest traffic - Create network with * device mode = failover * pick eth0 & eth1 based on mac addr * IPv4/6 address = none * Shared = no Results in bond0 - Create network with * device mode = vlan * pick bond0 based on network name * Optional IPv4/6 config if desired * Shared = yes * VLAN = 123 Results in bond0.123 + br0.123 - Create network with * device mode = vlan * pick bond0 based on network name * Optional IPv4/6 config if desired * Shared = yes * VLAN = 125 Results in bond0.125 + br0.125 4. Want device eth0 with one VLAN for guest traffic and one VLAN for mgmt traffic - Create network with * device mode = raw * pick eth0 based on mac addr * IPv4/6 config = none * shared = no Results in eth0 - Create network with * device mode = vlan * pick eth0 based on network name * shared = yes * VLAN = 123 Results in eth0.123 + br0.123 - Create network with * device mode = vlan * pick eth0 based on network name * shared = no * VLAN = 125 Results in eth0.125 So for bridging, bonding & raw devices everything can be done in one step by defining a network & picking devices. Additional networks need only be defined if we wish to make use of multiple VLANs on a raw device or bond. 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 dpierce at redhat.com Thu Oct 16 19:50:10 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 16 Oct 2008 15:50:10 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <20081016194224.GB6745@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> <20081016151649.GD1821@redhat.com> <48F7651A.7010507@redhat.com> <20081016161043.GI1821@redhat.com> <48F76F8E.7080906@redhat.com> <20081016194224.GB6745@redhat.com> Message-ID: <48F79AF2.7010000@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > Thinking about it some more, I wasn't far off - we just need some > dynamic handling of the devices list depending on what you select. Excellent! That's exactly the kind of usage detail I needed. I'll have an updated screenshot shortly. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj3mu4ACgkQjaT4DmxOfxvUXQCfQebkTZMmy1XpiJFNMZ8WEgUP XIQAn3mrJlyuQceqyQMGTrIBo+NnoOvq =G4+h -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 16 20:08:59 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 16 Oct 2008 16:08:59 -0400 Subject: [Ovirt-devel] Network configuration wiki page created... In-Reply-To: <20081016194224.GB6745@redhat.com> References: <48F60DCC.4000606@redhat.com> <48F7567A.40107@redhat.com> <20081016151649.GD1821@redhat.com> <48F7651A.7010507@redhat.com> <20081016161043.GI1821@redhat.com> <48F76F8E.7080906@redhat.com> <20081016194224.GB6745@redhat.com> Message-ID: <48F79F5B.8020809@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > Thinking about it some more, I wasn't far off - we just need some > dynamic handling of the devices list depending on what you select. > > Basic properties > > Name: free text name for the network Okay, I have an updated screenshot. Pretty rough, but captures the fields described below. Is that getting closer? Question: What does a mode of "None" mean for addressing? Or should that be an implicit addressing selected if the device is enslaved to a bonded interface? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj3n1cACgkQjaT4DmxOfxvhJwCgmzXza/+jMPSWLqab7H9jkWA2 p4gAn3hCTNYsoGdnAnol93B8RJlnIcfv =QEBC -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From imain at redhat.com Thu Oct 16 21:36:02 2008 From: imain at redhat.com (Ian Main) Date: Thu, 16 Oct 2008 14:36:02 -0700 Subject: [Ovirt-devel] [PATCH server] Fix bug in host-status. Message-ID: <1224192962-4349-1-git-send-email-imain@redhat.com> This patch fixes a bug in host-status where the connection was being closed at the end of the loop instead of after so consequent calls were failing making it think the vm was off. There's also a little extra debugging prints here and another small fix to return if we can't get a domain list. Signed-off-by: Ian Main --- src/host-status/host-status.rb | 16 +++++++++------- 1 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/host-status/host-status.rb b/src/host-status/host-status.rb index 6330d5c..3b07720 100755 --- a/src/host-status/host-status.rb +++ b/src/host-status/host-status.rb @@ -59,7 +59,7 @@ end require 'dutils' def check_state(vm, dom_info, host) - puts 'checking state of vm', vm.description + puts 'checking state of vm ' + vm.description case dom_info.state @@ -149,9 +149,10 @@ def check_status(host) rescue puts "Failed to request domain list on host " + host.hostname conn.close - next + return end + puts "** Host alive, checking vms by id **" # Here we're going through every vm listed through libvirt. This # really only lets us find ones that are started that shouldn't be. vm_ids.each do |vm_id| @@ -159,7 +160,7 @@ def check_status(host) begin dom = conn.lookup_domain_by_id(vm_id) rescue - puts "Failed to find domain " + vm.description + puts "Failed to find domain " + vm.description + " with vm_id ", vm_id next end @@ -178,25 +179,26 @@ def check_status(host) check_state(vm, info, host) end + puts "** Checking all vms as appear in the database **" # Now we get a list of all vms that should be on this system and see if # they are all running. Vm.find(:all, :conditions => [ "host_id = ?", host.id ]).each do |vm| begin + puts "Looking up domain by uuid #{vm.uuid}" dom = conn.lookup_domain_by_uuid(vm.uuid) rescue # OK. We couldn't find the UUID that we thought was there. The only # explanation is that the domain is dead. - puts "Failed to find domain " + vm.description + puts "Failed to find domain " + vm.description + ", marking as dead" kick_taskomatic(Vm::STATE_STOPPED, vm) next end info = dom.info check_state(vm, info, host) - - conn.close - end + + conn.close end get_credentials -- 1.5.5.1 From imain at redhat.com Thu Oct 16 23:39:38 2008 From: imain at redhat.com (Ian Main) Date: Thu, 16 Oct 2008 16:39:38 -0700 Subject: [Ovirt-devel] Re: [PATCH server] Fix bug in host-status. In-Reply-To: <1224192962-4349-1-git-send-email-imain@redhat.com> References: <1224192962-4349-1-git-send-email-imain@redhat.com> Message-ID: <20081016163938.3f8ef34f@tp.mains.net> Pushed. On Thu, 16 Oct 2008 14:36:02 -0700 Ian Main wrote: > This patch fixes a bug in host-status where the connection was being closed > at the end of the loop instead of after so consequent calls were failing > making it think the vm was off. > > There's also a little extra debugging prints here and another small fix > to return if we can't get a domain list. > > Signed-off-by: Ian Main > --- > src/host-status/host-status.rb | 16 +++++++++------- > 1 files changed, 9 insertions(+), 7 deletions(-) > > diff --git a/src/host-status/host-status.rb b/src/host-status/host-status.rb > index 6330d5c..3b07720 100755 > --- a/src/host-status/host-status.rb > +++ b/src/host-status/host-status.rb > @@ -59,7 +59,7 @@ end > require 'dutils' > > def check_state(vm, dom_info, host) > - puts 'checking state of vm', vm.description > + puts 'checking state of vm ' + vm.description > > case dom_info.state > > @@ -149,9 +149,10 @@ def check_status(host) > rescue > puts "Failed to request domain list on host " + host.hostname > conn.close > - next > + return > end > > + puts "** Host alive, checking vms by id **" > # Here we're going through every vm listed through libvirt. This > # really only lets us find ones that are started that shouldn't be. > vm_ids.each do |vm_id| > @@ -159,7 +160,7 @@ def check_status(host) > begin > dom = conn.lookup_domain_by_id(vm_id) > rescue > - puts "Failed to find domain " + vm.description > + puts "Failed to find domain " + vm.description + " with vm_id ", vm_id > next > end > > @@ -178,25 +179,26 @@ def check_status(host) > check_state(vm, info, host) > end > > + puts "** Checking all vms as appear in the database **" > # Now we get a list of all vms that should be on this system and see if > # they are all running. > Vm.find(:all, :conditions => [ "host_id = ?", host.id ]).each do |vm| > > begin > + puts "Looking up domain by uuid #{vm.uuid}" > dom = conn.lookup_domain_by_uuid(vm.uuid) > rescue > # OK. We couldn't find the UUID that we thought was there. The only > # explanation is that the domain is dead. > - puts "Failed to find domain " + vm.description > + puts "Failed to find domain " + vm.description + ", marking as dead" > kick_taskomatic(Vm::STATE_STOPPED, vm) > next > end > info = dom.info > check_state(vm, info, host) > - > - conn.close > - > end > + > + conn.close > end > > get_credentials > -- > 1.5.5.1 > From pmyers at redhat.com Fri Oct 17 12:50:17 2008 From: pmyers at redhat.com (Perry Myers) Date: Fri, 17 Oct 2008 08:50:17 -0400 Subject: [Ovirt-devel] Re: A pity thing to tell you In-Reply-To: <48F870EF.1060800@redhat.com> References: <48F870EF.1060800@redhat.com> Message-ID: <48F88A09.7030907@redhat.com> vbian wrote: > By the way,here is the other more question: a vm which could run > successfully turned to be an unreachable one. Why does it happen? How to > avoid it? :-) There is a bug in version 0.93 that is fixed in the new version (0.94) that causes this. So when you get to test 0.94 this should be fixed. If it is not, please let us know. Thanks, Perry From apevec at redhat.com Fri Oct 17 18:44:02 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 17 Oct 2008 20:44:02 +0200 Subject: [Ovirt-devel] [PATCH appliance] provisioning: add empty YUM repo to F9 minimal install tree Message-ID: <1224269042-5271-1-git-send-email-apevec@redhat.com> and point anaconda to download stage2.img we already have in the appliance Signed-off-by: Alan Pevec --- gettree.sh | 2 ++ ovirt-appliance.ks | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gettree.sh b/gettree.sh index f6ae46a..2bd78fe 100755 --- a/gettree.sh +++ b/gettree.sh @@ -25,6 +25,8 @@ pushd $dest printf "Downloading minimal Fedora install tree from $url" set -e download $url/.treeinfo +download $url/media.repo +createrepo . mkdir -p images/pxeboot cd images download $url/images/stage2.img diff --git a/ovirt-appliance.ks b/ovirt-appliance.ks index b344e47..67449b1 100644 --- a/ovirt-appliance.ks +++ b/ovirt-appliance.ks @@ -113,8 +113,7 @@ cobbler repo add --name=f9-$arch --arch=$arch --mirror-locally=0 \ --mirror=$url/releases/9/Everything/$arch/os cobbler repo add --name=f9-$arch-updates --arch=$arch --mirror-locally=0 \ --mirror=$url/updates/9/$arch -sed -e 's#^url .*#url --url=$url/releases/$ver/$os/$arch/os#' \ - -e 's#^reboot.*#poweroff#' /etc/cobbler/sample_end.ks \ +sed -e 's#^reboot.*#poweroff#' /etc/cobbler/sample_end.ks \ > /etc/cobbler/sample-$os-$ver-$arch.ks cobbler profile edit --name=$os-$ver-$arch \ --repos="f9-$arch f9-$arch-updates" \ -- 1.5.5.1 From dpierce at redhat.com Fri Oct 17 21:11:04 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 17 Oct 2008 17:11:04 -0400 Subject: [Ovirt-devel] [PATCH server] Introduces a new IP address model. Moves IP addressing to a separate Message-ID: <1224277864-13419-1-git-send-email-dpierce@redhat.com> You will need to run a migration after this patch to create the ip_addresses table and massage the bondings and nics tables to reference it. NEW CLASSES: IpAddress: base class for addresses IpV4Address: represents an IPv4 address IpV6Address: represents an IPv6 address Signed-off-by: Darryl L. Pierce --- src/app/models/bonding.rb | 1 + src/app/models/ip_address.rb | 25 +++++ src/app/models/ip_v4_address.rb | 46 +++++++++ src/app/models/ip_v6_address.rb | 40 ++++++++ src/app/models/nic.rb | 2 + src/db/migrate/025_create_ip_addresses.rb | 46 +++++++++ ...026_move_nic_addresses_to_ip_addresses_table.rb | 56 +++++++++++ ...7_move_bonding_address_to_ip_addresses_table.rb | 54 +++++++++++ src/host-browser/host-browser.rb | 17 ++-- src/lib/managed_node_configuration.rb | 8 +- src/test/fixtures/bondings.yml | 4 +- src/test/fixtures/ip_addresses.yml | 55 +++++++++++ src/test/fixtures/nics.yml | 82 ++++++++-------- .../functional/managed_node_configuration_test.rb | 26 +++--- src/test/unit/ip_address_test.rb | 8 ++ src/test/unit/ip_v4_address_test.rb | 99 ++++++++++++++++++++ src/test/unit/ip_v6_address_test.rb | 82 ++++++++++++++++ src/test/unit/nic_test.rb | 1 + 18 files changed, 584 insertions(+), 68 deletions(-) create mode 100644 src/app/models/ip_address.rb create mode 100644 src/app/models/ip_v4_address.rb create mode 100644 src/app/models/ip_v6_address.rb create mode 100644 src/db/migrate/025_create_ip_addresses.rb create mode 100644 src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb create mode 100644 src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb create mode 100644 src/test/fixtures/ip_addresses.yml create mode 100644 src/test/unit/ip_address_test.rb create mode 100644 src/test/unit/ip_v4_address_test.rb create mode 100644 src/test/unit/ip_v6_address_test.rb diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 006c261..2fa4aa8 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -51,6 +51,7 @@ class Bonding < ActiveRecord::Base belongs_to :host belongs_to :bonding_type belongs_to :boot_type + belongs_to :ip_address has_and_belongs_to_many :nics, :join_table => 'bondings_nics', diff --git a/src/app/models/ip_address.rb b/src/app/models/ip_address.rb new file mode 100644 index 0000000..f48cd4e --- /dev/null +++ b/src/app/models/ip_address.rb @@ -0,0 +1,25 @@ +# +# ip_address.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. + +# +IpAddress+ is the base class for all address related classes. +# +class IpAddress < ActiveRecord::Base + +end diff --git a/src/app/models/ip_v4_address.rb b/src/app/models/ip_v4_address.rb new file mode 100644 index 0000000..4495a13 --- /dev/null +++ b/src/app/models/ip_v4_address.rb @@ -0,0 +1,46 @@ +# ip_v4_address.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. + +# +IpV4Address+ represents a single IPv4 address. +# +class IpV4Address < IpAddress + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} + + validates_presence_of :address, + :message => 'An address must be supplied.' + validates_format_of :address, + :with => ADDRESS_TEST + + validates_presence_of :netmask, + :message => 'A netmask must be supplied.' + validates_format_of :netmask, + :with => ADDRESS_TEST + + validates_presence_of :gateway, + :message => 'A gateway address must be supplied.' + validates_format_of :gateway, + :with => ADDRESS_TEST + + validates_presence_of :broadcast, + :message => 'A broadcast address must be supplied.' + validates_format_of :broadcast, + :with => ADDRESS_TEST + +end diff --git a/src/app/models/ip_v6_address.rb b/src/app/models/ip_v6_address.rb new file mode 100644 index 0000000..1395886 --- /dev/null +++ b/src/app/models/ip_v6_address.rb @@ -0,0 +1,40 @@ +# ip_v6_address.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. + +# +IpV6Address+ represents a single IPv6 address. +# +class IpV6Address < IpAddress + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} + + validates_presence_of :address, + :message => 'An address must be provided.' + validates_format_of :address, + :with => ADDRESS_TEST + + validates_presence_of :gateway, + :message => 'A gateway address must be provided.' + validates_format_of :gateway, + :with => ADDRESS_TEST + + validates_presence_of :prefix, + :message => 'A prefix must be provided.' + validates_format_of :prefix, + :with => ADDRESS_TEST +end diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb index baf7095..69568a9 100644 --- a/src/app/models/nic.rb +++ b/src/app/models/nic.rb @@ -22,4 +22,6 @@ class Nic < ActiveRecord::Base belongs_to :boot_type has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' + + belongs_to :ip_address end diff --git a/src/db/migrate/025_create_ip_addresses.rb b/src/db/migrate/025_create_ip_addresses.rb new file mode 100644 index 0000000..0f528f4 --- /dev/null +++ b/src/db/migrate/025_create_ip_addresses.rb @@ -0,0 +1,46 @@ +# +# 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. + +# Creates a single-inheritance table for both IPv4 and IPv6 +# addresses. +# +class CreateIpAddresses < ActiveRecord::Migration + def self.up + create_table :ip_addresses do |t| + t.string :type + + # common attributes + t.string :address, :limit => 39 + t.string :gateway, :limit => 39 + + # attributes for IPv4 (type=IpV4Address) + t.string :netmask, :limit => 15 + t.string :broadcast, :limit => 15 + + # attributes for IPv6 (type=IpV6Address) + t.string :prefix, :limit => 39 + + t.timestamps + end + end + + def self.down + drop_table :ip_addresses + end +end diff --git a/src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb b/src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb new file mode 100644 index 0000000..b873e4d --- /dev/null +++ b/src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb @@ -0,0 +1,56 @@ +# +# 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. + +class MoveNicAddressesToIpAddressesTable < ActiveRecord::Migration + def self.up + add_column :nics, :ip_address_id, :integer, :null => true + + execute 'alter table nics add constraint fk_nic_ip_address + foreign key (ip_address_id) references ip_addresses(id)' + + Nic.find(:all).each do |nic| + address = IpV4Address.new(:address => nic.ip_addr, + :netmask => nic.netmask, + :broadcast => nic.broadcast, + :gateway => nic.ip_addr) + nic.ip_address = address + + nic.save! + address.save! + end + + remove_column :nics, :ip_addr + remove_column :nics, :netmask + remove_column :nics, :broadcast + end + + def self.down + add_column :nics, :ip_addr, :string, :limit => 16 + add_column :nics, :netmask, :string, :limit => 16 + add_column :nics, :broadcast, :string, :limit => 16 + + Nic.find(:all).each do |nic| + nic.ip_addr = nic.ip_address.address + nic.netmask = nic.ip_address.netmask + nic.broadcast = nic.ip_address.broadcast + end + + remove_column :nics, :ip_address_id + end +end diff --git a/src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb b/src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb new file mode 100644 index 0000000..53f7058 --- /dev/null +++ b/src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb @@ -0,0 +1,54 @@ +# +# 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. + +class MoveBondingAddressToIpAddressesTable < ActiveRecord::Migration + def self.up + add_column :bondings, :ip_address_id, :integer, :null => true + + execute 'alter table bondings add constraint fk_bonding_ip_address + foreign key (ip_address_id) references ip_addresses(id)' + + Bonding.find(:all).each do |bonding| + ip_address = IpV4Address.new(:address => bonding.ip_addr, + :netmask => bonding.netmask, + :broadcast => bonding.broadcast, + :gateway => bonding.ip_addr) + end + + remove_column :bondings, :ip_addr + remove_column :bondings, :netmask + remove_column :bondings, :broadcast + end + + def self.down + t.string :ip_addr, :null => true, :limit => 15 + t.string :netmask, :null => true, :limit => 15 + t.string :broadcast, :null => true, :limit => 15 + + Bonding.each do |bonding| + bonding.ip_addr = bonding.ip_address.address + bonding.netmask = bonding.ip_address.netmask + bonding.broadcast = bonding.ip_address.broadcast + + bonding.save! + end + + remove_column :bondings, :ip_address_id + end +end diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 79d34e8..bbfbf3b 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -283,10 +283,10 @@ class HostBrowser updated_nic = Nic.find_by_id(nic.id) - updated_nic.bandwidth = detail['BANDWIDTH'] - updated_nic.ip_addr = detail['IP_ADDRESS'] - updated_nic.netmask = detail['NETMASK'] - updated_nic.broadcast = detail['BROADCAST'] + updated_nic.bandwidth = detail['BANDWIDTH'] + updated_nic.ip_address.address = detail['IP_ADDRESS'] + updated_nic.ip_address.netmask = detail['NETMASK'] + updated_nic.ip_address.broadcast = detail['BROADCAST'] updated_nic.save! found=true @@ -310,11 +310,14 @@ class HostBrowser 'mac' => nic['MAC'].upcase, 'bandwidth' => nic['BANDWIDTH'], 'usage_type' => 1, - 'ip_addr' => nic['IP_ADDRESS'], - 'netmask' => nic['NETMASK'], - 'broadcast' => nic['BROADCAST'], 'boot_type_id' => boot_type.id) + ip_address = IpV4Address.new('address' => nic['IP_ADDRESS'], + 'netmask' => nic['NETMASK'], + 'broadcast' => nic['BROADCAST'], + 'gateway' => nic['GATEWAY']) + detail.ip_address = ip_address + host.nics << detail end diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb index 93f8448..fff7914 100644 --- a/src/lib/managed_node_configuration.rb +++ b/src/lib/managed_node_configuration.rb @@ -51,7 +51,7 @@ class ManagedNodeConfiguration host.bondings.each do |bonding| result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}" result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_addr}" if bonding.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_address.address}" if bonding.ip_address result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/ONBOOT yes" bonding.nics.each do |nic| @@ -97,9 +97,9 @@ class ManagedNodeConfiguration result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO #{nic.boot_type.proto}" if nic.boot_type.proto == 'static' - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_address.address}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.ip_address.netmask}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.ip_address.broadcast}" end if bridged diff --git a/src/test/fixtures/bondings.yml b/src/test/fixtures/bondings.yml index 29473c7..1a74f42 100644 --- a/src/test/fixtures/bondings.yml +++ b/src/test/fixtures/bondings.yml @@ -4,8 +4,6 @@ mailservers_managed_node_bonding: bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> host_id: <%= Fixtures.identify(:mailservers_managed_node) %> boot_type_id: <%= Fixtures.identify(:boot_type_static) %> - ip_addr: 172.31.0.15 - netmask: 255.255.255. - broadcast: 172.31.0.255 + ip_address_id: <%= Fixtures.identify(:ip_v4_mailservers_managed_node_bonding) %> arp_ping_address: 172.31.0.100 arp_interval: 0 diff --git a/src/test/fixtures/ip_addresses.yml b/src/test/fixtures/ip_addresses.yml new file mode 100644 index 0000000..0472669 --- /dev/null +++ b/src/test/fixtures/ip_addresses.yml @@ -0,0 +1,55 @@ +ip_v4_one: + type: IpV4Address + address: 1.2.3.4 + netmask: 255.255.255.0 + gateway: 1.2.3.1 + broadcast: 1.2.3.255 + +ip_v4_two: + type: IpV4Address + address: 2.3.4.5 + netmask: 255.255.255.0 + gateway: 1.2.3.1 + broadcast: 2.3.4.255 + +ip_v4_three: + type: IpV4Address + address: 3.4.5.6 + netmask: 255.255.255.0 + gateway: 3.4.5.1 + broadcast: 3.4.5.255 + +ip_v4_four: + type: IpV4Address + address: 3.4.5.6 + netmask: 255.255.255.0 + gateway: 3.4.5.1 + broadcast: 3.4.5.255 + +ip_v4_mailserver_nic_one: + type: IpV4Address + address: 172.31.0.15 + netmask: 255.255.255.0 + gateway: 172.31.0.1 + broadcast: 172.31.0.255 + +ip_v4_ldapserver_nic_one: + type: IpV4Address + address: 172.31.0.25 + netmask: 255.255.255.0 + gateway: 172.31.0.1 + broadcast: 172.31.0.255 + +ip_v4_buildserver_nic_two: + type: IpV4Address + address: 172.31.0.31 + netmask: 255.255.255.0 + gateway: 172.31.0.1 + broadcast: 172.31.0.255 + +ip_v4_mailservers_managed_node_bonding: + type: IpV4Address + address: 192.168.50.100 + netmask: 255.255.255.0 + gateway: 192.168.50.1 + broadcast: 192.168.50.255 diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index ccf71d2..dbbc511 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -1,80 +1,80 @@ one: id: 1 mac: '00:11:22:33:44:55' - ip_addr: '1.2.3.4' - usage_type: '1' + ip_address_id: <%= Fixtures.identify(:ip_v4_one) %> bandwidth: 100 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + two: id: 2 mac: 'AA:BB:CC:DD:EE:FF' - ip_addr: '2.3.4.5' + ip_address_id: <%= Fixtures.identify(:ip_v4_two) %> usage_type: '2' bandwidth: 1000 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + three: id: 3 mac: '00:FF:11:EE:22:DD' - ip_addr: '3.4.5.6' + ip_address_id: <%= Fixtures.identify(:ip_v4_three) %> usage_type: '1' bandwidth: 10 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + four: id: 4 mac: '00:FF:11:EE:22:DD' - ip_addr: '3.4.5.6' + ip_address: <%= Fixtures.identify(:ip_v4_four) %> usage_type: '1' bandwidth: 10 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> mailserver_nic_one: - mac: '00:11:22:33:44:55' - usage_type: '1' - bandwidth: 100 - ip_addr: '172.31.0.15' - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + mac: '00:11:22:33:44:55' + ip_address: <%= Fixtures.identify(:ip_v4_mailserver_nic_one) %> + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> mailserver_nic_two: - mac: '22:11:33:66:44:55' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + mac: '22:11:33:66:44:55' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> fileserver_nic_one: - mac: '00:99:00:99:13:07' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:fileserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + mac: '00:99:00:99:13:07' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:fileserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> ldapserver_nic_one: - mac: '00:03:02:00:09:06' - usage_type: '1' - bandwidth: 100 - bridge: 'ovirtbr0' - ip_addr: '172.31.0.25' - host_id: <%= Fixtures.identify(:ldapserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + mac: '00:03:02:00:09:06' + usage_type: '1' + bandwidth: 100 + bridge: 'ovirtbr0' + ip_address_id: <%= Fixtures.identify(:ip_v4_ldapserver_nic_one) %> + host_id: <%= Fixtures.identify(:ldapserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> buildserver_nic_one: - mac: '07:17:19:65:03:38' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:buildserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + mac: '07:17:19:65:03:38' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:buildserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> buildserver_nic_two: - mac: '07:17:19:65:03:39' - usage_type: '1' - bandwidth: 100 - ip_addr: '172.31.0.31' - netmask: '255.255.255.0' - broadcast: '172.31.0.255' - host_id: <%= Fixtures.identify(:buildserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + mac: '07:17:19:65:03:39' + usage_type: '1' + bandwidth: 100 + ip_address_id: <%= Fixtures.identify(:ip_v4_buildserver_nic_two) %> + host_id: <%= Fixtures.identify(:buildserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index d0d8aa3..2158c8d 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -83,17 +83,17 @@ cat <<\EOF > /var/tmp/node-augtool 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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes 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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 @@ -123,17 +123,17 @@ cat <<\EOF > /var/tmp/node-augtool 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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes 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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 @@ -177,7 +177,7 @@ EOF cat <<\EOF > /var/tmp/node-augtool rm /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name} set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR 172.31.0.15 +set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_address.address} set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/ONBOOT yes rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 diff --git a/src/test/unit/ip_address_test.rb b/src/test/unit/ip_address_test.rb new file mode 100644 index 0000000..152578e --- /dev/null +++ b/src/test/unit/ip_address_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class IpAddressTest < ActiveSupport::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/src/test/unit/ip_v4_address_test.rb b/src/test/unit/ip_v4_address_test.rb new file mode 100644 index 0000000..14f0175 --- /dev/null +++ b/src/test/unit/ip_v4_address_test.rb @@ -0,0 +1,99 @@ +# +# 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' + +class IpV4AddressTest < ActiveSupport::TestCase + def setup + @address = IpV4Address.new(:address => '192.168.50.2', + :netmask => '255.255.255.0', + :gateway => '192.168.50.1', + :broadcast => '192.168.50.255') + end + + # Ensures that an address must be supplied. + # + def test_valid_fails_without_address + @address.address = nil + + flunk "An address must be present." if @address.valid? + end + + # Ensures that an address has to be in the correct format. + # + def test_valid_fails_with_bad_address + @address.address = '192.168' + + flunk "An address must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a netmask must be supplied. + # + def test_valid_fails_without_netmask + @address.netmask = nil + + flunk "An address must have a netmask." if @address.valid? + end + + # Ensures that a netmask must have the correct format. + # + def test_valid_fails_with_bad_netmask + @address.netmask = 'farkle' + + flunk "A netmask must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a gateway must be supplied. + # + def test_valid_fails_without_gateway + @address.gateway = nil + + flunk "A gateway address must be supplied." if @address.valid? + end + + # Ensures that a gateway must be in the correct format. + # + def test_valid_fails_with_bad_gateway + @address.gateway = '-3.a2.0.8' + + flunk "The gateway address must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a broadcast must be supplied. + # + def test_valid_fails_without_broadcast + @address.broadcast = nil + + flunk "A broadcast addres must be supplied." if @address.valid? + end + + # Ensures that a broadcast must be in the correct format. + # + def test_valid_fails_with_bad_broadcast + @address.broadcast = '-3.2.0.8' + + flunk "The broadcast address must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a well-formed address is valid. + # + def test_valid + flunk "There is an error with validation." unless @address.valid? + end +end diff --git a/src/test/unit/ip_v6_address_test.rb b/src/test/unit/ip_v6_address_test.rb new file mode 100644 index 0000000..a2be85f --- /dev/null +++ b/src/test/unit/ip_v6_address_test.rb @@ -0,0 +1,82 @@ +# +# 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' + +class IpV6AddressTest < ActiveSupport::TestCase + def setup + @address = IpV6Address.new(:address => 'fe80:0:0:0:200:f8ff:fe21:67cf', + :gateway => ':::::::717', + :prefix => '0000:0000:0000:0000:1234:1234:1234:1234') + end + + # Ensures that the address must be provided. + # + def test_valid_fails_without_address + @address.address = nil + + flunk "An address must be provided." if @address.valid? + end + + # Ensures that the address must be in the correct format. + # + def test_valid_fails_with_bad_address + @address.address = "farkle" + + flunk "The address must be in the correct format." if @address.valid? + end + + # Ensures that the gateway must be provided. + # + def test_valid_fails_without_gateway + @address.gateway = nil + + flunk "The gateway address must be provided." if @address.valid? + end + + # Ensures that the gateway address is in the correct format. + # + def test_valid_fails_with_bad_gateway + @address.gateway = '0-:::::::717' + + flunk "The gateway address must be in the correct format." if @address.valid? + end + + # Ensures that the prefix must be provided. + # + def test_valid_fails_without_prefix + @address.prefix = nil + + flunk "The prefix must be provided." if @address.valid? + end + + # Ensures that the prefix is in the correct format. + # + def test_valid_fails_with_invalid_prefix + @address.prefix = 'whoops' + + flunk "The prefix must be in the correct format." if @address.valid? + end + + # Ensures that a well-formed address is considered valid. + # + def test_valid + flunk "There is an problem with address validation." unless @address.valid? + end +end diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb index a0776a2..1de1e00 100644 --- a/src/test/unit/nic_test.rb +++ b/src/test/unit/nic_test.rb @@ -20,6 +20,7 @@ require File.dirname(__FILE__) + '/../test_helper' class NicTest < Test::Unit::TestCase + fixtures :ip_addresses fixtures :nics # Replace this with your real tests. -- 1.5.5.1 From imain at redhat.com Fri Oct 17 21:52:33 2008 From: imain at redhat.com (Ian Main) Date: Fri, 17 Oct 2008 14:52:33 -0700 Subject: [Ovirt-devel] Issue with ISO provisioning In-Reply-To: <48F55135.2050100@redhat.com> References: <48F55135.2050100@redhat.com> Message-ID: <20081017145233.40451ed2@tp.mains.net> On Tue, 14 Oct 2008 22:11:01 -0400 Perry Myers wrote: [snip] > Second problem... > > I tried to provision a second guest using the same ISO image off of the > same NFS share. This failed with the following error in taskomatic: > > libvir: Storage error : no storage vol with matching name > > start_vm > > Task action processing failed: Libvirt::RetrieveError: Call to function virStorageVolLookupByName failed > > /usr/share/ovirt-server/task-omatic/./utils.rb:124:in `lookup_volume_by_name' > > /usr/share/ovirt-server/task-omatic/./utils.rb:124:in `connect_storage_pools' > > /usr/share/ovirt-server/task-omatic/./utils.rb:81:in `each' > > /usr/share/ovirt-server/task-omatic/./utils.rb:81:in `connect_storage_pools' > > /usr/share/ovirt-server/task-omatic/./task_vm.rb:314:in `start_vm' > > /usr/share/ovirt-server/task-omatic/taskomatic.rb:99 > > /usr/share/ovirt-server/task-omatic/taskomatic.rb:88:in `each' > > /usr/share/ovirt-server/task-omatic/taskomatic.rb:88 > > /usr/share/ovirt-server/task-omatic/taskomatic.rb:68:in `loop' > > /usr/share/ovirt-server/task-omatic/taskomatic.rb:68 > > Seems that if the NFS share is previously mounted as a storage pool in > libvirt, it causes taskomatic to fail. If I manually destroy the storage > pool for the NFS mount on the host and undefine it and then try again, the > start_vm succeeds and recreates the storage pool. I've been trying to reproduce this but it seems fine. I even have a cobbler image and a disk image both being used on the same nfs share by the same vm while other vms are using that iso image too. All worked.. ? Ian From dpierce at redhat.com Mon Oct 20 14:44:49 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Mon, 20 Oct 2008 10:44:49 -0400 Subject: [Ovirt-devel] Re: [PATCH server] Introduces a new IP address model. Moves IP addressing to a separate In-Reply-To: <1224277864-13419-1-git-send-email-dpierce@redhat.com> References: <1224277864-13419-1-git-send-email-dpierce@redhat.com> Message-ID: <48FC9961.80507@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Darryl L. Pierce wrote: > You will need to run a migration after this patch to create the > ip_addresses table and massage the bondings and nics tables to > reference it. > > NEW CLASSES: > > IpAddress: base class for addresses > IpV4Address: represents an IPv4 address > IpV6Address: represents an IPv6 address > > Signed-off-by: Darryl L. Pierce > --- Mo, can you review this patch and give ms feedkbac? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj8mV0ACgkQjaT4DmxOfxs7PACg2ZqxdS1eJLzvqK0vzC+6DMWL 79gAnApw0RXsM6UEDaVGLbPT37NHcbMJ =r04J -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From danken at redhat.com Mon Oct 20 15:22:35 2008 From: danken at redhat.com (Dan Kenigsberg) Date: Mon, 20 Oct 2008 17:22:35 +0200 Subject: [Ovirt-devel] make ovirt-node-image.iso w/o its rpm Message-ID: <20081020152233.GA10200@csdan> Hi, I would like to create ovirt-node-image.iso and mount it; wrapping it within an rpm is unhelpful for our needs. Also, moving logic from .spec to Makefile is generally a good thing that shortens dev cycles. Please review the following patch and consider applying something on these lines. I hope this is the place for such requests. Regrads, Dan. commit 5f1ada4cde747eb09714d38067d3f9d20eaaf607 Author: Dan Kenigsberg Date: Mon Oct 20 16:40:55 2008 +0200 Move iso-making code from spec to Makefile Signed-off-by: Dan Kenigsberg diff --git a/Makefile.am b/Makefile.am index 17b26c0..13421af 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,59 @@ RPM_FLAGS += $(if $(FEDORA_URL),--define "fedora_url $(FEDORA_URL)") # OVIRT_URL env var can be set to the root of an ovirt.org mirror RPM_FLAGS += $(if $(OVIRT_URL),--define "ovirt_url $(OVIRT_URL)") +FEDORA=$(shell rpm --eval '%{fedora}') +ARCH=$(shell rpm --eval '%{_arch}') +FEDORA_MIRROR=http://mirrors.fedoraproject.org/mirrorlist +CUR_RAWHIDE=10 +OVIRT_URL=http://ovirt.org/repos/ovirt + +repos.ks: + ( \ + if [ 0$(FEDORA) == 0$(CUR_RAWHIDE) ]; then \ + FEDORA_REPO=rawhide ;\ + FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/development/$(ARCH)/os,--mirrorlist=$(FEDORA_MIRROR)?repo=rawhide&arch=$(ARCH))" ;\ + OVIRT_DISTRO=development ;\ + else \ + FEDORA_REPO=f$(FEDORA) ;\ + FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/${ARCH}/os,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-$(FEDORA)&arch=$(ARCH))" ;\ + OVIRT_DISTRO=$(FEDORA) ;\ + UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates-newkey $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH}.newkey,--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))" ;\ + fi ;\ + echo "repo --name=$${FEDORA_REPO} $${FEDORA_REPO_LOC}" > $@ ;\ + echo "repo --name=ovirt-org --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/$(ARCH)" >> $@ ;\ + echo "$${UPDATE_REPO_LINE}" >> $@ ;\ + echo "repo --name=ovirt-local --baseurl=file://$(OVIRT_CACHE_DIR)/ovirt" >> $@ \ + ) + +SELINUX_ENFORCING=$(shell getenforce) +iso: repos.ks + mkdir -p $(OVIRT_CACHE_DIR)/node-image-tmp + mkdir -p $(OVIRT_CACHE_DIR)/yum + ( \ + case $(SELINUX_ENFORCING) in \ + Enforcing) setenforce Permissive ;; \ + Permissive) ;; \ + *) if ksflatten ovirt-image-node.ks 2>/dev/null \ + | grep -q '^selinux --disabled'; then \ + echo WARNING: SELinux disabled in kickstart ;\ + else \ + echo ERROR: SELinux enabled in kickstart, \ + but disabled on the build machine ;\ + exit 1 ;\ + fi ;; \ + esac ;\ + ) + sudo livecd-creator --skip-minimize -c ovirt-node-image.ks \ + -f ovirt-node-image \ + --tmpdir='$(OVIRT_CACHE_DIR)/node-image-tmp' \ + --cache='$(OVIRT_CACHE_DIR)/yum' + sudo chown $${USER} ovirt-node-image.iso + ( \ + if [ $(SELINUX_ENFORCING) = Enforcing ]; then \ + setenforce Enforcing || exit 1 ;\ + fi \ + ) + rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz diff --git a/ovirt-node-image.spec.in b/ovirt-node-image.spec.in index 6f9ddac..7184c77 100644 --- a/ovirt-node-image.spec.in +++ b/ovirt-node-image.spec.in @@ -1,8 +1,3 @@ -%{!?ovirt_cache_dir: %define ovirt_cache_dir /var/tmp/ovirt-cache} -%{!?ovirt_local_repo: %define ovirt_local_repo file://%{ovirt_cache_dir}/ovirt} -%{!?ovirt_url: %define ovirt_url http://ovirt.org/repos/ovirt} -%define fedora_mirror http://mirrors.fedoraproject.org/mirrorlist - Summary: oVirt Node ISO image Name: ovirt-node-image Version: @VERSION@ @@ -35,66 +30,10 @@ The PXE boot image for oVirt Node network boot from oVirt Server. %prep %setup -q +./configure %build -%if 0%{?fedora} == 010 - # XXX current rawhide - %if "%{?fedora_url}" == "" -cat > repos.ks << EOF -repo --name=rawhide --mirrorlist=%{fedora_mirror}?repo=rawhide&arch=%{_arch} -EOF - %else -cat > repos.ks << EOF -repo --name=rawhide --baseurl=%{fedora_url}/development/%{_arch}/os -EOF - %endif -cat >> repos.ks << EOF -repo --name=ovirt-org --baseurl=%{ovirt_url}/development/%{_arch} -EOF -%else - %if "%{?fedora_url}" == "" -cat > repos.ks << EOF -repo --name=f%{fedora} --mirrorlist=%{fedora_mirror}?repo=fedora-%{fedora}&arch=%{_arch} -repo --name=f%{fedora}-updates-newkey --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}.newkey&arch=%{_arch} -EOF - %else -cat > repos.ks << EOF -repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os -repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey -EOF - %endif -cat >> repos.ks << EOF -repo --name=ovirt-org --baseurl=%{ovirt_url}/%{fedora}/%{_arch} -EOF -%endif - -cat >> repos.ks << EOF -repo --name=ovirt-local --baseurl=%{ovirt_local_repo} -EOF - -mkdir -p %{ovirt_cache_dir}/node-image-tmp -mkdir -p %{ovirt_cache_dir}/yum - -sudo su - -c "cd $(pwd) && - enforcing=\$(getenforce) - case \$enforcing in - Enforcing) setenforce Permissive ;; - Permissive) ;; - *) if ksflatten %{name}.ks 2>/dev/null \ - | grep -q '^selinux --disabled$'; then - echo WARNING: SELinux disabled in kickstart - else - echo ERROR: SELinux enabled in kickstart, \ - but disabled on the build machine - exit 1 - fi ;; - esac - livecd-creator --skip-minimize -c %{name}.ks -f %{name} \ - --tmpdir='%{ovirt_cache_dir}/node-image-tmp' \ - --cache='%{ovirt_cache_dir}/yum' - if [ \$enforcing = Enforcing ]; then - setenforce Enforcing || exit 1 - fi" +make iso sudo su - -c "cd $(pwd) && ./ovirt-pxe %{name}.iso" sudo su - -c "cd $(pwd) && chown -R $USER ." From dpierce at redhat.com Mon Oct 20 17:26:29 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 20 Oct 2008 13:26:29 -0400 Subject: [Ovirt-devel] [PATCH server] Changed the database configuration to use a different database for development Message-ID: <1224523589-10262-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- src/config/database.yml | 21 +++++++++++++++++---- 1 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/config/database.yml b/src/config/database.yml index c60ee8f..497eb26 100644 --- a/src/config/database.yml +++ b/src/config/database.yml @@ -5,16 +5,29 @@ # sudo /sbin/service postgresql initdb # sudo /sbin/service postgresql start # sudo su - postgres -# $ createdb ovirt # $ psql ovirt # $-# CREATE USER ovirt WITH PASSWORD 'v23zj59an'; -# $-# CREATE DATABASE ovirt; -# $-# GRANT ALL PRIVILEGES ON DATABASE ovirt to ovirt; +# $-# CREATE DATABASE ovirt_development; +# $-# GRANT ALL PRIVILEGES ON DATABASE ovirt_development to ovirt; # $-# CREATE DATABASE ovirt_test; # $-# GRANT ALL PRIVILEGES ON DATABASE ovirt_test to ovirt; +# $-# CREATE DATABASE ovirt; +# $-# GRANT ALL PRIVILEGES ON DATABASE ovirt to ovirt; # $-# \q # $ exit # +# To start off a new environment, use the following commands: +# +# rake db:drop:all +# rake db:create:all +# rake db:migrate +# rake db:tests:prepare +# +# And when there are changes to the database, simple run: +# +# rake db:migrate +# rake db:test:prepare +# # On a local system you may need to edit this file to have only the following # /var/lib/pgsql/data/pg_hba.conf # local all all trust @@ -23,7 +36,7 @@ development: adapter: postgresql - database: ovirt + database: ovirt_development username: ovirt password: v23zj59an host: localhost -- 1.5.5.1 From dpierce at redhat.com Mon Oct 20 18:05:48 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 20 Oct 2008 14:05:48 -0400 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. Message-ID: <1224525948-12473-1-git-send-email-dpierce@redhat.com> Rather than sending the node a series of scripts that load kernel modules, or are tightly coupled to tools like augeas, this patch introduces an encoding scheme for data. A line that begins with "kmod" describes a kernel module that needs to be loaded. It will containing the module's name, an optional alias for the module, and then the module options if such are required. A line that begins with "ifcfg" describes an network interface. It will contain the mac address and interface name, followed by all needed configuration values to bring the interface up. Signed-off-by: Darryl L. Pierce --- src/db/migrate/024_add_boot_type_to_bonding.rb | 2 +- .../migrate/025_allow_nic_boot_type_to_be_null.rb | 28 +++++ src/lib/managed_node_configuration.rb | 81 +++++++------ src/test/fixtures/bondings.yml | 10 ++- src/test/fixtures/bondings_nics.yml | 8 ++ src/test/fixtures/hosts.yml | 10 ++ src/test/fixtures/nics.yml | 14 ++ .../functional/managed_node_configuration_test.rb | 127 ++++++-------------- 8 files changed, 152 insertions(+), 128 deletions(-) create mode 100644 src/db/migrate/025_allow_nic_boot_type_to_be_null.rb diff --git a/src/db/migrate/024_add_boot_type_to_bonding.rb b/src/db/migrate/024_add_boot_type_to_bonding.rb index e2cf1c6..17e085f 100644 --- a/src/db/migrate/024_add_boot_type_to_bonding.rb +++ b/src/db/migrate/024_add_boot_type_to_bonding.rb @@ -19,7 +19,7 @@ class AddBootTypeToBonding < ActiveRecord::Migration def self.up - add_column :bondings, :boot_type_id, :integer, :null => false + add_column :bondings, :boot_type_id, :integer, :null => true execute 'alter table bondings add constraint fk_bondings_boot_type foreign key (boot_type_id) references boot_types(id)' diff --git a/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb b/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb new file mode 100644 index 0000000..b09ada1 --- /dev/null +++ b/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb @@ -0,0 +1,28 @@ +# +# 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. + +class AllowNicBootTypeToBeNull < ActiveRecord::Migration + def self.up + change_column :nics, :boot_type_id, :integer, :null => true + end + + def self.down + change_column :nics, :boot_type_id, :integer, :null => false + end +end diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb index 93f8448..101be9f 100644 --- a/src/lib/managed_node_configuration.rb +++ b/src/lib/managed_node_configuration.rb @@ -23,36 +23,57 @@ require 'stringio' +# +ManagedNodeConfiguration+ generates a configuration file for an oVirt node +# based on information about the hardware submitted by the node and the +# configuration details held in the database. +# +# The configuration is returned as a series of encoded lines. +# +# For a kernel module, the formation of the line is as follows: +# +# bonding=[bonding alias] +# +# An example would be for loading the +bonding+ kernel module to setup a bonded +# interface for load balancing. In this example, the bonded interface would be +# named +failover0+ on the node: +# +# bonding=failover0 +# +# For a network interface (including a bonded interface) an example would be: +# +# ifcfg=00:11:22:33:44|eth0|BOOTPROTO=dhcp|bridge=ovirtbr0|ONBOOT=yes +# +# In this line, the network interface +eth0+ has a hardware MAC address of +# +00:11:22:33:44+. It will use DHCP for retrieving it's IP address details, +# and will use the +ovirtbr0+ interface as a bridge. +# class ManagedNodeConfiguration NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' def self.generate(host, macs) result = StringIO.new - result.puts "#!/bin/bash" result.puts "# THIS FILE IS GENERATED!" # first process any bondings that're defined unless host.bondings.empty? - result.puts "cat <<\EOF > /var/tmp/pre-config-script" - result.puts "#!/bin/bash" - result.puts "# THIS FILE IS GENERATED!" - host.bondings.each do |bonding| - result.puts "/sbin/modprobe bonding mode=#{bonding.bonding_type.mode}" + result.puts "bonding=#{bonding.interface_name}" end - - result.puts "EOF" end # now process the network interfaces and bondings - result.puts "cat <<\EOF > /var/tmp/node-augtool" host.bondings.each do |bonding| - result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_addr}" if bonding.ip_addr - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/ONBOOT yes" + entry = "ifcfg=none|#{bonding.interface_name}|BONDING_OPTS=\"mode=#{bonding.bonding_type.mode} miimon=100\"" + + if bonding.ip_addr == nil || bonding.ip_addr.empty? + entry += "|BOOTPROTO=dhcp" + else + entry += "|BOOTPROTO=static|IPADDR=#{bonding.ip_addr}|NETMASK=#{bonding.netmask}|BROADCAST=#{bonding.broadcast}" + end + + result.puts "#{entry}|ONBOOT=yes" bonding.nics.each do |nic| process_nic result, nic, macs, bonding @@ -75,9 +96,6 @@ class ManagedNodeConfiguration end end - result.puts "save" - result.puts "EOF" - result.string end @@ -87,31 +105,20 @@ class ManagedNodeConfiguration 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}" + entry = "ifcfg=#{nic.mac}|#{iface_name}" if bonding - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/MASTER #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/SLAVE yes" + entry += "|MASTER=#{bonding.interface_name}|SLAVE=yes" else - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO #{nic.boot_type.proto}" - - if nic.boot_type.proto == 'static' - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" - end - - if bridged - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE ovirtbr0" - elsif is_bridge - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/TYPE bridge" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/PEERNTP yes" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DELAY 0" - end + entry += "|BOOTPROTO=#{nic.boot_type.proto}" + entry += "|IPADDR=#{nic.ip_addr}|NETMASK=#{nic.netmask}|BROADCAST=#{nic.broadcast}" if nic.boot_type.proto == 'static' + entry += "|BRIDGE=#{nic.bridge}" if nic.bridge && !is_bridge + entry += "|BRIDGE=ovirtbr0" if !nic.bridge && !is_bridge + entry += "|TYPE=bridge" if is_bridge end - - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/ONBOOT yes" + entry += "|ONBOOT=yes" end + + result.puts entry if defined? entry end end diff --git a/src/test/fixtures/bondings.yml b/src/test/fixtures/bondings.yml index 29473c7..227b92b 100644 --- a/src/test/fixtures/bondings.yml +++ b/src/test/fixtures/bondings.yml @@ -1,11 +1,17 @@ mailservers_managed_node_bonding: name: Production Network - interface_name: bond0 + interface_name: mailbonding0 bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> ip_addr: 172.31.0.15 netmask: 255.255.255. broadcast: 172.31.0.255 arp_ping_address: 172.31.0.100 arp_interval: 0 + +mediaserver_managed_node_bonding: + name: Fileserver Network + interface_name: mediabonding0 + bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> diff --git a/src/test/fixtures/bondings_nics.yml b/src/test/fixtures/bondings_nics.yml index 11a3d1a..607ff8b 100644 --- a/src/test/fixtures/bondings_nics.yml +++ b/src/test/fixtures/bondings_nics.yml @@ -5,3 +5,11 @@ mailservers_managed_node_bonding_nic_1: mailservers_managed_node_bonding_nic_2: bonding_id: <%= Fixtures.identify(:mailservers_managed_node_bonding) %> nic_id: <%= Fixtures.identify(:mailserver_nic_two) %> + +mediaservers_managed_node_bonding_nic_1: + bonding_id: <%= Fixtures.identify(:mediaserver_managed_node_bonding) %> + nic_id: <%= Fixtures.identify(:mediaserver_nic_one) %> + +mediaservers_managed_node_bonding_nic_2: + bonding_id: <%= Fixtures.identify(:mediaserver_managed_node_bonding) %> + nic_id: <%= Fixtures.identify(:mediaserver_nic_two) %> diff --git a/src/test/fixtures/hosts.yml b/src/test/fixtures/hosts.yml index 5b8af15..28282c2 100644 --- a/src/test/fixtures/hosts.yml +++ b/src/test/fixtures/hosts.yml @@ -125,3 +125,13 @@ buildserver_managed_node: is_disabled: 0 hypervisor_type: 'kvm' hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + +mediaserver_managed_node: + uuid: '6293acd9-2784-11dc-9387-001558c41534' + hostname: 'build.mynetwork.com' + arch: 'x86_64' + memory: 65536 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index ccf71d2..5b2cecc 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -78,3 +78,17 @@ buildserver_nic_two: broadcast: '172.31.0.255' host_id: <%= Fixtures.identify(:buildserver_managed_node) %> boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + +mediaserver_nic_one: + mac: '07:17:19:65:03:32' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + +mediaserver_nic_two: + mac: '07:17:19:65:03:31' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index d0d8aa3..14c9736 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -36,6 +36,7 @@ class ManagedNodeConfigurationTest < Test::Unit::TestCase @host_with_ip_address = hosts(:ldapserver_managed_node) @host_with_multiple_nics = hosts(:buildserver_managed_node) @host_with_bondings = hosts(:mailservers_managed_node) + @host_with_dhcp_bondings = hosts(:mediaserver_managed_node) end # Ensures that network interfaces uses DHCP when no IP address is specified. @@ -44,23 +45,9 @@ class ManagedNodeConfigurationTest < Test::Unit::TestCase nic = @host_with_dhcp_card.nics.first expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes -save -EOF +ifcfg=#{nic.mac}|eth0|BOOTPROTO=dhcp|BRIDGE=ovirtbr0|ONBOOT=yes +ifcfg=#{nic.mac}|ovirtbr0|BOOTPROTO=dhcp|TYPE=bridge|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -77,29 +64,9 @@ EOF nic = @host_with_ip_address.nics.first expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes -save -EOF +ifcfg=#{nic.mac}|eth0|BOOTPROTO=#{nic.boot_type.proto}|IPADDR=#{nic.ip_addr}|NETMASK=#{nic.netmask}|BROADCAST=#{nic.broadcast}|BRIDGE=#{nic.bridge}|ONBOOT=yes +ifcfg=#{nic.mac}|ovirtbr0|BOOTPROTO=#{nic.boot_type.proto}|IPADDR=#{nic.ip_addr}|NETMASK=|BROADCAST=#{nic.netmask}|TYPE=bridge|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -117,34 +84,10 @@ EOF nic2 = @host_with_multiple_nics.nics[1] expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes -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/BOOTPROTO #{nic2.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes -save -EOF +ifcfg=#{nic1.mac}|eth0|BOOTPROTO=#{nic1.boot_type.proto}|IPADDR=#{nic1.ip_addr}|NETMASK=#{nic1.netmask}|BROADCAST=#{nic1.broadcast}|BRIDGE=ovirtbr0|ONBOOT=yes +ifcfg=#{nic1.mac}|ovirtbr0|BOOTPROTO=#{nic1.boot_type.proto}|IPADDR=#{nic1.ip_addr}|NETMASK=#{nic1.netmask}|BROADCAST=#{nic1.broadcast}|TYPE=bridge|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|BOOTPROTO=#{nic2.boot_type.proto}|BRIDGE=ovirtbr0|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -167,30 +110,11 @@ EOF nic2 = bonding.nics[1] expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/pre-config-script -#!/bin/bash -# THIS FILE IS GENERATED! -/sbin/modprobe bonding mode=#{bonding.bonding_type.mode} -EOF -cat <<\EOF > /var/tmp/node-augtool -rm /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR 172.31.0.15 -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/ONBOOT yes -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/MASTER #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/SLAVE yes -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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/MASTER #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/SLAVE yes -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes -save -EOF +bonding=#{bonding.interface_name} +ifcfg=none|#{bonding.interface_name}|BONDING_OPTS="mode=#{bonding.bonding_type.mode} miimon=100"|BOOTPROTO=static|IPADDR=#{bonding.ip_addr}|NETMASK=#{bonding.netmask}|BROADCAST=#{bonding.broadcast}|ONBOOT=yes +ifcfg=#{nic1.mac}|eth0|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -203,4 +127,31 @@ HERE assert_equal expected, result end + # Ensures that the generated bonding supports DHCP boot protocol. + # + def test_generate_with_dhcp_bonding + bonding = @host_with_dhcp_bondings.bondings.first + + bonding.ip_addr=nil + nic1 = bonding.nics[0] + nic2 = bonding.nics[1] + + expected = <<-HERE +# THIS FILE IS GENERATED! +bonding=#{bonding.interface_name} +ifcfg=none|#{bonding.interface_name}|BONDING_OPTS="mode=#{bonding.bonding_type.mode} miimon=100"|BOOTPROTO=dhcp|ONBOOT=yes +ifcfg=#{nic1.mac}|eth0|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +HERE + + result = ManagedNodeConfiguration.generate( + @host_with_dhcp_bondings, + { + "#{nic1.mac}" => 'eth0', + "#{nic2.mac}" => 'eth1' + }) + + assert_equal expected, result + end + end -- 1.5.5.1 From dpierce at redhat.com Mon Oct 20 18:10:14 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 20 Oct 2008 14:10:14 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. Message-ID: <1224526214-12727-1-git-send-email-dpierce@redhat.com> The system now takes an encoded configuration descriptor from the server. It then parses from that a set of aliases for bondings if such exist. It then also extracts configuration details for the various network interfaces on the node. Afterward, it reloads module dependencies and then restarts the networking service. This patch also includes updated rdocs for the configuration generator. Signed-off-by: Darryl L. Pierce --- Makefile.am | 1 + ovirt-node.spec.in | 2 + scripts/ovirt-early | 14 +++----- scripts/ovirt-process-config | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100755 scripts/ovirt-process-config diff --git a/Makefile.am b/Makefile.am index 1d63310..c55db13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ scripts/ovirt-early \ scripts/ovirt-functions \ scripts/ovirt-post \ + scripts/ovirt-process-config \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index c5c836b..912192e 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -87,6 +87,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake @@ -158,6 +159,7 @@ fi %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake +%{_sbindir}/ovirt-process-config %{_sbindir}/ovirt-identify-node %{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 4723426..3c8c494 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -12,6 +12,8 @@ # size of the oVirt partition in megabytes OVIRT_SIZE=64 +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding +AUGTOOL_CONFIG=/var/tmp/augtool-config get_mac_addresses() { macs=$(ifconfig | awk '/HWaddr/ { print $5"="$1 }' \ @@ -43,16 +45,10 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/managed_node/config?host=$(hostname)&macs=$macs" if [ $? -eq 0 ]; then echo "Remote configuration bundle retrieved to $cfgdb" - bash $cfgdb - if [ -f /var/tmp/pre-config-script ]; then - echo "Loading kernel modules" - bash /var/tmp/pre-config-script \ - && echo "Kernel modules loaded" \ - || echo "Failed loading kernel modules" - fi - if [ -f /var/tmp/node-augtool ]; then + ovirt-process-config $cfgdb $BONDING_MODCONF_FILE $AUGTOOL_CONFIG + if [ -f $AUGTOOL_CONFIG ]; then echo "Loading remote config" - augtool < /var/tmp/node-augtool \ + augtool < $AUGTOOL_CONFIG \ && echo "Remote config applied" \ || echo "Failed applying remote config" fi diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config new file mode 100755 index 0000000..9af0822 --- /dev/null +++ b/scripts/ovirt-process-config @@ -0,0 +1,68 @@ +#!/bin/bash + +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} + +case $# in + 0|1|2) warn "too few arguments"; try_help;; + 3) ;; + *) warn "too many arguments"; try_help;; +esac + +CONFIG=$1 +OVIRT_KERNEL_MODULE_FILE=$2 +OVIRT_CONFIG_OUTPUT_FILE=$3 + +modconf=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + split(data[1], mod, "|") + + if (match("[^[:alnum:]=_ at -]", mod[1]) >= 0) { + printf "invalid bonding alias: \"%s\"\n", mod[1]; + exit 1; + } + + alias=mod[1] + + printf("install %s bonding", alias) + }' $CONFIG) + +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE + +# now build the list of module aliases to load and load them +modules=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + split(data[1], mod, "|") + + printf("%s ", mod[1]) + }' $CONFIG) + +networking=$(awk '/ifcfg=/ { + match($0, "^[ \t]*ifcfg=(.*)", data) + split(data[1], ifcfg, "|") + + mac = ifcfg[1] + iface = ifcfg[2] + ifcfg_dir = "/files/etc/sysconfig/network-scripts" + + printf("rm %s/ifcfg-%s\n", ifcfg_dir, iface) + printf("set %s/ifcfg-%s/DEVICE %s\n", ifcfg_dir, iface, iface) + + for (line in ifcfg) { + if(line > 2) { + match(ifcfg[line], "(^[^=]+)=(.*)", values) + field=values[1] + value=values[2] + + printf("set %s/ifcfg-%s/%s %s\n", ifcfg_dir, iface, field, value) + } + } + + + printf("save\n") + +}' $CONFIG) + +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE -- 1.5.5.1 From sseago at redhat.com Mon Oct 20 18:17:28 2008 From: sseago at redhat.com (Scott Seago) Date: Mon, 20 Oct 2008 14:17:28 -0400 Subject: [Ovirt-devel] Re: [PATCH] initial model work for lvm storage (revised2) In-Reply-To: <1224184470-7912-1-git-send-email-sseago@redhat.com> References: <1224184470-7912-1-git-send-email-sseago@redhat.com> Message-ID: <48FCCB38.5080400@redhat.com> Scott Seago wrote: > This includes the model classes w/ migrations for the lvm storage pools and volumes, and some changes required to support Storage Volume tasks. This patch does not include all of the necessary model API methods to support lvm, but it's a starting point -- some changes to clalance's taskomatic bits will be required to support this, and we will need additional model enhancements when the UI work is done, and when the taskomatic back end is finalized. > > revised to fix a couple controller bugs and to increment the migration version # > and to fix some additional model problems in taskomatic and host-status > > Signed-off-by: Scott Seago > --- > src/app/controllers/host_controller.rb | 8 +- > src/app/controllers/storage_controller.rb | 35 ++----- > src/app/controllers/vm_controller.rb | 34 +++--- > src/app/models/host.rb | 2 +- > src/app/models/host_task.rb | 11 ++- > src/app/models/iscsi_storage_pool.rb | 2 +- > .../models/{host_task.rb => lvm_storage_pool.rb} | 30 ++++-- > .../{nfs_storage_pool.rb => lvm_storage_volume.rb} | 12 +-- > src/app/models/nfs_storage_pool.rb | 2 +- > src/app/models/storage_pool.rb | 12 ++- > src/app/models/storage_task.rb | 11 ++- > src/app/models/storage_volume.rb | 15 +++- > .../{host_task.rb => storage_volume_task.rb} | 22 +++- > src/app/models/task.rb | 15 ++- > src/app/models/vm.rb | 12 +- > src/app/models/vm_task.rb | 15 ++- > src/db/migrate/025_add_lvm_storage.rb | 106 ++++++++++++++++++++ > src/dutils/active_record_env.rb | 2 + > src/host-status/host-status.rb | 5 +- > src/task-omatic/task_host.rb | 2 +- > src/task-omatic/task_storage.rb | 4 +- > src/task-omatic/task_vm.rb | 48 +++------ > src/test/fixtures/tasks.yml | 32 ++++-- > 23 files changed, 296 insertions(+), 141 deletions(-) > copy src/app/models/{host_task.rb => lvm_storage_pool.rb} (62%) > copy src/app/models/{nfs_storage_pool.rb => lvm_storage_volume.rb} (82%) > copy src/app/models/{host_task.rb => storage_volume_task.rb} (69%) > create mode 100644 src/db/migrate/025_add_lvm_storage.rb > I pushed this today. The only thing lacking for the ACK from clalance was further testing after I fixed the problems found earlier. I fixed these and tested creating storage pools as well as creating/stopping/starting VMs. Scott From dpierce at redhat.com Mon Oct 20 18:25:21 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 20 Oct 2008 14:25:21 -0400 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. Message-ID: <1224527121-13802-1-git-send-email-dpierce@redhat.com> Rather than sending the node a series of scripts that load kernel modules, or are tightly coupled to tools like augeas, this patch introduces an encoding scheme for data. A line that begins with "bonding" describes a bonded interface. >From it the configuration processor generates a simple bonding file that includes an alias for the bonding kernel module A line that begins with "ifcfg" describes an network interface. It will contain the mac address and interface name, followed by all needed configuration values to bring the interface up. THIS PATCH FIXES THE ABOVE CHECKIN MESSAGE. --- src/db/migrate/024_add_boot_type_to_bonding.rb | 2 +- .../migrate/025_allow_nic_boot_type_to_be_null.rb | 28 +++++ src/lib/managed_node_configuration.rb | 81 +++++++------ src/test/fixtures/bondings.yml | 10 ++- src/test/fixtures/bondings_nics.yml | 8 ++ src/test/fixtures/hosts.yml | 10 ++ src/test/fixtures/nics.yml | 14 ++ .../functional/managed_node_configuration_test.rb | 127 ++++++-------------- 8 files changed, 152 insertions(+), 128 deletions(-) create mode 100644 src/db/migrate/025_allow_nic_boot_type_to_be_null.rb diff --git a/src/db/migrate/024_add_boot_type_to_bonding.rb b/src/db/migrate/024_add_boot_type_to_bonding.rb index e2cf1c6..17e085f 100644 --- a/src/db/migrate/024_add_boot_type_to_bonding.rb +++ b/src/db/migrate/024_add_boot_type_to_bonding.rb @@ -19,7 +19,7 @@ class AddBootTypeToBonding < ActiveRecord::Migration def self.up - add_column :bondings, :boot_type_id, :integer, :null => false + add_column :bondings, :boot_type_id, :integer, :null => true execute 'alter table bondings add constraint fk_bondings_boot_type foreign key (boot_type_id) references boot_types(id)' diff --git a/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb b/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb new file mode 100644 index 0000000..b09ada1 --- /dev/null +++ b/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb @@ -0,0 +1,28 @@ +# +# 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. + +class AllowNicBootTypeToBeNull < ActiveRecord::Migration + def self.up + change_column :nics, :boot_type_id, :integer, :null => true + end + + def self.down + change_column :nics, :boot_type_id, :integer, :null => false + end +end diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb index 93f8448..101be9f 100644 --- a/src/lib/managed_node_configuration.rb +++ b/src/lib/managed_node_configuration.rb @@ -23,36 +23,57 @@ require 'stringio' +# +ManagedNodeConfiguration+ generates a configuration file for an oVirt node +# based on information about the hardware submitted by the node and the +# configuration details held in the database. +# +# The configuration is returned as a series of encoded lines. +# +# For a kernel module, the formation of the line is as follows: +# +# bonding=[bonding alias] +# +# An example would be for loading the +bonding+ kernel module to setup a bonded +# interface for load balancing. In this example, the bonded interface would be +# named +failover0+ on the node: +# +# bonding=failover0 +# +# For a network interface (including a bonded interface) an example would be: +# +# ifcfg=00:11:22:33:44|eth0|BOOTPROTO=dhcp|bridge=ovirtbr0|ONBOOT=yes +# +# In this line, the network interface +eth0+ has a hardware MAC address of +# +00:11:22:33:44+. It will use DHCP for retrieving it's IP address details, +# and will use the +ovirtbr0+ interface as a bridge. +# class ManagedNodeConfiguration NIC_ENTRY_PREFIX='/files/etc/sysconfig/network-scripts' def self.generate(host, macs) result = StringIO.new - result.puts "#!/bin/bash" result.puts "# THIS FILE IS GENERATED!" # first process any bondings that're defined unless host.bondings.empty? - result.puts "cat <<\EOF > /var/tmp/pre-config-script" - result.puts "#!/bin/bash" - result.puts "# THIS FILE IS GENERATED!" - host.bondings.each do |bonding| - result.puts "/sbin/modprobe bonding mode=#{bonding.bonding_type.mode}" + result.puts "bonding=#{bonding.interface_name}" end - - result.puts "EOF" end # now process the network interfaces and bondings - result.puts "cat <<\EOF > /var/tmp/node-augtool" host.bondings.each do |bonding| - result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_addr}" if bonding.ip_addr - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/ONBOOT yes" + entry = "ifcfg=none|#{bonding.interface_name}|BONDING_OPTS=\"mode=#{bonding.bonding_type.mode} miimon=100\"" + + if bonding.ip_addr == nil || bonding.ip_addr.empty? + entry += "|BOOTPROTO=dhcp" + else + entry += "|BOOTPROTO=static|IPADDR=#{bonding.ip_addr}|NETMASK=#{bonding.netmask}|BROADCAST=#{bonding.broadcast}" + end + + result.puts "#{entry}|ONBOOT=yes" bonding.nics.each do |nic| process_nic result, nic, macs, bonding @@ -75,9 +96,6 @@ class ManagedNodeConfiguration end end - result.puts "save" - result.puts "EOF" - result.string end @@ -87,31 +105,20 @@ class ManagedNodeConfiguration 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}" + entry = "ifcfg=#{nic.mac}|#{iface_name}" if bonding - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/MASTER #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/SLAVE yes" + entry += "|MASTER=#{bonding.interface_name}|SLAVE=yes" else - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO #{nic.boot_type.proto}" - - if nic.boot_type.proto == 'static' - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" - end - - if bridged - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BRIDGE ovirtbr0" - elsif is_bridge - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/TYPE bridge" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/PEERNTP yes" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/DELAY 0" - end + entry += "|BOOTPROTO=#{nic.boot_type.proto}" + entry += "|IPADDR=#{nic.ip_addr}|NETMASK=#{nic.netmask}|BROADCAST=#{nic.broadcast}" if nic.boot_type.proto == 'static' + entry += "|BRIDGE=#{nic.bridge}" if nic.bridge && !is_bridge + entry += "|BRIDGE=ovirtbr0" if !nic.bridge && !is_bridge + entry += "|TYPE=bridge" if is_bridge end - - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/ONBOOT yes" + entry += "|ONBOOT=yes" end + + result.puts entry if defined? entry end end diff --git a/src/test/fixtures/bondings.yml b/src/test/fixtures/bondings.yml index 29473c7..227b92b 100644 --- a/src/test/fixtures/bondings.yml +++ b/src/test/fixtures/bondings.yml @@ -1,11 +1,17 @@ mailservers_managed_node_bonding: name: Production Network - interface_name: bond0 + interface_name: mailbonding0 bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> ip_addr: 172.31.0.15 netmask: 255.255.255. broadcast: 172.31.0.255 arp_ping_address: 172.31.0.100 arp_interval: 0 + +mediaserver_managed_node_bonding: + name: Fileserver Network + interface_name: mediabonding0 + bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> diff --git a/src/test/fixtures/bondings_nics.yml b/src/test/fixtures/bondings_nics.yml index 11a3d1a..607ff8b 100644 --- a/src/test/fixtures/bondings_nics.yml +++ b/src/test/fixtures/bondings_nics.yml @@ -5,3 +5,11 @@ mailservers_managed_node_bonding_nic_1: mailservers_managed_node_bonding_nic_2: bonding_id: <%= Fixtures.identify(:mailservers_managed_node_bonding) %> nic_id: <%= Fixtures.identify(:mailserver_nic_two) %> + +mediaservers_managed_node_bonding_nic_1: + bonding_id: <%= Fixtures.identify(:mediaserver_managed_node_bonding) %> + nic_id: <%= Fixtures.identify(:mediaserver_nic_one) %> + +mediaservers_managed_node_bonding_nic_2: + bonding_id: <%= Fixtures.identify(:mediaserver_managed_node_bonding) %> + nic_id: <%= Fixtures.identify(:mediaserver_nic_two) %> diff --git a/src/test/fixtures/hosts.yml b/src/test/fixtures/hosts.yml index 5b8af15..28282c2 100644 --- a/src/test/fixtures/hosts.yml +++ b/src/test/fixtures/hosts.yml @@ -125,3 +125,13 @@ buildserver_managed_node: is_disabled: 0 hypervisor_type: 'kvm' hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + +mediaserver_managed_node: + uuid: '6293acd9-2784-11dc-9387-001558c41534' + hostname: 'build.mynetwork.com' + arch: 'x86_64' + memory: 65536 + is_disabled: 0 + hypervisor_type: 'kvm' + hardware_pool_id: <%= Fixtures.identify(:prodops_pool) %> + diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index ccf71d2..5b2cecc 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -78,3 +78,17 @@ buildserver_nic_two: broadcast: '172.31.0.255' host_id: <%= Fixtures.identify(:buildserver_managed_node) %> boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + +mediaserver_nic_one: + mac: '07:17:19:65:03:32' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + +mediaserver_nic_two: + mac: '07:17:19:65:03:31' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mediaserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index d0d8aa3..14c9736 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -36,6 +36,7 @@ class ManagedNodeConfigurationTest < Test::Unit::TestCase @host_with_ip_address = hosts(:ldapserver_managed_node) @host_with_multiple_nics = hosts(:buildserver_managed_node) @host_with_bondings = hosts(:mailservers_managed_node) + @host_with_dhcp_bondings = hosts(:mediaserver_managed_node) end # Ensures that network interfaces uses DHCP when no IP address is specified. @@ -44,23 +45,9 @@ class ManagedNodeConfigurationTest < Test::Unit::TestCase nic = @host_with_dhcp_card.nics.first expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes -save -EOF +ifcfg=#{nic.mac}|eth0|BOOTPROTO=dhcp|BRIDGE=ovirtbr0|ONBOOT=yes +ifcfg=#{nic.mac}|ovirtbr0|BOOTPROTO=dhcp|TYPE=bridge|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -77,29 +64,9 @@ EOF nic = @host_with_ip_address.nics.first expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes -save -EOF +ifcfg=#{nic.mac}|eth0|BOOTPROTO=#{nic.boot_type.proto}|IPADDR=#{nic.ip_addr}|NETMASK=#{nic.netmask}|BROADCAST=#{nic.broadcast}|BRIDGE=#{nic.bridge}|ONBOOT=yes +ifcfg=#{nic.mac}|ovirtbr0|BOOTPROTO=#{nic.boot_type.proto}|IPADDR=#{nic.ip_addr}|NETMASK=|BROADCAST=#{nic.netmask}|TYPE=bridge|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -117,34 +84,10 @@ EOF nic2 = @host_with_multiple_nics.nics[1] expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/node-augtool -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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.broadcast} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/ONBOOT yes -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/BOOTPROTO #{nic2.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/BRIDGE ovirtbr0 -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes -save -EOF +ifcfg=#{nic1.mac}|eth0|BOOTPROTO=#{nic1.boot_type.proto}|IPADDR=#{nic1.ip_addr}|NETMASK=#{nic1.netmask}|BROADCAST=#{nic1.broadcast}|BRIDGE=ovirtbr0|ONBOOT=yes +ifcfg=#{nic1.mac}|ovirtbr0|BOOTPROTO=#{nic1.boot_type.proto}|IPADDR=#{nic1.ip_addr}|NETMASK=#{nic1.netmask}|BROADCAST=#{nic1.broadcast}|TYPE=bridge|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|BOOTPROTO=#{nic2.boot_type.proto}|BRIDGE=ovirtbr0|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -167,30 +110,11 @@ EOF nic2 = bonding.nics[1] expected = <<-HERE -#!/bin/bash # THIS FILE IS GENERATED! -cat <<\EOF > /var/tmp/pre-config-script -#!/bin/bash -# THIS FILE IS GENERATED! -/sbin/modprobe bonding mode=#{bonding.bonding_type.mode} -EOF -cat <<\EOF > /var/tmp/node-augtool -rm /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR 172.31.0.15 -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/ONBOOT yes -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/MASTER #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/SLAVE yes -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes -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/MASTER #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/SLAVE yes -set /files/etc/sysconfig/network-scripts/ifcfg-eth1/ONBOOT yes -save -EOF +bonding=#{bonding.interface_name} +ifcfg=none|#{bonding.interface_name}|BONDING_OPTS="mode=#{bonding.bonding_type.mode} miimon=100"|BOOTPROTO=static|IPADDR=#{bonding.ip_addr}|NETMASK=#{bonding.netmask}|BROADCAST=#{bonding.broadcast}|ONBOOT=yes +ifcfg=#{nic1.mac}|eth0|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes HERE result = ManagedNodeConfiguration.generate( @@ -203,4 +127,31 @@ HERE assert_equal expected, result end + # Ensures that the generated bonding supports DHCP boot protocol. + # + def test_generate_with_dhcp_bonding + bonding = @host_with_dhcp_bondings.bondings.first + + bonding.ip_addr=nil + nic1 = bonding.nics[0] + nic2 = bonding.nics[1] + + expected = <<-HERE +# THIS FILE IS GENERATED! +bonding=#{bonding.interface_name} +ifcfg=none|#{bonding.interface_name}|BONDING_OPTS="mode=#{bonding.bonding_type.mode} miimon=100"|BOOTPROTO=dhcp|ONBOOT=yes +ifcfg=#{nic1.mac}|eth0|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +ifcfg=#{nic2.mac}|eth1|MASTER=#{bonding.interface_name}|SLAVE=yes|ONBOOT=yes +HERE + + result = ManagedNodeConfiguration.generate( + @host_with_dhcp_bondings, + { + "#{nic1.mac}" => 'eth0', + "#{nic2.mac}" => 'eth1' + }) + + assert_equal expected, result + end + end -- 1.5.5.1 From dpierce at redhat.com Mon Oct 20 18:26:10 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 20 Oct 2008 14:26:10 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. Message-ID: <1224527170-13867-1-git-send-email-dpierce@redhat.com> The system now takes an encoded configuration descriptor from the server. It then parses from that a set of aliases for bondings if such exist. It then also extracts configuration details for the various network interfaces on the node. Afterward, it reloads module dependencies and then restarts the networking service. This patch also includes updated rdocs for the configuration generator. --- Makefile.am | 1 + ovirt-node.spec.in | 2 + scripts/ovirt-early | 14 +++------ scripts/ovirt-process-config | 59 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) create mode 100755 scripts/ovirt-process-config diff --git a/Makefile.am b/Makefile.am index 1d63310..c55db13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ scripts/ovirt-early \ scripts/ovirt-functions \ scripts/ovirt-post \ + scripts/ovirt-process-config \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index c5c836b..912192e 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -87,6 +87,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake @@ -158,6 +159,7 @@ fi %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake +%{_sbindir}/ovirt-process-config %{_sbindir}/ovirt-identify-node %{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 4723426..3c8c494 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -12,6 +12,8 @@ # size of the oVirt partition in megabytes OVIRT_SIZE=64 +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding +AUGTOOL_CONFIG=/var/tmp/augtool-config get_mac_addresses() { macs=$(ifconfig | awk '/HWaddr/ { print $5"="$1 }' \ @@ -43,16 +45,10 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/managed_node/config?host=$(hostname)&macs=$macs" if [ $? -eq 0 ]; then echo "Remote configuration bundle retrieved to $cfgdb" - bash $cfgdb - if [ -f /var/tmp/pre-config-script ]; then - echo "Loading kernel modules" - bash /var/tmp/pre-config-script \ - && echo "Kernel modules loaded" \ - || echo "Failed loading kernel modules" - fi - if [ -f /var/tmp/node-augtool ]; then + ovirt-process-config $cfgdb $BONDING_MODCONF_FILE $AUGTOOL_CONFIG + if [ -f $AUGTOOL_CONFIG ]; then echo "Loading remote config" - augtool < /var/tmp/node-augtool \ + augtool < $AUGTOOL_CONFIG \ && echo "Remote config applied" \ || echo "Failed applying remote config" fi diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config new file mode 100755 index 0000000..83a3eb3 --- /dev/null +++ b/scripts/ovirt-process-config @@ -0,0 +1,59 @@ +#!/bin/bash + +ME=$(basename "$0") +warn() { printf "$ME: $@\n" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} + +case $# in + 0|1|2) warn "too few arguments"; try_help;; + 3) ;; + *) warn "too many arguments"; try_help;; +esac + +CONFIG=$1 +OVIRT_KERNEL_MODULE_FILE=$2 +OVIRT_CONFIG_OUTPUT_FILE=$3 + +modconf=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + + if (match("[^[:alnum:]=_ at -]", data[1]) >= 0) { + printf "invalid bonding alias: \"%s\"\n", data[1]; + exit 1; + } + + alias=data[1] + + printf("install %s bonding", alias) + }' $CONFIG) + +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE + +networking=$(awk '/ifcfg=/ { + match($0, "^[ \t]*ifcfg=(.*)", data) + split(data[1], ifcfg, "|") + + mac = ifcfg[1] + iface = ifcfg[2] + ifcfg_dir = "/files/etc/sysconfig/network-scripts" + + printf("rm %s/ifcfg-%s\n", ifcfg_dir, iface) + printf("set %s/ifcfg-%s/DEVICE %s\n", ifcfg_dir, iface, iface) + + for (line in ifcfg) { + if(line > 2) { + match(ifcfg[line], "(^[^=]+)=(.*)", values) + field=values[1] + value=values[2] + + printf("set %s/ifcfg-%s/%s %s\n", ifcfg_dir, iface, field, value) + } + } + + + printf("save\n") + +}' $CONFIG) + +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE -- 1.5.5.1 From dpierce at redhat.com Mon Oct 20 19:09:59 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 20 Oct 2008 15:09:59 -0400 Subject: [Ovirt-devel] [PATCH server] Changed the database configuration to use a different database for development Message-ID: <1224529799-16294-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- scripts/ovirt-server-install | 2 +- src/config/database.yml | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/scripts/ovirt-server-install b/scripts/ovirt-server-install index cd921f7..055ff4c 100755 --- a/scripts/ovirt-server-install +++ b/scripts/ovirt-server-install @@ -186,7 +186,7 @@ touch $EXISTS_FILE cd ${OVIRT_DIR} mkdir -p log -rake db:migrate +export RAILS_ENV="production" && rake db:migrate cd - ${OVIRT_DIR}/script/grant_admin_privileges ovirtadmin diff --git a/src/config/database.yml b/src/config/database.yml index c60ee8f..497eb26 100644 --- a/src/config/database.yml +++ b/src/config/database.yml @@ -5,16 +5,29 @@ # sudo /sbin/service postgresql initdb # sudo /sbin/service postgresql start # sudo su - postgres -# $ createdb ovirt # $ psql ovirt # $-# CREATE USER ovirt WITH PASSWORD 'v23zj59an'; -# $-# CREATE DATABASE ovirt; -# $-# GRANT ALL PRIVILEGES ON DATABASE ovirt to ovirt; +# $-# CREATE DATABASE ovirt_development; +# $-# GRANT ALL PRIVILEGES ON DATABASE ovirt_development to ovirt; # $-# CREATE DATABASE ovirt_test; # $-# GRANT ALL PRIVILEGES ON DATABASE ovirt_test to ovirt; +# $-# CREATE DATABASE ovirt; +# $-# GRANT ALL PRIVILEGES ON DATABASE ovirt to ovirt; # $-# \q # $ exit # +# To start off a new environment, use the following commands: +# +# rake db:drop:all +# rake db:create:all +# rake db:migrate +# rake db:tests:prepare +# +# And when there are changes to the database, simple run: +# +# rake db:migrate +# rake db:test:prepare +# # On a local system you may need to edit this file to have only the following # /var/lib/pgsql/data/pg_hba.conf # local all all trust @@ -23,7 +36,7 @@ development: adapter: postgresql - database: ovirt + database: ovirt_development username: ovirt password: v23zj59an host: localhost -- 1.5.5.1 From dpierce at redhat.com Mon Oct 20 19:11:21 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Mon, 20 Oct 2008 15:11:21 -0400 Subject: [Ovirt-devel] Re: [PATCH server] Changed the database configuration to use a different database for development In-Reply-To: <1224529799-16294-1-git-send-email-dpierce@redhat.com> References: <1224529799-16294-1-git-send-email-dpierce@redhat.com> Message-ID: <48FCD7D9.6070501@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Darryl L. Pierce wrote: > Signed-off-by: Darryl L. Pierce > --- This script has been tested on the appliance and worked. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj819UACgkQjaT4DmxOfxs70gCgtNgjp0CRDz6mIBZ6ebFrYfSw nbYAn3pHmT9aqpdT0eKMcXS1wCVoTzYn =SD0g -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From jguiditt at redhat.com Mon Oct 20 19:17:48 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Mon, 20 Oct 2008 15:17:48 -0400 Subject: [Ovirt-devel] [PATCH] model apis for the storage tree view for the new VM form. In-Reply-To: <1224083229-29533-1-git-send-email-sseago@redhat.com> References: <1224083229-29533-1-git-send-email-sseago@redhat.com> Message-ID: <1224530268.30551.1.camel@localhost.localdomain> On Wed, 2008-10-15 at 15:07 +0000, Scott Seago wrote: > HardwarePool.storage_tree returns a list of storage pools in the HW pool. For each pool, the :children hash element has a list of storage volumes, filtered to include only available volumes (and ones attached to the current VM for edit operations). For each storage volume, if it supports LVM, then it has a :children element with LVM volumes defined on it. > > The hash includes fields showing which are available for use (:available) and which support creating new child volumes (currently iSCSI volumes, with NFS Pools soon to be added) > > Signed-off-by: Scott Seago > --- > src/app/models/hardware_pool.rb | 6 +++++ > src/app/models/iscsi_storage_volume.rb | 6 +++++ > src/app/models/storage_pool.rb | 27 +++++++++++++++++++++++++ > src/app/models/storage_volume.rb | 34 ++++++++++++++++++++++++++++++++ > 4 files changed, 73 insertions(+), 0 deletions(-) > > diff --git a/src/app/models/hardware_pool.rb b/src/app/models/hardware_pool.rb > index 160f663..6780329 100644 > --- a/src/app/models/hardware_pool.rb > +++ b/src/app/models/hardware_pool.rb > @@ -101,4 +101,10 @@ class HardwarePool < Pool > return {:total => total, :labels => labels} > end > > + def storage_tree(vm_to_include=nil) > + storage_pools.find(:all, > + :conditions => "type != 'LvmStoragePool'").collect do |pool| > + pool.storage_tree_element(vm_to_include) > + end > + end > end > diff --git a/src/app/models/iscsi_storage_volume.rb b/src/app/models/iscsi_storage_volume.rb > index b254cd8..00d7db2 100644 > --- a/src/app/models/iscsi_storage_volume.rb > +++ b/src/app/models/iscsi_storage_volume.rb > @@ -21,4 +21,10 @@ class IscsiStorageVolume < StorageVolume > def label_components > "#{storage_pool[:target]}:#{lun}" > end > + > + #FIXME: should also take available free space into account > + def supports_lvm_subdivision > + return true > + end > + > end > diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb > index 7a6f8f0..aed2902 100644 > --- a/src/app/models/storage_pool.rb > +++ b/src/app/models/storage_pool.rb > @@ -28,6 +28,9 @@ class StoragePool < ActiveRecord::Base > def total_size_in_gb > find(:all).inject(0){ |sum, sv| sum + sv.size_in_gb } > end > + def full_vm_list > + find(:all).inject([]){ |list, sv| list + sv.vms } > + end > end > > has_many :smart_pool_tags, :as => :tagged, :dependent => :destroy > @@ -73,4 +76,28 @@ class StoragePool < ActiveRecord::Base > def search_users > hardware_pool.search_users > end > + > + def user_subdividable > + false > + end > + > + def storage_tree_element(vm_to_include=nil) > + return_hash = { :id => id, > + :type => self[:type], > + :text => display_name, > + :name => display_name, > + :available => false, > + :create_volume => user_subdividable, > + :selected => false} > + condition = "vms.id is null" > + if (vm_to_include and vm_to_include.id) > + condition +=" or vms.id=#{vm_to_include.id}" > + end > + return_hash[:children] = storage_volumes.find(:all, > + :include => :vms, > + :conditions => condition).collect do |volume| > + volume.storage_tree_element(vm_to_include) > + end > + return_hash > + end > end > diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb > index 378b58f..3bd5da0 100644 > --- a/src/app/models/storage_volume.rb > +++ b/src/app/models/storage_volume.rb > @@ -70,4 +70,38 @@ class StorageVolume < ActiveRecord::Base > return [] > end > end > + > + def supports_lvm_subdivision > + return false > + end > + > + def storage_tree_element(vm_to_include=nil) > + vm_ids = vms.collect {|vm| vm.id} > + return_hash = { :id => id, > + :type => self[:type], > + :text => display_name, > + :name => display_name, > + :available => ((vm_ids.empty?) or > + (vm_to_include and vm_to_include.id and > + vm_ids.include?(vm_to_include.id))), > + :create_volume => supports_lvm_subdivision, > + :selected => (!vm_ids.empty? and vm_to_include and vm_to_include.id and > + (vm_ids.include?(vm_to_include.id)))} > + if lvm_storage_pool > + if return_hash[:available] > + return_hash[:available] = lvm_storage_pool.storage_volumes.full_vm_list.empty? > + end > + condition = "vms.id is null" > + if (vm_to_include and vm_to_include.id) > + condition +=" or vms.id=#{vm_to_include.id}" > + end > + return_hash[:children] = lvm_storage_pool.storage_volumes.find(:all, > + :include => :vms, > + :conditions => condition).collect do |volume| > + volume.storage_tree_element(vm_to_include) > + end > + end > + return_hash > + end > + > end ACK. I have tested this out by manually creating some objects and associations in the rails console. calling storage_tree returns expected results. We can probably drop the :text attribute of the hash (unneeded), but that can be altered at any time as we are tweaking the json. -j From apevec at redhat.com Mon Oct 20 20:05:11 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 20 Oct 2008 22:05:11 +0200 Subject: [Ovirt-devel] make ovirt-node-image.iso w/o its rpm In-Reply-To: <20081020152233.GA10200@csdan> References: <20081020152233.GA10200@csdan> Message-ID: <48FCE477.3030002@redhat.com> Dan Kenigsberg wrote: > Also, moving logic from .spec to Makefile is generally a good thing that > shortens dev cycles. > Please review the following patch and consider applying something on > these lines. I hope this is the place for such requests. yes, that was on our TechnicalDebt TODO, thanks for doing it :) > +SELINUX_ENFORCING=$(shell getenforce) since we run build as non-root account, we need full path /usr/sbin/getenforce > +iso: repos.ks +ovirt-node-image.iso: repos.ks since we have known file output, it's better to use real target otherwise, iso should be listed in .PHONY > + Enforcing) setenforce Permissive ;; \ need sudo /usr/sbin/setenforce Permissive I'll git-send-email your patch now with those changes. From apevec at redhat.com Mon Oct 20 20:05:53 2008 From: apevec at redhat.com (Alan Pevec) Date: Mon, 20 Oct 2008 22:05:53 +0200 Subject: [Ovirt-devel] [PATCH ovirt-node-image] Move iso-making code from spec to Makefile In-Reply-To: <20081020152233.GA10200@csdan> References: <20081020152233.GA10200@csdan> Message-ID: <1224533153-18218-1-git-send-email-apevec@redhat.com> From: Dan Kenigsberg Signed-off-by: Dan Kenigsberg --- Makefile.am | 53 +++++++++++++++++++++++++++++++++++++ ovirt-node-image.spec.in | 65 +-------------------------------------------- 2 files changed, 55 insertions(+), 63 deletions(-) diff --git a/Makefile.am b/Makefile.am index 17b26c0..37e52c6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,59 @@ RPM_FLAGS += $(if $(FEDORA_URL),--define "fedora_url $(FEDORA_URL)") # OVIRT_URL env var can be set to the root of an ovirt.org mirror RPM_FLAGS += $(if $(OVIRT_URL),--define "ovirt_url $(OVIRT_URL)") +FEDORA=$(shell rpm --eval '%{fedora}') +ARCH=$(shell rpm --eval '%{_arch}') +FEDORA_MIRROR=http://mirrors.fedoraproject.org/mirrorlist +CUR_RAWHIDE=10 +OVIRT_URL=http://ovirt.org/repos/ovirt + +repos.ks: + ( \ + if [ 0$(FEDORA) == 0$(CUR_RAWHIDE) ]; then \ + FEDORA_REPO=rawhide ;\ + FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/development/$(ARCH)/os,--mirrorlist=$(FEDORA_MIRROR)?repo=rawhide&arch=$(ARCH))" ;\ + OVIRT_DISTRO=development ;\ + else \ + FEDORA_REPO=f$(FEDORA) ;\ + FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/${ARCH}/os,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-$(FEDORA)&arch=$(ARCH))" ;\ + OVIRT_DISTRO=$(FEDORA) ;\ + UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates-newkey $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH}.newkey,--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))" ;\ + fi ;\ + echo "repo --name=$${FEDORA_REPO} $${FEDORA_REPO_LOC}" > $@ ;\ + echo "repo --name=ovirt-org --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/$(ARCH)" >> $@ ;\ + echo "$${UPDATE_REPO_LINE}" >> $@ ;\ + echo "repo --name=ovirt-local --baseurl=file://$(OVIRT_CACHE_DIR)/ovirt" >> $@ \ + ) + +SELINUX_ENFORCING=$(shell /usr/sbin/getenforce) +ovirt-node-image.iso: repos.ks + mkdir -p $(OVIRT_CACHE_DIR)/node-image-tmp + mkdir -p $(OVIRT_CACHE_DIR)/yum + ( \ + case $(SELINUX_ENFORCING) in \ + Enforcing) sudo /usr/sbin/setenforce Permissive ;; \ + Permissive) ;; \ + *) if ksflatten ovirt-image-node.ks 2>/dev/null \ + | grep -q '^selinux --disabled'; then \ + echo WARNING: SELinux disabled in kickstart ;\ + else \ + echo ERROR: SELinux enabled in kickstart, \ + but disabled on the build machine ;\ + exit 1 ;\ + fi ;; \ + esac ;\ + ) + sudo livecd-creator --skip-minimize -c ovirt-node-image.ks \ + -f ovirt-node-image \ + --tmpdir='$(OVIRT_CACHE_DIR)/node-image-tmp' \ + --cache='$(OVIRT_CACHE_DIR)/yum' + sudo chown $${USER} ovirt-node-image.iso + ( \ + if [ $(SELINUX_ENFORCING) = Enforcing ]; then \ + sudo /usr/sbin/setenforce Enforcing || exit 1 ;\ + fi \ + ) + rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz diff --git a/ovirt-node-image.spec.in b/ovirt-node-image.spec.in index 6f9ddac..6fb968f 100644 --- a/ovirt-node-image.spec.in +++ b/ovirt-node-image.spec.in @@ -1,8 +1,3 @@ -%{!?ovirt_cache_dir: %define ovirt_cache_dir /var/tmp/ovirt-cache} -%{!?ovirt_local_repo: %define ovirt_local_repo file://%{ovirt_cache_dir}/ovirt} -%{!?ovirt_url: %define ovirt_url http://ovirt.org/repos/ovirt} -%define fedora_mirror http://mirrors.fedoraproject.org/mirrorlist - Summary: oVirt Node ISO image Name: ovirt-node-image Version: @VERSION@ @@ -35,66 +30,10 @@ The PXE boot image for oVirt Node network boot from oVirt Server. %prep %setup -q +./configure %build -%if 0%{?fedora} == 010 - # XXX current rawhide - %if "%{?fedora_url}" == "" -cat > repos.ks << EOF -repo --name=rawhide --mirrorlist=%{fedora_mirror}?repo=rawhide&arch=%{_arch} -EOF - %else -cat > repos.ks << EOF -repo --name=rawhide --baseurl=%{fedora_url}/development/%{_arch}/os -EOF - %endif -cat >> repos.ks << EOF -repo --name=ovirt-org --baseurl=%{ovirt_url}/development/%{_arch} -EOF -%else - %if "%{?fedora_url}" == "" -cat > repos.ks << EOF -repo --name=f%{fedora} --mirrorlist=%{fedora_mirror}?repo=fedora-%{fedora}&arch=%{_arch} -repo --name=f%{fedora}-updates-newkey --mirrorlist=%{fedora_mirror}?repo=updates-released-f%{fedora}.newkey&arch=%{_arch} -EOF - %else -cat > repos.ks << EOF -repo --name=f%{fedora} --baseurl=%{fedora_url}/releases/%{fedora}/Everything/%{_arch}/os -repo --name=f%{fedora}-updates-newkey --baseurl=%{fedora_url}/updates/%{fedora}/%{_arch}.newkey -EOF - %endif -cat >> repos.ks << EOF -repo --name=ovirt-org --baseurl=%{ovirt_url}/%{fedora}/%{_arch} -EOF -%endif - -cat >> repos.ks << EOF -repo --name=ovirt-local --baseurl=%{ovirt_local_repo} -EOF - -mkdir -p %{ovirt_cache_dir}/node-image-tmp -mkdir -p %{ovirt_cache_dir}/yum - -sudo su - -c "cd $(pwd) && - enforcing=\$(getenforce) - case \$enforcing in - Enforcing) setenforce Permissive ;; - Permissive) ;; - *) if ksflatten %{name}.ks 2>/dev/null \ - | grep -q '^selinux --disabled$'; then - echo WARNING: SELinux disabled in kickstart - else - echo ERROR: SELinux enabled in kickstart, \ - but disabled on the build machine - exit 1 - fi ;; - esac - livecd-creator --skip-minimize -c %{name}.ks -f %{name} \ - --tmpdir='%{ovirt_cache_dir}/node-image-tmp' \ - --cache='%{ovirt_cache_dir}/yum' - if [ \$enforcing = Enforcing ]; then - setenforce Enforcing || exit 1 - fi" +make ovirt-node-image.iso sudo su - -c "cd $(pwd) && ./ovirt-pxe %{name}.iso" sudo su - -c "cd $(pwd) && chown -R $USER ." -- 1.5.5.1 From vbian at redhat.com Tue Oct 21 01:23:13 2008 From: vbian at redhat.com (Xuewei Bian) Date: Tue, 21 Oct 2008 09:23:13 +0800 Subject: [Ovirt-devel] Ask for further help of the iscsi Message-ID: <48FD2F01.3030801@redhat.com> What should I do to configure the iscsi server on the real box? Yesterday , we make the iscsi server configured successfully on the same box of the ovirt-appliance.So I want to know ,how to configure it on the other machine.Please provision more detail for me.Thanks a lot. :-) Vivian Bian From arshad at binaryvm.com Tue Oct 21 09:36:33 2008 From: arshad at binaryvm.com (arshad) Date: Tue, 21 Oct 2008 15:06:33 +0530 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> Message-ID: <48FDA2A1.2040305@binaryvm.com> Hi, developer-appliance download leads me to 0.91 release. Is there any way i can download version 0.94 or maybe 0.93 for developer-appliance. Secondly, while working with 0.91 ( developer-appliance ) , create-wui-appliance.sh -v successfully defines "developer". But when i attempt to "ssh -Y root at 192.168.50.2 firefox", it throws no route error. Is there something i am missing. Thanks. xxqonline at hotmail.com wrote: > Hello Justin: > > Why not have a try > http://ovirt.org/download/ovirt-developer-appliance-0.91-1-x86_64.tar > http://ovirt.org/download/ovirt-developer-appliance-0.91-1-i386.tar > http://ovirt.org/download.html > > Not the admin url is not 192.168.50.2, it is the eth0 ip of the admin vm. > I said there are a lot of the error in their doc. > > > > Regards, > Qiang > > -------------------------------------------------- > From: > Sent: Monday, October 13, 2008 5:50 PM > To: "Justin Clacherty" > Cc: > Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool > >> The problem is I think the ovirt-appliance .img did not use the second >> nic. >> But the developer.img used. I am not the red hat guy, I really think >> they need to improve doc. >> >> -------------------------------------------------- >> From: "Justin Clacherty" >> Sent: Monday, October 13, 2008 5:48 PM >> To: >> Cc: "Alan Pevec" ; >> Subject: Re: [Ovirt-devel] unable to add hosts in web admin tool >> >>> xxqonline at hotmail.com wrote: >>>> I think you may download a developer.img, it is in download page >>> >>> Wasn't the developer image just there to allow you to run nodes on >>> the management server instead of needing seperate hardware? >>> >>>> >>>> BTW, I did not run ovirt-appliance successful. >>> >>> bummer. >>> >>> >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > From apevec at redhat.com Tue Oct 21 09:49:57 2008 From: apevec at redhat.com (Alan Pevec) Date: Tue, 21 Oct 2008 11:49:57 +0200 Subject: [Ovirt-devel] Re: Ask for further help of the iscsi In-Reply-To: <48FD2F01.3030801@redhat.com> References: <48FD2F01.3030801@redhat.com> Message-ID: <48FDA5C5.6020001@redhat.com> Xuewei Bian wrote: > > What should I do to configure the iscsi server on the real box? > Yesterday , we make the iscsi server configured successfully on the same > box of the ovirt-appliance.So I want to know ,how to configure it on the > other machine.Please provision more detail for me.Thanks a lot. :-) > > Vivian Bian I've been trying to reproduce your issue and I think you've hit the issue with hardcoded SCSI serial# in tgtd: it's hardcoded to beaf+ID+LUN see http://git.kernel.org/?p=linux/kernel/git/tomo/tgt.git;a=blob;f=usr/spc.c;h=ac5c3deacd14e94e15d9b0fcf136bd85b9362ed7;hb=HEAD#l1033 This makes udev create the same /dev/disk/by-id/scsi-S_beaf11 path so your iscsi target conflicts with iscsi provided by the appliance. Workaround is to set tgtadm --tid value to something different, I'll also move iscsi target defined in the appliance to the max id available, to reduce the possibility of collision. From jdong at redhat.com Tue Oct 21 09:50:04 2008 From: jdong at redhat.com (Jia Dong) Date: Tue, 21 Oct 2008 17:50:04 +0800 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48FDA2A1.2040305@binaryvm.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> <48FDA2A1.2040305@binaryvm.com> Message-ID: <48FDA5CC.4090104@redhat.com> arshad wrote: > Hi, > > developer-appliance download leads me to 0.91 release. Is there > any way i can download version 0.94 or maybe 0.93 for > developer-appliance. > > Secondly, while working with 0.91 ( developer-appliance ) , > create-wui-appliance.sh -v successfully defines "developer". But > when i attempt to "ssh -Y root at 192.168.50.2 firefox", it throws > no route error. > > Is there something i am missing. > > Thanks. > Hi arshad, Ovirt-appliance is a merged appliance. It is merged from developer and bundle. FYI: https://www.redhat.com/archives/ovirt-devel/2008-July/msg00024.html Some bug exists in lower version.You can get latest ovirt-appliance from ovirt.org.For further more help,you can wait for some instructions from authority who is a member of ovirt team. Just for your information. Hope this helps. :-) Nikki From arshad at binaryvm.com Tue Oct 21 10:02:28 2008 From: arshad at binaryvm.com (arshad) Date: Tue, 21 Oct 2008 15:32:28 +0530 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48FDA5CC.4090104@redhat.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com><48F3128A.7090800@redfish-group.com><48F31680.1090807@redfish-group.com><48F31976.8090407@redfish-group.com> <48FDA2A1.2040305@binaryvm.com> <48FDA5CC.4090104@redhat.com> Message-ID: <48FDA8B4.40407@binaryvm.com> Thanks very much for a quick reply. Arshad. Jia Dong wrote: > arshad wrote: >> Hi, >> >> developer-appliance download leads me to 0.91 release. Is there >> any way i can download version 0.94 or maybe 0.93 for >> developer-appliance. >> >> Secondly, while working with 0.91 ( developer-appliance ) , >> create-wui-appliance.sh -v successfully defines "developer". But >> when i attempt to "ssh -Y root at 192.168.50.2 firefox", it throws >> no route error. >> >> Is there something i am missing. >> >> Thanks. >> > Hi arshad, > Ovirt-appliance is a merged appliance. It is merged from developer and > bundle. > FYI: https://www.redhat.com/archives/ovirt-devel/2008-July/msg00024.html > Some bug exists in lower version.You can get latest ovirt-appliance > from ovirt.org.For further more help,you can wait for some instructions > from authority who is a member of ovirt team. > Just for your information. > Hope this helps. :-) > > Nikki > From apevec at redhat.com Tue Oct 21 13:34:04 2008 From: apevec at redhat.com (Alan Pevec) Date: Tue, 21 Oct 2008 15:34:04 +0200 Subject: [Ovirt-devel] [PATCH] change iscsi target id to something else than 1 Message-ID: <1224596044-22306-1-git-send-email-apevec@redhat.com> to avoid serial# collision, see http://git.kernel.org/?p=linux/kernel/git/tomo/tgt.git;a=blob;f=usr/spc.c;h=ac5c3deacd14e94e15d9b0fcf136bd85b9362ed7;hb=HEAD#l1033 Signed-off-by: Alan Pevec --- appliances/ovirt/files/ovirt-server-appliance | 21 +++++++++++---------- 1 files changed, 11 insertions(+), 10 deletions(-) diff --git a/appliances/ovirt/files/ovirt-server-appliance b/appliances/ovirt/files/ovirt-server-appliance index 778d0ce..e7ba7bd 100644 --- a/appliances/ovirt/files/ovirt-server-appliance +++ b/appliances/ovirt/files/ovirt-server-appliance @@ -8,6 +8,7 @@ # Source functions library . /etc/init.d/functions +TARGET_ID=32101 start() { echo -n "Starting ovirt-server-appliance: " @@ -27,23 +28,23 @@ start() { -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 \ + tgtadm --lld iscsi --op new --mode target --tid $TARGET_ID \ -T ovirtpriv:storage # # Now associate them to the LVs # - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ + tgtadm --lld iscsi --op new --mode logicalunit --tid $TARGET_ID \ --lun 1 -b /ovirtiscsi/iSCSI3 - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ + tgtadm --lld iscsi --op new --mode logicalunit --tid $TARGET_ID \ --lun 2 -b /ovirtiscsi/iSCSI4 - tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ + tgtadm --lld iscsi --op new --mode logicalunit --tid $TARGET_ID \ --lun 3 -b /ovirtiscsi/iSCSI5 # # Now make them available # - tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL + tgtadm --lld iscsi --op bind --mode target --tid $TARGET_ID -I ALL echo_success echo @@ -53,15 +54,15 @@ stop() { echo -n "Stopping ovirt-server-appliance: " # stop access to the iscsi target - tgtadm --lld iscsi --op unbind --mode target --tid 1 -I ALL + tgtadm --lld iscsi --op unbind --mode target --tid $TARGET_ID -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 + tgtadm --lld iscsi --op delete --mode logicalunit --tid $TARGET_ID --lun 3 + tgtadm --lld iscsi --op delete --mode logicalunit --tid $TARGET_ID --lun 2 + tgtadm --lld iscsi --op delete --mode logicalunit --tid $TARGET_ID --lun 1 # shutdown the target - tgtadm --lld iscsi --op delete --mode target --tid 1 + tgtadm --lld iscsi --op delete --mode target --tid $TARGET_ID kill $(cat /var/run/dnsmasq.pid) -- 1.5.5.1 From clalance at redhat.com Tue Oct 21 14:01:03 2008 From: clalance at redhat.com (Chris Lalancette) Date: Tue, 21 Oct 2008 16:01:03 +0200 Subject: [Ovirt-devel] [PATCH] change iscsi target id to something else than 1 In-Reply-To: <1224596044-22306-1-git-send-email-apevec@redhat.com> References: <1224596044-22306-1-git-send-email-apevec@redhat.com> Message-ID: <48FDE09F.4040908@redhat.com> Alan Pevec wrote: > to avoid serial# collision, see > http://git.kernel.org/?p=linux/kernel/git/tomo/tgt.git;a=blob;f=usr/spc.c;h=ac5c3deacd14e94e15d9b0fcf136bd85b9362ed7;hb=HEAD#l1033 I could have sworn tgtd use to do something sane here, but I could be wrong. In any case, this should eliminate a common collision where people use TID 1, and collide with us. If people use two external tgtd target's with TID 1, there's nothing we can do at the moment; they are going to collide. But at least this is a little bit better. ACK Chris Lalancette From dpierce at redhat.com Tue Oct 21 14:09:31 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Tue, 21 Oct 2008 10:09:31 -0400 Subject: [Ovirt-devel] Re: [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <1224527170-13867-1-git-send-email-dpierce@redhat.com> References: <1224527170-13867-1-git-send-email-dpierce@redhat.com> Message-ID: <48FDE29B.7000205@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Darryl L. Pierce wrote: > The system now takes an encoded configuration descriptor from the server. It > then parses from that a set of aliases for bondings if such exist. It then > also extracts configuration details for the various network interfaces on the > node. Afterward, it reloads module dependencies and then restarts the > networking service. > > This patch also includes updated rdocs for the configuration generator. Can I get an ACK or feedback on this and the related server patch? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj94pcACgkQjaT4DmxOfxvv2ACgou1IYldUAq46W27OeRcC4OUx UpMAoPWBDBRsCRGpkGNbA1eaLjSW/PMs =nbOH -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Tue Oct 21 14:10:35 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Tue, 21 Oct 2008 10:10:35 -0400 Subject: [Ovirt-devel] Re: [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <1224527121-13802-1-git-send-email-dpierce@redhat.com> References: <1224527121-13802-1-git-send-email-dpierce@redhat.com> Message-ID: <48FDE2DB.3000605@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Darryl L. Pierce wrote: > Rather than sending the node a series of scripts that load > kernel modules, or are tightly coupled to tools like augeas, > this patch introduces an encoding scheme for data. > > A line that begins with "bonding" describes a bonded interface. > From it the configuration processor generates a simple bonding > file that includes an alias for the bonding kernel module > > A line that begins with "ifcfg" describes an network > interface. It will contain the mac address and interface name, > followed by all needed configuration values to bring the > interface up. Jim/DanP: Can you review and ACK or give feedback on this and the node patch? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkj94tQACgkQjaT4DmxOfxvstQCfXkk582N4rpFT6LGeFJ1Fm6rf dXcAoN1CFeEdHpO+Lq/twrPb46KbjxDf =SiRD -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Tue Oct 21 17:22:46 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 21 Oct 2008 13:22:46 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. Message-ID: <1224609766-24650-1-git-send-email-dpierce@redhat.com> The system now takes an encoded configuration descriptor from the server. It then parses from that a set of aliases for bondings if such exist. It then also extracts configuration details for the various network interfaces on the node. Afterward, it reloads module dependencies and then restarts the networking service. This patch also includes updated rdocs for the configuration generator. --- Makefile.am | 1 + ovirt-node.spec.in | 2 + scripts/ovirt-early | 14 +++------ scripts/ovirt-process-config | 59 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) create mode 100755 scripts/ovirt-process-config diff --git a/Makefile.am b/Makefile.am index 1d63310..c55db13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ EXTRA_DIST = \ scripts/ovirt-early \ scripts/ovirt-functions \ scripts/ovirt-post \ + scripts/ovirt-process-config \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index c5c836b..912192e 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -87,6 +87,7 @@ cd - %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__install} -p -m0755 scripts/ovirt-awake %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-identify-node/ovirt-identify-node %{buildroot}%{_sbindir} %{__install} -p -m0755 ovirt-listen-awake/ovirt-listen-awake %{buildroot}%{_sbindir} %{__install} -Dp -m0755 ovirt-listen-awake/ovirt-listen-awake.init %{buildroot}%{_initrddir}/ovirt-listen-awake @@ -158,6 +159,7 @@ fi %files %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake +%{_sbindir}/ovirt-process-config %{_sbindir}/ovirt-identify-node %{_sbindir}/ovirt-listen-awake %{_sbindir}/ovirt-install-node diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 4723426..3c8c494 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -12,6 +12,8 @@ # size of the oVirt partition in megabytes OVIRT_SIZE=64 +BONDING_MODCONF_FILE=/etc/modprobe.d/bonding +AUGTOOL_CONFIG=/var/tmp/augtool-config get_mac_addresses() { macs=$(ifconfig | awk '/HWaddr/ { print $5"="$1 }' \ @@ -43,16 +45,10 @@ configure_from_network() { "http://$SRV_HOST:$SRV_PORT/ovirt/managed_node/config?host=$(hostname)&macs=$macs" if [ $? -eq 0 ]; then echo "Remote configuration bundle retrieved to $cfgdb" - bash $cfgdb - if [ -f /var/tmp/pre-config-script ]; then - echo "Loading kernel modules" - bash /var/tmp/pre-config-script \ - && echo "Kernel modules loaded" \ - || echo "Failed loading kernel modules" - fi - if [ -f /var/tmp/node-augtool ]; then + ovirt-process-config $cfgdb $BONDING_MODCONF_FILE $AUGTOOL_CONFIG + if [ -f $AUGTOOL_CONFIG ]; then echo "Loading remote config" - augtool < /var/tmp/node-augtool \ + augtool < $AUGTOOL_CONFIG \ && echo "Remote config applied" \ || echo "Failed applying remote config" fi diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config new file mode 100755 index 0000000..5698100 --- /dev/null +++ b/scripts/ovirt-process-config @@ -0,0 +1,59 @@ +#!/bin/bash + +ME=$(basename "$0") +warn() { printf '%s: %s\n' "$ME" "$*" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} + +case $# in + 0|1|2) warn "too few arguments"; try_help;; + 3) ;; + *) warn "too many arguments"; try_help;; +esac + +CONFIG=$1 +OVIRT_KERNEL_MODULE_FILE=$2 +OVIRT_CONFIG_OUTPUT_FILE=$3 + +modconf=$(awk '/bonding=/ { + match($0, "^[ \t]*bonding=(.*)", data) + + if (match("[^[:alnum:]=_ at -]", data[1]) >= 0) { + printf "invalid bonding alias: \"%s\"\n", data[1]; + exit 1; + } + + alias=data[1] + + printf("install %s bonding", alias) + }' $CONFIG) + +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE + +networking=$(awk '/ifcfg=/ { + match($0, "^[ \t]*ifcfg=(.*)", data) + split(data[1], ifcfg, "|") + + mac = ifcfg[1] + iface = ifcfg[2] + ifcfg_dir = "/files/etc/sysconfig/network-scripts" + + printf("rm %s/ifcfg-%s\n", ifcfg_dir, iface) + printf("set %s/ifcfg-%s/DEVICE %s\n", ifcfg_dir, iface, iface) + + for (line in ifcfg) { + if(line > 2) { + match(ifcfg[line], "(^[^=]+)=(.*)", values) + field=values[1] + value=values[2] + + printf("set %s/ifcfg-%s/%s %s\n", ifcfg_dir, iface, field, value) + } + } + + + printf("save\n") + +}' $CONFIG) + +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE -- 1.5.5.1 From pmyers at redhat.com Tue Oct 21 17:46:36 2008 From: pmyers at redhat.com (Perry Myers) Date: Tue, 21 Oct 2008 13:46:36 -0400 Subject: [Ovirt-devel] Re: Something puzzled about the ovirt In-Reply-To: <48F5C628.4060909@redhat.com> References: <48F5C628.4060909@redhat.com> Message-ID: <48FE157C.3030405@redhat.com> vbian wrote: > Hi,Pmyers: > In the reply of the bug #466220,you wrote,that we should do the test > ,"using the real nodes(ovirt node running on physical hardware) or VMs > running directly on the Appliance Host." > But the node 3-5 are the fake nodes. > =>So: > 1.How can we get the ovirt node? Maybe how to create the node exactly. Physical nodes are created by PXE booting real hardware on the same LAN as the oVirt Appliance. If you are running the appliance on a physical machine it needs to have a network card bridged to a network with only oVirt machines on it. You would use the create-ovirt-appliance script like this: create-ovirt-appliance -e eth1 (Assuming that eth1 is the network interface that you want to bridge) Then you just boot the other hosts on that network after configuring them to PXE and you're set. > 2.And at the same time, the VMs running directly on the Appliance Host > are the Virtual Machines created in the ovirt UI? In that case, I can't > make a vm run successfully by now.What should I do? On the physical host running the appliance, did you run the following command: ovirt-install-node stateful This sets up the physical host to be an ovirt-node that you can create virtual machines on. This works in version 0.93 and above. If this is done correctly you should see a host named: physical.priv.ovirt.org in the hosts tab in the default hardware pool. If you don't see this Node there, send me more details (logs preferably) that I can take a look at. > 3.About the nfs: by now, I can get the only nfs service in the Appliance > Host,and I wonder what is the function of the disk1-3.dsk files. What's > more ,the configuration of the nfs in ovirt-appliance is not the same as > the configuration in real physical host. > =>How could I configure another nfs in ovirt? Just run a Fedora box on the oVirt network that you've bridged over eth1 (or whatever interface your bridge is) and configure an NFS export on that host. You'll need to create empty disk images in the new NFS share using the qemu-img command line tool. You can also run an NFS server on the physical host running the appliance. Just make sure that the firewall is configured to allow NFS through it, or make sure that ovirtbr0 is a trusted interface using the following command: lokkit -t ovirtbr0 (0.94 release should do this by default) In this case, the NFS server IP address would be 192.168.50.1 and the share name is whatever you put into /etc/exports. > 4.I want to get the further information about the ovirtbr0 > (192.168.50.1) and the virbr0 (192.168.122.1).Whether the ovirtbr0 is > the bridge between the VM host and the physical host,but the VMs > couldn't access the outside world via the ovirtbr0,and they access the > outside world via the virbr0? Yes, VMs can access the outside world via virbr0. That is why the default gateway on the oVirt network is 192.168.50.2. This way the appliance acts like a router passing all external traffic through virbr0 to the outside world. ovirtbr0 does not have NAT or routing set up for it, so it does not route to the outside world. (This could be changed by adding some iptables rules to the host though) > 5.Today,you brought us a good news that the Windows could be installed > in the ovirt VM, I wonder how can you accomplish that? I used the .94 version which was just released and did the following: 1. Installed the appliance and ovirt-node RPMs on the physical host 2. # ovirt-install-node stateful 3. # create-ovirt-appliance 4. Boot the appliance. This allows the physical host node to get a kerberos keytab. 5. Shut down the appliance and virsh destroy it 6. # service libvirtd restart (so libvirt loads the keytab from step 4) 7. Boot the appliance 8. Set up an NFS share on the physical host like this: a. # mkdir -p /ovirtisos b. added this to /etc/exports /ovirtisos *(rw,no_root_squash,sync) c. # chkconfig nfsd on ; service nfsd restart 9. Copy a Windows XP install ISO to /ovirtisos/winxp.iso 10. In the oVirt WUI, added a new storage pool with the following info: ip address: 192.168.50.1 path: /ovirtisos 11. Create a cobbler profile for the ISO image on the appliance: # cobbler image add --name=WinXP \ --file=management.priv.ovirt.org:/ovirtisos/winxp.iso 12. Create a new vm pool and then in the vm pool create a new vm and select WinXP as the provisioning type. That should work, if you run into any problems, let me know. Thanks, Perry From jim at meyering.net Tue Oct 21 18:05:29 2008 From: jim at meyering.net (Jim Meyering) Date: Tue, 21 Oct 2008 20:05:29 +0200 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <1224527170-13867-1-git-send-email-dpierce@redhat.com> (Darryl L. Pierce's message of "Mon, 20 Oct 2008 14:26:10 -0400") References: <1224527170-13867-1-git-send-email-dpierce@redhat.com> Message-ID: <87r669x1qu.fsf@rho.meyering.net> "Darryl L. Pierce" wrote: ... Hi Darryl, Here's a nit about warn, and a fix for one more problem. Sorry I didn't spot the latter sooner. > diff --git a/scripts/ovirt-process-config b/scripts/ovirt-process-config > new file mode 100755 > index 0000000..83a3eb3 > --- /dev/null > +++ b/scripts/ovirt-process-config > @@ -0,0 +1,59 @@ > +#!/bin/bash > + > +ME=$(basename "$0") > +warn() { printf "$ME: $@\n" >&2; } That definition of warn in obsolete and slightly buggy. Please use this one instead: warn() { printf '%s: %s\n' "$ME" "$*" >&2; } Yes, we *will* factor these out, if only to avoid having to review them over and over again. > +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; exit 1;} > +try_help() { printf "Usage: \`$ME [config] [module output] [config output]\n" >&2; exit 1;} > + > +case $# in > + 0|1|2) warn "too few arguments"; try_help;; > + 3) ;; > + *) warn "too many arguments"; try_help;; > +esac > + > +CONFIG=$1 > +OVIRT_KERNEL_MODULE_FILE=$2 > +OVIRT_CONFIG_OUTPUT_FILE=$3 > + > +modconf=$(awk '/bonding=/ { > + match($0, "^[ \t]*bonding=(.*)", data) Please match the same regexp in those two cases. Otherwise, a commented out (or something like foobonding=...) line will cause this script to emit invalid output. modconf=$(awk '/^[ \t]*bonding=.+/ { match($0, "^[ \t]*bonding=(.+)", data) Also, note that I changed the "*" to a "+", so that an empty RHS will not match. > + > + if (match("[^[:alnum:]=_ at -]", data[1]) >= 0) { > + printf "invalid bonding alias: \"%s\"\n", data[1]; > + exit 1; > + } > + > + alias=data[1] > + > + printf("install %s bonding", alias) > + }' $CONFIG) > + > +echo "$modconf" > $OVIRT_KERNEL_MODULE_FILE > + > +networking=$(awk '/ifcfg=/ { > + match($0, "^[ \t]*ifcfg=(.*)", data) Please do the same here. > + split(data[1], ifcfg, "|") > + > + mac = ifcfg[1] > + iface = ifcfg[2] > + ifcfg_dir = "/files/etc/sysconfig/network-scripts" > + > + printf("rm %s/ifcfg-%s\n", ifcfg_dir, iface) > + printf("set %s/ifcfg-%s/DEVICE %s\n", ifcfg_dir, iface, iface) > + > + for (line in ifcfg) { > + if(line > 2) { > + match(ifcfg[line], "(^[^=]+)=(.*)", values) > + field=values[1] > + value=values[2] > + > + printf("set %s/ifcfg-%s/%s %s\n", ifcfg_dir, iface, field, value) > + } > + } > + > + > + printf("save\n") > + > +}' $CONFIG) > + > +echo "$networking" > $OVIRT_CONFIG_OUTPUT_FILE From jguiditt at redhat.com Tue Oct 21 18:29:43 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Tue, 21 Oct 2008 14:29:43 -0400 Subject: [Ovirt-devel] localization in rails Message-ID: <1224613783.30551.10.camel@localhost.localdomain> I just came across this article about native i18n support coming in rails 2.2, so this should make it easier for us to add i18n in future releases. The included gem will be the 'Simple' backend, but you can use/write others via (for example) I18n.backend = I18n::Backend::Gettext. Default setup looks pretty easy, similar to property file bundles in the java world, but in hash format. http://almosteffortless.com/2008/07/21/simple-localization-in-rails-22/ From bkahn at redhat.com Tue Oct 21 18:43:36 2008 From: bkahn at redhat.com (Benjamin Kahn) Date: Tue, 21 Oct 2008 14:43:36 -0400 Subject: [Ovirt-devel] localization in rails In-Reply-To: <1224613783.30551.10.camel@localhost.localdomain> References: <1224613783.30551.10.camel@localhost.localdomain> Message-ID: <1224614616.10225.14.camel@localhost.localdomain> On Tue, 2008-10-21 at 14:29 -0400, Jason Guiditta wrote: > I just came across this article about native i18n support coming in > rails 2.2, so this should make it easier for us to add i18n in future > releases. The included gem will be the 'Simple' backend, but you can > use/write others via (for example) I18n.backend = > I18n::Backend::Gettext. Default setup looks pretty easy, similar to > property file bundles in the java world, but in hash format. > http://almosteffortless.com/2008/07/21/simple-localization-in-rails-22/ I'd be interested in knowing what the i18n team thinks about this. Why not use gettext? http://manuals.rubyonrails.com/read/chapter/105 or, because that site appears to be down: http://74.125.95.104/search?q=cache:d1wHw7KeJq8J:manuals.rubyonrails.com/read/chapter/105+rails+gettext -- Benjamin Kahn Program Manager (978) 392 - 3176 From jguiditt at redhat.com Tue Oct 21 20:05:46 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Tue, 21 Oct 2008 16:05:46 -0400 Subject: [Ovirt-devel] localization in rails In-Reply-To: <1224614616.10225.14.camel@localhost.localdomain> References: <1224613783.30551.10.camel@localhost.localdomain> <1224614616.10225.14.camel@localhost.localdomain> Message-ID: <1224619546.30551.14.camel@localhost.localdomain> On Tue, 2008-10-21 at 14:43 -0400, Benjamin Kahn wrote: > On Tue, 2008-10-21 at 14:29 -0400, Jason Guiditta wrote: > > I just came across this article about native i18n support coming in > > rails 2.2, so this should make it easier for us to add i18n in future > > releases. The included gem will be the 'Simple' backend, but you can > > use/write others via (for example) I18n.backend = > > I18n::Backend::Gettext. Default setup looks pretty easy, similar to > > property file bundles in the java world, but in hash format. > > > http://almosteffortless.com/2008/07/21/simple-localization-in-rails-22/ > > I'd be interested in knowing what the i18n team thinks about this. Why > not use gettext? > > http://manuals.rubyonrails.com/read/chapter/105 > > or, because that site appears to be down: > http://74.125.95.104/search?q=cache:d1wHw7KeJq8J:manuals.rubyonrails.com/read/chapter/105+rails+gettext As I mentioned, you can use gettext or whatever else you want to plug in as the backend. They are shipping a default one to get people started, but the main point of what they are trying to do is provide easier access to i18n solutions from rails, which has always been a weak spot. Basically they are providing a core api to allow you to call multiple backends as needed, described here: http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized From mmorsi at redhat.com Tue Oct 21 20:54:30 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 21 Oct 2008 16:54:30 -0400 Subject: [Ovirt-devel] [PATCH server] Introduces a new IP address model. Moves IP addressing to a separate In-Reply-To: <1224277864-13419-1-git-send-email-dpierce@redhat.com> References: <1224277864-13419-1-git-send-email-dpierce@redhat.com> Message-ID: <48FE4186.3080407@redhat.com> NACK. Applies nicely (albeit with whitespace complaints) and everything seems to work with a few caveats as described below / inline. Darryl L. Pierce wrote: > You will need to run a migration after this patch to create the > ip_addresses table and massage the bondings and nics tables to > reference it. > > NEW CLASSES: > > IpAddress: base class for addresses > IpV4Address: represents an IPv4 address > IpV6Address: represents an IPv6 address > > Signed-off-by: Darryl L. Pierce > --- > src/app/models/bonding.rb | 1 + > src/app/models/ip_address.rb | 25 +++++ > src/app/models/ip_v4_address.rb | 46 +++++++++ > src/app/models/ip_v6_address.rb | 40 ++++++++ > src/app/models/nic.rb | 2 + > src/db/migrate/025_create_ip_addresses.rb | 46 +++++++++ > It seems since you've sent this out, migration 025 has been committed, so you'll need to start at 026 > ...026_move_nic_addresses_to_ip_addresses_table.rb | 56 +++++++++++ > ...7_move_bonding_address_to_ip_addresses_table.rb | 54 +++++++++++ > src/host-browser/host-browser.rb | 17 ++-- > src/lib/managed_node_configuration.rb | 8 +- > src/test/fixtures/bondings.yml | 4 +- > src/test/fixtures/ip_addresses.yml | 55 +++++++++++ > src/test/fixtures/nics.yml | 82 ++++++++-------- > .../functional/managed_node_configuration_test.rb | 26 +++--- > src/test/unit/ip_address_test.rb | 8 ++ > src/test/unit/ip_v4_address_test.rb | 99 ++++++++++++++++++++ > src/test/unit/ip_v6_address_test.rb | 82 ++++++++++++++++ > src/test/unit/nic_test.rb | 1 + > 18 files changed, 584 insertions(+), 68 deletions(-) > create mode 100644 src/app/models/ip_address.rb > create mode 100644 src/app/models/ip_v4_address.rb > create mode 100644 src/app/models/ip_v6_address.rb > create mode 100644 src/db/migrate/025_create_ip_addresses.rb > create mode 100644 src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb > create mode 100644 src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb > create mode 100644 src/test/fixtures/ip_addresses.yml > create mode 100644 src/test/unit/ip_address_test.rb > create mode 100644 src/test/unit/ip_v4_address_test.rb > create mode 100644 src/test/unit/ip_v6_address_test.rb > > diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb > index 006c261..2fa4aa8 100644 > --- a/src/app/models/bonding.rb > +++ b/src/app/models/bonding.rb > @@ -51,6 +51,7 @@ class Bonding < ActiveRecord::Base > belongs_to :host > belongs_to :bonding_type > belongs_to :boot_type > + belongs_to :ip_address > > has_and_belongs_to_many :nics, > :join_table => 'bondings_nics', > diff --git a/src/app/models/ip_address.rb b/src/app/models/ip_address.rb > new file mode 100644 > index 0000000..f48cd4e > --- /dev/null > +++ b/src/app/models/ip_address.rb > @@ -0,0 +1,25 @@ > +# > +# ip_address.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. > + > +# +IpAddress+ is the base class for all address related classes. > +# > +class IpAddress < ActiveRecord::Base > + > +end > Why isn't there a "has_many :nics" and a "has_many :bonding" relationship in the IpAddress table. We wont be able to lookup nics / bondings from an ip address otherwise. > diff --git a/src/app/models/ip_v4_address.rb b/src/app/models/ip_v4_address.rb > new file mode 100644 > index 0000000..4495a13 > --- /dev/null > +++ b/src/app/models/ip_v4_address.rb > @@ -0,0 +1,46 @@ > +# ip_v4_address.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. > + > +# +IpV4Address+ represents a single IPv4 address. > +# > +class IpV4Address < IpAddress > + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} > + > + validates_presence_of :address, > + :message => 'An address must be supplied.' > + validates_format_of :address, > + :with => ADDRESS_TEST > + > + validates_presence_of :netmask, > + :message => 'A netmask must be supplied.' > + validates_format_of :netmask, > + :with => ADDRESS_TEST > + > + validates_presence_of :gateway, > + :message => 'A gateway address must be supplied.' > + validates_format_of :gateway, > + :with => ADDRESS_TEST > + > + validates_presence_of :broadcast, > + :message => 'A broadcast address must be supplied.' > + validates_format_of :broadcast, > + :with => ADDRESS_TEST > + > +end > diff --git a/src/app/models/ip_v6_address.rb b/src/app/models/ip_v6_address.rb > new file mode 100644 > index 0000000..1395886 > --- /dev/null > +++ b/src/app/models/ip_v6_address.rb > @@ -0,0 +1,40 @@ > +# ip_v6_address.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. > + > +# +IpV6Address+ represents a single IPv6 address. > +# > +class IpV6Address < IpAddress > + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} > + > + validates_presence_of :address, > + :message => 'An address must be provided.' > + validates_format_of :address, > + :with => ADDRESS_TEST > + > + validates_presence_of :gateway, > + :message => 'A gateway address must be provided.' > + validates_format_of :gateway, > + :with => ADDRESS_TEST > + > + validates_presence_of :prefix, > + :message => 'A prefix must be provided.' > + validates_format_of :prefix, > + :with => ADDRESS_TEST > +end > diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb > index baf7095..69568a9 100644 > --- a/src/app/models/nic.rb > +++ b/src/app/models/nic.rb > @@ -22,4 +22,6 @@ class Nic < ActiveRecord::Base > belongs_to :boot_type > > has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' > + > + belongs_to :ip_address > end > diff --git a/src/db/migrate/025_create_ip_addresses.rb b/src/db/migrate/025_create_ip_addresses.rb > new file mode 100644 > index 0000000..0f528f4 > --- /dev/null > +++ b/src/db/migrate/025_create_ip_addresses.rb > @@ -0,0 +1,46 @@ > +# > +# 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. > + > +# Creates a single-inheritance table for both IPv4 and IPv6 > +# addresses. > +# > +class CreateIpAddresses < ActiveRecord::Migration > + def self.up > + create_table :ip_addresses do |t| > + t.string :type > + > + # common attributes > + t.string :address, :limit => 39 > + t.string :gateway, :limit => 39 > + > + # attributes for IPv4 (type=IpV4Address) > + t.string :netmask, :limit => 15 > + t.string :broadcast, :limit => 15 > + > + # attributes for IPv6 (type=IpV6Address) > + t.string :prefix, :limit => 39 > + > + t.timestamps > + end > + end > + > + def self.down > + drop_table :ip_addresses > + end > +end > diff --git a/src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb b/src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb > new file mode 100644 > index 0000000..b873e4d > --- /dev/null > +++ b/src/db/migrate/026_move_nic_addresses_to_ip_addresses_table.rb > @@ -0,0 +1,56 @@ > +# > +# 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. > + > +class MoveNicAddressesToIpAddressesTable < ActiveRecord::Migration > + def self.up > + add_column :nics, :ip_address_id, :integer, :null => true > + > + execute 'alter table nics add constraint fk_nic_ip_address > + foreign key (ip_address_id) references ip_addresses(id)' > + > + Nic.find(:all).each do |nic| > + address = IpV4Address.new(:address => nic.ip_addr, > + :netmask => nic.netmask, > + :broadcast => nic.broadcast, > + :gateway => nic.ip_addr) > + nic.ip_address = address > + > + nic.save! > + address.save! > + end > + > + remove_column :nics, :ip_addr > + remove_column :nics, :netmask > + remove_column :nics, :broadcast > + end > + > + def self.down > + add_column :nics, :ip_addr, :string, :limit => 16 > + add_column :nics, :netmask, :string, :limit => 16 > + add_column :nics, :broadcast, :string, :limit => 16 > + > + Nic.find(:all).each do |nic| > + nic.ip_addr = nic.ip_address.address > + nic.netmask = nic.ip_address.netmask > + nic.broadcast = nic.ip_address.broadcast > + end > + > + remove_column :nics, :ip_address_id > + end > +end > diff --git a/src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb b/src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb > new file mode 100644 > index 0000000..53f7058 > --- /dev/null > +++ b/src/db/migrate/027_move_bonding_address_to_ip_addresses_table.rb > @@ -0,0 +1,54 @@ > +# > +# 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. > + > +class MoveBondingAddressToIpAddressesTable < ActiveRecord::Migration > + def self.up > + add_column :bondings, :ip_address_id, :integer, :null => true > + > + execute 'alter table bondings add constraint fk_bonding_ip_address > + foreign key (ip_address_id) references ip_addresses(id)' > + > + Bonding.find(:all).each do |bonding| > + ip_address = IpV4Address.new(:address => bonding.ip_addr, > + :netmask => bonding.netmask, > + :broadcast => bonding.broadcast, > + :gateway => bonding.ip_addr) > + end > + > + remove_column :bondings, :ip_addr > + remove_column :bondings, :netmask > + remove_column :bondings, :broadcast > + end > + > + def self.down > + t.string :ip_addr, :null => true, :limit => 15 > + t.string :netmask, :null => true, :limit => 15 > + t.string :broadcast, :null => true, :limit => 15 > + > + Bonding.each do |bonding| > + bonding.ip_addr = bonding.ip_address.address > + bonding.netmask = bonding.ip_address.netmask > + bonding.broadcast = bonding.ip_address.broadcast > + > + bonding.save! > + end > + > + remove_column :bondings, :ip_address_id > + end > +end > diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb > index 79d34e8..bbfbf3b 100755 > --- a/src/host-browser/host-browser.rb > +++ b/src/host-browser/host-browser.rb > @@ -283,10 +283,10 @@ class HostBrowser > > updated_nic = Nic.find_by_id(nic.id) > > - updated_nic.bandwidth = detail['BANDWIDTH'] > Did you really mean to remove bandwidth here? I still see it in the db table after the migrations and this seems to be the only place that a change to it occurs. > - updated_nic.ip_addr = detail['IP_ADDRESS'] > - updated_nic.netmask = detail['NETMASK'] > - updated_nic.broadcast = detail['BROADCAST'] > + updated_nic.bandwidth = detail['BANDWIDTH'] > + updated_nic.ip_address.address = detail['IP_ADDRESS'] > + updated_nic.ip_address.netmask = detail['NETMASK'] > + updated_nic.ip_address.broadcast = detail['BROADCAST'] > > updated_nic.save! > found=true > @@ -310,11 +310,14 @@ class HostBrowser > 'mac' => nic['MAC'].upcase, > 'bandwidth' => nic['BANDWIDTH'], > 'usage_type' => 1, > - 'ip_addr' => nic['IP_ADDRESS'], > - 'netmask' => nic['NETMASK'], > - 'broadcast' => nic['BROADCAST'], > 'boot_type_id' => boot_type.id) > > + ip_address = IpV4Address.new('address' => nic['IP_ADDRESS'], > + 'netmask' => nic['NETMASK'], > + 'broadcast' => nic['BROADCAST'], > + 'gateway' => nic['GATEWAY']) > + detail.ip_address = ip_address > + > host.nics << detail > end > > diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb > index 93f8448..fff7914 100644 > --- a/src/lib/managed_node_configuration.rb > +++ b/src/lib/managed_node_configuration.rb > @@ -51,7 +51,7 @@ class ManagedNodeConfiguration > host.bondings.each do |bonding| > result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}" > result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name}" > - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_addr}" if bonding.ip_addr > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_address.address}" if bonding.ip_address > result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/ONBOOT yes" > > bonding.nics.each do |nic| > @@ -97,9 +97,9 @@ class ManagedNodeConfiguration > result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO #{nic.boot_type.proto}" > > if nic.boot_type.proto == 'static' > - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" > - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" > - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_address.address}" > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.ip_address.netmask}" > + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.ip_address.broadcast}" > end > > if bridged > The host-browser and managed_node_configuration are the bits I'm least familiar with. That being said, after applying your patch and rebooting, everything seems to work. I also went through the changes above and everything looks good (besides the aformentioned note on bandwidth). > diff --git a/src/test/fixtures/bondings.yml b/src/test/fixtures/bondings.yml > index 29473c7..1a74f42 100644 > --- a/src/test/fixtures/bondings.yml > +++ b/src/test/fixtures/bondings.yml > @@ -4,8 +4,6 @@ mailservers_managed_node_bonding: > bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> > host_id: <%= Fixtures.identify(:mailservers_managed_node) %> > boot_type_id: <%= Fixtures.identify(:boot_type_static) %> > - ip_addr: 172.31.0.15 > - netmask: 255.255.255. > - broadcast: 172.31.0.255 > + ip_address_id: <%= Fixtures.identify(:ip_v4_mailservers_managed_node_bonding) %> > arp_ping_address: 172.31.0.100 > arp_interval: 0 > diff --git a/src/test/fixtures/ip_addresses.yml b/src/test/fixtures/ip_addresses.yml > new file mode 100644 > index 0000000..0472669 > --- /dev/null > +++ b/src/test/fixtures/ip_addresses.yml > @@ -0,0 +1,55 @@ > +ip_v4_one: > + type: IpV4Address > + address: 1.2.3.4 > + netmask: 255.255.255.0 > + gateway: 1.2.3.1 > + broadcast: 1.2.3.255 > + > +ip_v4_two: > + type: IpV4Address > + address: 2.3.4.5 > + netmask: 255.255.255.0 > + gateway: 1.2.3.1 > + broadcast: 2.3.4.255 > + > +ip_v4_three: > + type: IpV4Address > + address: 3.4.5.6 > + netmask: 255.255.255.0 > + gateway: 3.4.5.1 > + broadcast: 3.4.5.255 > + > +ip_v4_four: > + type: IpV4Address > + address: 3.4.5.6 > + netmask: 255.255.255.0 > + gateway: 3.4.5.1 > + broadcast: 3.4.5.255 > + > +ip_v4_mailserver_nic_one: > + type: IpV4Address > + address: 172.31.0.15 > + netmask: 255.255.255.0 > + gateway: 172.31.0.1 > + broadcast: 172.31.0.255 > + > +ip_v4_ldapserver_nic_one: > + type: IpV4Address > + address: 172.31.0.25 > + netmask: 255.255.255.0 > + gateway: 172.31.0.1 > + broadcast: 172.31.0.255 > + > +ip_v4_buildserver_nic_two: > + type: IpV4Address > + address: 172.31.0.31 > + netmask: 255.255.255.0 > + gateway: 172.31.0.1 > + broadcast: 172.31.0.255 > + > +ip_v4_mailservers_managed_node_bonding: > + type: IpV4Address > + address: 192.168.50.100 > + netmask: 255.255.255.0 > + gateway: 192.168.50.1 > + broadcast: 192.168.50.255 > diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml > index ccf71d2..dbbc511 100644 > --- a/src/test/fixtures/nics.yml > +++ b/src/test/fixtures/nics.yml > @@ -1,80 +1,80 @@ > one: > id: 1 > mac: '00:11:22:33:44:55' > - ip_addr: '1.2.3.4' > - usage_type: '1' > + ip_address_id: <%= Fixtures.identify(:ip_v4_one) %> > bandwidth: 100 > host_id: 10 > boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > + > two: > id: 2 > mac: 'AA:BB:CC:DD:EE:FF' > - ip_addr: '2.3.4.5' > + ip_address_id: <%= Fixtures.identify(:ip_v4_two) %> > usage_type: '2' > bandwidth: 1000 > host_id: 10 > boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > + > three: > id: 3 > mac: '00:FF:11:EE:22:DD' > - ip_addr: '3.4.5.6' > + ip_address_id: <%= Fixtures.identify(:ip_v4_three) %> > usage_type: '1' > bandwidth: 10 > host_id: 10 > boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > + > four: > id: 4 > mac: '00:FF:11:EE:22:DD' > - ip_addr: '3.4.5.6' > + ip_address: <%= Fixtures.identify(:ip_v4_four) %> > usage_type: '1' > bandwidth: 10 > host_id: 10 > boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > > mailserver_nic_one: > - mac: '00:11:22:33:44:55' > - usage_type: '1' > - bandwidth: 100 > - ip_addr: '172.31.0.15' > - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> > - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > + mac: '00:11:22:33:44:55' > + ip_address: <%= Fixtures.identify(:ip_v4_mailserver_nic_one) %> > + usage_type: '1' > + bandwidth: 100 > + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> > + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > > mailserver_nic_two: > - mac: '22:11:33:66:44:55' > - usage_type: '1' > - bandwidth: 100 > - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> > - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> > + mac: '22:11:33:66:44:55' > + usage_type: '1' > + bandwidth: 100 > + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> > + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> > > fileserver_nic_one: > - mac: '00:99:00:99:13:07' > - usage_type: '1' > - bandwidth: 100 > - host_id: <%= Fixtures.identify(:fileserver_managed_node) %> > - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> > + mac: '00:99:00:99:13:07' > + usage_type: '1' > + bandwidth: 100 > + host_id: <%= Fixtures.identify(:fileserver_managed_node) %> > + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> > > ldapserver_nic_one: > - mac: '00:03:02:00:09:06' > - usage_type: '1' > - bandwidth: 100 > - bridge: 'ovirtbr0' > - ip_addr: '172.31.0.25' > - host_id: <%= Fixtures.identify(:ldapserver_managed_node) %> > - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > + mac: '00:03:02:00:09:06' > + usage_type: '1' > + bandwidth: 100 > + bridge: 'ovirtbr0' > + ip_address_id: <%= Fixtures.identify(:ip_v4_ldapserver_nic_one) %> > + host_id: <%= Fixtures.identify(:ldapserver_managed_node) %> > + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > > buildserver_nic_one: > - mac: '07:17:19:65:03:38' > - usage_type: '1' > - bandwidth: 100 > - host_id: <%= Fixtures.identify(:buildserver_managed_node) %> > - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> > + mac: '07:17:19:65:03:38' > + usage_type: '1' > + bandwidth: 100 > + host_id: <%= Fixtures.identify(:buildserver_managed_node) %> > + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> > > buildserver_nic_two: > - mac: '07:17:19:65:03:39' > - usage_type: '1' > - bandwidth: 100 > - ip_addr: '172.31.0.31' > - netmask: '255.255.255.0' > - broadcast: '172.31.0.255' > - host_id: <%= Fixtures.identify(:buildserver_managed_node) %> > - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > + mac: '07:17:19:65:03:39' > + usage_type: '1' > + bandwidth: 100 > + ip_address_id: <%= Fixtures.identify(:ip_v4_buildserver_nic_two) %> > + host_id: <%= Fixtures.identify(:buildserver_managed_node) %> > + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> > diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb > index d0d8aa3..2158c8d 100644 > --- a/src/test/functional/managed_node_configuration_test.rb > +++ b/src/test/functional/managed_node_configuration_test.rb > @@ -83,17 +83,17 @@ cat <<\EOF > /var/tmp/node-augtool > 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 #{nic.boot_type.proto} > -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_addr} > -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} > -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} > +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_address.address} > +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.ip_address.netmask} > +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.ip_address.broadcast} > set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 > set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes > 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 #{nic.boot_type.proto} > -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_addr} > -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.netmask} > -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.broadcast} > +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_address.address} > +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.ip_address.netmask} > +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.ip_address.broadcast} > set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge > set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes > set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 > @@ -123,17 +123,17 @@ cat <<\EOF > /var/tmp/node-augtool > 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 #{nic1.boot_type.proto} > -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} > -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} > -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} > +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_address.address} > +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.ip_address.netmask} > +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.ip_address.broadcast} > set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 > set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes > 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 #{nic1.boot_type.proto} > -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_addr} > -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.netmask} > -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.broadcast} > +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_address.address} > +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.ip_address.netmask} > +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.ip_address.broadcast} > set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge > set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes > set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 > @@ -177,7 +177,7 @@ EOF > cat <<\EOF > /var/tmp/node-augtool > rm /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name} > set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name} > -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR 172.31.0.15 > +set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_address.address} > set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/ONBOOT yes > rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 > set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 > diff --git a/src/test/unit/ip_address_test.rb b/src/test/unit/ip_address_test.rb > new file mode 100644 > index 0000000..152578e > --- /dev/null > +++ b/src/test/unit/ip_address_test.rb > @@ -0,0 +1,8 @@ > +require 'test_helper' > + > +class IpAddressTest < ActiveSupport::TestCase > + # Replace this with your real tests. > + def test_truth > + assert true > + end > +end > diff --git a/src/test/unit/ip_v4_address_test.rb b/src/test/unit/ip_v4_address_test.rb > new file mode 100644 > index 0000000..14f0175 > --- /dev/null > +++ b/src/test/unit/ip_v4_address_test.rb > @@ -0,0 +1,99 @@ > +# > +# 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' > + > +class IpV4AddressTest < ActiveSupport::TestCase > + def setup > + @address = IpV4Address.new(:address => '192.168.50.2', > + :netmask => '255.255.255.0', > + :gateway => '192.168.50.1', > + :broadcast => '192.168.50.255') > + end > + > + # Ensures that an address must be supplied. > + # > + def test_valid_fails_without_address > + @address.address = nil > + > + flunk "An address must be present." if @address.valid? > + end > + > + # Ensures that an address has to be in the correct format. > + # > + def test_valid_fails_with_bad_address > + @address.address = '192.168' > + > + flunk "An address must be in the format ##.##.##.##." if @address.valid? > + end > + > + # Ensures that a netmask must be supplied. > + # > + def test_valid_fails_without_netmask > + @address.netmask = nil > + > + flunk "An address must have a netmask." if @address.valid? > + end > + > + # Ensures that a netmask must have the correct format. > + # > + def test_valid_fails_with_bad_netmask > + @address.netmask = 'farkle' > + > + flunk "A netmask must be in the format ##.##.##.##." if @address.valid? > + end > + > + # Ensures that a gateway must be supplied. > + # > + def test_valid_fails_without_gateway > + @address.gateway = nil > + > + flunk "A gateway address must be supplied." if @address.valid? > + end > + > + # Ensures that a gateway must be in the correct format. > + # > + def test_valid_fails_with_bad_gateway > + @address.gateway = '-3.a2.0.8' > + > + flunk "The gateway address must be in the format ##.##.##.##." if @address.valid? > + end > + > + # Ensures that a broadcast must be supplied. > + # > + def test_valid_fails_without_broadcast > + @address.broadcast = nil > + > + flunk "A broadcast addres must be supplied." if @address.valid? > + end > + > + # Ensures that a broadcast must be in the correct format. > + # > + def test_valid_fails_with_bad_broadcast > + @address.broadcast = '-3.2.0.8' > + > + flunk "The broadcast address must be in the format ##.##.##.##." if @address.valid? > + end > + > + # Ensures that a well-formed address is valid. > + # > + def test_valid > + flunk "There is an error with validation." unless @address.valid? > + end > +end > diff --git a/src/test/unit/ip_v6_address_test.rb b/src/test/unit/ip_v6_address_test.rb > new file mode 100644 > index 0000000..a2be85f > --- /dev/null > +++ b/src/test/unit/ip_v6_address_test.rb > @@ -0,0 +1,82 @@ > +# > +# 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' > + > +class IpV6AddressTest < ActiveSupport::TestCase > + def setup > + @address = IpV6Address.new(:address => 'fe80:0:0:0:200:f8ff:fe21:67cf', > + :gateway => ':::::::717', > + :prefix => '0000:0000:0000:0000:1234:1234:1234:1234') > + end > + > + # Ensures that the address must be provided. > + # > + def test_valid_fails_without_address > + @address.address = nil > + > + flunk "An address must be provided." if @address.valid? > + end > + > + # Ensures that the address must be in the correct format. > + # > + def test_valid_fails_with_bad_address > + @address.address = "farkle" > + > + flunk "The address must be in the correct format." if @address.valid? > + end > + > + # Ensures that the gateway must be provided. > + # > + def test_valid_fails_without_gateway > + @address.gateway = nil > + > + flunk "The gateway address must be provided." if @address.valid? > + end > + > + # Ensures that the gateway address is in the correct format. > + # > + def test_valid_fails_with_bad_gateway > + @address.gateway = '0-:::::::717' > + > + flunk "The gateway address must be in the correct format." if @address.valid? > + end > + > + # Ensures that the prefix must be provided. > + # > + def test_valid_fails_without_prefix > + @address.prefix = nil > + > + flunk "The prefix must be provided." if @address.valid? > + end > + > + # Ensures that the prefix is in the correct format. > + # > + def test_valid_fails_with_invalid_prefix > + @address.prefix = 'whoops' > + > + flunk "The prefix must be in the correct format." if @address.valid? > + end > + > + # Ensures that a well-formed address is considered valid. > + # > + def test_valid > + flunk "There is an problem with address validation." unless @address.valid? > + end > +end > diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb > index a0776a2..1de1e00 100644 > --- a/src/test/unit/nic_test.rb > +++ b/src/test/unit/nic_test.rb > @@ -20,6 +20,7 @@ > require File.dirname(__FILE__) + '/../test_helper' > > class NicTest < Test::Unit::TestCase > + fixtures :ip_addresses > fixtures :nics > > # Replace this with your real tests. > I ran these tests and everything succeeded flawlessly. For the most part, besides the mentioned items, everything seemed to come together and work nicely. Good job. -Mo From justin at redfish-group.com Wed Oct 22 09:39:26 2008 From: justin at redfish-group.com (Justin Clacherty) Date: Wed, 22 Oct 2008 19:39:26 +1000 Subject: [Ovirt-devel] unable to add hosts in web admin tool In-Reply-To: <48F35A21.6070101@redhat.com> References: <48F2EED5.6030908@redfish-group.com> <48F2F6C7.4020300@redfish-group.com> <48F2FEEB.10705@redhat.com> <48F3128A.7090800@redfish-group.com> <48F35A21.6070101@redhat.com> Message-ID: <48FEF4CE.2090002@redfish-group.com> Alan Pevec wrote: > Justin Clacherty wrote: >> Alan Pevec wrote: >>> Please check the logs /var/log/ovirt* to find out what's going on >> >> Can't see anything much in the logs on the management server. I had >> a look at ovirt.log on the managed host and it looks as though it is >> having problems talking to the management node, not sure what to do >> about it though. Here's the relevant part of the log: >> >> Resolving management.priv.ovirt.org.... 192.168.50.2 >> Connecting to management.priv.ovirt.org.|192.168.50.2|:80... connected. >> HTTP request sent, awaiting response... 500 Internal Server Error >> 2008-10-13 16:52:33 ERROR 500: Internal Server Error. > > Seems that ovirt-appliance firstboot failed and left appliance in bad > state, check the mongrel log. > You can also reinstall ovirt-appliance image and try > http://ovirt.org/page/TroubleShooting#Debugging_oVirt_Appliance_firstboot I waited for 0.94. Just tried it and the same thing is occuring. Is there a trick to reinstalling the ovirt-appliance? Do I just run "create-ovirt-appliance -e eth0" again? I don't see any console output on the terminal when I run "virsh start ovirt-appliance; virsh console ovirt-appliance", just the output of the start command "Domain ovirt-appliance started". Justin. From imain at redhat.com Wed Oct 22 17:36:38 2008 From: imain at redhat.com (Ian Main) Date: Wed, 22 Oct 2008 10:36:38 -0700 Subject: [Ovirt-devel] Re: Ovirt-qpid API In-Reply-To: <20081008211933.089ac7a8@tp.mains.net> References: <20081008211933.089ac7a8@tp.mains.net> Message-ID: <20081022103638.7b86fac4@tp.mains.net> OK, here's another go around at this. This has all the bells and whistles for network config. Please take a moment to go through it and make sure I've done this properly. BTW, the UUID from hal is in there so that we can map this host to the host in libvirt. Hopefully they will always be the same. Xen doesn't mess with this does it in dom0? Thanks! Ian From clalance at redhat.com Wed Oct 22 18:32:58 2008 From: clalance at redhat.com (Chris Lalancette) Date: Wed, 22 Oct 2008 20:32:58 +0200 Subject: [Ovirt-devel] Re: Ovirt-qpid API In-Reply-To: <20081022103638.7b86fac4@tp.mains.net> References: <20081008211933.089ac7a8@tp.mains.net> <20081022103638.7b86fac4@tp.mains.net> Message-ID: <48FF71DA.4090001@redhat.com> Ian Main wrote: > OK, here's another go around at this. This has all the bells and whistles > for network config. Please take a moment to go through it and make sure I've > done this properly. > > BTW, the UUID from hal is in there so that we can map this host to the host > in libvirt. Hopefully they will always be the same. Xen doesn't mess with > this does it in dom0? No, but it's completely unreliable in general. Don't rely on it for anything. Chris Lalancette From jim at meyering.net Wed Oct 22 19:22:29 2008 From: jim at meyering.net (Jim Meyering) Date: Wed, 22 Oct 2008 21:22:29 +0200 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <1224527121-13802-1-git-send-email-dpierce@redhat.com> (Darryl L. Pierce's message of "Mon, 20 Oct 2008 14:25:21 -0400") References: <1224527121-13802-1-git-send-email-dpierce@redhat.com> Message-ID: <8763nksadm.fsf@rho.meyering.net> "Darryl L. Pierce" wrote: > Rather than sending the node a series of scripts that load > kernel modules, or are tightly coupled to tools like augeas, > this patch introduces an encoding scheme for data. > > A line that begins with "bonding" describes a bonded interface. >>From it the configuration processor generates a simple bonding > file that includes an alias for the bonding kernel module Looks good to me. ACK. > A line that begins with "ifcfg" describes an network s/an /a / > interface. It will contain the mac address and interface name, > followed by all needed configuration values to bring the > interface up. From berrange at redhat.com Wed Oct 22 20:17:58 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 22 Oct 2008 21:17:58 +0100 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <1224527121-13802-1-git-send-email-dpierce@redhat.com> References: <1224527121-13802-1-git-send-email-dpierce@redhat.com> Message-ID: <20081022201758.GA1148@redhat.com> On Mon, Oct 20, 2008 at 02:25:21PM -0400, Darryl L. Pierce wrote: > Rather than sending the node a series of scripts that load > kernel modules, or are tightly coupled to tools like augeas, > this patch introduces an encoding scheme for data. > > A line that begins with "bonding" describes a bonded interface. > >From it the configuration processor generates a simple bonding > file that includes an alias for the bonding kernel module > > A line that begins with "ifcfg" describes an network > interface. It will contain the mac address and interface name, > followed by all needed configuration values to bring the > interface up. ACK, i think this looks good now. THough should we add some kind of version number field in the data file. A simple "version=1.0" line would just be sufficient to let us indicate changes in the future 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 Oct 22 20:18:28 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 22 Oct 2008 21:18:28 +0100 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <1224527170-13867-1-git-send-email-dpierce@redhat.com> References: <1224527170-13867-1-git-send-email-dpierce@redhat.com> Message-ID: <20081022201828.GB1148@redhat.com> On Mon, Oct 20, 2008 at 02:26:10PM -0400, Darryl L. Pierce wrote: > The system now takes an encoded configuration descriptor from the server. It > then parses from that a set of aliases for bondings if such exist. It then > also extracts configuration details for the various network interfaces on the > node. Afterward, it reloads module dependencies and then restarts the > networking service. ACK, this looks good now. 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 Oct 22 20:44:48 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Wed, 22 Oct 2008 21:44:48 +0100 Subject: [Ovirt-devel] Re: Ovirt-qpid API In-Reply-To: <48FF71DA.4090001@redhat.com> References: <20081008211933.089ac7a8@tp.mains.net> <20081022103638.7b86fac4@tp.mains.net> <48FF71DA.4090001@redhat.com> Message-ID: <20081022204448.GD1148@redhat.com> On Wed, Oct 22, 2008 at 08:32:58PM +0200, Chris Lalancette wrote: > Ian Main wrote: > > OK, here's another go around at this. This has all the bells and whistles > > for network config. Please take a moment to go through it and make sure I've > > done this properly. > > > > BTW, the UUID from hal is in there so that we can map this host to the host > > in libvirt. Hopefully they will always be the same. Xen doesn't mess with > > this does it in dom0? > > No, but it's completely unreliable in general. Don't rely on it for anything. There are really two UUIDs you need to consider - the hardware identity - the operating system identity. The hardware identity is the UUID. For real hardware, the it is usually, but not always, unique / usable. For virtual hardware it should be guarenteed unique unless the virt mgmt app screws up. We do need to include the UUID from hal in the info because ultimately you could be running the qpid agent in the guest for nested virt and want to correlate host & guest views of the world. The operating system identity is the security token (ie kerberos principle, or x509 cert) is what is guarenteed to provide a unique identity for an OS instance. So the OS identity is the key piece of info. The oVirt node bootstrap process will take care of assigning identity. The hardware identity is really only useful for asset tracking / inventory metadata, and to provide a heuristic for data correlation, if you don't have access to the true OS identity at that point. 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 sseago at redhat.com Wed Oct 22 20:49:17 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 22 Oct 2008 16:49:17 -0400 Subject: [Ovirt-devel] [PATCH server] Changed the database configuration to use a different database for development In-Reply-To: <1224529799-16294-1-git-send-email-dpierce@redhat.com> References: <1224529799-16294-1-git-send-email-dpierce@redhat.com> Message-ID: <48FF91CD.4070803@redhat.com> Darryl L. Pierce wrote: > Signed-off-by: Darryl L. Pierce > --- > scripts/ovirt-server-install | 2 +- > src/config/database.yml | 21 +++++++++++++++++---- > 2 files changed, 18 insertions(+), 5 deletions(-) > > > OK, now that I finally got my appliance up and running, ACK -- seems to work fine with this change. Scott From sseago at redhat.com Wed Oct 22 22:16:46 2008 From: sseago at redhat.com (Scott Seago) Date: Wed, 22 Oct 2008 22:16:46 +0000 Subject: [Ovirt-devel] [PATCH] added smart pool UI bits to search results and other flexigrids Message-ID: <1224713806-2128-1-git-send-email-sseago@redhat.com> Search results now show what smart pools the items belong to. There's also a pulldown selection to add selected results to a smart pool. 'Add to smart pool' pulldown is also available for the single-type tab pages for hosts, storage, vm pools, and vms. Signed-off-by: Scott Seago --- src/app/controllers/pool_controller.rb | 4 ++- src/app/controllers/search_controller.rb | 6 +++- src/app/controllers/smart_pools_controller.rb | 24 ++++++++++++++- src/app/models/host.rb | 3 +- src/app/models/pool.rb | 3 +- src/app/models/smart_pool.rb | 32 +++++++++++++++++++- src/app/models/storage_pool.rb | 3 +- src/app/models/vm.rb | 3 +- src/app/views/hardware/show_hosts.rhtml | 29 +++++++++++++++++++ src/app/views/hardware/show_storage.rhtml | 29 +++++++++++++++++++ src/app/views/hardware/show_vms.rhtml | 29 +++++++++++++++++++ src/app/views/resources/show_vms.rhtml | 29 +++++++++++++++++++ src/app/views/search/_grid.rhtml | 3 +- src/app/views/search/results.rhtml | 38 +++++++++++++++++++++++- src/app/views/vm/_grid.rhtml | 2 +- 15 files changed, 223 insertions(+), 14 deletions(-) diff --git a/src/app/controllers/pool_controller.rb b/src/app/controllers/pool_controller.rb index 063cc43..03d1316 100644 --- a/src/app/controllers/pool_controller.rb +++ b/src/app/controllers/pool_controller.rb @@ -110,7 +110,9 @@ class PoolController < ApplicationController attr_list = [:id, :description, :uuid, :num_vcpus_allocated, :memory_allocated_in_mb, :vnic_mac_addr, :state, :id] - attr_list.insert(3, [:host, :hostname]) if @pool.get_hardware_pool.can_view(@user) + if (@pool.is_a? VmResourcePool) and @pool.get_hardware_pool.can_view(@user) + attr_list.insert(3, [:host, :hostname]) + end json_list(args[:full_items], attr_list, [:all], args[:find_opts]) end diff --git a/src/app/controllers/search_controller.rb b/src/app/controllers/search_controller.rb index cc7e527..3789309 100644 --- a/src/app/controllers/search_controller.rb +++ b/src/app/controllers/search_controller.rb @@ -96,7 +96,9 @@ class SearchController < ApplicationController 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| + item_hash[:cell] = [] + item_hash[:cell] << item_hash[:id] if params[:checkboxes] + 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)} @@ -106,6 +108,8 @@ class SearchController < ApplicationController end end item_hash[:cell] << result[:percent] + item_hash[:cell] << item.smart_pools.collect {|pool| pool.name}.join(', ') + item_hash end render :json => json_hash.to_json diff --git a/src/app/controllers/smart_pools_controller.rb b/src/app/controllers/smart_pools_controller.rb index 6bfe367..90b1419 100644 --- a/src/app/controllers/smart_pools_controller.rb +++ b/src/app/controllers/smart_pools_controller.rb @@ -23,7 +23,8 @@ class SmartPoolsController < PoolController before_filter :pre_modify, :only => [:add_hosts, :remove_hosts, :add_storage, :remove_storage, :add_vms, :remove_vms, - :add_pools, :remove_pools] + :add_pools, :remove_pools, + :add_items] def show_vms show end @@ -106,7 +107,7 @@ class SmartPoolsController < PoolController end find_opts = {:conditions => conditions} end - { :full_items => full_items, :find_opts => find_opts, :include_pool => :true} + { :full_items => full_items, :find_opts => find_opts, :include_pool => false} end def add_hosts @@ -154,6 +155,25 @@ class SmartPoolsController < PoolController end end + def add_items + class_and_ids_str = params[:class_and_ids] + class_and_ids = class_and_ids_str.split(",").collect {|x| x.split("_")} + + begin + @pool.transaction do + class_and_ids.each do |class_and_id| + @pool.add_item(class_and_id[0].constantize.find(class_and_id[1].to_i)) + end + end + render :json => { :success => true, + :alert => "Add items to smart pool successful." } + rescue => ex + render :json => { :success => false, + :alert => "Add items to smart pool failed: " + ex.message } + end + + end + def destroy if @pool.destroy alert="Smart Pool was successfully deleted." diff --git a/src/app/models/host.rb b/src/app/models/host.rb index 546da19..429f0c0 100644 --- a/src/app/models/host.rb +++ b/src/app/models/host.rb @@ -48,7 +48,8 @@ class Host < ActiveRecord::Base :values => [ [ :created_at, 0, "created_at", :date ], [ :updated_at, 1, "updated_at", :date ] ], :terms => [ [ :hostname, 'H', "hostname" ], - [ :search_users, 'U', "search_users" ] ] + [ :search_users, 'U', "search_users" ] ], + :eager_load => :smart_pools KVM_HYPERVISOR_TYPE = "KVM" diff --git a/src/app/models/pool.rb b/src/app/models/pool.rb index d189649..b25fa55 100644 --- a/src/app/models/pool.rb +++ b/src/app/models/pool.rb @@ -89,7 +89,8 @@ class Pool < ActiveRecord::Base end acts_as_xapian :texts => [ :name ], - :terms => [ [ :search_users, 'U', "search_users" ] ] + :terms => [ [ :search_users, 'U', "search_users" ] ], + :eager_load => :smart_pools # this method lists pools with direct permission grants, but by default does # not include implied permissions (i.e. subtrees) diff --git a/src/app/models/smart_pool.rb b/src/app/models/smart_pool.rb index 9104ee5..0ecbe05 100644 --- a/src/app/models/smart_pool.rb +++ b/src/app/models/smart_pool.rb @@ -39,8 +39,13 @@ class SmartPool < Pool end def add_item(item) - tag = SmartPoolTag.new(:smart_pool => self, :tagged => item) - tag.save! + begin + tag = SmartPoolTag.new(:smart_pool => self, :tagged => item) + tag.save! + rescue ActiveRecord::RecordInvalid + # this is thrown if the tagged item already belongs to the smart pool + # this operation should be a no-op rather than an error + end end def remove_item(item) smart_pool_tags.find(:first, :conditions=> { @@ -67,4 +72,27 @@ class SmartPool < Pool end end + def self.smart_pools_for_user(user) + nested_pools = DirectoryPool.get_smart_root.full_set_nested( + :privilege => Permission::PRIV_MODIFY, :user => user, + :smart_pool_set => true) + user_pools = [] + other_pools = [] + nested_pools.each do |pool_element| + pool = pool_element[:obj] + if pool.name == user + pool_element[:children].each do |child_element| + child_pool = child_element[:obj] + user_pools <<[child_pool.name, child_pool.id] + end + else + pool_element[:children].each do |child_element| + child_pool = child_element[:obj] + other_pools << [pool.name + " > " + child_pool.name, child_pool.id] + end + end + end + user_pools[-1] << "break" + user_pools + other_pools + end end diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb index aed2902..9c550b8 100644 --- a/src/app/models/storage_pool.rb +++ b/src/app/models/storage_pool.rb @@ -40,7 +40,8 @@ class StoragePool < ActiveRecord::Base validates_presence_of :hardware_pool_id acts_as_xapian :texts => [ :ip_addr, :target, :export_path, :type ], - :terms => [ [ :search_users, 'U', "search_users" ] ] + :terms => [ [ :search_users, 'U', "search_users" ] ], + :eager_load => :smart_pools ISCSI = "iSCSI" NFS = "NFS" LVM = "LVM" diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index 2eff87a..61c407c 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -37,7 +37,8 @@ class Vm < ActiveRecord::Base :memory_allocated, :vnic_mac_addr acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], - :terms => [ [ :search_users, 'U', "search_users" ] ] + :terms => [ [ :search_users, 'U', "search_users" ] ], + :eager_load => :smart_pools BOOT_DEV_HD = "hd" BOOT_DEV_NETWORK = "network" diff --git a/src/app/views/hardware/show_hosts.rhtml b/src/app/views/hardware/show_hosts.rhtml index d33c920..2fd29bc 100644 --- a/src/app/views/hardware/show_hosts.rhtml +++ b/src/app/views/hardware/show_hosts.rhtml @@ -5,6 +5,21 @@ <%= image_tag "icon_move.png", :style=>"vertical-align:middle;" %>  Move +

  • + <%= image_tag "icon_smartpool.png", :style => "vertical-align:middle;" %>  Add to Smart Pool    <%= image_tag "icon_toolbar_arrow.gif", :style => "vertical-align:middle;" %> +
      + <% smart_pools = SmartPool.smart_pools_for_user(@user) %> + <% smart_pools.each_index { |index| %> +
    • + style="border-bottom: 1px solid #CCCCCC;" + <% end %> + > + <%=smart_pools[index][0]%> +
    • + <% } %> +
    +
  • <% if @pool.id != HardwarePool.get_default_pool.id %>
  • <%= image_tag "icon_remove.png" %>  Remove
  • <% end %> @@ -22,6 +37,20 @@ $('#move_link_hidden').click(); } } + function add_hosts_to_smart_pool(smart_pool) + { + var hosts = get_selected_hosts(); + if (validate_selected(hosts, "host")) { + $.post('<%= url_for :controller => "smart_pools", :action => "add_hosts" %>', + { resource_ids: hosts.toString(), id: smart_pool }, + function(data,status){ + $('#hosts_grid').flexReload(); + if (data.alert) { + $.jGrowl(data.alert); + } + }, 'json'); + } + } function remove_hosts() { var hosts = get_selected_hosts(); diff --git a/src/app/views/hardware/show_storage.rhtml b/src/app/views/hardware/show_storage.rhtml index 6466f9b..5ade456 100644 --- a/src/app/views/hardware/show_storage.rhtml +++ b/src/app/views/hardware/show_storage.rhtml @@ -6,6 +6,21 @@
  • + <%= image_tag "icon_smartpool.png", :style => "vertical-align:middle;" %>  Add to Smart Pool    <%= image_tag "icon_toolbar_arrow.gif", :style => "vertical-align:middle;" %> +
      + <% smart_pools = SmartPool.smart_pools_for_user(@user) %> + <% smart_pools.each_index { |index| %> +
    • + style="border-bottom: 1px solid #CCCCCC;" + <% end %> + > + <%=smart_pools[index][0]%> +
    • + <% } %> +
    +
  • +
  • <%= image_tag "icon_remove.png", :style=>"vertical-align:middle;" %>  Remove
  • @@ -13,6 +28,20 @@
    @@ -48,7 +82,7 @@ <%= render :partial => "/search/grid", :locals => { :table_id => "search_grid", :terms => @terms, :model => @model_param, - :checkboxes => false, + :checkboxes => true, :on_select => "results_select" } %>
    diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml index 85bf094..b137de6 100644 --- a/src/app/views/vm/_grid.rhtml +++ b/src/app/views/vm/_grid.rhtml @@ -29,7 +29,7 @@ {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: <%= table_id %>checkbox}, {display: 'Description', name : 'description', width : 180, sortable : true, align: 'left'}, {display: 'UUID', name : 'uuid', width : 180, sortable : true, align: 'left'}, - <% if @pool.get_hardware_pool.can_view(@user) %> + <% if (pool.is_a? VmResourcePool) and pool.get_hardware_pool.can_view(@user) %> {display: 'Host', name : 'host', width: 180, sortable : true, align: 'left' }, <% end %> {display: 'CPUs', name : 'num_vcpus_allocated', width : 40, sortable : true, align: 'left'}, -- 1.5.5.1 From dpierce at redhat.com Thu Oct 23 11:46:44 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 07:46:44 -0400 Subject: [Ovirt-devel] [PATCH server] Changed the database configuration to use a different database for development In-Reply-To: <48FF91CD.4070803@redhat.com> References: <1224529799-16294-1-git-send-email-dpierce@redhat.com> <48FF91CD.4070803@redhat.com> Message-ID: <49006424.3000306@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Scott Seago wrote: > Darryl L. Pierce wrote: >> Signed-off-by: Darryl L. Pierce >> --- >> scripts/ovirt-server-install | 2 +- >> src/config/database.yml | 21 +++++++++++++++++---- >> 2 files changed, 18 insertions(+), 5 deletions(-) >> >> >> > OK, now that I finally got my appliance up and running, > > ACK -- seems to work fine with this change. Pushed now into the repo. Everybody, please take a moment on your next pull to create the "ovirt_development" database in Postgres according to the steps in config/database.yml. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEUEARECAAYFAkkAZCEACgkQjaT4DmxOfxs6HwCfVh8c9sZpRGoB940qHTr6KO6/ lt4Al0+OHgtKqQXSnqaZMLIl4pWJPV0= =A93R -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 23 11:56:24 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 07:56:24 -0400 Subject: [Ovirt-devel] [PATCH server] Introduces a new IP address model. Moves IP addressing to a separate In-Reply-To: <48FE4186.3080407@redhat.com> References: <1224277864-13419-1-git-send-email-dpierce@redhat.com> <48FE4186.3080407@redhat.com> Message-ID: <49006668.2090708@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Mohammed Morsi wrote: > NACK. Applies nicely (albeit with whitespace complaints) and everything > seems to work with a few caveats as described below / inline. >> src/db/migrate/025_create_ip_addresses.rb | 46 +++++++++ >> > It seems since you've sent this out, migration 025 has been committed, > so you'll need to start at 026 Fixed. >> +# +IpAddress+ is the base class for all address related classes. >> +# >> +class IpAddress < ActiveRecord::Base >> + >> +end >> > Why isn't there a "has_many :nics" and a "has_many :bonding" > relationship in the IpAddress table. We wont be able to lookup nics / > bondings from an ip address otherwise. An IpAddress will only belong to either one Nic or one Bonding, so a has_many didn't seem logical here. And I didn't know of a scenario where there would be a search on IP address that couldn't be done from the nic or bonding level; i.e., "find all nics where ip_address_id in (find id from ip_addresses where ...)" So to keep the IpAddress clean I left it out. Since it doesn't require a DB change to do, if we do find a needed reason for modeling on the IpAddress side, we can introduce it then. >> diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb >> index 79d34e8..bbfbf3b 100755 >> --- a/src/host-browser/host-browser.rb >> +++ b/src/host-browser/host-browser.rb >> @@ -283,10 +283,10 @@ class HostBrowser >> >> updated_nic = Nic.find_by_id(nic.id) >> >> - updated_nic.bandwidth = detail['BANDWIDTH'] >> > Did you really mean to remove bandwidth here? I still see it in the db > table after the migrations and this seems to be the only place that a > change to it occurs. Weird, but the above's not missing. I didn't remove it intentionally, and it's not removed from the code in my repo. I'll send out an updated patch to make sure something didn't get munged. > I ran these tests and everything succeeded flawlessly. For the most > part, besides the mentioned items, everything seemed to come together > and work nicely. Good job. I'm sending an updated patch. Please review it as soon as you can. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkAZmIACgkQjaT4DmxOfxuCvgCdF71JAbGLhmp0giKyVI9LhhMv SjEAnRnLx70haGOa0vBlEgClkBVo3UfF =1lxu -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 23 11:57:47 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 07:57:47 -0400 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <20081022201758.GA1148@redhat.com> References: <1224527121-13802-1-git-send-email-dpierce@redhat.com> <20081022201758.GA1148@redhat.com> Message-ID: <490066BB.1040407@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > On Mon, Oct 20, 2008 at 02:25:21PM -0400, Darryl L. Pierce wrote: >> Rather than sending the node a series of scripts that load >> kernel modules, or are tightly coupled to tools like augeas, >> this patch introduces an encoding scheme for data. >> >> A line that begins with "bonding" describes a bonded interface. >> >From it the configuration processor generates a simple bonding >> file that includes an alias for the bonding kernel module >> >> A line that begins with "ifcfg" describes an network >> interface. It will contain the mac address and interface name, >> followed by all needed configuration values to bring the >> interface up. > > ACK, i think this looks good now. THough should we add some kind of version > number field in the data file. A simple "version=1.0" line would just be > sufficient to let us indicate changes in the future That's not a bad idea. I can add it as an incremental change during the UI implementation. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkAZrgACgkQjaT4DmxOfxsdjgCfWOk061XkSByeQj+ca7Ubhr3H h1wAoPMwakIRyivM+PD/0Qt8n16yENBi =qczI -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 23 11:59:53 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 23 Oct 2008 07:59:53 -0400 Subject: [Ovirt-devel] [PATCH server] Introduces a new IP address model. Moves IP addressing to a separate Message-ID: <1224763193-5697-1-git-send-email-dpierce@redhat.com> (NOTE: I checked and I believe the BANDWIDTH line that Mo saw previously is just showing where that line and those around it were reformatted to keep the code readable) You will need to run a migration after this patch to create the ip_addresses table and massage the bondings and nics tables to reference it. NEW CLASSES: IpAddress: base class for addresses IpV4Address: represents an IPv4 address IpV6Address: represents an IPv6 address Signed-off-by: Darryl L. Pierce --- src/app/models/bonding.rb | 1 + src/app/models/ip_address.rb | 25 +++++ src/app/models/ip_v4_address.rb | 46 +++++++++ src/app/models/ip_v6_address.rb | 40 ++++++++ src/app/models/nic.rb | 2 + src/db/migrate/026_create_ip_addresses.rb | 46 +++++++++ ...027_move_nic_addresses_to_ip_addresses_table.rb | 56 +++++++++++ ...8_move_bonding_address_to_ip_addresses_table.rb | 54 +++++++++++ src/host-browser/host-browser.rb | 17 ++-- src/lib/managed_node_configuration.rb | 8 +- src/test/fixtures/bondings.yml | 4 +- src/test/fixtures/ip_addresses.yml | 55 +++++++++++ src/test/fixtures/nics.yml | 82 ++++++++-------- .../functional/managed_node_configuration_test.rb | 26 +++--- src/test/unit/ip_address_test.rb | 8 ++ src/test/unit/ip_v4_address_test.rb | 99 ++++++++++++++++++++ src/test/unit/ip_v6_address_test.rb | 82 ++++++++++++++++ src/test/unit/nic_test.rb | 1 + 18 files changed, 584 insertions(+), 68 deletions(-) create mode 100644 src/app/models/ip_address.rb create mode 100644 src/app/models/ip_v4_address.rb create mode 100644 src/app/models/ip_v6_address.rb create mode 100644 src/db/migrate/026_create_ip_addresses.rb create mode 100644 src/db/migrate/027_move_nic_addresses_to_ip_addresses_table.rb create mode 100644 src/db/migrate/028_move_bonding_address_to_ip_addresses_table.rb create mode 100644 src/test/fixtures/ip_addresses.yml create mode 100644 src/test/unit/ip_address_test.rb create mode 100644 src/test/unit/ip_v4_address_test.rb create mode 100644 src/test/unit/ip_v6_address_test.rb diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 006c261..2fa4aa8 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -51,6 +51,7 @@ class Bonding < ActiveRecord::Base belongs_to :host belongs_to :bonding_type belongs_to :boot_type + belongs_to :ip_address has_and_belongs_to_many :nics, :join_table => 'bondings_nics', diff --git a/src/app/models/ip_address.rb b/src/app/models/ip_address.rb new file mode 100644 index 0000000..f48cd4e --- /dev/null +++ b/src/app/models/ip_address.rb @@ -0,0 +1,25 @@ +# +# ip_address.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. + +# +IpAddress+ is the base class for all address related classes. +# +class IpAddress < ActiveRecord::Base + +end diff --git a/src/app/models/ip_v4_address.rb b/src/app/models/ip_v4_address.rb new file mode 100644 index 0000000..4495a13 --- /dev/null +++ b/src/app/models/ip_v4_address.rb @@ -0,0 +1,46 @@ +# ip_v4_address.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. + +# +IpV4Address+ represents a single IPv4 address. +# +class IpV4Address < IpAddress + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} + + validates_presence_of :address, + :message => 'An address must be supplied.' + validates_format_of :address, + :with => ADDRESS_TEST + + validates_presence_of :netmask, + :message => 'A netmask must be supplied.' + validates_format_of :netmask, + :with => ADDRESS_TEST + + validates_presence_of :gateway, + :message => 'A gateway address must be supplied.' + validates_format_of :gateway, + :with => ADDRESS_TEST + + validates_presence_of :broadcast, + :message => 'A broadcast address must be supplied.' + validates_format_of :broadcast, + :with => ADDRESS_TEST + +end diff --git a/src/app/models/ip_v6_address.rb b/src/app/models/ip_v6_address.rb new file mode 100644 index 0000000..1395886 --- /dev/null +++ b/src/app/models/ip_v6_address.rb @@ -0,0 +1,40 @@ +# ip_v6_address.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. + +# +IpV6Address+ represents a single IPv6 address. +# +class IpV6Address < IpAddress + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} + + validates_presence_of :address, + :message => 'An address must be provided.' + validates_format_of :address, + :with => ADDRESS_TEST + + validates_presence_of :gateway, + :message => 'A gateway address must be provided.' + validates_format_of :gateway, + :with => ADDRESS_TEST + + validates_presence_of :prefix, + :message => 'A prefix must be provided.' + validates_format_of :prefix, + :with => ADDRESS_TEST +end diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb index baf7095..69568a9 100644 --- a/src/app/models/nic.rb +++ b/src/app/models/nic.rb @@ -22,4 +22,6 @@ class Nic < ActiveRecord::Base belongs_to :boot_type has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' + + belongs_to :ip_address end diff --git a/src/db/migrate/026_create_ip_addresses.rb b/src/db/migrate/026_create_ip_addresses.rb new file mode 100644 index 0000000..0f528f4 --- /dev/null +++ b/src/db/migrate/026_create_ip_addresses.rb @@ -0,0 +1,46 @@ +# +# 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. + +# Creates a single-inheritance table for both IPv4 and IPv6 +# addresses. +# +class CreateIpAddresses < ActiveRecord::Migration + def self.up + create_table :ip_addresses do |t| + t.string :type + + # common attributes + t.string :address, :limit => 39 + t.string :gateway, :limit => 39 + + # attributes for IPv4 (type=IpV4Address) + t.string :netmask, :limit => 15 + t.string :broadcast, :limit => 15 + + # attributes for IPv6 (type=IpV6Address) + t.string :prefix, :limit => 39 + + t.timestamps + end + end + + def self.down + drop_table :ip_addresses + end +end diff --git a/src/db/migrate/027_move_nic_addresses_to_ip_addresses_table.rb b/src/db/migrate/027_move_nic_addresses_to_ip_addresses_table.rb new file mode 100644 index 0000000..b873e4d --- /dev/null +++ b/src/db/migrate/027_move_nic_addresses_to_ip_addresses_table.rb @@ -0,0 +1,56 @@ +# +# 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. + +class MoveNicAddressesToIpAddressesTable < ActiveRecord::Migration + def self.up + add_column :nics, :ip_address_id, :integer, :null => true + + execute 'alter table nics add constraint fk_nic_ip_address + foreign key (ip_address_id) references ip_addresses(id)' + + Nic.find(:all).each do |nic| + address = IpV4Address.new(:address => nic.ip_addr, + :netmask => nic.netmask, + :broadcast => nic.broadcast, + :gateway => nic.ip_addr) + nic.ip_address = address + + nic.save! + address.save! + end + + remove_column :nics, :ip_addr + remove_column :nics, :netmask + remove_column :nics, :broadcast + end + + def self.down + add_column :nics, :ip_addr, :string, :limit => 16 + add_column :nics, :netmask, :string, :limit => 16 + add_column :nics, :broadcast, :string, :limit => 16 + + Nic.find(:all).each do |nic| + nic.ip_addr = nic.ip_address.address + nic.netmask = nic.ip_address.netmask + nic.broadcast = nic.ip_address.broadcast + end + + remove_column :nics, :ip_address_id + end +end diff --git a/src/db/migrate/028_move_bonding_address_to_ip_addresses_table.rb b/src/db/migrate/028_move_bonding_address_to_ip_addresses_table.rb new file mode 100644 index 0000000..b0fed99 --- /dev/null +++ b/src/db/migrate/028_move_bonding_address_to_ip_addresses_table.rb @@ -0,0 +1,54 @@ +# +# 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. + +class MoveBondingAddressToIpAddressesTable < ActiveRecord::Migration + def self.up + add_column :bondings, :ip_address_id, :integer, :null => true + + execute 'alter table bondings add constraint fk_bonding_ip_address + foreign key (ip_address_id) references ip_addresses(id)' + + Bonding.find(:all).each do |bonding| + ip_address = IpV4Address.new(:address => bonding.ip_addr, + :netmask => bonding.netmask, + :broadcast => bonding.broadcast, + :gateway => bonding.ip_addr) + end + + remove_column :bondings, :ip_addr + remove_column :bondings, :netmask + remove_column :bondings, :broadcast + end + + def self.down + t.string :ip_addr, :null => true, :limit => 15 + t.string :netmask, :null => true, :limit => 15 + t.string :broadcast, :null => true, :limit => 15 + + Bonding.each do |bonding| + bonding.ip_addr = bonding.ip_address.address + bonding.netmask = bonding.ip_address.netmask + bonding.broadcast = bonding.ip_address.broadcast + + bonding.save! + end + + remove_column :bondings, :ip_address_id + end +end diff --git a/src/host-browser/host-browser.rb b/src/host-browser/host-browser.rb index 79d34e8..bbfbf3b 100755 --- a/src/host-browser/host-browser.rb +++ b/src/host-browser/host-browser.rb @@ -283,10 +283,10 @@ class HostBrowser updated_nic = Nic.find_by_id(nic.id) - updated_nic.bandwidth = detail['BANDWIDTH'] - updated_nic.ip_addr = detail['IP_ADDRESS'] - updated_nic.netmask = detail['NETMASK'] - updated_nic.broadcast = detail['BROADCAST'] + updated_nic.bandwidth = detail['BANDWIDTH'] + updated_nic.ip_address.address = detail['IP_ADDRESS'] + updated_nic.ip_address.netmask = detail['NETMASK'] + updated_nic.ip_address.broadcast = detail['BROADCAST'] updated_nic.save! found=true @@ -310,11 +310,14 @@ class HostBrowser 'mac' => nic['MAC'].upcase, 'bandwidth' => nic['BANDWIDTH'], 'usage_type' => 1, - 'ip_addr' => nic['IP_ADDRESS'], - 'netmask' => nic['NETMASK'], - 'broadcast' => nic['BROADCAST'], 'boot_type_id' => boot_type.id) + ip_address = IpV4Address.new('address' => nic['IP_ADDRESS'], + 'netmask' => nic['NETMASK'], + 'broadcast' => nic['BROADCAST'], + 'gateway' => nic['GATEWAY']) + detail.ip_address = ip_address + host.nics << detail end diff --git a/src/lib/managed_node_configuration.rb b/src/lib/managed_node_configuration.rb index 93f8448..fff7914 100644 --- a/src/lib/managed_node_configuration.rb +++ b/src/lib/managed_node_configuration.rb @@ -51,7 +51,7 @@ class ManagedNodeConfiguration host.bondings.each do |bonding| result.puts "rm #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}" result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_addr}" if bonding.ip_addr + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_address.address}" if bonding.ip_address result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{bonding.interface_name}/ONBOOT yes" bonding.nics.each do |nic| @@ -97,9 +97,9 @@ class ManagedNodeConfiguration result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BOOTPROTO #{nic.boot_type.proto}" if nic.boot_type.proto == 'static' - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_addr}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.netmask}" - result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.broadcast}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/IPADDR #{nic.ip_address.address}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/NETMASK #{nic.ip_address.netmask}" + result.puts "set #{NIC_ENTRY_PREFIX}/ifcfg-#{iface_name}/BROADCAST #{nic.ip_address.broadcast}" end if bridged diff --git a/src/test/fixtures/bondings.yml b/src/test/fixtures/bondings.yml index 29473c7..1a74f42 100644 --- a/src/test/fixtures/bondings.yml +++ b/src/test/fixtures/bondings.yml @@ -4,8 +4,6 @@ mailservers_managed_node_bonding: bonding_type_id: <%= Fixtures.identify(:link_aggregation_bonding_type) %> host_id: <%= Fixtures.identify(:mailservers_managed_node) %> boot_type_id: <%= Fixtures.identify(:boot_type_static) %> - ip_addr: 172.31.0.15 - netmask: 255.255.255. - broadcast: 172.31.0.255 + ip_address_id: <%= Fixtures.identify(:ip_v4_mailservers_managed_node_bonding) %> arp_ping_address: 172.31.0.100 arp_interval: 0 diff --git a/src/test/fixtures/ip_addresses.yml b/src/test/fixtures/ip_addresses.yml new file mode 100644 index 0000000..0472669 --- /dev/null +++ b/src/test/fixtures/ip_addresses.yml @@ -0,0 +1,55 @@ +ip_v4_one: + type: IpV4Address + address: 1.2.3.4 + netmask: 255.255.255.0 + gateway: 1.2.3.1 + broadcast: 1.2.3.255 + +ip_v4_two: + type: IpV4Address + address: 2.3.4.5 + netmask: 255.255.255.0 + gateway: 1.2.3.1 + broadcast: 2.3.4.255 + +ip_v4_three: + type: IpV4Address + address: 3.4.5.6 + netmask: 255.255.255.0 + gateway: 3.4.5.1 + broadcast: 3.4.5.255 + +ip_v4_four: + type: IpV4Address + address: 3.4.5.6 + netmask: 255.255.255.0 + gateway: 3.4.5.1 + broadcast: 3.4.5.255 + +ip_v4_mailserver_nic_one: + type: IpV4Address + address: 172.31.0.15 + netmask: 255.255.255.0 + gateway: 172.31.0.1 + broadcast: 172.31.0.255 + +ip_v4_ldapserver_nic_one: + type: IpV4Address + address: 172.31.0.25 + netmask: 255.255.255.0 + gateway: 172.31.0.1 + broadcast: 172.31.0.255 + +ip_v4_buildserver_nic_two: + type: IpV4Address + address: 172.31.0.31 + netmask: 255.255.255.0 + gateway: 172.31.0.1 + broadcast: 172.31.0.255 + +ip_v4_mailservers_managed_node_bonding: + type: IpV4Address + address: 192.168.50.100 + netmask: 255.255.255.0 + gateway: 192.168.50.1 + broadcast: 192.168.50.255 diff --git a/src/test/fixtures/nics.yml b/src/test/fixtures/nics.yml index ccf71d2..dbbc511 100644 --- a/src/test/fixtures/nics.yml +++ b/src/test/fixtures/nics.yml @@ -1,80 +1,80 @@ one: id: 1 mac: '00:11:22:33:44:55' - ip_addr: '1.2.3.4' - usage_type: '1' + ip_address_id: <%= Fixtures.identify(:ip_v4_one) %> bandwidth: 100 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + two: id: 2 mac: 'AA:BB:CC:DD:EE:FF' - ip_addr: '2.3.4.5' + ip_address_id: <%= Fixtures.identify(:ip_v4_two) %> usage_type: '2' bandwidth: 1000 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + three: id: 3 mac: '00:FF:11:EE:22:DD' - ip_addr: '3.4.5.6' + ip_address_id: <%= Fixtures.identify(:ip_v4_three) %> usage_type: '1' bandwidth: 10 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + four: id: 4 mac: '00:FF:11:EE:22:DD' - ip_addr: '3.4.5.6' + ip_address: <%= Fixtures.identify(:ip_v4_four) %> usage_type: '1' bandwidth: 10 host_id: 10 boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> mailserver_nic_one: - mac: '00:11:22:33:44:55' - usage_type: '1' - bandwidth: 100 - ip_addr: '172.31.0.15' - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + mac: '00:11:22:33:44:55' + ip_address: <%= Fixtures.identify(:ip_v4_mailserver_nic_one) %> + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> mailserver_nic_two: - mac: '22:11:33:66:44:55' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:mailservers_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + mac: '22:11:33:66:44:55' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:mailservers_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> fileserver_nic_one: - mac: '00:99:00:99:13:07' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:fileserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + mac: '00:99:00:99:13:07' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:fileserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> ldapserver_nic_one: - mac: '00:03:02:00:09:06' - usage_type: '1' - bandwidth: 100 - bridge: 'ovirtbr0' - ip_addr: '172.31.0.25' - host_id: <%= Fixtures.identify(:ldapserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + mac: '00:03:02:00:09:06' + usage_type: '1' + bandwidth: 100 + bridge: 'ovirtbr0' + ip_address_id: <%= Fixtures.identify(:ip_v4_ldapserver_nic_one) %> + host_id: <%= Fixtures.identify(:ldapserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> buildserver_nic_one: - mac: '07:17:19:65:03:38' - usage_type: '1' - bandwidth: 100 - host_id: <%= Fixtures.identify(:buildserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> + mac: '07:17:19:65:03:38' + usage_type: '1' + bandwidth: 100 + host_id: <%= Fixtures.identify(:buildserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_dhcp) %> buildserver_nic_two: - mac: '07:17:19:65:03:39' - usage_type: '1' - bandwidth: 100 - ip_addr: '172.31.0.31' - netmask: '255.255.255.0' - broadcast: '172.31.0.255' - host_id: <%= Fixtures.identify(:buildserver_managed_node) %> - boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> + mac: '07:17:19:65:03:39' + usage_type: '1' + bandwidth: 100 + ip_address_id: <%= Fixtures.identify(:ip_v4_buildserver_nic_two) %> + host_id: <%= Fixtures.identify(:buildserver_managed_node) %> + boot_type_id: <%= Fixtures.identify(:boot_type_static_ip) %> diff --git a/src/test/functional/managed_node_configuration_test.rb b/src/test/functional/managed_node_configuration_test.rb index d0d8aa3..2158c8d 100644 --- a/src/test/functional/managed_node_configuration_test.rb +++ b/src/test/functional/managed_node_configuration_test.rb @@ -83,17 +83,17 @@ cat <<\EOF > /var/tmp/node-augtool 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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes 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 #{nic.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 @@ -123,17 +123,17 @@ cat <<\EOF > /var/tmp/node-augtool 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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/IPADDR #{nic1.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/NETMASK #{nic1.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BROADCAST #{nic1.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-eth0/BRIDGE ovirtbr0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/ONBOOT yes 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 #{nic1.boot_type.proto} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_addr} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.netmask} -set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.broadcast} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/IPADDR #{nic1.ip_address.address} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/NETMASK #{nic1.ip_address.netmask} +set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/BROADCAST #{nic1.ip_address.broadcast} set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/TYPE bridge set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/PEERNTP yes set /files/etc/sysconfig/network-scripts/ifcfg-ovirtbr0/DELAY 0 @@ -177,7 +177,7 @@ EOF cat <<\EOF > /var/tmp/node-augtool rm /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name} set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/DEVICE #{bonding.interface_name} -set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR 172.31.0.15 +set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/IPADDR #{bonding.ip_address.address} set /files/etc/sysconfig/network-scripts/ifcfg-#{bonding.interface_name}/ONBOOT yes rm /files/etc/sysconfig/network-scripts/ifcfg-eth0 set /files/etc/sysconfig/network-scripts/ifcfg-eth0/DEVICE eth0 diff --git a/src/test/unit/ip_address_test.rb b/src/test/unit/ip_address_test.rb new file mode 100644 index 0000000..152578e --- /dev/null +++ b/src/test/unit/ip_address_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class IpAddressTest < ActiveSupport::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/src/test/unit/ip_v4_address_test.rb b/src/test/unit/ip_v4_address_test.rb new file mode 100644 index 0000000..14f0175 --- /dev/null +++ b/src/test/unit/ip_v4_address_test.rb @@ -0,0 +1,99 @@ +# +# 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' + +class IpV4AddressTest < ActiveSupport::TestCase + def setup + @address = IpV4Address.new(:address => '192.168.50.2', + :netmask => '255.255.255.0', + :gateway => '192.168.50.1', + :broadcast => '192.168.50.255') + end + + # Ensures that an address must be supplied. + # + def test_valid_fails_without_address + @address.address = nil + + flunk "An address must be present." if @address.valid? + end + + # Ensures that an address has to be in the correct format. + # + def test_valid_fails_with_bad_address + @address.address = '192.168' + + flunk "An address must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a netmask must be supplied. + # + def test_valid_fails_without_netmask + @address.netmask = nil + + flunk "An address must have a netmask." if @address.valid? + end + + # Ensures that a netmask must have the correct format. + # + def test_valid_fails_with_bad_netmask + @address.netmask = 'farkle' + + flunk "A netmask must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a gateway must be supplied. + # + def test_valid_fails_without_gateway + @address.gateway = nil + + flunk "A gateway address must be supplied." if @address.valid? + end + + # Ensures that a gateway must be in the correct format. + # + def test_valid_fails_with_bad_gateway + @address.gateway = '-3.a2.0.8' + + flunk "The gateway address must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a broadcast must be supplied. + # + def test_valid_fails_without_broadcast + @address.broadcast = nil + + flunk "A broadcast addres must be supplied." if @address.valid? + end + + # Ensures that a broadcast must be in the correct format. + # + def test_valid_fails_with_bad_broadcast + @address.broadcast = '-3.2.0.8' + + flunk "The broadcast address must be in the format ##.##.##.##." if @address.valid? + end + + # Ensures that a well-formed address is valid. + # + def test_valid + flunk "There is an error with validation." unless @address.valid? + end +end diff --git a/src/test/unit/ip_v6_address_test.rb b/src/test/unit/ip_v6_address_test.rb new file mode 100644 index 0000000..a2be85f --- /dev/null +++ b/src/test/unit/ip_v6_address_test.rb @@ -0,0 +1,82 @@ +# +# 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' + +class IpV6AddressTest < ActiveSupport::TestCase + def setup + @address = IpV6Address.new(:address => 'fe80:0:0:0:200:f8ff:fe21:67cf', + :gateway => ':::::::717', + :prefix => '0000:0000:0000:0000:1234:1234:1234:1234') + end + + # Ensures that the address must be provided. + # + def test_valid_fails_without_address + @address.address = nil + + flunk "An address must be provided." if @address.valid? + end + + # Ensures that the address must be in the correct format. + # + def test_valid_fails_with_bad_address + @address.address = "farkle" + + flunk "The address must be in the correct format." if @address.valid? + end + + # Ensures that the gateway must be provided. + # + def test_valid_fails_without_gateway + @address.gateway = nil + + flunk "The gateway address must be provided." if @address.valid? + end + + # Ensures that the gateway address is in the correct format. + # + def test_valid_fails_with_bad_gateway + @address.gateway = '0-:::::::717' + + flunk "The gateway address must be in the correct format." if @address.valid? + end + + # Ensures that the prefix must be provided. + # + def test_valid_fails_without_prefix + @address.prefix = nil + + flunk "The prefix must be provided." if @address.valid? + end + + # Ensures that the prefix is in the correct format. + # + def test_valid_fails_with_invalid_prefix + @address.prefix = 'whoops' + + flunk "The prefix must be in the correct format." if @address.valid? + end + + # Ensures that a well-formed address is considered valid. + # + def test_valid + flunk "There is an problem with address validation." unless @address.valid? + end +end diff --git a/src/test/unit/nic_test.rb b/src/test/unit/nic_test.rb index a0776a2..1de1e00 100644 --- a/src/test/unit/nic_test.rb +++ b/src/test/unit/nic_test.rb @@ -20,6 +20,7 @@ require File.dirname(__FILE__) + '/../test_helper' class NicTest < Test::Unit::TestCase + fixtures :ip_addresses fixtures :nics # Replace this with your real tests. -- 1.5.5.1 From dpierce at redhat.com Thu Oct 23 12:01:59 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 08:01:59 -0400 Subject: [Ovirt-devel] [PATCH server] Replaced the config scripts with configuration encoding. In-Reply-To: <20081022201758.GA1148@redhat.com> References: <1224527121-13802-1-git-send-email-dpierce@redhat.com> <20081022201758.GA1148@redhat.com> Message-ID: <490067B7.3070001@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > On Mon, Oct 20, 2008 at 02:25:21PM -0400, Darryl L. Pierce wrote: >> Rather than sending the node a series of scripts that load >> kernel modules, or are tightly coupled to tools like augeas, >> this patch introduces an encoding scheme for data. >> >> A line that begins with "bonding" describes a bonded interface. >> >From it the configuration processor generates a simple bonding >> file that includes an alias for the bonding kernel module >> >> A line that begins with "ifcfg" describes an network >> interface. It will contain the mac address and interface name, >> followed by all needed configuration values to bring the >> interface up. > > ACK, i think this looks good now. THough should we add some kind of version > number field in the data file. A simple "version=1.0" line would just be > sufficient to let us indicate changes in the future Pushed. Thanks. :) - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkAZ7QACgkQjaT4DmxOfxsWgwCghlDSP5jjLaBtA1v3XD2WrzcE rRkAn141Xx02b8rKC0UjazEnuccsfE8u =yq6U -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 23 12:02:26 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 08:02:26 -0400 Subject: [Ovirt-devel] [PATCH node] Updated the configuration processing to remove the need for scripts. In-Reply-To: <20081022201828.GB1148@redhat.com> References: <1224527170-13867-1-git-send-email-dpierce@redhat.com> <20081022201828.GB1148@redhat.com> Message-ID: <490067D2.6000101@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Daniel P. Berrange wrote: > On Mon, Oct 20, 2008 at 02:26:10PM -0400, Darryl L. Pierce wrote: >> The system now takes an encoded configuration descriptor from the server. It >> then parses from that a set of aliases for bondings if such exist. It then >> also extracts configuration details for the various network interfaces on the >> node. Afterward, it reloads module dependencies and then restarts the >> networking service. > > ACK, this looks good now. Pushed. Thanks. :) - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkAZ84ACgkQjaT4DmxOfxvUlgCfXiop3GwjY0gEjt/Ue2Taexg9 RWEAn2QgI61mYj0b7XUgH+vxnV6+Li6N =ZBP3 -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 23 12:20:51 2008 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 23 Oct 2008 08:20:51 -0400 Subject: [Ovirt-devel] [PATCH server] BUG#467099: Fixes a uniqueness constraint bug in bondings_nics. Message-ID: <1224764451-7447-1-git-send-email-dpierce@redhat.com> It moves the uniqueness constraint from being on the pair (bonding_id, nic_id) to being only on nic_id. The goal is just to ensure that a nic is only mapped once to any bonding. You will need to run a migration with this patch. Signed-off-by: Darryl L. Pierce --- ...ll.rb => 026_allow_nic_boot_type_to_be_null.rb} | 0 ..._fix_uniqueness_constraints_in_bondings_nics.rb | 11 +++++++++++ 2 files changed, 11 insertions(+), 0 deletions(-) rename src/db/migrate/{025_allow_nic_boot_type_to_be_null.rb => 026_allow_nic_boot_type_to_be_null.rb} (100%) create mode 100644 src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb diff --git a/src/db/migrate/025_allow_nic_boot_type_to_be_null.rb b/src/db/migrate/026_allow_nic_boot_type_to_be_null.rb similarity index 100% rename from src/db/migrate/025_allow_nic_boot_type_to_be_null.rb rename to src/db/migrate/026_allow_nic_boot_type_to_be_null.rb diff --git a/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb b/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb new file mode 100644 index 0000000..0daa210 --- /dev/null +++ b/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb @@ -0,0 +1,11 @@ +class FixUniquenessConstraintsInBondingsNics < ActiveRecord::Migration + def self.up + remove_index :bondings_nics, [:bonding_id, :nic_id] + add_index :bondings_nics, :nic_id, :unique => true + end + + def self.down + remove_index :bondings_nics, :nic_id + add_index :bondings_nics, [:bonding_id, :nic_id], :unique => true + end +end -- 1.5.5.1 From dpierce at redhat.com Thu Oct 23 13:14:05 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 09:14:05 -0400 Subject: [Ovirt-devel] Re: Ovirt-qpid API In-Reply-To: <20081022103638.7b86fac4@tp.mains.net> References: <20081008211933.089ac7a8@tp.mains.net> <20081022103638.7b86fac4@tp.mains.net> Message-ID: <4900789D.5040207@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Ian Main wrote: > > > > > > > > > > > We would also need a way to send option arguments for the bonding kernel module, such as miimon value, arp-interval and targets, etc. Can we add a flexible field for passing such arguments, or else select a subset that's needed and surface them as fields in the above? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkAeJoACgkQjaT4DmxOfxv75ACgrqmuzFZVpAsYGFEebnylrtHH kvEAn1YOs7fP+tG3KsK4NuLK1g3Q9jt2 =Z8z+ -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From jguiditt at redhat.com Thu Oct 23 16:01:58 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 23 Oct 2008 12:01:58 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? Message-ID: <1224777718.4805.33.camel@localhost.localdomain> I have been meaning to send an email to the list about this for weeks. I am looking to assemble a consensus on how to handle version control for some 3rd party code we are using in the server suite. These are, for the moment at least, rails plugins (not necessarily available as gems or rpms) and javascript libraries/plugins. There are a couple of issues we need to solve here: 1) Keeping up to date on these bits of code, which may or may not be available in git (a lot of the javascript is hosted in svn, some no version control at all) 2) We may have our own patches that we need to carry, though of course we will try to push them upstream. 3) Many of these items do no follow our practices- specifically whitespace and ^Ms. This makes it difficult to drop in the updated code and compare it to our (cleaned-up) version in git, especially if we have made any changes/customizations. The only idea I have heard/read about that seems to be a possible good solution is sub-modules, but there seems to be a fair amount of maintenance involved in keeping them in synch with our main repo whenever we do an update or patch to one of the modules. From what I have seen, you have to have a reference in the main repo pointing to the sub-module, including correct version. This would mean every update to the sub-module would also require an update to the server repo, which is easy enough, but feels like something that would easily be missed. Maybe this is easier than I think, perhaps someone else can clear that up for me. Also, if there are issues I did not outline above, please speak up so we can consider those as well. -j From dpierce at redhat.com Thu Oct 23 16:05:43 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 12:05:43 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <1224777718.4805.33.camel@localhost.localdomain> References: <1224777718.4805.33.camel@localhost.localdomain> Message-ID: <4900A0D7.6010704@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Jason Guiditta wrote: > I have been meaning to send an email to the list about this for weeks. > I am looking to assemble a consensus on how to handle version control > for some 3rd party code we are using in the server suite. These are, > for the moment at least, rails plugins (not necessarily available as > gems or rpms) and javascript libraries/plugins. There are a couple of > issues we need to solve here: > 1) Keeping up to date on these bits of code, which may or may not be > available in git (a lot of the javascript is hosted in svn, some no > version control at all) > 2) We may have our own patches that we need to carry, though of course > we will try to push them upstream. > 3) Many of these items do no follow our practices- specifically > whitespace and ^Ms. This makes it difficult to drop in the updated code > and compare it to our (cleaned-up) version in git, especially if we have > made any changes/customizations. > > The only idea I have heard/read about that seems to be a possible good > solution is sub-modules, but there seems to be a fair amount of > maintenance involved in keeping them in synch with our main repo > whenever we do an update or patch to one of the modules. From what I > have seen, you have to have a reference in the main repo pointing to the > sub-module, including correct version. This would mean every update to > the sub-module would also require an update to the server repo, which is > easy enough, but feels like something that would easily be missed. Maybe > this is easier than I think, perhaps someone else can clear that up for > me. Also, if there are issues I did not outline above, please speak up > so we can consider those as well. I think the best solution would be to keep their code out of our repository and instead package them as separate RPMs and then depend on them from ours. As an example, look at how we require rubygem-cobbler and rubygem-activeldap. Neither is kept in our repository. How many dependencies do we have? - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkAoNMACgkQjaT4DmxOfxvvLQCg82f4No1ZWZ7ZPticVZyz6zI0 YMAAnjENKEVDWcAJlXT+s7mPKnIltesX =fMUb -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From sseago at redhat.com Thu Oct 23 16:17:19 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 23 Oct 2008 12:17:19 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <4900A0D7.6010704@redhat.com> References: <1224777718.4805.33.camel@localhost.localdomain> <4900A0D7.6010704@redhat.com> Message-ID: <4900A38F.9030805@redhat.com> Darryl Pierce wrote: > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > Jason Guiditta wrote: > >> I have been meaning to send an email to the list about this for weeks. >> I am looking to assemble a consensus on how to handle version control >> for some 3rd party code we are using in the server suite. These are, >> for the moment at least, rails plugins (not necessarily available as >> gems or rpms) and javascript libraries/plugins. There are a couple of >> issues we need to solve here: >> 1) Keeping up to date on these bits of code, which may or may not be >> available in git (a lot of the javascript is hosted in svn, some no >> version control at all) >> 2) We may have our own patches that we need to carry, though of course >> we will try to push them upstream. >> 3) Many of these items do no follow our practices- specifically >> whitespace and ^Ms. This makes it difficult to drop in the updated code >> and compare it to our (cleaned-up) version in git, especially if we have >> made any changes/customizations. >> >> The only idea I have heard/read about that seems to be a possible good >> solution is sub-modules, but there seems to be a fair amount of >> maintenance involved in keeping them in synch with our main repo >> whenever we do an update or patch to one of the modules. From what I >> have seen, you have to have a reference in the main repo pointing to the >> sub-module, including correct version. This would mean every update to >> the sub-module would also require an update to the server repo, which is >> easy enough, but feels like something that would easily be missed. Maybe >> this is easier than I think, perhaps someone else can clear that up for >> me. Also, if there are issues I did not outline above, please speak up >> so we can consider those as well. >> > > I think the best solution would be to keep their code out of our > repository and instead package them as separate RPMs and then depend on > them from ours. As an example, look at how we require rubygem-cobbler > and rubygem-activeldap. Neither is kept in our repository. > > How many dependencies do we have? > > The difficulty here is that we're not talking about the sort of reusable components that just need to be installed in a standard location and added to the right search path (as is the case with rubygem-cobbler). We've got javascript files that need to be included in /usr/share/ovirt-server/public/javascript and the plugin code that needs to be installed under /usr/share/ovirt-server/vendor/plugins. We _could_ package them as separate RPMs but the dependencies would be messy -- since they must live under the app root they would depend on ovirt-server, but since ovirt-server needs them, then the dependency needs to go the other way. So what we have here is a collection of code from different sources that actually forms part of our app. On top of that we have some ovirt-specific customizations to the javascript libraries which does need to live in our repo. Scott From dpierce at redhat.com Thu Oct 23 16:24:44 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 12:24:44 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <4900A38F.9030805@redhat.com> References: <1224777718.4805.33.camel@localhost.localdomain> <4900A0D7.6010704@redhat.com> <4900A38F.9030805@redhat.com> Message-ID: <4900A54C.3030006@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Scott Seago wrote: >> I think the best solution would be to keep their code out of our >> repository and instead package them as separate RPMs and then depend on >> them from ours. As an example, look at how we require rubygem-cobbler >> and rubygem-activeldap. Neither is kept in our repository. >> >> How many dependencies do we have? >> >> > The difficulty here is that we're not talking about the sort of reusable > components that just need to be installed in a standard location and > added to the right search path (as is the case with rubygem-cobbler). > We've got javascript files that need to be included in > /usr/share/ovirt-server/public/javascript and the plugin code that needs > to be installed under /usr/share/ovirt-server/vendor/plugins. > > We _could_ package them as separate RPMs but the dependencies would be > messy -- since they must live under the app root they would depend on > ovirt-server, but since ovirt-server needs them, then the dependency > needs to go the other way. > > So what we have here is a collection of code from different sources that > actually forms part of our app. On top of that we have some > ovirt-specific customizations to the javascript libraries which does > need to live in our repo. So, in that case, isn't the solution still a separate RPM but one that's specific to our product? IOW, if there's a javascript package called "foo", then we would potentially have a foo.rpm and a foo-ovirt.rpm that would apply any patches we have to customize it to our need? I guess what I'm getting at is more about keeping third party code out of our repo as much as possible. Unless we're freezing on a specific snapshot of their code, we should avoid having to version their code. We had done that in a previous project and it was just a bear to deal with. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkApUgACgkQjaT4DmxOfxtQeQCgqACjjaGo+VOcKsQry25pv9jv BYoAn27b66oIYrhG6Tx05hsgMHqsMI9u =KyBi -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From dpierce at redhat.com Thu Oct 23 16:26:37 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 12:26:37 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <4900A54C.3030006@redhat.com> References: <1224777718.4805.33.camel@localhost.localdomain> <4900A0D7.6010704@redhat.com> <4900A38F.9030805@redhat.com> <4900A54C.3030006@redhat.com> Message-ID: <4900A5BD.2090702@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Darryl Pierce wrote: >> So what we have here is a collection of code from different sources that >> actually forms part of our app. On top of that we have some >> ovirt-specific customizations to the javascript libraries which does >> need to live in our repo. > > So, in that case, isn't the solution still a separate RPM but one that's > specific to our product? IOW, if there's a javascript package called > "foo", then we would potentially have a foo.rpm and a foo-ovirt.rpm that > would apply any patches we have to customize it to our need? Sorry, in this case, I meant to include that a dependant rpm that would bridge the javascript code to our code; i.e., one that would create or update the necessary symlinks to bring the code into /usr/share/ovirt-server as needed. That way the third party code wouldn't need to packaged more than once for any project to use it. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkApbgACgkQjaT4DmxOfxvQcACgtH0dlpoYIiF72iDu/z5g50fO CcIAnRZKh5eWJ+CTAVvF1/+yLrloOY6s =xKcC -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From jguiditt at redhat.com Thu Oct 23 16:37:03 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 23 Oct 2008 12:37:03 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <4900A5BD.2090702@redhat.com> References: <1224777718.4805.33.camel@localhost.localdomain> <4900A0D7.6010704@redhat.com> <4900A38F.9030805@redhat.com> <4900A54C.3030006@redhat.com> <4900A5BD.2090702@redhat.com> Message-ID: <1224779823.4805.41.camel@localhost.localdomain> On Thu, 2008-10-23 at 12:26 -0400, Darryl Pierce wrote: > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > Darryl Pierce wrote: > >> So what we have here is a collection of code from different sources that > >> actually forms part of our app. On top of that we have some > >> ovirt-specific customizations to the javascript libraries which does > >> need to live in our repo. > > > > So, in that case, isn't the solution still a separate RPM but one that's > > specific to our product? IOW, if there's a javascript package called > > "foo", then we would potentially have a foo.rpm and a foo-ovirt.rpm that > > would apply any patches we have to customize it to our need? > > Sorry, in this case, I meant to include that a dependant rpm that would > bridge the javascript code to our code; i.e., one that would create or > update the necessary symlinks to bring the code into > /usr/share/ovirt-server as needed. > > That way the third party code wouldn't need to packaged more than once > for any project to use it. Again, the issue here is we still need to version our changes. I could see this possibly working for plugins since we only have a few (though we did have some customizations there at one point as well) and they are more likely to be available as gems. Also, for the javascript, there are tons of little components/plugins, so splintering them all into separate rpms would be a really large number of dependencies, not to mention rpm seems like an odd way to handle javascript files. I agree with not keeping the code in our main repo, that is already getting annoying. This is why I brought up sub-modules, which I believe Alan and/or Jim had mentioned as a solution we should consider. -j From agx at sigxcpu.org Thu Oct 23 16:44:44 2008 From: agx at sigxcpu.org (Guido =?iso-8859-1?Q?G=FCnther?=) Date: Thu, 23 Oct 2008 18:44:44 +0200 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <4900A54C.3030006@redhat.com> References: <1224777718.4805.33.camel@localhost.localdomain> <4900A0D7.6010704@redhat.com> <4900A38F.9030805@redhat.com> <4900A54C.3030006@redhat.com> Message-ID: <20081023164444.GA13710@bogon.ms20.nix> On Thu, Oct 23, 2008 at 12:24:44PM -0400, Darryl Pierce wrote: [..snip..] > > We _could_ package them as separate RPMs but the dependencies would be > > messy -- since they must live under the app root they would depend on > > ovirt-server, but since ovirt-server needs them, then the dependency > > needs to go the other way. > > > > So what we have here is a collection of code from different sources that > > actually forms part of our app. On top of that we have some > > ovirt-specific customizations to the javascript libraries which does > > need to live in our repo. > > So, in that case, isn't the solution still a separate RPM but one that's > specific to our product? IOW, if there's a javascript package called > "foo", then we would potentially have a foo.rpm and a foo-ovirt.rpm that > would apply any patches we have to customize it to our need? If these are generic java script libraries, packaging them and putting them in e.g. /usr/share/ and simply symlinking this directory into oVirt could also be a solution. The benefit is that other packages can use the libs too. > I guess what I'm getting at is more about keeping third party code out > of our repo as much as possible. Unless we're freezing on a specific > snapshot of their code, we should avoid having to version their code. We > had done that in a previous project and it was just a bear to deal with. Having the code outside of the oVirt repo would also considerably help packaging oVirt for Debian (which we started recently). Cheers, -- Guido From berrange at redhat.com Thu Oct 23 18:01:43 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 23 Oct 2008 19:01:43 +0100 Subject: [Ovirt-devel] Explicit global network modelling Message-ID: <20081023180143.GB6753@redhat.com> The network configuration UI discussions have all focused around the idea of configuring NICs on machines. I've been thinking that this is really not the right model. You have some kind of physical topology which is known, and has certain properties like network address and prefix, and vlan. A host has one or more NICs, each of which is connected to a network. So if we can model a network as a global entity in its own right, we can simplify configuration of host interfaces, so simply a matter of association, and optionally defining an address. * Network - addr config eg, none, static vs dhcp - network address eg, 192.168.122.0 - prefix eg, /24 - usage eg, storage, management, guest - vlan number eg, 43 - vlan network eg, name of host network * Interface - name eg eth1 - mac eg 00:11:22:33:44:55 - addr config eg, static vs dhcp vs none With the association being: 1 n n 1 Network <-----> Interface <----> Node So if you have say * Network: - 'admin lan' - 192.168.122.0/24 - dhcp - usage: management * Network - 'guest lan' - dhcp - usage: guest Now, you come to configure a new host with two NICs 'eth0' and 'eth1. In this particular scenario all the admin would need todo is pick off the 'admin lan' network for 'eth0', and 'guest lan' for eth1. Job done. No need to specify any addressing, because the network properties already tell us it is a DHCP based LAN. Also, since eth1 is associated with a guest traffic only lan, there is no need for IP config on eth1 in the host. All we need do is put eth1 in a bridge. Also note we don't need to ask the admin whether eth1 should be bridged or not. We know for a fact that it must be bridged, because it is a guest traffic network. Likewise we know that a admin network does not need a bridge. If a network was designated for both mgmt and guest usage, then we would need a bridge and IP address config. Now an alternate example with VLANs... * Network: - 'office lan' - none addr config - usage: none * Network: - 'admin lan' - 192.168.122.0/24 - dhcp - usage: management - vlan: 43 - vlan host: office lan * Network - 'guest lan' - dhcp - usage: guest - vlan: 72 - vlan host: office lan Now, come to configure a host with two devices eth0 and eth1. The admin tells us that both eth0 and eth1 are connected to 'office lan'. With this, we know that eth0 and eth1 should be bonded since they're on same lan, so we merely need ask what kind of bonding is desired. Since we see that there are two VLAN networks associated with 'office lan', we also know that we have to create two VLAN devices on the host, and that one of those VLAN devices must be put in a bridge. Again no need to ask the admin about VLAN creation on the host So this kind of modelling can make our UI for setting up host networking much clearer / simpler, avoiding lots of redundant questions. Also, by having an explicit 'network <-> interface <-> host' assoication, we can trivally determine whether it is possible to migrate between two hosts from a network topology POV - its merely checking one DB relation 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 lutter at redhat.com Thu Oct 23 18:40:06 2008 From: lutter at redhat.com (David Lutterkort) Date: Thu, 23 Oct 2008 11:40:06 -0700 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <1224777718.4805.33.camel@localhost.localdomain> References: <1224777718.4805.33.camel@localhost.localdomain> Message-ID: <1224787206.15846.25.camel@localhost.localdomain> On Thu, 2008-10-23 at 12:01 -0400, Jason Guiditta wrote: > I have been meaning to send an email to the list about this for weeks. > I am looking to assemble a consensus on how to handle version control > for some 3rd party code we are using in the server suite. These are, > for the moment at least, rails plugins (not necessarily available as > gems or rpms) and javascript libraries/plugins. There are a couple of > issues we need to solve here: > 1) Keeping up to date on these bits of code, which may or may not be > available in git (a lot of the javascript is hosted in svn, some no > version control at all) > 2) We may have our own patches that we need to carry, though of course > we will try to push them upstream. > 3) Many of these items do no follow our practices- specifically > whitespace and ^Ms. This makes it difficult to drop in the updated code > and compare it to our (cleaned-up) version in git, especially if we have > made any changes/customizations. > > The only idea I have heard/read about that seems to be a possible good > solution is sub-modules, but there seems to be a fair amount of > maintenance involved in keeping them in synch with our main repo > whenever we do an update or patch to one of the modules. The overriding concern is that we don't want to maintain forks of all these upstream projects; the best solution would be to approach each upstream and ask them to take our patches, and go to a release model that is more amenable to reuse (though if they do not do properly versioned releases, it's fine to use e.g. svn revision numbers as a substitute) The question of how to package all that is secondary to that concern. Ideally, we would have a separate RPM for each upstream, but if that's not feasible in some cases (e.g. JS libraries, Fedora doesn't even have JS packaging guidelines) it would be ok to pull the sources into a ovirt-specific RPM as long as those sources do not diverge from upstream. As long as we follow a process of 'use pristine upstream + temporary patches', it should be fairly easy to script the import/update of newer upstream versions. I would put those things into a separate 'ovirt-js-modules' or similar and keep them out of the main ovirt-server git, with the assumption that developers will install/update that RPM separately. To avoid problems with the directories into which those files go, we should see if either Rails or Mongrel lets use reconfigure the location of JS libs - Rails plugins should be less problematic, since we can always futz with LOAD_PATH. David From sseago at redhat.com Thu Oct 23 20:13:24 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 23 Oct 2008 16:13:24 -0400 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <1224787206.15846.25.camel@localhost.localdomain> References: <1224777718.4805.33.camel@localhost.localdomain> <1224787206.15846.25.camel@localhost.localdomain> Message-ID: <4900DAE4.9000303@redhat.com> David Lutterkort wrote: > > As long as we follow a process of 'use pristine upstream + temporary > patches', it should be fairly easy to script the import/update of newer > upstream versions. I would put those things into a separate > 'ovirt-js-modules' or similar and keep them out of the main ovirt-server > git, with the assumption that developers will install/update that RPM > separately. > > So if, for example we need to modify the css for a javascript library component you're suggesting we should only keep the patch in our repo instead of the modified file? Most of our local changes are ovirt-specific styling tweaks that wouldn't be appropriate for upstream inclusion. Of course if these modules RPMs depend on the main ovirt-server RPM then we'd have to remember to install them separately. > To avoid problems with the directories into which those files go, we > should see if either Rails or Mongrel lets use reconfigure the location > of JS libs - Rails plugins should be less problematic, since we can > always futz with LOAD_PATH. > > Off the top of my head I"m not sure if LOAD_PATH changes are sufficient to find plugins but there's probably something we can do in the config here. For the javascript stuff though it either needs to be in the standard location or symlinked there since the rails expectation is that all CSS/javascript files are located in the same dir -- and we've got ovirt-specific files that we control that need to be in those dirs too. Scott From mmorsi at redhat.com Thu Oct 23 20:34:47 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 23 Oct 2008 16:34:47 -0400 Subject: [Ovirt-devel] Explicit global network modelling In-Reply-To: <20081023180143.GB6753@redhat.com> References: <20081023180143.GB6753@redhat.com> Message-ID: <4900DFE7.50808@redhat.com> Daniel P. Berrange wrote: > The network configuration UI discussions have all focused around > the idea of configuring NICs on machines. I've been thinking that > this is really not the right model. You have some kind of physical > topology which is known, and has certain properties like network > address and prefix, and vlan. A host has one or more NICs, each > of which is connected to a network. > > So if we can model a network as a global entity in its own right, > we can simplify configuration of host interfaces, so simply a > matter of association, and optionally defining an address. > > > * Network > - addr config eg, none, static vs dhcp > - network address eg, 192.168.122.0 > - prefix eg, /24 > - usage eg, storage, management, guest > - vlan number eg, 43 > - vlan network eg, name of host network > > * Interface > - name eg eth1 > - mac eg 00:11:22:33:44:55 > - addr config eg, static vs dhcp vs none > > With the association being: > > 1 n n 1 > Network <-----> Interface <----> Node > > > Based on our discussion today the following things are needed for this 1. Add new networks table, subclassed into virtual and physical networks, and associated with ip_addresses table 2. Unassociate nics and bondings with ip addresses, associate with networks 3. Change any backend components that depend on the model changes 4. Add tab to dashboard if user is able to edit default pool for networks, allowing user to view, create, edit, delete networks. Should come with FIXME to change permissions to something more robust, either by subclassing Network table from Pool (breaking necessary bits into any additional tables) or the most rebust way, to refactoring the permission model to accommodate permissions assigned to any object 5. Update nics / bonding interface to associate w/ a network as opposed to an ip address. I can take care of the interface bits (eg 4,5) if Darryl or someone else takes care of the first three. One additional thing that comes to mind as I read over the proposed changes is that this network model fits nicely with DHCP assigned nics / interfaces as the nic is associated with the network which it is to receive that information from, but for static assignments it doesn't fit so nicely. This is because the nic / interface will still need to be associated with an ip address directly and not just a network. If so we'd still need the associations between those tables, and might require some validation to make sure the assigned ip is valid for the selected network (or would no network be selected in that case?). -Mo From dpierce at redhat.com Thu Oct 23 20:40:17 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Thu, 23 Oct 2008 16:40:17 -0400 Subject: [Ovirt-devel] Explicit global network modelling In-Reply-To: <4900DFE7.50808@redhat.com> References: <20081023180143.GB6753@redhat.com> <4900DFE7.50808@redhat.com> Message-ID: <4900E131.206@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Mohammed Morse wrote: > Based on our discussion today the following things are needed for this > > > 1. Add new networks table, subclassed into virtual and physical > networks, and associated with ip_addresses table > 2. Unassociate nics and bondings with ip addresses, associate with networks > 3. Change any backend components that depend on the model changes > 4. Add tab to dashboard if user is able to edit default pool for > networks, allowing user to view, create, edit, delete networks. Should > come with FIXME to change permissions to something more robust, either > by subclassing Network table from Pool (breaking necessary bits into any > additional tables) or the most rebust way, to refactoring the permission > model to accommodate permissions assigned to any object > 5. Update nics / bonding interface to associate w/ a network as opposed > to an ip address. > > > I can take care of the interface bits (eg 4,5) if Darryl or someone else > takes care of the first three. I can do the first three. - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkA4S8ACgkQjaT4DmxOfxsj8ACfR4xhv4jZDe0OjmSM50Klq1+P 4UgAoKS1O+lidAZ2iDWvD+FvlV8wq+Z3 =IIDf -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From berrange at redhat.com Thu Oct 23 21:06:12 2008 From: berrange at redhat.com (Daniel P. Berrange) Date: Thu, 23 Oct 2008 22:06:12 +0100 Subject: [Ovirt-devel] Explicit global network modelling In-Reply-To: <4900DFE7.50808@redhat.com> References: <20081023180143.GB6753@redhat.com> <4900DFE7.50808@redhat.com> Message-ID: <20081023210612.GA11694@redhat.com> On Thu, Oct 23, 2008 at 04:34:47PM -0400, Mohammed Morsi wrote: > Daniel P. Berrange wrote: > > The network configuration UI discussions have all focused around > > the idea of configuring NICs on machines. I've been thinking that > > this is really not the right model. You have some kind of physical > > topology which is known, and has certain properties like network > > address and prefix, and vlan. A host has one or more NICs, each > > of which is connected to a network. > > > > So if we can model a network as a global entity in its own right, > > we can simplify configuration of host interfaces, so simply a > > matter of association, and optionally defining an address. > > > > > > * Network > > - addr config eg, none, static vs dhcp > > - network address eg, 192.168.122.0 > > - prefix eg, /24 > > - usage eg, storage, management, guest > > - vlan number eg, 43 > > - vlan network eg, name of host network > > > > * Interface > > - name eg eth1 > > - mac eg 00:11:22:33:44:55 > > - addr config eg, static vs dhcp vs none > > > > With the association being: > > > > 1 n n 1 > > Network <-----> Interface <----> Node > > > > > > > Based on our discussion today the following things are needed for this > > > 1. Add new networks table, subclassed into virtual and physical > networks, and associated with ip_addresses table Where does this virtual vs physical network distinction come from ? > One additional thing that comes to mind as I read over the proposed > changes is that this network model fits nicely with DHCP assigned nics / > interfaces as the nic is associated with the network which it is to > receive that information from, but for static assignments it doesn't fit > so nicely. This is because the nic / interface will still need to be > associated with an ip address directly and not just a network. If so > we'd still need the associations between those tables, and might require > some validation to make sure the assigned ip is valid for the selected > network (or would no network be selected in that case?). Static ip info is no trouble at all. You can still associate an IP address with with the interface directly. In fact you could even suggest a suitable static addres by looking at the netmask of the Network, and what static addresses are already known, and pick an unused one. In addition, even if the network is configured for DHCP, you may still wish to give certain hosts static ip addrs, so you need to maintain the IP address against the interface anyway. With both the network, and interface you need to cope with multiple addresses - both IPv4 and v6 1 n n 1 Network <-----> Interface <----> Node ^ 1 ^ 1 | | V n V n NetAddress Address In the NetAddress you store the network address (192.168.122.0) and the network prefix (/24). In the 'Address' you merely store the address '192.168.122.0'. You can't directly enforce validity constraint of network address + prefix matching the NIC address, because SQL just isn't that expressive. 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 lutter at redhat.com Thu Oct 23 21:20:10 2008 From: lutter at redhat.com (David Lutterkort) Date: Thu, 23 Oct 2008 14:20:10 -0700 Subject: [Ovirt-devel] How to handle 3rd party code? In-Reply-To: <4900DAE4.9000303@redhat.com> References: <1224777718.4805.33.camel@localhost.localdomain> <1224787206.15846.25.camel@localhost.localdomain> <4900DAE4.9000303@redhat.com> Message-ID: <1224796810.15846.33.camel@localhost.localdomain> On Thu, 2008-10-23 at 16:13 -0400, Scott Seago wrote: > David Lutterkort wrote: > > > > As long as we follow a process of 'use pristine upstream + temporary > > patches', it should be fairly easy to script the import/update of newer > > upstream versions. I would put those things into a separate > > 'ovirt-js-modules' or similar and keep them out of the main ovirt-server > > git, with the assumption that developers will install/update that RPM > > separately. > > > > > So if, for example we need to modify the css for a javascript library > component you're suggesting we should only keep the patch in our repo > instead of the modified file? Most of our local changes are > ovirt-specific styling tweaks that wouldn't be appropriate for upstream > inclusion. Yeah, CSS is icky in this context, and it doesn't have the same problems as keeping actual code pristine; not sure what the best way for handling that is, but there's nothing wrong with replacing style files whole hog. I was more concerned with bug fixes/enhancements to actual code that would need to be tracked and forward ported if we're not careful. > Of course if these modules RPMs depend on the main > ovirt-server RPM then we'd have to remember to install them separately. I think this makes only sense if the deps are the other way, i.e. if ovirt-server requires those extra libraries, and if things can be setup so that a developer can install the RPM's for those extra libs and then easily run a dev server out of the source checkout. > > To avoid problems with the directories into which those files go, we > > should see if either Rails or Mongrel lets use reconfigure the location > > of JS libs - Rails plugins should be less problematic, since we can > > always futz with LOAD_PATH. > > > > > Off the top of my head I"m not sure if LOAD_PATH changes are sufficient > to find plugins but there's probably something we can do in the config > here. For the javascript stuff though it either needs to be in the > standard location or symlinked there since the rails expectation is > that all CSS/javascript files are located in the same dir -- and we've > got ovirt-specific files that we control that need to be in those dirs too. Yeah, you'd probably need to put more of what's currently in server/src/public/javascripts into subdirs so that you can symlink a whole directory in. David From jguiditt at redhat.com Thu Oct 23 21:24:35 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 23 Oct 2008 17:24:35 -0400 Subject: [Ovirt-devel] [PATCH server] Stage of one of tree conversions to pluggable widget. Message-ID: <1224797075-12493-1-git-send-email-jguiditt@redhat.com> This patch includes the new pluggable tree widget code and conversion of Move popups for Hardware pools (hosts and storage). Simplified call to move so it returns an object which is used to build the tree (no extra ajax call anymore). The json_tree_internal method will be going away next patch, can't get rid of it yet because some Smart Pool stuff depends on it. Fixed minor bug in Pool.full_set_nested causing 'selected' never to be set. Also added to this method a 'type' option to filter results by pool type. Signed-off-by: Jason Guiditta --- src/app/controllers/hardware_controller.rb | 4 ++ src/app/models/pool.rb | 7 +++- src/app/views/hardware/move.rhtml | 60 +++++++++++++++----------- src/app/views/layouts/_tree.rhtml | 9 +--- src/app/views/layouts/redux.rhtml | 3 + src/public/javascripts/ovirt.tree.js | 64 +++++++++++++++++++++++++++- src/public/stylesheets/ovirt-tree/tree.css | 7 +++- 7 files changed, 119 insertions(+), 35 deletions(-) diff --git a/src/app/controllers/hardware_controller.rb b/src/app/controllers/hardware_controller.rb index 9c04210..4f24cb3 100644 --- a/src/app/controllers/hardware_controller.rb +++ b/src/app/controllers/hardware_controller.rb @@ -184,6 +184,10 @@ class HardwareController < PoolController def move pre_modify @resource_type = params[:resource_type] + @id = params[:id] + @pools = HardwarePool.get_default_pool.full_set_nested(:method => :json_hash_element, + :privilege => Permission::PRIV_MODIFY, :user => get_login_user, :current_id => @id, + :type => :select_hardware_pools).to_json render :layout => 'popup' end diff --git a/src/app/models/pool.rb b/src/app/models/pool.rb index d189649..a8b1589 100644 --- a/src/app/models/pool.rb +++ b/src/app/models/pool.rb @@ -230,6 +230,7 @@ class Pool < ActiveRecord::Base method = opts.delete(:method) {:hash_element} privilege = opts.delete(:privilege) user = opts.delete(:user) + type = opts.delete(:type) smart_pool_set = opts.delete(:smart_pool_set) if privilege and user opts[:include] = "permissions" @@ -241,6 +242,7 @@ class Pool < ActiveRecord::Base opts.delete(:order) subtree_list = full_set(opts) subtree_list -= [self] if smart_pool_set + subtree_list = Pool.send(type, subtree_list) if type return_tree_list = [] ref_hash = {} subtree_list.each do |pool| @@ -264,7 +266,10 @@ class Pool < ActiveRecord::Base end end end - ref_hash[current_id][:selected] = true if current_id +# FIXME: right now, we have inserted the pool id into the hash as an +# integer (database value type. Rather than converting the current_id +# param to an integer, we may want to use a symbol for both identifiers + ref_hash[current_id.to_i][:selected] = true if current_id return_tree_list end diff --git a/src/app/views/hardware/move.rhtml b/src/app/views/hardware/move.rhtml index 968e2ca..9688c39 100644 --- a/src/app/views/hardware/move.rhtml +++ b/src/app/views/hardware/move.rhtml @@ -6,21 +6,15 @@ <%- end -%> +
      +
      diff --git a/src/app/views/layouts/_tree.rhtml b/src/app/views/layouts/_tree.rhtml index a6bde14..fefd642 100644 --- a/src/app/views/layouts/_tree.rhtml +++ b/src/app/views/layouts/_tree.rhtml @@ -1,6 +1,3 @@ -<%= javascript_include_tag "trimpath-template-1.0.38.js" %> -<%= javascript_include_tag "ovirt.tree.js" %> -<%= stylesheet_link_tag 'ovirt-tree/tree' %> > >
      > @@ -48,7 +82,7 @@ > <%= render :partial => "/search/grid", :locals => { :table_id => "search_grid", > :terms => @terms, > :model => @model_param, > - :checkboxes => false, > + :checkboxes => true, > :on_select => "results_select" } %> >
      >
      > diff --git a/src/app/views/vm/_grid.rhtml b/src/app/views/vm/_grid.rhtml > index 85bf094..b137de6 100644 > --- a/src/app/views/vm/_grid.rhtml > +++ b/src/app/views/vm/_grid.rhtml > @@ -29,7 +29,7 @@ > {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: <%= table_id %>checkbox}, > {display: 'Description', name : 'description', width : 180, sortable : true, align: 'left'}, > {display: 'UUID', name : 'uuid', width : 180, sortable : true, align: 'left'}, > - <% if @pool.get_hardware_pool.can_view(@user) %> > + <% if (pool.is_a? VmResourcePool) and pool.get_hardware_pool.can_view(@user) %> > {display: 'Host', name : 'host', width: 180, sortable : true, align: 'left' }, > <% end %> > {display: 'CPUs', name : 'num_vcpus_allocated', width : 40, sortable : true, align: 'left'}, > ACK, pending the change mentioned above. All functionality works for me. From pmyers at redhat.com Wed Oct 29 12:31:46 2008 From: pmyers at redhat.com (Perry Myers) Date: Wed, 29 Oct 2008 08:31:46 -0400 Subject: [Ovirt-devel] Re: [virt-devel] oVirt and Virtualization Questions In-Reply-To: <49080410.4070305@redhat.com> References: <49080410.4070305@redhat.com> Message-ID: <490857B2.305@redhat.com> Christopher Curran wrote: > [[ oVirt Node ]] > Is ssh disabled on the Nodes? No, and it will continue to be enabled for the product as it provides a way for administrators to get into the node for troubleshooting purposes. However... The Node will come with the root account disabled in production (we don't want to set a default password since that is a huge security hole). We will provide a way for the person setting up Nodes to configure the root password both in standalone mode (through a local configuration at first boot) or through the oVirt Server through the web interface. Until the root password is set properly SSH will not be usable. In development we set the root password to 'ovirt' just for convenience. Alan/Darryl are working on Node configuration and standalone mode so they should take note of the above. > How can I change the cobbler server on the Appliance to boot the Node > from default? On the appliance run: cobbler system add --netboot-enabled=1 --profile=oVirt-Node-$arch \ --name= --mac= Just provide the hostname (i.e. Node10) and the MAC and you should be fine. Presently only node3, 4 and 5 are preregistered this way. All other IP addresses on the oVirt LAN are given the PXE boot screen on which you can choose whether to boot the host as a Node or another profile. What we need to do is the following: When a physical host boots for the first time it is presented with the PXE menu and the user can select Node as the option. When a Node boots for the first time (the user having selected Node as the profile in PXE) the Server should register that MAC address as a system so that subsequent boots don't present the PXE menu and instead just boot the Node profile by default. This is something that needs to be set up on the Server side. Darryl can you get a patch out there to do this? > are there any other cobbler examples out there? See ovirt-appliance.ks in the ovirt-appliance repository for examples for setting up profiles and systems. > The virtual machine pools display negative numbers, is this expected > behaviour? This is because the vmpools are based on quotas and the CPUs remaining are calculated by doing CPUs allocated - CPUs used. If you don't set a quota the calculation defaults to zero and then you have a negative number. Scott/Jay, this needs to be fixed what should we show here for the cases of infinite quotas? > Accessing the cobbler web interface is still not happening on my > appliance. Could someone write a howto for enabling it or integrate it > in code? http://192.168.50.2/cobbler/web The above URL works for me. Default user/pass is cobbler/cobbler. Perry From slinabery at redhat.com Wed Oct 29 20:39:23 2008 From: slinabery at redhat.com (Steve Linabery) Date: Wed, 29 Oct 2008 15:39:23 -0500 Subject: [Ovirt-devel] [PATCH server] Add tooltips, y-axis labels, and first animation transition to flexchart. Message-ID: <1225312763-31801-1-git-send-email-slinabery@redhat.com> Also improve OO design with new classes and better organization thereof. --- src/app/controllers/graph_controller.rb | 3 +- src/flexchart/README.txt | 2 +- src/flexchart/flexchart.mxml | 69 +++++++-- src/flexchart/org/ovirt/ChartLoader.as | 64 -------- src/flexchart/org/ovirt/Constants.as | 28 ++++ src/flexchart/org/ovirt/DataSeries.as | 42 ----- src/flexchart/org/ovirt/DataSource.as | 14 +- src/flexchart/org/ovirt/charts/BarChart.as | 158 ++++++++++++++++++++ src/flexchart/org/ovirt/charts/Chart.as | 46 ++++++ src/flexchart/org/ovirt/data/DataPoint.as | 47 ++++++ src/flexchart/org/ovirt/data/DataSeries.as | 54 +++++++ src/flexchart/org/ovirt/elements/SingleBar.as | 67 ++++++++ src/flexchart/org/ovirt/elements/TextLiberation.as | 46 ++++++ src/flexchart/org/ovirt/elements/YAxisLabel.as | 41 +++++ 14 files changed, 556 insertions(+), 125 deletions(-) delete mode 100644 src/flexchart/org/ovirt/ChartLoader.as create mode 100644 src/flexchart/org/ovirt/Constants.as delete mode 100644 src/flexchart/org/ovirt/DataSeries.as create mode 100644 src/flexchart/org/ovirt/charts/BarChart.as create mode 100644 src/flexchart/org/ovirt/charts/Chart.as create mode 100644 src/flexchart/org/ovirt/data/DataPoint.as create mode 100644 src/flexchart/org/ovirt/data/DataSeries.as create mode 100644 src/flexchart/org/ovirt/elements/SingleBar.as create mode 100644 src/flexchart/org/ovirt/elements/TextLiberation.as create mode 100644 src/flexchart/org/ovirt/elements/YAxisLabel.as diff --git a/src/app/controllers/graph_controller.rb b/src/app/controllers/graph_controller.rb index c1d27b5..0105ea6 100644 --- a/src/app/controllers/graph_controller.rb +++ b/src/app/controllers/graph_controller.rb @@ -15,7 +15,8 @@ class GraphController < ApplicationController :values => graph_obj[:dataset][2][:values].last(40) } my_data = graph_data[:labels].zip(graph_data[:values]) graph = { :vectors => my_data, - :max_value => graph_obj[:total_peak] + :max_value => graph_obj[:total_peak], + :description => params[:target] } render :json => graph end diff --git a/src/flexchart/README.txt b/src/flexchart/README.txt index 66eb183..3de7430 100644 --- a/src/flexchart/README.txt +++ b/src/flexchart/README.txt @@ -5,4 +5,4 @@ Once you have mxmlc on your system, run: mxmlc flexchart.mxml -in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public on your appliance. +in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public/swfs on your appliance. diff --git a/src/flexchart/flexchart.mxml b/src/flexchart/flexchart.mxml index 796329d..35fa9a6 100644 --- a/src/flexchart/flexchart.mxml +++ b/src/flexchart/flexchart.mxml @@ -1,20 +1,67 @@ - + + + - - - - + + + + diff --git a/src/flexchart/org/ovirt/ChartLoader.as b/src/flexchart/org/ovirt/ChartLoader.as deleted file mode 100644 index 4e493a4..0000000 --- a/src/flexchart/org/ovirt/ChartLoader.as +++ /dev/null @@ -1,64 +0,0 @@ -/* - 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. -*/ - -package org.ovirt { - - import mx.containers.Box; - import mx.containers.HBox; - import mx.controls.Text; - - public class ChartLoader { - - private var element:Box; - private var datasourceUrl:String; - - public function ChartLoader(element:Box, datasourceUrl:String) { - this.element = element; - this.datasourceUrl = datasourceUrl; - } - - public function addData(dataSeries:DataSeries):void { - var points:Array = dataSeries.getPoints(); - var maxValue:Number = dataSeries.getMaxValue(); - var scale:Number = maxValue; - if (scale == 0) { scale = 1; } - var size:int = points.length; - element.removeAllChildren(); - element.setStyle("horizontalGap","2"); - for (var i:int = 0; i < size; i++) { - var value:Number = (points[i] as Array)[1]; - var bar:HBox = new HBox(); - bar.percentHeight = ((value / scale) * 90); - bar.percentWidth = (100 / size); - bar.setStyle("backgroundColor","0x0000FF"); - bar.setStyle("left","1"); - bar.setStyle("right","1"); - bar.visible = true; - bar.setVisible(true); - element.addChild(bar); - } - } - - public function load():void { - var dataSource:DataSource = new DataSource(this); - dataSource.retrieveData(datasourceUrl); - } - } -} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/Constants.as b/src/flexchart/org/ovirt/Constants.as new file mode 100644 index 0000000..996a31e --- /dev/null +++ b/src/flexchart/org/ovirt/Constants.as @@ -0,0 +1,28 @@ +/* + 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. +*/ + +package org.ovirt { + public class Constants { + public static var width:int = 722; + public static var height:int = 297; + public static var barSpacing:int = 2; + public static var labelHeight:int = 40; + } +} diff --git a/src/flexchart/org/ovirt/DataSeries.as b/src/flexchart/org/ovirt/DataSeries.as deleted file mode 100644 index d63162a..0000000 --- a/src/flexchart/org/ovirt/DataSeries.as +++ /dev/null @@ -1,42 +0,0 @@ -/* - 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 to encapsulate the json object representation of a data -//series returned from stats package - -package org.ovirt { - - public class DataSeries { - - private var object:Object; - - public function DataSeries (object:Object) { - this.object = object; - } - - public function getPoints():Array { - return object["vectors"] as Array; - } - - public function getMaxValue():Number { - return object["max_value"] as Number; - } - } -} \ No newline at end of file diff --git a/src/flexchart/org/ovirt/DataSource.as b/src/flexchart/org/ovirt/DataSource.as index 1a64f03..44e482d 100644 --- a/src/flexchart/org/ovirt/DataSource.as +++ b/src/flexchart/org/ovirt/DataSource.as @@ -25,19 +25,21 @@ package org.ovirt { import com.adobe.serialization.json.JSON; import flash.events.Event; import flash.events.IOErrorEvent; + import org.ovirt.data.DataSeries; + import org.ovirt.charts.Chart; public class DataSource { - private var chartLoader:ChartLoader; + private var chart:Chart; - public function DataSource(chartLoader:ChartLoader) { - this.chartLoader = chartLoader; + public function DataSource(chart:Chart) { + this.chart = chart; } public function retrieveData(url:String):void { var loader:URLLoader = new URLLoader(); - loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError ); - loader.addEventListener( Event.COMPLETE, dataLoaded ); + loader.addEventListener(IOErrorEvent.IO_ERROR, this.ioError); + loader.addEventListener(Event.COMPLETE, dataLoaded); var request:URLRequest = new URLRequest(url); loader.load(request); } @@ -46,7 +48,7 @@ package org.ovirt { var loader:URLLoader = URLLoader(event.target); var object:Object = JSON.decode(loader.data); var series:DataSeries = new DataSeries(object); - chartLoader.addData(series); + chart.addData(series); } private function ioError( e:IOErrorEvent ):void { diff --git a/src/flexchart/org/ovirt/charts/BarChart.as b/src/flexchart/org/ovirt/charts/BarChart.as new file mode 100644 index 0000000..83cf0bb --- /dev/null +++ b/src/flexchart/org/ovirt/charts/BarChart.as @@ -0,0 +1,158 @@ +/* + 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. +*/ + +package org.ovirt.charts { + + import mx.containers.Box; + import mx.containers.HBox; + import mx.containers.VBox; + import mx.controls.Text; + import mx.containers.Canvas; + import org.ovirt.data.*; + import org.ovirt.elements.*; + import org.ovirt.Constants; + + public class BarChart extends Chart { + + private var chartArea:HBox; + private var labelArea:Canvas; + + public function BarChart(container:Box, + datasourceUrl:String) { + super(container,datasourceUrl); + chartArea = new HBox(); + chartArea.setStyle("horizontalGap",Constants.barSpacing); + chartArea.setStyle("verticalAlign","bottom"); + chartArea.percentHeight = 80; + chartArea.percentWidth = 100; + this.container.addChild(chartArea); + + labelArea = new Canvas(); + labelArea.height = Constants.labelHeight; + labelArea.minHeight = Constants.labelHeight; + labelArea.percentWidth = 100; + this.container.addChild(labelArea); + } + + override public function addData(dataSeries:DataSeries):void { + try { + var dataPoints:Array = dataSeries.getDataPoints(); + var maxValue:Number = dataSeries.getMaxValue(); + var scale:Number = maxValue; + //avoid divide by zero + if (scale == 0) { + scale = 1; + } + var size:int = dataPoints.length; + if (size == 0) { + throw new Error("No data points in range"); + } + + //have to iterate through datapoint.timestamp strings, + //create a TextLiberation object with them, and add them to + //a parent container before we can tell how wide they are in pixels. + var labelWidth:Number = 0; + for (var i:int = 0; i < size; i++) { + var dataPoint:DataPoint = dataPoints[i] as DataPoint; + var textTemp:TextLiberation = + new TextLiberation(dataPoint.getTimestamp()); + textTemp.setVisible(false); + chartArea.addChild(textTemp); + var tempLabelWidth:Number = textTemp.getTextWidth(); + if (! isNaN(tempLabelWidth)) { + labelWidth = Math.max(labelWidth, tempLabelWidth); + } + } + //now we have to remove all the children we just added, since we don't + //really want them to be part of the chart. + chartArea.removeAllChildren(); + + //we always want an odd number of y-axis labels, and we'll + //determine this by using the labelWidth we just determined + var labelCount:int = Math.floor(Constants.width / labelWidth); + if (labelCount > 3 && labelCount % 2 == 1) { + labelCount--; + } + + //the distance between left edges of adjacent bars + var gridWidth:Number = Constants.width / size; + + //the width of each SingleBar (does not including padding between bars) + var barWidth:Number = gridWidth - Constants.barSpacing; + + //use this to center y-axis labels on the bars + var labelOffset:Number = barWidth / 2; + + //distance between first and last label + var labelSpace:Number = Constants.width - gridWidth; + var labelSpacing:Number = labelSpace / labelCount; + + //add the bars & labels to the chart + var labelCounter:int = 0; + for (i = 0; i < size; i++) { + dataPoint = dataPoints[i] as DataPoint; + var value:Number = dataPoint.getValue(); + var bar:SingleBar = new SingleBar(dataPoint); + bar.percentHeight = ((value / scale) * 80); + bar.width = barWidth; + bar.setVisible(true); + chartArea.addChild(bar); + var currentLabelPosition:int = labelCounter * labelSpacing + + labelOffset; + + if (currentLabelPosition >= i * gridWidth && + currentLabelPosition < (i + 1) * gridWidth) { + var label:YAxisLabel = new YAxisLabel(dataPoint.getTimestamp()); + label.setVisible(false); + label.y = ((labelCounter + 1) % 2) * 13 + 4; + labelArea.addChild(label); + //make sure the label is fully within the chart width + label.x = Math.max(0, + Math.min((i) * gridWidth - + (label.labelText.getTextWidth() / 2) + + labelOffset, + Constants.width - + label.labelText.getTextWidth() - 6) + ); + label.setVisible(true); + labelCounter++; + + //add a 'tick' in the center of the bar to which this label + //corresponds + var ind:Box = new Box(); + ind.opaqueBackground = 0x000000; + ind.width=1; + ind.height=3; + ind.x = (i) * gridWidth + labelOffset; + ind.y = 0; + ind.setVisible(true); + ind.setStyle("backgroundColor","0x000000"); + labelArea.addChild(ind); + } + } + } catch (e:Error) { + var err:Text = new Text(); + err.text = e.message; + err.setVisible(true); + chartArea.addChild(err); + } + } + } +} diff --git a/src/flexchart/org/ovirt/charts/Chart.as b/src/flexchart/org/ovirt/charts/Chart.as new file mode 100644 index 0000000..26c8d02 --- /dev/null +++ b/src/flexchart/org/ovirt/charts/Chart.as @@ -0,0 +1,46 @@ +/* + 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. +*/ + +package org.ovirt.charts { + + public class Chart { + + import org.ovirt.DataSource; + import mx.containers.Box; + import org.ovirt.data.DataSeries; + + protected var container:Box; + protected var datasourceUrl:String; + + public function Chart(container:Box, datasourceUrl:String) { + this.container = container; + this.datasourceUrl = datasourceUrl; + } + + public function addData(dataSeries:DataSeries):void { + //override me! + } + + public function load():void { + var dataSource:DataSource = new DataSource(this); + dataSource.retrieveData(datasourceUrl); + } + } +} diff --git a/src/flexchart/org/ovirt/data/DataPoint.as b/src/flexchart/org/ovirt/data/DataPoint.as new file mode 100644 index 0000000..00cd0a4 --- /dev/null +++ b/src/flexchart/org/ovirt/data/DataPoint.as @@ -0,0 +1,47 @@ +/* + 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. +*/ + +package org.ovirt.data { + + public class DataPoint { + + private var timestamp:String; + private var value:Number; + private var description:String; + + public function DataPoint (timestamp:String, value:Number, description:String) { + this.timestamp = timestamp; + this.value = value; + this.description = description; + } + + public function getTimestamp():String { + return timestamp; + } + + public function getValue():Number { + return value; + } + + public function getDescription():String { + return description; + } + } +} diff --git a/src/flexchart/org/ovirt/data/DataSeries.as b/src/flexchart/org/ovirt/data/DataSeries.as new file mode 100644 index 0000000..764fd34 --- /dev/null +++ b/src/flexchart/org/ovirt/data/DataSeries.as @@ -0,0 +1,54 @@ +/* + 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 to encapsulate the json object representation of a data +//series returned from stats package + +package org.ovirt.data { + + public class DataSeries { + + private var object:Object; + private var dataPoints:Array; + private var description:String; + private var maxValue:Number; + + public function DataSeries (object:Object) { + this.object = object; + this.description = object["description"] as String; + dataPoints = new Array(); + var inDataPoints:Array = object["vectors"] as Array; + for (var i:int = 0; i < inDataPoints.length; i++) { + dataPoints.push(new DataPoint((inDataPoints[i] as Array)[0] as String, + (inDataPoints[i] as Array)[1] as Number, + description)); + } + maxValue = object["max_value"] as Number; + } + + public function getDataPoints():Array { + return dataPoints; + } + + public function getMaxValue():Number { + return maxValue; + } + } +} diff --git a/src/flexchart/org/ovirt/elements/SingleBar.as b/src/flexchart/org/ovirt/elements/SingleBar.as new file mode 100644 index 0000000..6e09bff --- /dev/null +++ b/src/flexchart/org/ovirt/elements/SingleBar.as @@ -0,0 +1,67 @@ +/* + 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. +*/ + +package org.ovirt.elements { + + import mx.containers.Box; + import mx.controls.ToolTip; + import mx.managers.ToolTipManager; + import flash.events.Event; + import flash.events.MouseEvent; + import flash.geom.Rectangle; + import flash.display.DisplayObject; + import org.ovirt.data.DataPoint; + + public class SingleBar extends Box { + + private var tip:ToolTip; + private var dataPoint:DataPoint; + + public function SingleBar(dataPoint:DataPoint) { + super(); + this.dataPoint = dataPoint; + addEventListener(MouseEvent.MOUSE_OVER,showTip); + addEventListener(MouseEvent.MOUSE_OUT,destroyTip); + this.setStyle("backgroundColor","0x0000FF"); + this.setStyle("left","1"); + this.setStyle("right","1"); + } + + private function showTip(event:Event):void { + var w:Number = this.stage.width; + var target:DisplayObject = event.currentTarget as DisplayObject; + var pt:Rectangle = this.stage.getBounds(target); + var yPos:Number = pt.y * -1; + var xPos:Number = pt.x * -1; + tip = ToolTipManager.createToolTip(dataPoint.getDescription() + "\n" + + dataPoint.getTimestamp() + "\n" + + dataPoint.getValue(), + xPos,yPos) as ToolTip; + tip.x = Math.min(tip.x, + w - tip.width); + tip.y = Math.max(0, + tip.y - tip.height); + } + + private function destroyTip(event:Event):void { + ToolTipManager.destroyToolTip(tip); + } + } +} diff --git a/src/flexchart/org/ovirt/elements/TextLiberation.as b/src/flexchart/org/ovirt/elements/TextLiberation.as new file mode 100644 index 0000000..f0314a2 --- /dev/null +++ b/src/flexchart/org/ovirt/elements/TextLiberation.as @@ -0,0 +1,46 @@ +/* + 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. +*/ + +package org.ovirt.elements { + + import mx.controls.Label; + + public class TextLiberation extends Label { + + //FIXME: this should point to a local asset somehow. + [Embed(source='/usr/share/fonts/liberation/LiberationSans-Regular.ttf', + fontName='liberation', + mimeType='application/x-font' + )] + //this variable exists so that the compiler will link in the font. + private var font1:Class; + + public function TextLiberation(text:String) { + super(); + this.text = text; + this.setStyle("fontFamily","liberation"); + } + + public function getTextWidth():Number { + this.validateNow(); + return this.textWidth; + } + } +} diff --git a/src/flexchart/org/ovirt/elements/YAxisLabel.as b/src/flexchart/org/ovirt/elements/YAxisLabel.as new file mode 100644 index 0000000..0e93b97 --- /dev/null +++ b/src/flexchart/org/ovirt/elements/YAxisLabel.as @@ -0,0 +1,41 @@ +/* + 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. +*/ + +package org.ovirt.elements { + + import mx.containers.Box; + import mx.core.ScrollPolicy; + + public class YAxisLabel extends Box { + + public var labelText:TextLiberation; + + public function YAxisLabel(text:String) { + super(); + labelText = new TextLiberation(text); + labelText.setVisible(true); + this.addChild(labelText); + this.horizontalScrollPolicy = ScrollPolicy.OFF; + this.verticalScrollPolicy = ScrollPolicy.OFF; + this.setStyle("paddingLeft","0"); + this.setStyle("paddingRight","0"); + } + } +} -- 1.5.6.5 From mburns at redhat.com Thu Oct 30 15:39:53 2008 From: mburns at redhat.com (mburns at redhat.com) Date: Thu, 30 Oct 2008 11:39:53 -0400 Subject: [Ovirt-devel] [PATCH node-image 1/1] Fixes Makefile.am to use the updates-released-f9.newkey repository when using a mirrorlist. Message-ID: <1225381193-2292-1-git-send-email-mburns@redhat.com> From: mburns --- Makefile.am | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index 5b5bc3a..fce5131 100644 --- a/Makefile.am +++ b/Makefile.am @@ -60,7 +60,7 @@ repos.ks: FEDORA_REPO=f$(FEDORA) ;\ FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/${ARCH}/os,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-$(FEDORA)&arch=$(ARCH))" ;\ OVIRT_DISTRO=$(FEDORA) ;\ - UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates-newkey $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH}.newkey,--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))" ;\ + UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates-newkey $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH}.newkey,--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA).newkey&arch=$(ARCH))" ;\ fi ;\ echo "repo --name=$${FEDORA_REPO} $${FEDORA_REPO_LOC}" > $@ ;\ echo "repo --name=ovirt-org --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/$(ARCH)" >> $@ ;\ -- 1.5.6.5 From danken at redhat.com Thu Oct 30 21:30:09 2008 From: danken at redhat.com (Dan Kenigsberg) Date: Thu, 30 Oct 2008 23:30:09 +0200 Subject: [Ovirt-devel] [PATCH appliance] allow for on-filesystem FEDORA_URL Message-ID: <1225402210-1630-1-git-send-email-danken@redhat.com> --- gettree.sh | 5 ++++- 1 files changed, 4 insertions(+), 1 deletions(-) diff --git a/gettree.sh b/gettree.sh index 42f1d29..67f6cf9 100755 --- a/gettree.sh +++ b/gettree.sh @@ -7,7 +7,10 @@ download() { local f=$1 - wget --progress=dot:mega --continue $1 + case "$f" in + file://*) cp ${f#file://} . ;; + *) wget --progress=dot:mega --continue $f ;; + esac printf "." } -- 1.5.6.5 From danken at redhat.com Thu Oct 30 21:30:10 2008 From: danken at redhat.com (Dan Kenigsberg) Date: Thu, 30 Oct 2008 23:30:10 +0200 Subject: [Ovirt-devel] [PATCH appliance] spell-out dependency In-Reply-To: <1225402210-1630-1-git-send-email-danken@redhat.com> References: <1225402210-1630-1-git-send-email-danken@redhat.com> Message-ID: <1225402210-1630-2-git-send-email-danken@redhat.com> --- ovirt-appliance.spec.in | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/ovirt-appliance.spec.in b/ovirt-appliance.spec.in index f42982d..fdf942f 100644 --- a/ovirt-appliance.spec.in +++ b/ovirt-appliance.spec.in @@ -16,7 +16,7 @@ License: GPLv2+ Group: Applications/System BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot URL: http://ovirt.org/ -BuildRequires: appliance-tools +BuildRequires: appliance-tools appliance-os BuildRequires: livecd-tools >= 017 BuildRequires: syslinux BuildRequires: qemu-img -- 1.5.6.5 From jguiditt at redhat.com Thu Oct 30 21:49:03 2008 From: jguiditt at redhat.com (Jason Guiditta) Date: Thu, 30 Oct 2008 17:49:03 -0400 Subject: [Ovirt-devel] [PATCH server] WIP - Rev 1 of Storage Tree UI for 'Create VM'. Message-ID: <1225403343-18309-1-git-send-email-jguiditt@redhat.com> This probably needs some visual tweaks, but should work as is. Needs integration testing with scott's work to create lvm volumes. Signed-off-by: Jason Guiditta --- src/app/controllers/vm_controller.rb | 2 + src/app/models/storage_volume.rb | 3 + src/app/views/vm/_form.rhtml | 59 ++++++++++++++------------- src/public/javascripts/ovirt.tree.js | 8 +++- src/public/stylesheets/ovirt-tree/tree.css | 5 ++- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index fc992b3..ee956da 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -41,6 +41,7 @@ class VmController < ApplicationController end def new + @storage_tree = VmResourcePool.find(params[:vm_resource_pool_id]).get_hardware_pool.storage_tree.to_json render :layout => 'popup' end @@ -80,6 +81,7 @@ class VmController < ApplicationController end def edit + @storage_tree = @vm.vm_resource_pool.get_hardware_pool.storage_tree(@vm).to_json render :layout => 'popup' end diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb index 3bd5da0..6f14c4d 100644 --- a/src/app/models/storage_volume.rb +++ b/src/app/models/storage_volume.rb @@ -81,6 +81,7 @@ class StorageVolume < ActiveRecord::Base :type => self[:type], :text => display_name, :name => display_name, + :size => size_in_gb, :available => ((vm_ids.empty?) or (vm_to_include and vm_to_include.id and vm_ids.include?(vm_to_include.id))), @@ -100,6 +101,8 @@ class StorageVolume < ActiveRecord::Base :conditions => condition).collect do |volume| volume.storage_tree_element(vm_to_include) end + else + return_hash[:children] = [] end return_hash end diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 3d4cd79..523e81e 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -29,7 +29,7 @@
      Storage:
      - +
        @@ -55,35 +55,38 @@ <%= check_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> - diff --git a/src/public/javascripts/ovirt.tree.js b/src/public/javascripts/ovirt.tree.js index 42719a1..13720b2 100644 --- a/src/public/javascripts/ovirt.tree.js +++ b/src/public/javascripts/ovirt.tree.js @@ -121,6 +121,7 @@ function processChildren(list, templateObj){ } } }); + this.openToSelected(self); }, toggle: function(e, elem) { $(elem) @@ -129,8 +130,11 @@ function processChildren(list, templateObj){ .siblings('ul').slideToggle("normal"); }, clickHandler: function(e,elem) { - alert('e: ' + e + ', elem: ' + elem); - var fred = 'fred'; + // make this a default impl if needed. + }, + openToSelected: function(self) { + //find 'selected' items and open tree accordingly. This may need to have a + //marker of some sort passed in since different trees may have different needs. }, off: function() { this.element.css({background: 'none'}); diff --git a/src/public/stylesheets/ovirt-tree/tree.css b/src/public/stylesheets/ovirt-tree/tree.css index bc5cca5..2c9456d 100644 --- a/src/public/stylesheets/ovirt-tree/tree.css +++ b/src/public/stylesheets/ovirt-tree/tree.css @@ -20,7 +20,7 @@ .ovirt-tree div { background-repeat: no-repeat; background-position: left; - padding: 4px 0 4px 28px; + padding: 4px 0 4px 4px; cursor: pointer; /* maybe move this into a clickable class? */ } @@ -30,14 +30,17 @@ } .HardwarePool { + padding: 4px 0 4px 28px !important; background-image: url('../../images/icon_hdwarepool.png'); } .VmResourcePool { + padding: 4px 0 4px 28px !important; background-image: url('../../images/icon_vmpool.png'); } .SmartPool { + padding: 4px 0 4px 28px !important; background-image: url('../../images/icon_smartpool.png'); } -- 1.5.6.5 From sseago at redhat.com Thu Oct 30 23:53:15 2008 From: sseago at redhat.com (Scott Seago) Date: Thu, 30 Oct 2008 23:53:15 +0000 Subject: [Ovirt-devel] [PATCH] lvm storage volume HW pool admin tab. Message-ID: <1225410795-2680-1-git-send-email-sseago@redhat.com> Includes 'view storage volume' facebox popup and new/delete LVM volume functionality. Although lvm tasks are created, taskomatic doesn't yet know what to do with them. Signed-off-by: Scott Seago --- src/app/controllers/hardware_controller.rb | 5 +- src/app/controllers/storage_controller.rb | 180 +++++++++++++++++++++++++- src/app/controllers/task_controller.rb | 2 + src/app/models/lvm_storage_pool.rb | 5 + src/app/models/storage_volume_task.rb | 3 +- src/app/views/storage/_lvm_volume_form.rhtml | 19 +++ src/app/views/storage/new_lvm_volume.rhtml | 42 ++++++ src/app/views/storage/show.rhtml | 63 +++++---- src/app/views/storage/show_volume.rhtml | 169 +++++++++++++++++------- 9 files changed, 404 insertions(+), 84 deletions(-) create mode 100644 src/app/views/storage/_lvm_volume_form.rhtml create mode 100644 src/app/views/storage/new_lvm_volume.rhtml diff --git a/src/app/controllers/hardware_controller.rb b/src/app/controllers/hardware_controller.rb index 4f24cb3..8c26184 100644 --- a/src/app/controllers/hardware_controller.rb +++ b/src/app/controllers/hardware_controller.rb @@ -117,7 +117,8 @@ class HardwareController < PoolController def show_tasks @task_types = [["VM Task", "VmTask"], ["Host Task", "HostTask"], - ["Storage Task", "StorageTask", "break"], + ["Storage Task", "StorageTask"], + ["Storage Volume Task", "StorageVolumeTask", "break"], ["Show All", ""]] super end @@ -162,7 +163,7 @@ class HardwareController < PoolController if params[:id] pre_show storage_pools = @pool.storage_pools - find_opts = {} + find_opts = {:conditions => "type != 'LvmStoragePool'"} include_pool = false else # FIXME: no permissions or usage checks here yet diff --git a/src/app/controllers/storage_controller.rb b/src/app/controllers/storage_controller.rb index fe524f3..75058cd 100644 --- a/src/app/controllers/storage_controller.rb +++ b/src/app/controllers/storage_controller.rb @@ -25,6 +25,7 @@ class StorageController < ApplicationController before_filter :pre_pool_admin, :only => [:refresh] before_filter :pre_new2, :only => [:new2] before_filter :pre_json, :only => [:storage_volumes_json] + before_filter :pre_create_volume, :only => [:create_volume] def index list @@ -90,15 +91,25 @@ class StorageController < ApplicationController flash[:notice] = 'You do not have permission to view this storage pool: redirecting to top level' redirect_to :controller => 'dashboard' end - json_list(@storage_pool.storage_volumes, - [:display_name, :size_in_gb, :get_type_label]) + attr_list = [] + attr_list << :id if (@storage_pool.user_subdividable and @can_modify) + attr_list += [:display_name, :size_in_gb, :get_type_label] + json_list(@storage_pool.storage_volumes, attr_list) end def show_volume @storage_volume = StorageVolume.find(params[:id]) set_perms(@storage_volume.storage_pool.hardware_pool) unless @can_view flash[:notice] = 'You do not have permission to view this storage volume: 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 => 'popup' } + format.xml { render :xml => @storage_volume.to_xml } + end end end @@ -110,6 +121,70 @@ class StorageController < ApplicationController render :layout => false end + def new_volume + new_volume_internal(StoragePool.find(params[:storage_pool_id]), + { :storage_pool_id => params[:storage_pool_id]}) + render :layout => 'popup' + end + + def new_lvm_volume + @source_volume = StorageVolume.find(params[:source_volume_id]) + @return_facebox = params[:return_facebox] + unless @source_volume.supports_lvm_subdivision + #fixme: proper error page for popups + redirect_to :controller => 'dashboard' + return + end + lvm_pool = @source_volume.lvm_storage_pool + unless lvm_pool + # FIXME: what should we do about VG/LV names? + # for now auto-create VG name as ovirt_vg_#{@source_volume.id} + lvm_pool = LvmStoragePool.new(:vg_name => "ovirt_vg_#{@source_volume.id}", + :hardware_pool_id => @source_volume.storage_pool.hardware_pool_id) + lvm_pool.source_volumes << @source_volume + lvm_pool.save! + end + new_volume_internal(lvm_pool, { :storage_pool_id => lvm_pool.id}) + @storage_volume.lv_owner_perms='0744' + @storage_volume.lv_group_perms='0744' + @storage_volume.lv_mode_perms='0744' + render :layout => 'popup' + end + + def create_volume + begin + StorageVolume.transaction do + @storage_volume.save! + @task = StorageVolumeTask.new({ :user => @user, + :task_target => @storage_volume, + :action => StorageVolumeTask::ACTION_CREATE_VOLUME, + :state => Task::STATE_QUEUED}) + @task.save! + end + respond_to do |format| + format.json { render :json => { :object => "storage_volume", + :success => true, + :alert => "Storage Volume was successfully created." } } + format.xml { render :xml => @storage_volume, + :status => :created, + # FIXME: create storage_volume_url method if relevant + :location => storage_pool_url(@storage_volume) + } + end + rescue => ex + # FIXME: need to distinguish volume vs. task save errors + respond_to do |format| + format.json { + json_hash = { :object => "storage_volume", :success => false, + :errors => @storage_volume.errors.localize_error_messages.to_a } + json_hash[:message] = ex.message if json_hash[:errors].empty? + render :json => json_hash } + format.xml { render :xml => @storage_volume.errors, + :status => :unprocessable_entity } + end + end + end + def insert_refresh_task @task = StorageTask.new({ :user => @user, :task_target => @storage_pool, @@ -244,6 +319,67 @@ class StorageController < ApplicationController end end + def delete_volumes + storage_volume_ids_str = params[:storage_volume_ids] + storage_volume_ids = storage_volume_ids_str.split(",").collect {|x| x.to_i} + alerts = [] + status = true + begin + StorageVolume.transaction do + storage = StorageVolume.find(:all, :conditions => "id in (#{storage_volume_ids.join(', ')})") + unless storage.empty? + set_perms(storage[0].storage_pool.hardware_pool) + unless @can_modify and storage[0].storage_pool.user_subdividable + respond_to do |format| + format.json { render :json => { :object => "storage_volume", + :success => false, + :alert => "You do not have permission to delete this storage volume." } } + format.xml { head :forbidden } + end + else + storage.each do |storage_volume| + alert, success = delete_volume_internal(storage_volume) + alerts << alert + status = false unless success + end + respond_to do |format| + format.json { render :json => { :object => "storage_volume", + :success => status, :alert => alerts.join("\n") } } + format.xml { head(status ? :ok : :method_not_allowed) } + end + end + else + respond_to do |format| + format.json { render :json => { :object => "storage_volume", + :success => false, :alert => "no volumes selected" } } + format.xml { head(status ? :ok : :method_not_allowed) } + end + end + end + end + end + + def delete_volume + @storage_volume = StorageVolume.find(params[:id]) + set_perms(@storage_volume.storage_pool.hardware_pool) + unless @can_modify and @storage_volume.storage_pool.user_subdividable + respond_to do |format| + format.json { render :json => { :object => "storage_volume", + :success => false, + :alert => "You do not have permission to delete this storage volume." } } + format.xml { head :forbidden } + end + else + alert, success = delete_volume_internal(@storage_volume) + respond_to do |format| + format.json { render :json => { :object => "storage_volume", + :success => success, :alert => alert } } + format.xml { head(success ? :ok : :method_not_allowed) } + end + end + + end + def pre_new @hardware_pool = HardwarePool.find(params[:hardware_pool_id]) @perm_obj = @hardware_pool @@ -274,6 +410,16 @@ class StorageController < ApplicationController @perm_obj = @storage_pool.hardware_pool @redir_obj = @storage_pool end + def pre_create_volume + volume = params[:storage_volume] + unless type = params[:storage_type] + type = volume.delete(:storage_type) + end + @storage_volume = StorageVolume.factory(type, volume) + @perm_obj = @storage_volume.storage_pool.hardware_pool + @redir_controller = @storage_volume.storage_pool.hardware_pool.get_controller + authorize_admin + end def pre_json pre_show end @@ -282,4 +428,32 @@ class StorageController < ApplicationController authorize_admin end + private + def new_volume_internal(storage_pool, new_params) + @storage_volume = StorageVolume.factory(storage_pool.get_type_label, new_params) + @perm_obj = @storage_volume.storage_pool.hardware_pool + authorize_admin + end + + def delete_volume_internal(volume) + begin + name = volume.display_name + if !volume.vms.empty? + vm_list = volume.vms.collect {|vm| vm.description}.join(", ") + ["Storage Volume #{name} must be unattached from VMs (#{vm_list}) before deleting it.", + false] + #FIXME: create delete volume task. include metadata in task + else + #FIXME: need to set volume to 'unavailable' state once we have states + @task = StorageVolumeTask.new({ :user => @user, + :task_target => volume, + :action => StorageVolumeTask::ACTION_DELETE_VOLUME, + :state => Task::STATE_QUEUED}) + @task.save! + ["Storage Volume #{name} deletion was successfully queued.", true] + end + rescue => ex + ["Failed to delete storage volume #{name} (#{ex.message}.",false] + end + end end diff --git a/src/app/controllers/task_controller.rb b/src/app/controllers/task_controller.rb index 73e5155..647d69c 100644 --- a/src/app/controllers/task_controller.rb +++ b/src/app/controllers/task_controller.rb @@ -24,6 +24,8 @@ class TaskController < ApplicationController set_perms(@task.vm.vm_resource_pool) elsif @task[:type] == StorageTask.name set_perms(@task.storage_pool.hardware_pool) + elsif @task[:type] == StorageVolumeTask.name + set_perms(@task.storage_volume.storage_pool.hardware_pool) elsif @task[:type] == HostTask.name set_perms(@task.host.hardware_pool) end diff --git a/src/app/models/lvm_storage_pool.rb b/src/app/models/lvm_storage_pool.rb index 08b8938..84fbb28 100644 --- a/src/app/models/lvm_storage_pool.rb +++ b/src/app/models/lvm_storage_pool.rb @@ -44,5 +44,10 @@ class LvmStoragePool < StoragePool kb_to_gb(size) end + # FIXME: needs to take free space into account here + def user_subdividable + true + end + end diff --git a/src/app/models/storage_volume_task.rb b/src/app/models/storage_volume_task.rb index 78f2895..136f1ba 100644 --- a/src/app/models/storage_volume_task.rb +++ b/src/app/models/storage_volume_task.rb @@ -20,7 +20,7 @@ class StorageVolumeTask < Task ACTION_CREATE_VOLUME = "create_volume" - ACTION_EDIT_VOLUME = "edit_volume" + ACTION_DELETE_VOLUME = "delete_volume" def after_initialize self.hardware_pool = task_target.storage_pool.hardware_pool if self.task_target_type=="StorageVolume" @@ -29,7 +29,6 @@ class StorageVolumeTask < Task def task_obj "StorageVolume;;;#{self.storage_volume.id};;;#{self.storage_volume.display_name}" end - end def host nil end diff --git a/src/app/views/storage/_lvm_volume_form.rhtml b/src/app/views/storage/_lvm_volume_form.rhtml new file mode 100644 index 0000000..823158e --- /dev/null +++ b/src/app/views/storage/_lvm_volume_form.rhtml @@ -0,0 +1,19 @@ +<%= error_messages_for 'storage_volume' %> + + +<%= hidden_field 'storage_volume', 'storage_pool_id' %> +<%= hidden_field_tag 'storage_type', @storage_volume.get_type_label %> + +<%= text_field_with_label "Size (GB):", 'storage_volume', 'size_in_gb' %> + +<%= text_field_with_label "LV Name:", 'storage_volume', 'lv_name' %> + +<%= text_field_with_label "Owner permissions:", 'storage_volume', 'lv_owner_perms' %> + +<%= text_field_with_label "Group permissions:", 'storage_volume', 'lv_group_perms' %> + +<%= text_field_with_label "Mode permissions:", 'storage_volume', 'lv_mode_perms' %> + + + + diff --git a/src/app/views/storage/new_lvm_volume.rhtml b/src/app/views/storage/new_lvm_volume.rhtml new file mode 100644 index 0000000..ea51cc5 --- /dev/null +++ b/src/app/views/storage/new_lvm_volume.rhtml @@ -0,0 +1,42 @@ +<%- content_for :title do -%> + <%= _("Add New LVM Volume") %> +<%- end -%> +<%- content_for :description do -%> + Add a new LVM Storage Volume to <%= @source_volume.display_name %>. +<%- end -%> +
        +
        +
        +
        +
        + <%= render :partial => 'lvm_volume_form' %> +
        +
        + + <%= popup_footer("$('#lvm_volume_form').submit()", "New LVM Volume") %> +
        +
        + diff --git a/src/app/views/storage/show.rhtml b/src/app/views/storage/show.rhtml index 82c9dab..a84dc62 100644 --- a/src/app/views/storage/show.rhtml +++ b/src/app/views/storage/show.rhtml @@ -18,24 +18,24 @@
        - IP address:
        + IP address:
        <% if @storage_pool[:type] == "IscsiStoragePool" %> - Port:
        - Target:
        + Port:
        + Target:
        <% elsif @storage_pool[:type] == "NfsStoragePool" %> - Export path:
        + Export path:
        <% end %> - Type:
        + Type:
        - <%=h @storage_pool.ip_addr %>
        + <%=h @storage_pool.ip_addr %>
        <% if @storage_pool[:type] == "IscsiStoragePool" %> - <%=h @storage_pool.port %>
        - <%=h @storage_pool.target %>
        + <%=h @storage_pool.port %>
        + <%=h @storage_pool.target %>
        <% elsif @storage_pool[:type] == "NfsStoragePool" %> - <%=h @storage_pool.export_path %>
        + <%=h @storage_pool.export_path %>
        <% end %> - <%=h @storage_pool.get_type_label %>
        + <%=h @storage_pool.get_type_label %>
        <%- content_for :right do -%>
        Volumes
        @@ -54,24 +54,33 @@ <%= confirmation_dialog("confirm_delete_storage", "Are you sure?", "delete_storage_pool()") %> -- 1.5.5.1 From mmorsi at redhat.com Fri Oct 31 01:27:35 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Thu, 30 Oct 2008 21:27:35 -0400 Subject: [Ovirt-devel] [PATCH server] network integration into ovirt server db and wui Message-ID: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> --- src/app/controllers/host_controller.rb | 12 +- src/app/controllers/network_controller.rb | 427 ++++++++++++++++++++ src/app/helpers/application_helper.rb | 28 +- src/app/helpers/network_helper.rb | 2 + src/app/models/bonding.rb | 27 +- src/app/models/ip_address.rb | 27 ++ src/app/models/ip_v4_address.rb | 48 +++ src/app/models/ip_v6_address.rb | 44 ++ src/app/models/network.rb | 37 ++ src/app/models/nic.rb | 16 +- src/app/models/physical_network.rb | 27 ++ src/app/models/usage.rb | 21 + src/app/models/vlan.rb | 30 ++ src/app/views/dashboard/index.html.erb | 7 + src/app/views/host/edit_network.rhtml | 85 ++++ src/app/views/host/show.rhtml | 4 + src/app/views/network/_bonding_form.rhtml | 46 +++ src/app/views/network/_form.rhtml | 31 ++ src/app/views/network/_grid.rhtml | 37 ++ src/app/views/network/_ip_address_form.rhtml | 62 +++ src/app/views/network/_ip_addresses_form.rhtml | 50 +++ src/app/views/network/_select.rhtml | 32 ++ src/app/views/network/edit.rhtml | 34 ++ src/app/views/network/edit_bonding.rhtml | 52 +++ src/app/views/network/edit_ip_address.rhtml | 66 +++ .../views/network/edit_network_ip_addresses.rhtml | 13 + src/app/views/network/edit_nic.rhtml | 62 +++ src/app/views/network/list.html.erb | 72 ++++ src/app/views/network/new.rhtml | 28 ++ src/app/views/network/new_bonding.rhtml | 32 ++ src/app/views/network/new_ip_address.rhtml | 33 ++ src/app/views/network/show.rhtml | 30 ++ src/app/views/nic/_list.rhtml | 2 - src/db/migrate/028_refactor_networking_model.rb | 271 +++++++++++++ src/public/javascripts/ovirt.js | 29 ++- src/public/stylesheets/components.css | 50 +++- 36 files changed, 1849 insertions(+), 25 deletions(-) create mode 100644 src/app/controllers/network_controller.rb create mode 100644 src/app/helpers/network_helper.rb create mode 100644 src/app/models/ip_address.rb create mode 100644 src/app/models/ip_v4_address.rb create mode 100644 src/app/models/ip_v6_address.rb create mode 100644 src/app/models/network.rb create mode 100644 src/app/models/physical_network.rb create mode 100644 src/app/models/usage.rb create mode 100644 src/app/models/vlan.rb create mode 100644 src/app/views/host/edit_network.rhtml create mode 100644 src/app/views/network/_bonding_form.rhtml create mode 100644 src/app/views/network/_form.rhtml create mode 100644 src/app/views/network/_grid.rhtml create mode 100644 src/app/views/network/_ip_address_form.rhtml create mode 100644 src/app/views/network/_ip_addresses_form.rhtml create mode 100644 src/app/views/network/_select.rhtml create mode 100644 src/app/views/network/edit.rhtml create mode 100644 src/app/views/network/edit_bonding.rhtml create mode 100644 src/app/views/network/edit_ip_address.rhtml create mode 100644 src/app/views/network/edit_network_ip_addresses.rhtml create mode 100644 src/app/views/network/edit_nic.rhtml create mode 100644 src/app/views/network/list.html.erb create mode 100644 src/app/views/network/new.rhtml create mode 100644 src/app/views/network/new_bonding.rhtml create mode 100644 src/app/views/network/new_ip_address.rhtml create mode 100644 src/app/views/network/show.rhtml create mode 100644 src/db/migrate/028_refactor_networking_model.rb diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb index a40d297..120fe2a 100644 --- a/src/app/controllers/host_controller.rb +++ b/src/app/controllers/host_controller.rb @@ -30,7 +30,7 @@ class HostController < ApplicationController end end - before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms] + before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms, :edit_network] # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => [:post, :put], :only => [ :create, :update ], @@ -156,6 +156,16 @@ class HostController < ApplicationController render :json => @json_hash end + def edit_network + render :layout => 'popup' + end + + def bondings_json + bondings = Host.find(params[:id]).bondings + bondings_json = [] + bondings.each{ |x| bondings_json.push({:id => x.id, :name => x.name}) } + render :json => bondings_json + end private #filter methods diff --git a/src/app/controllers/network_controller.rb b/src/app/controllers/network_controller.rb new file mode 100644 index 0000000..b7f8b6d --- /dev/null +++ b/src/app/controllers/network_controller.rb @@ -0,0 +1,427 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. +# + +class NetworkController < ApplicationController + ########################## Networks related actions + + def network_permissions + # TODO more robust permission system + # either by subclassing network from pool + # or by extending permission model to accomodate + # any object + @default_pool = HardwarePool.get_default_pool + set_perms(@default_pool) + unless @can_modify + flash[:notice] = 'You do not have permission to view networks' + redirect_to :controller => 'dashboard' + end + end + + def list + @networks = Network.find(:all) + network_permissions + end + + def networks_json + json_list(Network.find(:all), [:id, :name, :type, [:boot_type, :label]]) + end + + def show + @network = Network.find(params[:id]) + network_permissions + respond_to do |format| + format.html { render :layout => 'selection' } + format.xml { render :xml => @network.to_xml } + end + end + + def new + @boot_types = BootType.find(:all) + render :layout => 'popup' + end + + def create + begin + @network = PhysicalNetwork.new(params[:network]) if params[:network][:type] == 'PhysicalNetwork' + @network = Vlan.new(params[:network]) if params[:network][:type] == 'Vlan' + @network.save! + alert = "Network was successfully created." + render :json => { :object => "network", :success => true, + :alert => alert } + rescue + render :json => { :object => "network", :success => false, + :errors => + @network.errors.localize_error_messages.to_a } + end + end + + def edit + @network = Network.find(params[:id]) + @boot_types = BootType.find(:all) + render :layout => 'popup' + end + + def update + begin + @network = Network.find(params[:id]) + + # special case if we are switching types + if @network.type != params[:network][:type] + if ! @network.conversion_valid? + render :json => { :object => "network", :success => false, + :alert => + 'Can not change type of network with associated nics/bondings'} + return + end + + if params[:network][:type] == 'PhysicalNetwork' + Vlan.destroy(params[:id]) + @network = PhysicalNetwork.create(params[:network]) + else + PhysicalNetwork.destroy(params[:id]) + @network = Vlan.create(params[:network]) + end + else + @network = PhysicalNetwork.find(params[:id]) if @network.type == 'PhysicalNetwork' + @network = Vlan.find(params[:id]) if @network.type == 'Vlan' + @network.update_attributes!(params[:network]) + end + + alert = "Network was successfully updated." + render :json => { :object => "network", :success => true, + :alert => alert } + rescue Exception => e + render :json => { :object => "network", :success => false, + :errors => + @network.errors.localize_error_messages.to_a } + end + end + + def delete + failed_networks = [] + networks_ids_str = params[:network_ids] + network_ids = networks_ids_str.split(",").collect {|x| x.to_i} + network_ids.each{ |x| + network = Network.find(x) + if network.conversion_valid? + begin + Network.destroy(x) + rescue + failed_networks.push x + end + else + failed_networks.push x + end + } + if failed_networks.size == 0 + render :json => { :object => "network", + :success => true, + :alert => "Successfully deleted networks" } + else + render :json => { :object => "network", + :success => false, + :alert => "Failed deleting " + + failed_networks.size.to_s + + " networks due to existing bondings/nics" } + end + end + + def edit_network_ip_addresses + @network = Network.find(params[:id]) + render :layout => 'popup' + end + + + ########################## Ip Address related actions + + def ip_addresses_json + @parent_type = params[:parent_type] + if @parent_type == 'network' + ip_addresses = Network.find(params[:id]).ip_addresses + elsif @parent_type == 'nic' + ip_addresses = Nic.find(params[:id]).ip_addresses + elsif @parent_type == 'bonding' and params[:id] + ip_addresses = Bonding.find(params[:id]).ip_addresses + else + ip_addresses = [] + end + + ip_addresses_json = [] + ip_addresses.each{ |x| + ip_addresses_json.push({:id => x.id, :name => x.address}) } + render :json => ip_addresses_json + end + + def new_ip_address + @parent_type = params[:parent_type] + @network = Network.find(params[:id]) if @parent_type == 'network' + @nic = Nic.find(params[:id]) if @parent_type == 'nic' + @bonding = Bonding.find(params[:id]) if @parent_type == 'bonding' and params[:id] + + render :layout => false + end + + def _create_ip_address + if params[:ip_address][:type] == "IpV4Address" + @ip_address = IpV4Address.new(params[:ip_address]) + else + @ip_address = IpV6Address.new(params[:ip_address]) + end + @ip_address.save! + end + + def create_ip_address + begin + _create_ip_address + alert = "Ip Address was successfully created." + render :json => { :object => "ip_address", :success => true, + :alert => alert } + rescue + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a } + end + end + + def edit_ip_address + @ip_address = IpAddress.find(params[:id]) + + @parent_type = params[:parent_type] + @network = @ip_address.network if @ip_address.network_id + @nic = @ip_address.nic if @ip_address.nic_id + @bonding = @ip_address.bonding if @ip_address.bonding_id + + render :layout => false + end + + def _update_ip_address(id) + @ip_address = IpAddress.find(id) + + # special case if we are switching types + if @ip_address.type != params[:ip_address][:type] + if params[:ip_address][:type] == 'IpV4Address' + @ip_address = IpV4Address.new(params[:ip_address]) + @ip_address.save! + IpV6Address.delete(id) + else + @ip_address = IpV6Address.new(params[:ip_address]) + @ip_address.save! + IpV4Address.delete(id) + end + else + if @ip_address.type == 'IpV4Address' + @ip_address = IpV4Address.find(id) + else + @ip_address = IpV6Address.find(id) + end + @ip_address.update_attributes!(params[:ip_address]) + end + + end + + def update_ip_address + begin + _update_ip_address(params[:id]) + alert = "IpAddress was successfully updated." + render :json => { :object => "network", :success => true, + :alert => alert } + rescue + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a } + end + end + + def destroy_ip_address + begin + IpAddress.delete(params[:id]) + alert = "Ip Address was successfully deleted." + render :json => { :object => "ip_address", :success => true, + :alert => alert } + rescue + render :json => { :object => "ip_address", :success => false, + :alert => 'Ip Address Deletion Failed' } + end + end + + + ########################## NICs related actions + + def edit_nic + @nic = Nic.find(params[:id]) + @network = @nic.physical_network + + @networks = PhysicalNetwork.find(:all) + network_options + + render :layout => false + end + + def update_nic + begin + network_options + @network = Network.find(params[:nic][:physical_network_id]) + + if @network.boot_type.id == @static_boot_type.id + if params[:ip_address][:id] == "New" + _create_ip_address + elsif params[:ip_address][:id] != "" + _update_ip_address(params[:ip_address][:id]) + end + end + + @nic = Nic.find(params[:id]) + @nic.update_attributes!(params[:nic]) + + alert = "Nic was successfully updated." + render :json => { :object => "nic", :success => true, + :alert => alert } + rescue Exception => e + if @ip_address and @ip_address.errors.size != 0 + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a} + else + render :json => { :object => "nic", :success => false, + :errors => + @nic.errors.localize_error_messages.to_a } + end + end + end + + ########################## Bonding related actions + + def new_bonding + unless params[:host_id] + flash[:notice] = "Host is required." + redirect_to :controller => 'dashboard' + end + + @host = Host.find(params[:host_id]) + @networks = Vlan.find(:all) + network_options + + render :layout => false + end + + def create_bonding + begin + network_options + @network = Network.find(params[:bonding][:vlan_id]) + + if @network.boot_type.id == @static_boot_type.id + if params[:ip_address][:id] == "New" + _create_ip_address + elsif params[:ip_address][:id] != "" + _update_ip_address(params[:ip_address][:id]) + end + end + + @bonding = Bonding.new(params[:bonding]) + @bonding.save! + + if @ip_address + @ip_address.bonding_id = @bonding.id + @ip_address.save! + end + + alert = "Bonding was successfully created." + render :json => { :object => "bonding", :success => true, + :alert => alert } + rescue + if @ip_address and @ip_address.errors.size != 0 + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a} + else + render :json => { :object => "bonding", :success => false, + :errors => + @bonding.errors.localize_error_messages.to_a } + end + end + end + + def edit_bonding + @bonding = Bonding.find(params[:id]) + @network = @bonding.vlan + + @host = @bonding.host + @networks = Vlan.find(:all) + network_options + + render :layout => false + end + + def update_bonding + begin + network_options + @network = Network.find(params[:bonding][:vlan_id]) + + if @network.boot_type.id == @static_boot_type.id + if params[:ip_address][:id] == "New" + _create_ip_address + elsif params[:ip_address][:id] != "" + _update_ip_address(params[:ip_address][:id]) + end + end + + @bonding = Bonding.find(params[:id]) + @bonding.nics.each { |nic| @bonding.nics.delete(nic) } + @bonding.update_attributes!(params[:bonding]) + + alert = "Bonding was successfully updated." + render :json => { :object => "bonding", :success => true, + :alert => alert } + rescue + if @ip_address and @ip_address.errors.size != 0 + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a} + else + render :json => { :object => "bonding", :success => false, + :errors => + @bonding.errors.localize_error_messages.to_a } + end + end + end + + def destroy_bonding + begin + Bonding.destroy(params[:id]) + alert = "Bonding was successfully deleted." + render :json => { :object => "bonding", :success => true, + :alert => alert } + rescue + render :json => { :object => "bonding", :success => false, + :alert => 'Bonding Deletion Failed' } + end + + end + + + ########################## Misc methods + + protected + def network_options + @bonding_types = BondingType.find(:all) + @static_boot_type = BootType.find(:first, + :conditions => { :proto => 'static' }) + end + +end diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index d7b6628..0178ad0 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -84,21 +84,31 @@ module ApplicationHelper } end - def popup_footer(action, label) - %{ -
        + # expects hash of labels => actions + def multi_button_popup_footer(buttons = {}) + buttons_html = "" + buttons.each{ |label, action| + buttons_html+="
        " + + "
        " + + " " + + "
        " + + "
        " + } + + %{ +
        -
        -
        - -
        -
        + #{buttons_html}
        - } + } + end + + def popup_footer(action, label) + multi_button_popup_footer({label => action}) end def ok_footer diff --git a/src/app/helpers/network_helper.rb b/src/app/helpers/network_helper.rb new file mode 100644 index 0000000..ebbce0b --- /dev/null +++ b/src/app/helpers/network_helper.rb @@ -0,0 +1,2 @@ +module NetworkHelper +end diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 006c261..a3ad150 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -30,6 +30,16 @@ # interface. They can be ignored if not used. # class Bonding < ActiveRecord::Base + belongs_to :host + belongs_to :bonding_type + belongs_to :vlan + has_many :ip_addresses, :dependent => :destroy + + has_and_belongs_to_many :nics, + :join_table => 'bondings_nics', + :foreign_key => :bonding_id + + validates_presence_of :name, :message => 'A name is required.' @@ -42,18 +52,17 @@ class Bonding < ActiveRecord::Base validates_presence_of :interface_name, :message => 'An interface name is required.' - validates_presence_of :boot_type_id, - :message => 'A boot type must be specified.' - validates_presence_of :bonding_type_id, :message => 'A bonding type must be specified.' - belongs_to :host - belongs_to :bonding_type - belongs_to :boot_type + protected + def validate + errors.add("name", "must be specified") unless name + errors.add("interface_name", "must be specified") unless interface_name + errors.add("bonding_type_id", "must be specified") unless bonding_type_id + errors.add("host_id", "must be specified") unless host_id + errors.add("vlan_id", "must be specified") unless vlan_id + end - has_and_belongs_to_many :nics, - :join_table => 'bondings_nics', - :foreign_key => :bonding_id end diff --git a/src/app/models/ip_address.rb b/src/app/models/ip_address.rb new file mode 100644 index 0000000..5d2e6af --- /dev/null +++ b/src/app/models/ip_address.rb @@ -0,0 +1,27 @@ +# ip_address.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. + +# +IpAddress+ is the base class for all address related classes. +# +class IpAddress < ActiveRecord::Base + # one of these 3 will apply for each address + belongs_to :network + belongs_to :nic + belongs_to :bonding +end diff --git a/src/app/models/ip_v4_address.rb b/src/app/models/ip_v4_address.rb new file mode 100644 index 0000000..40e8cf9 --- /dev/null +++ b/src/app/models/ip_v4_address.rb @@ -0,0 +1,48 @@ +# ip_v4_address.rb +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Darryl L. Pierce , +# Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# +IpV4Address+ represents a single IPv4 address. +# +class IpV4Address < IpAddress + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} + + validates_presence_of :address, + :message => 'An address must be supplied.' + validates_format_of :address, + :with => ADDRESS_TEST + + protected + def validate + unless address and address =~ ADDRESS_TEST + errors.add("address", "is of incorrect format") + end + unless !netmask or netmask == "" or netmask =~ ADDRESS_TEST + errors.add("netmask", "is of incorrect format") + end + unless !gateway or gateway == "" or gateway =~ ADDRESS_TEST + errors.add("gateway", "is of incorrect format") + end + unless !broadcast or broadcast == "" or broadcast =~ ADDRESS_TEST + errors.add("broadcast", "is of incorrect format") + end + end + +end diff --git a/src/app/models/ip_v6_address.rb b/src/app/models/ip_v6_address.rb new file mode 100644 index 0000000..9720179 --- /dev/null +++ b/src/app/models/ip_v6_address.rb @@ -0,0 +1,44 @@ +# ip_v6_address.rb +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Darryl L. Pierce , +# Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# +IpV6Address+ represents a single IPv6 address. +# +class IpV6Address < IpAddress + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} + + validates_presence_of :address, + :message => 'An address must be provided.' + validates_format_of :address, + :with => ADDRESS_TEST + + protected + def validate + unless address =~ ADDRESS_TEST + errors.add("address", "is of incorrect format") + end + unless !prefix or prefix == "" or prefix =~ ADDRESS_TEST + errors.add("prefix", "is of incorrect format") + end + unless !gateway or gateway == "" or gateway =~ ADDRESS_TEST + errors.add("gateway", "is of incorrect format") + end + end +end diff --git a/src/app/models/network.rb b/src/app/models/network.rb new file mode 100644 index 0000000..99e1a94 --- /dev/null +++ b/src/app/models/network.rb @@ -0,0 +1,37 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class Network < ActiveRecord::Base + belongs_to :boot_type + has_many :ip_addresses, :dependent => :destroy + + has_and_belongs_to_many :usages, :join_table => 'networks_usages' + + validates_presence_of :type + validates_presence_of :name + validates_presence_of :boot_type_id + + def conversion_valid? + return false if type.to_s == 'Vlan' && + Vlan.find(id).bondings.size != 0 || + type.to_s == 'PhysicalNetwork' && + PhysicalNetwork.find(id).nics.size != 0 + return true + end + +end diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb index baf7095..25d3ac2 100644 --- a/src/app/models/nic.rb +++ b/src/app/models/nic.rb @@ -19,7 +19,19 @@ class Nic < ActiveRecord::Base belongs_to :host - belongs_to :boot_type + belongs_to :physical_network + has_many :ip_addresses, :dependent => :destroy - has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' + has_and_belongs_to_many :bondings, :join_table => 'bondings_nics' + + protected + def validate + errors.add("host_id", "must be specified") unless host_id + errors.add("physical_network_id", "must be specified") unless physical_network_id + if physical_network.boot_type.id == + BootType.find(:first, :conditions => { :proto => 'static' }).id and + ip_addresses.size == 0 + errors.add("ip_address_id", "must be specified") + end + end end diff --git a/src/app/models/physical_network.rb b/src/app/models/physical_network.rb new file mode 100644 index 0000000..cba696a --- /dev/null +++ b/src/app/models/physical_network.rb @@ -0,0 +1,27 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class PhysicalNetwork < Network + has_many :nics + + protected + def validate + errors.add("name", "must be specified") unless name + errors.add("boot_type_id", "must be specified") unless boot_type_id + end +end diff --git a/src/app/models/usage.rb b/src/app/models/usage.rb new file mode 100644 index 0000000..353e8f4 --- /dev/null +++ b/src/app/models/usage.rb @@ -0,0 +1,21 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class Usage < ActiveRecord::Base + has_and_belongs_to_many :networks, :join_table => 'networks_usages' +end diff --git a/src/app/models/vlan.rb b/src/app/models/vlan.rb new file mode 100644 index 0000000..2b96502 --- /dev/null +++ b/src/app/models/vlan.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class Vlan < Network + has_many :bondings + + validates_presence_of :number + + protected + def validate + errors.add("name", "must be specified") unless name + errors.add("number", "must be specified") unless number + errors.add("boot_type_id", "must be specified") unless boot_type_id + end +end diff --git a/src/app/views/dashboard/index.html.erb b/src/app/views/dashboard/index.html.erb index 92cb4da..8815ebc 100644 --- a/src/app/views/dashboard/index.html.erb +++ b/src/app/views/dashboard/index.html.erb @@ -13,6 +13,13 @@
        + <% if @can_modify %> +

        Networks

        + + View / Edit + + <% end %> +
        diff --git a/src/app/views/host/edit_network.rhtml b/src/app/views/host/edit_network.rhtml new file mode 100644 index 0000000..7ec3180 --- /dev/null +++ b/src/app/views/host/edit_network.rhtml @@ -0,0 +1,85 @@ +<%- content_for :title do -%> + Edit <%= @host.hostname %> Network Devices +<%- end -%> + +<%- content_for :description do -%> + Select and edit nics and bonded interfaces on <%= @host.hostname %> +<%- end -%> + + + + + +
        + +
        + + + + diff --git a/src/app/views/host/show.rhtml b/src/app/views/host/show.rhtml index d1b05ad..bc39a62 100644 --- a/src/app/views/host/show.rhtml +++ b/src/app/views/host/show.rhtml @@ -17,6 +17,10 @@ <%= image_tag "icon_x.png" %> Clear VMs <% end -%> + <%= link_to image_tag("icon_edit.png") +"Edit Network", + {:controller => 'host', + :action => 'edit_network', :id => @host.id}, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> <%- end -%> <%- end -%> diff --git a/src/app/views/network/_grid.rhtml b/src/app/views/network/_grid.rhtml new file mode 100644 index 0000000..6af0c05 --- /dev/null +++ b/src/app/views/network/_grid.rhtml @@ -0,0 +1,37 @@ +<% networks_per_page = 40 unless (defined? networks_per_page) and !(networks_per_page.nil?) %> +<% usepager = @networks.size > networks_per_page %> + +
        +
        + +
        +
        + + + diff --git a/src/app/views/network/_ip_address_form.rhtml b/src/app/views/network/_ip_address_form.rhtml new file mode 100644 index 0000000..b952807 --- /dev/null +++ b/src/app/views/network/_ip_address_form.rhtml @@ -0,0 +1,62 @@ +<%= error_messages_for 'ip_address' %> + +
        + <%= hidden_field_tag 'parent_type', @parent_type%> + <%= hidden_field_tag 'ip_address[id]', @ip_address.id if @ip_address%> + <%= hidden_field_tag 'ip_address[network_id]', @network.id if @network %> + <%= hidden_field_tag 'ip_address[nic_id]', @nic.id if @nic %> + <%= hidden_field_tag 'ip_address[bonding_id]', @bonding.id if @bonding %> + +
        Type:
        +
        + <%= select_with_label "", "ip_address", "type", + [[ "ipv4", "IpV4Address" ], [ "ipv6", "IpV6Address" ] ] %> +
        + + +
        +
        IP Address
        +
        + <%= text_field_with_label "", "ip_address", "address" %> +
        +
        + +
        +
        Netmask
        +
        + <%= text_field_with_label "", "ip_address", "netmask" %> +
        + +
        Broadcast
        +
        + <%= text_field_with_label "", "ip_address", "broadcast" %> +
        +
        + + + +
        +
        Gateway
        +
        + <%= text_field_with_label "", "ip_address", "gateway" %> +
        +
        + +
        + + diff --git a/src/app/views/network/_ip_addresses_form.rhtml b/src/app/views/network/_ip_addresses_form.rhtml new file mode 100644 index 0000000..f833b2a --- /dev/null +++ b/src/app/views/network/_ip_addresses_form.rhtml @@ -0,0 +1,50 @@ + + +
        + + + diff --git a/src/app/views/network/_select.rhtml b/src/app/views/network/_select.rhtml new file mode 100644 index 0000000..e69d9b0 --- /dev/null +++ b/src/app/views/network/_select.rhtml @@ -0,0 +1,32 @@ +<% target = 'nic' unless target + network_id = 'physical_network_id' if target == 'nic' + network_id = 'vlan_id' if target == 'bonding' + + %> + +
        Network:
        +
        + <%= select_with_label "", target, network_id, + @networks.collect { |n| [n.name + ' - ' + n.boot_type.label, n.id ] } %> + +
        + + diff --git a/src/app/views/network/edit.rhtml b/src/app/views/network/edit.rhtml new file mode 100644 index 0000000..6360b3f --- /dev/null +++ b/src/app/views/network/edit.rhtml @@ -0,0 +1,34 @@ +<%- content_for :title do -%> + Edit Network +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + + +
        +
        + <%= hidden_field_tag 'id', @network.id %> + <%= render :partial => 'form', :locals => { :create => false } %> +
        + + <%= popup_footer("$('#network_form').submit()", "Edit Network") %> +
        + + + + diff --git a/src/app/views/network/edit_bonding.rhtml b/src/app/views/network/edit_bonding.rhtml new file mode 100644 index 0000000..645e469 --- /dev/null +++ b/src/app/views/network/edit_bonding.rhtml @@ -0,0 +1,52 @@ +
        +
        + Editing Bonded Interface +
        + + <%= hidden_field_tag('id', @bonding.id) %> + <%= render :partial => 'bonding_form' %> + +
        + +
        + <%= hidden_field_tag('id', @bonding.id) %> +
        + +<%= multi_button_popup_footer({"Edit Bonding" => + "$('#edit_bonding_form').submit()", + "Delete Bonding" => + "$('#delete_bonding_form').submit()"}) %> + + diff --git a/src/app/views/network/edit_ip_address.rhtml b/src/app/views/network/edit_ip_address.rhtml new file mode 100644 index 0000000..5a3cc18 --- /dev/null +++ b/src/app/views/network/edit_ip_address.rhtml @@ -0,0 +1,66 @@ +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + Editing IP Address +
        + + <% if @parent_type != 'network' %> + Delete + <% end %> + + <%= hidden_field_tag('id', @ip_address.id) %> + <%= render :partial => 'ip_address_form' %> + +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + <%= hidden_field_tag('id', @ip_address.id) %> +
        + +<% if @parent_type == 'network' %> +<%= multi_button_popup_footer({" Edit IP Address" => + "$('#edit_ip_address_form').submit()", + "Delete IP Address" => + "$('#delete_ip_address_form').submit()"}) %> +<% end %> + + diff --git a/src/app/views/network/edit_network_ip_addresses.rhtml b/src/app/views/network/edit_network_ip_addresses.rhtml new file mode 100644 index 0000000..7a1e4cb --- /dev/null +++ b/src/app/views/network/edit_network_ip_addresses.rhtml @@ -0,0 +1,13 @@ +<%- content_for :title do -%> + Edit Network IP Addresses +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + +<%= render :partial => 'ip_addresses_form', + :locals => { :parent_type => 'network', + :parent_id => @network.id } %> + + diff --git a/src/app/views/network/edit_nic.rhtml b/src/app/views/network/edit_nic.rhtml new file mode 100644 index 0000000..75af6fb --- /dev/null +++ b/src/app/views/network/edit_nic.rhtml @@ -0,0 +1,62 @@ +
        +
        + Editing NIC +
        + + <%= error_messages_for 'nic' %> + +
        + <%= hidden_field_tag 'id', @nic.id %> + <%= hidden_field_tag 'nic_host_id', @nic.host.id %> + <%= hidden_field_tag 'nic_network_id', @nic.physical_network.id %> + +
        MAC:
        +
        <%= @nic.mac %>
        + + <% if @nic.host.bondings.size != 0 %> +
        Bonded Interfaces
        +
        + +
        + <% end %> + + + <%= render :partial => 'select' %> + +
        + <%= render :partial => 'ip_addresses_form', + :locals => { :parent_type => 'nic', + :parent_id => @nic.id } %> +
        + + +
        + <%= popup_footer("$('#nic_form').submit()", "Edit NIC") %> +
        + + diff --git a/src/app/views/network/list.html.erb b/src/app/views/network/list.html.erb new file mode 100644 index 0000000..9a304cf --- /dev/null +++ b/src/app/views/network/list.html.erb @@ -0,0 +1,72 @@ + + + + +
        +<% if @networks.size != 0 %> +
        + <%= render :partial => "grid", :locals => { :table_id => "networks_grid", + :networks => @networks, + :on_select => "networks_select"} %> +
        + +
        +
        +
        Select a network.
        +
        +
        +<% else %> +
        +
        + <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> + +
        + No networks found.

        + <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   + Add first network +
        +
        +
        +<% end %> diff --git a/src/app/views/network/new.rhtml b/src/app/views/network/new.rhtml new file mode 100644 index 0000000..15b7304 --- /dev/null +++ b/src/app/views/network/new.rhtml @@ -0,0 +1,28 @@ +<%- content_for :title do -%> + Add Network +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + + +
        +
        + <%= render :partial => 'form', :locals => { :create => true } %> +
        + + <%= popup_footer("$('#network_form').submit()", "Add Network") %> +
        + + + diff --git a/src/app/views/network/new_bonding.rhtml b/src/app/views/network/new_bonding.rhtml new file mode 100644 index 0000000..9ee6994 --- /dev/null +++ b/src/app/views/network/new_bonding.rhtml @@ -0,0 +1,32 @@ +
        + +
        + Create Bonded Interface +
        + + <%= render :partial => 'bonding_form' %> +
        + +<%= multi_button_popup_footer({"Create Bonding" => + "$('#bonding_form').submit()"}) %> + + diff --git a/src/app/views/network/new_ip_address.rhtml b/src/app/views/network/new_ip_address.rhtml new file mode 100644 index 0000000..6167a76 --- /dev/null +++ b/src/app/views/network/new_ip_address.rhtml @@ -0,0 +1,33 @@ +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + Create Ip Address +
        + + <%= render :partial => 'ip_address_form' %> + +<% if @parent_type == 'network' %> +
        +<% end %> + +<% if @parent_type == 'network' %> +<%= multi_button_popup_footer({"Create IP Address" => + "$('#ip_address_form').submit()"}) %> +<% end %> + + diff --git a/src/app/views/network/show.rhtml b/src/app/views/network/show.rhtml new file mode 100644 index 0000000..a56c41d --- /dev/null +++ b/src/app/views/network/show.rhtml @@ -0,0 +1,30 @@ +<%- content_for :title do -%> + <%=h @network.name %> +<%- end -%> + +<%- content_for :action_links do -%> + <%= link_to image_tag("icon_edit.png") + "Edit", + {:action => 'edit', :id => @network.id }, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> + + <%= link_to image_tag("icon_edit.png") + "Edit IP Addresses", + {:action => 'edit_network_ip_addresses', :id => @network.id }, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> +<%- end -%> + + +
        + Name:
        + Type:
        + Boot Type:
        + IP Addresses:
        +
        +
        + <%=h @network.name %>
        + <%=h @network.type %>
        + <%=h @network.boot_type.label %>
        + <%=h @network.ip_addresses.size %>
        +
        + +<%- content_for :right do -%> +<%- end -%> diff --git a/src/app/views/nic/_list.rhtml b/src/app/views/nic/_list.rhtml index 07f7e44..eb3a4a7 100644 --- a/src/app/views/nic/_list.rhtml +++ b/src/app/views/nic/_list.rhtml @@ -1,7 +1,6 @@ - @@ -11,7 +10,6 @@ <% for nic in nics %> - diff --git a/src/db/migrate/028_refactor_networking_model.rb b/src/db/migrate/028_refactor_networking_model.rb new file mode 100644 index 0000000..946b976 --- /dev/null +++ b/src/db/migrate/028_refactor_networking_model.rb @@ -0,0 +1,271 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# introduce networks and ip_addresses tables, refactor relationships +class RefactorNetworkingModel < ActiveRecord::Migration + def self.up + + #################################################### + # bugfix, bridge tables shouldn't have their own ids + remove_column :bondings_nics, :id + + ################################################################## + # add networks, usages tables and networks_usage_types bridge + create_table :networks do |t| + t.string :type, :null => false + t.string :name, :null => false + t.integer :boot_type_id, :null => false + + # attributes for Vlan (type=Vlan) + t.integer :number + end + + create_table :usages do |t| + t.string :label, :null => false + t.string :usage, :null => false + end + + create_table :networks_usages, :id => false do |t| + t.integer :network_id, :null => false + t.integer :usage_id, :null => false + end + + add_index :networks_usages, [:network_id, :usage_id], :unique => true + + # create usages + Usage.create(:label => 'Guest', :usage => 'guest') + Usage.create(:label => 'Management', :usage => 'management') + Usage.create(:label => 'Storage', :usage => 'storage') + + # referential integrity for networks tables + execute "alter table networks add constraint + fk_network_boot_types + foreign key (boot_type_id) references + boot_types(id)" + execute "alter table networks_usages add constraint + fk_networks_usages_network_id + foreign key (network_id) references + networks(id)" + execute "alter table networks_usages add constraint + fk_networks_usages_usage_id + foreign key (usage_id) references + usages(id)" + + # add foreign keys to nics / bondings table + add_column :nics, :physical_network_id, :integer + add_column :bondings, :vlan_id, :integer + + # referential integrity for nic/bondings network ids + execute "alter table nics add constraint + fk_nic_networks + foreign key (physical_network_id) references + networks(id)" + execute "alter table bondings add constraint + fk_bonding_networks + foreign key (vlan_id) references + networks(id)" + + #################################################### + # create ip_addresses table + create_table :ip_addresses do |t| + t.string :type + + # foreign keys to associated entities + t.integer :nic_id + t.integer :bonding_id + t.integer :network_id + + # common attributes + t.string :address, :limit => 39, :null => false + t.string :gateway, :limit => 39 + + # attributes for IPv4 (type=IpV4Address) + t.string :netmask, :limit => 15 + t.string :broadcast, :limit => 15 + + # attributes for IPv6 (type=IpV6Address) + t.string :prefix, :limit => 39 + t.timestamps + end + + # referential integrity for ip_addresses table + execute "alter table ip_addresses add constraint + fk_nic_ip_addresses + foreign key (nic_id) references nics(id)" + execute "alter table ip_addresses add constraint + fk_bonding_ip_addresses + foreign key (bonding_id) references bondings(id)" + execute "alter table ip_addresses add constraint + fk_network_ip_addresses + foreign key (network_id) references networks(id)" + + ################################################################### + static_boot_type_id = + BootType.find(:first, + :conditions => {:proto => 'static'} ).id + + # migrate nic ip_addresses to networks / ip_addresses table + i = 0 + Nic.find(:all).each do |nic| + if nic.boot_type_id == static_boot_type_id + IpV4Address.new(:nic_id => nic.id, + :address => nic.ip_addr).save! + + end + network = PhysicalNetwork.new( + :name => 'Physical Network ' + i.to_s, + :boot_type_id => nic.boot_type_id) + network.save! + + ip_address = IpV4Address.new(:address => nic.ip_addr ? nic.ip_addr : '0.0.0.0', + :netmask => nic.netmask, + :broadcast => nic.broadcast, + :gateway => nic.ip_addr) + ip_address.network = network + ip_address.save! + + nic.physical_network = network + nic.save! + + i += 1 + end + + # migrate bonding ip_addresses to networks / ip_addresses table + i = 0 + Bonding.find(:all).each do |bonding| + if bonding.boot_type_id == static_boot_type_id + IpV4Address.new(:bonding_id => bonding.id, + :address => bonding.ip_addr).save! + end + network = Vlan.new( + :name => 'VLAN ' + i.to_s, + :number => i, + :boot_type_id => bonding.boot_type_id) + network.save! + + ip_address = IpV4Address.new(:address => bonding.ip_addr ? bonding.ip_addr : '0.0.0.0', + :netmask => bonding.netmask, + :broadcast => bonding.broadcast, + :gateway => bonding.ip_addr) + ip_address.network = network + ip_address.save! + + bonding.vlan = network + bonding.save! + + i += 1 + end + + ############################################################## + # remove nics / bonding ip address and network related columns + remove_column :nics, :ip_addr + remove_column :nics, :netmask + remove_column :nics, :broadcast + remove_column :nics, :boot_type_id + remove_column :bondings, :ip_addr + remove_column :bondings, :netmask + remove_column :bondings, :broadcast + remove_column :bondings, :boot_type_id + + + end + + def self.down + ############################################################## + # readd nics / bonding ip address related columns + add_column :nics, :ip_addr, :string, :limit => 16 + add_column :nics, :netmask, :string, :limit => 16 + add_column :nics, :broadcast, :string, :limit => 16 + add_column :nics, :boot_type_id, :integer + add_column :bondings, :ip_addr, :string, :limit => 16 + add_column :bondings, :netmask, :string, :limit => 16 + add_column :bondings, :broadcast, :string, :limit => 16 + add_column :bondings, :boot_type_id, :integer + + execute "alter table nics add constraint + fk_nic_boot_types + foreign key (boot_type_id) references + boot_types(id)" + execute "alter table bondings add constraint + fk_bonding_boot_types + foreign key (boot_type_id) references + boot_types(id)" + + ############################################################## + # attempt to migrate ip information back into nics table. + # because a nic can have multiple ips (if statically + # assigned) as well as its network, just use the 1st + # found + Nic.find(:all).each do |nic| + if nic.physical_network.ip_addresses.size > 0 + # use the 1st configured network ip + nic.ip_addr = nic.physical_network.ip_addresses[0].address + nic.netmask = nic.physical_network.ip_addresses[0].netmask + nic.broadcast = nic.physical_network.ip_addresses[0].broadcast + end + + if nic.ip_addresses.size > 0 + # use the 1st assigned static ip + nic.ip_addr = nic.ip_addresses[0].address + end + + nic.boot_type_id = nic.physical_network.boot_type_id + + nic.save! + end + + # attempt to migrate ip information back into bondings table. + # because a bonding can have multiple ips (if statically + # assigned) as well as its network, just use the 1st + # found + Bonding.find(:all).each do |bonding| + if bonding.vlan.ip_addresses.size > 0 + # use the 1st configured network ip + bonding.ip_addr = bonding.vlan.ip_addresses[0].address + bonding.netmask = bonding.vlan.ip_addresses[0].netmask + bonding.broadcast = bonding.vlan.ip_addresses[0].broadcast + end + + if bonding.ip_addresses.size > 0 + # use the 1st assigned static ip + bonding.ip_addr = bonding.ip_addresses[0].address + end + + bonding.boot_type_id = bonding.vlan.boot_type_id + + bonding.save! + end + + ############################################################## + # drop ip_addresses table + drop_table :ip_addresses + + # drop network ids from nics / bondings table + remove_column :nics, :physical_network_id + remove_column :bondings, :vlan_id + + # drop networks tables + drop_table :networks_usages + drop_table :usages + drop_table :networks + + ############################################################## + # undo bugfix above + add_column :bondings_nics, :id, :integer + end +end diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js index 4579c80..818b862 100644 --- a/src/public/javascripts/ovirt.js +++ b/src/public/javascripts/ovirt.js @@ -297,4 +297,31 @@ function delete_pool(delete_url, id) $.jGrowl(data.alert); } }, 'json'); -} \ No newline at end of file +} + + +function get_selected_networks() +{ + return get_selected_checkboxes("networks_grid_form"); +} + +function afterNetwork(response, status){ + ajax_validation(response, status); + if (response.success) { + $(document).trigger('close.facebox'); + grid = $("#networks_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); + } else { + $tabs.tabs("load",$tabs.data('selected.tabs')); + } + } +} + +function afterIpAddress(response, status){ + ajax_validation(response, status); + if (response.success) { + reset_selected_ip_address(); + reset_ip_address_select(); + } +} diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 16eaf62..228ff7b 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -267,4 +267,52 @@ .detail-pane-chart { height: 50px; width: 375px; -}; +} + + +/************************* + * new popup components + *************************/ +.popup-content-selection { + float: left; + width: 45%; + padding-left: 20px; + padding-top: 10px; +} + +.popup-content-selection select{ + min-width: 200px; +} + +.popup-content-footer{ + width: 99%; + float: left; +} + +.selected_popup_content { + padding-left: 20px; + min-height: 50px; + float: left; + width: 96%; +} + +#selected_popup_content_header { + padding-bottom: 5px; + font-weight: bold; +} + +#selected_popup_content_expanded{ + padding-bottom: 50px; +} + +.selected_popup_content_left { + float: left; + width: 40%; + padding-bottom: 15px; +} + +.selected_popup_content_right { + float: left; + width: 40%; + padding-bottom: 15px; +} -- 1.5.6.5 From dpierce at redhat.com Fri Oct 31 14:06:56 2008 From: dpierce at redhat.com (Darryl Pierce) Date: Fri, 31 Oct 2008 10:06:56 -0400 Subject: [Ovirt-devel] [PATCH server] network integration into ovirt server db and wui In-Reply-To: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> References: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> Message-ID: <490B1100.5020401@redhat.com> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 NAK. Some of the first things I've noticed is that, by removing columns from the bondings and nics tables into the separate IP-related tables, updates need to be made to the ManagedNodeConfiguration class. Since the data itself is the same and just relocated, refactoring the configuration class to get data from the appropriate objects should take care of things initially. Before this patch can be pushed, it needs to fix that class. The unit test itself does not need changing since the data ought to still look the same once encoded. There may be changes needed for vlans, but that will be additional tests. Comments are inline. Mohammed Morsi wrote: > --- > src/app/controllers/host_controller.rb | 12 +- > src/app/controllers/network_controller.rb | 427 ++++++++++++++++++++ > src/app/helpers/application_helper.rb | 28 +- > src/app/helpers/network_helper.rb | 2 + > src/app/models/bonding.rb | 27 +- > src/app/models/ip_address.rb | 27 ++ > src/app/models/ip_v4_address.rb | 48 +++ > src/app/models/ip_v6_address.rb | 44 ++ > src/app/models/network.rb | 37 ++ > src/app/models/nic.rb | 16 +- > src/app/models/physical_network.rb | 27 ++ > src/app/models/usage.rb | 21 + > src/app/models/vlan.rb | 30 ++ > src/app/views/dashboard/index.html.erb | 7 + > src/app/views/host/edit_network.rhtml | 85 ++++ > src/app/views/host/show.rhtml | 4 + > src/app/views/network/_bonding_form.rhtml | 46 +++ > src/app/views/network/_form.rhtml | 31 ++ > src/app/views/network/_grid.rhtml | 37 ++ > src/app/views/network/_ip_address_form.rhtml | 62 +++ > src/app/views/network/_ip_addresses_form.rhtml | 50 +++ > src/app/views/network/_select.rhtml | 32 ++ > src/app/views/network/edit.rhtml | 34 ++ > src/app/views/network/edit_bonding.rhtml | 52 +++ > src/app/views/network/edit_ip_address.rhtml | 66 +++ > .../views/network/edit_network_ip_addresses.rhtml | 13 + > src/app/views/network/edit_nic.rhtml | 62 +++ > src/app/views/network/list.html.erb | 72 ++++ > src/app/views/network/new.rhtml | 28 ++ > src/app/views/network/new_bonding.rhtml | 32 ++ > src/app/views/network/new_ip_address.rhtml | 33 ++ > src/app/views/network/show.rhtml | 30 ++ > src/app/views/nic/_list.rhtml | 2 - > src/db/migrate/028_refactor_networking_model.rb | 271 +++++++++++++ > src/public/javascripts/ovirt.js | 29 ++- > src/public/stylesheets/components.css | 50 +++- > 36 files changed, 1849 insertions(+), 25 deletions(-) > create mode 100644 src/app/controllers/network_controller.rb > create mode 100644 src/app/helpers/network_helper.rb > create mode 100644 src/app/models/ip_address.rb > create mode 100644 src/app/models/ip_v4_address.rb > create mode 100644 src/app/models/ip_v6_address.rb > create mode 100644 src/app/models/network.rb > create mode 100644 src/app/models/physical_network.rb > create mode 100644 src/app/models/usage.rb > create mode 100644 src/app/models/vlan.rb > create mode 100644 src/app/views/host/edit_network.rhtml > create mode 100644 src/app/views/network/_bonding_form.rhtml > create mode 100644 src/app/views/network/_form.rhtml > create mode 100644 src/app/views/network/_grid.rhtml > create mode 100644 src/app/views/network/_ip_address_form.rhtml > create mode 100644 src/app/views/network/_ip_addresses_form.rhtml > create mode 100644 src/app/views/network/_select.rhtml > create mode 100644 src/app/views/network/edit.rhtml > create mode 100644 src/app/views/network/edit_bonding.rhtml > create mode 100644 src/app/views/network/edit_ip_address.rhtml > create mode 100644 src/app/views/network/edit_network_ip_addresses.rhtml > create mode 100644 src/app/views/network/edit_nic.rhtml > create mode 100644 src/app/views/network/list.html.erb > create mode 100644 src/app/views/network/new.rhtml > create mode 100644 src/app/views/network/new_bonding.rhtml > create mode 100644 src/app/views/network/new_ip_address.rhtml > create mode 100644 src/app/views/network/show.rhtml > create mode 100644 src/db/migrate/028_refactor_networking_model.rb > > diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb > index a40d297..120fe2a 100644 > --- a/src/app/controllers/host_controller.rb > +++ b/src/app/controllers/host_controller.rb > @@ -30,7 +30,7 @@ class HostController < ApplicationController > end > end > > - before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms] > + before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms, :edit_network] > > # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) > verify :method => [:post, :put], :only => [ :create, :update ], > @@ -156,6 +156,16 @@ class HostController < ApplicationController > render :json => @json_hash > end > > + def edit_network > + render :layout => 'popup' > + end > + > + def bondings_json > + bondings = Host.find(params[:id]).bondings > + bondings_json = [] > + bondings.each{ |x| bondings_json.push({:id => x.id, :name => x.name}) } > + render :json => bondings_json > + end > > private > #filter methods > diff --git a/src/app/controllers/network_controller.rb b/src/app/controllers/network_controller.rb > new file mode 100644 > index 0000000..b7f8b6d > --- /dev/null > +++ b/src/app/controllers/network_controller.rb > @@ -0,0 +1,427 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > +# > + > +class NetworkController < ApplicationController > + ########################## Networks related actions > + > + def network_permissions > + # TODO more robust permission system > + # either by subclassing network from pool > + # or by extending permission model to accomodate > + # any object > + @default_pool = HardwarePool.get_default_pool > + set_perms(@default_pool) > + unless @can_modify > + flash[:notice] = 'You do not have permission to view networks' > + redirect_to :controller => 'dashboard' > + end > + end > + > + def list > + @networks = Network.find(:all) > + network_permissions > + end > + > + def networks_json > + json_list(Network.find(:all), [:id, :name, :type, [:boot_type, :label]]) > + end > + > + def show > + @network = Network.find(params[:id]) > + network_permissions > + respond_to do |format| > + format.html { render :layout => 'selection' } > + format.xml { render :xml => @network.to_xml } > + end > + end > + > + def new > + @boot_types = BootType.find(:all) > + render :layout => 'popup' > + end > + > + def create > + begin > + @network = PhysicalNetwork.new(params[:network]) if params[:network][:type] == 'PhysicalNetwork' > + @network = Vlan.new(params[:network]) if params[:network][:type] == 'Vlan' > + @network.save! > + alert = "Network was successfully created." > + render :json => { :object => "network", :success => true, > + :alert => alert } > + rescue > + render :json => { :object => "network", :success => false, > + :errors => > + @network.errors.localize_error_messages.to_a } > + end > + end > + > + def edit > + @network = Network.find(params[:id]) > + @boot_types = BootType.find(:all) > + render :layout => 'popup' > + end > + > + def update > + begin > + @network = Network.find(params[:id]) > + > + # special case if we are switching types > + if @network.type != params[:network][:type] > + if ! @network.conversion_valid? > + render :json => { :object => "network", :success => false, > + :alert => > + 'Can not change type of network with associated nics/bondings'} > + return > + end > + > + if params[:network][:type] == 'PhysicalNetwork' > + Vlan.destroy(params[:id]) > + @network = PhysicalNetwork.create(params[:network]) > + else > + PhysicalNetwork.destroy(params[:id]) > + @network = Vlan.create(params[:network]) > + end > + else > + @network = PhysicalNetwork.find(params[:id]) if @network.type == 'PhysicalNetwork' > + @network = Vlan.find(params[:id]) if @network.type == 'Vlan' > + @network.update_attributes!(params[:network]) > + end > + > + alert = "Network was successfully updated." > + render :json => { :object => "network", :success => true, > + :alert => alert } > + rescue Exception => e > + render :json => { :object => "network", :success => false, > + :errors => > + @network.errors.localize_error_messages.to_a } > + end > + end > + > + def delete > + failed_networks = [] > + networks_ids_str = params[:network_ids] > + network_ids = networks_ids_str.split(",").collect {|x| x.to_i} > + network_ids.each{ |x| > + network = Network.find(x) > + if network.conversion_valid? > + begin > + Network.destroy(x) > + rescue > + failed_networks.push x > + end > + else > + failed_networks.push x > + end > + } > + if failed_networks.size == 0 > + render :json => { :object => "network", > + :success => true, > + :alert => "Successfully deleted networks" } > + else > + render :json => { :object => "network", > + :success => false, > + :alert => "Failed deleting " + > + failed_networks.size.to_s + > + " networks due to existing bondings/nics" } > + end > + end > + > + def edit_network_ip_addresses > + @network = Network.find(params[:id]) > + render :layout => 'popup' > + end > + > + > + ########################## Ip Address related actions > + > + def ip_addresses_json > + @parent_type = params[:parent_type] > + if @parent_type == 'network' > + ip_addresses = Network.find(params[:id]).ip_addresses > + elsif @parent_type == 'nic' > + ip_addresses = Nic.find(params[:id]).ip_addresses > + elsif @parent_type == 'bonding' and params[:id] > + ip_addresses = Bonding.find(params[:id]).ip_addresses > + else > + ip_addresses = [] > + end > + > + ip_addresses_json = [] > + ip_addresses.each{ |x| > + ip_addresses_json.push({:id => x.id, :name => x.address}) } > + render :json => ip_addresses_json > + end > + > + def new_ip_address > + @parent_type = params[:parent_type] > + @network = Network.find(params[:id]) if @parent_type == 'network' > + @nic = Nic.find(params[:id]) if @parent_type == 'nic' > + @bonding = Bonding.find(params[:id]) if @parent_type == 'bonding' and params[:id] > + > + render :layout => false > + end > + > + def _create_ip_address > + if params[:ip_address][:type] == "IpV4Address" > + @ip_address = IpV4Address.new(params[:ip_address]) > + else > + @ip_address = IpV6Address.new(params[:ip_address]) > + end > + @ip_address.save! > + end > + > + def create_ip_address > + begin > + _create_ip_address > + alert = "Ip Address was successfully created." > + render :json => { :object => "ip_address", :success => true, > + :alert => alert } > + rescue > + render :json => { :object => "ip_address", :success => false, > + :errors => > + @ip_address.errors.localize_error_messages.to_a } > + end > + end > + > + def edit_ip_address > + @ip_address = IpAddress.find(params[:id]) > + > + @parent_type = params[:parent_type] > + @network = @ip_address.network if @ip_address.network_id > + @nic = @ip_address.nic if @ip_address.nic_id > + @bonding = @ip_address.bonding if @ip_address.bonding_id > + > + render :layout => false > + end > + > + def _update_ip_address(id) > + @ip_address = IpAddress.find(id) > + > + # special case if we are switching types > + if @ip_address.type != params[:ip_address][:type] > + if params[:ip_address][:type] == 'IpV4Address' > + @ip_address = IpV4Address.new(params[:ip_address]) > + @ip_address.save! > + IpV6Address.delete(id) > + else > + @ip_address = IpV6Address.new(params[:ip_address]) > + @ip_address.save! > + IpV4Address.delete(id) > + end > + else > + if @ip_address.type == 'IpV4Address' > + @ip_address = IpV4Address.find(id) > + else > + @ip_address = IpV6Address.find(id) > + end > + @ip_address.update_attributes!(params[:ip_address]) > + end > + > + end > + > + def update_ip_address > + begin > + _update_ip_address(params[:id]) > + alert = "IpAddress was successfully updated." > + render :json => { :object => "network", :success => true, > + :alert => alert } > + rescue > + render :json => { :object => "ip_address", :success => false, > + :errors => > + @ip_address.errors.localize_error_messages.to_a } > + end > + end > + > + def destroy_ip_address > + begin > + IpAddress.delete(params[:id]) > + alert = "Ip Address was successfully deleted." > + render :json => { :object => "ip_address", :success => true, > + :alert => alert } > + rescue > + render :json => { :object => "ip_address", :success => false, > + :alert => 'Ip Address Deletion Failed' } > + end > + end > + > + > + ########################## NICs related actions > + > + def edit_nic > + @nic = Nic.find(params[:id]) > + @network = @nic.physical_network > + > + @networks = PhysicalNetwork.find(:all) > + network_options > + > + render :layout => false > + end > + > + def update_nic > + begin > + network_options > + @network = Network.find(params[:nic][:physical_network_id]) > + > + if @network.boot_type.id == @static_boot_type.id > + if params[:ip_address][:id] == "New" > + _create_ip_address > + elsif params[:ip_address][:id] != "" > + _update_ip_address(params[:ip_address][:id]) > + end > + end > + > + @nic = Nic.find(params[:id]) > + @nic.update_attributes!(params[:nic]) > + > + alert = "Nic was successfully updated." > + render :json => { :object => "nic", :success => true, > + :alert => alert } > + rescue Exception => e > + if @ip_address and @ip_address.errors.size != 0 > + render :json => { :object => "ip_address", :success => false, > + :errors => > + @ip_address.errors.localize_error_messages.to_a} > + else > + render :json => { :object => "nic", :success => false, > + :errors => > + @nic.errors.localize_error_messages.to_a } > + end > + end > + end > + > + ########################## Bonding related actions > + > + def new_bonding > + unless params[:host_id] > + flash[:notice] = "Host is required." > + redirect_to :controller => 'dashboard' > + end > + > + @host = Host.find(params[:host_id]) > + @networks = Vlan.find(:all) > + network_options > + > + render :layout => false > + end > + > + def create_bonding > + begin > + network_options > + @network = Network.find(params[:bonding][:vlan_id]) > + > + if @network.boot_type.id == @static_boot_type.id > + if params[:ip_address][:id] == "New" > + _create_ip_address > + elsif params[:ip_address][:id] != "" > + _update_ip_address(params[:ip_address][:id]) > + end > + end > + > + @bonding = Bonding.new(params[:bonding]) > + @bonding.save! > + > + if @ip_address > + @ip_address.bonding_id = @bonding.id > + @ip_address.save! > + end > + > + alert = "Bonding was successfully created." > + render :json => { :object => "bonding", :success => true, > + :alert => alert } > + rescue > + if @ip_address and @ip_address.errors.size != 0 > + render :json => { :object => "ip_address", :success => false, > + :errors => > + @ip_address.errors.localize_error_messages.to_a} > + else > + render :json => { :object => "bonding", :success => false, > + :errors => > + @bonding.errors.localize_error_messages.to_a } > + end > + end > + end > + > + def edit_bonding > + @bonding = Bonding.find(params[:id]) > + @network = @bonding.vlan > + > + @host = @bonding.host > + @networks = Vlan.find(:all) > + network_options > + > + render :layout => false > + end > + > + def update_bonding > + begin > + network_options > + @network = Network.find(params[:bonding][:vlan_id]) > + > + if @network.boot_type.id == @static_boot_type.id > + if params[:ip_address][:id] == "New" > + _create_ip_address > + elsif params[:ip_address][:id] != "" > + _update_ip_address(params[:ip_address][:id]) > + end > + end > + > + @bonding = Bonding.find(params[:id]) > + @bonding.nics.each { |nic| @bonding.nics.delete(nic) } > + @bonding.update_attributes!(params[:bonding]) > + > + alert = "Bonding was successfully updated." > + render :json => { :object => "bonding", :success => true, > + :alert => alert } > + rescue > + if @ip_address and @ip_address.errors.size != 0 > + render :json => { :object => "ip_address", :success => false, > + :errors => > + @ip_address.errors.localize_error_messages.to_a} > + else > + render :json => { :object => "bonding", :success => false, > + :errors => > + @bonding.errors.localize_error_messages.to_a } > + end > + end > + end > + > + def destroy_bonding > + begin > + Bonding.destroy(params[:id]) > + alert = "Bonding was successfully deleted." > + render :json => { :object => "bonding", :success => true, > + :alert => alert } > + rescue > + render :json => { :object => "bonding", :success => false, > + :alert => 'Bonding Deletion Failed' } > + end > + > + end > + > + > + ########################## Misc methods > + > + protected > + def network_options > + @bonding_types = BondingType.find(:all) > + @static_boot_type = BootType.find(:first, > + :conditions => { :proto => 'static' }) > + end > + > +end > diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb > index d7b6628..0178ad0 100644 > --- a/src/app/helpers/application_helper.rb > +++ b/src/app/helpers/application_helper.rb > @@ -84,21 +84,31 @@ module ApplicationHelper > } > end > > - def popup_footer(action, label) > - %{ > -
        > + # expects hash of labels => actions > + def multi_button_popup_footer(buttons = {}) > + buttons_html = "" > + buttons.each{ |label, action| > + buttons_html+="
        " + > + "
        " + > + " " + > + "
        " + > + "
        " > + } > + > + %{ > +
        >
        >
        > >
        >
        > -
        > -
        > - > -
        > -
        > + #{buttons_html} >
        > - } > + } > + end > + > + def popup_footer(action, label) > + multi_button_popup_footer({label => action}) > end > > def ok_footer > diff --git a/src/app/helpers/network_helper.rb b/src/app/helpers/network_helper.rb > new file mode 100644 > index 0000000..ebbce0b > --- /dev/null > +++ b/src/app/helpers/network_helper.rb > @@ -0,0 +1,2 @@ > +module NetworkHelper > +end > diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb > index 006c261..a3ad150 100644 > --- a/src/app/models/bonding.rb > +++ b/src/app/models/bonding.rb > @@ -30,6 +30,16 @@ > # interface. They can be ignored if not used. > # > class Bonding < ActiveRecord::Base > + belongs_to :host > + belongs_to :bonding_type > + belongs_to :vlan > + has_many :ip_addresses, :dependent => :destroy > + > + has_and_belongs_to_many :nics, > + :join_table => 'bondings_nics', > + :foreign_key => :bonding_id > + > + > validates_presence_of :name, > :message => 'A name is required.' > > @@ -42,18 +52,17 @@ class Bonding < ActiveRecord::Base > validates_presence_of :interface_name, > :message => 'An interface name is required.' > > - validates_presence_of :boot_type_id, > - :message => 'A boot type must be specified.' > - > validates_presence_of :bonding_type_id, > :message => 'A bonding type must be specified.' > > - belongs_to :host > - belongs_to :bonding_type > - belongs_to :boot_type > + protected > + def validate > + errors.add("name", "must be specified") unless name > + errors.add("interface_name", "must be specified") unless interface_name > + errors.add("bonding_type_id", "must be specified") unless bonding_type_id This is duplicating the above validation with the validates_presence_of check. > + errors.add("host_id", "must be specified") unless host_id > + errors.add("vlan_id", "must be specified") unless vlan_id > + end > > - has_and_belongs_to_many :nics, > - :join_table => 'bondings_nics', > - :foreign_key => :bonding_id > > end > diff --git a/src/app/models/ip_address.rb b/src/app/models/ip_address.rb > new file mode 100644 > index 0000000..5d2e6af > --- /dev/null > +++ b/src/app/models/ip_address.rb > @@ -0,0 +1,27 @@ > +# ip_address.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. > + > +# +IpAddress+ is the base class for all address related classes. > +# > +class IpAddress < ActiveRecord::Base > + # one of these 3 will apply for each address > + belongs_to :network > + belongs_to :nic > + belongs_to :bonding > +end > diff --git a/src/app/models/ip_v4_address.rb b/src/app/models/ip_v4_address.rb > new file mode 100644 > index 0000000..40e8cf9 > --- /dev/null > +++ b/src/app/models/ip_v4_address.rb > @@ -0,0 +1,48 @@ > +# ip_v4_address.rb > +# > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Darryl L. Pierce , > +# Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +# +IpV4Address+ represents a single IPv4 address. > +# > +class IpV4Address < IpAddress > + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} > + > + validates_presence_of :address, > + :message => 'An address must be supplied.' > + validates_format_of :address, > + :with => ADDRESS_TEST > + > + protected > + def validate > + unless address and address =~ ADDRESS_TEST > + errors.add("address", "is of incorrect format") > + end > + unless !netmask or netmask == "" or netmask =~ ADDRESS_TEST > + errors.add("netmask", "is of incorrect format") > + end > + unless !gateway or gateway == "" or gateway =~ ADDRESS_TEST > + errors.add("gateway", "is of incorrect format") > + end > + unless !broadcast or broadcast == "" or broadcast =~ ADDRESS_TEST > + errors.add("broadcast", "is of incorrect format") > + end In the original patch I submitted each of these fields was tested using validates_format_of. > + end > + > +end > diff --git a/src/app/models/ip_v6_address.rb b/src/app/models/ip_v6_address.rb > new file mode 100644 > index 0000000..9720179 > --- /dev/null > +++ b/src/app/models/ip_v6_address.rb > @@ -0,0 +1,44 @@ > +# ip_v6_address.rb > +# > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Darryl L. Pierce , > +# Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +# +IpV6Address+ represents a single IPv6 address. > +# > +class IpV6Address < IpAddress > + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} > + > + validates_presence_of :address, > + :message => 'An address must be provided.' > + validates_format_of :address, > + :with => ADDRESS_TEST > + > + protected > + def validate > + unless address =~ ADDRESS_TEST > + errors.add("address", "is of incorrect format") > + end > + unless !prefix or prefix == "" or prefix =~ ADDRESS_TEST > + errors.add("prefix", "is of incorrect format") > + end > + unless !gateway or gateway == "" or gateway =~ ADDRESS_TEST > + errors.add("gateway", "is of incorrect format") > + end See note in the IpV4Address regarding validations. The Rails pattern is to use validates_format_of for checking content. > + end > +end > diff --git a/src/app/models/network.rb b/src/app/models/network.rb > new file mode 100644 > index 0000000..99e1a94 > --- /dev/null > +++ b/src/app/models/network.rb > @@ -0,0 +1,37 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +class Network < ActiveRecord::Base > + belongs_to :boot_type > + has_many :ip_addresses, :dependent => :destroy > + > + has_and_belongs_to_many :usages, :join_table => 'networks_usages' > + > + validates_presence_of :type > + validates_presence_of :name > + validates_presence_of :boot_type_id > + > + def conversion_valid? > + return false if type.to_s == 'Vlan' && > + Vlan.find(id).bondings.size != 0 || > + type.to_s == 'PhysicalNetwork' && > + PhysicalNetwork.find(id).nics.size != 0 > + return true > + end > + > +end > diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb > index baf7095..25d3ac2 100644 > --- a/src/app/models/nic.rb > +++ b/src/app/models/nic.rb > @@ -19,7 +19,19 @@ > > class Nic < ActiveRecord::Base > belongs_to :host > - belongs_to :boot_type > + belongs_to :physical_network > + has_many :ip_addresses, :dependent => :destroy > > - has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' > + has_and_belongs_to_many :bondings, :join_table => 'bondings_nics' > + > + protected > + def validate > + errors.add("host_id", "must be specified") unless host_id > + errors.add("physical_network_id", "must be specified") unless physical_network_id These should be done with validates_presence_of to keep the code consistent throughout the project. The error message added here is the default from Rails, so need not be specified. > + if physical_network.boot_type.id == > + BootType.find(:first, :conditions => { :proto => 'static' }).id and You can test this without the second DB call using: if physical_network.boot_type.proto == static' > + ip_addresses.size == 0 > + errors.add("ip_address_id", "must be specified") > + end > + end > end > diff --git a/src/app/models/physical_network.rb b/src/app/models/physical_network.rb > new file mode 100644 > index 0000000..cba696a > --- /dev/null > +++ b/src/app/models/physical_network.rb > @@ -0,0 +1,27 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +class PhysicalNetwork < Network > + has_many :nics > + > + protected > + def validate > + errors.add("name", "must be specified") unless name > + errors.add("boot_type_id", "must be specified") unless boot_type_id Use validates_presence_of here. > + end > +end > diff --git a/src/app/models/usage.rb b/src/app/models/usage.rb > new file mode 100644 > index 0000000..353e8f4 > --- /dev/null > +++ b/src/app/models/usage.rb > @@ -0,0 +1,21 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +class Usage < ActiveRecord::Base > + has_and_belongs_to_many :networks, :join_table => 'networks_usages' > +end > diff --git a/src/app/models/vlan.rb b/src/app/models/vlan.rb > new file mode 100644 > index 0000000..2b96502 > --- /dev/null > +++ b/src/app/models/vlan.rb > @@ -0,0 +1,30 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +class Vlan < Network > + has_many :bondings > + > + validates_presence_of :number > + > + protected > + def validate > + errors.add("name", "must be specified") unless name > + errors.add("number", "must be specified") unless number > + errors.add("boot_type_id", "must be specified") unless boot_type_id Use validates_presence_of to be consistent. > + end > +end > diff --git a/src/app/views/dashboard/index.html.erb b/src/app/views/dashboard/index.html.erb > index 92cb4da..8815ebc 100644 > --- a/src/app/views/dashboard/index.html.erb > +++ b/src/app/views/dashboard/index.html.erb > @@ -13,6 +13,13 @@ >
        IP MAC Bridge Usage Type
        <%= link_to nic.ip_addr, { :controller => "nic", :action => 'show', :id => nic }, { :class => "show" } %> <%= nic.mac %> <%= nic.bridge %> <%= nic.usage_type %>
        >
        > > + <% if @can_modify %> > +

        Networks

        > + > + View / Edit > + > + <% end %> > + >
        > > > diff --git a/src/app/views/host/edit_network.rhtml b/src/app/views/host/edit_network.rhtml > new file mode 100644 > index 0000000..7ec3180 > --- /dev/null > +++ b/src/app/views/host/edit_network.rhtml > @@ -0,0 +1,85 @@ > +<%- content_for :title do -%> > + Edit <%= @host.hostname %> Network Devices > +<%- end -%> > + > +<%- content_for :description do -%> > + Select and edit nics and bonded interfaces on <%= @host.hostname %> > +<%- end -%> > + > + > + > + > + > +
        > + > +
        > + > + > + > + > diff --git a/src/app/views/host/show.rhtml b/src/app/views/host/show.rhtml > index d1b05ad..bc39a62 100644 > --- a/src/app/views/host/show.rhtml > +++ b/src/app/views/host/show.rhtml > @@ -17,6 +17,10 @@ > <%= image_tag "icon_x.png" %> Clear VMs > > <% end -%> > + <%= link_to image_tag("icon_edit.png") +"Edit Network", > + {:controller => 'host', > + :action => 'edit_network', :id => @host.id}, > + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> > <%- end -%> > <%- end -%> > > diff --git a/src/app/views/network/_grid.rhtml b/src/app/views/network/_grid.rhtml > new file mode 100644 > index 0000000..6af0c05 > --- /dev/null > +++ b/src/app/views/network/_grid.rhtml > @@ -0,0 +1,37 @@ > +<% networks_per_page = 40 unless (defined? networks_per_page) and !(networks_per_page.nil?) %> > +<% usepager = @networks.size > networks_per_page %> > + > +
        > +
        > + > +
        > +
        > + > + > + > diff --git a/src/app/views/network/_ip_address_form.rhtml b/src/app/views/network/_ip_address_form.rhtml > new file mode 100644 > index 0000000..b952807 > --- /dev/null > +++ b/src/app/views/network/_ip_address_form.rhtml > @@ -0,0 +1,62 @@ > +<%= error_messages_for 'ip_address' %> > + > +
        > + <%= hidden_field_tag 'parent_type', @parent_type%> > + <%= hidden_field_tag 'ip_address[id]', @ip_address.id if @ip_address%> > + <%= hidden_field_tag 'ip_address[network_id]', @network.id if @network %> > + <%= hidden_field_tag 'ip_address[nic_id]', @nic.id if @nic %> > + <%= hidden_field_tag 'ip_address[bonding_id]', @bonding.id if @bonding %> > + > +
        Type:
        > +
        > + <%= select_with_label "", "ip_address", "type", > + [[ "ipv4", "IpV4Address" ], [ "ipv6", "IpV6Address" ] ] %> > +
        > + > + > +
        > +
        IP Address
        > +
        > + <%= text_field_with_label "", "ip_address", "address" %> > +
        > +
        > + > +
        > +
        Netmask
        > +
        > + <%= text_field_with_label "", "ip_address", "netmask" %> > +
        > + > +
        Broadcast
        > +
        > + <%= text_field_with_label "", "ip_address", "broadcast" %> > +
        > +
        > + > + > + > +
        > +
        Gateway
        > +
        > + <%= text_field_with_label "", "ip_address", "gateway" %> > +
        > +
        > + > +
        > + > + > diff --git a/src/app/views/network/_ip_addresses_form.rhtml b/src/app/views/network/_ip_addresses_form.rhtml > new file mode 100644 > index 0000000..f833b2a > --- /dev/null > +++ b/src/app/views/network/_ip_addresses_form.rhtml > @@ -0,0 +1,50 @@ > + > + > +
        > + > + > + > diff --git a/src/app/views/network/_select.rhtml b/src/app/views/network/_select.rhtml > new file mode 100644 > index 0000000..e69d9b0 > --- /dev/null > +++ b/src/app/views/network/_select.rhtml > @@ -0,0 +1,32 @@ > +<% target = 'nic' unless target > + network_id = 'physical_network_id' if target == 'nic' > + network_id = 'vlan_id' if target == 'bonding' > + > + %> > + > +
        Network:
        > +
        > + <%= select_with_label "", target, network_id, > + @networks.collect { |n| [n.name + ' - ' + n.boot_type.label, n.id ] } %> > + > +
        > + > + > diff --git a/src/app/views/network/edit.rhtml b/src/app/views/network/edit.rhtml > new file mode 100644 > index 0000000..6360b3f > --- /dev/null > +++ b/src/app/views/network/edit.rhtml > @@ -0,0 +1,34 @@ > +<%- content_for :title do -%> > + Edit Network > +<%- end -%> > +<%- content_for :description do -%> > +<%- end -%> > + > + > +
        > +
        > + <%= hidden_field_tag 'id', @network.id %> > + <%= render :partial => 'form', :locals => { :create => false } %> > +
        > + > + <%= popup_footer("$('#network_form').submit()", "Edit Network") %> > +
        > + > + > + > + > diff --git a/src/app/views/network/edit_bonding.rhtml b/src/app/views/network/edit_bonding.rhtml > new file mode 100644 > index 0000000..645e469 > --- /dev/null > +++ b/src/app/views/network/edit_bonding.rhtml > @@ -0,0 +1,52 @@ > +
        + action="<%= url_for :action => 'update_bonding' %>" id="edit_bonding_form" > > +
        > + Editing Bonded Interface > +
        > + > + <%= hidden_field_tag('id', @bonding.id) %> > + <%= render :partial => 'bonding_form' %> > + > +
        > + > +
        > + <%= hidden_field_tag('id', @bonding.id) %> > +
        > + > +<%= multi_button_popup_footer({"Edit Bonding" => > + "$('#edit_bonding_form').submit()", > + "Delete Bonding" => > + "$('#delete_bonding_form').submit()"}) %> > + > + > diff --git a/src/app/views/network/edit_ip_address.rhtml b/src/app/views/network/edit_ip_address.rhtml > new file mode 100644 > index 0000000..5a3cc18 > --- /dev/null > +++ b/src/app/views/network/edit_ip_address.rhtml > @@ -0,0 +1,66 @@ > +<% if @parent_type == 'network' %> > +
        + action="<%= url_for :action => 'update_ip_address' %>" > + id="edit_ip_address_form" > > +<% end %> > + > +
        > + Editing IP Address > +
        > + > + <% if @parent_type != 'network' %> > + Delete > + <% end %> > + > + <%= hidden_field_tag('id', @ip_address.id) %> > + <%= render :partial => 'ip_address_form' %> > + > +<% if @parent_type == 'network' %> > +
        > +<% end %> > + > +
        + action="<%= url_for :action => 'destroy_ip_address' %>" > + id="delete_ip_address_form" > > + <%= hidden_field_tag('id', @ip_address.id) %> > +
        > + > +<% if @parent_type == 'network' %> > +<%= multi_button_popup_footer({" Edit IP Address" => > + "$('#edit_ip_address_form').submit()", > + "Delete IP Address" => > + "$('#delete_ip_address_form').submit()"}) %> > +<% end %> > + > + > diff --git a/src/app/views/network/edit_network_ip_addresses.rhtml b/src/app/views/network/edit_network_ip_addresses.rhtml > new file mode 100644 > index 0000000..7a1e4cb > --- /dev/null > +++ b/src/app/views/network/edit_network_ip_addresses.rhtml > @@ -0,0 +1,13 @@ > +<%- content_for :title do -%> > + Edit Network IP Addresses > +<%- end -%> > +<%- content_for :description do -%> > +<%- end -%> > + > +<%= render :partial => 'ip_addresses_form', > + :locals => { :parent_type => 'network', > + :parent_id => @network.id } %> > + > + > diff --git a/src/app/views/network/edit_nic.rhtml b/src/app/views/network/edit_nic.rhtml > new file mode 100644 > index 0000000..75af6fb > --- /dev/null > +++ b/src/app/views/network/edit_nic.rhtml > @@ -0,0 +1,62 @@ > +
        + action="<%= url_for :action => 'update_nic' %>" id="nic_form" > > +
        > + Editing NIC > +
        > + > + <%= error_messages_for 'nic' %> > + > +
        > + <%= hidden_field_tag 'id', @nic.id %> > + <%= hidden_field_tag 'nic_host_id', @nic.host.id %> > + <%= hidden_field_tag 'nic_network_id', @nic.physical_network.id %> > + > +
        MAC:
        > +
        <%= @nic.mac %>
        > + > + <% if @nic.host.bondings.size != 0 %> > +
        Bonded Interfaces
        > +
        > + > +
        > + <% end %> > + > + > + <%= render :partial => 'select' %> > + > +
        + style="<% if @network.boot_type_id != @static_boot_type.id %> > + display: none;<%end %>"> > + <%= render :partial => 'ip_addresses_form', > + :locals => { :parent_type => 'nic', > + :parent_id => @nic.id } %> > +
        > + > + > +
        > + <%= popup_footer("$('#nic_form').submit()", "Edit NIC") %> > +
        > + > + > diff --git a/src/app/views/network/list.html.erb b/src/app/views/network/list.html.erb > new file mode 100644 > index 0000000..9a304cf > --- /dev/null > +++ b/src/app/views/network/list.html.erb > @@ -0,0 +1,72 @@ > + > + > + > + > +
        > +<% if @networks.size != 0 %> > +
        > + <%= render :partial => "grid", :locals => { :table_id => "networks_grid", > + :networks => @networks, > + :on_select => "networks_select"} %> > +
        > + > +
        > +
        > +
        Select a network.
        > +
        > +
        > +<% else %> > +
        > +
        > + <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> > + > +
        > + No networks found.

        > + <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   > + Add first network > +
        > +
        > +
        > +<% end %> > diff --git a/src/app/views/network/new.rhtml b/src/app/views/network/new.rhtml > new file mode 100644 > index 0000000..15b7304 > --- /dev/null > +++ b/src/app/views/network/new.rhtml > @@ -0,0 +1,28 @@ > +<%- content_for :title do -%> > + Add Network > +<%- end -%> > +<%- content_for :description do -%> > +<%- end -%> > + > + > +
        > +
        > + <%= render :partial => 'form', :locals => { :create => true } %> > +
        > + > + <%= popup_footer("$('#network_form').submit()", "Add Network") %> > +
        > + > + > + > diff --git a/src/app/views/network/new_bonding.rhtml b/src/app/views/network/new_bonding.rhtml > new file mode 100644 > index 0000000..9ee6994 > --- /dev/null > +++ b/src/app/views/network/new_bonding.rhtml > @@ -0,0 +1,32 @@ > +
        + action="<%= url_for :action => 'create_bonding' %>" id="bonding_form" > > + > +
        > + Create Bonded Interface > +
        > + > + <%= render :partial => 'bonding_form' %> > +
        > + > +<%= multi_button_popup_footer({"Create Bonding" => > + "$('#bonding_form').submit()"}) %> > + > + > diff --git a/src/app/views/network/new_ip_address.rhtml b/src/app/views/network/new_ip_address.rhtml > new file mode 100644 > index 0000000..6167a76 > --- /dev/null > +++ b/src/app/views/network/new_ip_address.rhtml > @@ -0,0 +1,33 @@ > +<% if @parent_type == 'network' %> > +
        + action="<%= url_for :action => 'create_ip_address' %>" > + id="ip_address_form" > > +<% end %> > + > +
        > + Create Ip Address > +
        > + > + <%= render :partial => 'ip_address_form' %> > + > +<% if @parent_type == 'network' %> > +
        > +<% end %> > + > +<% if @parent_type == 'network' %> > +<%= multi_button_popup_footer({"Create IP Address" => > + "$('#ip_address_form').submit()"}) %> > +<% end %> > + > + > diff --git a/src/app/views/network/show.rhtml b/src/app/views/network/show.rhtml > new file mode 100644 > index 0000000..a56c41d > --- /dev/null > +++ b/src/app/views/network/show.rhtml > @@ -0,0 +1,30 @@ > +<%- content_for :title do -%> > + <%=h @network.name %> > +<%- end -%> > + > +<%- content_for :action_links do -%> > + <%= link_to image_tag("icon_edit.png") + "Edit", > + {:action => 'edit', :id => @network.id }, > + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> > + > + <%= link_to image_tag("icon_edit.png") + "Edit IP Addresses", > + {:action => 'edit_network_ip_addresses', :id => @network.id }, > + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> > +<%- end -%> > + > + > +
        > + Name:
        > + Type:
        > + Boot Type:
        > + IP Addresses:
        > +
        > +
        > + <%=h @network.name %>
        > + <%=h @network.type %>
        > + <%=h @network.boot_type.label %>
        > + <%=h @network.ip_addresses.size %>
        > +
        > + > +<%- content_for :right do -%> > +<%- end -%> > diff --git a/src/app/views/nic/_list.rhtml b/src/app/views/nic/_list.rhtml > index 07f7e44..eb3a4a7 100644 > --- a/src/app/views/nic/_list.rhtml > +++ b/src/app/views/nic/_list.rhtml > @@ -1,7 +1,6 @@ > > > > - > > > > @@ -11,7 +10,6 @@ > > <% for nic in nics %> > > - > > > > diff --git a/src/db/migrate/028_refactor_networking_model.rb b/src/db/migrate/028_refactor_networking_model.rb > new file mode 100644 > index 0000000..946b976 > --- /dev/null > +++ b/src/db/migrate/028_refactor_networking_model.rb > @@ -0,0 +1,271 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +# introduce networks and ip_addresses tables, refactor relationships > +class RefactorNetworkingModel < ActiveRecord::Migration > + def self.up > + > + #################################################### > + # bugfix, bridge tables shouldn't have their own ids > + remove_column :bondings_nics, :id > + > + ################################################################## > + # add networks, usages tables and networks_usage_types bridge > + create_table :networks do |t| > + t.string :type, :null => false > + t.string :name, :null => false > + t.integer :boot_type_id, :null => false > + > + # attributes for Vlan (type=Vlan) > + t.integer :number > + end > + > + create_table :usages do |t| > + t.string :label, :null => false > + t.string :usage, :null => false > + end > + > + create_table :networks_usages, :id => false do |t| > + t.integer :network_id, :null => false > + t.integer :usage_id, :null => false > + end > + > + add_index :networks_usages, [:network_id, :usage_id], :unique => true > + > + # create usages > + Usage.create(:label => 'Guest', :usage => 'guest') > + Usage.create(:label => 'Management', :usage => 'management') > + Usage.create(:label => 'Storage', :usage => 'storage') > + > + # referential integrity for networks tables > + execute "alter table networks add constraint > + fk_network_boot_types > + foreign key (boot_type_id) references > + boot_types(id)" > + execute "alter table networks_usages add constraint > + fk_networks_usages_network_id > + foreign key (network_id) references > + networks(id)" > + execute "alter table networks_usages add constraint > + fk_networks_usages_usage_id > + foreign key (usage_id) references > + usages(id)" > + > + # add foreign keys to nics / bondings table > + add_column :nics, :physical_network_id, :integer > + add_column :bondings, :vlan_id, :integer > + > + # referential integrity for nic/bondings network ids > + execute "alter table nics add constraint > + fk_nic_networks > + foreign key (physical_network_id) references > + networks(id)" > + execute "alter table bondings add constraint > + fk_bonding_networks > + foreign key (vlan_id) references > + networks(id)" > + > + #################################################### > + # create ip_addresses table > + create_table :ip_addresses do |t| > + t.string :type > + > + # foreign keys to associated entities > + t.integer :nic_id > + t.integer :bonding_id > + t.integer :network_id > + > + # common attributes > + t.string :address, :limit => 39, :null => false > + t.string :gateway, :limit => 39 > + > + # attributes for IPv4 (type=IpV4Address) > + t.string :netmask, :limit => 15 > + t.string :broadcast, :limit => 15 > + > + # attributes for IPv6 (type=IpV6Address) > + t.string :prefix, :limit => 39 > + t.timestamps > + end > + > + # referential integrity for ip_addresses table > + execute "alter table ip_addresses add constraint > + fk_nic_ip_addresses > + foreign key (nic_id) references nics(id)" > + execute "alter table ip_addresses add constraint > + fk_bonding_ip_addresses > + foreign key (bonding_id) references bondings(id)" > + execute "alter table ip_addresses add constraint > + fk_network_ip_addresses > + foreign key (network_id) references networks(id)" > + > + ################################################################### > + static_boot_type_id = > + BootType.find(:first, > + :conditions => {:proto => 'static'} ).id > + > + # migrate nic ip_addresses to networks / ip_addresses table > + i = 0 > + Nic.find(:all).each do |nic| > + if nic.boot_type_id == static_boot_type_id > + IpV4Address.new(:nic_id => nic.id, > + :address => nic.ip_addr).save! > + > + end > + network = PhysicalNetwork.new( > + :name => 'Physical Network ' + i.to_s, > + :boot_type_id => nic.boot_type_id) > + network.save! > + > + ip_address = IpV4Address.new(:address => nic.ip_addr ? nic.ip_addr : '0.0.0.0', > + :netmask => nic.netmask, > + :broadcast => nic.broadcast, > + :gateway => nic.ip_addr) > + ip_address.network = network > + ip_address.save! > + > + nic.physical_network = network > + nic.save! > + > + i += 1 > + end > + > + # migrate bonding ip_addresses to networks / ip_addresses table > + i = 0 > + Bonding.find(:all).each do |bonding| > + if bonding.boot_type_id == static_boot_type_id > + IpV4Address.new(:bonding_id => bonding.id, > + :address => bonding.ip_addr).save! > + end > + network = Vlan.new( > + :name => 'VLAN ' + i.to_s, > + :number => i, > + :boot_type_id => bonding.boot_type_id) > + network.save! > + > + ip_address = IpV4Address.new(:address => bonding.ip_addr ? bonding.ip_addr : '0.0.0.0', > + :netmask => bonding.netmask, > + :broadcast => bonding.broadcast, > + :gateway => bonding.ip_addr) > + ip_address.network = network > + ip_address.save! > + > + bonding.vlan = network > + bonding.save! > + > + i += 1 > + end > + > + ############################################################## > + # remove nics / bonding ip address and network related columns > + remove_column :nics, :ip_addr > + remove_column :nics, :netmask > + remove_column :nics, :broadcast > + remove_column :nics, :boot_type_id > + remove_column :bondings, :ip_addr > + remove_column :bondings, :netmask > + remove_column :bondings, :broadcast > + remove_column :bondings, :boot_type_id > + > + > + end > + > + def self.down > + ############################################################## > + # readd nics / bonding ip address related columns > + add_column :nics, :ip_addr, :string, :limit => 16 > + add_column :nics, :netmask, :string, :limit => 16 > + add_column :nics, :broadcast, :string, :limit => 16 > + add_column :nics, :boot_type_id, :integer > + add_column :bondings, :ip_addr, :string, :limit => 16 > + add_column :bondings, :netmask, :string, :limit => 16 > + add_column :bondings, :broadcast, :string, :limit => 16 > + add_column :bondings, :boot_type_id, :integer > + > + execute "alter table nics add constraint > + fk_nic_boot_types > + foreign key (boot_type_id) references > + boot_types(id)" > + execute "alter table bondings add constraint > + fk_bonding_boot_types > + foreign key (boot_type_id) references > + boot_types(id)" > + > + ############################################################## > + # attempt to migrate ip information back into nics table. > + # because a nic can have multiple ips (if statically > + # assigned) as well as its network, just use the 1st > + # found > + Nic.find(:all).each do |nic| > + if nic.physical_network.ip_addresses.size > 0 > + # use the 1st configured network ip > + nic.ip_addr = nic.physical_network.ip_addresses[0].address > + nic.netmask = nic.physical_network.ip_addresses[0].netmask > + nic.broadcast = nic.physical_network.ip_addresses[0].broadcast > + end > + > + if nic.ip_addresses.size > 0 > + # use the 1st assigned static ip > + nic.ip_addr = nic.ip_addresses[0].address > + end > + > + nic.boot_type_id = nic.physical_network.boot_type_id > + > + nic.save! > + end > + > + # attempt to migrate ip information back into bondings table. > + # because a bonding can have multiple ips (if statically > + # assigned) as well as its network, just use the 1st > + # found > + Bonding.find(:all).each do |bonding| > + if bonding.vlan.ip_addresses.size > 0 > + # use the 1st configured network ip > + bonding.ip_addr = bonding.vlan.ip_addresses[0].address > + bonding.netmask = bonding.vlan.ip_addresses[0].netmask > + bonding.broadcast = bonding.vlan.ip_addresses[0].broadcast > + end > + > + if bonding.ip_addresses.size > 0 > + # use the 1st assigned static ip > + bonding.ip_addr = bonding.ip_addresses[0].address > + end > + > + bonding.boot_type_id = bonding.vlan.boot_type_id > + > + bonding.save! > + end > + > + ############################################################## > + # drop ip_addresses table > + drop_table :ip_addresses > + > + # drop network ids from nics / bondings table > + remove_column :nics, :physical_network_id > + remove_column :bondings, :vlan_id > + > + # drop networks tables > + drop_table :networks_usages > + drop_table :usages > + drop_table :networks > + > + ############################################################## > + # undo bugfix above > + add_column :bondings_nics, :id, :integer > + end > +end > diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js > index 4579c80..818b862 100644 > --- a/src/public/javascripts/ovirt.js > +++ b/src/public/javascripts/ovirt.js > @@ -297,4 +297,31 @@ function delete_pool(delete_url, id) > $.jGrowl(data.alert); > } > }, 'json'); > -} > \ No newline at end of file > +} > + > + > +function get_selected_networks() > +{ > + return get_selected_checkboxes("networks_grid_form"); > +} > + > +function afterNetwork(response, status){ > + ajax_validation(response, status); > + if (response.success) { > + $(document).trigger('close.facebox'); > + grid = $("#networks_grid"); > + if (grid.size()>0 && grid != null) { > + grid.flexReload(); > + } else { > + $tabs.tabs("load",$tabs.data('selected.tabs')); > + } > + } > +} > + > +function afterIpAddress(response, status){ > + ajax_validation(response, status); > + if (response.success) { > + reset_selected_ip_address(); > + reset_ip_address_select(); > + } > +} > diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css > index 16eaf62..228ff7b 100644 > --- a/src/public/stylesheets/components.css > +++ b/src/public/stylesheets/components.css > @@ -267,4 +267,52 @@ > .detail-pane-chart { > height: 50px; > width: 375px; > -}; > +} > + > + > +/************************* > + * new popup components > + *************************/ > +.popup-content-selection { > + float: left; > + width: 45%; > + padding-left: 20px; > + padding-top: 10px; > +} > + > +.popup-content-selection select{ > + min-width: 200px; > +} > + > +.popup-content-footer{ > + width: 99%; > + float: left; > +} > + > +.selected_popup_content { > + padding-left: 20px; > + min-height: 50px; > + float: left; > + width: 96%; > +} > + > +#selected_popup_content_header { > + padding-bottom: 5px; > + font-weight: bold; > +} > + > +#selected_popup_content_expanded{ > + padding-bottom: 50px; > +} > + > +.selected_popup_content_left { > + float: left; > + width: 40%; > + padding-bottom: 15px; > +} > + > +.selected_popup_content_right { > + float: left; > + width: 40%; > + padding-bottom: 15px; > +} - -- Darryl L. Pierce : GPG KEYID: 6C4E7F1B -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org iEYEARECAAYFAkkLEPoACgkQjaT4DmxOfxsw5wCaA5ztOXBYcMG/MEwiAJvm4xSh s7YAnRYUdFzlX5DNzjzePT/uq9Pdojw1 =geqD -----END PGP SIGNATURE----- -------------- next part -------------- A non-text attachment was scrubbed... Name: dpierce.vcf Type: text/x-vcard Size: 319 bytes Desc: not available URL: From mmorsi at redhat.com Fri Oct 31 14:47:43 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 31 Oct 2008 10:47:43 -0400 Subject: [Ovirt-devel] [PATCH server] network integration into ovirt server db and wui In-Reply-To: <490B1100.5020401@redhat.com> References: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> <490B1100.5020401@redhat.com> Message-ID: <490B1A8F.2030403@redhat.com> Hey Darryl, Thanks for the feedback. Comments are also inline below. Darryl Pierce wrote: > NAK. > > Some of the first things I've noticed is that, by removing columns from > the bondings and nics tables into the separate IP-related tables, > updates need to be made to the ManagedNodeConfiguration class. Since the > data itself is the same and just relocated, refactoring the > configuration class to get data from the appropriate objects should take > care of things initially. > > Before this patch can be pushed, it needs to fix that class. The unit > test itself does not need changing since the data ought to still look > the same once encoded. There may be changes needed for vlans, but that > will be additional tests. > Did you say on irc you were going to take care of this ManagedNodeConfiguration bit or did I mishear? [snip] > > diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb > > index 006c261..a3ad150 100644 > > --- a/src/app/models/bonding.rb > > +++ b/src/app/models/bonding.rb > > @@ -30,6 +30,16 @@ > > # interface. They can be ignored if not used. > > # > > class Bonding < ActiveRecord::Base > > + belongs_to :host > > + belongs_to :bonding_type > > + belongs_to :vlan > > + has_many :ip_addresses, :dependent => :destroy > > + > > + has_and_belongs_to_many :nics, > > + :join_table => 'bondings_nics', > > + :foreign_key => :bonding_id > > + > > + > > validates_presence_of :name, > > :message => 'A name is required.' > > > @@ -42,18 +52,17 @@ class Bonding < ActiveRecord::Base > > validates_presence_of :interface_name, > > :message => 'An interface name is required.' > > > - validates_presence_of :boot_type_id, > > - :message => 'A boot type must be specified.' > > - > > validates_presence_of :bonding_type_id, > > :message => 'A bonding type must be specified.' > > > - belongs_to :host > > - belongs_to :bonding_type > > - belongs_to :boot_type > > + protected > > + def validate > > + errors.add("name", "must be specified") unless name > > + errors.add("interface_name", "must be specified") unless > interface_name > > + errors.add("bonding_type_id", "must be specified") unless > bonding_type_id > > This is duplicating the above validation with the validates_presence_of > check. The thing I noticed with validates_presence_of is that I found that it doesn't seem to trigger the validation components of the interface. For example if name isn't specified in the wui which is caught with a validate_presence_of instead of in the validate function, the corresponding errors is never added to the 'errors' array and thus the 'name' textbox on the interface is never highlighted so that the user knows that he/she must provide that information. [snip] > > > diff --git a/src/app/models/ip_v4_address.rb > b/src/app/models/ip_v4_address.rb > > new file mode 100644 > > index 0000000..40e8cf9 > > --- /dev/null > > +++ b/src/app/models/ip_v4_address.rb > > @@ -0,0 +1,48 @@ > > +# ip_v4_address.rb > > +# > > +# Copyright (C) 2008 Red Hat, Inc. > > +# Written by Darryl L. Pierce , > > +# Mohammed Morsi > > +# > > +# This program is free software; you can redistribute it and/or modify > > +# it under the terms of the GNU General Public License as published by > > +# the Free Software Foundation; version 2 of the License. > > +# > > +# This program is distributed in the hope that it will be useful, > > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > > +# GNU General Public License for more details. > > +# > > +# You should have received a copy of the GNU General Public License > > +# along with this program; if not, write to the Free Software > > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > > +# MA 02110-1301, USA. A copy of the GNU General Public License is > > +# also available at http://www.gnu.org/copyleft/gpl.html. > > + > > +# +IpV4Address+ represents a single IPv4 address. > > +# > > +class IpV4Address < IpAddress > > + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} > > + > > + validates_presence_of :address, > > + :message => 'An address must be supplied.' > > + validates_format_of :address, > > + :with => ADDRESS_TEST > > + > > + protected > > + def validate > > + unless address and address =~ ADDRESS_TEST > > + errors.add("address", "is of incorrect format") > > + end > > + unless !netmask or netmask == "" or netmask =~ ADDRESS_TEST > > + errors.add("netmask", "is of incorrect format") > > + end > > + unless !gateway or gateway == "" or gateway =~ ADDRESS_TEST > > + errors.add("gateway", "is of incorrect format") > > + end > > + unless !broadcast or broadcast == "" or broadcast =~ ADDRESS_TEST > > + errors.add("broadcast", "is of incorrect format") > > + end > > In the original patch I submitted each of these fields was tested using > validates_format_of. Thing is, since ip address is now being used for network, bonding, and nic, some of those fields won't apply in all cases. Specifically only the 'address' field should be set for ip addresses associated with nics / bondings (setting the others won't break anything, they just won't be used) and only when the nic is associated with a statically assigned network. The other fields are only used when an ip address is associated with a network, thus it doesn't work to validate them for every instance. > > > + end > > + > > +end > > diff --git a/src/app/models/ip_v6_address.rb > b/src/app/models/ip_v6_address.rb > > new file mode 100644 > > index 0000000..9720179 > > --- /dev/null > > +++ b/src/app/models/ip_v6_address.rb > > @@ -0,0 +1,44 @@ > > +# ip_v6_address.rb > > +# > > +# Copyright (C) 2008 Red Hat, Inc. > > +# Written by Darryl L. Pierce , > > +# Mohammed Morsi > > +# > > +# This program is free software; you can redistribute it and/or modify > > +# it under the terms of the GNU General Public License as published by > > +# the Free Software Foundation; version 2 of the License. > > +# > > +# This program is distributed in the hope that it will be useful, > > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > > +# GNU General Public License for more details. > > +# > > +# You should have received a copy of the GNU General Public License > > +# along with this program; if not, write to the Free Software > > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > > +# MA 02110-1301, USA. A copy of the GNU General Public License is > > +# also available at http://www.gnu.org/copyleft/gpl.html. > > + > > +# +IpV6Address+ represents a single IPv6 address. > > +# > > +class IpV6Address < IpAddress > > + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} > > + > > + validates_presence_of :address, > > + :message => 'An address must be provided.' > > + validates_format_of :address, > > + :with => ADDRESS_TEST > > + > > + protected > > + def validate > > + unless address =~ ADDRESS_TEST > > + errors.add("address", "is of incorrect format") > > + end > > + unless !prefix or prefix == "" or prefix =~ ADDRESS_TEST > > + errors.add("prefix", "is of incorrect format") > > + end > > + unless !gateway or gateway == "" or gateway =~ ADDRESS_TEST > > + errors.add("gateway", "is of incorrect format") > > + end > > See note in the IpV4Address regarding validations. The Rails pattern is > to use validates_format_of for checking content. It is also a valid rails approach to use the validate method for validations. [snip] > > > diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb > > index baf7095..25d3ac2 100644 > > --- a/src/app/models/nic.rb > > +++ b/src/app/models/nic.rb > > @@ -19,7 +19,19 @@ > > > class Nic < ActiveRecord::Base > > belongs_to :host > > - belongs_to :boot_type > > + belongs_to :physical_network > > + has_many :ip_addresses, :dependent => :destroy > > > - has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' > > + has_and_belongs_to_many :bondings, :join_table => 'bondings_nics' > > + > > + protected > > + def validate > > + errors.add("host_id", "must be specified") unless host_id > > + errors.add("physical_network_id", "must be specified") unless > physical_network_id > > These should be done with validates_presence_of to keep the code > consistent throughout the project. The error message added here is the > default from Rails, so need not be specified. > > > + if physical_network.boot_type.id == > > + BootType.find(:first, :conditions => { :proto => 'static' > }).id and > > You can test this without the second DB call using: > > if physical_network.boot_type.proto == static' Good catch on this boot_type bit. [snip] I'll see what I can change based on this feedback. Once again I'm not sure that the validation bit will be able to be changed due to the aforementioned reasons. -Mo From sseago at redhat.com Fri Oct 31 14:59:05 2008 From: sseago at redhat.com (Scott Seago) Date: Fri, 31 Oct 2008 10:59:05 -0400 Subject: [Ovirt-devel] [PATCH server] network integration into ovirt server db and wui In-Reply-To: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> References: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> Message-ID: <490B1D39.4060202@redhat.com> Mohammed Morsi wrote: > diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb > index a40d297..120fe2a 100644 > --- a/src/app/controllers/host_controller.rb > +++ b/src/app/controllers/host_controller.rb > @@ -156,6 +156,16 @@ class HostController < ApplicationController > render :json => @json_hash > end > > + def edit_network > + render :layout => 'popup' > + end > + > + def bondings_json > + bondings = Host.find(params[:id]).bondings > + bondings_json = [] > + bondings.each{ |x| bondings_json.push({:id => x.id, :name => x.name}) } > + render :json => bondings_json > + end > This is a really minor comment, but this could be written more compactly as: + def bondings_json + bondings = Host.find(params[:id]).bondings + render :json => bondings.collect{ |x| {:id => x.id, :name => x.name} + end > > > diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb > index 006c261..a3ad150 100644 > --- a/src/app/models/bonding.rb > +++ b/src/app/models/bonding.rb > @@ -30,6 +30,16 @@ > # interface. They can be ignored if not used. > # > class Bonding < ActiveRecord::Base > + belongs_to :host > + belongs_to :bonding_type > + belongs_to :vlan > + has_many :ip_addresses, :dependent => :destroy > + > + has_and_belongs_to_many :nics, > + :join_table => 'bondings_nics', > + :foreign_key => :bonding_id > + > + > validates_presence_of :name, > :message => 'A name is required.' > > @@ -42,18 +52,17 @@ class Bonding < ActiveRecord::Base > validates_presence_of :interface_name, > :message => 'An interface name is required.' > > - validates_presence_of :boot_type_id, > - :message => 'A boot type must be specified.' > - > validates_presence_of :bonding_type_id, > :message => 'A bonding type must be specified.' > > - belongs_to :host > - belongs_to :bonding_type > - belongs_to :boot_type > + protected > + def validate > + errors.add("name", "must be specified") unless name > + errors.add("interface_name", "must be specified") unless interface_name > + errors.add("bonding_type_id", "must be specified") unless bonding_type_id > + errors.add("host_id", "must be specified") unless host_id > + errors.add("vlan_id", "must be specified") unless vlan_id > + end > Why not just use validates_presence_of for these? This seems like the standard out-of-the-box use case for validates_presence_of. Also, it looks like name and interface_name have the valiadition in both places -- validates_presence_of and validate. > > - has_and_belongs_to_many :nics, > - :join_table => 'bondings_nics', > - :foreign_key => :bonding_id > > end > diff --git a/src/app/models/physical_network.rb b/src/app/models/physical_network.rb > new file mode 100644 > index 0000000..cba696a > --- /dev/null > +++ b/src/app/models/physical_network.rb > @@ -0,0 +1,27 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +class PhysicalNetwork < Network > + has_many :nics > + > + protected > + def validate > + errors.add("name", "must be specified") unless name > + errors.add("boot_type_id", "must be specified") unless boot_type_id > + end > +end > Why not use validates_presence_of for these? > diff --git a/src/app/models/vlan.rb b/src/app/models/vlan.rb > new file mode 100644 > index 0000000..2b96502 > --- /dev/null > +++ b/src/app/models/vlan.rb > @@ -0,0 +1,30 @@ > +# Copyright (C) 2008 Red Hat, Inc. > +# Written by Mohammed Morsi > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +class Vlan < Network > + has_many :bondings > + > + validates_presence_of :number > + > + protected > + def validate > + errors.add("name", "must be specified") unless name > + errors.add("number", "must be specified") unless number > + errors.add("boot_type_id", "must be specified") unless boot_type_id > + end > +end > same comment on validates_presence_of here > diff --git a/src/app/views/dashboard/index.html.erb b/src/app/views/dashboard/index.html.erb > index 92cb4da..8815ebc 100644 > --- a/src/app/views/dashboard/index.html.erb > +++ b/src/app/views/dashboard/index.html.erb > @@ -13,6 +13,13 @@ >
        IPMACBridgeUsage Type
        <%= link_to nic.ip_addr, { :controller => "nic", :action => 'show', :id => nic }, { :class => "show" } %><%= nic.mac %><%= nic.bridge %><%= nic.usage_type %>
        > > > + <% if @can_modify %> > +

        Networks

        > + > + View / Edit > + > + <% end %> > + > > > I'm not sure adding this to the currently-empty dashboard page is the right place -- although I realize the discussions on what to do with this were somewhat inconclusive. Here's another thought. Since it doesn't really belong to a "dashboard" per se -- and we're not sure that the dashboard needs multiple tabs, could we rename the nav column to something more generic (like "Home" or something). and then have Dashboard as ths first tab, Networks as another tab (if you have permissions on them), etc.? although this doesn't necessarily have to be done for this patch since it's more of a general UI cleanup task. The other issue here is that this breaks the left nav state since it's a standard http link instead of an ajax request. We should probably address that at the same time we place this on a tab in the UI > > diff --git a/src/app/views/host/edit_network.rhtml b/src/app/views/host/edit_network.rhtml > new file mode 100644 > index 0000000..7ec3180 > --- /dev/null > +++ b/src/app/views/host/edit_network.rhtml > For edit, we should remove the ability to change the type. Also the network details pane needs to show a list of associated IP addresses, Number (for vlans) and usages. And the 'new network' screen doesn't allow you to set usages either. > diff --git a/src/app/views/host/show.rhtml b/src/app/views/host/show.rhtml > index d1b05ad..bc39a62 100644 > --- a/src/app/views/host/show.rhtml > +++ b/src/app/views/host/show.rhtml > @@ -17,6 +17,10 @@ > <%= image_tag "icon_x.png" %> Clear VMs > > <% end -%> > + <%= link_to image_tag("icon_edit.png") +"Edit Network", > + {:controller => 'host', > + :action => 'edit_network', :id => @host.id}, > + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> > <%- end -%> > <%- end -%> > diff --git a/src/app/views/host/show.rhtml b/src/app/views/host/show.rhtml index d1b05ad..c9562b1 100644 --- a/src/app/views/host/show.rhtml +++ b/src/app/views/host/show.rhtml @@ -17,6 +17,10 @@ <%= image_tag "icon_x.png" %> Clear VMs <% end -%> + <%= link_to image_tag("icon_edit.png") +"Edit Network", + {:controller => 'host', + :action => 'edit_network', :id => @host.id}, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> <%- end -%> <%- end -%> diff --git a/src/app/views/network/_grid.rhtml b/src/app/views/network/_grid.rhtml new file mode 100644 index 0000000..6af0c05 --- /dev/null +++ b/src/app/views/network/_grid.rhtml @@ -0,0 +1,37 @@ +<% networks_per_page = 40 unless (defined? networks_per_page) and !(networks_per_page.nil?) %> +<% usepager = @networks.size > networks_per_page %> + +
        +
        + +
        +
        + + + diff --git a/src/app/views/network/_ip_address_form.rhtml b/src/app/views/network/_ip_address_form.rhtml new file mode 100644 index 0000000..b952807 --- /dev/null +++ b/src/app/views/network/_ip_address_form.rhtml @@ -0,0 +1,62 @@ +<%= error_messages_for 'ip_address' %> + +
        + <%= hidden_field_tag 'parent_type', @parent_type%> + <%= hidden_field_tag 'ip_address[id]', @ip_address.id if @ip_address%> + <%= hidden_field_tag 'ip_address[network_id]', @network.id if @network %> + <%= hidden_field_tag 'ip_address[nic_id]', @nic.id if @nic %> + <%= hidden_field_tag 'ip_address[bonding_id]', @bonding.id if @bonding %> + +
        Type:
        +
        + <%= select_with_label "", "ip_address", "type", + [[ "ipv4", "IpV4Address" ], [ "ipv6", "IpV6Address" ] ] %> +
        + + +
        +
        IP Address
        +
        + <%= text_field_with_label "", "ip_address", "address" %> +
        +
        + +
        +
        Netmask
        +
        + <%= text_field_with_label "", "ip_address", "netmask" %> +
        + +
        Broadcast
        +
        + <%= text_field_with_label "", "ip_address", "broadcast" %> +
        +
        + + + +
        +
        Gateway
        +
        + <%= text_field_with_label "", "ip_address", "gateway" %> +
        +
        + +
        + + diff --git a/src/app/views/network/_ip_addresses_form.rhtml b/src/app/views/network/_ip_addresses_form.rhtml new file mode 100644 index 0000000..f833b2a --- /dev/null +++ b/src/app/views/network/_ip_addresses_form.rhtml @@ -0,0 +1,50 @@ + + +
        + + + diff --git a/src/app/views/network/_select.rhtml b/src/app/views/network/_select.rhtml new file mode 100644 index 0000000..e69d9b0 --- /dev/null +++ b/src/app/views/network/_select.rhtml @@ -0,0 +1,32 @@ +<% target = 'nic' unless target + network_id = 'physical_network_id' if target == 'nic' + network_id = 'vlan_id' if target == 'bonding' + + %> + +
        Network:
        +
        + <%= select_with_label "", target, network_id, + @networks.collect { |n| [n.name + ' - ' + n.boot_type.label, n.id ] } %> + +
        + + diff --git a/src/app/views/network/edit.rhtml b/src/app/views/network/edit.rhtml new file mode 100644 index 0000000..6360b3f --- /dev/null +++ b/src/app/views/network/edit.rhtml @@ -0,0 +1,34 @@ +<%- content_for :title do -%> + Edit Network +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + + +
        +
        + <%= hidden_field_tag 'id', @network.id %> + <%= render :partial => 'form', :locals => { :create => false } %> +
        + + <%= popup_footer("$('#network_form').submit()", "Edit Network") %> +
        + + + + diff --git a/src/app/views/network/edit_bonding.rhtml b/src/app/views/network/edit_bonding.rhtml new file mode 100644 index 0000000..645e469 --- /dev/null +++ b/src/app/views/network/edit_bonding.rhtml @@ -0,0 +1,52 @@ +
        +
        + Editing Bonded Interface +
        + + <%= hidden_field_tag('id', @bonding.id) %> + <%= render :partial => 'bonding_form' %> + +
        + +
        + <%= hidden_field_tag('id', @bonding.id) %> +
        + +<%= multi_button_popup_footer({"Edit Bonding" => + "$('#edit_bonding_form').submit()", + "Delete Bonding" => + "$('#delete_bonding_form').submit()"}) %> + + diff --git a/src/app/views/network/edit_ip_address.rhtml b/src/app/views/network/edit_ip_address.rhtml new file mode 100644 index 0000000..60fe8f1 --- /dev/null +++ b/src/app/views/network/edit_ip_address.rhtml @@ -0,0 +1,76 @@ +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + Editing IP Address +
        + + <% if @parent_type != 'network' %> + Delete + <% end %> + + <%= hidden_field_tag('id', @ip_address.id) %> + <%= render :partial => 'ip_address_form' %> + +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + <%= hidden_field_tag('id', @ip_address.id) %> +
        + +<% if @parent_type == 'network' %> +<%= multi_button_popup_footer({" Edit IP Address" => + "$('#edit_ip_address_form').submit()", + "Delete IP Address" => + "$('#delete_ip_address_form').submit()"}) %> +<% end %> + + diff --git a/src/app/views/network/edit_network_ip_addresses.rhtml b/src/app/views/network/edit_network_ip_addresses.rhtml new file mode 100644 index 0000000..7a1e4cb --- /dev/null +++ b/src/app/views/network/edit_network_ip_addresses.rhtml @@ -0,0 +1,13 @@ +<%- content_for :title do -%> + Edit Network IP Addresses +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + +<%= render :partial => 'ip_addresses_form', + :locals => { :parent_type => 'network', + :parent_id => @network.id } %> + + diff --git a/src/app/views/network/edit_nic.rhtml b/src/app/views/network/edit_nic.rhtml new file mode 100644 index 0000000..75af6fb --- /dev/null +++ b/src/app/views/network/edit_nic.rhtml @@ -0,0 +1,62 @@ +
        +
        + Editing NIC +
        + + <%= error_messages_for 'nic' %> + +
        + <%= hidden_field_tag 'id', @nic.id %> + <%= hidden_field_tag 'nic_host_id', @nic.host.id %> + <%= hidden_field_tag 'nic_network_id', @nic.physical_network.id %> + +
        MAC:
        +
        <%= @nic.mac %>
        + + <% if @nic.host.bondings.size != 0 %> +
        Bonded Interfaces
        +
        + +
        + <% end %> + + + <%= render :partial => 'select' %> + +
        + <%= render :partial => 'ip_addresses_form', + :locals => { :parent_type => 'nic', + :parent_id => @nic.id } %> +
        + + +
        + <%= popup_footer("$('#nic_form').submit()", "Edit NIC") %> +
        + + diff --git a/src/app/views/network/list.html.erb b/src/app/views/network/list.html.erb new file mode 100644 index 0000000..9a304cf --- /dev/null +++ b/src/app/views/network/list.html.erb @@ -0,0 +1,72 @@ + + + + +
        +<% if @networks.size != 0 %> +
        + <%= render :partial => "grid", :locals => { :table_id => "networks_grid", + :networks => @networks, + :on_select => "networks_select"} %> +
        + +
        +
        +
        Select a network.
        +
        +
        +<% else %> +
        +
        + <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> + +
        + No networks found.

        + <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   + Add first network +
        +
        +
        +<% end %> diff --git a/src/app/views/network/new.rhtml b/src/app/views/network/new.rhtml new file mode 100644 index 0000000..15b7304 --- /dev/null +++ b/src/app/views/network/new.rhtml @@ -0,0 +1,28 @@ +<%- content_for :title do -%> + Add Network +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + + +
        +
        + <%= render :partial => 'form', :locals => { :create => true } %> +
        + + <%= popup_footer("$('#network_form').submit()", "Add Network") %> +
        + + + diff --git a/src/app/views/network/new_bonding.rhtml b/src/app/views/network/new_bonding.rhtml new file mode 100644 index 0000000..9ee6994 --- /dev/null +++ b/src/app/views/network/new_bonding.rhtml @@ -0,0 +1,32 @@ +
        + +
        + Create Bonded Interface +
        + + <%= render :partial => 'bonding_form' %> +
        + +<%= multi_button_popup_footer({"Create Bonding" => + "$('#bonding_form').submit()"}) %> + + diff --git a/src/app/views/network/new_ip_address.rhtml b/src/app/views/network/new_ip_address.rhtml new file mode 100644 index 0000000..df2a2bb --- /dev/null +++ b/src/app/views/network/new_ip_address.rhtml @@ -0,0 +1,44 @@ +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + Create Ip Address +
        + + <%= render :partial => 'ip_address_form' %> + +<% if @parent_type == 'network' %> +
        +<% end %> + +<% if @parent_type == 'network' %> +<%= multi_button_popup_footer({"Create IP Address" => + "$('#ip_address_form').submit()"}) %> +<% end %> + + diff --git a/src/app/views/network/show.rhtml b/src/app/views/network/show.rhtml new file mode 100644 index 0000000..2ac68ea --- /dev/null +++ b/src/app/views/network/show.rhtml @@ -0,0 +1,31 @@ +<%- content_for :title do -%> + <%=h @network.name %> +<%- end -%> + +<%- content_for :action_links do -%> + <%= link_to image_tag("icon_edit.png") + "Edit", + {:action => 'edit', :id => @network.id }, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> + + <%= link_to image_tag("icon_edit.png") + "Edit IP Addresses", + {:action => 'edit_network_ip_addresses', :id => @network.id }, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> +<%- end -%> + + +
        + Name:
        + Type:
        + Boot Type:
        + IP Addresses:
        +
        +
        + <%=h @network.name %>
        + <%=h @network.type %>
        + <%=h @network.boot_type.label %>
        + <%=@network.ip_addresses.collect{ |ip| + ip.address}.join("
        ") %> +
        + +<%- content_for :right do -%> +<%- end -%> diff --git a/src/app/views/nic/_list.rhtml b/src/app/views/nic/_list.rhtml index 07f7e44..eb3a4a7 100644 --- a/src/app/views/nic/_list.rhtml +++ b/src/app/views/nic/_list.rhtml @@ -1,7 +1,6 @@ - @@ -11,7 +10,6 @@ <% for nic in nics %> - diff --git a/src/db/migrate/028_refactor_networking_model.rb b/src/db/migrate/028_refactor_networking_model.rb new file mode 100644 index 0000000..946b976 --- /dev/null +++ b/src/db/migrate/028_refactor_networking_model.rb @@ -0,0 +1,271 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# introduce networks and ip_addresses tables, refactor relationships +class RefactorNetworkingModel < ActiveRecord::Migration + def self.up + + #################################################### + # bugfix, bridge tables shouldn't have their own ids + remove_column :bondings_nics, :id + + ################################################################## + # add networks, usages tables and networks_usage_types bridge + create_table :networks do |t| + t.string :type, :null => false + t.string :name, :null => false + t.integer :boot_type_id, :null => false + + # attributes for Vlan (type=Vlan) + t.integer :number + end + + create_table :usages do |t| + t.string :label, :null => false + t.string :usage, :null => false + end + + create_table :networks_usages, :id => false do |t| + t.integer :network_id, :null => false + t.integer :usage_id, :null => false + end + + add_index :networks_usages, [:network_id, :usage_id], :unique => true + + # create usages + Usage.create(:label => 'Guest', :usage => 'guest') + Usage.create(:label => 'Management', :usage => 'management') + Usage.create(:label => 'Storage', :usage => 'storage') + + # referential integrity for networks tables + execute "alter table networks add constraint + fk_network_boot_types + foreign key (boot_type_id) references + boot_types(id)" + execute "alter table networks_usages add constraint + fk_networks_usages_network_id + foreign key (network_id) references + networks(id)" + execute "alter table networks_usages add constraint + fk_networks_usages_usage_id + foreign key (usage_id) references + usages(id)" + + # add foreign keys to nics / bondings table + add_column :nics, :physical_network_id, :integer + add_column :bondings, :vlan_id, :integer + + # referential integrity for nic/bondings network ids + execute "alter table nics add constraint + fk_nic_networks + foreign key (physical_network_id) references + networks(id)" + execute "alter table bondings add constraint + fk_bonding_networks + foreign key (vlan_id) references + networks(id)" + + #################################################### + # create ip_addresses table + create_table :ip_addresses do |t| + t.string :type + + # foreign keys to associated entities + t.integer :nic_id + t.integer :bonding_id + t.integer :network_id + + # common attributes + t.string :address, :limit => 39, :null => false + t.string :gateway, :limit => 39 + + # attributes for IPv4 (type=IpV4Address) + t.string :netmask, :limit => 15 + t.string :broadcast, :limit => 15 + + # attributes for IPv6 (type=IpV6Address) + t.string :prefix, :limit => 39 + t.timestamps + end + + # referential integrity for ip_addresses table + execute "alter table ip_addresses add constraint + fk_nic_ip_addresses + foreign key (nic_id) references nics(id)" + execute "alter table ip_addresses add constraint + fk_bonding_ip_addresses + foreign key (bonding_id) references bondings(id)" + execute "alter table ip_addresses add constraint + fk_network_ip_addresses + foreign key (network_id) references networks(id)" + + ################################################################### + static_boot_type_id = + BootType.find(:first, + :conditions => {:proto => 'static'} ).id + + # migrate nic ip_addresses to networks / ip_addresses table + i = 0 + Nic.find(:all).each do |nic| + if nic.boot_type_id == static_boot_type_id + IpV4Address.new(:nic_id => nic.id, + :address => nic.ip_addr).save! + + end + network = PhysicalNetwork.new( + :name => 'Physical Network ' + i.to_s, + :boot_type_id => nic.boot_type_id) + network.save! + + ip_address = IpV4Address.new(:address => nic.ip_addr ? nic.ip_addr : '0.0.0.0', + :netmask => nic.netmask, + :broadcast => nic.broadcast, + :gateway => nic.ip_addr) + ip_address.network = network + ip_address.save! + + nic.physical_network = network + nic.save! + + i += 1 + end + + # migrate bonding ip_addresses to networks / ip_addresses table + i = 0 + Bonding.find(:all).each do |bonding| + if bonding.boot_type_id == static_boot_type_id + IpV4Address.new(:bonding_id => bonding.id, + :address => bonding.ip_addr).save! + end + network = Vlan.new( + :name => 'VLAN ' + i.to_s, + :number => i, + :boot_type_id => bonding.boot_type_id) + network.save! + + ip_address = IpV4Address.new(:address => bonding.ip_addr ? bonding.ip_addr : '0.0.0.0', + :netmask => bonding.netmask, + :broadcast => bonding.broadcast, + :gateway => bonding.ip_addr) + ip_address.network = network + ip_address.save! + + bonding.vlan = network + bonding.save! + + i += 1 + end + + ############################################################## + # remove nics / bonding ip address and network related columns + remove_column :nics, :ip_addr + remove_column :nics, :netmask + remove_column :nics, :broadcast + remove_column :nics, :boot_type_id + remove_column :bondings, :ip_addr + remove_column :bondings, :netmask + remove_column :bondings, :broadcast + remove_column :bondings, :boot_type_id + + + end + + def self.down + ############################################################## + # readd nics / bonding ip address related columns + add_column :nics, :ip_addr, :string, :limit => 16 + add_column :nics, :netmask, :string, :limit => 16 + add_column :nics, :broadcast, :string, :limit => 16 + add_column :nics, :boot_type_id, :integer + add_column :bondings, :ip_addr, :string, :limit => 16 + add_column :bondings, :netmask, :string, :limit => 16 + add_column :bondings, :broadcast, :string, :limit => 16 + add_column :bondings, :boot_type_id, :integer + + execute "alter table nics add constraint + fk_nic_boot_types + foreign key (boot_type_id) references + boot_types(id)" + execute "alter table bondings add constraint + fk_bonding_boot_types + foreign key (boot_type_id) references + boot_types(id)" + + ############################################################## + # attempt to migrate ip information back into nics table. + # because a nic can have multiple ips (if statically + # assigned) as well as its network, just use the 1st + # found + Nic.find(:all).each do |nic| + if nic.physical_network.ip_addresses.size > 0 + # use the 1st configured network ip + nic.ip_addr = nic.physical_network.ip_addresses[0].address + nic.netmask = nic.physical_network.ip_addresses[0].netmask + nic.broadcast = nic.physical_network.ip_addresses[0].broadcast + end + + if nic.ip_addresses.size > 0 + # use the 1st assigned static ip + nic.ip_addr = nic.ip_addresses[0].address + end + + nic.boot_type_id = nic.physical_network.boot_type_id + + nic.save! + end + + # attempt to migrate ip information back into bondings table. + # because a bonding can have multiple ips (if statically + # assigned) as well as its network, just use the 1st + # found + Bonding.find(:all).each do |bonding| + if bonding.vlan.ip_addresses.size > 0 + # use the 1st configured network ip + bonding.ip_addr = bonding.vlan.ip_addresses[0].address + bonding.netmask = bonding.vlan.ip_addresses[0].netmask + bonding.broadcast = bonding.vlan.ip_addresses[0].broadcast + end + + if bonding.ip_addresses.size > 0 + # use the 1st assigned static ip + bonding.ip_addr = bonding.ip_addresses[0].address + end + + bonding.boot_type_id = bonding.vlan.boot_type_id + + bonding.save! + end + + ############################################################## + # drop ip_addresses table + drop_table :ip_addresses + + # drop network ids from nics / bondings table + remove_column :nics, :physical_network_id + remove_column :bondings, :vlan_id + + # drop networks tables + drop_table :networks_usages + drop_table :usages + drop_table :networks + + ############################################################## + # undo bugfix above + add_column :bondings_nics, :id, :integer + end +end diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js index 4579c80..d84b3d9 100644 --- a/src/public/javascripts/ovirt.js +++ b/src/public/javascripts/ovirt.js @@ -297,4 +297,23 @@ function delete_pool(delete_url, id) $.jGrowl(data.alert); } }, 'json'); -} \ No newline at end of file +} + + +function get_selected_networks() +{ + return get_selected_checkboxes("networks_grid_form"); +} + +function afterNetwork(response, status){ + ajax_validation(response, status); + if (response.success) { + $(document).trigger('close.facebox'); + grid = $("#networks_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); + } else { + $tabs.tabs("load",$tabs.data('selected.tabs')); + } + } +} diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 16eaf62..228ff7b 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -267,4 +267,52 @@ .detail-pane-chart { height: 50px; width: 375px; -}; +} + + +/************************* + * new popup components + *************************/ +.popup-content-selection { + float: left; + width: 45%; + padding-left: 20px; + padding-top: 10px; +} + +.popup-content-selection select{ + min-width: 200px; +} + +.popup-content-footer{ + width: 99%; + float: left; +} + +.selected_popup_content { + padding-left: 20px; + min-height: 50px; + float: left; + width: 96%; +} + +#selected_popup_content_header { + padding-bottom: 5px; + font-weight: bold; +} + +#selected_popup_content_expanded{ + padding-bottom: 50px; +} + +.selected_popup_content_left { + float: left; + width: 40%; + padding-bottom: 15px; +} + +.selected_popup_content_right { + float: left; + width: 40%; + padding-bottom: 15px; +} -- 1.5.6.5 From sseago at redhat.com Fri Oct 31 21:47:23 2008 From: sseago at redhat.com (Scott Seago) Date: Fri, 31 Oct 2008 17:47:23 -0400 Subject: [Ovirt-devel] Re: [PATCH server] network integration into ovirt server db and wui In-Reply-To: <490B30A6.70406@redhat.com> References: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> <490B25C8.1020509@redhat.com> <20081031160305.GH30553@redhat.com> <490B30A6.70406@redhat.com> Message-ID: <490B7CEB.2080302@redhat.com> Testing the revised patch now. Mohammed Morsi wrote: > Hugh O. Brock wrote: > >> On Fri, Oct 31, 2008 at 11:35:36AM -0400, Mohammed Morsi wrote: >> >> >>> 1. Remove 'validate' method, replace w/ 'validates_presence_of', >>> 'validate_format_of' where we can >>> >>> > I can take care of this bit > > OK this is done. >>> 2. Change ManageNodeConfiguration bit, to use new network model / schema >>> >>> > The only thing this depends on is the model changes I did, which was > based on http://www.ovirt.org/page/Redesigned_Network_Configuration. If > someone who knows this bit more that I do can take care of it, it would > speed things up > > I'm not sure what the state of this is, and I'm not sure how to test it. If there's anything here that must be in the release, hopefully dpierce can confirm. >>> 3. Disable type selection for 'edit network' form >>> >>> > Small, quick fix that I can take care of > > This is done. >>> 5. Add ip address information to the network details pane >>> 6. Add nic / bonding information to host details pane >>> >>> > Both these items should be fairly straightforward and I can take care of. > > Basic summary of IP address is showing (i.e. the actual IP address) but the other metadata isn't there. The only way you can see it is the edit form -- but someone without edit rights wouldn't be able to do that. I think this is good enough for this patch and release, but we need to go back and make all of the data available in some sort of view page -- either via a flexigrid on the details pane or via a 'details' dialog. For 6) -- I'm not sure if NIC is showing up properly, as my current DB doesn't have any NICs for hosts, but in addition to just the name we need a way to show all the metadata. For the bonding -- I can't create a bonding w/ a static IP VLAN. for dhcp it works. However, the summary pane isn't giving me anything useful for the bonding metadata. it shows: '71bcc3d3-bc81-fe8d-bf93-e2fa9814ba54' instead of the name. And the additional metadata isn't available. The points that need to be fixed before pushing here are 1) static IP vlan; 2) bonding field in details pane needs to show the proper name. The other parts can be fixed later. > >>> 8. Add usages multi-select box to create / edit network form >>> >>> > Another straightforward item I can take care of > > Usage is in the form -- it's not showing up in details -- another one for post-release if there are no objections here. so not yet ACK -- we need to fix: 1) ManagedNodeConfiguration bit -- I'm not sure about this one -- but my concern is without this fix networking will be completely broken on the nodes/VMs 2) static IP VLAN for bondings 3) host details pane needs to show the bonding name in the 'bonding' section. Scott From apevec at redhat.com Fri Oct 31 22:35:49 2008 From: apevec at redhat.com (Alan Pevec) Date: Fri, 31 Oct 2008 23:35:49 +0100 Subject: [Ovirt-devel] [PATCH recipe] Add new rubygem-qpid package to the wui. In-Reply-To: <1225477828-3304-1-git-send-email-imain@redhat.com> References: <1225477828-3304-1-git-send-email-imain@redhat.com> Message-ID: <490B8845.5060405@redhat.com> > +Requires: rubygem-qpid Fedora Review BZ for this package please :) From lutter at redhat.com Fri Oct 31 23:01:29 2008 From: lutter at redhat.com (David Lutterkort) Date: Fri, 31 Oct 2008 16:01:29 -0700 Subject: [Ovirt-devel] [PATCH recipe] Add new rubygem-qpid package to the wui. In-Reply-To: <490B8845.5060405@redhat.com> References: <1225477828-3304-1-git-send-email-imain@redhat.com> <490B8845.5060405@redhat.com> Message-ID: <1225494089.10508.21.camel@localhost.localdomain> On Fri, 2008-10-31 at 23:35 +0100, Alan Pevec wrote: > > +Requires: rubygem-qpid > > Fedora Review BZ for this package please :) A catch-22: we haven't handed the ruby/qpid code over to the Qpid team yet, since we first wanted to make sure it works well for ovirt's need (right now, a bugfix is a commit to our internal git repo, after the handoff we'll have to send in patches and wait for them to show up in Qpid's svn, and for them to make a release) OTOH, we don't want to own that package, i.e. the review request should come from the Qpid folks. Can we live with this situation for a few days until Ian has used it enough to make sure we don't have terribly obvious bugs in the client ? David From mmorsi at redhat.com Fri Oct 31 23:51:53 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 31 Oct 2008 19:51:53 -0400 Subject: [Ovirt-devel] [PATCH server] network integration into ovirt server db and wui Message-ID: <1225497113-5039-1-git-send-email-mmorsi@redhat.com> --- src/app/controllers/host_controller.rb | 14 +- src/app/controllers/network_controller.rb | 419 ++++++++++++++++++++ src/app/helpers/application_helper.rb | 28 +- src/app/helpers/network_helper.rb | 2 + src/app/models/bonding.rb | 30 +- src/app/models/ip_address.rb | 27 ++ src/app/models/ip_v4_address.rb | 53 +++ src/app/models/ip_v6_address.rb | 46 +++ src/app/models/network.rb | 32 ++ src/app/models/nic.rb | 21 +- src/app/models/physical_network.rb | 21 + src/app/models/usage.rb | 21 + src/app/models/vlan.rb | 24 ++ src/app/views/dashboard/index.html.erb | 7 + src/app/views/host/edit_network.rhtml | 85 ++++ src/app/views/host/show.rhtml | 30 +- src/app/views/network/_bonding_form.rhtml | 46 +++ src/app/views/network/_form.rhtml | 46 +++ src/app/views/network/_grid.rhtml | 37 ++ src/app/views/network/_ip_address_form.rhtml | 62 +++ src/app/views/network/_ip_addresses_form.rhtml | 50 +++ src/app/views/network/_select.rhtml | 32 ++ src/app/views/network/edit.rhtml | 34 ++ src/app/views/network/edit_bonding.rhtml | 60 +++ src/app/views/network/edit_ip_address.rhtml | 76 ++++ .../views/network/edit_network_ip_addresses.rhtml | 13 + src/app/views/network/edit_nic.rhtml | 62 +++ src/app/views/network/list.html.erb | 72 ++++ src/app/views/network/new.rhtml | 28 ++ src/app/views/network/new_bonding.rhtml | 36 ++ src/app/views/network/new_ip_address.rhtml | 44 ++ src/app/views/network/show.rhtml | 31 ++ src/app/views/nic/_list.rhtml | 2 - src/db/migrate/028_refactor_networking_model.rb | 271 +++++++++++++ src/public/javascripts/ovirt.js | 21 +- src/public/stylesheets/components.css | 50 +++- 36 files changed, 1894 insertions(+), 39 deletions(-) create mode 100644 src/app/controllers/network_controller.rb create mode 100644 src/app/helpers/network_helper.rb create mode 100644 src/app/models/ip_address.rb create mode 100644 src/app/models/ip_v4_address.rb create mode 100644 src/app/models/ip_v6_address.rb create mode 100644 src/app/models/network.rb create mode 100644 src/app/models/physical_network.rb create mode 100644 src/app/models/usage.rb create mode 100644 src/app/models/vlan.rb create mode 100644 src/app/views/host/edit_network.rhtml create mode 100644 src/app/views/network/_bonding_form.rhtml create mode 100644 src/app/views/network/_form.rhtml create mode 100644 src/app/views/network/_grid.rhtml create mode 100644 src/app/views/network/_ip_address_form.rhtml create mode 100644 src/app/views/network/_ip_addresses_form.rhtml create mode 100644 src/app/views/network/_select.rhtml create mode 100644 src/app/views/network/edit.rhtml create mode 100644 src/app/views/network/edit_bonding.rhtml create mode 100644 src/app/views/network/edit_ip_address.rhtml create mode 100644 src/app/views/network/edit_network_ip_addresses.rhtml create mode 100644 src/app/views/network/edit_nic.rhtml create mode 100644 src/app/views/network/list.html.erb create mode 100644 src/app/views/network/new.rhtml create mode 100644 src/app/views/network/new_bonding.rhtml create mode 100644 src/app/views/network/new_ip_address.rhtml create mode 100644 src/app/views/network/show.rhtml create mode 100644 src/db/migrate/028_refactor_networking_model.rb diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb index a40d297..f961d1a 100644 --- a/src/app/controllers/host_controller.rb +++ b/src/app/controllers/host_controller.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -30,7 +30,7 @@ class HostController < ApplicationController end end - before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms] + before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms, :edit_network] # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => [:post, :put], :only => [ :create, :update ], @@ -81,7 +81,7 @@ class HostController < ApplicationController def addhost @hardware_pool = Pool.find(params[:hardware_pool_id]) - render :layout => 'popup' + render :layout => 'popup' end def add_to_smart_pool @@ -156,6 +156,14 @@ class HostController < ApplicationController render :json => @json_hash end + def edit_network + render :layout => 'popup' + end + + def bondings_json + bondings = Host.find(params[:id]).bondings + render :json => bondings.collect{ |x| {:id => x.id, :name => x.name} } + end private #filter methods diff --git a/src/app/controllers/network_controller.rb b/src/app/controllers/network_controller.rb new file mode 100644 index 0000000..725e60d --- /dev/null +++ b/src/app/controllers/network_controller.rb @@ -0,0 +1,419 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. +# + +class NetworkController < ApplicationController + ########################## Networks related actions + + def network_permissions + # TODO more robust permission system + # either by subclassing network from pool + # or by extending permission model to accomodate + # any object + @default_pool = HardwarePool.get_default_pool + set_perms(@default_pool) + unless @can_modify + flash[:notice] = 'You do not have permission to view networks' + redirect_to :controller => 'dashboard' + end + end + + def list + @networks = Network.find(:all) + network_permissions + end + + def networks_json + json_list(Network.find(:all), [:id, :name, :type, [:boot_type, :label]]) + end + + def show + @network = Network.find(params[:id]) + network_permissions + respond_to do |format| + format.html { render :layout => 'selection' } + format.xml { render :xml => @network.to_xml } + end + end + + def new + @boot_types = BootType.find(:all) + @usage_types = Usage.find(:all) + render :layout => 'popup' + end + + def create + begin + @network = PhysicalNetwork.new(params[:network]) if params[:network][:type] == 'PhysicalNetwork' + @network = Vlan.new(params[:network]) if params[:network][:type] == 'Vlan' + @network.save! + alert = "Network was successfully created." + render :json => { :object => "network", :success => true, + :alert => alert } + rescue + render :json => { :object => "network", :success => false, + :errors => + @network.errors.localize_error_messages.to_a } + end + end + + def edit + @network = Network.find(params[:id]) + @usage_types = Usage.find(:all) + @boot_types = BootType.find(:all) + render :layout => 'popup' + end + + def update + begin + @network = Network.find(params[:id]) + if @network.type == 'PhysicalNetwork' + @network = PhysicalNetwork.find(params[:id]) + else + @network = Vlan.find(params[:id]) + end + + @network.usages.delete_all + @network.update_attributes!(params[:network]) + + alert = "Network was successfully updated." + render :json => { :object => "network", :success => true, + :alert => alert } + rescue Exception => e + render :json => { :object => "network", :success => false, + :errors => + @network.errors.localize_error_messages.to_a } + end + end + + def delete + failed_networks = [] + networks_ids_str = params[:network_ids] + network_ids = networks_ids_str.split(",").collect {|x| x.to_i} + network_ids.each{ |x| + network = Network.find(x) + unless network.type.to_s == 'Vlan' && + Vlan.find(x).bondings.size != 0 || + network.type.to_s == 'PhysicalNetwork' && + PhysicalNetwork.find(x).nics.size != 0 + begin + Network.destroy(x) + rescue + failed_networks.push x + end + else + failed_networks.push x + end + } + if failed_networks.size == 0 + render :json => { :object => "network", + :success => true, + :alert => "Successfully deleted networks" } + else + render :json => { :object => "network", + :success => false, + :alert => "Failed deleting " + + failed_networks.size.to_s + + " networks due to existing bondings/nics" } + end + end + + def edit_network_ip_addresses + @network = Network.find(params[:id]) + render :layout => 'popup' + end + + + ########################## Ip Address related actions + + def ip_addresses_json + @parent_type = params[:parent_type] + if @parent_type == 'network' + ip_addresses = Network.find(params[:id]).ip_addresses + elsif @parent_type == 'nic' + ip_addresses = Nic.find(params[:id]).ip_addresses + elsif @parent_type == 'bonding' and params[:id] + ip_addresses = Bonding.find(params[:id]).ip_addresses + else + ip_addresses = [] + end + + ip_addresses_json = [] + ip_addresses.each{ |x| + ip_addresses_json.push({:id => x.id, :name => x.address}) } + render :json => ip_addresses_json + end + + def new_ip_address + @parent_type = params[:parent_type] + @network = Network.find(params[:id]) if @parent_type == 'network' + @nic = Nic.find(params[:id]) if @parent_type == 'nic' + @bonding = Bonding.find(params[:id]) if @parent_type == 'bonding' and params[:id] + + render :layout => false + end + + def _create_ip_address + if params[:ip_address][:type] == "IpV4Address" + @ip_address = IpV4Address.new(params[:ip_address]) + else + @ip_address = IpV6Address.new(params[:ip_address]) + end + @ip_address.save! + end + + def create_ip_address + begin + _create_ip_address + alert = "Ip Address was successfully created." + render :json => { :object => "ip_address", :success => true, + :alert => alert } + rescue + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a } + end + end + + def edit_ip_address + @ip_address = IpAddress.find(params[:id]) + + @parent_type = params[:parent_type] + @network = @ip_address.network if @ip_address.network_id + @nic = @ip_address.nic if @ip_address.nic_id + @bonding = @ip_address.bonding if @ip_address.bonding_id + + render :layout => false + end + + def _update_ip_address(id) + @ip_address = IpAddress.find(id) + + # special case if we are switching types + if @ip_address.type != params[:ip_address][:type] + if params[:ip_address][:type] == 'IpV4Address' + @ip_address = IpV4Address.new(params[:ip_address]) + @ip_address.save! + IpV6Address.delete(id) + else + @ip_address = IpV6Address.new(params[:ip_address]) + @ip_address.save! + IpV4Address.delete(id) + end + else + if @ip_address.type == 'IpV4Address' + @ip_address = IpV4Address.find(id) + else + @ip_address = IpV6Address.find(id) + end + @ip_address.update_attributes!(params[:ip_address]) + end + + end + + def update_ip_address + begin + _update_ip_address(params[:id]) + alert = "IpAddress was successfully updated." + render :json => { :object => "network", :success => true, + :alert => alert } + rescue + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a } + end + end + + def destroy_ip_address + begin + IpAddress.delete(params[:id]) + alert = "Ip Address was successfully deleted." + render :json => { :object => "ip_address", :success => true, + :alert => alert } + rescue + render :json => { :object => "ip_address", :success => false, + :alert => 'Ip Address Deletion Failed' } + end + end + + + ########################## NICs related actions + + def edit_nic + @nic = Nic.find(params[:id]) + @network = @nic.physical_network + + @networks = PhysicalNetwork.find(:all) + network_options + + render :layout => false + end + + def update_nic + begin + network_options + @network = Network.find(params[:nic][:physical_network_id]) + + if @network.boot_type.id == @static_boot_type.id + if params[:ip_address][:id] == "New" + _create_ip_address + elsif params[:ip_address][:id] != "" + _update_ip_address(params[:ip_address][:id]) + end + end + + @nic = Nic.find(params[:id]) + @nic.update_attributes!(params[:nic]) + + alert = "Nic was successfully updated." + render :json => { :object => "nic", :success => true, + :alert => alert } + rescue Exception => e + if @ip_address and @ip_address.errors.size != 0 + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a} + else + render :json => { :object => "nic", :success => false, + :errors => + @nic.errors.localize_error_messages.to_a } + end + end + end + + ########################## Bonding related actions + + def new_bonding + unless params[:host_id] + flash[:notice] = "Host is required." + redirect_to :controller => 'dashboard' + end + + @host = Host.find(params[:host_id]) + @networks = Vlan.find(:all) + network_options + + render :layout => false + end + + def create_bonding + begin + network_options + @network = Network.find(params[:bonding][:vlan_id]) + + if @network.boot_type.id == @static_boot_type.id + if params[:ip_address][:id] == "New" + _create_ip_address + elsif params[:ip_address][:id] != "" + _update_ip_address(params[:ip_address][:id]) + end + end + + @bonding = Bonding.new(params[:bonding]) + @bonding.ip_addresses << @ip_address if @ip_address + @bonding.save! + + if @ip_address + @ip_address.bonding_id = @bonding.id + @ip_address.save! + end + + alert = "Bonding was successfully created." + render :json => { :object => "bonding", :success => true, + :alert => alert } + rescue + if @ip_address and @ip_address.errors.size != 0 + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a} + else + render :json => { :object => "bonding", :success => false, + :errors => + @bonding.errors.localize_error_messages.to_a } + end + end + end + + def edit_bonding + @bonding = Bonding.find(params[:id]) + @network = @bonding.vlan + + @host = @bonding.host + @networks = Vlan.find(:all) + network_options + + render :layout => false + end + + def update_bonding + begin + network_options + @network = Network.find(params[:bonding][:vlan_id]) + + if @network.boot_type.id == @static_boot_type.id + if params[:ip_address][:id] == "New" + _create_ip_address + elsif params[:ip_address][:id] != "" + _update_ip_address(params[:ip_address][:id]) + end + end + + @bonding = Bonding.find(params[:id]) + @bonding.nics.delete_all + @bonding.update_attributes!(params[:bonding]) + + alert = "Bonding was successfully updated." + render :json => { :object => "bonding", :success => true, + :alert => alert } + rescue + if @ip_address and @ip_address.errors.size != 0 + render :json => { :object => "ip_address", :success => false, + :errors => + @ip_address.errors.localize_error_messages.to_a} + else + render :json => { :object => "bonding", :success => false, + :errors => + @bonding.errors.localize_error_messages.to_a } + end + end + end + + def destroy_bonding + begin + Bonding.destroy(params[:id]) + alert = "Bonding was successfully deleted." + render :json => { :object => "bonding", :success => true, + :alert => alert } + rescue + render :json => { :object => "bonding", :success => false, + :alert => 'Bonding Deletion Failed' } + end + + end + + + ########################## Misc methods + + protected + def network_options + @bonding_types = BondingType.find(:all) + @static_boot_type = BootType.find(:first, + :conditions => { :proto => 'static' }) + end + +end diff --git a/src/app/helpers/application_helper.rb b/src/app/helpers/application_helper.rb index d7b6628..0178ad0 100644 --- a/src/app/helpers/application_helper.rb +++ b/src/app/helpers/application_helper.rb @@ -84,21 +84,31 @@ module ApplicationHelper } end - def popup_footer(action, label) - %{ -
        + # expects hash of labels => actions + def multi_button_popup_footer(buttons = {}) + buttons_html = "" + buttons.each{ |label, action| + buttons_html+="
        " + + "
        " + + " " + + "
        " + + "
        " + } + + %{ +
        -
        -
        - -
        -
        + #{buttons_html}
        - } + } + end + + def popup_footer(action, label) + multi_button_popup_footer({label => action}) end def ok_footer diff --git a/src/app/helpers/network_helper.rb b/src/app/helpers/network_helper.rb new file mode 100644 index 0000000..ebbce0b --- /dev/null +++ b/src/app/helpers/network_helper.rb @@ -0,0 +1,2 @@ +module NetworkHelper +end diff --git a/src/app/models/bonding.rb b/src/app/models/bonding.rb index 006c261..c9af38c 100644 --- a/src/app/models/bonding.rb +++ b/src/app/models/bonding.rb @@ -30,6 +30,16 @@ # interface. They can be ignored if not used. # class Bonding < ActiveRecord::Base + belongs_to :host + belongs_to :bonding_type + belongs_to :vlan + has_many :ip_addresses, :dependent => :destroy + + has_and_belongs_to_many :nics, + :join_table => 'bondings_nics', + :foreign_key => :bonding_id + + validates_presence_of :name, :message => 'A name is required.' @@ -42,18 +52,20 @@ class Bonding < ActiveRecord::Base validates_presence_of :interface_name, :message => 'An interface name is required.' - validates_presence_of :boot_type_id, - :message => 'A boot type must be specified.' - validates_presence_of :bonding_type_id, :message => 'A bonding type must be specified.' - belongs_to :host - belongs_to :bonding_type - belongs_to :boot_type + validates_presence_of :vlan_id, + :message => 'A vlan must be specified.' + + protected + def validate + if vlan.boot_type.proto == 'static' and ip_addresses.size == 0 + errors.add("vlan_id", + "is static. Must create at least one static ip") + end + + end - has_and_belongs_to_many :nics, - :join_table => 'bondings_nics', - :foreign_key => :bonding_id end diff --git a/src/app/models/ip_address.rb b/src/app/models/ip_address.rb new file mode 100644 index 0000000..5d2e6af --- /dev/null +++ b/src/app/models/ip_address.rb @@ -0,0 +1,27 @@ +# ip_address.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. + +# +IpAddress+ is the base class for all address related classes. +# +class IpAddress < ActiveRecord::Base + # one of these 3 will apply for each address + belongs_to :network + belongs_to :nic + belongs_to :bonding +end diff --git a/src/app/models/ip_v4_address.rb b/src/app/models/ip_v4_address.rb new file mode 100644 index 0000000..f3128a4 --- /dev/null +++ b/src/app/models/ip_v4_address.rb @@ -0,0 +1,53 @@ +# ip_v4_address.rb +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Darryl L. Pierce , +# Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# +IpV4Address+ represents a single IPv4 address. +# +class IpV4Address < IpAddress + ADDRESS_TEST = %r{^(\d{1,3}\.){3}\d{1,3}$} + + validates_presence_of :address, + :message => 'An address must be supplied.' + validates_format_of :address, + :with => ADDRESS_TEST + + validates_presence_of :netmask, + :message => 'A netmask must be supplied.', + :if => Proc.new { |ip| ip.network_id != nil } + validates_format_of :netmask, + :with => ADDRESS_TEST, + :if => Proc.new { |ip| ip.network_id != nil } + + validates_presence_of :gateway, + :message => 'A gateway address must be supplied.', + :if => Proc.new { |ip| ip.network_id != nil } + validates_format_of :gateway, + :with => ADDRESS_TEST, + :if => Proc.new { |ip| ip.network_id != nil } + + validates_presence_of :broadcast, + :message => 'A broadcast address must be supplied.', + :if => Proc.new { |ip| ip.network_id != nil } + validates_format_of :broadcast, + :with => ADDRESS_TEST, + :if => Proc.new { |ip| ip.network_id != nil } + +end diff --git a/src/app/models/ip_v6_address.rb b/src/app/models/ip_v6_address.rb new file mode 100644 index 0000000..11951cc --- /dev/null +++ b/src/app/models/ip_v6_address.rb @@ -0,0 +1,46 @@ +# ip_v6_address.rb +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Darryl L. Pierce , +# Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# +IpV6Address+ represents a single IPv6 address. +# +class IpV6Address < IpAddress + ADDRESS_TEST = %r{^([0-9a-fA-F]{0,4}|0)(\:([0-9a-fA-F]{0,4}|0)){7}$} + + validates_presence_of :address, + :message => 'An address must be provided.' + validates_format_of :address, + :with => ADDRESS_TEST + + validates_presence_of :gateway, + :message => 'A gateway address must be provided.', + :if => Proc.new { |ip| ip.network_id != nil } + validates_format_of :gateway, + :with => ADDRESS_TEST, + :if => Proc.new { |ip| ip.network_id != nil } + + validates_presence_of :prefix, + :message => 'A prefix must be provided.', + :if => Proc.new { |ip| ip.network_id != nil } + validates_format_of :prefix, + :with => ADDRESS_TEST, + :if => Proc.new { |ip| ip.network_id != nil } + +end diff --git a/src/app/models/network.rb b/src/app/models/network.rb new file mode 100644 index 0000000..404633d --- /dev/null +++ b/src/app/models/network.rb @@ -0,0 +1,32 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class Network < ActiveRecord::Base + belongs_to :boot_type + has_many :ip_addresses, :dependent => :destroy + + has_and_belongs_to_many :usages, :join_table => 'networks_usages' + + validates_presence_of :type, + :message => 'A type must be specified.' + validates_presence_of :name, + :message => 'A name must be specified.' + validates_presence_of :boot_type_id, + :message => 'A boot type must be specified.' + +end diff --git a/src/app/models/nic.rb b/src/app/models/nic.rb index baf7095..5649763 100644 --- a/src/app/models/nic.rb +++ b/src/app/models/nic.rb @@ -1,4 +1,4 @@ -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Scott Seago # @@ -19,7 +19,22 @@ class Nic < ActiveRecord::Base belongs_to :host - belongs_to :boot_type + belongs_to :physical_network + has_many :ip_addresses, :dependent => :destroy + + has_and_belongs_to_many :bondings, :join_table => 'bondings_nics' + + validates_presence_of :host_id, + :message => 'A host must be specified.' + + validates_presence_of :physical_network_id, + :message => 'A network must be specified.' - has_and_belongs_to_many :bonding, :join_table => 'bondings_nics' + protected + def validate + if physical_network.boot_type.proto == 'static' and ip_addresses.size == 0 + errors.add("physical_network_id", + "is static. Must create at least one static ip") + end + end end diff --git a/src/app/models/physical_network.rb b/src/app/models/physical_network.rb new file mode 100644 index 0000000..6923e40 --- /dev/null +++ b/src/app/models/physical_network.rb @@ -0,0 +1,21 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class PhysicalNetwork < Network + has_many :nics +end diff --git a/src/app/models/usage.rb b/src/app/models/usage.rb new file mode 100644 index 0000000..353e8f4 --- /dev/null +++ b/src/app/models/usage.rb @@ -0,0 +1,21 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class Usage < ActiveRecord::Base + has_and_belongs_to_many :networks, :join_table => 'networks_usages' +end diff --git a/src/app/models/vlan.rb b/src/app/models/vlan.rb new file mode 100644 index 0000000..f7889f4 --- /dev/null +++ b/src/app/models/vlan.rb @@ -0,0 +1,24 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class Vlan < Network + has_many :bondings + + validates_presence_of :number, + :message => 'A number must be specified.' +end diff --git a/src/app/views/dashboard/index.html.erb b/src/app/views/dashboard/index.html.erb index 92cb4da..8815ebc 100644 --- a/src/app/views/dashboard/index.html.erb +++ b/src/app/views/dashboard/index.html.erb @@ -13,6 +13,13 @@
        IP MAC Bridge Usage Type
        <%= link_to nic.ip_addr, { :controller => "nic", :action => 'show', :id => nic }, { :class => "show" } %> <%= nic.mac %> <%= nic.bridge %> <%= nic.usage_type %>
        + <% if @can_modify %> +

        Networks

        + + View / Edit + + <% end %> + diff --git a/src/app/views/host/edit_network.rhtml b/src/app/views/host/edit_network.rhtml new file mode 100644 index 0000000..7ec3180 --- /dev/null +++ b/src/app/views/host/edit_network.rhtml @@ -0,0 +1,85 @@ +<%- content_for :title do -%> + Edit <%= @host.hostname %> Network Devices +<%- end -%> + +<%- content_for :description do -%> + Select and edit nics and bonded interfaces on <%= @host.hostname %> +<%- end -%> + + + + + +
        + +
        + + + + diff --git a/src/app/views/host/show.rhtml b/src/app/views/host/show.rhtml index d1b05ad..b671578 100644 --- a/src/app/views/host/show.rhtml +++ b/src/app/views/host/show.rhtml @@ -6,17 +6,21 @@ <%if @host.disabled? -%> <%= image_tag "icon_start.png" %> Enable Host - + <% else -%> <%= image_tag "icon_suspend.png" %> Disable Host - + <% end -%> <%if @host.is_clear_task_valid? -%> <%= image_tag "icon_x.png" %> Clear VMs - + <% end -%> + <%= link_to image_tag("icon_edit.png") +"Edit Network", + {:controller => 'host', + :action => 'edit_network', :id => @host.id}, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> <%- end -%> <%- end -%> diff --git a/src/app/views/network/_grid.rhtml b/src/app/views/network/_grid.rhtml new file mode 100644 index 0000000..6af0c05 --- /dev/null +++ b/src/app/views/network/_grid.rhtml @@ -0,0 +1,37 @@ +<% networks_per_page = 40 unless (defined? networks_per_page) and !(networks_per_page.nil?) %> +<% usepager = @networks.size > networks_per_page %> + +
        +
        + +
        +
        + + + diff --git a/src/app/views/network/_ip_address_form.rhtml b/src/app/views/network/_ip_address_form.rhtml new file mode 100644 index 0000000..b952807 --- /dev/null +++ b/src/app/views/network/_ip_address_form.rhtml @@ -0,0 +1,62 @@ +<%= error_messages_for 'ip_address' %> + +
        + <%= hidden_field_tag 'parent_type', @parent_type%> + <%= hidden_field_tag 'ip_address[id]', @ip_address.id if @ip_address%> + <%= hidden_field_tag 'ip_address[network_id]', @network.id if @network %> + <%= hidden_field_tag 'ip_address[nic_id]', @nic.id if @nic %> + <%= hidden_field_tag 'ip_address[bonding_id]', @bonding.id if @bonding %> + +
        Type:
        +
        + <%= select_with_label "", "ip_address", "type", + [[ "ipv4", "IpV4Address" ], [ "ipv6", "IpV6Address" ] ] %> +
        + + +
        +
        IP Address
        +
        + <%= text_field_with_label "", "ip_address", "address" %> +
        +
        + +
        +
        Netmask
        +
        + <%= text_field_with_label "", "ip_address", "netmask" %> +
        + +
        Broadcast
        +
        + <%= text_field_with_label "", "ip_address", "broadcast" %> +
        +
        + + + +
        +
        Gateway
        +
        + <%= text_field_with_label "", "ip_address", "gateway" %> +
        +
        + +
        + + diff --git a/src/app/views/network/_ip_addresses_form.rhtml b/src/app/views/network/_ip_addresses_form.rhtml new file mode 100644 index 0000000..f833b2a --- /dev/null +++ b/src/app/views/network/_ip_addresses_form.rhtml @@ -0,0 +1,50 @@ + + +
        + + + diff --git a/src/app/views/network/_select.rhtml b/src/app/views/network/_select.rhtml new file mode 100644 index 0000000..e69d9b0 --- /dev/null +++ b/src/app/views/network/_select.rhtml @@ -0,0 +1,32 @@ +<% target = 'nic' unless target + network_id = 'physical_network_id' if target == 'nic' + network_id = 'vlan_id' if target == 'bonding' + + %> + +
        Network:
        +
        + <%= select_with_label "", target, network_id, + @networks.collect { |n| [n.name + ' - ' + n.boot_type.label, n.id ] } %> + +
        + + diff --git a/src/app/views/network/edit.rhtml b/src/app/views/network/edit.rhtml new file mode 100644 index 0000000..6360b3f --- /dev/null +++ b/src/app/views/network/edit.rhtml @@ -0,0 +1,34 @@ +<%- content_for :title do -%> + Edit Network +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + + +
        +
        + <%= hidden_field_tag 'id', @network.id %> + <%= render :partial => 'form', :locals => { :create => false } %> +
        + + <%= popup_footer("$('#network_form').submit()", "Edit Network") %> +
        + + + + diff --git a/src/app/views/network/edit_bonding.rhtml b/src/app/views/network/edit_bonding.rhtml new file mode 100644 index 0000000..e7f3cda --- /dev/null +++ b/src/app/views/network/edit_bonding.rhtml @@ -0,0 +1,60 @@ +
        +
        + Editing Bonded Interface +
        + + <%= hidden_field_tag('id', @bonding.id) %> + <%= render :partial => 'bonding_form' %> + +
        + +
        + <%= hidden_field_tag('id', @bonding.id) %> +
        + +<%= multi_button_popup_footer({"Edit Bonding" => + "$('#edit_bonding_form').submit()", + "Delete Bonding" => + "$('#delete_bonding_form').submit()"}) %> + + diff --git a/src/app/views/network/edit_ip_address.rhtml b/src/app/views/network/edit_ip_address.rhtml new file mode 100644 index 0000000..60fe8f1 --- /dev/null +++ b/src/app/views/network/edit_ip_address.rhtml @@ -0,0 +1,76 @@ +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + Editing IP Address +
        + + <% if @parent_type != 'network' %> + Delete + <% end %> + + <%= hidden_field_tag('id', @ip_address.id) %> + <%= render :partial => 'ip_address_form' %> + +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + <%= hidden_field_tag('id', @ip_address.id) %> +
        + +<% if @parent_type == 'network' %> +<%= multi_button_popup_footer({" Edit IP Address" => + "$('#edit_ip_address_form').submit()", + "Delete IP Address" => + "$('#delete_ip_address_form').submit()"}) %> +<% end %> + + diff --git a/src/app/views/network/edit_network_ip_addresses.rhtml b/src/app/views/network/edit_network_ip_addresses.rhtml new file mode 100644 index 0000000..7a1e4cb --- /dev/null +++ b/src/app/views/network/edit_network_ip_addresses.rhtml @@ -0,0 +1,13 @@ +<%- content_for :title do -%> + Edit Network IP Addresses +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + +<%= render :partial => 'ip_addresses_form', + :locals => { :parent_type => 'network', + :parent_id => @network.id } %> + + diff --git a/src/app/views/network/edit_nic.rhtml b/src/app/views/network/edit_nic.rhtml new file mode 100644 index 0000000..75af6fb --- /dev/null +++ b/src/app/views/network/edit_nic.rhtml @@ -0,0 +1,62 @@ +
        +
        + Editing NIC +
        + + <%= error_messages_for 'nic' %> + +
        + <%= hidden_field_tag 'id', @nic.id %> + <%= hidden_field_tag 'nic_host_id', @nic.host.id %> + <%= hidden_field_tag 'nic_network_id', @nic.physical_network.id %> + +
        MAC:
        +
        <%= @nic.mac %>
        + + <% if @nic.host.bondings.size != 0 %> +
        Bonded Interfaces
        +
        + +
        + <% end %> + + + <%= render :partial => 'select' %> + +
        + <%= render :partial => 'ip_addresses_form', + :locals => { :parent_type => 'nic', + :parent_id => @nic.id } %> +
        + + +
        + <%= popup_footer("$('#nic_form').submit()", "Edit NIC") %> +
        + + diff --git a/src/app/views/network/list.html.erb b/src/app/views/network/list.html.erb new file mode 100644 index 0000000..9a304cf --- /dev/null +++ b/src/app/views/network/list.html.erb @@ -0,0 +1,72 @@ + + + + +
        +<% if @networks.size != 0 %> +
        + <%= render :partial => "grid", :locals => { :table_id => "networks_grid", + :networks => @networks, + :on_select => "networks_select"} %> +
        + +
        +
        +
        Select a network.
        +
        +
        +<% else %> +
        +
        + <%= image_tag 'no-grid-items.png', :style => 'float: left;' %> + +
        + No networks found.

        + <%= image_tag "icon_addhost.png", :style=>"vertical-align:middle;" %>   + Add first network +
        +
        +
        +<% end %> diff --git a/src/app/views/network/new.rhtml b/src/app/views/network/new.rhtml new file mode 100644 index 0000000..15b7304 --- /dev/null +++ b/src/app/views/network/new.rhtml @@ -0,0 +1,28 @@ +<%- content_for :title do -%> + Add Network +<%- end -%> +<%- content_for :description do -%> +<%- end -%> + + +
        +
        + <%= render :partial => 'form', :locals => { :create => true } %> +
        + + <%= popup_footer("$('#network_form').submit()", "Add Network") %> +
        + + + diff --git a/src/app/views/network/new_bonding.rhtml b/src/app/views/network/new_bonding.rhtml new file mode 100644 index 0000000..76d3b94 --- /dev/null +++ b/src/app/views/network/new_bonding.rhtml @@ -0,0 +1,36 @@ +
        + +
        + Create Bonded Interface +
        + + <%= render :partial => 'bonding_form' %> +
        + +<%= multi_button_popup_footer({"Create Bonding" => + "$('#bonding_form').submit()"}) %> + + diff --git a/src/app/views/network/new_ip_address.rhtml b/src/app/views/network/new_ip_address.rhtml new file mode 100644 index 0000000..df2a2bb --- /dev/null +++ b/src/app/views/network/new_ip_address.rhtml @@ -0,0 +1,44 @@ +<% if @parent_type == 'network' %> +
        +<% end %> + +
        + Create Ip Address +
        + + <%= render :partial => 'ip_address_form' %> + +<% if @parent_type == 'network' %> +
        +<% end %> + +<% if @parent_type == 'network' %> +<%= multi_button_popup_footer({"Create IP Address" => + "$('#ip_address_form').submit()"}) %> +<% end %> + + diff --git a/src/app/views/network/show.rhtml b/src/app/views/network/show.rhtml new file mode 100644 index 0000000..2ac68ea --- /dev/null +++ b/src/app/views/network/show.rhtml @@ -0,0 +1,31 @@ +<%- content_for :title do -%> + <%=h @network.name %> +<%- end -%> + +<%- content_for :action_links do -%> + <%= link_to image_tag("icon_edit.png") + "Edit", + {:action => 'edit', :id => @network.id }, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> + + <%= link_to image_tag("icon_edit.png") + "Edit IP Addresses", + {:action => 'edit_network_ip_addresses', :id => @network.id }, + :rel=>"facebox[.bolder]", :class=>"selection_facebox" %> +<%- end -%> + + +
        + Name:
        + Type:
        + Boot Type:
        + IP Addresses:
        +
        +
        + <%=h @network.name %>
        + <%=h @network.type %>
        + <%=h @network.boot_type.label %>
        + <%=@network.ip_addresses.collect{ |ip| + ip.address}.join("
        ") %> +
        + +<%- content_for :right do -%> +<%- end -%> diff --git a/src/app/views/nic/_list.rhtml b/src/app/views/nic/_list.rhtml index 07f7e44..eb3a4a7 100644 --- a/src/app/views/nic/_list.rhtml +++ b/src/app/views/nic/_list.rhtml @@ -1,7 +1,6 @@ - @@ -11,7 +10,6 @@ <% for nic in nics %> - diff --git a/src/db/migrate/028_refactor_networking_model.rb b/src/db/migrate/028_refactor_networking_model.rb new file mode 100644 index 0000000..946b976 --- /dev/null +++ b/src/db/migrate/028_refactor_networking_model.rb @@ -0,0 +1,271 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# introduce networks and ip_addresses tables, refactor relationships +class RefactorNetworkingModel < ActiveRecord::Migration + def self.up + + #################################################### + # bugfix, bridge tables shouldn't have their own ids + remove_column :bondings_nics, :id + + ################################################################## + # add networks, usages tables and networks_usage_types bridge + create_table :networks do |t| + t.string :type, :null => false + t.string :name, :null => false + t.integer :boot_type_id, :null => false + + # attributes for Vlan (type=Vlan) + t.integer :number + end + + create_table :usages do |t| + t.string :label, :null => false + t.string :usage, :null => false + end + + create_table :networks_usages, :id => false do |t| + t.integer :network_id, :null => false + t.integer :usage_id, :null => false + end + + add_index :networks_usages, [:network_id, :usage_id], :unique => true + + # create usages + Usage.create(:label => 'Guest', :usage => 'guest') + Usage.create(:label => 'Management', :usage => 'management') + Usage.create(:label => 'Storage', :usage => 'storage') + + # referential integrity for networks tables + execute "alter table networks add constraint + fk_network_boot_types + foreign key (boot_type_id) references + boot_types(id)" + execute "alter table networks_usages add constraint + fk_networks_usages_network_id + foreign key (network_id) references + networks(id)" + execute "alter table networks_usages add constraint + fk_networks_usages_usage_id + foreign key (usage_id) references + usages(id)" + + # add foreign keys to nics / bondings table + add_column :nics, :physical_network_id, :integer + add_column :bondings, :vlan_id, :integer + + # referential integrity for nic/bondings network ids + execute "alter table nics add constraint + fk_nic_networks + foreign key (physical_network_id) references + networks(id)" + execute "alter table bondings add constraint + fk_bonding_networks + foreign key (vlan_id) references + networks(id)" + + #################################################### + # create ip_addresses table + create_table :ip_addresses do |t| + t.string :type + + # foreign keys to associated entities + t.integer :nic_id + t.integer :bonding_id + t.integer :network_id + + # common attributes + t.string :address, :limit => 39, :null => false + t.string :gateway, :limit => 39 + + # attributes for IPv4 (type=IpV4Address) + t.string :netmask, :limit => 15 + t.string :broadcast, :limit => 15 + + # attributes for IPv6 (type=IpV6Address) + t.string :prefix, :limit => 39 + t.timestamps + end + + # referential integrity for ip_addresses table + execute "alter table ip_addresses add constraint + fk_nic_ip_addresses + foreign key (nic_id) references nics(id)" + execute "alter table ip_addresses add constraint + fk_bonding_ip_addresses + foreign key (bonding_id) references bondings(id)" + execute "alter table ip_addresses add constraint + fk_network_ip_addresses + foreign key (network_id) references networks(id)" + + ################################################################### + static_boot_type_id = + BootType.find(:first, + :conditions => {:proto => 'static'} ).id + + # migrate nic ip_addresses to networks / ip_addresses table + i = 0 + Nic.find(:all).each do |nic| + if nic.boot_type_id == static_boot_type_id + IpV4Address.new(:nic_id => nic.id, + :address => nic.ip_addr).save! + + end + network = PhysicalNetwork.new( + :name => 'Physical Network ' + i.to_s, + :boot_type_id => nic.boot_type_id) + network.save! + + ip_address = IpV4Address.new(:address => nic.ip_addr ? nic.ip_addr : '0.0.0.0', + :netmask => nic.netmask, + :broadcast => nic.broadcast, + :gateway => nic.ip_addr) + ip_address.network = network + ip_address.save! + + nic.physical_network = network + nic.save! + + i += 1 + end + + # migrate bonding ip_addresses to networks / ip_addresses table + i = 0 + Bonding.find(:all).each do |bonding| + if bonding.boot_type_id == static_boot_type_id + IpV4Address.new(:bonding_id => bonding.id, + :address => bonding.ip_addr).save! + end + network = Vlan.new( + :name => 'VLAN ' + i.to_s, + :number => i, + :boot_type_id => bonding.boot_type_id) + network.save! + + ip_address = IpV4Address.new(:address => bonding.ip_addr ? bonding.ip_addr : '0.0.0.0', + :netmask => bonding.netmask, + :broadcast => bonding.broadcast, + :gateway => bonding.ip_addr) + ip_address.network = network + ip_address.save! + + bonding.vlan = network + bonding.save! + + i += 1 + end + + ############################################################## + # remove nics / bonding ip address and network related columns + remove_column :nics, :ip_addr + remove_column :nics, :netmask + remove_column :nics, :broadcast + remove_column :nics, :boot_type_id + remove_column :bondings, :ip_addr + remove_column :bondings, :netmask + remove_column :bondings, :broadcast + remove_column :bondings, :boot_type_id + + + end + + def self.down + ############################################################## + # readd nics / bonding ip address related columns + add_column :nics, :ip_addr, :string, :limit => 16 + add_column :nics, :netmask, :string, :limit => 16 + add_column :nics, :broadcast, :string, :limit => 16 + add_column :nics, :boot_type_id, :integer + add_column :bondings, :ip_addr, :string, :limit => 16 + add_column :bondings, :netmask, :string, :limit => 16 + add_column :bondings, :broadcast, :string, :limit => 16 + add_column :bondings, :boot_type_id, :integer + + execute "alter table nics add constraint + fk_nic_boot_types + foreign key (boot_type_id) references + boot_types(id)" + execute "alter table bondings add constraint + fk_bonding_boot_types + foreign key (boot_type_id) references + boot_types(id)" + + ############################################################## + # attempt to migrate ip information back into nics table. + # because a nic can have multiple ips (if statically + # assigned) as well as its network, just use the 1st + # found + Nic.find(:all).each do |nic| + if nic.physical_network.ip_addresses.size > 0 + # use the 1st configured network ip + nic.ip_addr = nic.physical_network.ip_addresses[0].address + nic.netmask = nic.physical_network.ip_addresses[0].netmask + nic.broadcast = nic.physical_network.ip_addresses[0].broadcast + end + + if nic.ip_addresses.size > 0 + # use the 1st assigned static ip + nic.ip_addr = nic.ip_addresses[0].address + end + + nic.boot_type_id = nic.physical_network.boot_type_id + + nic.save! + end + + # attempt to migrate ip information back into bondings table. + # because a bonding can have multiple ips (if statically + # assigned) as well as its network, just use the 1st + # found + Bonding.find(:all).each do |bonding| + if bonding.vlan.ip_addresses.size > 0 + # use the 1st configured network ip + bonding.ip_addr = bonding.vlan.ip_addresses[0].address + bonding.netmask = bonding.vlan.ip_addresses[0].netmask + bonding.broadcast = bonding.vlan.ip_addresses[0].broadcast + end + + if bonding.ip_addresses.size > 0 + # use the 1st assigned static ip + bonding.ip_addr = bonding.ip_addresses[0].address + end + + bonding.boot_type_id = bonding.vlan.boot_type_id + + bonding.save! + end + + ############################################################## + # drop ip_addresses table + drop_table :ip_addresses + + # drop network ids from nics / bondings table + remove_column :nics, :physical_network_id + remove_column :bondings, :vlan_id + + # drop networks tables + drop_table :networks_usages + drop_table :usages + drop_table :networks + + ############################################################## + # undo bugfix above + add_column :bondings_nics, :id, :integer + end +end diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js index 4579c80..d84b3d9 100644 --- a/src/public/javascripts/ovirt.js +++ b/src/public/javascripts/ovirt.js @@ -297,4 +297,23 @@ function delete_pool(delete_url, id) $.jGrowl(data.alert); } }, 'json'); -} \ No newline at end of file +} + + +function get_selected_networks() +{ + return get_selected_checkboxes("networks_grid_form"); +} + +function afterNetwork(response, status){ + ajax_validation(response, status); + if (response.success) { + $(document).trigger('close.facebox'); + grid = $("#networks_grid"); + if (grid.size()>0 && grid != null) { + grid.flexReload(); + } else { + $tabs.tabs("load",$tabs.data('selected.tabs')); + } + } +} diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 16eaf62..228ff7b 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -267,4 +267,52 @@ .detail-pane-chart { height: 50px; width: 375px; -}; +} + + +/************************* + * new popup components + *************************/ +.popup-content-selection { + float: left; + width: 45%; + padding-left: 20px; + padding-top: 10px; +} + +.popup-content-selection select{ + min-width: 200px; +} + +.popup-content-footer{ + width: 99%; + float: left; +} + +.selected_popup_content { + padding-left: 20px; + min-height: 50px; + float: left; + width: 96%; +} + +#selected_popup_content_header { + padding-bottom: 5px; + font-weight: bold; +} + +#selected_popup_content_expanded{ + padding-bottom: 50px; +} + +.selected_popup_content_left { + float: left; + width: 40%; + padding-bottom: 15px; +} + +.selected_popup_content_right { + float: left; + width: 40%; + padding-bottom: 15px; +} -- 1.5.6.5 From mmorsi at redhat.com Fri Oct 31 23:57:37 2008 From: mmorsi at redhat.com (Mohammed Morsi) Date: Fri, 31 Oct 2008 19:57:37 -0400 Subject: [Ovirt-devel] Re: [PATCH server] network integration into ovirt server db and wui In-Reply-To: <490B7CEB.2080302@redhat.com> References: <1225416455-7607-1-git-send-email-mmorsi@redhat.com> <490B25C8.1020509@redhat.com> <20081031160305.GH30553@redhat.com> <490B30A6.70406@redhat.com> <490B7CEB.2080302@redhat.com> Message-ID: <490B9B71.2040409@redhat.com> [snip] > so not yet ACK -- we need to fix: > > 1) ManagedNodeConfiguration bit -- I'm not sure about this one -- but > my concern is without this fix networking will be completely broken on > the nodes/VMs > 2) static IP VLAN for bondings > 3) host details pane needs to show the bonding name in the 'bonding' > section. > > Scott > I just sent out an updated patch containing fixes for #2 and #3. I'm not sure about #1. If it isn't an issue then I believe this patch is committable. -Mo
        IP MAC Bridge Usage Type
        <%= link_to nic.ip_addr, { :controller => "nic", :action => 'show', :id => nic }, { :class => "show" } %> <%= nic.mac %> <%= nic.bridge %> <%= nic.usage_type %>