knife-xapi 0.3.6 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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