esx 0.3.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ # 0.4.1 - 2012/03/26
2
+
3
+ * Minor fixes
4
+
5
+ # 0.4 - 2012/03/25
6
+
7
+ * Do not include test data inside the gem.
8
+
9
+ * Define a Logger object
10
+
11
+ ESX::Log.info "foobar"
12
+
13
+ * Added some debugging with Log.debug
14
+
15
+ * ESX::Host now has a *templates_dir* attribute and supports uploading and cloning templates:
16
+
17
+ host = ESX::Host.connect 'my-esx-host',
18
+ 'root',
19
+ 'password'
20
+
21
+ host.import_template "/path/to/template.vmdk"
22
+
23
+ This will copy the "template.vmdk" file to the default templates_dir in ESX. Default templates dir is "/vmfs/volumes/datastore1/esx-gem/templates".
24
+
25
+ The template is automatically converted to VMDK thin format.
26
+
27
+ Using the template:
28
+
29
+ host.copy_from_template "template.vmdk", "/vmfs/volumes/datastore1/foo.vmdk"
30
+
31
+ Sorter version:
32
+
33
+ host.import_disk "/path/to/local/template.vmdk", # local file
34
+ "/vmfs/volumes/datastore1/foo.vmdk", # remote path in ESX
35
+ { :use_template => true }
36
+
37
+ If the template "template.vmdk" is found, use that. Otherwise import the disk, save it as a template and clone the template to "/vmfs/volumes/datastore1/foo.vmdk"
38
+
39
+ * Added the following methods to ESX::Host
40
+
41
+ * ESX::Host.has_template?
42
+ * ESX::Host.list_templates
43
+ * ESX::Host.delete_template
44
+
45
+ * Better test coverage
data/Rakefile CHANGED
@@ -21,6 +21,7 @@ Jeweler::Tasks.new do |gem|
21
21
  gem.add_runtime_dependency 'net-ssh'
22
22
  gem.add_runtime_dependency 'net-scp'
23
23
  gem.add_runtime_dependency 'clamp'
24
+ gem.files.exclude 'spec/data/*'
24
25
  # gem.add_development_dependency 'rspec', '> 1.2.3'
25
26
  end
26
27
  Jeweler::RubygemsDotOrgTasks.new
data/lib/esx.rb CHANGED
@@ -6,16 +6,28 @@ require 'net/ssh'
6
6
 
7
7
  module ESX
8
8
 
9
- VERSION = '0.3.2'
9
+ VERSION = '0.4.1'
10
+
11
+ if !defined? Log or Log.nil?
12
+ Log = Logger.new($stdout)
13
+ Log.formatter = proc do |severity, datetime, progname, msg|
14
+ "[ESX] #{severity}: #{msg}\n"
15
+ end
16
+ Log.level = Logger::INFO unless (ENV["DEBUG"].eql? "yes" or \
17
+ ENV["DEBUG"].eql? "true")
18
+ Log.debug "Initializing logger"
19
+ end
10
20
 
11
21
  class Host
12
22
 
13
23
  attr_reader :address, :user, :password
24
+ attr_accessor :templates_dir
14
25
 
15
- def initialize(address, user, password)
26
+ def initialize(address, user, password, opts = {})
16
27
  @address = address
17
28
  @password = password
18
29
  @user = user
30
+ @templates_dir = opts[:templates_dir] || "/vmfs/volumes/datastore1/esx-gem/templates"
19
31
  end
20
32
 
21
33
  # Connect to a ESX host
@@ -216,46 +228,144 @@ module ESX
216
228
  # Run a command in the ESX host via SSH
217
229
  #
218
230
  def remote_command(cmd)
231
+ output = ""
219
232
  Net::SSH.start(@address, @user, :password => @password) do |ssh|
220
- ssh.exec! cmd
233
+ output = ssh.exec! cmd
221
234
  end
235
+ output
222
236
  end
223
237
 
224
238
  #
225
239
  # Upload file
226
240
  #
227
- def upload_file(source, dest, print_progress = true)
241
+ def upload_file(source, dest, print_progress = false)
228
242
  Net::SSH.start(@address, @user, :password => @password) do |ssh|
229
- puts "Uploading file... (#{File.basename(source)})"
243
+ Log.info "Uploading file #{File.basename(source)}..." if print_progress
230
244
  ssh.scp.upload!(source, dest) do |ch, name, sent, total|
231
245
  if print_progress
232
246
  print "\rProgress: #{(sent.to_f * 100 / total.to_f).to_i}% completed"
233
247
  end
234
248
  end
235
249
  end
236
- puts if print_progress
237
250
  end
238
251
 
239
- def import_disk(source, destination, print_progress = true)
252
+ def template_exist?(vmdk_file)
253
+ template_file = File.join(@templates_dir, File.basename(vmdk_file))
254
+ Log.debug "Checking if template #{template_file} exists"
255
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
256
+ return false if (ssh.exec! "ls -la #{@templates_dir} 2>/dev/null").nil?
257
+ return false if (ssh.exec! "ls #{template_file} 2>/dev/null").nil?
258
+ end
259
+ Log.debug "Template #{template_file} found"
260
+ true
261
+ end
262
+ alias :has_template? :template_exist?
263
+
264
+ def list_templates
265
+ templates = []
266
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
267
+ output = (ssh.exec! "ls -l #{@templates_dir}/*-flat.vmdk 2>/dev/null")
268
+ output.each_line do |t|
269
+ templates << t.gsub(/-flat\.vmdk/,".vmdk").split().last.strip.chomp rescue next
270
+ end unless output.nil?
271
+ end
272
+ templates
273
+ end
274
+
275
+ #
276
+ # Expects fooimg.vmdk
277
+ # Trims path if /path/to/fooimg.vmdk
278
+ #
279
+ def delete_template(template_disk)
280
+ Log.debug "deleting template #{template_disk}"
281
+ template = File.join(@templates_dir, File.basename(template_disk))
282
+ template_flat = File.join(@templates_dir, File.basename(template_disk, ".vmdk") + "-flat.vmdk")
283
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
284
+ if (ssh.exec! "ls #{template} 2>/dev/null").nil?
285
+ Log.error "Template #{template_disk} does not exist"
286
+ raise "Template does not exist"
287
+ end
288
+ ssh.exec!("rm -f #{template} && rm -f #{template_flat} 2>&1")
289
+ end
290
+ end
291
+
292
+ def import_template(source, params = {})
293
+ print_progress = params[:print_progress] || false
294
+ dest_file = File.join(@templates_dir, File.basename(source))
295
+ Log.debug "Importing template #{source} to #{dest_file}"
296
+ return dest_file if template_exist?(dest_file)
297
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
298
+ if (ssh.exec! "ls -la #{@templates_dir} 2>/dev/null").nil?
299
+ # Create template dir
300
+ Log.debug "Creating templates dir #{@templates_dir}"
301
+ ssh.exec "mkdir -p #{@templates_dir}"
302
+ end
303
+ end
304
+ import_disk_convert(source, dest_file, print_progress)
305
+ end
306
+
307
+ #
308
+ # Expects vmdk source file path and destination path
309
+ #
310
+ # copy_from_template "/home/fooser/my.vmdk", "/vmfs/volumes/datastore1/foovm/foovm.vmdk"
311
+ #
312
+ # Destination directory must exist otherwise rises exception
313
+ #
314
+ def copy_from_template(template_disk, destination)
315
+ Log.debug "Copying from template #{template_disk} to #{destination}"
316
+ raise "Template does not exist" if not template_exist?(template_disk)
317
+ source = File.join(@templates_dir, File.basename(template_disk))
318
+ Net::SSH.start(@address, @user, :password => @password) do |ssh|
319
+ Log.debug "Clone disk #{source} to #{destination}"
320
+ Log.debug ssh.exec!("vmkfstools -i #{source} --diskformat thin #{destination} 2>&1")
321
+ end
322
+ end
323
+
324
+ #
325
+ # Imports a VMDK
326
+ #
327
+ # if params has :use_template => true, the disk is saved as a template in
328
+ # @templates_dir and cloned from there.
329
+ #
330
+ # Destination directory must exist otherwise rises exception
331
+ #
332
+ def import_disk(source, destination, print_progress = false, params = {})
333
+ use_template = params[:use_template] || false
334
+ if use_template
335
+ Log.debug "import_disk :use_template => true"
336
+ if !template_exist?(source)
337
+ Log.debug "import_disk, template does not exist, importing."
338
+ import_template(source, { :print_progress => print_progress })
339
+ end
340
+ copy_from_template(source, destination)
341
+ else
342
+ import_disk_convert source, destination, print_progress
343
+ end
344
+ end
345
+
346
+ #
347
+ # This method does all the heavy lifting when importing the disk.
348
+ # It also converts the imported VMDK to thin format
349
+ #
350
+ def import_disk_convert(source, destination, print_progress = false)
240
351
  tmp_dest = destination + ".tmp"
241
352
  Net::SSH.start(@address, @user, :password => @password) do |ssh|
242
353
  if not (ssh.exec! "ls #{destination} 2>/dev/null").nil?
243
354
  raise Exception.new("Destination file #{destination} already exists")
244
355
  end
245
- puts "Uploading file... (#{File.basename(source)})"
356
+ Log.info "Uploading file... (#{File.basename(source)})" if print_progress
246
357
  ssh.scp.upload!(source, tmp_dest) do |ch, name, sent, total|
247
358
  if print_progress
248
359
  print "\rProgress: #{(sent.to_f * 100 / total.to_f).to_i}%"
249
360
  end
250
361
  end
251
362
  if print_progress
252
- puts "\nConverting disk..."
363
+ Log.info "Converting disk..."
253
364
  ssh.exec "vmkfstools -i #{tmp_dest} --diskformat thin #{destination}; rm -f #{tmp_dest}"
254
365
  else
255
366
  ssh.exec "vmkfstools -i #{tmp_dest} --diskformat thin #{destination} >/dev/null 2>&1; rm -f #{tmp_dest}"
256
367
  end
257
368
  end
258
- puts
259
369
  end
260
370
 
261
371
  private
@@ -317,7 +427,9 @@ module ESX
317
427
  def self.wrap(vm)
318
428
  _vm = VM.new
319
429
  _vm.name = vm.name
320
- _vm.memory_size = vm.summary.config.memorySizeMB*1024*1024
430
+ ## HACK: for some reason vm.summary.config.memorySizeMB returns nil
431
+ # under some conditions
432
+ _vm.memory_size = vm.summary.config.memorySizeMB*1024*1024 rescue 0
321
433
  _vm.cpus = vm.summary.config.numCpu
322
434
  _vm.ethernet_cards_number = vm.summary.config.numEthernetCards
323
435
  _vm.virtual_disks_number = vm.summary.config.numVirtualDisks
@@ -13,15 +13,15 @@ require 'esx'
13
13
  module ESXTestHelpers
14
14
 
15
15
  def esx_host
16
- "esx-test-host"
16
+ ENV["ESX_HOST"] || "esx-test-host"
17
17
  end
18
18
 
19
19
  def esx_user
20
- "root"
20
+ ENV["ESX_USER"] || "root"
21
21
  end
22
22
 
23
23
  def esx_password
24
- ""
24
+ ENV["ESX_PASSWORD"] || ""
25
25
  end
26
26
 
27
27
  def test_data_dir
@@ -145,4 +145,58 @@ describe "ESX host" do
145
145
  @test_host.memory_usage.should be > 0
146
146
  end
147
147
 
148
+ it "should have default templates path" do
149
+ @test_host.templates_dir.should == "/vmfs/volumes/datastore1/esx-gem/templates"
150
+ end
151
+
152
+ it "should not have tc.vmdk template" do
153
+ @test_host.has_template?("tc.vmdk").should == false
154
+ @test_host.list_templates.size.should == 0
155
+ end
156
+
157
+ it "should not have -flat.vmdk postfix in template names" do
158
+ @test_host.list_templates.each do |t|
159
+ t.should_not match /-flat\.vmdk$/
160
+ end
161
+ end
162
+
163
+ it "should import tc.vmdk template" do
164
+ @test_host.import_template File.join(test_data_dir, "tc.vmdk")
165
+ @test_host.has_template?("tc.vmdk").should == true
166
+ end
167
+
168
+ it "should copy from template" do
169
+ @test_host.has_template?("tc.vmdk").should == true
170
+ @test_host.remote_command("mkdir -p /vmfs/volumes/datastore1/foovm/")
171
+ @test_host.copy_from_template "tc.vmdk", "/vmfs/volumes/datastore1/foovm/foovm.vmdk"
172
+ @test_host.remote_command("ls /vmfs/volumes/datastore1/foovm/foovm.vmdk").strip.chomp.should == "/vmfs/volumes/datastore1/foovm/foovm.vmdk"
173
+ @test_host.remote_command("rm -rf /vmfs/volumes/datastore1/foovm")
174
+ end
175
+
176
+ it "should list one template" do
177
+ @test_host.list_templates.is_a?(Array).should be_true
178
+ @test_host.list_templates.size.should == 1
179
+ end
180
+
181
+ it "should import duplicated tc.vmdk template" do
182
+ @test_host.import_template File.join(test_data_dir, "tc.vmdk")
183
+ @test_host.has_template?("tc.vmdk").should == true
184
+ @test_host.list_templates.size.should == 1
185
+ end
186
+
187
+ it "should delete tc.vmdk and tc-flat.vmdk template" do
188
+ @test_host.delete_template File.join(test_data_dir, "tc.vmdk")
189
+ @test_host.has_template?("tc.vmdk").should == false
190
+ @test_host.list_templates.size.should == 0
191
+ end
192
+
193
+ it "should import disk tc.vmdk into desired dir" do
194
+ @test_host.remote_command("mkdir /vmfs/volumes/datastore1/foobar/")
195
+ @test_host.import_disk(File.join(test_data_dir, "tc.vmdk"),
196
+ "/vmfs/volumes/datastore1/foobar/foobar.vmdk")
197
+
198
+ @test_host.remote_command("ls /vmfs/volumes/datastore1/foobar/foobar.vmdk").strip.chomp.should == "/vmfs/volumes/datastore1/foobar/foobar.vmdk"
199
+ @test_host.remote_command "rm -rf /vmfs/volumes/datastore1/foobar/"
200
+ end
201
+
148
202
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: esx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-03 00:00:00.000000000 Z
12
+ date: 2012-03-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &13584940 !ruby/object:Gem::Requirement
16
+ requirement: &13782400 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *13584940
24
+ version_requirements: *13782400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: bundler
27
- requirement: &13582680 !ruby/object:Gem::Requirement
27
+ requirement: &13797440 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *13582680
35
+ version_requirements: *13797440
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jeweler
38
- requirement: &13616860 !ruby/object:Gem::Requirement
38
+ requirement: &13795980 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *13616860
46
+ version_requirements: *13795980
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: simplecov
49
- requirement: &13614040 !ruby/object:Gem::Requirement
49
+ requirement: &13793800 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *13614040
57
+ version_requirements: *13793800
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: alchemist
60
- requirement: &13611940 !ruby/object:Gem::Requirement
60
+ requirement: &13791580 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *13611940
68
+ version_requirements: *13791580
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rbvmomi
71
- requirement: &13610820 !ruby/object:Gem::Requirement
71
+ requirement: &13790180 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *13610820
79
+ version_requirements: *13790180
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: terminal-table
82
- requirement: &13710140 !ruby/object:Gem::Requirement
82
+ requirement: &13805300 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *13710140
90
+ version_requirements: *13805300
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: net-ssh
93
- requirement: &13708680 !ruby/object:Gem::Requirement
93
+ requirement: &13803800 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :runtime
100
100
  prerelease: false
101
- version_requirements: *13708680
101
+ version_requirements: *13803800
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: net-scp
104
- requirement: &13707760 !ruby/object:Gem::Requirement
104
+ requirement: &13802480 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: '0'
110
110
  type: :runtime
111
111
  prerelease: false
112
- version_requirements: *13707760
112
+ version_requirements: *13802480
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: clamp
115
- requirement: &13707000 !ruby/object:Gem::Requirement
115
+ requirement: &13800940 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ! '>='
@@ -120,7 +120,7 @@ dependencies:
120
120
  version: '0'
121
121
  type: :runtime
122
122
  prerelease: false
123
- version_requirements: *13707000
123
+ version_requirements: *13800940
124
124
  description: Manage VMWare ESX hosts with ease
125
125
  email: rubiojr@frameos.org
126
126
  executables:
@@ -131,6 +131,7 @@ extra_rdoc_files:
131
131
  - README.md
132
132
  files:
133
133
  - .document
134
+ - CHANGELOG.md
134
135
  - Gemfile
135
136
  - LICENSE.txt
136
137
  - README.md
@@ -138,7 +139,6 @@ files:
138
139
  - bin/esx
139
140
  - examples/basics.rb
140
141
  - lib/esx.rb
141
- - spec/data/tc.vmdk
142
142
  - spec/spec_helper.rb
143
143
  - spec/unit/host_spec.rb
144
144
  - spec/unit/vm_spec.rb
@@ -163,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
163
  version: '0'
164
164
  requirements: []
165
165
  rubyforge_project:
166
- rubygems_version: 1.8.10
166
+ rubygems_version: 1.8.17
167
167
  signing_key:
168
168
  specification_version: 3
169
169
  summary: Simple RbVmomi wrapper library to manage VMWare ESX hosts
Binary file