vagrant-clusterfuck 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +6 -0
- data/Rakefile +9 -0
- data/Vagrantfile +23 -0
- data/cluster.rb +13 -0
- data/clusterfuck.gemspec +24 -0
- data/lib/clusterfuck/bgp_peer.rb +43 -0
- data/lib/clusterfuck/cluster.rb +74 -0
- data/lib/clusterfuck/core_ext/ipaddr.rb +14 -0
- data/lib/clusterfuck/leaf_spine_dsl.rb +45 -0
- data/lib/clusterfuck/machine.rb +78 -0
- data/lib/clusterfuck/quagga_bgp_router.rb +65 -0
- data/lib/clusterfuck/subnet_factory.rb +26 -0
- data/lib/clusterfuck/test_helper.rb +36 -0
- data/lib/clusterfuck/version.rb +3 -0
- data/lib/clusterfuck.rb +13 -0
- data/templates/quagga.bgpd.conf.erb +43 -0
- data/templates/quagga.daemons.erb +31 -0
- data/templates/quagga.zebra.conf.erb +23 -0
- data/templates/routes.sh.erb +34 -0
- data/test/bgp_peer_test.rb +45 -0
- data/test/helper_test.rb +19 -0
- data/test/machine_test.rb +24 -0
- data/test/network_test.rb +201 -0
- data/test/subnet_test.rb +48 -0
- data/test/test_helper.rb +2 -0
- metadata +120 -0
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
data/Gemfile
ADDED
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
data/Rakefile
ADDED
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
data/clusterfuck.gemspec
ADDED
@@ -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,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
|
data/lib/clusterfuck.rb
ADDED
@@ -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
|
data/test/helper_test.rb
ADDED
@@ -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
|
data/test/subnet_test.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
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
|