testlab 0.7.4 → 0.7.5

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/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  *log
19
19
  Vagrantfile
20
+ .vagrant
data/Rakefile CHANGED
@@ -18,6 +18,7 @@
18
18
  #
19
19
  ################################################################################
20
20
 
21
+ require 'rake/clean'
21
22
  require 'bundler/gem_tasks'
22
23
 
23
24
  ################################################################################
@@ -29,6 +30,17 @@ task :test => [:spec]
29
30
 
30
31
  ################################################################################
31
32
 
33
+ require 'cucumber/rake/task'
34
+ desc 'Run features'
35
+ Cucumber::Rake::Task.new(:features) do |t|
36
+ opts = "features --format pretty -x"
37
+ opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
38
+ t.cucumber_opts = opts
39
+ t.fork = false
40
+ end
41
+
42
+ ################################################################################
43
+
32
44
  require 'coveralls/rake/task'
33
45
  Coveralls::RakeTask.new
34
46
  task :coveralls => [:spec, 'coveralls:push']
data/bin/tl CHANGED
@@ -18,8 +18,6 @@
18
18
  # limitations under the License.
19
19
  #
20
20
  ################################################################################
21
- require 'socket'
22
-
23
21
  require 'gli'
24
22
  require 'testlab'
25
23
 
@@ -34,6 +32,8 @@ program_desc %(TestLab - A toolkit for building virtual computer labs)
34
32
  sort_help :manually
35
33
  default_command :help
36
34
 
35
+ preserve_argv true
36
+
37
37
  commands_from 'commands'
38
38
 
39
39
  desc 'Show verbose output'
@@ -44,9 +44,9 @@ desc 'Quiet mode'
44
44
  default_value false
45
45
  switch [:q, :quiet]
46
46
 
47
- desc 'Path to Labfile: ${PWD}/Labfile'
47
+ desc 'Path to Labfile: ${REPO}/Labfile'
48
48
  arg_name 'path/to/file'
49
- default_value File.join(Dir.pwd, 'Labfile')
49
+ # default_value File.join(Dir.pwd, 'Labfile')
50
50
  flag [:l, :labfile]
51
51
 
52
52
  desc 'Path to Repository directory: ${PWD}'
@@ -54,15 +54,15 @@ arg_name 'path/to/directory'
54
54
  default_value Dir.pwd
55
55
  flag [:r, :repo]
56
56
 
57
- desc 'Path to Configuration directory: ${PWD}/.testlab-$(hostname -s)'
57
+ desc 'Path to Configuration directory: ${REPO}/.testlab-$(hostname -s)'
58
58
  arg_name 'path/to/directory'
59
- default_value File.join(Dir.pwd, ".testlab-#{TestLab.hostname}")
59
+ # default_value File.join(Dir.pwd, ".testlab-#{TestLab.hostname}")
60
60
  flag [:c, :config]
61
61
 
62
62
  pre do |global,command,options,args|
63
63
  (global[:verbose] == true) and (ENV['LOG_LEVEL'] = 'DEBUG')
64
64
 
65
- log_file = File.join(Dir.pwd, "testlab-#{TestLab.hostname}.log")
65
+ log_file = File.join(global[:repo], "testlab-#{TestLab.hostname}.log")
66
66
  @logger = ZTK::Logger.new(log_file)
67
67
 
68
68
  @ui = ZTK::UI.new(
@@ -73,9 +73,9 @@ pre do |global,command,options,args|
73
73
 
74
74
  @testlab = TestLab.new(
75
75
  :ui => @ui,
76
- :labfile => global[:labfile],
77
- :config_dir => global[:config],
78
- :repo_dir => global[:repo]
76
+ :labfile_path => global[:labfile],
77
+ :config_dir => global[:config],
78
+ :repo_dir => global[:repo]
79
79
  )
80
80
 
81
81
  @ui.logger.debug { "global(#{global.inspect})" }
@@ -0,0 +1,35 @@
1
+ When /^I get the containers status with "([^"]*)"$/ do |app_name|
2
+ container_cmd(app_name, %W(status -n test-server))
3
+ end
4
+
5
+ When /^I get the containers ssh-config with "([^"]*)"$/ do |app_name|
6
+ container_cmd(app_name, %W(ssh-config -n test-server))
7
+ end
8
+
9
+ When /^I up the containers with "([^"]*)"$/ do |app_name|
10
+ container_cmd(app_name, %W(up -n test-server))
11
+ end
12
+
13
+ When /^I down the containers with "([^"]*)"$/ do |app_name|
14
+ container_cmd(app_name, %W(down -n test-server))
15
+ end
16
+
17
+ When /^I clone the containers with "([^"]*)"$/ do |app_name|
18
+ container_cmd(app_name, %W(clone -n test-server))
19
+ end
20
+
21
+ When /^I build the containers with "([^"]*)"$/ do |app_name|
22
+ container_cmd(app_name, %W(build -n test-server))
23
+ end
24
+
25
+ When /^I export the containers with "([^"]*)"$/ do |app_name|
26
+ container_cmd(app_name, %W(export -n test-server --output=/tmp/test-server.sc))
27
+ end
28
+
29
+ When /^I import the containers with "([^"]*)"$/ do |app_name|
30
+ container_cmd(app_name, %W(import -n test-server --input=/tmp/test-server.sc))
31
+ end
32
+
33
+ def container_cmd(app_name, *args)
34
+ testlab_cmd(app_name, [%(container), args].flatten)
35
+ end
@@ -0,0 +1,7 @@
1
+ When /^I get the networks status with "([^"]*)"$/ do |app_name|
2
+ network_cmd(app_name, %W(status -n labnet))
3
+ end
4
+
5
+ def network_cmd(app_name, *args)
6
+ testlab_cmd(app_name, [%(network), args].flatten)
7
+ end
@@ -0,0 +1,19 @@
1
+ When /^I get the nodes status with "([^"]*)"$/ do |app_name|
2
+ node_cmd(app_name, %W(status -n vagrant))
3
+ end
4
+
5
+ When /^I up the nodes with "([^"]*)"$/ do |app_name|
6
+ node_cmd(app_name, %W(up -n vagrant))
7
+ end
8
+
9
+ When /^I down the nodes with "([^"]*)"$/ do |app_name|
10
+ node_cmd(app_name, %W(down -n vagrant))
11
+ end
12
+
13
+ When /^I build the nodes with "([^"]*)"$/ do |app_name|
14
+ node_cmd(app_name, %W(build -n vagrant))
15
+ end
16
+
17
+ def node_cmd(app_name, *args)
18
+ testlab_cmd(app_name, [%(node), args].flatten)
19
+ end
@@ -0,0 +1,28 @@
1
+ When /^I get help for "([^"]*)"$/ do |app_name|
2
+ testlab_cmd(app_name, %W(help))
3
+ end
4
+
5
+ When /^I get the status with "([^"]*)"$/ do |app_name|
6
+ testlab_cmd(app_name, %W(status))
7
+ end
8
+
9
+ When /^I build the lab with "([^"]*)"$/ do |app_name|
10
+ testlab_cmd(app_name, %W(build))
11
+ end
12
+
13
+ When /^I up the lab with "([^"]*)"$/ do |app_name|
14
+ testlab_cmd(app_name, %W(up))
15
+ end
16
+
17
+ When /^I down the lab with "([^"]*)"$/ do |app_name|
18
+ testlab_cmd(app_name, %W(down))
19
+ end
20
+
21
+ When /^I destroy the lab with "([^"]*)"$/ do |app_name|
22
+ testlab_cmd(app_name, %W(destroy))
23
+ end
24
+
25
+ def testlab_cmd(app_name, *args)
26
+ args = args.join(' ')
27
+ step %(I run `#{app_name} --repo=#{TEST_REPO} #{args}`)
28
+ end
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ #^syntax detection
3
+
4
+ # Labfile Simple Example
5
+ #########################
6
+ # This is a simple example that builds a lab with a chef-server and chef-client.
7
+
8
+ # Here we define our node; in this case we are using the localhost. We've
9
+ # selected the Vagrant provider so the Vagrant VM will be our target LXC node.
10
+ node :vagrant do
11
+
12
+ # This sets the provider for our node. This could be many things, from
13
+ # Vagrant, to AWS, to Local, to custom internal providers. Any class can be
14
+ # supplied assuming it's interface honors our contract.
15
+ provider TestLab::Provider::Vagrant
16
+
17
+ # These are provisioners that only need to run against the node (i.e. they
18
+ # do not have any container components). Provisioner classes applied to
19
+ # containers only are automatically interogated and executed during runtime
20
+ # if they have a node component.
21
+ provisioners [
22
+ TestLab::Provisioner::Raring,
23
+ TestLab::Provisioner::Bind
24
+ ]
25
+
26
+ # this is a generic configuration object (hash) which is supplied to
27
+ # the provider
28
+ config ({
29
+ :vagrant => {
30
+ :id => "test-cucumber-#{TestLab.hostname}".downcase,
31
+ :cpus => ZTK::Parallel::MAX_FORKS.div(2), # use half of the available processors
32
+ :memory => ZTK::Parallel::MAX_MEMORY.div(3).div(1024 * 1024), # use a third of available RAM
33
+ :box => 'raring64',
34
+ :box_url => 'https://dl.dropboxusercontent.com/u/22904185/boxes/raring64.box',
35
+ :file => File.dirname(__FILE__)
36
+ },
37
+ :bind => {
38
+ :domain => "default.zone"
39
+ }
40
+ })
41
+
42
+ # Here we define our network segments; these will manifest into network
43
+ # bridges on the target LXC node.
44
+ network 'labnet' do
45
+ # provisioners [TestLab::Provisioner::Route]
46
+ address '10.128.0.1/16'
47
+ bridge :br0
48
+ end
49
+
50
+ # test-server.default.zone
51
+ ###########################
52
+ container "test-server" do
53
+ distro "ubuntu"
54
+ release "precise"
55
+
56
+ provisioners [
57
+ TestLab::Provisioner::Resolv,
58
+ TestLab::Provisioner::AptCacherNG,
59
+ TestLab::Provisioner::Apt
60
+ ]
61
+
62
+ # Here we define a default user to seed onto the box. This user will be
63
+ # given passwordless sudo access as well.
64
+ user 'deployer' do
65
+ password 'deployer'
66
+ identity File.join(ENV['HOME'], '.ssh', 'id_rsa')
67
+ public_identity File.join(ENV['HOME'], '.ssh', 'id_rsa.pub')
68
+ uid 2600
69
+ gid 2600
70
+ end
71
+
72
+ # Interfaces define what networks this container is linked to and what our
73
+ # configuration on that network should be. In the event we have more than
74
+ # one interface we should define one as the "primary". When multiple
75
+ # interfaces are at play, the default route for the container will be off
76
+ # the primary interface.
77
+ interface do
78
+ network_id 'labnet'
79
+ name :eth0
80
+ address '10.128.0.254/16'
81
+ mac '00:00:5e:63:b5:9f'
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,18 @@
1
+ require 'aruba/cucumber'
2
+
3
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
4
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
5
+ TEST_REPO = File.dirname(__FILE__)
6
+
7
+ Before do
8
+ # Using "announce" causes massive warnings on 1.9.2
9
+ @aruba_timeout_seconds = 3600
10
+ @puts = true
11
+ @original_rubylib = ENV['RUBYLIB']
12
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
13
+ # ENV['VAGRANT_HOME'] = File.join("", "tmp", ".vagrant.d")
14
+ end
15
+
16
+ After do
17
+ ENV['RUBYLIB'] = @original_rubylib
18
+ end
@@ -0,0 +1,64 @@
1
+ Feature: TestLab command-line
2
+
3
+ Background: TestLab status
4
+ When I get the status with "tl"
5
+ Then the exit status should be 0
6
+ When I get the nodes status with "tl"
7
+ Then the exit status should be 0
8
+ When I get the networks status with "tl"
9
+ Then the exit status should be 0
10
+ When I get the containers status with "tl"
11
+ Then the exit status should be 0
12
+ When I get the containers ssh-config with "tl"
13
+ Then the exit status should be 0
14
+
15
+
16
+ Scenario: TestLab help
17
+ When I get help for "tl"
18
+ Then the exit status should be 0
19
+
20
+
21
+ Scenario: TestLab export
22
+ When I build the lab with "tl"
23
+ Then the exit status should be 0
24
+ When I export the containers with "tl"
25
+ Then the exit status should be 0
26
+
27
+
28
+ Scenario: TestLab import
29
+ When I down the lab with "tl"
30
+ Then the exit status should be 0
31
+ When I build the nodes with "tl"
32
+ Then the exit status should be 0
33
+ When I import the containers with "tl"
34
+ Then the exit status should be 0
35
+ When I build the lab with "tl"
36
+ Then the exit status should be 0
37
+
38
+
39
+ Scenario: TestLab clone
40
+ When I down the lab with "tl"
41
+ Then the exit status should be 0
42
+ When I build the lab with "tl"
43
+ Then the exit status should be 0
44
+ When I clone the containers with "tl"
45
+ Then the exit status should be 0
46
+ When I build the containers with "tl"
47
+ Then the exit status should be 0
48
+ When I clone the containers with "tl"
49
+ Then the exit status should be 0
50
+ When I build the containers with "tl"
51
+ Then the exit status should be 0
52
+ When I down the containers with "tl"
53
+ Then the exit status should be 0
54
+ When I up the containers with "tl"
55
+ Then the exit status should be 0
56
+ When I build the containers with "tl"
57
+ Then the exit status should be 0
58
+
59
+
60
+ Scenario: TestLab destroy
61
+ When I down the lab with "tl"
62
+ Then the exit status should be 0
63
+ When I destroy the lab with "tl"
64
+ Then the exit status should be 0
@@ -24,25 +24,28 @@ desc 'Manage containers'
24
24
  arg_name 'Describe arguments to container here'
25
25
  command :container do |c|
26
26
 
27
- c.desc 'Container ID or Name'
28
- c.arg_name 'container'
27
+ c.desc 'Single or comma separated list of container IDs'
28
+ c.arg_name 'container[,container,...]'
29
29
  c.flag [:n, :name]
30
30
 
31
31
  # CONTAINER CREATE
32
32
  ###################
33
33
  c.desc 'Create a container'
34
34
  c.long_desc <<-EOF
35
- Create a container. The container is created.
35
+ Creates a container on the node the container belongs to.
36
36
  EOF
37
37
  c.command :create do |create|
38
38
  create.action do |global_options, options, args|
39
39
  if options[:name].nil?
40
40
  help_now!('a name is required') if options[:name].nil?
41
41
  else
42
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
43
- container.nil? and raise TestLab::TestLabError, "We could not find the container you screateplied!"
42
+ names = options[:name].split(',')
43
+ containers = TestLab::Container.find(names)
44
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
44
45
 
45
- container.create
46
+ containers.each do |container|
47
+ container.create
48
+ end
46
49
  end
47
50
  end
48
51
  end
@@ -51,17 +54,20 @@ EOF
51
54
  ####################
52
55
  c.desc 'Destroy a container'
53
56
  c.long_desc <<-EOF
54
- Destroy a container. The container is stopped and destroyed.
57
+ Destroys the container, force stopping it if necessary. The containers file system is purged from disk. This is a destructive operation, there is no way to recover from it.
55
58
  EOF
56
59
  c.command :destroy do |destroy|
57
60
  destroy.action do |global_options, options, args|
58
61
  if options[:name].nil?
59
62
  help_now!('a name is required') if options[:name].nil?
60
63
  else
61
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
62
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
64
+ names = options[:name].split(',')
65
+ containers = TestLab::Container.find(names)
66
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
63
67
 
64
- container.destroy
68
+ containers.each do |container|
69
+ container.destroy
70
+ end
65
71
  end
66
72
  end
67
73
  end
@@ -70,17 +76,20 @@ EOF
70
76
  ###############
71
77
  c.desc 'Up a container'
72
78
  c.long_desc <<-EOF
73
- Up a container. The container is started and brought online.
79
+ The container is started and brought online.
74
80
  EOF
75
81
  c.command :up do |up|
76
82
  up.action do |global_options, options, args|
77
83
  if options[:name].nil?
78
84
  help_now!('a name is required') if options[:name].nil?
79
85
  else
80
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
81
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
86
+ names = options[:name].split(',')
87
+ containers = TestLab::Container.find(names)
88
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
82
89
 
83
- container.up
90
+ containers.each do |container|
91
+ container.up
92
+ end
84
93
  end
85
94
  end
86
95
  end
@@ -89,17 +98,20 @@ EOF
89
98
  #################
90
99
  c.desc 'Down a container'
91
100
  c.long_desc <<-EOF
92
- Down a container. The container is stopped taking it offline.
101
+ The container is stopped taking it offline.
93
102
  EOF
94
103
  c.command :down do |down|
95
104
  down.action do |global_options, options, args|
96
105
  if options[:name].nil?
97
106
  help_now!('a name is required') if options[:name].nil?
98
107
  else
99
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
100
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
108
+ names = options[:name].split(',')
109
+ containers = TestLab::Container.find(names)
110
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
101
111
 
102
- container.down
112
+ containers.each do |container|
113
+ container.down
114
+ end
103
115
  end
104
116
  end
105
117
  end
@@ -108,17 +120,20 @@ EOF
108
120
  ####################
109
121
  c.desc 'Setup a container'
110
122
  c.long_desc <<-EOF
111
- Setup a container. The container is created, started and provisioned.
123
+ The container is provisioned.
112
124
  EOF
113
125
  c.command :setup do |setup|
114
126
  setup.action do |global_options, options, args|
115
127
  if options[:name].nil?
116
128
  help_now!('a name is required') if options[:name].nil?
117
129
  else
118
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
119
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
130
+ names = options[:name].split(',')
131
+ containers = TestLab::Container.find(names)
132
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
120
133
 
121
- container.setup
134
+ containers.each do |container|
135
+ container.setup
136
+ end
122
137
  end
123
138
  end
124
139
  end
@@ -127,17 +142,20 @@ EOF
127
142
  ####################
128
143
  c.desc 'Teardown a container'
129
144
  c.long_desc <<-EOF
130
- Teardown a container. The container is offlined and destroyed.
145
+ The container is deprovisioned.
131
146
  EOF
132
147
  c.command :teardown do |teardown|
133
148
  teardown.action do |global_options, options, args|
134
149
  if options[:name].nil?
135
150
  help_now!('a name is required') if options[:name].nil?
136
151
  else
137
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
138
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
152
+ names = options[:name].split(',')
153
+ containers = TestLab::Container.find(names)
154
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
139
155
 
140
- container.teardown
156
+ containers.each do |container|
157
+ container.teardown
158
+ end
141
159
  end
142
160
  end
143
161
  end
@@ -157,41 +175,47 @@ EOF
157
175
  if options[:name].nil?
158
176
  help_now!('a name is required') if options[:name].nil?
159
177
  else
160
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
161
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
178
+ names = options[:name].split(',')
179
+ containers = TestLab::Container.find(names)
180
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
162
181
 
163
- container.build
182
+ containers.each do |container|
183
+ container.build
184
+ end
164
185
  end
165
186
  end
166
187
  end
167
188
 
168
189
  # CONTAINER STATUS
169
190
  ###################
170
- c.desc 'Display the status of container(s)'
191
+ c.desc 'Display the status of containers'
171
192
  c.long_desc <<-EOF
172
- Displays the status of all containers or a single container if supplied via the ID parameter.
193
+ Displays the status of all containers or single/multiple containers if supplied via the ID parameter.
173
194
  EOF
174
195
  c.command :status do |status|
175
196
  status.action do |global_options, options, args|
197
+ containers = Array.new
198
+
176
199
  if options[:name].nil?
177
200
  # No ID supplied; show everything
178
- containers = @testlab.containers.delete_if{ |c| c.node.dead? }
179
- if containers.count == 0
180
- @testlab.ui.stderr.puts("You either have no containers defined or dead nodes!".yellow)
181
- else
182
- ZTK::Report.new(:ui => @testlab.ui).list(containers, TestLab::Container::STATUS_KEYS) do |container|
183
- OpenStruct.new(container.status)
184
- end
185
- end
201
+ containers = TestLab::Container.all
186
202
  else
187
- # ID supplied; show just that item
188
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
189
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
203
+ # ID supplied; show just those items
204
+ names = options[:name].split(',')
205
+ containers = TestLab::Container.find(names)
206
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
207
+ end
208
+
209
+ containers = containers.delete_if{ |container| container.node.dead? }
190
210
 
191
- ZTK::Report.new(:ui => @testlab.ui).list(container, TestLab::Container::STATUS_KEYS) do |container|
211
+ if (containers.count == 0)
212
+ @testlab.ui.stderr.puts("You either have no containers defined or dead nodes!".yellow)
213
+ else
214
+ ZTK::Report.new(:ui => @testlab.ui).list(containers, TestLab::Container::STATUS_KEYS) do |container|
192
215
  OpenStruct.new(container.status)
193
216
  end
194
217
  end
218
+
195
219
  end
196
220
  end
197
221
 
@@ -231,12 +255,17 @@ EOF
231
255
  c.command :'ssh-config' do |ssh_config|
232
256
 
233
257
  ssh_config.action do |global_options, options, args|
234
- help_now!('a name is required') if options[:name].nil?
235
-
236
- container = @testlab.containers.select{ |n| n.id.to_sym == options[:name].to_sym }.first
237
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
258
+ if options[:name].nil?
259
+ help_now!('a name is required') if options[:name].nil?
260
+ else
261
+ names = options[:name].split(',')
262
+ containers = TestLab::Container.find(names)
263
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
238
264
 
239
- puts(container.ssh_config)
265
+ containers.each do |container|
266
+ puts(container.ssh_config)
267
+ end
268
+ end
240
269
  end
241
270
  end
242
271
 
@@ -255,16 +284,19 @@ EOF
255
284
  if options[:name].nil?
256
285
  help_now!('a name is required') if options[:name].nil?
257
286
  else
258
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
259
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
260
-
261
- container.teardown
262
- container.down
263
- container.destroy
264
-
265
- container.create
266
- container.up
267
- container.setup
287
+ names = options[:name].split(',')
288
+ containers = TestLab::Container.find(names)
289
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
290
+
291
+ containers.each do |container|
292
+ container.teardown
293
+ container.down
294
+ container.destroy
295
+
296
+ container.create
297
+ container.up
298
+ container.setup
299
+ end
268
300
  end
269
301
  end
270
302
  end
@@ -273,17 +305,20 @@ EOF
273
305
  ##################
274
306
  c.desc 'Clone a container'
275
307
  c.long_desc <<-EOF
276
- Clone a container. The container is offlined and an ephemeral copy of it is started.
308
+ An ephemeral copy of the container is started. There is a small delay incured during the first clone operation.
277
309
  EOF
278
310
  c.command :clone do |clone|
279
311
  clone.action do |global_options, options, args|
280
312
  if options[:name].nil?
281
313
  help_now!('a name is required') if options[:name].nil?
282
314
  else
283
- container = @testlab.containers.select{ |c| c.id.to_sym == options[:name].to_sym }.first
284
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
315
+ names = options[:name].split(',')
316
+ containers = TestLab::Container.find(names)
317
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
285
318
 
286
- container.clone
319
+ containers.each do |container|
320
+ container.clone
321
+ end
287
322
  end
288
323
  end
289
324
  end
@@ -304,12 +339,17 @@ EOF
304
339
  export.flag [:output]
305
340
 
306
341
  export.action do |global_options, options, args|
307
- help_now!('a name is required') if options[:name].nil?
308
-
309
- container = @testlab.containers.select{ |n| n.id.to_sym == options[:name].to_sym }.first
310
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
342
+ if options[:name].nil?
343
+ help_now!('a name is required') if options[:name].nil?
344
+ else
345
+ names = options[:name].split(',')
346
+ containers = TestLab::Container.find(names)
347
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
311
348
 
312
- container.export(options[:compression], options[:output])
349
+ containers.each do |container|
350
+ container.export(options[:compression], options[:output])
351
+ end
352
+ end
313
353
  end
314
354
  end
315
355
 
@@ -323,13 +363,18 @@ EOF
323
363
  import.flag [:input]
324
364
 
325
365
  import.action do |global_options, options, args|
326
- help_now!('a name is required') if options[:name].nil?
327
- help_now!('a filename is required') if options[:input].nil?
328
-
329
- container = @testlab.containers.select{ |n| n.id.to_sym == options[:name].to_sym }.first
330
- container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!"
366
+ if (options[:name].nil? || options[:input].nil?)
367
+ help_now!('a name is required') if options[:name].nil?
368
+ help_now!('a filename is required') if options[:input].nil?
369
+ else
370
+ names = options[:name].split(',')
371
+ containers = TestLab::Container.find(names)
372
+ (containers.nil? || (containers.count == 0)) and raise TestLab::TestLabError, "We could not find any of the containers you supplied!"
331
373
 
332
- container.import(options[:input])
374
+ containers.each do |container|
375
+ container.import(options[:input])
376
+ end
377
+ end
333
378
  end
334
379
  end
335
380
 
@@ -167,30 +167,34 @@ EOF
167
167
 
168
168
  # NETWORK STATUS
169
169
  #################
170
- c.desc 'Display the status of network(s)'
170
+ c.desc 'Display the status of networks'
171
171
  c.long_desc <<-EOF
172
- Displays the status of all networks or a single network if supplied via the ID parameter.
172
+ Displays the status of all networks or single/multiple networks if supplied via the ID parameter.
173
173
  EOF
174
174
  c.command :status do |status|
175
175
  status.action do |global_options, options, args|
176
176
  if options[:name].nil?
177
- # No ID supplied; show everything
178
- networks = @testlab.networks.delete_if{|n| n.node.dead? }
179
- if networks.count == 0
177
+ networks = Array.new
178
+
179
+ if options[:name].nil?
180
+ # No ID supplied; show everything
181
+ networks = TestLab::Network.all
182
+ else
183
+ # ID supplied; show just those items
184
+ names = options[:name].split(',')
185
+ networks = TestLab::Network.find(names)
186
+ (networks.nil? || (networks.count == 0)) and raise TestLab::TestLabError, "We could not find any of the networks you supplied!"
187
+ end
188
+
189
+ networks = networks.delete_if{ |network| network.node.dead? }
190
+
191
+ if (networks.count == 0)
180
192
  @testlab.ui.stderr.puts("You either have no networks defined or dead nodes!".yellow)
181
193
  else
182
194
  ZTK::Report.new(:ui => @testlab.ui).list(networks, TestLab::Network::STATUS_KEYS) do |network|
183
195
  OpenStruct.new(network.status)
184
196
  end
185
197
  end
186
- else
187
- # ID supplied; show just that item
188
- network = @testlab.networks.select{ |c| c.id.to_sym == options[:name].to_sym }.first
189
- network.nil? and raise TestLab::TestLabError, "We could not find the network you supplied!"
190
-
191
- ZTK::Report.new(:ui => @testlab.ui).list(network, TestLab::Network::STATUS_KEYS) do |network|
192
- OpenStruct.new(network.status)
193
- end
194
198
  end
195
199
  end
196
200
  end
@@ -12,6 +12,7 @@ class TestLab
12
12
  def create
13
13
  @ui.logger.debug { "Container Create: #{self.id} " }
14
14
 
15
+ (self.node.state == :not_created) and return false
15
16
  (self.lxc.state != :not_created) and return false
16
17
 
17
18
  please_wait(:ui => @ui, :message => format_object_action(self, 'Create', :green)) do
@@ -31,6 +32,7 @@ class TestLab
31
32
  def destroy
32
33
  @ui.logger.debug { "Container Destroy: #{self.id} " }
33
34
 
35
+ (self.node.state == :not_created) and return false
34
36
  (self.lxc.state == :not_created) and return false
35
37
 
36
38
  please_wait(:ui => @ui, :message => format_object_action(self, 'Destroy', :red)) do
@@ -49,6 +51,7 @@ class TestLab
49
51
  def up
50
52
  @ui.logger.debug { "Container Up: #{self.id} " }
51
53
 
54
+ (self.node.state != :running) and return false
52
55
  (self.lxc.state == :running) and return false
53
56
 
54
57
  please_wait(:ui => @ui, :message => format_object_action(self, 'Up', :green)) do
@@ -79,6 +82,7 @@ class TestLab
79
82
  def down
80
83
  @ui.logger.debug { "Container Down: #{self.id} " }
81
84
 
85
+ (self.node.state != :running) and return false
82
86
  (self.lxc.state != :running) and return false
83
87
 
84
88
  please_wait(:ui => @ui, :message => format_object_action(self, 'Down', :red)) do
@@ -18,7 +18,11 @@ class TestLab
18
18
  # execute. This is generally a bash script of some sort for example.
19
19
  # @return [String] The output of *lxc-attach*.
20
20
  def bootstrap(content)
21
- self.lxc.bootstrap(content)
21
+ if self.lxc_clone.exists?
22
+ self.ssh.bootstrap(content)
23
+ else
24
+ self.lxc.bootstrap(content)
25
+ end
22
26
  end
23
27
 
24
28
  # LXC::Container object
@@ -7,6 +7,7 @@ class TestLab
7
7
  def create
8
8
  @ui.logger.debug { "Network Create: #{self.id} " }
9
9
 
10
+ (self.node.state == :not_created) and return false
10
11
  (self.state != :not_created) and return false
11
12
 
12
13
  please_wait(:ui => @ui, :message => format_object_action(self, 'Create', :green)) do
@@ -20,6 +21,7 @@ class TestLab
20
21
  def destroy
21
22
  @ui.logger.debug { "Network Destroy: #{self.id} " }
22
23
 
24
+ (self.node.state == :not_created) and return false
23
25
  (self.state == :not_created) and return false
24
26
 
25
27
  please_wait(:ui => @ui, :message => format_object_action(self, 'Destroy', :red)) do
@@ -33,6 +35,7 @@ class TestLab
33
35
  def up
34
36
  @ui.logger.debug { "Network Up: #{self.id} " }
35
37
 
38
+ (self.node.state != :running) and return false
36
39
  (self.state == :running) and return false
37
40
 
38
41
  please_wait(:ui => @ui, :message => format_object_action(self, 'Up', :green)) do
@@ -46,6 +49,7 @@ class TestLab
46
49
  def down
47
50
  @ui.logger.debug { "Network Down: #{self.id} " }
48
51
 
52
+ (self.node.state != :running) and return false
49
53
  (self.state != :running) and return false
50
54
 
51
55
  please_wait(:ui => @ui, :message => format_object_action(self, 'Down', :red)) do
@@ -27,7 +27,8 @@ class TestLab
27
27
  "repo_dir" => testlab.repo_dir.inspect,
28
28
  "labfile_path" => testlab.labfile_path.inspect,
29
29
  "logdev" => testlab.ui.logger.logdev.inspect,
30
- "version" => TestLab::VERSION
30
+ "version" => TestLab::VERSION.inspect,
31
+ "argv" => ARGV.inspect
31
32
  }
32
33
  end
33
34
 
@@ -1,6 +1,6 @@
1
1
  class TestLab
2
2
  unless const_defined?(:VERSION)
3
3
  # TestLab Gem Version
4
- VERSION = "0.7.4"
4
+ VERSION = "0.7.5"
5
5
  end
6
6
  end
data/lib/testlab.rb CHANGED
@@ -108,13 +108,18 @@ class TestLab
108
108
  self.ui = (options[:ui] || ZTK::UI.new)
109
109
  self.class.ui = self.ui
110
110
 
111
- @config_dir = (options[:config_dir] || File.join(Dir.pwd, ".testlab-#{TestLab.hostname}"))
112
- @repo_dir = (options[:repo_dir] || Dir.pwd)
111
+ @repo_dir = File.expand_path(options[:repo_dir] || Dir.pwd)
112
+
113
+ @config_dir = File.expand_path(options[:config_dir] || File.join(@repo_dir, ".testlab-#{TestLab.hostname}"))
114
+ File.exists?(@config_dir) or FileUtils.mkdir_p(@config_dir)
115
+
116
+ labfile_path = (options[:labfile_path] || File.join(@repo_dir, 'Labfile'))
117
+ @labfile_path = File.expand_path(ZTK::Locator.find(labfile_path))
113
118
 
114
- labfile = (options[:labfile] || File.join(Dir.pwd, 'Labfile'))
115
- @labfile_path = ZTK::Locator.find(labfile)
116
119
  @labfile = TestLab::Labfile.load(labfile_path)
117
120
  @labfile.testlab = self
121
+
122
+ Dir.chdir(@repo_dir)
118
123
  end
119
124
 
120
125
  # Test Lab Nodes
@@ -23,7 +23,7 @@ describe TestLab::Container do
23
23
 
24
24
  subject {
25
25
  @ui = ZTK::UI.new(:stdout => StringIO.new, :stderr => StringIO.new)
26
- @testlab = TestLab.new(:labfile => LABFILE, :ui => @ui)
26
+ @testlab = TestLab.new(:labfile_path => LABFILE, :ui => @ui)
27
27
  @testlab.containers.first
28
28
  }
29
29
 
@@ -145,6 +145,7 @@ describe TestLab::Container do
145
145
 
146
146
  describe "#create" do
147
147
  it "should create the container" do
148
+ subject.node.stub(:state) { :running }
148
149
  subject.lxc.config.stub(:save) { true }
149
150
  subject.stub(:detect_arch) { "amd64" }
150
151
  subject.lxc.stub(:create) { true }
@@ -156,6 +157,7 @@ describe TestLab::Container do
156
157
 
157
158
  describe "#destroy" do
158
159
  it "should destroy the container" do
160
+ subject.node.stub(:state) { :running }
159
161
  subject.lxc.stub(:exists?) { true }
160
162
  subject.lxc.stub(:state) { :stopped }
161
163
  subject.lxc.stub(:destroy) { true }
@@ -166,6 +168,7 @@ describe TestLab::Container do
166
168
 
167
169
  describe "#up" do
168
170
  it "should up the container" do
171
+ subject.node.stub(:state) { :running }
169
172
  subject.lxc.stub(:exists?) { true }
170
173
  subject.lxc.stub(:start) { true }
171
174
  subject.lxc.stub(:wait) { true }
@@ -180,6 +183,7 @@ describe TestLab::Container do
180
183
 
181
184
  describe "#down" do
182
185
  it "should down the container" do
186
+ subject.node.stub(:state) { :running }
183
187
  subject.lxc.stub(:exists?) { true }
184
188
  subject.lxc.stub(:stop) { true }
185
189
  subject.lxc.stub(:wait) { true }
data/spec/network_spec.rb CHANGED
@@ -23,7 +23,7 @@ describe TestLab::Network do
23
23
 
24
24
  subject {
25
25
  @ui = ZTK::UI.new(:stdout => StringIO.new, :stderr => StringIO.new)
26
- @testlab = TestLab.new(:labfile => LABFILE, :ui => @ui)
26
+ @testlab = TestLab.new(:labfile_path => LABFILE, :ui => @ui)
27
27
  @testlab.networks.first
28
28
  }
29
29
 
@@ -108,6 +108,7 @@ describe TestLab::Network do
108
108
 
109
109
  describe "#create" do
110
110
  it "should create the network bridge" do
111
+ subject.node.stub(:state) { :running }
111
112
  subject.stub(:state) { :not_created }
112
113
  subject.node.ssh.stub(:exec) { true }
113
114
  subject.create
@@ -116,6 +117,7 @@ describe TestLab::Network do
116
117
 
117
118
  describe "#destroy" do
118
119
  it "should destroy the network bridge" do
120
+ subject.node.stub(:state) { :running }
119
121
  subject.stub(:state) { :stopped }
120
122
  subject.node.ssh.stub(:exec) { true }
121
123
  subject.destroy
@@ -124,6 +126,7 @@ describe TestLab::Network do
124
126
 
125
127
  describe "#up" do
126
128
  it "should online the network bridge" do
129
+ subject.node.stub(:state) { :running }
127
130
  subject.stub(:state) { :stopped }
128
131
  subject.node.ssh.stub(:exec) { true }
129
132
  subject.up
@@ -132,6 +135,7 @@ describe TestLab::Network do
132
135
 
133
136
  describe "#down" do
134
137
  it "should offline the network bridge" do
138
+ subject.node.stub(:state) { :running }
135
139
  subject.stub(:state) { :running }
136
140
  subject.node.ssh.stub(:exec) { true }
137
141
  subject.down
data/spec/node_spec.rb CHANGED
@@ -23,7 +23,7 @@ describe TestLab::Node do
23
23
 
24
24
  subject {
25
25
  @ui = ZTK::UI.new(:stdout => StringIO.new, :stderr => StringIO.new)
26
- @testlab = TestLab.new(:labfile => LABFILE, :ui => @ui)
26
+ @testlab = TestLab.new(:labfile_path => LABFILE, :ui => @ui)
27
27
  @testlab.nodes.first
28
28
  }
29
29
 
@@ -23,7 +23,7 @@ describe TestLab::Provisioner::Shell do
23
23
 
24
24
  subject {
25
25
  @ui = ZTK::UI.new(:stdout => StringIO.new, :stderr => StringIO.new)
26
- @testlab = TestLab.new(:labfile => LABFILE, :ui => @ui)
26
+ @testlab = TestLab.new(:labfile_path => LABFILE, :ui => @ui)
27
27
  TestLab::Container.first('server-shell')
28
28
  }
29
29
 
@@ -42,7 +42,9 @@ describe TestLab::Provisioner::Shell do
42
42
  it "should provision the container" do
43
43
  subject.node.ssh.stub(:file).and_yield(StringIO.new)
44
44
  subject.stub(:fs_root) { "/var/lib/lxc/#{subject.id}/rootfs" }
45
+ subject.ssh.stub(:bootstrap) { "" }
45
46
  subject.lxc.stub(:bootstrap) { "" }
47
+ subject.lxc_clone.stub(:exists?) { false }
46
48
 
47
49
  p = TestLab::Provisioner::Shell.new(subject.config, @ui)
48
50
  p.on_container_setup(subject)
@@ -53,7 +55,9 @@ describe TestLab::Provisioner::Shell do
53
55
  it "should raise an exception" do
54
56
  subject.node.ssh.stub(:file).and_yield(StringIO.new)
55
57
  subject.stub(:fs_root) { "/var/lib/lxc/#{subject.id}/rootfs" }
58
+ subject.ssh.stub(:bootstrap) { "" }
56
59
  subject.lxc.stub(:bootstrap) { "" }
60
+ subject.lxc_clone.stub(:exists?) { false }
57
61
 
58
62
  p = TestLab::Provisioner::Shell.new(Hash.new, @ui)
59
63
  lambda{ p.on_container_setup(subject) }.should raise_error TestLab::Provisioner::ShellError
data/spec/testlab_spec.rb CHANGED
@@ -23,7 +23,7 @@ describe TestLab do
23
23
 
24
24
  subject {
25
25
  @ui = ZTK::UI.new(:stdout => StringIO.new, :stderr => StringIO.new)
26
- @testlab = TestLab.new(:labfile => LABFILE, :ui => @ui)
26
+ @testlab = TestLab.new(:labfile_path => LABFILE, :ui => @ui)
27
27
  }
28
28
 
29
29
  describe "class" do
data/testlab.gemspec CHANGED
@@ -45,6 +45,7 @@ Gem::Specification.new do |spec|
45
45
  spec.add_development_dependency("pry")
46
46
  spec.add_development_dependency("rake")
47
47
  spec.add_development_dependency("redcarpet")
48
+ spec.add_development_dependency("aruba")
48
49
  spec.add_development_dependency("rspec")
49
50
  spec.add_development_dependency("yard")
50
51
  spec.add_development_dependency("coveralls")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testlab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.7.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-01 00:00:00.000000000 Z
12
+ date: 2013-07-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gli
@@ -139,6 +139,22 @@ dependencies:
139
139
  - - ! '>='
140
140
  - !ruby/object:Gem::Version
141
141
  version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: aruba
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
142
158
  - !ruby/object:Gem::Dependency
143
159
  name: rspec
144
160
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +222,13 @@ files:
206
222
  - README.md
207
223
  - Rakefile
208
224
  - bin/tl
225
+ - features/step_definitions/container_steps.rb
226
+ - features/step_definitions/network_steps.rb
227
+ - features/step_definitions/node_steps.rb
228
+ - features/step_definitions/testlab_steps.rb
229
+ - features/support/Labfile
230
+ - features/support/env.rb
231
+ - features/testlab.feature
209
232
  - lib/commands/container.rb
210
233
  - lib/commands/network.rb
211
234
  - lib/commands/node.rb
@@ -303,7 +326,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
303
326
  version: '0'
304
327
  segments:
305
328
  - 0
306
- hash: -2914304308752783600
329
+ hash: -1054202206124374407
307
330
  required_rubygems_version: !ruby/object:Gem::Requirement
308
331
  none: false
309
332
  requirements:
@@ -312,7 +335,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
312
335
  version: '0'
313
336
  segments:
314
337
  - 0
315
- hash: -2914304308752783600
338
+ hash: -1054202206124374407
316
339
  requirements: []
317
340
  rubyforge_project:
318
341
  rubygems_version: 1.8.25
@@ -320,6 +343,13 @@ signing_key:
320
343
  specification_version: 3
321
344
  summary: A toolkit for building virtual computer labs
322
345
  test_files:
346
+ - features/step_definitions/container_steps.rb
347
+ - features/step_definitions/network_steps.rb
348
+ - features/step_definitions/node_steps.rb
349
+ - features/step_definitions/testlab_steps.rb
350
+ - features/support/Labfile
351
+ - features/support/env.rb
352
+ - features/testlab.feature
323
353
  - spec/container_spec.rb
324
354
  - spec/network_spec.rb
325
355
  - spec/node_spec.rb