diff options
author | James Shubin <james@shubin.ca> | 2014-03-11 12:14:52 -0400 |
---|---|---|
committer | James Shubin <james@shubin.ca> | 2014-03-16 22:39:07 -0400 |
commit | 170780287dd99191d1ea908c78ce88489366c0e0 (patch) | |
tree | 6be1a0d31c0b0a638960d6095a3f18f6b976ce1f | |
parent | 1152b69c58e3f4a6bb959ee1521a1ab68daa5599 (diff) | |
download | puppet-gluster-170780287dd99191d1ea908c78ce88489366c0e0.tar.gz puppet-gluster-170780287dd99191d1ea908c78ce88489366c0e0.tar.xz puppet-gluster-170780287dd99191d1ea908c78ce88489366c0e0.zip |
Add per volume support for brick chaining.
* Don't use this feature unless you _really_ know what you're doing.
* Managing chained volumes is much harder than managing normal ones.
* If some of the volumes in the cluster use this, and others don't, then
you'll probably have an even crazier time with management.
* Please verify my algorithm and feel free to suggest changes.
* Some edge cases haven't been tested.
* This patch breaks out brick layout ordering into individual functions.
-rw-r--r-- | lib/puppet/parser/functions/brick_layout_chained.rb | 137 | ||||
-rw-r--r-- | lib/puppet/parser/functions/brick_layout_simple.rb | 105 | ||||
-rw-r--r-- | manifests/simple.pp | 2 | ||||
-rw-r--r-- | manifests/volume.pp | 12 |
4 files changed, 255 insertions, 1 deletions
diff --git a/lib/puppet/parser/functions/brick_layout_chained.rb b/lib/puppet/parser/functions/brick_layout_chained.rb new file mode 100644 index 0000000..072fba9 --- /dev/null +++ b/lib/puppet/parser/functions/brick_layout_chained.rb @@ -0,0 +1,137 @@ +# GlusterFS module by James +# Copyright (C) 2010-2013+ James Shubin +# Written by James Shubin <james@shubin.ca> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +module Puppet::Parser::Functions + newfunction(:brick_layout_chained, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Return the complex chained brick list + + Example: + + $layout = brick_layout_chained($replica, $bricks) + notice("layout is: ${layout}") + + This function is used internally for automatic brick layouts. + + ENDHEREDOC + + # signature: node, zone -> ip + unless args.length == 2 + raise Puppet::ParseError, "brick_layout_chained(): wrong number of arguments (#{args.length}; must be 2)" + end + if not(args[0].is_a?(Integer)) and not(args[0].is_a?(String)) + # NOTE: strings that convert to int's with .to_i are ok + raise Puppet::ParseError, "brick_layout_chained(): expects the first argument to be an integer, got #{args[0].inspect} which is of type #{args[0].class}" + end + unless args[1].is_a?(Array) + raise Puppet::ParseError, "brick_layout_chained(): expects the first argument to be an array, got #{args[1].inspect} which is of type #{args[1].class}" + end + + replica = args[0].to_i # convert from string if needed + bricks = args[1] + + # TODO: these functions could be in separate puppet files + # eg: Puppet::Parser::Functions.function('myfunc') + # function_myfunc(...) + def brick_str_to_hash(bricks) + # this loop converts brick strings to brick dict's... + result = [] + bricks.each do |x| + a = x.split(':') + #assert a.length == 2 # TODO + p = a[1] + p = ((p[-1, 1] == '/') ? p : (p+'/')) # endswith + + result.push({'host'=> a[0], 'path'=> p}) + end + return result + end + + def get_hostlist(bricks) + hosts = [] + bricks.each do |x| + key = x['host'] + val = x['path'] + + if not hosts.include?(key) + hosts.push(key) + end + end + return hosts + end + + def get_brickstacks(bricks, sort=false) + stacks = {} + hosts = get_hostlist(bricks) + bricks.each do |x| + key = x['host'] + val = x['path'] + if not stacks.include?(key); stacks[key] = []; end # initialize + stacks[key].push(val) + end + + # optionally sort the paths in each individual host stack... + if sort + sorted_stacks = {} + stacks.each do |k, v| + # TODO: there should probably be a proper 'sorted' function for + # paths, in case they aren't numbered sanely _WITH_ padding. + sorted_stacks[k] = v.sort + end + return sorted_stacks + end + return stacks + end + + final = [] + pointer = 0 + parsed = brick_str_to_hash(bricks) + # TODO: there should probably be a proper 'sorted' function for + # hostnames, in case they aren't numbered sanely _WITH_ padding. + hosts = get_hostlist(bricks).sort + brickstack = get_brickstacks(bricks, sort=true) + + if bricks.length == 0; return []; end + + # FIXME: this works with homogeneous volumes only! + while pointer < (hosts.length * brickstack[hosts[0]].length) do + start = hosts[pointer % hosts.length] + #puts "host is #{host}, pointer is: #{pointer}" + + index = 0 + while index < replica do + host = hosts[(pointer+index) % hosts.length] + #puts "host is #{host}, index is: #{index}" + index+= 1 + + path = brickstack[host].shift # yoink + if path.nil? + # TODO: is raise okay? + raise Puppet::ParseError, "brick_layout_chained(): brick list is not valid" + next + end + final.append({'host' => host, 'path' => path}) # save + end + pointer+=1 + end + + # build final result + result = final.collect {|x| x['host']+':'+x['path'] } + result # return + end +end + +# vim: ts=8 diff --git a/lib/puppet/parser/functions/brick_layout_simple.rb b/lib/puppet/parser/functions/brick_layout_simple.rb new file mode 100644 index 0000000..7b3a6eb --- /dev/null +++ b/lib/puppet/parser/functions/brick_layout_simple.rb @@ -0,0 +1,105 @@ +# GlusterFS module by James +# Copyright (C) 2010-2013+ James Shubin +# Written by James Shubin <james@shubin.ca> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +module Puppet::Parser::Functions + newfunction(:brick_layout_simple, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Return the simple symmetrical brick list + + Example: + + $layout = brick_layout_simple($replica, $bricks) + notice("layout is: ${layout}") + + This function is used internally for automatic brick layouts. + + ENDHEREDOC + + # signature: node, zone -> ip + unless args.length == 2 + raise Puppet::ParseError, "brick_layout_simple(): wrong number of arguments (#{args.length}; must be 2)" + end + if not(args[0].is_a?(Integer)) and not(args[0].is_a?(String)) + # NOTE: strings that convert to int's with .to_i are ok + raise Puppet::ParseError, "brick_layout_simple(): expects the first argument to be an integer, got #{args[0].inspect} which is of type #{args[0].class}" + end + unless args[1].is_a?(Array) + raise Puppet::ParseError, "brick_layout_simple(): expects the first argument to be an array, got #{args[1].inspect} which is of type #{args[1].class}" + end + + replica = args[0].to_i # convert from string if needed + bricks = args[1] + + # TODO: these functions could be in separate puppet files + # eg: Puppet::Parser::Functions.function('myfunc') + # function_myfunc(...) + def brick_str_to_hash(bricks) + # this loop converts brick strings to brick dict's... + result = [] + bricks.each do |x| + a = x.split(':') + #assert a.length == 2 # TODO + p = a[1] + p = ((p[-1, 1] == '/') ? p : (p+'/')) # endswith + + result.push({'host'=> a[0], 'path'=> p}) + end + return result + end + + collect = {} + parsed = brick_str_to_hash(bricks) + parsed.each do |x| + key = x['host'] + val = x['path'] + + if not collect.has_key?(key) + collect[key] = [] # initialize + end + + collect[key].push(val) # save in array + # TODO: ensure this array is always sorted (we could also do this after + # or always insert elements in the correct sorted order too :P) + collect[key] = collect[key].sort + end + + # we also could do this sort here... + collect.keys.each do |x| + collect[x] = collect[x].sort + end + + final = [] # final order... + # TODO: here we can probably detect if this is an asymmetrical configurations, or maybe bad naming... + while collect.size > 0 + collect.keys.sort.each do |x| + + # NOTE: this array should already be sorted! + p = collect[x].shift # assume an array of at least 1 element + final.push( { 'host' => x, 'path' => p } ) # save + + if collect[x].size == 0 # maybe the array is empty now + collect.delete(x) # remove that empty list's key + end + end + end + + # build final result + result = final.collect {|x| x['host']+':'+x['path'] } + result # return + end +end + +# vim: ts=8 diff --git a/manifests/simple.pp b/manifests/simple.pp index fc4da96..dbd43ed 100644 --- a/manifests/simple.pp +++ b/manifests/simple.pp @@ -20,6 +20,7 @@ class gluster::simple( $volume = 'puppet', # NOTE: this can be a list... $replica = 1, $stripe = 1, # TODO: not fully implemented in puppet-gluster + $layout = '', # brick layout to use (default, chained, etc...) $vip = '', # strongly recommended $vrrp = false, $password = '', # global vrrp password to use @@ -158,6 +159,7 @@ class gluster::simple( gluster::volume { $valid_volumes: replica => $replica, stripe => $stripe, + layout => "${layout}", # NOTE: with this method you do not choose the order of course! # the gluster_fqdns fact is alphabetical, but not complete till # at least a puppet run of each node has occured. watch out for diff --git a/manifests/volume.pp b/manifests/volume.pp index 645c090..c6e285d 100644 --- a/manifests/volume.pp +++ b/manifests/volume.pp @@ -21,6 +21,9 @@ define gluster::volume( $transport = 'tcp', $replica = 1, $stripe = 1, + # TODO: maybe this should be called 'chained' => true/false, and maybe, + # we can also specify an offset count for chaining, or other parameters + $layout = '', # brick layout to use (default, chained, etc...) $vip = '', # vip of the cluster (optional but recommended) $ping = true, # do we want to include fping checks ? $settle = true, # do we want to run settle checks ? @@ -76,9 +79,15 @@ define gluster::volume( $gluster_brick_group_fact = getvar("gluster_brick_group_${group}") $collected_bricks = split($gluster_brick_group_fact, ',') + # run the appropriate layout function here + $ordered_brick_layout = $layout ? { + 'chained' => brick_layout_chained($replica, $collected_bricks), + default => brick_layout_simple($replica, $collected_bricks), + } + $valid_bricks = type($bricks) ? { 'boolean' => $bricks ? { - true => $collected_bricks, # an array... + true => $ordered_brick_layout, # an array... default => [], # invalid type }, 'list' => $bricks, @@ -86,6 +95,7 @@ define gluster::volume( } # helpful debugging! + notice(inline_template('collected_bricks: <%= @collected_bricks.inspect %>')) notice(inline_template('valid_bricks: <%= @valid_bricks.inspect %>')) # NOTE: we're using the valid_bricks value here, and not the collected |