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 +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
|