vagrant-clusterfuck 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b20a1129ef6b8c782255ea00ddbf8026a45b50d0
4
- data.tar.gz: 98d03daca3a5d97656db316f603535a6730777e0
3
+ metadata.gz: a5e7cd97a8254d5f76623b8405587f7b98985713
4
+ data.tar.gz: 617d505a9df006f677d39c7019ad66fc701f4733
5
5
  SHA512:
6
- metadata.gz: cd9624b80c9795f879abca0774f57cd8182cbc740a466a90ec582f75357b4091c184aaa0e5c3b87081cbe0776ab54a1acab727054c403c4a5c774281c2c1b80f
7
- data.tar.gz: 38e9a07958f3ed4e73e453f53e80ac8d4b3092d335e081b3151606cd863488b9d8df543828b923874eb43ca2bb3d8f026f7e78bba784dff56bd9c0c65864bee8
6
+ metadata.gz: e3e23a8411a5442b73c75659a912fe8c9c758d8fefa634293913c9e283ad9a9822eadef1da232daad3ae4c2e5182239f7a14a08e4b36861e441ed7e3dd836779
7
+ data.tar.gz: e1e7df9a9205bd49e923608cf8609cb27f3195d9e1a4564a90ce7e0e711ed44d237c275906530a9bcfe6fe6c22f898109e513fa68730e6aca800a1a7a5309ace
data/.gitignore CHANGED
@@ -14,3 +14,4 @@
14
14
  mkmf.log
15
15
  vendor/
16
16
  .vagrant/
17
+ *.gem
data/README.md CHANGED
@@ -3,4 +3,58 @@
3
3
  Clusterfuck is a library for setting up a networked cluster of servers to test
4
4
  failure conditions against. It's a library to fuck with clusters.
5
5
 
6
+ Currently it ships with a DSL for EBGP leaf-spine architectures, but has a
7
+ generic backend that allows configuring any topology.
8
+
9
+ It's currently using Virtualbox for probably no good reason other than
10
+ `network_lab`, which this works derives from, used it. VMWare will be
11
+ investigated in the future.
12
+
6
13
  ## Usage
14
+
15
+ Install the `vagrant-clusterfuck` Vagrant plugin:
16
+
17
+ ```bash
18
+ $ vagrant plugin install vagrant-clusterfuck
19
+ ```
20
+
21
+ Create a `cluster.rb` file to define your cluster (see below). This must be a separate file,
22
+ because it needs to be loaded in your test environment which does not implement
23
+ the Vagrant DSL.
24
+
25
+ In your `Vagrantfile`, build your cluster:
26
+
27
+ ```ruby
28
+ cluster = Clusterfuck::Cluster[:test]
29
+ cluster[:host1].build(config) do |box|
30
+ box.vm.provision :shell, inline: "echo 1 > /tmp/test"
31
+ end
32
+
33
+ cluster[:host2].build(config)
34
+ ```
35
+
36
+ For a complete example, see the `Vagrantfile` and `cluster.rb` in this
37
+ repository.
38
+
39
+ ```bash
40
+ $ vagrant up --provider virtualbox
41
+ ```
42
+
43
+ When making changes to `cluster.rb` and `Vagrantfile` make sure to reload and
44
+ reprovision the cluster:
45
+
46
+ ```bash
47
+ $ vagrant reload --provision
48
+ ```
49
+
50
+ ## Building Clusters
51
+
52
+ Defining a cluster consists of two steps:
53
+
54
+ 1. **Creating instances.** You must specify which VMs your topology requires.
55
+ 2. **Layer 2 network.** You must specify the relationships between the VMs, that
56
+ is, the layer 2 network of which nodes belong to the same subnets and can
57
+ thus directly communicate with each other.
58
+
59
+ Please see `./cluster.rb` for an annotated example. In the future we may build
60
+ DSLs on top of the low-level graph DSL.
data/Vagrantfile CHANGED
@@ -10,7 +10,7 @@ Vagrant.configure('2') do |config|
10
10
  box.memory = 256
11
11
  end
12
12
 
13
- cluster = Clusterfuck::Cluster[:test]
13
+ cluster = Clusterfuck::BGPCluster[:test]
14
14
  cluster[:spine].build(config)
15
15
  cluster[:leaf1].build(config)
16
16
 
@@ -20,4 +20,5 @@ Vagrant.configure('2') do |config|
20
20
 
21
21
  cluster[:leaf2].build(config)
22
22
  cluster[:host21].build(config)
23
+ cluster[:host22].build(config)
23
24
  end
data/cluster.rb CHANGED
@@ -1,13 +1,43 @@
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
1
+ require_relative "lib/clusterfuck"
2
+ include Clusterfuck
3
+
4
+ # Create a BGPCluster. The BGPCluster is just a cluster that knows how to
5
+ # discover neighbouring BGP nodes.
6
+ cluster = BGPCluster.create(:test)
7
+
8
+ # The VIP subnet includes all the VIPs that any BGPPeer can take and announce.
9
+ # All hosts, even if they're on the same subnet as the node that owns the IP,
10
+ # will go through their gateway to route the IP.
11
+ vip_subnet = IPAddr.new("192.168.99.0/24")
12
+
13
+ # Spine becomes the catchall for 10.0.0.0/16 which is the subnet all the nodes
14
+ # belong to (each subnet is a /24).
15
+ priv_subnet = IPAddr.new("10.0.0.0/16")
16
+
17
+ # The Spine announces the VIP and private subnet.
18
+ spine = QuaggaBGPRouter.new(:spine, announce: [vip_subnet, priv_subnet])
19
+
20
+ # Define the leaf, announcements come later
21
+ leaf1_subnet = SubnetFactory.next_subnet
22
+ leaf1 = QuaggaBGPRouter.new(:leaf1, announce: leaf1_subnet.last_ip)
23
+
24
+ leaf2_subnet = SubnetFactory.next_subnet
25
+ leaf2 = QuaggaBGPRouter.new(:leaf2, announce: leaf2_subnet.last_ip)
26
+
27
+ # A normal host. All the Spine's BGP announcements (vip + priv) will go through
28
+ # the gateway.
29
+ host11 = Machine.new(:host11, routes: spine.bgp_announce, gateway: leaf1)
30
+
31
+ # A BGPPeer is just a machine with BGP information so that its BGP neighbours
32
+ # will automatically add it as a BGP peer. This is for a node to run e.g.
33
+ # ExaBGP.
34
+ host21 = BGPPeer.new(:host21, routes: spine.bgp_announce, gateway: leaf2)
35
+ host22 = Machine.new(:host22, routes: spine.bgp_announce, gateway: leaf2)
36
+
37
+ # The spines and leaves have their own subnets
38
+ cluster.connect(spine, leaf1)
39
+ cluster.connect(spine, leaf2)
40
+
41
+ # The leaves create a subnet with all their hosts.
42
+ cluster.connect(leaf1, host11, subnet: leaf1_subnet)
43
+ cluster.connect(leaf2, host21, host22, subnet: leaf2_subnet)
data/clusterfuck.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Clusterfuck::VERSION
9
9
  spec.authors = ["Simon Eskildsen"]
10
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}
11
+ spec.summary = %q{Clusterfuck lets you fuck with clusters}
12
+ spec.description = %q{Clusterfuck lets you fuck with clusters}
13
13
  spec.homepage = ""
14
14
  spec.license = "MIT"
15
15
 
@@ -2,26 +2,18 @@ module Clusterfuck
2
2
  class BGPPeer < Machine
3
3
  attr_accessor :bgp_asn,
4
4
  :bgp_router_id,
5
- :bgp_announced
5
+ :bgp_announce
6
6
 
7
7
  def initialize(name, **args)
8
8
  super
9
9
 
10
- @bgp_announced = args[:announced] ? Array(args[:announced]) : []
10
+ @bgp_announce = args[:announce] ? Array(args[:announce]) : []
11
11
  @bgp_asn = args[:asn] || ASNFactory.next
12
12
  @bgp_router_id = args[:router_id] || RouterIDFactory.next
13
13
  end
14
14
 
15
- def bgp_announced
16
- @bgp_announced.map { |a|
17
- a.kind_of?(SubnetFactory) ? a.subnet : a
18
- }
19
- end
20
-
21
15
  def bgp_neighbors
22
- network.adjacent(self).select { |neighbor|
23
- neighbor.kind_of?(BGPPeer)
24
- }
16
+ cluster.bgp_neighbors(self)
25
17
  end
26
18
 
27
19
  class ASNFactory
@@ -32,6 +24,8 @@ module Clusterfuck
32
24
  end
33
25
  end
34
26
 
27
+ # A router ID just an IP to identify the router on BGP. It does not have to
28
+ # be equal to the IP; just has to be an IP. We just pick start somewhere.
35
29
  class RouterIDFactory
36
30
  @id = IPAddr.new("10.0.20.1")
37
31
 
@@ -1,74 +1,65 @@
1
1
  module Clusterfuck
2
2
  class Cluster
3
- attr_reader :graph, :name
3
+ attr_reader :name, :network
4
4
 
5
5
  def initialize(name)
6
6
  @name = name
7
- @nodes = {}
8
7
  @graph = {}
9
8
  end
10
9
 
11
10
  def self.create(name)
12
- @clusters ||= {}
13
- @clusters[name] = Cluster.new(name)
11
+ @@clusters ||= {}
12
+ @@clusters[name] = self.new(name)
14
13
  end
15
14
 
16
15
  def self.[](name)
17
- @clusters[name]
16
+ @@clusters[name]
18
17
  end
19
18
 
20
19
  def self.all
21
- @clusters.values
20
+ @@clusters.values
22
21
  end
23
22
 
24
- def nodes
25
- @nodes.values
23
+ def find_by_name(name)
24
+ nodes.find { |node| node.name == name }
26
25
  end
26
+ alias_method :[], :find_by_name
27
27
 
28
- def [](name)
29
- @nodes[name]
28
+ def nodes
29
+ @graph.keys
30
30
  end
31
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
-
32
+ def connect(*nodes, subnet: SubnetFactory.next_subnet)
33
+ # Create a complete graph (a graph where all nodes are connected to each
34
+ # other) between all the nodes, because that's what a subnet is.
42
35
  nodes.permutation(2).each do |(a, b)|
36
+ @graph[a] ||= []
43
37
  @graph[a] << b
44
38
  end
45
- end
46
39
 
47
- def adjacent?(a, b)
48
- adjacent(a).include?(b)
49
- end
50
-
51
- def adjacent(a)
52
- @graph[a]
53
- end
40
+ nodes.each do |node|
41
+ node.ips << subnet.next_ip
42
+ node.cluster = self
43
+ end
54
44
 
55
- def register(node)
56
- @nodes[node.name] = node unless @nodes[node.name]
57
- @graph[node] = [] unless @graph[node]
45
+ subnet.last_ip
58
46
  end
59
47
 
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)
48
+ def ip_in_same_subnet(a, b)
49
+ a.ips.find { |a_ip|
50
+ b.ips.find { |b_ip|
51
+ a_ip.include?(b_ip)
65
52
  }
66
53
  }
67
54
  end
55
+ alias_method :neighbor?, :ip_in_same_subnet
56
+ end
68
57
 
69
- private
70
- def in_subnet?(subnet, node)
71
- node.subnets.any? { |ip| subnet.include?(ip) }
58
+ class BGPCluster < Cluster
59
+ def bgp_neighbors(node)
60
+ @graph[node].select { |neighbor|
61
+ neighbor.is_a?(BGPPeer)
62
+ }
72
63
  end
73
64
  end
74
65
  end
@@ -10,5 +10,9 @@ class IPAddr
10
10
  def to_cidr
11
11
  to_string + "/" + cidr.to_s
12
12
  end
13
+
14
+ def addr
15
+ to_string
16
+ end
13
17
  end
14
18
 
@@ -1,18 +1,18 @@
1
1
  module Clusterfuck
2
2
  class Machine
3
- attr_reader :name, :ssh_port, :routes, :gateway
4
- attr_accessor :subnets, :provisioners, :network
3
+ attr_reader :name, :ssh_port
4
+ attr_accessor :ips, :gateway, :routes, :cluster
5
5
 
6
6
  def initialize(name, **args)
7
7
  @name = name
8
- @subnets = []
8
+ @ips = []
9
9
  @ssh_port = args[:ssh_port] || PortFactory.next
10
10
  @gateway = args[:gateway]
11
11
  @routes = args[:routes]
12
12
  end
13
13
 
14
- def overlapping_subnets(other)
15
- network.overlapping_subnets(self, other)
14
+ def ip_in_same_subnet(other)
15
+ cluster.ip_in_same_subnet(self, other)
16
16
  end
17
17
 
18
18
  def build(config)
@@ -26,11 +26,11 @@ module Clusterfuck
26
26
  host: ssh_port,
27
27
  id: "ssh"
28
28
 
29
- subnets.each do |subnet|
29
+ ips.each do |ip|
30
30
  box.vm.network :private_network,
31
- ip: subnet.to_s,
31
+ ip: ip.addr,
32
32
  # TODO Troll Rubby people by using a refinement
33
- netmask: subnet.netmask,
33
+ netmask: ip.netmask,
34
34
  # TODO Vagrant already provides an abstraction for declaring networks
35
35
  # so we should not need to duplicate this abstraction by creating
36
36
  # subclasses for each provider. This bullshit needs to be gone for
@@ -43,11 +43,11 @@ EOS
43
43
  private
44
44
  # TODO clean up these messy functions
45
45
  def tmp_zebra_config
46
- File.expand_path("../../../templates/quagga.zebra.conf.erb", __FILE__)
46
+ File.expand_path("../../../templates/quagga.zebra.conf", __FILE__)
47
47
  end
48
48
 
49
49
  def tmp_daemons_config
50
- File.expand_path("../../../templates/quagga.daemons.erb", __FILE__)
50
+ File.expand_path("../../../templates/quagga.daemons", __FILE__)
51
51
  end
52
52
 
53
53
  def tmp_bgp_config
@@ -1,20 +1,21 @@
1
1
  module Clusterfuck
2
2
  class SubnetFactory
3
- attr_reader :subnet
3
+ attr_reader :last_ip
4
4
 
5
5
  START_SUBNET = "10.0.39.0/24"
6
6
  @previous = IPAddr.new(START_SUBNET)
7
7
 
8
- def initialize(subnet)
9
- @subnet = subnet
8
+ def initialize(last_ip)
9
+ @last_ip = last_ip
10
10
  end
11
11
 
12
- def next
13
- @subnet = @subnet.succ
12
+ def next_ip
13
+ @last_ip = @last_ip.succ
14
14
  end
15
15
 
16
- def self.next(cidr = 24)
17
- # succ twice to start at x.x.x.2
16
+ def self.next_subnet(cidr = 24)
17
+ # `succ` twice to start at x.x.2.1, for example:
18
+ # 10.0.39.1 -> 10.0.40.1
18
19
  @previous = @previous.mask(cidr).to_range.last.succ.succ
19
20
  SubnetFactory.new(@previous)
20
21
  end
@@ -1,3 +1,3 @@
1
1
  module Clusterfuck
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/clusterfuck.rb CHANGED
@@ -1,13 +1,11 @@
1
1
  require "ipaddr"
2
2
  require "erb"
3
- require_relative "clusterfuck/test_helper"
4
3
  require_relative "clusterfuck/cluster"
5
4
  require_relative "clusterfuck/subnet_factory"
6
5
  require_relative "clusterfuck/machine"
7
6
  require_relative "clusterfuck/bgp_peer"
8
7
  require_relative "clusterfuck/quagga_bgp_router"
9
8
  require_relative "clusterfuck/core_ext/ipaddr"
10
- require_relative "clusterfuck/leaf_spine_dsl"
11
9
 
12
10
  module Clusterfuck
13
11
  end
@@ -11,15 +11,15 @@ password zebra
11
11
  router bgp <%= bgp_asn %>
12
12
  bgp router-id <%= bgp_router_id %>
13
13
  timers bgp 1 3
14
- <% bgp_announced.each do |subnet| %>
15
- network <%= subnet.to_cidr %>
14
+ <% bgp_announce.each do |announce| %>
15
+ network <%= announce.to_cidr %>
16
16
  <% end %>
17
17
 
18
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
19
+ neighbor <%= node.ip_in_same_subnet(self) %> remote-as <%= node.bgp_asn %>
20
+ neighbor <%= node.ip_in_same_subnet(self) %> route-map DOWNSTREAM in
21
+ neighbor <%= node.ip_in_same_subnet(self) %> next-hop-self
22
+ neighbor <%= node.ip_in_same_subnet(self) %> advertisement-interval 1
23
23
  <% end %>
24
24
 
25
25
  ip community-list standard cm-prefmod-300 permit 65534:300
File without changes
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # We can't do this in `/etc/networks/interfaces` because Vagrant doesn't support
5
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
6
+ # means we have to hardcode the device names and the user has to guess them from
7
7
  # the order of the network definitions. The alternative to this is to run the ip
8
8
  # commands on every boot, which is arguably even worse.
9
9
  #
@@ -28,7 +28,7 @@ if [ "$IFACE" != eth1 ]; then
28
28
  fi
29
29
 
30
30
  <% routes.each do |route| %>
31
- ip route add <%= route.to_cidr %> via <%= gateway.overlapping_subnets(self).first %> dev eth1 || true
31
+ ip route add <%= route.to_cidr %> via <%= gateway.ip_in_same_subnet(self) %> dev eth1 || true
32
32
  <% end %>
33
33
 
34
34
  exit 0
@@ -0,0 +1,51 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestBGPCluster < Minitest::Unit::TestCase
4
+ include Clusterfuck
5
+
6
+ def setup
7
+ @cluster = BGPCluster.new(:test)
8
+ end
9
+
10
+ def test_bgp_neighbors_simple
11
+ one = BGPPeer.new(:peer1)
12
+ two = BGPPeer.new(:peer2)
13
+
14
+ @cluster.connect(one, two)
15
+
16
+ assert_equal [one], @cluster.bgp_neighbors(two)
17
+ assert_equal [two], @cluster.bgp_neighbors(one)
18
+ end
19
+
20
+ def test_bgp_neighbors_simple_tree
21
+ spine = BGPPeer.new(:spine)
22
+ leaf1 = BGPPeer.new(:leaf1)
23
+ leaf2 = BGPPeer.new(:leaf2)
24
+
25
+ @cluster.connect(spine, leaf1)
26
+ @cluster.connect(spine, leaf2)
27
+
28
+ assert_equal [leaf1, leaf2], @cluster.bgp_neighbors(spine)
29
+ assert_equal [spine], @cluster.bgp_neighbors(leaf1)
30
+ assert_equal [spine], @cluster.bgp_neighbors(leaf2)
31
+ end
32
+
33
+ def test_bgp_neighbors_tree_with_non_bgp_peers
34
+ spine = BGPPeer.new(:spine)
35
+ leaf1 = BGPPeer.new(:leaf1)
36
+ host11 = Machine.new(:host11)
37
+
38
+ leaf2 = BGPPeer.new(:leaf2)
39
+ host21 = Machine.new(:host21)
40
+
41
+ @cluster.connect(spine, leaf1)
42
+ @cluster.connect(spine, leaf2)
43
+
44
+ @cluster.connect(leaf1, host11)
45
+ @cluster.connect(leaf2, host21)
46
+
47
+ assert_equal [leaf1, leaf2], @cluster.bgp_neighbors(spine)
48
+ assert_equal [spine], @cluster.bgp_neighbors(leaf1)
49
+ assert_equal [spine], @cluster.bgp_neighbors(leaf2)
50
+ end
51
+ end
@@ -25,21 +25,4 @@ class TestBGPPeer < Minitest::Unit::TestCase
25
25
  def test_assigned_different_default_ips_as_router_id
26
26
  refute_equal @peer.bgp_router_id, @peer2.bgp_router_id
27
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
28
  end
@@ -0,0 +1,130 @@
1
+ require "minitest/autorun"
2
+ require "minitest/unit"
3
+ require_relative "../lib/clusterfuck"
4
+
5
+ class TestCluster < Minitest::Unit::TestCase
6
+ include Clusterfuck
7
+
8
+ def setup
9
+ SubnetFactory.reset
10
+ @cluster = Cluster.new(:test)
11
+ end
12
+
13
+ def test_two_machine_cluster
14
+ one = Machine.new(:one)
15
+ two = Machine.new(:two)
16
+
17
+ @cluster.connect(one, two)
18
+
19
+ assert_neighbor one, two
20
+ end
21
+
22
+ def test_simple_tree
23
+ root = Machine.new(:root)
24
+ leaf1 = Machine.new(:leaf1)
25
+ leaf2 = Machine.new(:leaf2)
26
+
27
+ @cluster.connect(root, leaf1)
28
+ @cluster.connect(root, leaf2)
29
+
30
+ assert_neighbor root, leaf1
31
+ assert_neighbor root, leaf2
32
+ refute_neighbor leaf1, leaf2
33
+ end
34
+
35
+ def test_different_ips_on_subnet
36
+ root = Machine.new(:root)
37
+ leaf1 = Machine.new(:leaf1)
38
+ leaf2 = Machine.new(:leaf2)
39
+
40
+ @cluster.connect(root, leaf1)
41
+ @cluster.connect(root, leaf2)
42
+
43
+ root_ips = root.ips.map(&:to_s)
44
+ refute root_ips.include?(*leaf1.ips), "Did not expect leaf1 and root to share ips"
45
+ refute root_ips.include?(*leaf2.ips), "Did not expect leaf2 and root to share ips"
46
+ end
47
+
48
+ def test_adjacent_on_simple_tree
49
+ root = Machine.new(:root)
50
+ leaf1 = Machine.new(:leaf1)
51
+ leaf2 = Machine.new(:leaf2)
52
+
53
+ @cluster.connect(root, leaf1)
54
+ @cluster.connect(root, leaf2)
55
+
56
+ assert_neighbor root, leaf1
57
+ assert_neighbor leaf1, root
58
+
59
+ assert_neighbor root, leaf2
60
+ assert_neighbor leaf2, root
61
+
62
+ refute_neighbor leaf1, leaf2
63
+ refute_neighbor leaf2, leaf1
64
+ end
65
+
66
+ def test_apply_subnetwork_to_larger_tree
67
+ root = Machine.new(:root)
68
+
69
+ leaf1 = Machine.new(:leaf1)
70
+ host11 = Machine.new(:host11)
71
+
72
+ leaf2 = Machine.new(:leaf2)
73
+ host21 = Machine.new(:host21)
74
+ host22 = Machine.new(:host22)
75
+
76
+ @cluster.connect(root, leaf1)
77
+ @cluster.connect(root, leaf2)
78
+ @cluster.connect(leaf2, host21, host22)
79
+ @cluster.connect(leaf1, host11)
80
+
81
+ assert_neighbor root, leaf1
82
+ assert_neighbor root, leaf2
83
+
84
+ refute_neighbor leaf1, leaf2
85
+ refute_neighbor leaf1, host21
86
+ refute_neighbor leaf1, host22
87
+
88
+ assert_neighbor leaf2, host21
89
+ assert_neighbor leaf2, host22
90
+ assert_neighbor host21, host22
91
+
92
+ assert_neighbor leaf1, host11
93
+ end
94
+
95
+ def test_node_is_assigned_ip_with_netmask
96
+ one = Machine.new(:one)
97
+ two = Machine.new(:two)
98
+
99
+ @cluster.connect(one, two)
100
+
101
+ assert_neighbor one, two
102
+
103
+ one_ip = one.ips.first
104
+ two_ip = two.ips.first
105
+
106
+ assert_equal "255.255.255.0", one_ip.netmask
107
+ assert_equal "10.0.40.2", one_ip.addr
108
+
109
+ assert_equal "255.255.255.0", two_ip.netmask
110
+ assert_equal "10.0.40.3", two_ip.addr
111
+ end
112
+
113
+ def test_ip_in_same_subnet_returns_shared_ip
114
+ one = Machine.new(:one)
115
+ two = Machine.new(:two)
116
+
117
+ @cluster.connect(one, two)
118
+
119
+ assert_equal "10.0.40.2/24", @cluster.ip_in_same_subnet(one, two).to_cidr
120
+ end
121
+
122
+ private
123
+ def assert_neighbor(*nodes)
124
+ assert @cluster.neighbor?(*nodes), "Expected #{nodes.inspect} to be neighbors"
125
+ end
126
+
127
+ def refute_neighbor(*nodes)
128
+ refute @cluster.neighbor?(*nodes), "Did not expect #{nodes.inspect} to be neighbors"
129
+ end
130
+ end
data/test/machine_test.rb CHANGED
@@ -5,6 +5,7 @@ class TestMachine < Minitest::Unit::TestCase
5
5
 
6
6
  def setup
7
7
  @machine = Machine.new(:test)
8
+ @cluster = Cluster.new(:test)
8
9
  end
9
10
 
10
11
  def test_get_name
data/test/subnet_test.rb CHANGED
@@ -7,35 +7,27 @@ class TestSubnet < Minitest::Unit::TestCase
7
7
 
8
8
  def setup
9
9
  SubnetFactory.reset
10
+ @subnet = SubnetFactory.next_subnet
10
11
  end
11
12
 
12
13
  def test_next_subnet
13
- subnet = SubnetFactory.next
14
- assert_equal IPAddr.new("10.0.40.2"), subnet.next
14
+ assert_equal IPAddr.new("10.0.40.2"), @subnet.next_ip
15
15
  end
16
16
 
17
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
18
+ assert_equal IPAddr.new("10.0.40.2"), @subnet.next_ip
19
+ assert_equal IPAddr.new("10.0.40.3"), @subnet.next_ip
21
20
  end
22
21
 
23
22
  def test_next_subnet_twice
24
- subnet = SubnetFactory.next
25
- assert_equal IPAddr.new("10.0.40.2"), subnet.next
23
+ assert_equal IPAddr.new("10.0.40.2"), @subnet.next_ip
26
24
 
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
25
+ subnet = SubnetFactory.next_subnet
26
+ assert_equal IPAddr.new("10.0.41.2"), subnet.next_ip
34
27
  end
35
28
 
36
29
  def test_mask_returns_classic_syntax_netmask
37
- subnet = SubnetFactory.next
38
- assert_equal "255.255.255.0", subnet.next.netmask
30
+ assert_equal "255.255.255.0", @subnet.next_ip.netmask
39
31
  end
40
32
 
41
33
  def test_cidr_on_subet
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-clusterfuck
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Eskildsen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-31 00:00:00.000000000 Z
11
+ date: 2015-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description: Clusterfuck let's you fuck with clusters
55
+ description: Clusterfuck lets you fuck with clusters
56
56
  email:
57
57
  - sirup@sirupsen.com
58
58
  executables: []
@@ -71,20 +71,18 @@ files:
71
71
  - lib/clusterfuck/bgp_peer.rb
72
72
  - lib/clusterfuck/cluster.rb
73
73
  - lib/clusterfuck/core_ext/ipaddr.rb
74
- - lib/clusterfuck/leaf_spine_dsl.rb
75
74
  - lib/clusterfuck/machine.rb
76
75
  - lib/clusterfuck/quagga_bgp_router.rb
77
76
  - lib/clusterfuck/subnet_factory.rb
78
- - lib/clusterfuck/test_helper.rb
79
77
  - lib/clusterfuck/version.rb
80
78
  - templates/quagga.bgpd.conf.erb
81
- - templates/quagga.daemons.erb
82
- - templates/quagga.zebra.conf.erb
79
+ - templates/quagga.daemons
80
+ - templates/quagga.zebra.conf
83
81
  - templates/routes.sh.erb
82
+ - test/bgp_network_test.rb
84
83
  - test/bgp_peer_test.rb
85
- - test/helper_test.rb
84
+ - test/cluster_test.rb
86
85
  - test/machine_test.rb
87
- - test/network_test.rb
88
86
  - test/subnet_test.rb
89
87
  - test/test_helper.rb
90
88
  homepage: ''
@@ -110,11 +108,11 @@ rubyforge_project:
110
108
  rubygems_version: 2.2.2
111
109
  signing_key:
112
110
  specification_version: 4
113
- summary: Clusterfuck let's you fuck with clusters
111
+ summary: Clusterfuck lets you fuck with clusters
114
112
  test_files:
113
+ - test/bgp_network_test.rb
115
114
  - test/bgp_peer_test.rb
116
- - test/helper_test.rb
115
+ - test/cluster_test.rb
117
116
  - test/machine_test.rb
118
- - test/network_test.rb
119
117
  - test/subnet_test.rb
120
118
  - test/test_helper.rb
@@ -1,45 +0,0 @@
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
@@ -1,36 +0,0 @@
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/test/helper_test.rb DELETED
@@ -1,19 +0,0 @@
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
data/test/network_test.rb DELETED
@@ -1,201 +0,0 @@
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