This is a helper patch to add all of the necessary information to
nodes and groups to enable them to get classes/groups/params with
their complete history.

Signed-off-by: Nick Lewis <[email protected]>
---
 app/models/node.rb       |   42 +++-----------------
 app/models/node_group.rb |   26 +++++++++++-
 lib/node_group_graph.rb  |   96 +++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 117 insertions(+), 47 deletions(-)

diff --git a/app/models/node.rb b/app/models/node.rb
index ddfbb92..42ed344 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -97,7 +97,7 @@ class Node < ActiveRecord::Base
   end
 
   def inherited_classes
-    (node_group_list - [self]).map(&:node_classes).flatten.uniq
+    (node_group_list.keys - [self]).map(&:node_classes).flatten.uniq
   end
 
   def all_classes
@@ -105,7 +105,7 @@ class Node < ActiveRecord::Base
   end
 
   def configuration
-    { 'name' => name, 'classes' => all_classes.collect(&:name), 'parameters' 
=> compiled_parameters }
+    { 'name' => name, 'classes' => all_classes.collect(&:name), 'parameters' 
=> parameter_list }
   end
 
   def to_yaml(opts={})
@@ -116,40 +116,10 @@ class Node < ActiveRecord::Base
     TimelineEvent.for_node(self)
   end
 
-  # This wrapper method is just used to cache the result of the recursive 
method
-  def compiled_parameters(allow_conflicts=false)
-    unless @compiled_parameters
-      @compiled_parameters, @conflicts = compile_subgraph_parameters(self, 
node_group_graph)
-      @conflicts.each do |key|
-        errors.add(:parameters,key)
-      end
-    end
-    raise ParameterConflictError unless allow_conflicts or @conflicts.empty?
-    @compiled_parameters
-  end
-
-  # Walks the graph of node groups for the given node, compiling parameters by
-  # merging down (preferring parameters specified in node groups that are
-  # nearer). Raises a ParameterConflictError if parameters at the same distance
-  # from the node have the same name.
-  def compile_subgraph_parameters(group,subgraph)
-    children = subgraph.map do |child,child_subgraph|
-      compile_subgraph_parameters(child,child_subgraph)
-    end
-    # Pick-up conflicts that our children had
-    conflicts = children.map(&:last).inject(Set.new,&:merge)
-    params = group.parameters.to_hash
-    inherited = {}
-    # Now collect our inherited params and their conflicts
-    children.map(&:first).map {|h| [*h]}.flatten.each_slice(2) do |key,value|
-      conflicts.add(key) if inherited[key] && inherited[key] != value
-      inherited[key] = value
-    end
-    # Resolve all possible conflicts
-    conflicts.each do |key|
-      conflicts.delete(key) if params[key]
-    end
-    [params.reverse_merge(inherited), conflicts]
+  # Placeholder attributes
+  
+  def environment
+    'production'
   end
 
   def status_class
diff --git a/app/models/node_group.rb b/app/models/node_group.rb
index 036b033..b82e86d 100644
--- a/app/models/node_group.rb
+++ b/app/models/node_group.rb
@@ -12,8 +12,11 @@ class NodeGroup < ActiveRecord::Base
   has_many :node_group_edges_out, :class_name => "NodeGroupEdge", :foreign_key 
=> 'from_id', :dependent => :destroy
   has_many :node_group_edges_in, :class_name => "NodeGroupEdge", :foreign_key 
=> 'to_id', :dependent => :destroy
 
-  # TODO Want to add a list of groups have edges into us, may want to rename 
node_groups
-  has_many :node_groups, :through => :node_group_edges_out, :source => :to
+  has_many :node_group_children, :class_name => "NodeGroup", :through => 
:node_group_edges_in, :source => :from
+  has_many :node_group_parents, :class_name => "NodeGroup", :through => 
:node_group_edges_out, :source => :to
+
+  # Alias for compatibility with Node
+  alias :node_groups :node_group_parents
 
   has_parameters
 
@@ -69,5 +72,24 @@ class NodeGroup < ActiveRecord::Base
 
   def self.find_from_form_ids(*ids)
     ids.map{|entry| entry.to_s.split(/[ 
,]/)}.flatten.reject(&:blank?).uniq.map{|id| self.find(id)}
+    begin
+      self.node_groups = (@node_group_names || []).map{|name| 
NodeGroup.find_by_name(name)}
+    rescue ActiveRecord::RecordInvalid => e
+      self.errors.add_to_base(e.message)
+      return false
+    end
+  end
+
+  def node_group_child_list
+    return @node_group_child_list if @node_group_child_list
+    all = {}
+    self.walk(:node_group_children,:loops => false) do |group,children|
+      children.each do |child|
+        all[child] ||= Set.new
+        all[child] << group
+      end
+      group
+    end
+    @node_group_child_list = all
   end
 end
diff --git a/lib/node_group_graph.rb b/lib/node_group_graph.rb
index 7f00514..c5bc3cf 100644
--- a/lib/node_group_graph.rb
+++ b/lib/node_group_graph.rb
@@ -1,17 +1,95 @@
 module NodeGroupGraph
+  # Returns a hash of all the groups for this group/node, direct or inherited.
+  # Each key is a group, and each value is the Set of parents for that group.
+  def node_group_list
+    return @node_group_list if @node_group_list
+    all = {}
+    self.walk(:node_groups,:loops => false) do |group,children|
+      children.each do |child|
+        all[child] ||= Set.new
+        all[child] << group
+      end
+      group
+    end
+    @node_group_list = all
+  end
+
   def node_group_graph
-    @node_group_graph ||= compile_node_group_graph.last
+    @node_group_graph ||= self.walk(:node_groups,:loops => false) do 
|group,children|
+      {group => children.inject({},&:merge)}
+    end.values.first
   end
 
-  def node_group_list
-    @node_group_list ||= compile_node_group_graph.first
+  def node_class_list
+    return @node_class_list if @node_class_list
+    all = {}
+    self.walk(:node_groups,:loops => false) do |group,_|
+      group.node_classes.each do |node_class|
+        all[node_class] ||= Set.new
+        all[node_class] << group
+      end
+      group
+    end
+    @node_class_list = all
+  end
+
+  # Collects all the parameters of the node, starting at the "most distant" 
groups
+  # and working its ways up to the node itself. If there is a conflict between 
two
+  # groups at the same level, the conflict is deferred to the next level. If 
the
+  # conflict reaches the top without being resolved, a ParameterConflictError 
is
+  # raised.
+  def compiled_parameters(allow_conflicts=false)
+    unless @compiled_parameters
+      @compiled_parameters,@conflicts = self.walk(:node_groups,:loops => 
false) do |group,children|
+        # Pick-up conflicts that our children had
+        conflicts = children.map(&:last).inject(Set.new,&:merge)
+        params = group.parameters.to_hash.map { |key,value|
+          {key => [value,Set[group]]}
+        }.inject({},&:merge)
+        inherited = {}
+        # Now collect our inherited params and their conflicts
+        children.map(&:first).map {|h| [*h]}.flatten.each_slice(3) do 
|key,value,source|
+          if inherited[key] && inherited[key].first != value
+            conflicts.add(key)
+            inherited[key].last << source.first
+          else
+            inherited[key] = [value,source]
+          end
+        end
+        # Resolve all possible conflicts
+        conflicts.each do |key|
+          conflicts.delete(key) if params[key]
+        end
+        [params.reverse_merge(inherited), conflicts]
+      end
+      @conflicts.each { |key| errors.add(:parameters,key) }
+    end
+    raise ParameterConflictError unless allow_conflicts or @conflicts.empty?
+    @compiled_parameters
+  end
+
+  def parameter_list
+    compiled_parameters.map{|key,value_sources_pair| {key => 
value_sources_pair.first}}.inject({},&:merge)
   end
 
-  private
-  def compile_node_group_graph(group=self, seen=[], all=[])
-    return [nil,{}] if seen.include? group
-    all << group
-    graph = group.node_groups.map {|grp| {grp => compile_node_group_graph(grp, 
seen + [group], all).last}}.inject({},&:merge)
-    [all.uniq, graph]
+  # Options include
+  #  - loops [true|false] : whether to allow loops. If set to false, will 
return nil when a node is
+  #                         visited a second time on a single branch
+  # NL: Possible options that might need to be added some day:
+  #  - compact [true|false] : whether to flatten the returned list. This 
always happens now.
+  #  - default : the value to return when a loop is found. This is currently 
always nil.
+  #  - memo [true|false] : whether to memoize the results for a particular 
node. This doesn't happen now.
+  def walk(branch_method,options={},&block)
+    def do_dfs(branch_method,options,all,seen,&block)
+      return nil if seen.include?(self) and options[:loops] == false
+      all << self
+      results = self.send(branch_method).map{|b| 
b.do_dfs(branch_method,options,all,seen+[self],&block)}.compact
+      yield self,results
+    end
+    options.reverse_merge({:loops => false})
+    return unless block
+    seen = []
+    all = []
+    do_dfs(branch_method,options,all,seen,&block)
   end
 end
-- 
1.7.2.1

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to