knife-xapi 0.3.6 → 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.
@@ -1,6 +1,6 @@
1
1
  # Xapi Base Module
2
2
  #
3
- # Description:: Setup the Session and auth for xapi
3
+ # Description:: Setup the Session and auth for xapi
4
4
  # other common methods used for talking with the xapi
5
5
  #
6
6
  # Author:: Jesse Nelson <spheromak@gmail.com>
@@ -21,27 +21,6 @@
21
21
  # See the License for the specific language governing permissions and
22
22
  # limitations under the License.
23
23
 
24
- # ruby 1.8.7 doesn't like ||= with Constants
25
- unless defined?(XAPI_TEMP_REGEX)
26
- XAPI_TEMP_REGEX = /^CentOS 5.*\(64-bit\)/
27
- end
28
-
29
- unless defined?(XAPI_DEFAULTS)
30
- XAPI_DEFAULTS = {
31
- :domain => "",
32
- :ssh_user => "root",
33
- :ssh_port => "22",
34
- :install_repo => "http://isoredirect.centos.org/centos/6/os/x86_64/",
35
- :xapi_disk_size => "graphical utf8",
36
- :xapi_disk_size => "8g",
37
- :xapi_cpus => "1g",
38
- :xapi_mem => "1g",
39
- :bootstrap_template => "ubuntu10.04-gems",
40
- :template_file => false,
41
- :run_list => [],
42
- :json_attributes => {}
43
- }
44
- end
45
24
 
46
25
  require 'chef/knife'
47
26
  require 'units/standard'
@@ -50,15 +29,17 @@ require 'xenapi'
50
29
  class Chef::Knife
51
30
  module XapiBase
52
31
 
32
+ attr :defaults
53
33
 
54
34
  def self.included(includer)
55
35
  includer.class_eval do
36
+
56
37
  deps do
57
38
  require 'xenapi'
58
39
  require 'highline'
59
40
  require 'highline/import'
60
- require 'readline'
61
- end
41
+ require 'readline'
42
+ end
62
43
 
63
44
  option :xapi_host,
64
45
  :short => "-h SERVER_URL",
@@ -77,36 +58,51 @@ class Chef::Knife
77
58
  :long => "--xapi-username USERNAME",
78
59
  :proc => Proc.new { |key| Chef::Config[:knife][:xapi_username] = key },
79
60
  :description => "Your xenserver username"
80
-
61
+
81
62
  option :domain,
82
63
  :short => "-f Name",
83
64
  :long => "--domain Name",
84
65
  :description => "the domain name for the guest",
85
66
  :proc => Proc.new { |key| Chef::Config[:knife][:domain] = key }
86
-
87
67
  end
68
+ end
69
+
70
+
71
+
72
+ def self.set_defaults(config)
73
+ @defaults ||= Hash.new
74
+ @defaults.merge!(config)
75
+ end
76
+
77
+ # set the default template
78
+ self.set_defaults( { :template_regex => /^CentOS 5.*\(64-bit\)/ } )
79
+
80
+ def self.defaults
81
+ @defaults ||= Hash.new
82
+ end
88
83
 
84
+ def self.get_default(key)
85
+ @defaults[key] || nil
89
86
  end
90
87
 
91
- # highline setup
92
88
  def h
93
89
  @highline ||= ui.highline
94
- end
90
+ end
95
91
 
96
92
  # setup and return an authed xen api instance
97
93
  def xapi
98
- @xapi ||= begin
94
+ @xapi ||= begin
99
95
 
100
96
  ui.fatal "Must provide a xapi host with --host "unless locate_config_value(:xapi_host)
101
97
  session = XenApi::Client.new( locate_config_value(:xapi_host) )
102
-
98
+
103
99
  # get the password from the user
104
100
  password = locate_config_value(:xapi_password) || nil
105
101
  username = locate_config_value(:xapi_username) || "root"
106
102
  if password.nil? or password.empty?
107
103
  password = h.ask("Enter password for user #{username}: " ) { |input| input.echo = "*" }
108
104
  end
109
- session.login_with_password(username, password)
105
+ session.login_with_password(username, password)
110
106
 
111
107
  session
112
108
  end
@@ -114,7 +110,7 @@ class Chef::Knife
114
110
 
115
111
  def locate_config_value(key)
116
112
  key = key.to_sym
117
- config[key] || Chef::Config[:knife][key] || XAPI_DEFAULTS[key]
113
+ config[key] || Chef::Config[:knife][key] || Chef::Knife::XapiBase.get_default(key)
118
114
  end
119
115
 
120
116
  # get template by name_label
@@ -123,12 +119,13 @@ class Chef::Knife
123
119
  end
124
120
 
125
121
  #
126
- # find a template matching what the user provided
127
- #
122
+ # find a template matching what the user provided
123
+ #
128
124
  # returns a ref to the vm or nil if nothing found
129
- #
130
- def find_template(template=XAPI_TEMP_REGEX)
131
- # if we got a string then try to find that template exact
125
+ #
126
+ def find_template(template)
127
+ template = locate_config_value(:template_regex) if template.nil?
128
+ # if we got a string then try to find that template exact
132
129
  # if no exact template matches, search
133
130
  if template.is_a?(String)
134
131
  found = get_template(template)
@@ -137,23 +134,23 @@ class Chef::Knife
137
134
 
138
135
  # make sure our nil template gets set to default
139
136
  if template.nil?
140
- template = XAPI_TEMP_REGEX
141
- end
137
+ template = locate_config_value(:template_regex)
138
+ end
142
139
 
143
140
  Chef::Log.debug "Name: #{template.class}"
144
141
  # quick and dirty string to regex
145
142
  unless template.is_a?(Regexp)
146
- template = /#{template}/
143
+ template = /#{template}/
147
144
  end
148
145
 
149
- # loop over all vm's and find the template
146
+ # loop over all vm's and find the template
150
147
  # Wish there was a better API method for this, and there might be
151
148
  # but i couldn't find it
152
149
  Chef::Log.debug "Using regex: #{template}"
153
150
  xapi.VM.get_all_records().each_value do |vm|
154
151
  if vm["is_a_template"] and vm["name_label"] =~ template
155
152
  Chef::Log.debug "Matched: #{h.color(vm["name_label"], :yellow )}"
156
- found = vm # we're gonna go with the last found
153
+ found = vm # we're gonna go with the last found
157
154
  end
158
155
  end
159
156
 
@@ -164,26 +161,67 @@ class Chef::Knife
164
161
  end
165
162
  return nil
166
163
  end
167
-
168
- # present a list of options for a user to select
164
+
165
+ # present a list of options for a user to select
169
166
  # return the selected item
170
167
  def user_select(items)
171
- choose do |menu|
168
+ h.choose do |menu|
172
169
  menu.index = :number
173
170
  menu.prompt = "Please Choose One:"
174
171
  menu.select_by = :index_or_name
175
172
  items.each do |item|
176
- menu.choice item.to_sym do |command|
177
- say "Using: #{command}"
173
+ menu.choice item.to_sym do |command|
174
+ ui.msg "Using: #{command}"
178
175
  selected = command.to_s
179
176
  end
180
177
  end
178
+ menu.choice :all do return :all end
181
179
  menu.choice :exit do exit 1 end
182
180
  end
183
181
  end
184
182
 
183
+ # destroy/remove VM refs and exit
184
+ def cleanup(vm_ref)
185
+ # shutdown and dest
186
+ unless xapi.VM.get_power_state(vm_ref) == "Halted"
187
+ ui.msg "Shutting down Guest"
188
+ task = xapi.Async.VM.hard_shutdown(vm_ref)
189
+ get_task_ref(task) unless task == "Halted"
190
+ end
191
+
192
+ ui.msg "Removing disks attached to Guest"
193
+ Chef::Log.debug "getting vbds attached to #{vm_ref}"
194
+ wait_tasks = []
195
+ xapi.VM.get_VBDs(vm_ref).to_a.each do |vbd|
196
+ next unless vbd
197
+
198
+ Chef::Log.debug "removing vbd: #{vbd}"
199
+ wait_tasks << xapi.Async.VDI.destroy( xapi.VBD.get_record(vbd)["VDI"] )
200
+ wait_tasks << xapi.Async.VBD.destroy(vbd)
201
+ end
202
+
203
+ # wait for disk cleanup to finish up
204
+ unless wait_tasks.empty?
205
+ ui.msg "waiting for disks to cleanup"
206
+ wait_tasks.each do |task|
207
+ wait_on_task(task)
208
+ end
209
+ end
210
+
211
+ ui.msg "Destroying Guest"
212
+ task = xapi.Async.VM.destroy(vm_ref)
213
+ wait_on_task(task)
214
+ end
215
+
216
+ # cleanup a vm and exit (fail)
217
+ def fail(ref=nil)
218
+ ui.warn "Error encountered clenaing up and exiting"
219
+ cleanup ref if ref
220
+ exit 1
221
+ end
222
+
185
223
  # generate a random mac address
186
- def generate_mac
224
+ def generate_mac
187
225
  ("%02x"%(rand(64)*4|2))+(0..4).inject(""){|s,x|s+":%02x"%rand(256)}
188
226
  end
189
227
 
@@ -191,19 +229,19 @@ class Chef::Knife
191
229
  def add_vif_by_name(vm_ref, dev_num, net_name)
192
230
  Chef::Log.debug "Looking up vif for: #{h.color(net_name, :cyan)}"
193
231
  network_ref = xapi.network.get_by_name_label(net_name).first
194
- if network_ref.nil?
195
- if net_name =~ /Network (\d)+$/ # special handing for 'Network X' As displayed by XenCenter
232
+ if network_ref.nil?
233
+ if net_name =~ /Network (\d)+$/ # special handing for 'Network X' As displayed by XenCenter
196
234
  add_vif_by_name(vm_ref, dev_num, "Pool-wide network associated with eth#{$1}")
197
235
  else
198
236
  ui.warn "#{h.color(net_name,:red)} not found, moving on"
199
237
  end
200
- return
238
+ return
201
239
  end
202
240
 
203
241
  mac = generate_mac
204
242
  Chef::Log.debug "Provisioning: #{h.color(net_name, :cyan)}, #{h.color(mac,:green)}, #{h.color(network_ref, :yellow)}"
205
243
 
206
- vif = {
244
+ vif = {
207
245
  'device' => dev_num.to_s,
208
246
  'network' => network_ref,
209
247
  'VM' => vm_ref,
@@ -227,7 +265,7 @@ class Chef::Knife
227
265
 
228
266
  # returns sr_ref to the default sr on pool
229
267
  def find_default_sr()
230
- xapi.pool.get_default_SR( xapi.pool.get_all()[0] )
268
+ xapi.pool.get_default_SR( xapi.pool.get_all()[0] )
231
269
  end
232
270
 
233
271
  # return an SR record from the name_label
@@ -259,7 +297,7 @@ class Chef::Knife
259
297
  def create_vdi(name, sr_ref, size)
260
298
  vdi_record = {
261
299
  "name_label" => "#{name}",
262
- "name_description" => "Root disk for #{name} created by knfie xapi",
300
+ "name_description" => "Root disk for #{name} created by #{ENV['USER']} with knfie xapi",
263
301
  "SR" => sr_ref,
264
302
  "virtual_size" => input_to_bytes(size).to_s,
265
303
  "type" => "system",
@@ -267,7 +305,7 @@ class Chef::Knife
267
305
  "read_only" => false,
268
306
  "other_config" => {},
269
307
  }
270
-
308
+
271
309
  # Async create the VDI
272
310
  task = xapi.Async.VDI.create(vdi_record)
273
311
  ui.msg "waiting for VDI Create"
@@ -282,35 +320,41 @@ class Chef::Knife
282
320
  sleep 1
283
321
  end
284
322
  end
285
-
323
+
286
324
  # return the opaque ref of the task that was run by a task record if it succeded.
287
- # else it returns nil
325
+ # else it returns nil
288
326
  def get_task_ref(task)
327
+ Chef::Log.debug "Waiting on task #{task}"
289
328
  wait_on_task(task)
290
- case xapi.task.get_status(task)
329
+ status_ = xapi.task.get_status(task)
330
+
331
+ case status_
291
332
  when "success"
292
- # xapi task record returns result as <value>OpaqueRef:....</value>
333
+ puts "#{h.color "#{status_}", :green }"
334
+ # xapi task record returns result as <value>OpaqueRef:....</value>
293
335
  # we want the ref. this way it will work if they fix it to return jsut the ref
294
336
  ref = xapi.task.get_result(task).match(/OpaqueRef:[^<]+/).to_s
337
+
295
338
  #cleanup our task
296
339
  xapi.task.destroy(task)
297
340
  return ref
298
- else
299
- ui.msg( "#{h.color 'ERROR:', :red } Task returned: #{xapi.task.get_result(task)}" )
300
- return nil
341
+ else
342
+ ui.msg( "#{h.color "#{status_}", :red } ")
343
+ ui.msg( "#{h.color 'ERROR:', :red } #{xapi.task.get_error_info(task)}" )
301
344
  end
302
345
  end
303
346
 
304
347
 
305
- # create vbd and return a ref
306
- def create_vbd(vm_ref, vdi_ref, position)
348
+ # create vbd and return a ref
349
+ # defaults to bootable
350
+ def create_vbd(vm_ref, vdi_ref, position, boot=true)
307
351
  vbd_record = {
308
352
  "VM" => vm_ref,
309
353
  "VDI" => vdi_ref,
310
354
  "empty" => false,
311
355
  "other_config" => {"owner"=>""},
312
356
  "userdevice" => position.to_s,
313
- "bootable" => true,
357
+ "bootable" => boot,
314
358
  "mode" => "RW",
315
359
  "qos_algorithm_type" => "",
316
360
  "qos_algorithm_params" => {},
@@ -320,7 +364,7 @@ class Chef::Knife
320
364
 
321
365
  task = xapi.Async.VBD.create(vbd_record)
322
366
  ui.msg "Waiting for VBD create"
323
- vbd_ref = get_task_ref(task)
367
+ vbd_ref = get_task_ref(task)
324
368
  end
325
369
 
326
370
  # try to get a guest ip and return it
@@ -334,7 +378,51 @@ class Chef::Knife
334
378
  end
335
379
  end
336
380
  return guest_ip
337
- end
381
+ end
382
+
383
+ def get_vbds_from_vdi(vdi_ref)
384
+ return xapi.VDI.get_VBDs(vdi_ref)
385
+ end
386
+
387
+ def get_all_vdis()
388
+ return xapi.VDI.get_all()
389
+ end
338
390
 
391
+ def get_vdi_by_uuid(id)
392
+ return xapi.VDI.get_by_uuid(id)
393
+ end
394
+
395
+ def get_vdi_by_name_label(name)
396
+ return xapi.VDI.get_by_name_label(name)
397
+ end
398
+
399
+ def print_vdi_info(vdi_ref)
400
+ puts "#{h.color "VDI name: " + xapi.VDI.get_name_label(vdi_ref), :green}"
401
+ puts " -Description: " + xapi.VDI.get_name_description(vdi_ref)
402
+ puts " -Type: " + xapi.VDI.get_type(vdi_ref)
403
+ end
404
+
405
+ def yes_no_prompt(str)
406
+ print str
407
+ choice = STDIN.gets
408
+
409
+ while !(choice.match(/^yes$|^no$/))
410
+ puts "Invalid input! Type \'yes\' or \'no\':"
411
+ choice = STDIN.gets
412
+ end
413
+
414
+ if choice.match('yes')
415
+ return true
416
+ else
417
+ return false
418
+ end
419
+ end
420
+
421
+ def destroy_vdi(vdi_ref)
422
+ task = xapi.Async.VDI.destroy(vdi_ref)
423
+ print "Destroying volume "
424
+ puts "#{h.color xapi.VDI.get_name_label(vdi_ref), :cyan}"
425
+ task_ref = get_task_ref(task)
426
+ end
339
427
  end
340
428
  end
@@ -8,9 +8,9 @@
8
8
  # Licensed under the Apache License, Version 2.0 (the "License");
9
9
  # you may not use this file except in compliance with the License.
10
10
  # You may obtain a copy of the License at
11
- #
11
+ #
12
12
  # http://www.apache.org/licenses/LICENSE-2.0
13
- #
13
+ #
14
14
  # Unless required by applicable law or agreed to in writing, software
15
15
  # distributed under the License is distributed on an "AS IS" BASIS,
16
16
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,17 +24,30 @@ require 'chef/knife/xapi_base'
24
24
  class Chef
25
25
  class Knife
26
26
  class XapiGuestCreate < Knife
27
+ require 'timeout'
27
28
  include Chef::Knife::XapiBase
28
29
 
29
- require 'timeout'
30
+ Chef::Knife::XapiBase.set_defaults( {
31
+ :domain => "",
32
+ :ssh_user => "root",
33
+ :ssh_port => "22",
34
+ :ping_timeout => 600,
35
+ :install_repo => "http://isoredirect.centos.org/centos/6/os/x86_64/",
36
+ :kernel_params => "graphical utf8",
37
+ :xapi_disk_size => "8g",
38
+ :xapi_cpus => "1",
39
+ :xapi_mem => "1g",
40
+ :bootstrap_template => "ubuntu10.04-gems",
41
+ :template_file => false,
42
+ :run_list => [],
43
+ :json_attributes => {}
44
+ })
30
45
 
31
46
  deps do
32
47
  require 'chef/knife/bootstrap'
33
48
  Chef::Knife::Bootstrap.load_deps
34
49
  end
35
50
 
36
-
37
-
38
51
  banner "knife xapi guest create NAME [NETWORKS] (options)"
39
52
 
40
53
  option :xapi_vm_template,
@@ -107,6 +120,10 @@ class Chef
107
120
  :long => "--ssh-port PORT",
108
121
  :description => "The ssh port"
109
122
 
123
+ option :ping_timeout,
124
+ :long => "--ping-timeout",
125
+ :description => "Seconds to timeout waiting for ip from guest"
126
+
110
127
  option :bootstrap_version,
111
128
  :long => "--bootstrap-version VERSION",
112
129
  :description => "The version of Chef to install"
@@ -120,7 +137,7 @@ class Chef
120
137
  :short => "-F FILEPATH",
121
138
  :long => "--template-file TEMPLATE",
122
139
  :description => "Full path to location of template to use"
123
-
140
+
124
141
  option :json_attributes,
125
142
  :short => "-j JSON_ATTRIBS",
126
143
  :long => "--json-attributes",
@@ -153,7 +170,6 @@ class Chef
153
170
  rescue Errno::ECONNREFUSED
154
171
  sleep 2
155
172
  false
156
- # This happens on EC2 quite often
157
173
  rescue Errno::EHOSTUNREACH
158
174
  sleep 2
159
175
  false
@@ -163,11 +179,11 @@ class Chef
163
179
 
164
180
  def wait_for_guest_ip(vm_ref)
165
181
  begin
166
- timeout(480) do
167
- ui.msg "Waiting for guest ip address"
182
+ timeout( locate_config_value(:ping_timeout).to_i ) do
183
+ ui.msg "Waiting for guest ip address"
168
184
  guest_ip = ""
169
185
  while guest_ip.empty?
170
- print(".")
186
+ print(".")
171
187
  sleep @initial_sleep_delay ||= 10
172
188
  vgm = xapi.VM.get_guest_metrics(vm_ref)
173
189
  next if "OpaqueRef:NULL" == vgm
@@ -176,56 +192,41 @@ class Chef
176
192
  guest_ip = networks["0/ip"]
177
193
  end
178
194
  end
179
- puts "\n"
195
+ puts "\n"
180
196
  return guest_ip
181
- end
197
+ end
182
198
  rescue Timeout::Error
183
199
  ui.msg "Timeout waiting for XAPI to report IP address "
184
200
  end
185
201
  end
186
202
 
187
- # destroy/remove VM refs and exit
188
- def cleanup(vm_ref)
189
- ui.warn "Cleaning up work and exiting"
190
- # shutdown and dest
191
- unless xapi.VM.get_power_state(vm_ref) == "Halted"
192
- print "Shutting down Guest"
193
- task = xapi.Async.VM.hard_shutdown(vm_ref)
194
- wait_on_task(task)
195
- print " #{h.color "Done", :green} \n"
196
- end
197
-
198
- print "Destroying Guest"
199
- task = xapi.Async.VM.destroy(vm_ref)
200
- wait_on_task(task)
201
- print " #{h.color "Done", :green} \n"
202
- exit 1
203
- end
204
-
205
203
 
206
- def run
204
+ def run
207
205
  server_name = @name_args[0]
208
206
  domainname = locate_config_value(:domain)
209
207
  if domainname.empty?
210
208
  fqdn = server_name
211
- else
209
+ else
212
210
  fqdn = "#{server_name}.#{domainname}"
213
211
  end
214
-
212
+
215
213
  # get the template vm we are going to build from
216
214
  template_ref = find_template( locate_config_value(:xapi_vm_template) )
217
215
 
218
- Chef::Log.debug "Cloning Guest from Template: #{h.color(template_ref, :bold, :cyan )}"
219
- vm_ref = xapi.VM.clone(template_ref, fqdn)
216
+ Chef::Log.debug "Cloning Guest from Template: #{h.color(template_ref, :bold, :cyan )}"
217
+ task = xapi.Async.VM.clone(template_ref, fqdn)
218
+ ui.msg "Waiting on Template Clone"
219
+ vm_ref = get_task_ref(task)
220
+
221
+ Chef::Log.debug "New VM ref: #{vm_ref}"
220
222
 
221
223
  # TODO: lift alot of this
222
224
  begin
223
- xapi.VM.set_name_description(vm_ref, "VM from knife-xapi as #{server_name}")
225
+ xapi.VM.set_name_description(vm_ref, "VM from knife-xapi as #{server_name} by #{ENV['USER']}")
224
226
 
225
227
  # configure the install repo
226
228
  repo = locate_config_value(:install_repo)
227
229
  xapi.VM.set_other_config(vm_ref, { "install-repository" => repo } )
228
-
229
230
 
230
231
  cpus = locate_config_value( :xapi_cpus ).to_s
231
232
 
@@ -234,84 +235,88 @@ class Chef
234
235
 
235
236
  memory_size = input_to_bytes( locate_config_value(:xapi_mem) ).to_s
236
237
  # static-min <= dynamic-min = dynamic-max = static-max
237
- xapi.VM.set_memory_limits(vm_ref, memory_size, memory_size, memory_size, memory_size)
238
+ xapi.VM.set_memory_limits(vm_ref, memory_size, memory_size, memory_size, memory_size)
238
239
 
239
- #
240
+ #
240
241
  # setup the Boot args
241
242
  #
242
- boot_args = locate_config_value(:kernel_params)
243
+ boot_args = locate_config_value(:kernel_params)
243
244
 
244
245
  # if no hostname param set hostname to given vm name
245
- boot_args << " hostname=#{server_name}" unless boot_args.match(/hostname=.+\s?/)
246
+ boot_args << " hostname=#{server_name}" unless boot_args.match(/hostname=.+\s?/)
246
247
  # if domainname is supplied we put that in there as well
247
- boot_args << " domainname=#{domainname}" unless boot_args.match(/domainname=.+\s?/)
248
+ boot_args << " dnsdomain=#{domainname}" unless boot_args.match(/dnsdomain=.+\s?/)
248
249
 
249
- xapi.VM.set_PV_args( vm_ref, boot_args )
250
+ xapi.VM.set_PV_args( vm_ref, boot_args )
250
251
 
251
252
  # TODO: validate that the vm gets a network here
252
253
  networks = @name_args[1..-1]
253
254
  # if the user has provided networks
254
- if networks.length >= 1
255
+ if networks.length >= 1
255
256
  clear_vm_vifs( xapi.VM.get_record( vm_ref ) )
256
- networks.each_with_index do |net, index|
257
+ networks.each_with_index do |net, index|
257
258
  add_vif_by_name(vm_ref, index, net)
258
259
  end
259
260
  end
260
261
 
261
262
  if locate_config_value(:xapi_sr)
262
- sr_ref = get_sr_by_name( locate_config_value(:xapi_sr) )
263
+ sr_ref = get_sr_by_name( locate_config_value(:xapi_sr) )
263
264
  else
264
265
  sr_ref = find_default_sr
265
266
  end
266
267
 
267
268
  if sr_ref.nil?
268
269
  ui.error "SR specified not found or can't be used Aborting"
269
- cleanup(vm_ref)
270
- end
271
- Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
272
-
273
- # Create the VDI
274
- vdi_ref = create_vdi("#{server_name}-root", sr_ref, locate_config_value(:xapi_disk_size) )
275
- # if vdi_ref is nill we need to bail/cleanup
276
- cleanup(vm_ref) unless vdi_ref
277
- ui.msg( "#{ h.color "OK", :green} ")
278
-
279
- # Attach the VDI to the VM
280
- vbd_ref = create_vbd(vm_ref, vdi_ref, 0)
281
- cleanup(vm_ref) unless vbd_ref
282
- ui.msg( "#{ h.color "OK", :green}" )
283
-
284
- ui.msg "Provisioning new Guest: #{h.color(fqdn, :bold, :cyan )}"
270
+ fail(vm_ref) if sr_ref.nil?
271
+ end
272
+ Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
273
+
274
+ # setup disks
275
+ if locate_config_value(:xapi_disk_size)
276
+ # when a template already has disks, we decide the position number based on it.
277
+ position = xapi.VM.get_VBDs(vm_ref).length
278
+
279
+ # Create the VDI
280
+ vdi_ref = create_vdi("#{server_name}-root", sr_ref, locate_config_value(:xapi_disk_size) )
281
+ fail(vm_ref) unless vdi_ref
282
+
283
+ # Attach the VDI to the VM
284
+ # if its position is 0 set it bootable
285
+ vbd_ref = create_vbd(vm_ref, vdi_ref, position, position == 0)
286
+ fail(vm_ref) unless vbd_ref
287
+ end
288
+
289
+
290
+ ui.msg "Provisioning new Guest: #{h.color(fqdn, :bold, :cyan )}"
285
291
  ui.msg "Boot Args: #{h.color boot_args,:bold, :cyan}"
286
292
  ui.msg "Install Repo: #{ h.color(repo,:bold, :cyan)}"
287
- ui.msg "Memory: #{ h.color( locate_config_value(:xapi_mem).to_s, :bold, :cyan)}"
293
+ ui.msg "Memory: #{ h.color( locate_config_value(:xapi_mem).to_s, :bold, :cyan)}"
288
294
  ui.msg "CPUs: #{ h.color( locate_config_value(:xapi_cpus).to_s, :bold, :cyan)}"
289
295
  ui.msg "Disk: #{ h.color( locate_config_value(:xapi_disk_size).to_s, :bold, :cyan)}"
290
296
  provisioned = xapi.VM.provision(vm_ref)
291
297
 
292
298
  ui.msg "Starting new Guest #{h.color( provisioned, :cyan)} "
293
299
  task = xapi.Async.VM.start(vm_ref, false, true)
294
- wait_on_task(task)
300
+ wait_on_task(task)
295
301
  ui.msg( "#{ h.color "OK!", :green}" )
296
302
 
297
- exit 0 unless locate_config_value(:run_list)
303
+ exit 0 unless locate_config_value(:run_list)
298
304
  rescue Exception => e
299
305
  ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}"
300
306
  # have to use join here to pass a string to highline
301
307
  puts "Nested backtrace:"
302
308
  ui.msg "#{h.color( e.backtrace.join("\n"), :yellow)}"
303
-
304
- cleanup(vm_ref)
309
+ fail(vm_ref)
305
310
  end
306
311
 
307
312
  if locate_config_value(:run_list).empty? or ! locate_config_value(:template_file)
308
- exit 0
313
+ exit 0
309
314
  end
310
315
 
311
316
  guest_addr = wait_for_guest_ip(vm_ref)
312
317
  if guest_addr.nil? or guest_addr.empty?
313
318
  ui.msg("ip seems wrong using host+domain name instead")
314
- guest_addr = "#{host_name}.#{domainname}"
319
+ guest_addr = "#{server_name}.#{domainname}"
315
320
  end
316
321
  ui.msg "Trying to connect to guest @ #{guest_addr} "
317
322
 
@@ -323,12 +328,11 @@ class Chef
323
328
  }
324
329
  end
325
330
  rescue Timeout::Error
326
- ui.msg "Timeout trying to login cleaning up"
327
- cleanup(vm_ref)
331
+ ui.msg "Timeout trying to login Wont bootstrap"
332
+ fail
328
333
  end
329
334
 
330
-
331
- begin
335
+ begin
332
336
  bootstrap = Chef::Knife::Bootstrap.new
333
337
  bootstrap.name_args = [ guest_addr ]
334
338
  bootstrap.config[:run_list] = locate_config_value(:run_list)
@@ -345,15 +349,14 @@ class Chef
345
349
  bootstrap.config[:environment] = config[:environment]
346
350
  bootstrap.config[:host_key_verify] = false
347
351
  bootstrap.config[:run_list] = locate_config_value(:run_list)
348
-
352
+
349
353
  bootstrap.run
350
- rescue Exception => e
354
+ rescue Exception => e
351
355
  ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}"
352
356
  puts "Nested backtrace:"
353
357
  ui.msg "#{h.color( e.backtrace.join("\n"), :yellow)}"
354
- cleanup(vm_ref)
358
+ fail
355
359
  end
356
-
357
360
  end
358
361
 
359
362
  end
@@ -26,6 +26,11 @@ class Chef
26
26
  class XapiGuestDelete < Knife
27
27
  include Chef::Knife::XapiBase
28
28
 
29
+ deps do
30
+ require 'chef/api_client'
31
+ require 'chef/json_compat'
32
+ end
33
+
29
34
  banner "knife xapi guest delete NAME_LABEL (options)"
30
35
 
31
36
  option :uuid,
@@ -33,9 +38,30 @@ class Chef
33
38
  :long => "--uuid",
34
39
  :description => "Treat the label as a UUID not a name label"
35
40
 
41
+ option :keep_client,
42
+ :short => "-C",
43
+ :long => "--keep-client",
44
+ :description => "Keep client info on the chef-server"
45
+
46
+ option :keep_node,
47
+ :short => "-N",
48
+ :long => "--keep-node",
49
+ :description => "Keep node info on the chef-server"
50
+
36
51
  def run
37
52
  server_name = @name_args[0]
38
53
 
54
+ if server_name.nil?
55
+ puts "Error: No VM Name specified..."
56
+ puts "Usage: " + banner
57
+ exit 1
58
+ end
59
+
60
+ name = server_name
61
+ if config[:uuid]
62
+ name = get_name_label(vm)
63
+ end
64
+
39
65
  vms = []
40
66
  if config[:uuid]
41
67
  vms << xapi.VM.get_by_uuid(server_name)
@@ -45,30 +71,48 @@ class Chef
45
71
  vms.flatten!
46
72
 
47
73
  if vms.empty?
48
- ui.msg "VM not found: #{h.color server_name, :red}"
74
+ puts "VM not found: #{h.color server_name, :red}"
49
75
  exit 1
50
76
  elsif vms.length > 1
51
- ui.msg "Multiple VM matches found use guest list if you are unsure"
77
+ puts "Multiple VM matches found use guest list if you are unsure"
52
78
  vm = user_select(vms)
53
79
  else
54
80
  vm = vms.first
55
81
  end
82
+
83
+ # Cleanup the VM
84
+ if vm == :all
85
+ vms.each {|vm| cleanup(vm) }
86
+ else
87
+ cleanup(vm)
88
+ end
56
89
 
57
- # shutdown and dest
58
- unless xapi.VM.get_power_state(vm) == "Halted"
59
- print "Shutting down Guest:"
60
- task = xapi.Async.VM.hard_shutdown(vm)
61
- wait_on_task(task)
62
- print " #{h.color "Done", :green} \n"
90
+ #############################################
91
+ # Delete client and node on the chef server #
92
+ #############################################
93
+ unless config[:keep_client]
94
+ client_list = Chef::ApiClient.list
95
+
96
+ if client_list.has_key?(name)
97
+ ui.msg "Removing client #{h.color name, :cyan} from chef"
98
+ delete_object(Chef::ApiClient, name)
99
+ else
100
+ puts "Client not found on the chef server.. Nothing to delete.."
101
+ end
63
102
  end
64
103
 
65
- print "Destroying Guest: #{h.color( server_name, :cyan)} "
66
- task = xapi.Async.VM.destroy(vm)
67
- wait_on_task(task)
68
- print " #{h.color "Done", :green}\n"
104
+ unless config[:keep_node]
105
+ env = Chef::Config[:environment]
106
+ node_list = env ? Chef::Node.list_by_environment(env) : Chef::Node.list
107
+ if node_list.has_key?(name)
108
+ ui.msg "Removing node #{h.color name, :cyan} from chef "
109
+ delete_object(Chef::Node, name)
110
+ else
111
+ puts "Node not found on the chef server.. Nothing to delete.."
112
+ end
113
+ end
69
114
  end
70
115
 
71
116
  end
72
117
  end
73
118
  end
74
-
@@ -0,0 +1,90 @@
1
+ #
2
+ # Author:: Jesse Nelson (<spheromak@gmail.com>)
3
+ #
4
+ # Copyright:: Copyright (c) 2012 Jesse Nelson
5
+ #
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+
22
+ require 'chef/knife/xapi_base'
23
+
24
+ class Chef
25
+ class Knife
26
+ class XapiVdiCreate < Knife
27
+ require 'timeout'
28
+ include Chef::Knife::XapiBase
29
+
30
+ banner "knife xapi vdi create NAME (options)"
31
+
32
+ option :xapi_sr,
33
+ :short => "-S Storage repo to provision VM from",
34
+ :long => "--xapi-sr",
35
+ :proc => Proc.new { |key| Chef::Config[:knife][:xapi_sr] = key },
36
+ :description => "The Xen SR to use, If blank will use pool/hypervisor default"
37
+
38
+ option :xapi_disk_size,
39
+ :short => "-D Size of disk. 1g 512m etc",
40
+ :long => "--xapi-disk-size",
41
+ :description => "The size of the root disk, use 'm' 'g' 't' if no unit specified assumes g",
42
+ :proc => Proc.new { |key| Chef::Config[:knife][:xapi_disk_size] = key.to_s }
43
+
44
+ def run
45
+ disk_name = @name_args[0]
46
+ if disk_name.nil?
47
+ puts "Error: No Disk Name specified..."
48
+ puts "Usage: " + banner
49
+ exit 1
50
+ end
51
+
52
+ begin
53
+ if locate_config_value(:xapi_sr)
54
+ sr_ref = get_sr_by_name( locate_config_value(:xapi_sr) )
55
+ else
56
+ sr_ref = find_default_sr
57
+ end
58
+
59
+ if sr_ref.nil?
60
+ ui.error "SR specified not found or can't be used Aborting"
61
+ end
62
+ Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
63
+
64
+ size = locate_config_value(:xapi_disk_size)
65
+
66
+ vdi_record = {
67
+ "name_label" => disk_name,
68
+ "name_description" => "#{disk_name} created by #{ENV['USER']}",
69
+ "SR" => sr_ref,
70
+ "virtual_size" => input_to_bytes(size).to_s,
71
+ "type" => "system",
72
+ "sharable" => false,
73
+ "read_only" => false,
74
+ "other_config" => {},
75
+ }
76
+
77
+ # Async create the VDI
78
+ task = xapi.Async.VDI.create(vdi_record)
79
+ ui.msg "waiting for VDI Create.."
80
+ vdi_ref = get_task_ref(task)
81
+
82
+ ui.msg "Disk Name: #{ h.color( disk_name, :bold, :cyan)}"
83
+ ui.msg "Disk Size: #{ h.color( size.to_s, :bold, :cyan)}"
84
+
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
@@ -0,0 +1,131 @@
1
+ #
2
+ # Author:: Jesse Nelson (<spheromak@gmail.com>)
3
+ #
4
+ # Copyright:: Copyright (c) 2012 Jesse Nelson
5
+ #
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+
22
+ require 'chef/knife/xapi_base'
23
+
24
+ class Chef
25
+ class Knife
26
+ class XapiVdiDelete < Knife
27
+ include Chef::Knife::XapiBase
28
+
29
+ banner "knife xapi vdi delete NAME_LABEL (options)"
30
+
31
+ option :uuid,
32
+ :short => "-U",
33
+ :long => "--uuid",
34
+ :description => "Treat the label as a UUID not a name label"
35
+
36
+ option :cleanup,
37
+ :short => "-C",
38
+ :long => "--cleanup",
39
+ :description => "Clean up all orphaned volumes."
40
+
41
+ option :interactive,
42
+ :short => "-I",
43
+ :long => "--interactive",
44
+ :description => "Interactive clean-up of orphaned volumes"
45
+
46
+ def run
47
+ if config[:interactive]
48
+ # Get all VDIs known to the system
49
+ vdis = get_all_vdis()
50
+ first = true
51
+
52
+ for vdi_ in vdis do
53
+ vbds = get_vbds_from_vdi(vdi_)
54
+ if vbds.empty? and xapi.VDI.get_type(vdi_).match('system')
55
+ if first
56
+ puts "================================================"
57
+ first = false
58
+ end
59
+
60
+ print_vdi_info(vdi_)
61
+ ret = yes_no_prompt(" No VM attached! Do you want to destroy this volume? (Type \'yes\' or \'no\'): ")
62
+
63
+ if ret
64
+ destroy_vdi(vdi_)
65
+ end
66
+ puts "================================================"
67
+ end
68
+ end
69
+
70
+ elsif config[:cleanup]
71
+ orphaned_vdis = []
72
+ vdis = get_all_vdis()
73
+
74
+ for vdi_ in vdis do
75
+ vbds = get_vbds_from_vdi(vdi_)
76
+ if vbds.empty? and xapi.VDI.get_type(vdi_).match('system')
77
+ orphaned_vdis << vdi_
78
+ end
79
+ end
80
+
81
+ for item in orphaned_vdis do
82
+ print_vdi_info(item)
83
+ end
84
+
85
+ unless orphaned_vdis.empty?
86
+ ret = yes_no_prompt("Do you want to destroy all these volumes? (Type \'yes\' or \'no\'): ")
87
+ if ret
88
+ for item in orphaned_vdis do
89
+ destroy_vdi(item)
90
+ end
91
+ end
92
+ end
93
+
94
+ else
95
+ vdi_name = @name_args[0]
96
+ if vdi_name.nil?
97
+ puts "Error: No VDI Name specified..."
98
+ puts "Usage: " + banner
99
+ exit 1
100
+ end
101
+
102
+ vdis = []
103
+ if config[:uuid]
104
+ vdis << get_vdi_by_uuid(vdi_name)
105
+ else
106
+ vdis << get_vdi_by_name_label(vdi_name)
107
+ end
108
+ vdis.flatten!
109
+
110
+ if vdis.empty?
111
+ puts "VDI not found: #{h.color vdi_name, :red}"
112
+ exit 1
113
+ elsif vdis.length > 1
114
+ puts "Multiple VDI matches found. Use vdi list if you are unsure"
115
+ vdi = user_select(vdis)
116
+ else
117
+ vdi = vdis.first
118
+ end
119
+
120
+ vbds = get_vbds_from_vdi(vdi)
121
+
122
+ if vbds.empty?
123
+ destroy_vdi(vdi)
124
+ else
125
+ puts "ERROR! The VDI is still in use."
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,60 @@
1
+ #
2
+ # Author:: Jesse Nelson (<spheromak@gmail.com>)
3
+ #
4
+ # Copyright:: Copyright (c) 2012 Jesse Nelson
5
+ #
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+
22
+ require 'chef/knife/xapi_base'
23
+
24
+ class Chef
25
+ class Knife
26
+ class XapiVdiList < Knife
27
+ include Chef::Knife::XapiBase
28
+
29
+ banner "knife xapi vdi list"
30
+
31
+ def run
32
+ # Get all VDIs known to the system
33
+ vdis = xapi.VDI.get_all()
34
+
35
+ puts "================================================"
36
+ for vdi_ in vdis do
37
+ puts "#{h.color "VDI name: " + xapi.VDI.get_name_label(vdi_), :green}"
38
+ puts " -UUID: " + xapi.VDI.get_uuid(vdi_)
39
+ puts " -Description: " + xapi.VDI.get_name_description(vdi_)
40
+ puts " -Type: " + xapi.VDI.get_type(vdi_)
41
+
42
+ vbds = xapi.VDI.get_VBDs(vdi_)
43
+ for vbd in vbds do
44
+ vm = xapi.VBD.get_VM(vbd)
45
+ state = xapi.VM.get_power_state(vm)
46
+ puts " -VM name: " + xapi.VM.get_name_label(vm)
47
+ puts " -VM state: " + state + "\n"
48
+ end
49
+
50
+ if vbds.empty? and xapi.VDI.get_type(vdi_).match('system')
51
+ puts " No VM attached!"
52
+ #puts " No VM attached! Use vdi delete --cleanup to delete this volume."
53
+ end
54
+ puts "================================================"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -1,3 +1,3 @@
1
1
  module KnifeXenserver
2
- VERSION = "0.3.6"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-xapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.1
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: 2012-06-14 00:00:00.000000000 Z
12
+ date: 2012-07-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
@@ -87,6 +87,9 @@ files:
87
87
  - lib/chef/knife/xapi_guest_create.rb
88
88
  - lib/chef/knife/xapi_guest_delete.rb
89
89
  - lib/chef/knife/xapi_guest_list.rb
90
+ - lib/chef/knife/xapi_vdi_create.rb
91
+ - lib/chef/knife/xapi_vdi_delete.rb
92
+ - lib/chef/knife/xapi_vdi_list.rb
90
93
  - lib/knife-xapi/version.rb
91
94
  homepage: https://github.com/spheromak/knife-xapi
92
95
  licenses: []
@@ -108,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
111
  version: '0'
109
112
  requirements: []
110
113
  rubyforge_project:
111
- rubygems_version: 1.8.24
114
+ rubygems_version: 1.8.23
112
115
  signing_key:
113
116
  specification_version: 3
114
117
  summary: Xen API Support for Chef's Knife Command