testlab 0.0.3 → 0.0.4
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.
- data/README.md +11 -5
- data/lib/testlab/container/actions.rb +47 -0
- data/lib/testlab/container/generators.rb +42 -0
- data/lib/testlab/container/lifecycle.rb +25 -0
- data/lib/testlab/container/lxc.rb +43 -0
- data/lib/testlab/container/network.rb +31 -0
- data/lib/testlab/container/status.rb +28 -0
- data/lib/testlab/container.rb +58 -150
- data/lib/testlab/network/actions.rb +37 -0
- data/lib/testlab/network/cidr.rb +102 -0
- data/lib/testlab/network/lifecycle.rb +25 -0
- data/lib/testlab/network/status.rb +30 -0
- data/lib/testlab/network.rb +11 -101
- data/lib/testlab/node/bind.rb +95 -0
- data/lib/testlab/node/lifecycle.rb +43 -0
- data/lib/testlab/node/lxc.rb +31 -0
- data/lib/testlab/node/resolv.rb +30 -0
- data/lib/testlab/node/ssh.rb +43 -0
- data/lib/testlab/node/status.rb +26 -0
- data/lib/testlab/node/templates/bind-db.erb +12 -0
- data/lib/testlab/node/templates/bind-setup.erb +6 -0
- data/lib/testlab/node/templates/bind-zone.erb +4 -0
- data/lib/testlab/node/templates/bind.erb +60 -0
- data/lib/testlab/node/templates/node-setup.erb +18 -0
- data/lib/testlab/node/templates/resolv.erb +4 -0
- data/lib/testlab/node.rb +36 -89
- data/lib/testlab/providers/vagrant.rb +9 -1
- data/lib/testlab/provisioner.rb +0 -1
- data/lib/testlab/router.rb +3 -8
- data/lib/testlab/version.rb +1 -1
- data/lib/testlab.rb +21 -7
- data/spec/spec_helper.rb +2 -0
- data/spec/testlab_spec.rb +1 -1
- data/testlab.gemspec +2 -2
- metadata +28 -6
@@ -0,0 +1,30 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Network
|
3
|
+
|
4
|
+
module Status
|
5
|
+
|
6
|
+
# Network status
|
7
|
+
def status
|
8
|
+
interface = "#{bridge}:#{ip}"
|
9
|
+
{
|
10
|
+
:id => self.id,
|
11
|
+
:node_id => self.node.id,
|
12
|
+
:state => self.state,
|
13
|
+
:interface => interface
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Network Bridge State
|
18
|
+
def state
|
19
|
+
output = self.node.ssh.exec(%(sudo ifconfig #{self.bridge} | grep 'MTU'), :silence => true, :ignore_exit_status => true).output.strip
|
20
|
+
if ((output =~ /UP/) && (output =~ /RUNNING/))
|
21
|
+
:running
|
22
|
+
else
|
23
|
+
:stopped
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/testlab/network.rb
CHANGED
@@ -13,115 +13,25 @@ class TestLab
|
|
13
13
|
|
14
14
|
attribute :bridge
|
15
15
|
|
16
|
-
attribute :
|
16
|
+
attribute :ip
|
17
17
|
attribute :config
|
18
18
|
|
19
|
+
autoload :Actions, 'testlab/network/actions'
|
20
|
+
autoload :CIDR, 'testlab/network/cidr'
|
21
|
+
autoload :Lifecycle, 'testlab/network/lifecycle'
|
22
|
+
autoload :Status, 'testlab/network/status'
|
23
|
+
|
24
|
+
include TestLab::Network::Actions
|
25
|
+
include TestLab::Network::CIDR
|
26
|
+
include TestLab::Network::Lifecycle
|
27
|
+
include TestLab::Network::Status
|
28
|
+
|
19
29
|
def initialize(*args)
|
20
30
|
super(*args)
|
21
31
|
|
22
32
|
@ui = TestLab.ui
|
23
33
|
end
|
24
34
|
|
25
|
-
# Network status
|
26
|
-
def status
|
27
|
-
interface = "#{bridge}:#{cidr}"
|
28
|
-
{
|
29
|
-
:id => self.id,
|
30
|
-
:node_id => self.node.id,
|
31
|
-
:state => self.state,
|
32
|
-
:interface => interface
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
################################################################################
|
37
|
-
|
38
|
-
# Create the network
|
39
|
-
def create
|
40
|
-
@ui.logger.debug { "Network Create: #{self.id} " }
|
41
|
-
|
42
|
-
self.node.ssh.exec(%(sudo brctl addbr #{self.bridge}), :silence => true, :ignore_exit_status => true)
|
43
|
-
self.node.ssh.exec(%(sudo ifconfig #{self.bridge} #{self.cidr} down), :silence => true, :ignore_exit_status => true)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Destroy the network
|
47
|
-
def destroy
|
48
|
-
@ui.logger.debug { "Network Destroy: #{self.id} " }
|
49
|
-
|
50
|
-
self.node.ssh.exec(%(sudo brctl delbr #{self.bridge}), :silence => true, :ignore_exit_status => true)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Start the network
|
54
|
-
def up
|
55
|
-
@ui.logger.debug { "Network Up: #{self.id} " }
|
56
|
-
|
57
|
-
self.node.ssh.exec(%(sudo ifconfig #{self.bridge} up), :silence => true, :ignore_exit_status => true)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Stop the network
|
61
|
-
def down
|
62
|
-
@ui.logger.debug { "Network Down: #{self.id} " }
|
63
|
-
|
64
|
-
self.node.ssh.exec(%(sudo ifconfig #{self.bridge} down), :silence => true, :ignore_exit_status => true)
|
65
|
-
end
|
66
|
-
|
67
|
-
################################################################################
|
68
|
-
|
69
|
-
# Reload the network
|
70
|
-
def reload
|
71
|
-
@ui.logger.debug { "Network Reload: #{self.id} " }
|
72
|
-
|
73
|
-
self.down
|
74
|
-
self.up
|
75
|
-
end
|
76
|
-
|
77
|
-
################################################################################
|
78
|
-
|
79
|
-
# State of the network
|
80
|
-
def state
|
81
|
-
output = self.node.ssh.exec(%(sudo ifconfig #{self.bridge} | grep 'MTU'), :silence => true, :ignore_exit_status => true).output.strip
|
82
|
-
if ((output =~ /UP/) && (output =~ /RUNNING/))
|
83
|
-
:running
|
84
|
-
else
|
85
|
-
:stopped
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
################################################################################
|
90
|
-
|
91
|
-
# Network Callback: after_create
|
92
|
-
def after_create
|
93
|
-
@ui.logger.debug { "Network Callback: After Create: #{self.id} " }
|
94
|
-
end
|
95
|
-
|
96
|
-
# Network Callback: after_up
|
97
|
-
def after_up
|
98
|
-
@ui.logger.debug { "Network Callback: After Up: #{self.id} " }
|
99
|
-
|
100
|
-
self.create
|
101
|
-
self.up
|
102
|
-
end
|
103
|
-
|
104
|
-
# Network Callback: before_down
|
105
|
-
def before_down
|
106
|
-
@ui.logger.debug { "Network Callback: Before Down: #{self.id} " }
|
107
|
-
|
108
|
-
self.down
|
109
|
-
self.destroy
|
110
|
-
end
|
111
|
-
|
112
|
-
# Network Callback: before_destroy
|
113
|
-
def before_destroy
|
114
|
-
@ui.logger.debug { "Network Callback: Before Destroy: #{self.id} " }
|
115
|
-
end
|
116
|
-
|
117
|
-
################################################################################
|
118
|
-
|
119
|
-
# Method missing handler
|
120
|
-
def method_missing(method_name, *method_args)
|
121
|
-
@ui.logger.debug { "NETWORK METHOD MISSING: #{method_name.inspect}(#{method_args.inspect})" }
|
122
|
-
super(method_name, *method_args)
|
123
|
-
end
|
124
|
-
|
125
35
|
end
|
126
36
|
|
127
37
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Node
|
3
|
+
|
4
|
+
module Bind
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
# Builds the main bind configuration sections
|
8
|
+
def build_bind_main_partial(file)
|
9
|
+
bind_conf_template = File.join(self.class.template_dir, "bind.erb")
|
10
|
+
|
11
|
+
file.puts(ZTK::Template.do_not_edit_notice(:message => "TestLab v#{TestLab::VERSION} BIND Configuration", :char => '//'))
|
12
|
+
file.puts(ZTK::Template.render(bind_conf_template, {}))
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_bind_records
|
16
|
+
forward_records = Hash.new
|
17
|
+
reverse_records = Hash.new
|
18
|
+
|
19
|
+
TestLab::Container.all.each do |container|
|
20
|
+
interface = container.primary_interface
|
21
|
+
domain = (container.domain || container.node.labfile.config[:domain])
|
22
|
+
|
23
|
+
forward_records[domain] ||= Array.new
|
24
|
+
forward_records[domain] << %(#{container.id} IN A #{container.ip})
|
25
|
+
|
26
|
+
reverse_records[interface.first] ||= Array.new
|
27
|
+
reverse_records[interface.first] << %(#{container.ptr} IN PTR #{container.id}.#{domain}.)
|
28
|
+
end
|
29
|
+
{ :forward => forward_records, :reverse => reverse_records }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Builds the bind configuration sections for our zones
|
33
|
+
def build_bind_zone_partial(file)
|
34
|
+
bind_zone_template = File.join(self.class.template_dir, 'bind-zone.erb')
|
35
|
+
|
36
|
+
bind_records = build_bind_records
|
37
|
+
forward_records = bind_records[:forward]
|
38
|
+
reverse_records = bind_records[:reverse]
|
39
|
+
|
40
|
+
TestLab::Network.all.each do |network|
|
41
|
+
context = {
|
42
|
+
:zone => network.arpa
|
43
|
+
}
|
44
|
+
|
45
|
+
file.puts
|
46
|
+
file.puts(ZTK::Template.render(bind_zone_template, context))
|
47
|
+
|
48
|
+
build_bind_db(network.arpa, reverse_records[network.id])
|
49
|
+
end
|
50
|
+
|
51
|
+
domains = ([self.labfile.config[:domain]] + TestLab::Container.domains).flatten
|
52
|
+
domains.each do |domain|
|
53
|
+
context = {
|
54
|
+
:zone => domain
|
55
|
+
}
|
56
|
+
|
57
|
+
file.puts
|
58
|
+
file.puts(ZTK::Template.render(bind_zone_template, context))
|
59
|
+
|
60
|
+
build_bind_db(domain, forward_records[domain])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_bind_db(zone, records)
|
65
|
+
bind_db_template = File.join(self.class.template_dir, 'bind-db.erb')
|
66
|
+
|
67
|
+
self.ssh.file(:target => "/etc/bind/db.#{zone}", :chown => "bind:bind") do |file|
|
68
|
+
file.puts(ZTK::Template.do_not_edit_notice(:message => "TestLab v#{TestLab::VERSION} BIND DB: #{zone}", :char => ';'))
|
69
|
+
file.puts(ZTK::Template.render(bind_db_template, { :zone => zone, :records => records }))
|
70
|
+
end
|
71
|
+
|
72
|
+
# self.ssh.exec(%(sudo rm -fv /etc/bind/db.#{zone}.jnl))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Builds the BIND configuration
|
76
|
+
def build_bind_conf
|
77
|
+
self.ssh.file(:target => File.join("/etc/bind/named.conf"), :chown => "bind:bind") do |file|
|
78
|
+
build_bind_main_partial(file)
|
79
|
+
build_bind_zone_partial(file)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def bind_setup
|
84
|
+
bind_setup_template = File.join(self.class.template_dir, 'bind-setup.erb')
|
85
|
+
self.ssh.bootstrap(ZTK::Template.render(bind_setup_template))
|
86
|
+
|
87
|
+
build_bind_conf
|
88
|
+
|
89
|
+
self.ssh.exec(%(sudo /bin/bash -c 'service bind9 restart || service bind9 start'))
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Node
|
3
|
+
|
4
|
+
module Lifecycle
|
5
|
+
|
6
|
+
# Bootstrap the node
|
7
|
+
def node_setup
|
8
|
+
node_setup_template = File.join(self.class.template_dir, 'node-setup.erb')
|
9
|
+
self.ssh.bootstrap(ZTK::Template.render(node_setup_template))
|
10
|
+
end
|
11
|
+
|
12
|
+
# Setup the node.
|
13
|
+
def setup
|
14
|
+
@ui.logger.debug { "Node Setup: #{self.id} " }
|
15
|
+
|
16
|
+
node_setup
|
17
|
+
|
18
|
+
if self.components.include?('resolv')
|
19
|
+
build_resolv_conf
|
20
|
+
end
|
21
|
+
|
22
|
+
if self.components.include?('bind')
|
23
|
+
bind_setup
|
24
|
+
end
|
25
|
+
|
26
|
+
call_collections([self.networks, self.routers, self.containers], :setup)
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
# Teardown the node.
|
32
|
+
def teardown
|
33
|
+
@ui.logger.debug { "Node Teardown: #{self.id} " }
|
34
|
+
|
35
|
+
call_collections([self.containers, self.routers, self.networks], :teardown)
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Node
|
3
|
+
|
4
|
+
module LXC
|
5
|
+
require 'lxc'
|
6
|
+
|
7
|
+
# Returns the LXC object for this Node
|
8
|
+
#
|
9
|
+
# This object is used to control containers on the node via it's provider
|
10
|
+
#
|
11
|
+
# @return [LXC] An instance of LXC configured for this node.
|
12
|
+
def lxc(options={})
|
13
|
+
if (!defined?(@lxc) || @lxc.nil?)
|
14
|
+
@lxc ||= ::LXC.new
|
15
|
+
@lxc.use_sudo = true
|
16
|
+
@lxc.use_ssh = self.ssh
|
17
|
+
end
|
18
|
+
@lxc
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the machine type of the node.
|
22
|
+
#
|
23
|
+
# @return [String] The output of 'uname -m'.
|
24
|
+
def arch
|
25
|
+
@arch ||= self.ssh.exec(%(uname -m)).output.strip
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Node
|
3
|
+
|
4
|
+
module Resolv
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
# Builds the main resolv configuration sections
|
8
|
+
def build_resolv_main_conf(file)
|
9
|
+
resolv_conf_template = File.join(self.class.template_dir, "resolv.erb")
|
10
|
+
|
11
|
+
domains = ([self.labfile.config[:domain]] + TestLab::Container.domains).flatten
|
12
|
+
context = {
|
13
|
+
:servers => [TestLab::Network.all.map(&:clean_ip), "8.8.8.8", "8.8.4.4" ].flatten,
|
14
|
+
:search => domains.join(' ')
|
15
|
+
}
|
16
|
+
|
17
|
+
file.puts(ZTK::Template.do_not_edit_notice(:message => "TestLab v#{TestLab::VERSION} RESOLVER Configuration"))
|
18
|
+
file.puts(ZTK::Template.render(resolv_conf_template, context))
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_resolv_conf
|
22
|
+
self.ssh.file(:target => File.join("/etc/resolv.conf"), :chown => "root:root") do |file|
|
23
|
+
build_resolv_main_conf(file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Node
|
3
|
+
|
4
|
+
module SSH
|
5
|
+
|
6
|
+
# SSH to the Node
|
7
|
+
def ssh(options={})
|
8
|
+
if (!defined?(@ssh) || @ssh.nil?)
|
9
|
+
@ssh ||= ZTK::SSH.new({:ui => @ui, :timeout => 1200, :silence => true}.merge(options))
|
10
|
+
@ssh.config do |c|
|
11
|
+
c.host_name = @provider.ip
|
12
|
+
c.port = @provider.port
|
13
|
+
c.user = @provider.user
|
14
|
+
c.keys = @provider.identity
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@ssh
|
18
|
+
end
|
19
|
+
|
20
|
+
# SSH to a container running on the Node
|
21
|
+
def container_ssh(container, options={})
|
22
|
+
name = container.id
|
23
|
+
@container_ssh ||= Hash.new
|
24
|
+
if @container_ssh[name].nil?
|
25
|
+
@container_ssh[name] ||= ZTK::SSH.new({:ui => @ui, :timeout => 1200, :silence => true}.merge(options))
|
26
|
+
@container_ssh[name].config do |c|
|
27
|
+
c.proxy_host_name = @provider.ip
|
28
|
+
c.proxy_port = @provider.port
|
29
|
+
c.proxy_user = @provider.user
|
30
|
+
c.proxy_keys = @provider.identity
|
31
|
+
|
32
|
+
c.host_name = container.ip
|
33
|
+
c.user = (container.user || "ubuntu")
|
34
|
+
c.keys = container.keys
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@container_ssh[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class TestLab
|
2
|
+
class Node
|
3
|
+
|
4
|
+
module Status
|
5
|
+
|
6
|
+
# Node Status
|
7
|
+
#
|
8
|
+
# @return [Hash] A hash detailing the status of the node.
|
9
|
+
def status
|
10
|
+
{
|
11
|
+
:instance_id => @provider.instance_id,
|
12
|
+
:state => @provider.state,
|
13
|
+
:user => @provider.user,
|
14
|
+
:ip => @provider.ip,
|
15
|
+
:port => @provider.port,
|
16
|
+
:provider => @provider.class,
|
17
|
+
:con => self.containers.count,
|
18
|
+
:net => self.networks.count,
|
19
|
+
:rtr => self.routers.count
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
options {
|
2
|
+
directory "/var/cache/bind";
|
3
|
+
|
4
|
+
// If there is a firewall between you and nameservers you want
|
5
|
+
// to talk to, you may need to fix the firewall to allow multiple
|
6
|
+
// ports to talk. See http://www.kb.cert.org/vuls/id/800113
|
7
|
+
|
8
|
+
// If your ISP provided one or more IP addresses for stable
|
9
|
+
// nameservers, you probably want to use them as forwarders.
|
10
|
+
// Uncomment the following block, and insert the addresses replacing
|
11
|
+
// the all-0's placeholder.
|
12
|
+
|
13
|
+
// forwarders {
|
14
|
+
// 0.0.0.0;
|
15
|
+
// };
|
16
|
+
|
17
|
+
//========================================================================
|
18
|
+
// If BIND logs error messages about the root key being expired,
|
19
|
+
// you will need to update your keys. See https://www.isc.org/bind-keys
|
20
|
+
//========================================================================
|
21
|
+
dnssec-validation auto;
|
22
|
+
|
23
|
+
auth-nxdomain no; # conform to RFC1035
|
24
|
+
listen-on-v6 { any; };
|
25
|
+
};
|
26
|
+
|
27
|
+
include "/etc/bind/rndc.key";
|
28
|
+
|
29
|
+
controls {
|
30
|
+
inet 127.0.0.1 allow { 127.0.0.1; } keys { "rndc-key"; };
|
31
|
+
};
|
32
|
+
|
33
|
+
// prime the server with knowledge of the root servers
|
34
|
+
zone "." {
|
35
|
+
type hint;
|
36
|
+
file "/etc/bind/db.root";
|
37
|
+
};
|
38
|
+
|
39
|
+
// be authoritative for the localhost forward and reverse zones, and for
|
40
|
+
// broadcast zones as per RFC 1912
|
41
|
+
|
42
|
+
zone "localhost" {
|
43
|
+
type master;
|
44
|
+
file "/etc/bind/db.local";
|
45
|
+
};
|
46
|
+
|
47
|
+
zone "127.in-addr.arpa" {
|
48
|
+
type master;
|
49
|
+
file "/etc/bind/db.127";
|
50
|
+
};
|
51
|
+
|
52
|
+
zone "0.in-addr.arpa" {
|
53
|
+
type master;
|
54
|
+
file "/etc/bind/db.0";
|
55
|
+
};
|
56
|
+
|
57
|
+
zone "255.in-addr.arpa" {
|
58
|
+
type master;
|
59
|
+
file "/etc/bind/db.255";
|
60
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
set -x
|
2
|
+
|
3
|
+
# Update APT and ensure our required packages are installed
|
4
|
+
apt-get -y update
|
5
|
+
apt-get -y install lxc bridge-utils debootstrap yum iptables ntpdate ntp
|
6
|
+
|
7
|
+
# Ensure the default lxc networking services are off
|
8
|
+
service lxc-net stop
|
9
|
+
|
10
|
+
# Ensure NTP services are enabled and running
|
11
|
+
service ntp restart || service ntp start
|
12
|
+
|
13
|
+
# Enable ipv4 forwarding
|
14
|
+
sysctl net.ipv4.ip_forward | grep "net.ipv4.ip_forward = 1" || sysctl -w net.ipv4.ip_forward=1
|
15
|
+
|
16
|
+
# Install an iptable NAT rule
|
17
|
+
iptables -t nat --list -v | grep "MASQUERADE all -- any eth0 anywhere anywhere" || iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
18
|
+
|