[Ovirt-devel] [PATCH server] Create VM/Storage integration w/ tree widget enhancements

Jason Guiditta jguiditt at redhat.com
Thu Jan 22 22:31:22 UTC 2009


== Create VM/ Storage Flow ==
  * Can add as many storage volumes as you need to, flow allows you to
return to secondary form w/o limits.
  * Create VM form, along with its state and state of tree are stored in
a temporary area and returned after create storage via pub/sub.

== Tree enhancements ==
  * Now stores state of selected (open) nodes, so if we have a need to
reload or generate a new tree with certain nodes open, we now can.
  * Has ability to subscribe to messages via the 'channel' option
  * Added refresh method, which can be triggered by a message or fired
manually.
  * Refresh adds newly returned items to tree, stubs in place for update
and delete.
  * Added lookup object with references to all items in the tree to
allow for easy recreation of tree w/o another call to server.
  * Added ui_parent and ui_object references (generated in model via a
convenience method) to simplify finding nodes to update/add/delete.
  * Changed click behavior to use event delegation, which means we no
longer need to use livequery, which scans the dom for changes.  Event
delegation is much faster and cleaner (can remove livequery entirely
once all trees use widget).

== Widget tests/files ==
 *  Adds the standard_tree (actual widget vs nav) test harness
 *  Cleans up the tree.rhtml to account for some previous restructuring
 *  Moves sample json data into a 'test' subfolder.

I am looking into some more proper javascript testing systems, so this
will undoubtedly be changing again in the near future.

Signed-off-by: Jason Guiditta <jguiditt at redhat.com>
---
 src/app/controllers/storage_controller.rb          |   20 +-
 src/app/models/lvm_storage_volume.rb               |    3 +
 src/app/models/pool.rb                             |    1 +
 src/app/models/storage_pool.rb                     |   28 +++-
 src/app/models/storage_volume.rb                   |   28 +++-
 src/app/views/hardware/show_storage.rhtml          |   12 +-
 src/app/views/layouts/_tree.rhtml                  |    4 +-
 .../views/layouts/components/standard_tree.rhtml   |  202 ++++++++++++++++++++
 src/app/views/layouts/components/tree.rhtml        |   11 +-
 src/app/views/smart_pools/show_storage.rhtml       |   10 +-
 src/app/views/storage/new_volume.rhtml             |   22 ++-
 src/app/views/vm/_form.rhtml                       |   24 ++-
 src/public/javascripts/ovirt.js                    |   59 ++++++-
 src/public/javascripts/ovirt.tree.js               |  163 ++++++++++++----
 src/public/javascripts/smart_nav_test_data.js      |  151 ---------------
 .../javascripts/test/smart_nav_sample_data.js      |  151 +++++++++++++++
 .../javascripts/test/storage_tree_sample_data.js   |   68 +++++++
 src/test/fixtures/storage_volumes.yml              |   10 +
 src/test/unit/storage_volume_test.rb               |   21 ++-
 19 files changed, 752 insertions(+), 236 deletions(-)
 create mode 100644 src/app/views/layouts/components/standard_tree.rhtml
 delete mode 100644 src/public/javascripts/smart_nav_test_data.js
 create mode 100644 src/public/javascripts/test/smart_nav_sample_data.js
 create mode 100644 src/public/javascripts/test/storage_tree_sample_data.js

diff --git a/src/app/controllers/storage_controller.rb b/src/app/controllers/storage_controller.rb
index e4b72f1..3c7d98a 100644
--- a/src/app/controllers/storage_controller.rb
+++ b/src/app/controllers/storage_controller.rb
@@ -1,4 +1,4 @@
-# 
+#
 # Copyright (C) 2008 Red Hat, Inc.
 # Written by Scott Seago <sseago at redhat.com>
 #
@@ -122,7 +122,8 @@ class StorageController < ApplicationController
   end
 
   def new_volume
-    @return_facebox = params[:return_facebox]
+    @return_to_workflow = params[:return_to_workflow]
+    @return_to_workflow ||= false
     if params[:storage_pool_id]
       @storage_pool = StoragePool.find(params[:storage_pool_id])
       unless @storage_pool.user_subdividable
@@ -170,7 +171,8 @@ class StorageController < ApplicationController
       respond_to do |format|
         format.json { render :json => { :object => "storage_volume",
             :success => true,
-            :alert => "Storage Volume was successfully created." } }
+            :alert => "Storage Volume was successfully created.",
+            :new_volume => @storage_volume.storage_tree_element({:filter_unavailable => false, :state => 'new'})} }
         format.xml { render :xml => @storage_volume,
             :status => :created,
             # FIXME: create storage_volume_url method if relevant
@@ -240,7 +242,7 @@ class StorageController < ApplicationController
   end
 
   def edit
-    render :layout => 'popup'    
+    render :layout => 'popup'
   end
 
   def update
@@ -249,11 +251,11 @@ class StorageController < ApplicationController
         @storage_pool.update_attributes!(params[:storage_pool])
         insert_refresh_task
       end
-      render :json => { :object => "storage_pool", :success => true, 
+      render :json => { :object => "storage_pool", :success => true,
                         :alert => "Storage Pool was successfully modified." }
     rescue
       # FIXME: need to distinguish pool vs. task save errors (but should mostly be pool)
-      render :json => { :object => "storage_pool", :success => false, 
+      render :json => { :object => "storage_pool", :success => false,
                         :errors => @storage_pool.errors.localize_error_messages.to_a  }
     end
   end
@@ -269,7 +271,7 @@ class StorageController < ApplicationController
 
   def addstorage
     add_internal
-    render :layout => 'popup'    
+    render :layout => 'popup'
   end
 
   def add
@@ -301,10 +303,10 @@ class StorageController < ApplicationController
           storage_pool.destroy
         end
       end
-      render :json => { :object => "storage_pool", :success => true, 
+      render :json => { :object => "storage_pool", :success => true,
         :alert => "Storage Pools were successfully deleted." }
     rescue
-      render :json => { :object => "storage_pool", :success => true, 
+      render :json => { :object => "storage_pool", :success => true,
         :alert => "Error deleting storage pools." }
     end
   end
diff --git a/src/app/models/lvm_storage_volume.rb b/src/app/models/lvm_storage_volume.rb
index 38949ce..5b6177b 100644
--- a/src/app/models/lvm_storage_volume.rb
+++ b/src/app/models/lvm_storage_volume.rb
@@ -35,4 +35,7 @@ class LvmStorageVolume < StorageVolume
   validates_presence_of :lv_group_perms
   validates_presence_of :lv_mode_perms
 
+  def ui_parent
+    storage_pool.source_volumes[0][:type].to_s + '_' +storage_pool.source_volumes[0].id.to_s
+  end
 end
diff --git a/src/app/models/pool.rb b/src/app/models/pool.rb
index 62cb732..372cf54 100644
--- a/src/app/models/pool.rb
+++ b/src/app/models/pool.rb
@@ -180,6 +180,7 @@ class Pool < ActiveRecord::Base
                      ["Disk", :storage_in_gb, "(gb)"]]
 
   #needed by tree widget for display
+  #FIXME: this can be removed once all instances of treeview are gone
   def hasChildren
     return (rgt - lft) != 1
   end
diff --git a/src/app/models/storage_pool.rb b/src/app/models/storage_pool.rb
index f9abb55..a021a26 100644
--- a/src/app/models/storage_pool.rb
+++ b/src/app/models/storage_pool.rb
@@ -1,4 +1,4 @@
-# 
+#
 # Copyright (C) 2008 Red Hat, Inc.
 # Written by Scott Seago <sseago at redhat.com>
 #
@@ -97,16 +97,37 @@ class StoragePool < ActiveRecord::Base
     false
   end
 
+  #--
+  #TODO: the following two methods should be moved out somewhere, perhaps an 'acts_as' plugin?
+  #Though ui_parent will have class specific impl
+  #++
+  #This is a convenience method for use in the ui to simplify creating a unigue id for placement/retrieval
+  #in/from the DOM.  This was added because there is a chance of duplicate ids between different object types,
+  #and multiple object type will appear concurrently in the ui.  The combination of type and id should be unique.
+  def ui_object
+    self.class.to_s + '_' + id.to_s
+  end
+
+  #This is a convenience method for use in the processing and manipulation of json in the ui.
+  #This serves as a key both for determining where to attached elements in the DOM and quickly
+  #accessing and updating a cached object on the client.
+  def ui_parent
+    nil
+  end
+
   def storage_tree_element(params = {})
     vm_to_include=params.fetch(:vm_to_include, nil)
     filter_unavailable = params.fetch(:filter_unavailable, true)
     include_used = params.fetch(:include_used, false)
+    state = params.fetch(:state,nil)
     return_hash = { :id => id,
       :type => self[:type],
-      :text => display_name,
       :name => display_name,
+      :ui_object => ui_object,
+      :state => state,
       :available => false,
       :create_volume => user_subdividable,
+      :ui_parent => ui_parent,
       :selected => false,
       :is_pool => true}
     conditions = nil
@@ -117,7 +138,8 @@ class StoragePool < ActiveRecord::Base
       end
     end
     if filter_unavailable
-      availability_conditions = "storage_volumes.state = '#{StoragePool::STATE_AVAILABLE}'"
+      availability_conditions = "(storage_volumes.state = '#{StoragePool::STATE_AVAILABLE}'
+        or storage_volumes.state = '#{StoragePool::STATE_PENDING_SETUP}')"
       if conditions.nil?
         conditions = availability_conditions
       else
diff --git a/src/app/models/storage_volume.rb b/src/app/models/storage_volume.rb
index e289a96..59d166e 100644
--- a/src/app/models/storage_volume.rb
+++ b/src/app/models/storage_volume.rb
@@ -1,4 +1,4 @@
-# 
+#
 # Copyright (C) 2008 Red Hat, Inc.
 # Written by Scott Seago <sseago at redhat.com>
 #
@@ -97,20 +97,41 @@ class StorageVolume < ActiveRecord::Base
     storage_pool.user_subdividable and vms.empty? and (lvm_storage_pool.nil? or lvm_storage_pool.storage_volumes.empty?)
   end
 
+  #--
+  #TODO: the following two methods should be moved out somewhere, perhaps an 'acts_as' plugin?
+  #Though ui_parent will have class specific impl
+  #++
+  ##This is a convenience method for use in the ui to simplify creating a unigue id for placement/retrieval
+  #in/from the DOM.  This was added because there is a chance of duplicate ids between different object types,
+  #and multiple object type will appear concurrently in the ui.  The combination of type and id should be unique.
+  def ui_object
+    self.class.to_s + '_' + id.to_s
+  end
+
+  #This is a convenience method for use in the processing and manipulation of json in the ui.
+  #This serves as a key both for determining where to attached elements in the DOM and quickly
+  #accessing and updating a cached object on the client.
+  def ui_parent
+    storage_pool[:type].to_s + '_' + storage_pool_id.to_s
+  end
+
   def storage_tree_element(params = {})
     vm_to_include=params.fetch(:vm_to_include, nil)
     filter_unavailable = params.fetch(:filter_unavailable, true)
     include_used = params.fetch(:include_used, false)
     vm_ids = vms.collect {|vm| vm.id}
+    state = params.fetch(:state,'new')
     return_hash = { :id => id,
       :type => self[:type],
-      :text => display_name,
+      :ui_object => ui_object,
+      :state => state,
       :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))),
       :create_volume => supports_lvm_subdivision,
+      :ui_parent => ui_parent,
       :selected => (!vm_ids.empty? and vm_to_include and vm_to_include.id and
                    (vm_ids.include?(vm_to_include.id))),
       :is_pool => false}
@@ -126,7 +147,8 @@ class StorageVolume < ActiveRecord::Base
         end
       end
       if filter_unavailable
-        availability_conditions = "storage_volumes.state = '#{StoragePool::STATE_AVAILABLE}'"
+        availability_conditions = "(storage_volumes.state = '#{StoragePool::STATE_AVAILABLE}'
+        or storage_volumes.state = '#{StoragePool::STATE_PENDING_SETUP}')"
         if conditions.nil?
           conditions = availability_conditions
         else
diff --git a/src/app/views/hardware/show_storage.rhtml b/src/app/views/hardware/show_storage.rhtml
index 5643c83..35f0a55 100644
--- a/src/app/views/hardware/show_storage.rhtml
+++ b/src/app/views/hardware/show_storage.rhtml
@@ -111,11 +111,13 @@ ${htmlList(pools)}
   }
   function storage_select(e, elem)
   {
-    $('#storage_tree_form ul.ovirt-tree li div').removeClass('current');
-    $(elem)
-      .addClass('current');
-    $('#storage_selection').load('<%= url_for :controller => "search", :action => "single_result" %>',
-                { class_and_id: elem.id.substring(elem.id.indexOf("_")+1)});
+    if ($(e.target).is('div') && $(e.target).parent().is('li')){
+      $('#storage_tree_form .current').removeClass('current');
+      $(e.target)
+        .addClass('current');
+      $('#storage_selection').load('<%= url_for :controller => "search", :action => "single_result" %>',
+                  { class_and_id: e.target.id.substring(e.target.id.indexOf("_")+1)});
+    }
   }
 
 </script>
diff --git a/src/app/views/layouts/_tree.rhtml b/src/app/views/layouts/_tree.rhtml
index fa3effc..8d5ce51 100644
--- a/src/app/views/layouts/_tree.rhtml
+++ b/src/app/views/layouts/_tree.rhtml
@@ -27,7 +27,7 @@
               });
               e.preventDefault();
           }
-      })
+      });
       $('div.nav-networks a').bind('click', function(e){
           if(this === e.target){
               var myURL = $(this).attr('href');
@@ -41,7 +41,7 @@
               });
               e.preventDefault();
           }
-      })
+      });
       $('#nav_tree_form ul.ovirt-tree li').livequery(
         function(){
           $(this)
diff --git a/src/app/views/layouts/components/standard_tree.rhtml b/src/app/views/layouts/components/standard_tree.rhtml
new file mode 100644
index 0000000..02ea74e
--- /dev/null
+++ b/src/app/views/layouts/components/standard_tree.rhtml
@@ -0,0 +1,202 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+  <title><%= yield :title -%></title>
+  <%= stylesheet_link_tag 'ovirt-tree/tree' %>
+  <%= stylesheet_link_tag 'facebox' %>
+
+  <%= javascript_include_tag "jquery-1.2.6.min.js" -%>
+  <%= javascript_include_tag "jquery.ui-1.5.2/ui/packed/ui.core.packed.js" -%>
+  <%= javascript_include_tag "test/storage_tree_sample_data.js" -%>
+  <%= javascript_include_tag "test/smart_nav_sample_data.js" -%>
+  <%= javascript_include_tag "jquery.form.js" -%>
+  <%= javascript_include_tag "trimpath-template-1.0.38.js" %>
+  <%= javascript_include_tag "ovirt.js" %>
+  <%= javascript_include_tag "ovirt.tree.js" %>
+  <%= javascript_include_tag "facebox.js" %>
+  <script type="text/javascript">
+      var currentDomTreeElem = 'storage_volumes_tree', treeObject;
+    $(document).ready(function(){
+      //initialize tree widget
+      $('#nav_tree').tree({
+          content: {"pools" : pools3.pools},
+          clickHandler: myClickHandler,
+          cacheContent: false
+      });
+      /* $('#some_tree').tree({
+          content: {"pools" : pools3.pools},
+          toggle: myToggler
+      });*/
+      $('#storage_volumes_tree').tree({
+        content: {"pools" : storage_pools.pools},
+        template : 'storage_tree_template',
+        clickHandler: myClickHandler,
+        channel: 'STORAGE_VOLUME',
+        refresh: refreshPostHandler,
+        cacheContent: true
+      });
+      $('#swap_button').bind('click', function(event){
+          var newDomTreeElem;
+          if (currentDomTreeElem === 'storage_volumes_tree') {
+              newDomTreeElem = 'temp_storage_tree';
+          } else { newDomTreeElem = 'storage_volumes_tree'}
+          $('#' + currentDomTreeElem).clone().attr({style: 'background:green',id: newDomTreeElem}).appendTo('body').end().remove();
+          currentDomTreeElem = newDomTreeElem;
+      });
+      $('#refresh_button').bind('click', function(event){
+          $('ul.ovirt-tree').trigger('STORAGE_VOLUME', data_snippet);
+      });
+      $('#empty_button').bind('click', function(event){
+          $('#' + currentDomTreeElem).empty();
+      });
+      $('#populate_button').bind('click', function(event){
+          $('#' + currentDomTreeElem).tree({
+            content: treeObject.content,
+            template : 'storage_tree_template',
+            clickHandler: myClickHandler,
+            selectedNodes: treeObject.selectedNodes,
+            channel: 'STORAGE_VOLUME',
+            refresh: refreshPostHandler,
+            cacheContent: true
+          });
+      });
+      $('#return_button').bind('click', function(event){
+          treeObject = $('#' + currentDomTreeElem).data('tree').options;
+      });
+      $('#toggle_button').bind('click', function(e) {
+          $('#' + currentDomTreeElem).toggle();
+      });
+      $('#open_button').bind('click', function(event){
+          $('#' + currentDomTreeElem).tree('openToSelected');
+      });
+      $('#show_button').bind('click', function(event){
+          $.facebox('');
+          $('#' + currentDomTreeElem).clone().appendTo('td.body > div.content').end().remove();
+          $('#populate_button').trigger('click');
+      });
+    });
+
+    function myToggler(e, elem) {
+        if ($(e.target).is('span.hitarea')){
+            alert('Yay, I am a custom toggler!');
+        }
+    }
+
+    function refreshPostHandler(e, self, data) {
+        //alert('I am ' + self.element.get(0).id + ', and I have a parent of ' + self.getData('sourceID'));
+    }
+
+    function myClickHandler(e, elem) {
+        //VmCreator.test(elem.element.get(0).id);
+        if ($(e.target).is('div') && $(e.target).parent().is('li')){
+          $(e.target)
+          .toggleClass('current');
+        }
+        if ($(e.target).is('img') && $(e.target).parent().is('a')){
+            alert('I got yer link right hea! (' + e.target + ')');
+            e.preventDefault();
+        }
+    }
+
+    var data_snippet = [{
+          "selected":false,
+          "name":"LVM: test_lvm:test_lun3_2",
+          "available":true,
+          "children":[],
+          "create_volume":false,
+          "id":13,
+          "type":"LvmStorageVolume",
+          "ui_object": "LvmStorageVolume_13",
+          "ui_parent": "IscsiStorageVolume_4"
+        }];
+
+  </script>
+ </head>
+
+ <body>
+   <button id="swap_button">Swap Tree</button>
+   <button id="refresh_button">refresh</button>
+   <button id="empty_button">Empty Tree</button>
+   <button id="populate_button">Populate Storage Tree</button>
+   <button id="return_button">Store Tree Info</button>
+   <button id="toggle_button">Toggle Visibility</button>
+   <button id="open_button">Open To Selected</button>
+   <button id="show_button">Show tree in facebox</button>
+     <form id="nav_tree_form">
+         <ul id="nav_tree" class="ovirt-tree"></ul>
+     </form>
+     <br/><br/><br/><br/>
+     <form id="some_tree_form">
+         <ul id="some_tree" class="ovirt-tree"></ul>
+     </form>
+     <textarea id="tree_template" style="display:none;">
+      {macro htmlList(list, optionalListType)}
+        {var listType = optionalListType != null ? optionalListType : "ul"}
+        <${listType} style="display:none;">
+          {for item in list}
+            <li>
+              <input type="checkbox" name="item[]" value="${item.id}-${item.name}" style="display:none" checked="checked"/>
+              <span class="hitarea {if item.children} expandable{/if}"> </span><div id="${item.id}" class="${item.type}">${item.name}</div>
+              {if item.children}
+                ${htmlList(item.children)}
+              {/if}
+            </li>
+          {/for}
+        </${listType}>
+      {/macro}
+
+      {for item in pools}
+        <li>
+          <input type="checkbox" name="item[]" value="${item.id}-${item.name}" style="display:none" checked="checked"/>
+          <span class="hitarea {if item.children} expandable{/if}"> </span><div id="${item.id}" class="${item.type}">${item.name}</div>
+          {if item.children}
+            ${htmlList(item.children)}
+          {/if}
+        </li>
+      {/for}
+     </textarea>
+
+    <div id="tree_placeholder">
+     <form id="storage_tree_form">
+       My Name: <input type="text" name="my_name"/>
+       <div>
+         <ul id="storage_volumes_tree" class="ovirt-tree"></ul>
+       </div>
+     </form>
+    </div>
+     <textarea id="storage_tree_template" style="display:none;">
+       {macro htmlList(list, id, isFullList)}
+        {if isFullList}
+        <ul style="display:none;">
+        {/if}
+          {for item in list}
+            <li>
+              <span class="hitarea {if item.children.length > 0} expandable{/if}"> </span>
+              <div id="${id}-${item.ui_object}" class="{if !item.available} unclickable{/if}">
+                <input type="checkbox" name="vm[storage_volume_ids][]" value="${item.id}"
+                  {if !item.available}disabled="disabled" style="display:none"{/if}
+                  {if item.selected}checked="checked"{/if}/> ${item.name} {if item.size}(${item.size} GB){/if}
+                  {if item.create_volume}
+                    <input type="hidden" name="return_item" value="true"/><%=image_tag("icon_addstorage.png")%>
+                    <a href="<%= url_for :controller => 'storage', :action => 'new_volume'%>?source_volume_id=${item.id}"
+                      class="selection_facebox"></a>
+                  {/if}
+              </div>
+              {if item.children.length > 0}
+                ${htmlList(item.children, id, true)}
+              {/if}
+            </li>
+          {/for}
+        {if isFullList}
+        </ul>
+        {/if}
+      {/macro}
+
+      ${htmlList(pools, id)}
+     </textarea>
+ </body>
+</html>
diff --git a/src/app/views/layouts/components/tree.rhtml b/src/app/views/layouts/components/tree.rhtml
index 063a6df..402c6e3 100644
--- a/src/app/views/layouts/components/tree.rhtml
+++ b/src/app/views/layouts/components/tree.rhtml
@@ -6,10 +6,15 @@
 <head>
   <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
   <title><%= yield :title -%></title>
+  <%= stylesheet_link_tag 'ovirt-tree/tree' %>
+
   <%= 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.ui-1.5.2/ui/packed/ui.core.packed.js" -%>
+  <%= javascript_include_tag "test/smart_nav_sample_data.js" -%>
   <%= javascript_include_tag "jquery.form.js" -%>
+  <%= javascript_include_tag "trimpath-template-1.0.38.js" %>
+  <%= javascript_include_tag "ovirt.tree.js" %>
   <%= javascript_include_tag "ovirt.js" -%>
 
   <script type="text/javascript">
@@ -43,12 +48,8 @@
 
  <body>
    <div id="side">
-     <button id="stop">Stop!</button>
      <%= render :partial => '/layouts/tree' %>
    </div>
-   <div id="side-toolbar" class="header_menu_wrapper">
-     <%= render :partial => '/layouts/side_toolbar' %>
-   </div>
 
    <div id="tabs-and-content-container">
      <div id="main">
diff --git a/src/app/views/smart_pools/show_storage.rhtml b/src/app/views/smart_pools/show_storage.rhtml
index 68f1b48..2549e7d 100644
--- a/src/app/views/smart_pools/show_storage.rhtml
+++ b/src/app/views/smart_pools/show_storage.rhtml
@@ -63,11 +63,13 @@ ${htmlList(pools)}
   }
   function smart_storage_select(e, elem)
   {
-    $('#smart_storage_tree_form ul.ovirt-tree li div').removeClass('current');
-    $(elem)
+    if ($(e.target).is('div') && $(e.target).parent().is('li')){
+      $('#smart_storage_tree_form .current').removeClass('current');
+      $(e.target)
       .addClass('current');
-    $('#smart_storage_selection').load('<%= url_for :controller => "search", :action => "single_result" %>',
-                { class_and_id: elem.id.substring(elem.id.indexOf("_")+1)});
+      $('#smart_storage_selection').load('<%= url_for :controller => "search", :action => "single_result" %>',
+                { class_and_id: e.target.id.substring(e.target.id.indexOf("_")+1)});
+    }
   }
 
 </script>
diff --git a/src/app/views/storage/new_volume.rhtml b/src/app/views/storage/new_volume.rhtml
index 958c463..2e49d16 100644
--- a/src/app/views/storage/new_volume.rhtml
+++ b/src/app/views/storage/new_volume.rhtml
@@ -18,20 +18,26 @@
     </div>
   </div>
   <!-- FIXME: need to pop up the details dialog again -->
-  <%= popup_footer("$('#storage_volume_form').submit()", "New Storage Volume") %>
+  <% if @return_to_workflow %>
+    <%# TODO: update this method in application_helper to take an array, so we can include
+        a callback or trigger to to go previous step in flow. %>
+    <%= popup_footer("$('#storage_volume_form').submit()", "New Storage Volume") %>
+  <% else %>
+    <%= popup_footer("$('#storage_volume_form').submit()", "New Storage Volume") %>
+  <%  end %>
 </form>
 </div>
 <script type="text/javascript">
 function afterStorageVolume(response, status){
     ajax_validation(response, status);
     if (response.success) {
-    <% if @return_facebox %>
-      $('#window').fadeOut('fast');
-      $("#window").empty().load("<%= @return_facebox %>")
-      $('#window').fadeIn('fast');
-    <% else %>
-      $(document).trigger('close.facebox');
-    <% end %>
+        //this is where I want to publish to...
+        //$(document).trigger('STORAGE_VOLUME', [response.new_volume]);
+        //but it only picks up correctly right now if I push it here, so this needs to change later
+      $('ul.ovirt-tree').trigger('STORAGE_VOLUME', [response.new_volume]);
+      <% unless @return_to_workflow -%>
+        $(document).trigger('close.facebox');
+      <% end -%>
     }
 }
 $(function() {
diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml
index 523e81e..fb3d137 100644
--- a/src/app/views/vm/_form.rhtml
+++ b/src/app/views/vm/_form.rhtml
@@ -37,7 +37,7 @@
     <div class="clear_row" style="height:15px;"></div>
     <div class="clear_row"></div>
     <div class="clear_row"></div>
-    
+
     <div class="form_heading">Network</div>
     <div class="clear_row"></div>
     <div class="clear_row"></div>
@@ -56,21 +56,27 @@
 
 <!--[eoform:vm]-->
 
-<textarea id="tree_template" style="display:none;">
-{macro htmlList(list, isFullList)}
+<textarea id="storage_volumes_template" style="display:none;">
+{macro htmlList(list, id, isFullList)}
   {if isFullList}
   <ul style="display:none;">
   {/if}
     {for item in list}
       <li>
         <span class="hitarea {if item.children.length > 0} expandable{/if}"> </span>
-        <div id="move-${item.id}" class="{if !item.available} unclickable{/if}">
+        <div id="${id}-${item.ui_object}" class="{if !item.available} unclickable{/if}">
           <input type="checkbox" name="vm[storage_volume_ids][]" value="${item.id}"
             {if !item.available}disabled="disabled" style="display:none"{/if}
             {if item.selected}checked="checked"{/if}/> ${item.name} {if item.size}(${item.size} GB){/if}
+            {if item.create_volume}
+              <input type="hidden" name="return_item" value="true"/>
+              <%=image_tag("icon_addstorage.png")%>
+              <a href="<%= url_for :controller => 'storage', :action => 'new_volume'%>?source_volume_id=${item.id}&return_to_workflow=true"
+                rel="facebox[.bolder]" class="selection_facebox"></a>
+            {/if}
         </div>
         {if item.children.length > 0}
-          ${htmlList(item.children, true)}
+          ${htmlList(item.children, id, true)}
         {/if}
       </li>
     {/for}
@@ -79,12 +85,16 @@
   {/if}
 {/macro}
 
-${htmlList(pools)}
+${htmlList(pools, id)}
 </textarea>
 <script type="text/javascript">
     $(document).ready(function(){
       $('#storage_volumes_tree').tree({
-        content: {"pools" : <%=  @storage_tree%>}
+        content: {"pools" : <%=  @storage_tree%>},
+        template: "storage_volumes_template",
+        clickHandler: VmCreator.goToCreateStorageHandler,
+        channel: 'STORAGE_VOLUME',
+        refresh: VmCreator.returnToVmForm
       });
     });
 </script>
diff --git a/src/public/javascripts/ovirt.js b/src/public/javascripts/ovirt.js
index a43f004..09d8bdc 100644
--- a/src/public/javascripts/ovirt.js
+++ b/src/public/javascripts/ovirt.js
@@ -173,7 +173,7 @@ function afterHwPool(response, status){
           $tabs.tabs("load",$tabs.data('selected.tabs'));
         }
       }
-      
+
       //FIXME: point all these refs at a widget so we dont need the functions in here
       processTree();
 
@@ -322,4 +322,61 @@ function afterNetwork(response, status){
 function handleTabsAndContent(data) {
   $('#side-toolbar').html($(data).find('div.toolbar'));
   $('#tabs-and-content-container').html($(data).not('div#side-toolbar'));
+}
+
+var VmCreator = {
+  checkedBoxesFromTree : [],
+  buildCheckboxList: function(id) {
+      var rawList = $('#'+ id + ' :checkbox:checked').parent('div');
+      if (rawList.length >0) {
+          rawList.each(function(i) {
+            VmCreator.checkedBoxesFromTree.push(rawList.get(i).id);
+          });
+      } else {
+          VmCreator.checkedBoxesFromTree.splice(0);
+      }
+  },
+  clickCheckboxes: function() {
+      $.each(VmCreator.checkedBoxesFromTree, function(n, curBox){
+          $('#' + curBox).children(':checkbox').click();
+      });
+      VmCreator.checkedBoxesFromTree = [];
+  },
+  recreateTree: function(o){
+      $('#storage_volumes_tree').tree({
+        content: o.content,
+        template: "storage_volumes_template",
+        selectedNodes: o.selectedNodes,
+        clickHandler: VmCreator.goToCreateStorageHandler,
+        channel: 'STORAGE_VOLUME',
+        refresh: VmCreator.returnToVmForm
+      });
+  },
+  goToCreateStorageHandler: function goToCreateStorageHandler(e,elem){
+    if ($(e.target).is('img') && $(e.target).parent().is('div')){
+        //remove the temp form in case there is one hanging around for some reason
+        $('temp_create_vm_form').remove();
+        VmCreator.buildCheckboxList(elem.element.get(0).id);
+        var storedOptions = $('#storage_volumes_tree').data('tree').options;
+        // copy/rename form
+        $('#window').clone(true).attr({style: 'display:none', id: 'temp_window'}).appendTo('body');
+        $('#temp_window #vm_form').attr({id: 'temp_create_vm_form'});
+        // continue standard calls to go to next step (create storage)
+        $(e.target).siblings('a').click();
+        // empty tree
+        $('#temp_create_vm_form #storage_volumes_tree').empty();
+        // reinitialize tree so it has data and is subscribed
+        VmCreator.recreateTree(storedOptions);
+    }
+  },
+  returnToVmForm: function returnToVmForm(e,elem) {
+      //The item has now been added to the tree, now copy it into a facebox
+      var storedOptions = $('#storage_volumes_tree').data('tree').options;
+      $('#window').remove();
+      $('#temp_window').clone(true).attr({style: 'display:block', id: 'window'})
+        .appendTo('td.body > div.content').end().remove();
+      $('#window #temp_create_vm_form').attr({id: 'vm_form'});
+      VmCreator.recreateTree(storedOptions);
+      VmCreator.clickCheckboxes();
+  }
 }
\ No newline at end of file
diff --git a/src/public/javascripts/ovirt.tree.js b/src/public/javascripts/ovirt.tree.js
index 77d6c55..48f529c 100644
--- a/src/public/javascripts/ovirt.tree.js
+++ b/src/public/javascripts/ovirt.tree.js
@@ -75,60 +75,149 @@ function processChildren(list, templateObj){
                     this.setData('template', TrimPath.parseDOMTemplate(this.getData('template')));
 		},
 		init: function() {
+                    var self = this, o = this.options;
                     this.setTemplate(this.getTemplate());
-                    this.element.html(this.getTemplate().process(this.getData('content')));
-                    var self = this;
+                    this.populate();
                     this.element
-                    .find('li:has(ul)')
-                    .children('span.hitarea')
-                    .click(function(event){
-                      if (this == event.target) {
-                          if($(this).siblings('ul').size() >0) {
-                              if(self.getData('toggle') === 'toggle') {
-                                  self.toggle(event, this);  //we need 'this' so we have the right element to toggle
-                              } else {
-                                self.element.triggerHandler('toggle',[event,this],self.getData('toggle'));
-                              }
-                          }
-                      }
+                    .bind('click', function(event){
+                        self.clickHandler(event, self);
+                        if(self.getData('toggle') === 'toggle') {
+                          self.toggle(event, this);
+                        } else {
+                          self.element.triggerHandler('toggle',[event,this],self.getData('toggle'));
+                        }
                     });
-                    this.element
-                    .find('li > div')
-                    .filter(':not(.unclickable)')
-                    .bind('click', function(event) {
-                      if (this == event.target) {
-                          if(self.getData('clickHandler') === 'clickHandler') {
-                            self.clickHandler(event, this);  //we need 'this' so we have the right element to add click behavior to
-                          } else {
-                            self.element.triggerHandler('clickHandler',[event,this],self.getData('clickHandler'));
-                          }
-                      }
+                    o.selectedNodes !== undefined? this.openToSelected() :o.selectedNodes=[];
+                    o.channel !== undefined? this.subscribe(o.channel): o.channel = '';
+                    if (o.cacheContent === true) this.buildLookup();
+                },
+                populate: function() {
+                    var contentWithId = this.getData('content');
+                    contentWithId.id = this.element.get(0).id;
+                    this.element.html(this.getTemplate().process(contentWithId));
+                },
+                buildLookup: function() {
+                    this.setData('lookupList', this.walkTree(this.getData('content').pools, [], this));
+                },
+                walkTree: function(list, lookup, self) {
+                    $.each(list, function(n,obj){
+                        lookup.push(obj);
+                        if (obj.children.length > 0) self.walkTree(obj.children, lookup, self);
                     });
-                    this.openToSelected(self);
+                    return lookup;
+                },
+                subscribe: function subscribe(channel) {
+                    var self = this;
+                    this.element.bind(channel, function(e,data){self.refresh(e,data);});
                 },
                 toggle: function(e, elem) {
-                    $(elem)
+                    if ($(e.target).is('span.hitarea')){
+                      $(e.target)
                       .toggleClass('expanded')
                       .toggleClass('expandable')
                       .siblings('ul').slideToggle("normal");
+                      if ($(e.target).hasClass('expanded')) {
+                        this.setSelectedNode(this.chop(e.target), true);
+                      } else {
+                          this.setSelectedNode(this.chop(e.target), false);
+                      }
+                    }
+                },
+                chop: function(elem) {
+                    var id = $(elem).siblings('div').get(0).id;
+                    return id.substring(id.indexOf('-') +1);
+                },
+                clickHandler: function(e,elem) { //TODO: make this a default impl if needed.
+                    this.options.clickHandler !== undefined? this.element.triggerHandler('clickHandler',[e,this],this.getData('clickHandler')): null;
+                    if ($(e.target).is('div') && $(e.target).parent().is('li')){}
+                },
+                setSelectedNode: function(id, isOpen) {
+                    if (isOpen) {
+                        if($.inArray(id,this.getData('selectedNodes')) == -1){
+                          this.setData(this.getData('selectedNodes').push(id));
+                        }
+                    } else {
+                        if($.inArray(id,this.getData('selectedNodes')) != -1){
+                          this.setData(this.getData('selectedNodes').splice(this.getData('selectedNodes').indexOf(id),1));
+                        }
+                    }
+                },
+                openToSelected: function() {
+                    for (var i = 0; i < this.getData('selectedNodes').length; i++){
+                      this.toggle($.event.fix({type: 'toggle',
+                                              target: this.element.find('#' +this.element.get(0).id + '-' + this.getData('selectedNodes')[i]).siblings('span').get(0)})
+                                  , this);
+                    }
+                },
+                refresh: function(e, list) {
+                    //NOTE: The widget expects the convention used elsewhere of {blah}-{ui_object}
+                    //(where {blah} is the id of the container element, see above for an example soon),
+                    //since there may be 2 items with the same db id.
+                    var self = this;
+                    list = $.makeArray(list);
+                    $.each(list, function(n,data){
+                      switch(data.state) {
+                          case 'deleted': {
+                            self._delete(data);
+                            break;
+                          }
+                          case 'changed': {
+                            self._update(data);
+                            break;
+                          }
+                          default: {
+                            self._add(data);
+                            break;
+                          }
+                      }
+                    });
+                    self.options.refresh !== undefined? self.element.triggerHandler('refresh',[e,list],self.getData('refresh')): null;
                 },
-                clickHandler: function(e,elem) {
-                    // make this a default impl if needed.
+                //methods meant to be called internally by widget
+                _add: function(data){
+                  var myLookupList = this.getData('lookupList');
+                    if (data.ui_parent !==null) {
+                      var matchedItems = $.grep(myLookupList,function(value) {return value.ui_object == data.ui_parent;});
+                      var self = this;
+                      $.each(matchedItems, function(n,obj){
+                        var existingObj = [];
+                        if(obj.children.length >0) {
+                          existingObj = $.grep(obj.children,function(value) {return value.ui_object == data.ui_object;});
+                        }
+                        if (existingObj.length === 0){
+                            obj.children.push(data);
+                            myLookupList.push(data);
+                            self._addDomElem(data);
+                        } else {}
+                      });
+                    } else {myLookupList.push(data);}
                 },
-                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.
+                _delete: function(data){}, //TODO: implement
+                _update: function(data) {}, //TODO: implement
+                _addDomElem: function(data) {
+                  var dataToInsert = this.getTemplate().process({"pools":[data], "id":this.element.get(0).id});
+                  if (data.ui_parent) {
+                    var searchString = '#' + this.element.get(0).id + '-' + data.ui_parent;
+                    var parentElem = this.element.find(searchString).siblings('ul');
+                    if (parentElem.size() === 0) {
+                        this.element.find(searchString).parent().append('<ul>' + dataToInsert + '</ul>');
+                        this.element.find(searchString).siblings('span').addClass('expanded');
+                    } else {
+                      parentElem.append(dataToInsert);
+                    }
+                  } else {
+                      this.element.append(dataToInsert);
+                  }
                 },
-		off: function() {
-			this.element.css({background: 'none'});
-			this.destroy(); // use the predefined function
-		}
+                _deleteDomElem: function(data) {}, //TODO: implement
+                _updateDomElem: function(data) {} //TODO: implement
 	};
 	$.yi = $.yi || {}; // create the namespace
 	$.widget("yi.tree", Tree);
 	$.yi.tree.defaults = {
             template: 'tree_template',
             toggle: 'toggle',
-            clickHandler: 'clickHandler'
+            clickHandler: 'clickHandler',
+            cacheContent: true
 	};
 })(jQuery);
\ No newline at end of file
diff --git a/src/public/javascripts/smart_nav_test_data.js b/src/public/javascripts/smart_nav_test_data.js
deleted file mode 100644
index 43e7dbc..0000000
--- a/src/public/javascripts/smart_nav_test_data.js
+++ /dev/null
@@ -1,151 +0,0 @@
-var pools3 = {
-    "deleted" : {},
-    "pools" :[
- {  "name": "default",
-    "text": "default",
-    "children":
-        [{  "name": "Engineering",
-            "text": "Engineering",
-            "children":
-                [{  "name": "Development",
-                    "text": "Development",
-                    "children":
-                        [{  "name": "Project X",
-                            "text": "Project X",
-                            "id": 19,
-                            "type": "VmResourcePool"},
-                         {  "name": "Project Y",
-                            "text": "Project Y",
-                            "id": 20,
-                            "type": "VmResourcePool"}],
-                    "id": 9,
-                    "type": "HardwarePool"},
-                 {  "name": "QA",
-                    "text": "QA",
-                    "children":
-                        [{  "name": "Bob's Team",
-                            "text": "Bob's Team",
-                            "children":
-                                [{  "name": "Bob's VMs",
-                                    "text": "Bob's VMs",
-                                    "id": 21,
-                                    "type": "VmResourcePool"}],
-                            "id": 17,
-                            "type": "HardwarePool"},
-                         {  "name": "Jim's Team",
-                            "text": "Jim's Team",
-                            "children":
-                                [{  "name": "Jim's VMs",
-                                    "text": "Jim's VMs",
-                                    "id": 22,
-                                    "type": "VmResourcePool"}],
-                            "id": 18,
-                            "type": "HardwarePool"},
-                         {  "name": "Sally's Team",
-                            "text": "Sally's Team",
-                            "children":
-                                [{  "name": "Sally's VMs",
-                                    "text": "Sally's VMs",
-                                    "id": 33,
-                                    "type": "VmResourcePool"}],
-                            "id": 32,
-                            "type": "HardwarePool"}],
-                    "id": 10,
-                    "type": "HardwarePool"},
-                 {  "name": "Stage",
-                    "text": "Stage",
-                    "children":
-                        [{  "name": "stage1",
-                            "text": "stage1",
-                            "id": 45,
-                            "type": "HardwarePool"},
-                         {  "name": "stage2",
-                            "text": "stage2",
-                            "id": 46,
-                            "type": "HardwarePool"}],
-                    "id": 44,
-                    "type": "HardwarePool"}],
-            "id": 5,
-            "type": "HardwarePool"},
-         {  "name": "Finance",
-            "text": "Finance",
-            "children":
-                [{  "name": "Payroll",
-                    "text": "Payroll",
-                    "children":
-                        [{  "name": "Payroll VMs",
-                            "text": "Payroll VMs",
-                            "id": 23,
-                            "type": "VmResourcePool"}],
-                    "id": 11,
-                    "type": "HardwarePool"},
-                 {  "name": "Accts. Receivable",
-                     "text": "Accts. Receivable",
-                     "children":
-                         [{  "name": "our VMs",
-                             "text": "our VMs",
-                             "id": 24,
-                             "type": "VmResourcePool"}],
-                     "id": 12,
-                     "type": "HardwarePool"}],
-             "id": 6,
-             "type": "HardwarePool"},
-          {  "name": "HR",
-              "text": "HR",
-              "children":
-                  [{  "name": "Hiring Team",
-                      "text": "Hiring Team",
-                      "id": 13,
-                      "type": "HardwarePool"},
-                   {  "name": "Benefits",
-                       "text": "Benefits",
-                       "id": 14,
-                       "type": "HardwarePool"}],
-               "id": 7,
-               "type": "HardwarePool"},
-          {  "name": "External (DMZ)",
-              "text": "External (DMZ)",
-              "children":
-                  [{  "name": "VMs",
-                      "text": "VMs",
-                      "id": 25,
-                      "type": "VmResourcePool"},
-                   {  "name": "DB Cluster",
-                      "text": "DB Cluster",
-                      "children":
-                          [{  "name": "VMs",
-                              "text": "VMs",
-                              "id": 27,
-                              "type": "VmResourcePool"}],
-                      "id": 26,
-                      "type": "HardwarePool"}],
-              "id": 8,
-              "type": "HardwarePool"}],
-      "id": 1,
-      "type": "HardwarePool"}],
-"smart_pools":[{  "name": "ovirtadmin",
-   "text": "ovirtadmin",
-   "children":
-       [{  "name": "not so smart",
-           "text": "not so smart",
-           "id": 39,
-           "type": "SmartPool"},
-        {  "name": "a little smarter",
-           "text": "a little smarter",
-           "id": 40,
-           "type": "SmartPool"},
-        {  "name": "arrrrr",
-            "text": "arrrrr",
-            "id": 41,
-            "type": "SmartPool"},
-        {  "name": "huh?",
-           "text": "huh?",
-           "id": 42,
-           "type": "SmartPool"},
-        {  "name": "booya",
-           "text": "booya",
-           "id": 43,
-           "type": "SmartPool"}],
-   "id": 37,
-   "type": "DirectoryPool"}]
-}
\ No newline at end of file
diff --git a/src/public/javascripts/test/smart_nav_sample_data.js b/src/public/javascripts/test/smart_nav_sample_data.js
new file mode 100644
index 0000000..43e7dbc
--- /dev/null
+++ b/src/public/javascripts/test/smart_nav_sample_data.js
@@ -0,0 +1,151 @@
+var pools3 = {
+    "deleted" : {},
+    "pools" :[
+ {  "name": "default",
+    "text": "default",
+    "children":
+        [{  "name": "Engineering",
+            "text": "Engineering",
+            "children":
+                [{  "name": "Development",
+                    "text": "Development",
+                    "children":
+                        [{  "name": "Project X",
+                            "text": "Project X",
+                            "id": 19,
+                            "type": "VmResourcePool"},
+                         {  "name": "Project Y",
+                            "text": "Project Y",
+                            "id": 20,
+                            "type": "VmResourcePool"}],
+                    "id": 9,
+                    "type": "HardwarePool"},
+                 {  "name": "QA",
+                    "text": "QA",
+                    "children":
+                        [{  "name": "Bob's Team",
+                            "text": "Bob's Team",
+                            "children":
+                                [{  "name": "Bob's VMs",
+                                    "text": "Bob's VMs",
+                                    "id": 21,
+                                    "type": "VmResourcePool"}],
+                            "id": 17,
+                            "type": "HardwarePool"},
+                         {  "name": "Jim's Team",
+                            "text": "Jim's Team",
+                            "children":
+                                [{  "name": "Jim's VMs",
+                                    "text": "Jim's VMs",
+                                    "id": 22,
+                                    "type": "VmResourcePool"}],
+                            "id": 18,
+                            "type": "HardwarePool"},
+                         {  "name": "Sally's Team",
+                            "text": "Sally's Team",
+                            "children":
+                                [{  "name": "Sally's VMs",
+                                    "text": "Sally's VMs",
+                                    "id": 33,
+                                    "type": "VmResourcePool"}],
+                            "id": 32,
+                            "type": "HardwarePool"}],
+                    "id": 10,
+                    "type": "HardwarePool"},
+                 {  "name": "Stage",
+                    "text": "Stage",
+                    "children":
+                        [{  "name": "stage1",
+                            "text": "stage1",
+                            "id": 45,
+                            "type": "HardwarePool"},
+                         {  "name": "stage2",
+                            "text": "stage2",
+                            "id": 46,
+                            "type": "HardwarePool"}],
+                    "id": 44,
+                    "type": "HardwarePool"}],
+            "id": 5,
+            "type": "HardwarePool"},
+         {  "name": "Finance",
+            "text": "Finance",
+            "children":
+                [{  "name": "Payroll",
+                    "text": "Payroll",
+                    "children":
+                        [{  "name": "Payroll VMs",
+                            "text": "Payroll VMs",
+                            "id": 23,
+                            "type": "VmResourcePool"}],
+                    "id": 11,
+                    "type": "HardwarePool"},
+                 {  "name": "Accts. Receivable",
+                     "text": "Accts. Receivable",
+                     "children":
+                         [{  "name": "our VMs",
+                             "text": "our VMs",
+                             "id": 24,
+                             "type": "VmResourcePool"}],
+                     "id": 12,
+                     "type": "HardwarePool"}],
+             "id": 6,
+             "type": "HardwarePool"},
+          {  "name": "HR",
+              "text": "HR",
+              "children":
+                  [{  "name": "Hiring Team",
+                      "text": "Hiring Team",
+                      "id": 13,
+                      "type": "HardwarePool"},
+                   {  "name": "Benefits",
+                       "text": "Benefits",
+                       "id": 14,
+                       "type": "HardwarePool"}],
+               "id": 7,
+               "type": "HardwarePool"},
+          {  "name": "External (DMZ)",
+              "text": "External (DMZ)",
+              "children":
+                  [{  "name": "VMs",
+                      "text": "VMs",
+                      "id": 25,
+                      "type": "VmResourcePool"},
+                   {  "name": "DB Cluster",
+                      "text": "DB Cluster",
+                      "children":
+                          [{  "name": "VMs",
+                              "text": "VMs",
+                              "id": 27,
+                              "type": "VmResourcePool"}],
+                      "id": 26,
+                      "type": "HardwarePool"}],
+              "id": 8,
+              "type": "HardwarePool"}],
+      "id": 1,
+      "type": "HardwarePool"}],
+"smart_pools":[{  "name": "ovirtadmin",
+   "text": "ovirtadmin",
+   "children":
+       [{  "name": "not so smart",
+           "text": "not so smart",
+           "id": 39,
+           "type": "SmartPool"},
+        {  "name": "a little smarter",
+           "text": "a little smarter",
+           "id": 40,
+           "type": "SmartPool"},
+        {  "name": "arrrrr",
+            "text": "arrrrr",
+            "id": 41,
+            "type": "SmartPool"},
+        {  "name": "huh?",
+           "text": "huh?",
+           "id": 42,
+           "type": "SmartPool"},
+        {  "name": "booya",
+           "text": "booya",
+           "id": 43,
+           "type": "SmartPool"}],
+   "id": 37,
+   "type": "DirectoryPool"}]
+}
\ No newline at end of file
diff --git a/src/public/javascripts/test/storage_tree_sample_data.js b/src/public/javascripts/test/storage_tree_sample_data.js
new file mode 100644
index 0000000..f798a45
--- /dev/null
+++ b/src/public/javascripts/test/storage_tree_sample_data.js
@@ -0,0 +1,68 @@
+var storage_pools = {"pools":
+[
+  {
+    "selected":false,
+    "name":"iSCSI: 192.168.50.2:ovirtpriv:storage",
+    "available":false,
+    "children":
+      [
+        {
+          "selected":false,
+          "name":"iSCSI: 192.168.50.2:ovirtpriv:storage:lun-2",
+          "available":true,
+          "children":[],
+          "create_volume":true,
+          "text":"iSCSI: 192.168.50.2:ovirtpriv:storage:lun-2",
+          "id":5,
+          "type":"IscsiStorageVolume",
+          "ui_object": "IscsiStorageVolume_5",
+          "ui_parent": "IscsiStoragePool_2"
+        },
+
+        {
+          "selected":false,
+          "name":"iSCSI: 192.168.50.2:ovirtpriv:storage:lun-3",
+          "available":true,
+          "children":[],
+          "create_volume":true,
+          "text":"iSCSI: 192.168.50.2:ovirtpriv:storage:lun-3",
+          "id":4,
+          "type":"IscsiStorageVolume",
+          "ui_object": "IscsiStorageVolume_4",
+          "ui_parent": "IscsiStoragePool_2"
+        }
+      ],
+    "create_volume":false,
+    "text":"iSCSI: 192.168.50.2:ovirtpriv:storage",
+    "id":2,
+    "type":"IscsiStoragePool",
+    "ui_object": "IscsiStoragePool_2",
+    "ui_parent": null
+  },
+
+  {
+    "selected":false,
+    "name":"iSCSI: 192.68.60.2:/fred",
+    "available":false,
+    "children":[],
+    "create_volume":false,
+    "text":"iSCSI: 192.68.60.2:/fred",
+    "id":7,
+    "type":"IscsiStoragePool",
+    "ui_object": "IscsiStoragePool_7",
+    "ui_parent": null
+  },
+
+  {
+    "selected":false,
+    "name":"iSCSI: 192.168.60.4:/mo",
+    "available":false,
+    "children":[],
+    "create_volume":false,
+    "text":"iSCSI: 192.168.60.4:/mo",
+    "id":6,
+    "type":"IscsiStoragePool",
+    "ui_object": "IscsiStoragePool_6",
+    "ui_parent": null
+  }
+]}
\ No newline at end of file
diff --git a/src/test/fixtures/storage_volumes.yml b/src/test/fixtures/storage_volumes.yml
index a3711bf..8e5b60a 100644
--- a/src/test/fixtures/storage_volumes.yml
+++ b/src/test/fixtures/storage_volumes.yml
@@ -19,6 +19,16 @@ ovirtpriv_storage_lun_3:
   storage_pool: corp_com_ovirtpriv_storage
   type: IscsiStorageVolume
   state: available
+  lvm_pool_id: corp_com_dev_lvm_ovirtlvm
+ovirtpriv_lvm_volume_1:
+  size: 1048576
+  path: /dev/disk/by-id/scsi-S_beaf321013
+  storage_pool: corp_com_dev_lvm_ovirtlvm
+  type: LvmStorageVolume
+  state: available
+  lv_owner_perms: 0744
+  lv_group_perms: 0744
+  lv_mode_perms: 0744
 ovirt_nfs_disk_1:
   size: 3145728
   path: /mnt/a3q49Sj4Sfmae1bV/disk1.dsk
diff --git a/src/test/unit/storage_volume_test.rb b/src/test/unit/storage_volume_test.rb
index 3978685..16be0fb 100644
--- a/src/test/unit/storage_volume_test.rb
+++ b/src/test/unit/storage_volume_test.rb
@@ -1,4 +1,4 @@
-# 
+#
 # Copyright (C) 2008 Red Hat, Inc.
 # Written by Scott Seago <sseago at redhat.com>
 #
@@ -112,4 +112,23 @@ class StorageVolumeTest < Test::Unit::TestCase
     assert_equal @storage_volume.movable?, false, "Storage volume w/ vms should not be movable"
   end
 
+  def test_create_valid_lvm_volume
+#    FIXME: Write this test, using similer steps as in storage_controller#new_volume.
+#    Also add validation to model to make sure the lvm volume's lvm pool has a source volume
+  end
+  def test_return_correct_lvm_ui_parent
+    #test lvm volume values
+    assert_equal storage_volumes(:ovirtpriv_storage_lun_3).type.to_s + '_' +storage_volumes(:ovirtpriv_storage_lun_3).id.to_s,
+      storage_volumes(:ovirtpriv_lvm_volume_1).ui_parent,
+      'Incorrect ui parent returned'
+    #test isci volume values
+    assert_equal storage_volumes(:corp_com_ovirtpriv_storage).type.to_s + '_' +storage_volumes(:corp_com_ovirtpriv_storage).id.to_s,
+      storage_volumes(:ovirtpriv_storage_lun_3).ui_parent,
+      'Incorrect ui parent returned'
+    #test nfs volume values
+    assert_equal storage_volumes(:corp_com_nfs_ovirtnfs).type.to_s + '_' +storage_volumes(:corp_com_nfs_ovirtnfs).id.to_s,
+      storage_volumes(:ovirt_nfs_disk_3).ui_parent,
+      'Incorrect ui parent returned'
+  end
+
 end
-- 
1.5.6.6




More information about the ovirt-devel mailing list