vagrant-clusterfuck 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b20a1129ef6b8c782255ea00ddbf8026a45b50d0
4
+ data.tar.gz: 98d03daca3a5d97656db316f603535a6730777e0
5
+ SHA512:
6
+ metadata.gz: cd9624b80c9795f879abca0774f57cd8182cbc740a466a90ec582f75357b4091c184aaa0e5c3b87081cbe0776ab54a1acab727054c403c4a5c774281c2c1b80f
7
+ data.tar.gz: 38e9a07958f3ed4e73e453f53e80ac8d4b3092d335e081b3151606cd863488b9d8df543828b923874eb43ca2bb3d8f026f7e78bba784dff56bd9c0c65864bee8
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ vendor/
16
+ .vagrant/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in clusterfuck.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Simon Eskildsen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # Clusterfuck
2
+
3
+ Clusterfuck is a library for setting up a networked cluster of servers to test
4
+ failure conditions against. It's a library to fuck with clusters.
5
+
6
+ ## Usage
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :test
4
+
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.pattern = "test/**/*_test.rb"
9
+ end
data/Vagrantfile ADDED
@@ -0,0 +1,23 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+ require_relative "lib/clusterfuck"
4
+ require_relative "cluster"
5
+
6
+ Vagrant.configure('2') do |config|
7
+ config.vm.box = "ubuntu/trusty64"
8
+
9
+ config.vm.provider "virtualbox" do |box|
10
+ box.memory = 256
11
+ end
12
+
13
+ cluster = Clusterfuck::Cluster[:test]
14
+ cluster[:spine].build(config)
15
+ cluster[:leaf1].build(config)
16
+
17
+ cluster[:host11].build(config) do |box|
18
+ box.vm.provision :shell, inline: "echo 1 > /tmp/test"
19
+ end
20
+
21
+ cluster[:leaf2].build(config)
22
+ cluster[:host21].build(config)
23
+ end
data/cluster.rb ADDED
@@ -0,0 +1,13 @@
1
+ include Clusterfuck::LeafSpineDSL
2
+
3
+ cluster :test do
4
+ spine :spine do
5
+ leaf :leaf1 do
6
+ host :host11
7
+ end
8
+
9
+ leaf :leaf2 do
10
+ host :host21, driver: Clusterfuck::BGPPeer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'clusterfuck/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "vagrant-clusterfuck"
8
+ spec.version = Clusterfuck::VERSION
9
+ spec.authors = ["Simon Eskildsen"]
10
+ spec.email = ["sirup@sirupsen.com"]
11
+ spec.summary = %q{Clusterfuck let's you fuck with clusters}
12
+ spec.description = %q{Clusterfuck let's you fuck with clusters}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "byebug"
24
+ end
@@ -0,0 +1,43 @@
1
+ module Clusterfuck
2
+ class BGPPeer < Machine
3
+ attr_accessor :bgp_asn,
4
+ :bgp_router_id,
5
+ :bgp_announced
6
+
7
+ def initialize(name, **args)
8
+ super
9
+
10
+ @bgp_announced = args[:announced] ? Array(args[:announced]) : []
11
+ @bgp_asn = args[:asn] || ASNFactory.next
12
+ @bgp_router_id = args[:router_id] || RouterIDFactory.next
13
+ end
14
+
15
+ def bgp_announced
16
+ @bgp_announced.map { |a|
17
+ a.kind_of?(SubnetFactory) ? a.subnet : a
18
+ }
19
+ end
20
+
21
+ def bgp_neighbors
22
+ network.adjacent(self).select { |neighbor|
23
+ neighbor.kind_of?(BGPPeer)
24
+ }
25
+ end
26
+
27
+ class ASNFactory
28
+ @asn = 64999
29
+
30
+ def self.next
31
+ @asn += 1
32
+ end
33
+ end
34
+
35
+ class RouterIDFactory
36
+ @id = IPAddr.new("10.0.20.1")
37
+
38
+ def self.next
39
+ @id = @id.succ
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,74 @@
1
+ module Clusterfuck
2
+ class Cluster
3
+ attr_reader :graph, :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ @nodes = {}
8
+ @graph = {}
9
+ end
10
+
11
+ def self.create(name)
12
+ @clusters ||= {}
13
+ @clusters[name] = Cluster.new(name)
14
+ end
15
+
16
+ def self.[](name)
17
+ @clusters[name]
18
+ end
19
+
20
+ def self.all
21
+ @clusters.values
22
+ end
23
+
24
+ def nodes
25
+ @nodes.values
26
+ end
27
+
28
+ def [](name)
29
+ @nodes[name]
30
+ end
31
+
32
+ def connect(subnet_factory, *nodes)
33
+ nodes.each do |node|
34
+ register(node)
35
+
36
+ unless in_subnet?(subnet_factory.subnet, node)
37
+ node.subnets << subnet_factory.next
38
+ node.network = self
39
+ end
40
+ end
41
+
42
+ nodes.permutation(2).each do |(a, b)|
43
+ @graph[a] << b
44
+ end
45
+ end
46
+
47
+ def adjacent?(a, b)
48
+ adjacent(a).include?(b)
49
+ end
50
+
51
+ def adjacent(a)
52
+ @graph[a]
53
+ end
54
+
55
+ def register(node)
56
+ @nodes[node.name] = node unless @nodes[node.name]
57
+ @graph[node] = [] unless @graph[node]
58
+ end
59
+
60
+ def overlapping_subnets(a, b)
61
+ a_subnets, b_subnets = Array(a.subnets), Array(b.subnets)
62
+ a_subnets.select { |a_subnet|
63
+ b_subnets.find { |b_subnet|
64
+ a_subnet.include?(b_subnet)
65
+ }
66
+ }
67
+ end
68
+
69
+ private
70
+ def in_subnet?(subnet, node)
71
+ node.subnets.any? { |ip| subnet.include?(ip) }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,14 @@
1
+ class IPAddr
2
+ def netmask
3
+ _to_string(@mask_addr)
4
+ end
5
+
6
+ def cidr
7
+ IPAddr.new(netmask).to_i.to_s(2).count("1")
8
+ end
9
+
10
+ def to_cidr
11
+ to_string + "/" + cidr.to_s
12
+ end
13
+ end
14
+
@@ -0,0 +1,45 @@
1
+ module Clusterfuck
2
+ module LeafSpineDSL
3
+ def cluster(name, &block)
4
+ @cluster = Cluster.create(name)
5
+ yield(@cluster)
6
+ end
7
+
8
+ def spine(name, **args, &block)
9
+ vip_subnet = args[:vip_subnet] || IPAddr.new("192.168.99.1/24")
10
+ priv_subnet = args[:priv_subnet] || IPAddr.new("10.0.0.0/16")
11
+ args = { announced: [vip_subnet, priv_subnet] }.merge(args)
12
+
13
+ driver = args[:driver] || QuaggaBGPRouter
14
+
15
+ @spine = driver.new(name, args)
16
+ yield(@spine)
17
+ end
18
+
19
+ def leaf(name, **args, &block)
20
+ driver = args[:driver] || QuaggaBGPRouter
21
+ leaf_spine_subnet = args[:spine_subnet] || SubnetFactory.next
22
+
23
+ @leaf = driver.new(name, **args)
24
+ @cluster.connect(leaf_spine_subnet, @leaf, @spine)
25
+
26
+ yield(@leaf)
27
+ end
28
+
29
+ def host(name, **args)
30
+ driver = args[:driver] || Machine
31
+ leaf_subnet = args[:leaf_subnet] || SubnetFactory.next
32
+
33
+ args = {
34
+ gateway: @leaf,
35
+ routes: @spine.bgp_announced,
36
+ }.merge(args)
37
+
38
+ machine = driver.new(name, args)
39
+
40
+ # TODO this sucks
41
+ @leaf.bgp_announced = @leaf.bgp_announced.push(leaf_subnet.subnet)
42
+ @cluster.connect(leaf_subnet, @leaf, machine)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,78 @@
1
+ module Clusterfuck
2
+ class Machine
3
+ attr_reader :name, :ssh_port, :routes, :gateway
4
+ attr_accessor :subnets, :provisioners, :network
5
+
6
+ def initialize(name, **args)
7
+ @name = name
8
+ @subnets = []
9
+ @ssh_port = args[:ssh_port] || PortFactory.next
10
+ @gateway = args[:gateway]
11
+ @routes = args[:routes]
12
+ end
13
+
14
+ def overlapping_subnets(other)
15
+ network.overlapping_subnets(self, other)
16
+ end
17
+
18
+ def build(config)
19
+ config.vm.define(name) do |box|
20
+ box.vm.hostname = name
21
+
22
+ # Set up SSH on a predictable port which allows us to hook into it
23
+ # during tests.
24
+ box.vm.network :forwarded_port,
25
+ guest: 22,
26
+ host: ssh_port,
27
+ id: "ssh"
28
+
29
+ subnets.each do |subnet|
30
+ box.vm.network :private_network,
31
+ ip: subnet.to_s,
32
+ # TODO Troll Rubby people by using a refinement
33
+ netmask: subnet.netmask,
34
+ # TODO Vagrant already provides an abstraction for declaring networks
35
+ # so we should not need to duplicate this abstraction by creating
36
+ # subclasses for each provider. This bullshit needs to be gone for
37
+ # AWS/CI support.
38
+ virtualbox__intnet: true
39
+ end
40
+
41
+ if gateway
42
+ box.vm.provision :file,
43
+ source: tmp_routes_file,
44
+ destination: "/tmp/network-clusterfuck"
45
+
46
+ box.vm.provision :shell,
47
+ inline: <<-EOS
48
+ mv /tmp/network-clusterfuck /etc/network/if-up.d/clusterfuck
49
+ chmod +x /etc/network/if-up.d/clusterfuck
50
+ ifdown eth1
51
+ ifup eth1
52
+ EOS
53
+ end
54
+
55
+ yield box if block_given?
56
+ end
57
+ end
58
+
59
+ private
60
+ # TODO clean this up
61
+ def tmp_routes_file
62
+ path = File.expand_path("../../../templates/routes.sh.erb", __FILE__)
63
+ result = ERB.new(File.read(path)).result(binding)
64
+ temp = Tempfile.new("routes")
65
+ ObjectSpace.undefine_finalizer(temp) # finalizer removes it, nthx
66
+ temp.write(result)
67
+ temp.path
68
+ end
69
+
70
+ class PortFactory
71
+ @port = 28344
72
+
73
+ def self.next
74
+ @port += 1
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,65 @@
1
+ module Clusterfuck
2
+ class QuaggaBGPRouter < BGPPeer
3
+ def build(config)
4
+ super(config)
5
+
6
+ config.vm.define(name) do |box|
7
+ box.vm.provision :shell, inline: <<-EOS
8
+ mkdir -p /tmp/quagga
9
+ chown -R vagrant /tmp/quagga
10
+
11
+ apt-get update
12
+ apt-get -y install quagga
13
+ EOS
14
+
15
+ # TODO should be configured from BGP module if enabled
16
+ # TODO have to move to /tmp/ first and then later to /etc/quagga because
17
+ # Vagrant doens't copy as root.
18
+ box.vm.provision :file,
19
+ source: tmp_daemons_config,
20
+ destination: "/tmp/quagga/daemons"
21
+
22
+ box.vm.provision :file,
23
+ source: tmp_zebra_config,
24
+ destination: "/tmp/quagga/zebra.conf"
25
+
26
+ box.vm.provision :file,
27
+ source: tmp_bgp_config,
28
+ destination: "/tmp/quagga/bgpd.conf"
29
+
30
+ box.vm.provision :shell, inline: <<-EOS
31
+ echo net.ipv4.ip_forward=1 > /etc/sysctl.d/60-clusterfuck.conf
32
+ sysctl -p /etc/sysctl.d/60-clusterfuck.conf
33
+
34
+ mv /tmp/quagga/* /etc/quagga
35
+ chown quagga.quagga /etc/quagga/*.conf
36
+ chmod 640 /etc/quagga/*.conf
37
+
38
+ service quagga restart
39
+ EOS
40
+ end
41
+ end
42
+
43
+ private
44
+ # TODO clean up these messy functions
45
+ def tmp_zebra_config
46
+ File.expand_path("../../../templates/quagga.zebra.conf.erb", __FILE__)
47
+ end
48
+
49
+ def tmp_daemons_config
50
+ File.expand_path("../../../templates/quagga.daemons.erb", __FILE__)
51
+ end
52
+
53
+ def tmp_bgp_config
54
+ temp = Tempfile.new("routes")
55
+ ObjectSpace.undefine_finalizer(temp) # finalizer removes it, nthx
56
+ temp.write(bgp_config)
57
+ temp.path
58
+ end
59
+
60
+ def bgp_config
61
+ path = File.expand_path("../../../templates/quagga.bgpd.conf.erb", __FILE__)
62
+ ERB.new(File.read(path)).result(binding)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,26 @@
1
+ module Clusterfuck
2
+ class SubnetFactory
3
+ attr_reader :subnet
4
+
5
+ START_SUBNET = "10.0.39.0/24"
6
+ @previous = IPAddr.new(START_SUBNET)
7
+
8
+ def initialize(subnet)
9
+ @subnet = subnet
10
+ end
11
+
12
+ def next
13
+ @subnet = @subnet.succ
14
+ end
15
+
16
+ def self.next(cidr = 24)
17
+ # succ twice to start at x.x.x.2
18
+ @previous = @previous.mask(cidr).to_range.last.succ.succ
19
+ SubnetFactory.new(@previous)
20
+ end
21
+
22
+ def self.reset
23
+ @previous = IPAddr.new(START_SUBNET)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ module Clusterfuck
2
+ module TestHelper
3
+ SSHCommandFailedError = Class.new(StandardError)
4
+
5
+ def self.included(base)
6
+ require "./cluster"
7
+ end
8
+
9
+ # We do not check exit status, don't want to control people.
10
+ def ssh_command(host, command, cluster: nil)
11
+ `ssh -i .vagrant/machines/#{host}/virtualbox/private_key vagrant@127.0.0.1 -p #{host(host, cluster: cluster).ssh_port} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o IdentitiesOnly=yes -o LogLevel=fatal "#{command}"`
12
+ end
13
+
14
+ def host(name, cluster: nil)
15
+ cluster(cluster).nodes.find { |node| node.name == name }
16
+ end
17
+
18
+ def ip_to_host(ip, cluster: nil)
19
+ cluster(cluster).nodes.find { |host|
20
+ host.subnets.find { |subnet|
21
+ subnet == ip
22
+ }
23
+ }
24
+ end
25
+
26
+ # TODO add iptables(1) and nc(1) wrappers to cause ~chaos~
27
+ private
28
+ def cluster(name)
29
+ if name
30
+ Clusterfuck::Cluster[name]
31
+ else
32
+ Clusterfuck::Cluster.all.first
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Clusterfuck
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,13 @@
1
+ require "ipaddr"
2
+ require "erb"
3
+ require_relative "clusterfuck/test_helper"
4
+ require_relative "clusterfuck/cluster"
5
+ require_relative "clusterfuck/subnet_factory"
6
+ require_relative "clusterfuck/machine"
7
+ require_relative "clusterfuck/bgp_peer"
8
+ require_relative "clusterfuck/quagga_bgp_router"
9
+ require_relative "clusterfuck/core_ext/ipaddr"
10
+ require_relative "clusterfuck/leaf_spine_dsl"
11
+
12
+ module Clusterfuck
13
+ end
@@ -0,0 +1,43 @@
1
+ ! -*- bgp -*-
2
+ !
3
+ ! BGPd configuration file
4
+ !
5
+ hostname leaf
6
+ password zebra
7
+ !enable password please-set-at-here
8
+ !
9
+ !bgp mulitple-instance
10
+ !
11
+ router bgp <%= bgp_asn %>
12
+ bgp router-id <%= bgp_router_id %>
13
+ timers bgp 1 3
14
+ <% bgp_announced.each do |subnet| %>
15
+ network <%= subnet.to_cidr %>
16
+ <% end %>
17
+
18
+ <% bgp_neighbors.each do |node| %>
19
+ neighbor <%= node.overlapping_subnets(self).first %> remote-as <%= node.bgp_asn %>
20
+ neighbor <%= node.overlapping_subnets(self).first %> route-map DOWNSTREAM in
21
+ neighbor <%= node.overlapping_subnets(self).first %> next-hop-self
22
+ neighbor <%= node.overlapping_subnets(self).first %> advertisement-interval 1
23
+ <% end %>
24
+
25
+ ip community-list standard cm-prefmod-300 permit 65534:300
26
+ ip community-list standard cm-prefmod-200 permit 65534:200
27
+ ip community-list standard cm-prefmod-100 permit 65534:100
28
+
29
+ route-map DOWNSTREAM permit 10
30
+ match community cm-prefmod-300
31
+ set local-preference 300
32
+ route-map DOWNSTREAM permit 20
33
+ match community cm-prefmod-200
34
+ set local-preference 200
35
+ route-map DOWNSTREAM permit 30
36
+ match community cm-prefmod-100
37
+ set local-preference 100
38
+ route-map DOWNSTREAM permit 40
39
+ set local-preference 90
40
+
41
+ log file /var/log/quagga/bgpd.log
42
+ !
43
+ log stdout
@@ -0,0 +1,31 @@
1
+ # This file tells the quagga package which daemons to start.
2
+ #
3
+ # Entries are in the format: <daemon>=(yes|no|priority)
4
+ # 0, "no" = disabled
5
+ # 1, "yes" = highest priority
6
+ # 2 .. 10 = lower priorities
7
+ # Read /usr/share/doc/quagga/README.Debian for details.
8
+ #
9
+ # Sample configurations for these daemons can be found in
10
+ # /usr/share/doc/quagga/examples/.
11
+ #
12
+ # ATTENTION:
13
+ #
14
+ # When activation a daemon at the first time, a config file, even if it is
15
+ # empty, has to be present *and* be owned by the user and group "quagga", else
16
+ # the daemon will not be started by /etc/init.d/quagga. The permissions should
17
+ # be u=rw,g=r,o=.
18
+ # When using "vtysh" such a config file is also needed. It should be owned by
19
+ # group "quaggavty" and set to ug=rw,o= though. Check /etc/pam.d/quagga, too.
20
+ #
21
+ # The watchquagga daemon is always started. Per default in monitoring-only but
22
+ # that can be changed via /etc/quagga/debian.conf.
23
+ #
24
+ zebra=yes
25
+ bgpd=yes
26
+ ospfd=no
27
+ ospf6d=no
28
+ ripd=no
29
+ ripngd=no
30
+ isisd=no
31
+ babeld=no
@@ -0,0 +1,23 @@
1
+ ! -*- zebra -*-
2
+ !
3
+ ! zebra configuration file
4
+ !
5
+ hostname leaf1
6
+ password zebra
7
+ enable password zebra
8
+ !
9
+ ! Interface's description.
10
+ !
11
+ !interface lo
12
+ ! description test of desc.
13
+ !
14
+ !interface sit0
15
+ ! multicast
16
+
17
+ !
18
+ ! Static default route sample.
19
+ !
20
+ !ip route 0.0.0.0/0 203.181.89.241
21
+ !
22
+
23
+ log file /var/log/quagga/zebra.log
@@ -0,0 +1,34 @@
1
+ #!/bin/sh -e
2
+ # Provision routes on Vagrant controled interfaces.
3
+ #
4
+ # We can't do this in `/etc/networks/interfaces` because Vagrant doesn't support
5
+ # adding arbritrary commands to its generated interfaces. This sucks, because it
6
+ # means we have to hardcore the device names and the user has to guess them from
7
+ # the order of the network definitions. The alternative to this is to run the ip
8
+ # commands on every boot, which is arguably even worse.
9
+ #
10
+ # TODO found out `private_network` takes an `auto_config => false` which allows
11
+ # doing the `/etc/networks/interfaces` entry ourselves.
12
+ #
13
+ # TODO Send a patch to Vagrant core allowing specifying arbritrary network
14
+ # interface parameters to the definitions. An example syntax could be:
15
+ #
16
+ # config.vm.network :private_network, ip: "...", netmask: "...",
17
+ # interface: { up: "ip route add .. via .." }
18
+ #
19
+ # `iptool` commands don't need to specify the interface when they're part of of
20
+ # the network hooks in the interface.
21
+
22
+ if [ "$MODE" != start ]; then
23
+ exit 0
24
+ fi
25
+
26
+ if [ "$IFACE" != eth1 ]; then
27
+ exit 0
28
+ fi
29
+
30
+ <% routes.each do |route| %>
31
+ ip route add <%= route.to_cidr %> via <%= gateway.overlapping_subnets(self).first %> dev eth1 || true
32
+ <% end %>
33
+
34
+ exit 0
@@ -0,0 +1,45 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestBGPPeer < Minitest::Unit::TestCase
4
+ include Clusterfuck
5
+
6
+ def setup
7
+ @peer = BGPPeer.new(:peer)
8
+ @peer2 = BGPPeer.new(:peer2)
9
+ @cluster = Cluster.new(:test)
10
+ end
11
+
12
+ def test_assigned_random_asn_in_private_range
13
+ assert_instance_of Fixnum, @peer.bgp_asn
14
+ assert 65000 <= @peer.bgp_asn
15
+ end
16
+
17
+ def test_assigned_random_ipv4_ip_as_router_id
18
+ assert_match(/\d+\.\d+\.\d+\.\d+/, @peer.bgp_router_id.to_s)
19
+ end
20
+
21
+ def test_assigned_successive_asns
22
+ assert_equal @peer.bgp_asn + 1, @peer2.bgp_asn
23
+ end
24
+
25
+ def test_assigned_different_default_ips_as_router_id
26
+ refute_equal @peer.bgp_router_id, @peer2.bgp_router_id
27
+ end
28
+
29
+ def test_bgp_neighbors_returns_direct_neighbours
30
+ @cluster.connect(SubnetFactory.next, @peer, @peer2)
31
+
32
+ assert_equal [@peer2], @peer.bgp_neighbors
33
+ assert_equal [@peer], @peer2.bgp_neighbors
34
+ end
35
+
36
+ def test_bgp_neighbours_doesnt_include_non_bgp_peers
37
+ @non_peer = Machine.new(:test)
38
+
39
+ @cluster.connect(SubnetFactory.next, @peer2, @peer)
40
+ @cluster.connect(SubnetFactory.next, @peer2, @non_peer)
41
+
42
+ assert_equal [@peer2], @peer.bgp_neighbors
43
+ assert_equal [@peer], @peer2.bgp_neighbors
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestTestHelper < Minitest::Unit::TestCase
4
+ include Clusterfuck::TestHelper
5
+
6
+ def test_host
7
+ assert_equal :leaf1, host(:leaf1).name
8
+ end
9
+
10
+ def test_ssh_command_into_node
11
+ output = ssh_command :leaf1, "hostname"
12
+ assert_equal "leaf1\n", output
13
+ end
14
+
15
+ def test_ip_to_host
16
+ spine_ip = host(:spine).subnets.first.to_s
17
+ assert_equal :spine, ip_to_host(spine_ip).name
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestMachine < Minitest::Unit::TestCase
4
+ include Clusterfuck
5
+
6
+ def setup
7
+ @machine = Machine.new(:test)
8
+ end
9
+
10
+ def test_get_name
11
+ assert_equal :test, @machine.name
12
+ end
13
+
14
+ def test_assign_ssh_port
15
+ assert_kind_of Fixnum, @machine.ssh_port
16
+ end
17
+
18
+ def test_assign_successive_ssh_ports
19
+ first = @machine.ssh_port
20
+ second = Machine.new(:test2)
21
+
22
+ assert_equal first + 1, second.ssh_port
23
+ end
24
+ end
@@ -0,0 +1,201 @@
1
+ require "minitest/autorun"
2
+ require "minitest/unit"
3
+ require_relative "../lib/clusterfuck"
4
+
5
+ class TestNetwork < Minitest::Unit::TestCase
6
+ include Clusterfuck
7
+
8
+ def setup
9
+ SubnetFactory.reset
10
+ @cluster = Cluster.new(:test)
11
+ end
12
+
13
+ def test_one_machine_cluster
14
+ one = Machine.new(:one)
15
+ @cluster.register(one)
16
+
17
+ assert_equal({one => []}, @cluster.graph)
18
+ end
19
+
20
+ def test_two_machine_cluster
21
+ one = Machine.new(:one)
22
+ two = Machine.new(:two)
23
+
24
+ @cluster.connect(SubnetFactory.next, one, two)
25
+
26
+ assert_equal({one => [two], two => [one]}, @cluster.graph)
27
+ end
28
+
29
+ def test_simple_tree
30
+ root = Machine.new(:root)
31
+ leaf1 = Machine.new(:leaf1)
32
+ leaf2 = Machine.new(:leaf2)
33
+
34
+ @cluster.connect(SubnetFactory.next, root, leaf1)
35
+ @cluster.connect(SubnetFactory.next, root, leaf2)
36
+
37
+ assert_equal({root => [leaf1, leaf2], leaf1 => [root], leaf2 => [root]}, @cluster.graph)
38
+ end
39
+
40
+ def test_different_ips_on_subnet
41
+ root = Machine.new(:root)
42
+ leaf1 = Machine.new(:leaf1)
43
+ leaf2 = Machine.new(:leaf2)
44
+
45
+ @cluster.connect(SubnetFactory.next, root, leaf1)
46
+ @cluster.connect(SubnetFactory.next, root, leaf2)
47
+
48
+ refute root.subnets.map(&:to_s).include?(leaf1.subnets.first.to_s)
49
+ refute root.subnets.map(&:to_s).include?(leaf2.subnets.first.to_s)
50
+ end
51
+
52
+ def test_adjacent_on_simple_tree
53
+ root = Machine.new(:root)
54
+ leaf1 = Machine.new(:leaf1)
55
+ leaf2 = Machine.new(:leaf2)
56
+
57
+ @cluster.connect(SubnetFactory.next, root, leaf1)
58
+ @cluster.connect(SubnetFactory.next, root, leaf2)
59
+
60
+ assert @cluster.adjacent?(root, leaf1)
61
+ assert @cluster.adjacent?(leaf1, root)
62
+
63
+ assert @cluster.adjacent?(root, leaf2)
64
+ assert @cluster.adjacent?(leaf2, root)
65
+
66
+ refute @cluster.adjacent?(leaf1, leaf2)
67
+ refute @cluster.adjacent?(leaf2, leaf1)
68
+ end
69
+
70
+ def test_apply_subnetwork_to_connected_graph
71
+ one = Machine.new(:one)
72
+ two = Machine.new(:two)
73
+
74
+ @cluster.connect(SubnetFactory.next, one, two)
75
+
76
+ assert_equal [IPAddr.new("10.0.40.2")], one.subnets
77
+ assert_equal [IPAddr.new("10.0.40.3")], two.subnets
78
+
79
+ assert_overlapping_subnets one, two
80
+
81
+ assert_equal 1, one.subnets.size
82
+ assert_equal 1, two.subnets.size
83
+ end
84
+
85
+ def test_apply_subnetwork_to_simple_tree
86
+ root = Machine.new(:root)
87
+ leaf1 = Machine.new(:leaf1)
88
+ leaf2 = Machine.new(:leaf2)
89
+
90
+ @cluster.connect(SubnetFactory.next, root, leaf1)
91
+ @cluster.connect(SubnetFactory.next, root, leaf2)
92
+
93
+ assert_overlapping_subnets root, leaf1
94
+ assert_overlapping_subnets root, leaf2
95
+ refute_overlapping_subnets leaf1, leaf2
96
+
97
+ assert_equal 2, root.subnets.size
98
+ assert_equal 1, leaf1.subnets.size
99
+ assert_equal 1, leaf2.subnets.size
100
+ end
101
+
102
+ def test_apply_subnetwork_to_larger_tree
103
+ root = Machine.new(:root)
104
+
105
+ leaf1 = Machine.new(:leaf1)
106
+ host11 = Machine.new(:host11)
107
+
108
+ leaf2 = Machine.new(:leaf2)
109
+ host21 = Machine.new(:host21)
110
+ host22 = Machine.new(:host21)
111
+
112
+ @cluster.connect(SubnetFactory.next, root, leaf1)
113
+ @cluster.connect(SubnetFactory.next, root, leaf2)
114
+
115
+ leaf2_subnet = SubnetFactory.next
116
+ @cluster.connect(leaf2_subnet, leaf2, host21)
117
+ @cluster.connect(leaf2_subnet, leaf2, host22)
118
+ @cluster.connect(leaf2_subnet, host21, host22)
119
+
120
+ leaf1_subnet = SubnetFactory.next
121
+ @cluster.connect(leaf1_subnet, leaf1, host11)
122
+
123
+ assert_overlapping_subnets root, leaf1
124
+ assert_overlapping_subnets root, leaf2
125
+
126
+ refute_overlapping_subnets leaf1, leaf2
127
+ refute_overlapping_subnets leaf1, host21
128
+ refute_overlapping_subnets leaf1, host22
129
+
130
+ assert_overlapping_subnets leaf2, host21
131
+ assert_overlapping_subnets leaf2, host22
132
+ assert_overlapping_subnets host21, host22
133
+
134
+ assert_overlapping_subnets leaf1, host11
135
+
136
+ assert_equal 2, root.subnets.size
137
+
138
+ assert_equal 2, leaf2.subnets.size
139
+ assert_equal 1, host21.subnets.size
140
+ assert_equal 1, host22.subnets.size
141
+
142
+ assert_equal 2, leaf1.subnets.size
143
+ assert_equal 1, host11.subnets.size
144
+ end
145
+
146
+ def test_overlapping_subnets_with_one
147
+ one = Machine.new(:one)
148
+ two = Machine.new(:two)
149
+
150
+ @cluster.connect(SubnetFactory.next, one, two)
151
+
152
+ assert_equal [IPAddr.new("10.0.40.2")], @cluster.overlapping_subnets(one, two)
153
+ end
154
+
155
+ def test_overlapping_subnets_in_tree
156
+ root = Machine.new(:root)
157
+ leaf1 = Machine.new(:leaf1)
158
+ leaf2 = Machine.new(:leaf2)
159
+
160
+ @cluster.connect(SubnetFactory.next, root, leaf1)
161
+ @cluster.connect(SubnetFactory.next, root, leaf2)
162
+
163
+ assert_equal [IPAddr.new("10.0.40.2")], @cluster.overlapping_subnets(root, leaf1)
164
+ assert_equal [IPAddr.new("10.0.41.2")], @cluster.overlapping_subnets(root, leaf2)
165
+ assert_equal [], @cluster.overlapping_subnets(leaf1, leaf2)
166
+ end
167
+
168
+ def test_support_multiple_arguments_to_connect
169
+ one = Machine.new(:one)
170
+ two = Machine.new(:two)
171
+ three = Machine.new(:three)
172
+
173
+ @cluster.connect(SubnetFactory.next, one, two, three)
174
+
175
+ assert_overlapping_subnets one, two
176
+ assert_overlapping_subnets one, three
177
+ assert_overlapping_subnets two, three
178
+ end
179
+
180
+ private
181
+
182
+ def assert_overlapping_subnets(a, b)
183
+ assert overlapping_subnets?(a, b), <<-EOE
184
+ Expected networks to overlap:
185
+ #{a.name}: #{a.subnets}
186
+ #{b.name}: #{b.subnets}
187
+ EOE
188
+ end
189
+
190
+ def refute_overlapping_subnets(a, b)
191
+ refute overlapping_subnets?(a, b), <<-EOE
192
+ Expected networks to not overlap:
193
+ #{a.name}: #{a.subnets}
194
+ #{b.name}: #{b.subnets}
195
+ EOE
196
+ end
197
+
198
+ def overlapping_subnets?(a, b)
199
+ @cluster.overlapping_subnets(a, b).any?
200
+ end
201
+ end
@@ -0,0 +1,48 @@
1
+ require "minitest/autorun"
2
+ require "minitest/unit"
3
+ require_relative "../lib/clusterfuck"
4
+
5
+ class TestSubnet < Minitest::Unit::TestCase
6
+ include Clusterfuck
7
+
8
+ def setup
9
+ SubnetFactory.reset
10
+ end
11
+
12
+ def test_next_subnet
13
+ subnet = SubnetFactory.next
14
+ assert_equal IPAddr.new("10.0.40.2"), subnet.next
15
+ end
16
+
17
+ def test_next_ip_on_subnet
18
+ subnet = SubnetFactory.next
19
+ assert_equal IPAddr.new("10.0.40.2"), subnet.next
20
+ assert_equal IPAddr.new("10.0.40.3"), subnet.next
21
+ end
22
+
23
+ def test_next_subnet_twice
24
+ subnet = SubnetFactory.next
25
+ assert_equal IPAddr.new("10.0.40.2"), subnet.next
26
+
27
+ subnet = SubnetFactory.next
28
+ assert_equal IPAddr.new("10.0.41.2"), subnet.next
29
+ end
30
+
31
+ def test_next_16_subnet
32
+ subnet = SubnetFactory.next(16)
33
+ assert_equal IPAddr.new("10.1.0.2"), subnet.next
34
+ end
35
+
36
+ def test_mask_returns_classic_syntax_netmask
37
+ subnet = SubnetFactory.next
38
+ assert_equal "255.255.255.0", subnet.next.netmask
39
+ end
40
+
41
+ def test_cidr_on_subet
42
+ assert_equal 24, IPAddr.new("10.0.0.0/24").cidr
43
+ end
44
+
45
+ def test_to_cidr_on_subet
46
+ assert_equal "10.0.0.0/24", IPAddr.new("10.0.0.0/24").to_cidr
47
+ end
48
+ end
@@ -0,0 +1,2 @@
1
+ require "minitest/autorun"
2
+ require_relative "../lib/clusterfuck"
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-clusterfuck
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Simon Eskildsen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Clusterfuck let's you fuck with clusters
56
+ email:
57
+ - sirup@sirupsen.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - Vagrantfile
68
+ - cluster.rb
69
+ - clusterfuck.gemspec
70
+ - lib/clusterfuck.rb
71
+ - lib/clusterfuck/bgp_peer.rb
72
+ - lib/clusterfuck/cluster.rb
73
+ - lib/clusterfuck/core_ext/ipaddr.rb
74
+ - lib/clusterfuck/leaf_spine_dsl.rb
75
+ - lib/clusterfuck/machine.rb
76
+ - lib/clusterfuck/quagga_bgp_router.rb
77
+ - lib/clusterfuck/subnet_factory.rb
78
+ - lib/clusterfuck/test_helper.rb
79
+ - lib/clusterfuck/version.rb
80
+ - templates/quagga.bgpd.conf.erb
81
+ - templates/quagga.daemons.erb
82
+ - templates/quagga.zebra.conf.erb
83
+ - templates/routes.sh.erb
84
+ - test/bgp_peer_test.rb
85
+ - test/helper_test.rb
86
+ - test/machine_test.rb
87
+ - test/network_test.rb
88
+ - test/subnet_test.rb
89
+ - test/test_helper.rb
90
+ homepage: ''
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.2.2
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Clusterfuck let's you fuck with clusters
114
+ test_files:
115
+ - test/bgp_peer_test.rb
116
+ - test/helper_test.rb
117
+ - test/machine_test.rb
118
+ - test/network_test.rb
119
+ - test/subnet_test.rb
120
+ - test/test_helper.rb