vagrant-clusterfuck 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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