esx 0.3.2 → 0.4.1

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