testlab 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|