diff options
21 files changed, 936 insertions, 0 deletions
diff --git a/puppet/modules/haproxy/.fixtures.yml b/puppet/modules/haproxy/.fixtures.yml new file mode 100644 index 00000000..8d6f22d6 --- /dev/null +++ b/puppet/modules/haproxy/.fixtures.yml @@ -0,0 +1,5 @@ +fixtures: + repositories: + concat: "git://github.com/ripienaar/puppet-concat.git" + symlinks: + haproxy: "#{source_dir}" diff --git a/puppet/modules/haproxy/.gemfile b/puppet/modules/haproxy/.gemfile new file mode 100644 index 00000000..9aad840c --- /dev/null +++ b/puppet/modules/haproxy/.gemfile @@ -0,0 +1,5 @@ +source :rubygems + +puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 2.7'] +gem 'puppet', puppetversion +gem 'puppetlabs_spec_helper', '>= 0.1.0' diff --git a/puppet/modules/haproxy/.gitrepo b/puppet/modules/haproxy/.gitrepo new file mode 100644 index 00000000..ed92831a --- /dev/null +++ b/puppet/modules/haproxy/.gitrepo @@ -0,0 +1,11 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = https://leap.se/git/puppet_haproxy + branch = master + commit = af322a73c013f80a958ab7d5d31d0c75cf6d0523 + parent = 04279dd8d1390d61d696d2c14817199304ccd4d8 + cmdver = 0.3.0 diff --git a/puppet/modules/haproxy/.travis.yml b/puppet/modules/haproxy/.travis.yml new file mode 100644 index 00000000..fdbc95dc --- /dev/null +++ b/puppet/modules/haproxy/.travis.yml @@ -0,0 +1,23 @@ +language: ruby +rvm: + - 1.8.7 + - 1.9.3 +script: "rake spec" +branches: + only: + - master +env: + - PUPPET_VERSION=2.6.17 + - PUPPET_VERSION=2.7.19 + #- PUPPET_VERSION=3.0.1 # Breaks due to rodjek/rspec-puppet#58 +notifications: + email: false +gemfile: .gemfile +matrix: + exclude: + - rvm: 1.9.3 + gemfile: .gemfile + env: PUPPET_VERSION=2.6.17 + - rvm: 1.8.7 + gemfile: .gemfile + env: PUPPET_VERSION=3.0.1 diff --git a/puppet/modules/haproxy/CHANGELOG b/puppet/modules/haproxy/CHANGELOG new file mode 100644 index 00000000..0b6d670f --- /dev/null +++ b/puppet/modules/haproxy/CHANGELOG @@ -0,0 +1,5 @@ +2012-10-12 - Version 0.2.0 +- Initial public release +- Backwards incompatible changes all around +- No longer needs ordering passed for more than one listener +- Accepts multiple listen ips/ports/server_names diff --git a/puppet/modules/haproxy/Modulefile b/puppet/modules/haproxy/Modulefile new file mode 100644 index 00000000..e729739b --- /dev/null +++ b/puppet/modules/haproxy/Modulefile @@ -0,0 +1,12 @@ +name 'puppetlabs-haproxy' +version '0.2.0' +source 'git://github.com/puppetlabs/puppetlabs-haproxy' +author 'Puppet Labs' +license 'Apache License, Version 2.0' +summary 'Haproxy Module' +description 'An Haproxy module for Redhat family OSes using Storeconfigs' +project_page 'http://github.com/puppetlabs/puppetlabs-haproxy' + +## Add dependencies, if any: +# dependency 'username/name', '>= 1.2.0' +dependency 'ripienaar/concat', '>= 0.1.0' diff --git a/puppet/modules/haproxy/README.md b/puppet/modules/haproxy/README.md new file mode 100644 index 00000000..d209e9ab --- /dev/null +++ b/puppet/modules/haproxy/README.md @@ -0,0 +1,87 @@ +PuppetLabs Module for haproxy +============================= + +HAProxy is an HA proxying daemon for load-balancing to clustered services. It +can proxy TCP directly, or other kinds of traffic such as HTTP. + +Dependencies +------------ + +Tested and built on Debian, Ubuntu and CentOS + +Currently requires the ripienaar/concat module on the Puppet Forge and uses storeconfigs on the Puppet Master to export/collect resources +from all balancer members. + +Basic Usage +----------- + +This haproxy uses storeconfigs to collect and realize balancer member servers +on a load balancer server. + +*To install and configure HAProxy server listening on port 8140* + +```puppet +node 'haproxy-server' { + class { 'haproxy': } + haproxy::listen { 'puppet00': + ipaddress => $::ipaddress, + ports => '8140', + } +} +``` + +*To add backend loadbalance members* + +```puppet +node 'webserver01' { + @@haproxy::balancermember { $fqdn: + listening_service => 'puppet00', + server_names => $::hostname, + ipaddresses => $::ipaddress, + ports => '8140', + options => 'check' + } +} +``` + +Configuring haproxy options +--------------------------- + +The base `haproxy` class can accept two parameters which will configure basic +behaviour of the haproxy server daemon: + +- `global_options` to configure the `global` section in `haproxy.cfg` +- `defaults_options` to configure the `defaults` section in `haproxy.cfg` + +Configuring haproxy daemon listener +----------------------------------- + +One `haproxy::listen` defined resource should be defined for each HAProxy loadbalanced set of backend servers. The title of the `haproxy::listen` resource is the key to which balancer members will be proxied to. The `ipaddress` field should be the public ip address which the loadbalancer will be contacted on. The `ports` attribute can accept an array or comma-separated list of ports which should be proxied to the `haproxy::balancermemeber` nodes. + +Configuring haproxy loadbalanced member nodes +--------------------------------------------- + +The `haproxy::balacemember` defined resource should be exported from each node +which is serving loadbalanced traffic. the `listening_service` attribute will +associate it with `haproxy::listen` directives on the haproxy node. +`ipaddresses` and `ports` will be assigned to the member to be contacted on. If an array of `ipaddresses` and `server_names` are provided then they will be added to the config in lock-step. + + +Copyright and License +--------------------- + +Copyright (C) 2012 [Puppet Labs](https://www.puppetlabs.com/) Inc + +Puppet Labs can be contacted at: info@puppetlabs.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/puppet/modules/haproxy/Rakefile b/puppet/modules/haproxy/Rakefile new file mode 100644 index 00000000..cd3d3799 --- /dev/null +++ b/puppet/modules/haproxy/Rakefile @@ -0,0 +1 @@ +require 'puppetlabs_spec_helper/rake_tasks' diff --git a/puppet/modules/haproxy/manifests/balancermember.pp b/puppet/modules/haproxy/manifests/balancermember.pp new file mode 100644 index 00000000..a0e27539 --- /dev/null +++ b/puppet/modules/haproxy/manifests/balancermember.pp @@ -0,0 +1,95 @@ +# == Define Resource Type: haproxy::balancermember +# +# This type will setup a balancer member inside a listening service +# configuration block in /etc/haproxy/haproxy.cfg on the load balancer. +# currently it only has the ability to specify the instance name, +# ip address, port, and whether or not it is a backup. More features +# can be added as needed. The best way to implement this is to export +# this resource for all haproxy balancer member servers, and then collect +# them on the main haproxy load balancer. +# +# === Requirement/Dependencies: +# +# Currently requires the ripienaar/concat module on the Puppet Forge and +# uses storeconfigs on the Puppet Master to export/collect resources +# from all balancer members. +# +# === Parameters +# +# [*name*] +# The title of the resource is arbitrary and only utilized in the concat +# fragment name. +# +# [*listening_service*] +# The haproxy service's instance name (or, the title of the +# haproxy::listen resource). This must match up with a declared +# haproxy::listen resource. +# +# [*ports*] +# An array or commas-separated list of ports for which the balancer member +# will accept connections from the load balancer. Note that cookie values +# aren't yet supported, but shouldn't be difficult to add to the +# configuration. If you use an array in server_names and ipaddresses, the +# same port is used for all balancermembers. +# +# [*server_names*] +# The name of the balancer member server as known to haproxy in the +# listening service's configuration block. This defaults to the +# hostname. Can be an array of the same length as ipaddresses, +# in which case a balancermember is created for each pair of +# server_names and ipaddresses (in lockstep). +# +# [*ipaddresses*] +# The ip address used to contact the balancer member server. +# Can be an array, see documentation to server_names. +# +# [*options*] +# An array of options to be specified after the server declaration +# in the listening service's configuration block. +# +# +# === Examples +# +# Exporting the resource for a balancer member: +# +# @@haproxy::balancermember { 'haproxy': +# listening_service => 'puppet00', +# ports => '8140', +# server_names => $::hostname, +# ipaddresses => $::ipaddress, +# options => 'check', +# } +# +# +# Collecting the resource on a load balancer +# +# Haproxy::Balancermember <<| listening_service == 'puppet00' |>> +# +# Creating the resource for multiple balancer members at once +# (for single-pass installation of haproxy without requiring a first +# pass to export the resources if you know the members in advance): +# +# haproxy::balancermember { 'haproxy': +# listening_service => 'puppet00', +# ports => '8140', +# server_names => ['server01', 'server02'], +# ipaddresses => ['192.168.56.200', '192.168.56.201'], +# options => 'check', +# } +# +# (this resource can be declared anywhere) +# +define haproxy::balancermember ( + $listening_service, + $ports, + $server_names = $::hostname, + $ipaddresses = $::ipaddress, + $options = '' +) { + # Template uses $ipaddresses, $server_name, $ports, $option + concat::fragment { "${listening_service}_balancermember_${name}": + order => "20-${listening_service}-${name}", + target => '/etc/haproxy/haproxy.cfg', + content => template('haproxy/haproxy_balancermember.erb'), + } +} diff --git a/puppet/modules/haproxy/manifests/init.pp b/puppet/modules/haproxy/manifests/init.pp new file mode 100644 index 00000000..b91591a3 --- /dev/null +++ b/puppet/modules/haproxy/manifests/init.pp @@ -0,0 +1,149 @@ +# == Class: haproxy +# +# A Puppet module, using storeconfigs, to model an haproxy configuration. +# Currently VERY limited - Pull requests accepted! +# +# === Requirement/Dependencies: +# +# Currently requires the ripienaar/concat module on the Puppet Forge and +# uses storeconfigs on the Puppet Master to export/collect resources +# from all balancer members. +# +# === Parameters +# +# [*enable*] +# Chooses whether haproxy should be installed or ensured absent. +# Currently ONLY accepts valid boolean true/false values. +# +# [*version*] +# Allows you to specify what version of the package to install. +# Default is simply 'present' +# +# [*global_options*] +# A hash of all the haproxy global options. If you want to specify more +# than one option (i.e. multiple timeout or stats options), pass those +# options as an array and you will get a line for each of them in the +# resultant haproxy.cfg file. +# +# [*defaults_options*] +# A hash of all the haproxy defaults options. If you want to specify more +# than one option (i.e. multiple timeout or stats options), pass those +# options as an array and you will get a line for each of them in the +# resultant haproxy.cfg file. +# +# +# === Examples +# +# class { 'haproxy': +# enable => true, +# global_options => { +# 'log' => "${::ipaddress} local0", +# 'chroot' => '/var/lib/haproxy', +# 'pidfile' => '/var/run/haproxy.pid', +# 'maxconn' => '4000', +# 'user' => 'haproxy', +# 'group' => 'haproxy', +# 'daemon' => '', +# 'stats' => 'socket /var/lib/haproxy/stats' +# }, +# defaults_options => { +# 'log' => 'global', +# 'stats' => 'enable', +# 'option' => 'redispatch', +# 'retries' => '3', +# 'timeout' => [ +# 'http-request 10s', +# 'queue 1m', +# 'connect 10s', +# 'client 1m', +# 'server 1m', +# 'check 10s' +# ], +# 'maxconn' => '8000' +# }, +# } +# +class haproxy ( + $manage_service = true, + $enable = true, + $version = 'present', + $global_options = $haproxy::params::global_options, + $defaults_options = $haproxy::params::defaults_options +) inherits haproxy::params { + include concat::setup + + package { 'haproxy': + ensure => $enable ? { + true => $version, + false => absent, + }, + name => 'haproxy', + } + + if $enable { + concat { '/etc/haproxy/haproxy.cfg': + owner => '0', + group => '0', + mode => '0644', + require => Package['haproxy'], + notify => $manage_service ? { + true => Service['haproxy'], + false => undef, + }, + } + + # Simple Header + concat::fragment { '00-header': + target => '/etc/haproxy/haproxy.cfg', + order => '01', + content => "# This file managed by Puppet\n", + } + + # Template uses $global_options, $defaults_options + concat::fragment { 'haproxy-base': + target => '/etc/haproxy/haproxy.cfg', + order => '10', + content => template('haproxy/haproxy-base.cfg.erb'), + } + + if ($::osfamily == 'Debian') { + file { '/etc/default/haproxy': + content => 'ENABLED=1', + require => Package['haproxy'], + before => $manage_service ? { + true => Service['haproxy'], + false => undef, + }, + } + } + + file { $global_options['chroot']: + ensure => directory, + owner => $global_options['user'], + group => $global_options['group'], + mode => '0550', + require => Package['haproxy'] + } + + } + + if $manage_service { + service { 'haproxy': + ensure => $enable ? { + true => running, + false => stopped, + }, + enable => $enable ? { + true => true, + false => false, + }, + name => 'haproxy', + hasrestart => true, + hasstatus => true, + require => [ + Concat['/etc/haproxy/haproxy.cfg'], + File[$global_options['chroot']], + ], + } + } +} diff --git a/puppet/modules/haproxy/manifests/listen.pp b/puppet/modules/haproxy/manifests/listen.pp new file mode 100644 index 00000000..00636e3d --- /dev/null +++ b/puppet/modules/haproxy/manifests/listen.pp @@ -0,0 +1,95 @@ +# == Define Resource Type: haproxy::listen +# +# This type will setup a listening service configuration block inside +# the haproxy.cfg file on an haproxy load balancer. Each listening service +# configuration needs one or more load balancer member server (that can be +# declared with the haproxy::balancermember defined resource type). Using +# storeconfigs, you can export the haproxy::balancermember resources on all +# load balancer member servers, and then collect them on a single haproxy +# load balancer server. +# +# === Requirement/Dependencies: +# +# Currently requires the ripienaar/concat module on the Puppet Forge and +# uses storeconfigs on the Puppet Master to export/collect resources +# from all balancer members. +# +# === Parameters +# +# [*name*] +# The namevar of the defined resource type is the listening service's name. +# This name goes right after the 'listen' statement in haproxy.cfg +# +# [*ports*] +# Ports on which the proxy will listen for connections on the ip address +# specified in the virtual_ip parameter. Accepts either a single +# comma-separated string or an array of strings which may be ports or +# hyphenated port ranges. +# +# [*ipaddress*] +# The ip address the proxy binds to. Empty addresses, '*', and '0.0.0.0' +# mean that the proxy listens to all valid addresses on the system. +# +# [*mode*] +# The mode of operation for the listening service. Valid values are 'tcp', +# HTTP', and 'health'. +# +# [*options*] +# A hash of options that are inserted into the listening service +# configuration block. +# +# [*collect_exported*] +# Boolean, default 'true'. True means 'collect exported @@balancermember resources' +# (for the case when every balancermember node exports itself), false means +# 'rely on the existing declared balancermember resources' (for the case when you +# know the full set of balancermembers in advance and use haproxy::balancermember +# with array arguments, which allows you to deploy everything in 1 run) +# +# +# === Examples +# +# Exporting the resource for a balancer member: +# +# haproxy::listen { 'puppet00': +# ipaddress => $::ipaddress, +# ports => '18140', +# mode => 'tcp', +# options => { +# 'option' => [ +# 'tcplog', +# 'ssl-hello-chk' +# ], +# 'balance' => 'roundrobin' +# }, +# } +# +# === Authors +# +# Gary Larizza <gary@puppetlabs.com> +# +define haproxy::listen ( + $ports, + $ipaddress = [$::ipaddress], + $mode = 'tcp', + $collect_exported = true, + $options = { + 'option' => [ + 'tcplog', + 'ssl-hello-chk' + ], + 'balance' => 'roundrobin' + } +) { + # Template uses: $name, $ipaddress, $ports, $options + concat::fragment { "${name}_listen_block": + order => "20-${name}-00", + target => '/etc/haproxy/haproxy.cfg', + content => template('haproxy/haproxy_listen_block.erb'), + } + + if $collect_exported { + Haproxy::Balancermember <<| listening_service == $name |>> + } + # else: the resources have been created and they introduced their + # concat fragments. We don't have to do anything about them. +} diff --git a/puppet/modules/haproxy/manifests/params.pp b/puppet/modules/haproxy/manifests/params.pp new file mode 100644 index 00000000..53442ddc --- /dev/null +++ b/puppet/modules/haproxy/manifests/params.pp @@ -0,0 +1,65 @@ +# == Class: haproxy::params +# +# This is a container class holding default parameters for for haproxy class. +# currently, only the Redhat family is supported, but this can be easily +# extended by changing package names and configuration file paths. +# +class haproxy::params { + case $osfamily { + Redhat: { + $global_options = { + 'log' => "${::ipaddress} local0", + 'chroot' => '/var/lib/haproxy', + 'pidfile' => '/var/run/haproxy.pid', + 'maxconn' => '4000', + 'user' => 'haproxy', + 'group' => 'haproxy', + 'daemon' => '', + 'stats' => 'socket /var/lib/haproxy/stats' + } + $defaults_options = { + 'log' => 'global', + 'stats' => 'enable', + 'option' => 'redispatch', + 'retries' => '3', + 'timeout' => [ + 'http-request 10s', + 'queue 1m', + 'connect 10s', + 'client 1m', + 'server 1m', + 'check 10s', + ], + 'maxconn' => '8000' + } + } + Debian: { + $global_options = { + 'log' => "${::ipaddress} local0", + 'chroot' => '/var/lib/haproxy', + 'pidfile' => '/var/run/haproxy.pid', + 'maxconn' => '4000', + 'user' => 'haproxy', + 'group' => 'haproxy', + 'daemon' => '', + 'stats' => 'socket /var/lib/haproxy/stats' + } + $defaults_options = { + 'log' => 'global', + 'stats' => 'enable', + 'option' => 'redispatch', + 'retries' => '3', + 'timeout' => [ + 'http-request 10s', + 'queue 1m', + 'connect 10s', + 'client 1m', + 'server 1m', + 'check 10s', + ], + 'maxconn' => '8000' + } + } + default: { fail("The $::osfamily operating system is not supported with the haproxy module") } + } +} diff --git a/puppet/modules/haproxy/spec/classes/haproxy_spec.rb b/puppet/modules/haproxy/spec/classes/haproxy_spec.rb new file mode 100644 index 00000000..4b5902ce --- /dev/null +++ b/puppet/modules/haproxy/spec/classes/haproxy_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' + +describe 'haproxy', :type => :class do + let(:default_facts) do + { + :concat_basedir => '/dne', + :ipaddress => '10.10.10.10' + } + end + context 'on supported platforms' do + describe 'for OS-agnostic configuration' do + ['Debian', 'RedHat'].each do |osfamily| + context "on #{osfamily} family operatingsystems" do + let(:facts) do + { :osfamily => osfamily }.merge default_facts + end + let(:params) do + {'enable' => true} + end + it { should include_class('concat::setup') } + it 'should install the haproxy package' do + subject.should contain_package('haproxy').with( + 'ensure' => 'present' + ) + end + it 'should install the haproxy service' do + subject.should contain_service('haproxy').with( + 'ensure' => 'running', + 'enable' => 'true', + 'hasrestart' => 'true', + 'hasstatus' => 'true', + 'require' => [ + 'Concat[/etc/haproxy/haproxy.cfg]', + 'File[/var/lib/haproxy]' + ] + ) + end + it 'should set up /etc/haproxy/haproxy.cfg as a concat resource' do + subject.should contain_concat('/etc/haproxy/haproxy.cfg').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644' + ) + end + it 'should manage the chroot directory' do + subject.should contain_file('/var/lib/haproxy').with( + 'ensure' => 'directory' + ) + end + it 'should contain a header concat fragment' do + subject.should contain_concat__fragment('00-header').with( + 'target' => '/etc/haproxy/haproxy.cfg', + 'order' => '01', + 'content' => "# This file managed by Puppet\n" + ) + end + it 'should contain a haproxy-base concat fragment' do + subject.should contain_concat__fragment('haproxy-base').with( + 'target' => '/etc/haproxy/haproxy.cfg', + 'order' => '10' + ) + end + describe 'Base concat fragment contents' do + let(:contents) { param_value(subject, 'concat::fragment', 'haproxy-base', 'content').split("\n") } + it 'should contain global and defaults sections' do + contents.should include('global') + contents.should include('defaults') + end + it 'should log to an ip address for local0' do + contents.should be_any { |match| match =~ / log \d+(\.\d+){3} local0/ } + end + it 'should specify the default chroot' do + contents.should include(' chroot /var/lib/haproxy') + end + it 'should specify the correct user' do + contents.should include(' user haproxy') + end + it 'should specify the correct group' do + contents.should include(' group haproxy') + end + it 'should specify the correct pidfile' do + contents.should include(' pidfile /var/run/haproxy.pid') + end + end + end + context "on #{osfamily} family operatingsystems without managing the service" do + let(:facts) do + { :osfamily => osfamily }.merge default_facts + end + let(:params) do + { + 'enable' => true, + 'manage_service' => false, + } + end + it { should include_class('concat::setup') } + it 'should install the haproxy package' do + subject.should contain_package('haproxy').with( + 'ensure' => 'present' + ) + end + it 'should install the haproxy service' do + subject.should_not contain_service('haproxy') + end + end + end + end + describe 'for OS-specific configuration' do + context 'only on Debian family operatingsystems' do + let(:facts) do + { :osfamily => 'Debian' }.merge default_facts + end + it 'should manage haproxy service defaults' do + subject.should contain_file('/etc/default/haproxy').with( + 'before' => 'Service[haproxy]', + 'require' => 'Package[haproxy]' + ) + verify_contents(subject, '/etc/default/haproxy', ['ENABLED=1']) + end + end + context 'only on RedHat family operatingsystems' do + let(:facts) do + { :osfamily => 'RedHat' }.merge default_facts + end + end + end + end + context 'on unsupported operatingsystems' do + let(:facts) do + { :osfamily => 'RainbowUnicorn' }.merge default_facts + end + it do + expect { + should contain_service('haproxy') + }.to raise_error(Puppet::Error, /operating system is not supported with the haproxy module/) + end + end +end diff --git a/puppet/modules/haproxy/spec/defines/balancermember_spec.rb b/puppet/modules/haproxy/spec/defines/balancermember_spec.rb new file mode 100644 index 00000000..74bc7a8b --- /dev/null +++ b/puppet/modules/haproxy/spec/defines/balancermember_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe 'haproxy::balancermember' do + let(:title) { 'tyler' } + let(:facts) do + { + :ipaddress => '1.1.1.1', + :hostname => 'dero' + } + end + + context 'with a single balancermember option' do + let(:params) do + { + :name => 'tyler', + :listening_service => 'croy', + :ports => '18140', + :options => 'check' + } + end + + it { should contain_concat__fragment('croy_balancermember_tyler').with( + 'order' => '20-croy-tyler', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => " server dero 1.1.1.1:18140 check\n\n" + ) } + end + + context 'with multiple balancermember options' do + let(:params) do + { + :name => 'tyler', + :listening_service => 'croy', + :ports => '18140', + :options => ['check', 'close'] + } + end + + it { should contain_concat__fragment('croy_balancermember_tyler').with( + 'order' => '20-croy-tyler', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => " server dero 1.1.1.1:18140 check close\n\n" + ) } + end + + context 'with multiple servers' do + let(:params) do + { + :name => 'tyler', + :listening_service => 'croy', + :ports => '18140', + :server_names => ['server01', 'server02'], + :ipaddresses => ['192.168.56.200', '192.168.56.201'], + :options => ['check'] + } + end + + it { should contain_concat__fragment('croy_balancermember_tyler').with( + 'order' => '20-croy-tyler', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => " server server01 192.168.56.200:18140 check\n server server02 192.168.56.201:18140 check\n\n" + ) } + end + context 'with multiple servers and multiple ports' do + let(:params) do + { + :name => 'tyler', + :listening_service => 'croy', + :ports => ['18140','18150'], + :server_names => ['server01', 'server02'], + :ipaddresses => ['192.168.56.200', '192.168.56.201'], + :options => ['check'] + } + end + + it { should contain_concat__fragment('croy_balancermember_tyler').with( + 'order' => '20-croy-tyler', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => " server server01 192.168.56.200:18140,192.168.56.200:18150 check\n server server02 192.168.56.201:18140,192.168.56.201:18150 check\n\n" + ) } + end +end diff --git a/puppet/modules/haproxy/spec/defines/listen_spec.rb b/puppet/modules/haproxy/spec/defines/listen_spec.rb new file mode 100644 index 00000000..31dd4c85 --- /dev/null +++ b/puppet/modules/haproxy/spec/defines/listen_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe 'haproxy::listen' do + let(:title) { 'tyler' } + let(:facts) {{ :ipaddress => '1.1.1.1' }} + context "when only one port is provided" do + let(:params) do + { + :name => 'croy', + :ports => '18140' + } + end + + it { should contain_concat__fragment('croy_listen_block').with( + 'order' => '20-croy-00', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => "listen croy\n\n bind 1.1.1.1:18140\n\n balance roundrobin\n option tcplog\n option ssl-hello-chk\n" + ) } + end + context "when an array of ports is provided" do + let(:params) do + { + :name => 'apache', + :ipaddress => '23.23.23.23', + :ports => [ + '80', + '443', + ] + } + end + + it { should contain_concat__fragment('apache_listen_block').with( + 'order' => '20-apache-00', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => "listen apache\n\n bind 23.23.23.23:80\n\n bind 23.23.23.23:443\n\n balance roundrobin\n option tcplog\n option ssl-hello-chk\n" + ) } + end + context "when a comma-separated list of ports is provided" do + let(:params) do + { + :name => 'apache', + :ipaddress => '23.23.23.23', + :ports => '80,443' + } + end + + it { should contain_concat__fragment('apache_listen_block').with( + 'order' => '20-apache-00', + 'target' => '/etc/haproxy/haproxy.cfg', + 'content' => "listen apache\n\n bind 23.23.23.23:80\n\n bind 23.23.23.23:443\n\n balance roundrobin\n option tcplog\n option ssl-hello-chk\n" + ) } + end +end diff --git a/puppet/modules/haproxy/spec/spec.opts b/puppet/modules/haproxy/spec/spec.opts new file mode 100644 index 00000000..91cd6427 --- /dev/null +++ b/puppet/modules/haproxy/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/puppet/modules/haproxy/spec/spec_helper.rb b/puppet/modules/haproxy/spec/spec_helper.rb new file mode 100644 index 00000000..2c6f5664 --- /dev/null +++ b/puppet/modules/haproxy/spec/spec_helper.rb @@ -0,0 +1 @@ +require 'puppetlabs_spec_helper/module_spec_helper' diff --git a/puppet/modules/haproxy/templates/haproxy-base.cfg.erb b/puppet/modules/haproxy/templates/haproxy-base.cfg.erb new file mode 100644 index 00000000..f25d5c34 --- /dev/null +++ b/puppet/modules/haproxy/templates/haproxy-base.cfg.erb @@ -0,0 +1,21 @@ +global +<% @global_options.sort.each do |key,val| -%> +<% if val.is_a?(Array) -%> +<% val.each do |item| -%> + <%= key %> <%= item %> +<% end -%> +<% else -%> + <%= key %> <%= val %> +<% end -%> +<% end -%> + +defaults +<% @defaults_options.sort.each do |key,val| -%> +<% if val.is_a?(Array) -%> +<% val.each do |item| -%> + <%= key %> <%= item %> +<% end -%> +<% else -%> + <%= key %> <%= val %> +<% end -%> +<% end -%> diff --git a/puppet/modules/haproxy/templates/haproxy_balancermember.erb b/puppet/modules/haproxy/templates/haproxy_balancermember.erb new file mode 100644 index 00000000..1d03f565 --- /dev/null +++ b/puppet/modules/haproxy/templates/haproxy_balancermember.erb @@ -0,0 +1,3 @@ +<% Array(ipaddresses).zip(Array(server_names)).each do |ipaddress,host| -%> + server <%= host %> <%= ipaddress %>:<%= Array(ports).collect {|x|x.split(',')}.flatten.join(",#{ipaddress}:") %> <%= Array(options).join(" ") %> +<% end %> diff --git a/puppet/modules/haproxy/templates/haproxy_listen_block.erb b/puppet/modules/haproxy/templates/haproxy_listen_block.erb new file mode 100644 index 00000000..129313f1 --- /dev/null +++ b/puppet/modules/haproxy/templates/haproxy_listen_block.erb @@ -0,0 +1,10 @@ +listen <%= name %> + mode <%= mode %> +<% Array(ipaddress).uniq.each do |virtual_ip| (ports.is_a?(Array) ? ports : Array(ports.split(","))).each do |port| %> + bind <%= virtual_ip %>:<%= port %> +<% end end %> +<% options.sort.each do |key, val| -%> +<% Array(val).each do |item| -%> + <%= key %> <%= item %> +<% end -%> +<% end -%> diff --git a/puppet/modules/haproxy/tests/init.pp b/puppet/modules/haproxy/tests/init.pp new file mode 100644 index 00000000..77590ac8 --- /dev/null +++ b/puppet/modules/haproxy/tests/init.pp @@ -0,0 +1,69 @@ +# Declare haproxy base class with configuration options +class { 'haproxy': + enable => true, + global_options => { + 'log' => "${::ipaddress} local0", + 'chroot' => '/var/lib/haproxy', + 'pidfile' => '/var/run/haproxy.pid', + 'maxconn' => '4000', + 'user' => 'haproxy', + 'group' => 'haproxy', + 'daemon' => '', + 'stats' => 'socket /var/lib/haproxy/stats', + }, + defaults_options => { + 'log' => 'global', + 'stats' => 'enable', + 'option' => 'redispatch', + 'retries' => '3', + 'timeout' => [ + 'http-request 10s', + 'queue 1m', + 'connect 10s', + 'client 1m', + 'server 1m', + 'check 10s', + ], + 'maxconn' => '8000', + }, +} + +# Export a balancermember server, note that the listening_service parameter +# will/must correlate with an haproxy::listen defined resource type. +@@haproxy::balancermember { $fqdn: + order => '21', + listening_service => 'puppet00', + server_name => $::hostname, + balancer_ip => $::ipaddress, + balancer_port => '8140', + balancermember_options => 'check' +} + +# Declare a couple of Listening Services for haproxy.cfg +# Note that the balancermember server resources are being collected in +# the haproxy::config defined resource type with the following line: +# Haproxy::Balancermember <<| listening_service == $name |>> +haproxy::listen { 'puppet00': + order => '20', + ipaddress => $::ipaddress, + ports => '18140', + options => { + 'option' => [ + 'tcplog', + 'ssl-hello-chk', + ], + 'balance' => 'roundrobin', + }, +} +haproxy::listen { 'stats': + order => '30', + ipaddress => '', + ports => '9090', + options => { + 'mode' => 'http', + 'stats' => [ + 'uri /', + 'auth puppet:puppet' + ], + }, +} |