vagrant-clusterfuck 0.0.1 → 0.0.2

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