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.
