summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Shubin <james@shubin.ca>2013-12-31 17:06:03 -0500
committerJames Shubin <james@shubin.ca>2013-12-31 17:06:03 -0500
commit2b1e94f559a5d952068ed1ffb5051ca0c6e6dd04 (patch)
tree8fa2eedb8d701a31a50a681608721288155e1faf
parenta8d2d9f73e66670e5b9ef1ebff84cc2eb14da603 (diff)
downloadpuppet-gluster-2b1e94f559a5d952068ed1ffb5051ca0c6e6dd04.tar.gz
puppet-gluster-2b1e94f559a5d952068ed1ffb5051ca0c6e6dd04.tar.xz
puppet-gluster-2b1e94f559a5d952068ed1ffb5051ca0c6e6dd04.zip
Add VRRP integration.
This adds VRRP integration to puppet-gluster. All you need to do is set vrrp => true, and set a vip, and the rest should happen automatically. The shared keepalived password is built by a distributed password selection algorithm that I made up. Feel free to review this if you'd like. It's probably as secure as your puppet server and clients are. If you'd prefer to specify each token manually, you can do so in the gluster::host password argument, or you can set one global vrrp password in the gluster::server or gluster::simple classes. There's a chance that you'll see a bit of VRRP flip-flop when you add/remove hosts because the distributed password should change. The benefit is that by default you don't need to set or manage any of those passwords! This doesn't add firewalling so that the VIP can be used by clients.
-rw-r--r--README1
-rw-r--r--lib/facter/gluster_vrrp.rb199
-rw-r--r--manifests/host.pp91
-rw-r--r--manifests/server.pp9
-rw-r--r--manifests/simple.pp4
5 files changed, 303 insertions, 1 deletions
diff --git a/README b/README
index f175561..816a0b5 100644
--- a/README
+++ b/README
@@ -20,6 +20,7 @@ Dependencies:
* puppetlabs-stdlib (required)
* my puppet-common module (optional)
* my puppet-shorewall module (optional)
+* my puppet-keepalived module (optional)
* my puppet-puppet module (optional)
* my puppet-yum module (optional)
* gluster packages (see above notes)
diff --git a/lib/facter/gluster_vrrp.rb b/lib/facter/gluster_vrrp.rb
new file mode 100644
index 0000000..1c35758
--- /dev/null
+++ b/lib/facter/gluster_vrrp.rb
@@ -0,0 +1,199 @@
+# 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/>.
+
+require 'facter'
+require 'digest/sha1'
+require 'ipaddr'
+
+length = 16
+# pass regexp
+regexp = /^[a-zA-Z0-9]{#{length}}$/
+ipregexp = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
+netmaskregexp = /^(((128|192|224|240|248|252|254)\.0\.0\.0)|(255\.(0|128|192|224|240|248|252|254)\.0\.0)|(255\.255\.(0|128|192|224|240|248|252|254)\.0)|(255\.255\.255\.(0|128|192|224|240|248|252|254)))$/
+chars = [('a'..'z'), ('A'..'Z'), (0..9)].map { |i| i.to_a }.flatten
+
+
+# find the module_vardir
+dir = Facter.value('puppet_vardirtmp') # nil if missing
+if dir.nil? # let puppet decide if present!
+ dir = Facter.value('puppet_vardir')
+ if dir.nil?
+ var = nil
+ else
+ var = dir.gsub(/\/$/, '')+'/'+'tmp/' # ensure trailing slash
+ end
+else
+ var = dir.gsub(/\/$/, '')+'/'
+end
+
+if var.nil?
+ # if we can't get a valid vardirtmp, then we can't continue
+ vrrpfile = nil
+ ipfile = nil
+else
+ module_vardir = var+'gluster/'
+ vrrpdir = module_vardir+'vrrp/'
+ vrrpfile = vrrpdir+'vrrp'
+ ipfile = vrrpdir+'ip'
+end
+
+# NOTE: module specific mkdirs, needed to ensure there is no blocking/deadlock!
+if not(var.nil?) and not File.directory?(var)
+ Dir::mkdir(var)
+end
+
+if not(module_vardir.nil?) and not File.directory?(module_vardir)
+ Dir::mkdir(module_vardir)
+end
+
+if not(vrrpdir.nil?) and not File.directory?(vrrpdir)
+ Dir::mkdir(vrrpdir)
+end
+
+# generate pass and parent directory if they don't already exist...
+if not(module_vardir.nil?) and File.directory?(module_vardir)
+ if not File.directory?(vrrpdir)
+ Dir::mkdir(vrrpdir)
+ end
+
+ # create a pass and store it in our vardir if it doesn't already exist!
+ if File.directory?(vrrpdir) and ((not File.exist?(vrrpfile)) or (File.size(vrrpfile) == 0))
+ # include a built-in pwgen-like backup
+ string = (0..length-1).map { chars[rand(chars.length)] }.join
+ result = system("(/usr/bin/test -z /usr/bin/pwgen && /usr/bin/pwgen -N 1 #{length} || /bin/echo '#{string}') > '" + vrrpfile + "'")
+ if not(result)
+ # TODO: print warning
+ end
+ end
+end
+
+# create the fact if the vrrp file contains a valid pass
+if not(vrrpfile.nil?) and File.exist?(vrrpfile)
+ pass = File.open(vrrpfile, 'r').read.strip # read into str
+ # skip over pass's of the wrong length or that don't match (security!!)
+ if pass.length == length and regexp.match(pass)
+ Facter.add('gluster_vrrp') do
+ #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+ setcode {
+ # don't reuse pass variable to avoid bug #:
+ # http://projects.puppetlabs.com/issues/22455
+ pass
+ }
+ end
+ # TODO: print warning on else...
+ end
+end
+
+# create facts from externally collected vrrp files
+_pass = ''
+found = {}
+prefix = 'vrrp_'
+if not(vrrpdir.nil?) and File.directory?(vrrpdir)
+ Dir.glob(vrrpdir+prefix+'*').each do |f|
+
+ b = File.basename(f)
+ # strip off leading prefix
+ fqdn = b[prefix.length, b.length-prefix.length]
+
+ _pass = File.open(f, 'r').read.strip.downcase # read into str
+ if _pass.length == length and regexp.match(_pass)
+ # avoid: http://projects.puppetlabs.com/issues/22455
+ found[fqdn] = _pass
+ # TODO: print warning on else...
+ end
+ end
+end
+
+#found.keys.each do |x|
+# Facter.add('gluster_vrrp_'+x) do
+# #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+# setcode {
+# found[x]
+# }
+# end
+#end
+
+#Facter.add('gluster_vrrp_facts') do
+# #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+# setcode {
+# found.keys.collect {|x| 'gluster_vrrp_'+x }.join(',')
+# }
+#end
+
+# distributed password (uses a piece from each host)
+collected = found.keys.sort.collect {|x| found[x] }.join('#') # combine pieces
+Facter.add('gluster_vrrp_password') do
+ #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+ setcode {
+ Digest::SHA1.hexdigest(collected)
+ }
+end
+
+Facter.add('gluster_vrrp_fqdns') do
+ #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+ setcode {
+ # sorting is very important
+ found.keys.sort.join(',')
+ }
+end
+
+# create these facts if the ip file contains a valid ip address
+if not(ipfile.nil?) and File.exist?(ipfile)
+ ip = File.open(ipfile, 'r').read.strip.downcase # read into str
+ # skip over ip that doesn't match (security!!)
+ if ipregexp.match(ip)
+
+ # TODO: replace with system-getifaddrs if i can get it working!
+ cmd = "/sbin/ip -o a show to #{ip} | /bin/awk '{print $2}'"
+ interface = `#{cmd}`.strip
+ if $?.exitstatus == 0 and interface.length > 0
+
+ Facter.add('gluster_vrrp_interface') do
+ #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+ setcode {
+ interface
+ }
+ end
+
+ # lookup from fact
+ netmask = Facter.value('netmask_'+interface)
+ if netmaskregexp.match(netmask)
+
+ Facter.add('gluster_vrrp_netmask') do
+ #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+ setcode {
+ netmask
+ }
+ end
+
+ cidr = IPAddr.new("#{netmask}").to_i.to_s(2).count('1')
+ Facter.add('gluster_vrrp_cidr') do
+ #confine :operatingsystem => %w{CentOS, RedHat, Fedora}
+ setcode {
+ cidr
+ }
+ end
+ end
+
+ # TODO: print warning on else...
+ end
+
+ # TODO: print warning on else...
+ end
+end
+
+# vim: ts=8
diff --git a/manifests/host.pp b/manifests/host.pp
index 9cd7b03..21df69d 100644
--- a/manifests/host.pp
+++ b/manifests/host.pp
@@ -21,7 +21,8 @@
define gluster::host(
$ip = $::ipaddress, # specify which ip address to use (if multiple)
- $uuid = '' # if empty, puppet will attempt to use the gluster fact
+ $uuid = '', # if empty, puppet will attempt to use the gluster fact
+ $password = '' # if empty, puppet will attempt to choose one magically
) {
include gluster::vardir
@@ -205,6 +206,94 @@ define gluster::host(
}
}
+ # vrrp...
+ $vrrp = $::gluster::server::vrrp
+ if ( "${fqdn}" == "${name}" ) and $vrrp {
+
+ $vip = $::gluster::server::vip
+ if ! ($vip =~ /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/) {
+ fail('You must specify a valid VIP to use with VRRP.')
+ }
+
+ file { "${vardir}/vrrp/":
+ ensure => directory, # make sure this is a directory
+ recurse => true, # recurse into directory
+ purge => true, # purge unmanaged files
+ force => true, # purge subdirs and links
+ require => File["${vardir}/"],
+ }
+
+ # store so that a fact can figure out the interface and cidr...
+ file { "${vardir}/vrrp/ip":
+ content => "${ip}\n",
+ owner => root,
+ group => root,
+ mode => 600, # might as well...
+ ensure => present,
+ require => File["${vardir}/vrrp/"],
+ }
+
+ # NOTE: this is a tag to protect the pass file...
+ file { "${vardir}/vrrp/vrrp":
+ content => "${password}" ? {
+ '' => undef,
+ default => "${password}",
+ },
+ owner => root,
+ group => root,
+ mode => 600, # might as well...
+ ensure => present,
+ require => File["${vardir}/vrrp/"],
+ }
+
+ # NOTE: $name here should probably be the fqdn...
+ @@file { "${vardir}/vrrp/vrrp_${name}":
+ content => "${::gluster_vrrp}\n",
+ tag => 'gluster_vrrp',
+ owner => root,
+ group => root,
+ mode => 600,
+ ensure => present,
+ }
+
+ File <<| tag == 'gluster_vrrp' |>> { # collect to make facts
+ }
+
+ # this figures out the interface from the $ip value
+ $if = "${::gluster_vrrp_interface}" # a smart fact!
+ $cidr = "${::gluster_vrrp_cidr}" # even smarter!
+ $p = "${::gluster::server::password}" ? { # shh secret...
+ '' => "${::gluster_vrrp_password}", # combined fact
+ default => "${::gluster::server::password}",
+ }
+ # this fact is sorted, which is very, very important...!
+ $fqdns_fact = "${::gluster_vrrp_fqdns}" # fact !
+ $fqdns = split($fqdns_fact, ',') # list !
+
+ if "${if}" != '' and "${cidr}" != '' and "${p}" != '' {
+
+ keepalived::vrrp { 'VI_GLUSTER': # TODO: groups!
+ state => "${fqdns[0]}" ? { # first in list
+ '' => 'MASTER', # list is empty
+ "${fqdn}" => 'MASTER', # we are first!
+ default => 'BACKUP', # other in list
+ },
+ interface => "${if}",
+ mcastsrc => "${ip}",
+ # TODO: support configuring the label index!
+ # label ethX:1 for first VIP ethX:2 for second...
+ ipaddress => "${vip}/${cidr} dev ${if} label ${if}:1",
+ # FIXME: this limits puppet-gluster to 256 hosts maximum
+ priority => inline_template("<%= 255 - (@fqdns.index('${fqdn}') or 0) %>"),
+ routerid => 42, # TODO: support configuring it!
+ advertint => 3, # TODO: support configuring it!
+ password => "${p}",
+ #group => 'gluster', # TODO: groups!
+ watchip => "${vip}",
+ }
+ }
+ }
+
# firewalling...
$shorewall = $::gluster::server::shorewall
if ( "${fqdn}" == "${name}" ) and $shorewall {
diff --git a/manifests/server.pp b/manifests/server.pp
index 8c2da38..158f873 100644
--- a/manifests/server.pp
+++ b/manifests/server.pp
@@ -20,6 +20,8 @@ class gluster::server(
$nfs = false, # TODO
$repo = true, # add a repo automatically? true or false
$version = '', # pick a specific version (defaults to latest)
+ $vrrp = false,
+ $password = '', # global vrrp password to use
$shorewall = false,
$zone = 'net', # TODO: allow a list of zones
$ips = false, # an optional list of ip's for each in hosts[]
@@ -115,6 +117,13 @@ class gluster::server(
require => File['/var/lib/glusterd/'],
}
+ if $vrrp {
+ class { '::keepalived':
+ start => true,
+ shorewall => $shorewall,
+ }
+ }
+
if $shorewall {
# XXX: WIP
#if type($ips) == 'array' {
diff --git a/manifests/simple.pp b/manifests/simple.pp
index 63bc914..c750196 100644
--- a/manifests/simple.pp
+++ b/manifests/simple.pp
@@ -23,6 +23,8 @@ class gluster::simple(
$vip = '', # strongly recommended
$repo = true,
$version = '',
+ $vrrp = false,
+ $password = '', # global vrrp password to use
$shorewall = true
) {
include gluster::vardir
@@ -62,6 +64,8 @@ class gluster::simple(
vip => "${vip}",
repo => $repo,
version => "${version}",
+ vrrp => $vrrp,
+ password => "${password}",
#zone => 'net', # defaults to net
shorewall => $shorewall,
}