chef-provisioning-opennebula 0.3.4 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,7 +36,7 @@ class Chef
36
36
 
37
37
  def exists?(filter)
38
38
  new_driver = driver
39
- @current_user = new_driver.one.get_resource('user', filter)
39
+ @current_user = new_driver.one.get_resource(:user, filter)
40
40
  Chef::Log.debug("user '#{filter}' exists: #{!@current_user.nil?}")
41
41
  !@current_user.nil?
42
42
  end
@@ -36,7 +36,7 @@ class Chef
36
36
 
37
37
  def exists?(filter)
38
38
  new_driver = driver
39
- @current_vnet = new_driver.one.get_resource('vnet', filter)
39
+ @current_vnet = new_driver.one.get_resource(:virtualnetwork, filter)
40
40
  Chef::Log.debug("VNET '#{filter}' exists: #{!@current_vnet.nil?}")
41
41
  !@current_vnet.nil?
42
42
  end
@@ -53,6 +53,7 @@ class Chef
53
53
  vnet = new_driver.one.allocate_vnet(template_str, @new_resource.cluster_id)
54
54
  Chef::Log.debug(template_str)
55
55
  fail "failed to create vnet '#{@new_resource.name}': #{vnet.message}" if OpenNebula.is_error?(vnet)
56
+ new_driver.one.chmod_resource(vnet, new_resource.mode)
56
57
  @new_resource.updated_by_last_action(true)
57
58
  end
58
59
  end
@@ -37,7 +37,7 @@ class Chef
37
37
  def exists?
38
38
  new_driver = driver
39
39
  filter = { @new_resource.vnet.is_a?(Integer) ? :id : :name => @new_resource.vnet }
40
- @current_vnet = new_driver.one.get_resource('vnet', filter)
40
+ @current_vnet = new_driver.one.get_resource(:virtualnetwork, filter)
41
41
  fail "vnet '#{@new_resource.vnet}' does not exist" if @current_vnet.nil?
42
42
  @current_vnet.info!
43
43
  hash = @current_vnet.to_hash
@@ -49,22 +49,7 @@ class Chef
49
49
  ar_pool = get_ar_pool(ar_pool, @new_resource.ar_id.to_s)
50
50
  fail "ar_id not found '#{@new_resource.ar_id}'" if ar_pool.nil?
51
51
  end
52
- available = lease_available?(ar_pool, lookup)
53
- fail "'#{name}' is already allocated to a VM (ID: #{vm})" unless available
54
- available
55
- # ar_pool.each do |a|
56
- # if a['AR']['LEASES']['LEASE']
57
- # [a['AR']['LEASES']['LEASE']].flatten.each do |l|
58
- # if l[lookup] && l[lookup] == @new_resource.name
59
- # exists = true
60
- # vm = l['VM'].to_i
61
- # break
62
- # end
63
- # end
64
- # end
65
- # end
66
- # fail "'#{name}' is already allocated to a VM (ID: #{vm})" if exists && vm > -1
67
- # (exists && vm == -1)
52
+ lease_available?(ar_pool, lookup)
68
53
  end
69
54
 
70
55
  action :hold do
@@ -69,8 +69,12 @@ class Chef
69
69
  begin
70
70
  content_hash = JSON.parse(File.read(file), :symbolize_names => true)
71
71
  content_hash.each { |k, v| json[k.to_s] = v }
72
- rescue Exception => e
73
- Chef::Log.warn("Failed to read and parse config file #{file}: #{e.message}")
72
+ rescue StandardError => e_file
73
+ Chef::Log.warn("Failed to read config file #{file}: #{e_file.message}")
74
+ rescue JSON::ParserError => e_json
75
+ Chef::Log.warn("Failed to parse config file #{file}: #{e_json.message}")
76
+ rescue
77
+ Chef::Log.warn("Failed to read or parse config file #{file}: #{$!}")
74
78
  end
75
79
  @credentials.merge!(json)
76
80
  end
@@ -21,6 +21,7 @@ require 'chef/provisioning/transport/ssh'
21
21
  require 'chef/provisioning/opennebula_driver/version'
22
22
  require 'chef/provisioning/opennebula_driver/one_lib'
23
23
  require 'chef/provisioning/opennebula_driver/credentials'
24
+ require 'net/ssh/proxy/command'
24
25
 
25
26
  class Chef
26
27
  module Provisioning
@@ -84,14 +85,7 @@ class Chef
84
85
  #
85
86
  def initialize(driver_url, config)
86
87
  super
87
- scan = driver_url.match(%r/(opennebula):(https?:\/\/[^:\/]+ (?::[0-9]{2,5})? (?:\/[^:\s]+) ) :?([^:\s]+)?/x)
88
- endpoint = scan[2]
89
- profile_name = scan[3]
90
- fail "OpenNebula endpoint must be specified in 'driver_url': #{driver_url}" if endpoint.nil?
91
-
92
- profile = profile_name ? one_credentials[profile_name] : one_credentials.default
93
- Chef::Log.warn("':credentials' and ':secret_file' will be deprecated in next version in favour of 'opennebula:<endpoint>:<profile_name>'") if profile_name.nil?
94
- @one = OneLib.new(profile[:credentials], endpoint, profile[:options] || {})
88
+ @one = OneLib.new(:driver_url => driver_url)
95
89
  end
96
90
 
97
91
  def self.from_url(driver_url, config)
@@ -127,27 +121,34 @@ class Chef
127
121
  # saved back after allocate_machine completes.
128
122
  #
129
123
  def allocate_machine(action_handler, machine_spec, machine_options)
130
- fqdn = begin
131
- machine_options.bootstrap_options[:enforce_chef_fqdn]
132
- rescue NoMethodError
133
- false
134
- end
135
- fail "Machine 'name' must be a FQDN" if fqdn && machine_spec.name.scan(/([a-z0-9-]+\.)/i).length == 0
136
124
  instance = instance_for(machine_spec)
125
+ return machine_spec unless instance.nil?
137
126
 
138
- if instance.nil?
139
- fail "'bootstrap_options' must be specified" unless machine_options.bootstrap_options
140
- check_unique_names(machine_options.bootstrap_options, machine_spec)
141
- action_handler.perform_action "created vm '#{machine_spec.name}'" do
142
- Chef::Log.debug(machine_options)
143
- tpl = @one.get_template(machine_spec.name, machine_options.bootstrap_options)
144
- vm = @one.allocate_vm(tpl)
145
- populate_node_object(machine_spec, machine_options, vm)
127
+ unless machine_options.bootstrap_options
128
+ fail "'bootstrap_options' must be specified"
129
+ end
130
+ check_unique_names(machine_options, machine_spec)
131
+ action_handler.perform_action "created vm '#{machine_spec.name}'" do
132
+ Chef::Log.debug(machine_options)
133
+ tpl = @one.get_template(machine_spec.name,
134
+ machine_options.bootstrap_options)
135
+ vm = @one.allocate_vm(tpl)
136
+ populate_node_object(machine_spec, machine_options, vm)
137
+ @one.chmod_resource(vm, machine_options.bootstrap_options[:mode])
138
+
139
+ # This option allows to manipulate how the machine shows up
140
+ # in the OpenNebula UI and CLI tools. We either set the VM
141
+ # name to the short hostname of the machine, rename it
142
+ # to the String passed to us, or leave it alone.
143
+ if machine_options[:vm_name] == :short
144
+ @one.rename_vm(vm, machine_spec.name.split('.').first)
145
+ elsif machine_options[:vm_name].is_a?(String)
146
+ @one.rename_vm(vm, machine_options[:vm_name])
147
+ # else use machine_spec.name for name in OpenNebula
146
148
  end
147
- Chef::Log.debug(machine_spec.reference)
148
- else
149
- Chef::Log.info("vm '#{machine_spec.name}' already exists - nothing to do")
150
149
  end
150
+ Chef::Log.debug(machine_spec.reference)
151
+
151
152
  machine_spec
152
153
  end
153
154
 
@@ -178,8 +179,9 @@ class Chef
178
179
  deployed = @one.wait_for_vm(instance.id)
179
180
  machine_spec.reference['name'] = deployed.name
180
181
  machine_spec.reference['state'] = deployed.state_str
181
- nic_hash = deployed.to_hash
182
- ip = [nic_hash['VM']['TEMPLATE']['NIC']].flatten[0]['IP']
182
+ if deployed.to_hash['VM']['TEMPLATE']['NIC']
183
+ ip = [deployed.to_hash['VM']['TEMPLATE']['NIC']].flatten.first['IP']
184
+ end
183
185
  fail "Could not get IP from VM '#{deployed.name}'" if ip.nil? || ip.to_s.empty?
184
186
  machine_spec.reference['ip'] = ip
185
187
  machine = machine_for(machine_spec, machine_options)
@@ -241,8 +243,8 @@ class Chef
241
243
  instance = instance_for(machine_spec)
242
244
  if !instance.nil?
243
245
  action_handler.perform_action "powered off machine #{machine_spec.name} (#{machine_spec.reference['instance_id']})" do
244
- if machine_spec.reference[:is_shutdown] || machine_options.bootstrap_options[:is_shutdown]
245
- hard = machine_spec.reference[:shutdown_hard] || machine_options.bootstrap_options[:shutdown_hard] || false
246
+ if machine_spec.reference[:is_shutdown] || (machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:is_shutdown])
247
+ hard = machine_spec.reference[:shutdown_hard] || machine_options[:bootstrap_options][:shutdown_hard] || false
246
248
  instance.shutdown(hard)
247
249
  else
248
250
  instance.stop
@@ -264,19 +266,19 @@ class Chef
264
266
  def allocate_image(action_handler, image_spec, image_options, machine_spec)
265
267
  if image_spec.reference
266
268
  # check if image already exists
267
- image = @one.get_resource('img', :id => image_spec.reference['image_id'].to_i)
269
+ image = @one.get_resource(:image, :id => image_spec.reference['image_id'].to_i)
268
270
  action_handler.report_progress "image #{image_spec.name} (ID: #{image_spec.reference['image_id']}) already exists" unless image.nil?
269
271
  else
270
272
  action_handler.perform_action "create image #{image_spec.name} from machine ID #{machine_spec.reference['instance_id']} with options #{image_options.inspect}" do
271
- vm = @one.get_resource('vm', :id => machine_spec.reference['instance_id'])
273
+ vm = @one.get_resource(:virtualmachine, :id => machine_spec.reference['instance_id'])
272
274
  fail "allocate_image: VM does not exist" if vm.nil?
273
275
  # set default disk ID
274
276
  disk_id = 1
275
277
  if image_options.disk_id
276
278
  disk_id = image_options.disk_id.is_a?(Integer) ? image_options.disk_id : @one.get_disk_id(vm, new_resource.disk_id)
277
279
  end
280
+ new_img = @one.version_ge_4_14 ? vm.disk_saveas(disk_id, image_spec.name) : vm.disk_snapshot(disk_id, image_spec.name, "", true)
278
281
 
279
- new_img = vm.disk_snapshot(disk_id, image_spec.name, "", true)
280
282
  fail "Failed to create snapshot '#{new_resource.name}': #{new_img.message}" if OpenNebula.is_error?(new_img)
281
283
  populate_img_object(image_spec, new_image)
282
284
  end
@@ -292,7 +294,7 @@ class Chef
292
294
  # @param [Hash] image_options
293
295
  # A set of options representing the desired state of the machine
294
296
  def ready_image(action_handler, image_spec, _image_options)
295
- img = @one.get_resource('img', :id => image_spec.reference['image_id'].to_i)
297
+ img = @one.get_resource(:image, :id => image_spec.reference['image_id'].to_i)
296
298
  fail "Image #{image_spec.name} (#{image_spec.reference['image_id']}) does not exist" if img.nil?
297
299
  action_handler.perform_action "image #{image_spec.name} is ready" do
298
300
  deployed = @one.wait_for_img(img.name, img.id)
@@ -310,7 +312,7 @@ class Chef
310
312
  # @param [Hash] image_options
311
313
  # A set of options representing the desired state of the machine
312
314
  def destroy_image(action_handler, image_spec, _image_options)
313
- img = @one.get_resource('img', :id => image_spec.location['image_id'].to_i)
315
+ img = @one.get_resource(:image, :id => image_spec.location['image_id'].to_i)
314
316
  if img.nil?
315
317
  action_handler.report_progress "image #{image_spec.name} (#{image_spec.location['image_id']}) does not exist - nothing to do"
316
318
  else
@@ -441,8 +443,18 @@ class Chef
441
443
  end
442
444
  end
443
445
 
444
- def check_unique_names(bootstrap_options, machine_spec)
445
- fail "VM with name '#{machine_spec.name}' already exists" unless @one.get_resource('vm', :name => machine_spec.name).nil? if bootstrap_options[:unique_names]
446
+ def check_unique_names(machine_options, machine_spec)
447
+ return unless machine_options.bootstrap_options[:unique_names]
448
+ hostname = if machine_options[:vm_name] == :short
449
+ machine_spec.name.split('.').first
450
+ elsif machine_options[:vm_name].is_a?(String)
451
+ machine_options[:vm_name]
452
+ else
453
+ machine_spec.name
454
+ end
455
+
456
+ return if @one.get_resource(:virtualmachine, :name => hostname).nil?
457
+ fail "VM with name '#{hostname}' already exists"
446
458
  end
447
459
 
448
460
  def populate_node_object(machine_spec, machine_options, vm)
@@ -482,11 +494,11 @@ class Chef
482
494
  if machine_spec.reference
483
495
  fail "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not supported!" \
484
496
  " Use machine :destroy and then :create the machine on the new driver." if machine_spec.driver_url != driver_url
485
- instance = @one.get_resource('vm', :id => machine_spec.reference['instance_id'].to_i)
497
+ instance = @one.get_resource(:virtualmachine, :id => machine_spec.reference['instance_id'].to_i)
486
498
  elsif machine_spec.location
487
499
  fail "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not supported!" \
488
500
  " Use machine :destroy and then :create the machine on the new driver." if machine_spec.driver_url != driver_url
489
- instance = @one.get_resource('vm', :id => machine_spec.location['server_id'].to_i)
501
+ instance = @one.get_resource(:virtualmachine, :id => machine_spec.location['server_id'].to_i)
490
502
  unless instance.nil?
491
503
  # Convert from previous driver
492
504
  machine_spec.reference = {
@@ -506,7 +518,9 @@ class Chef
506
518
  instance = instance_for(machine_spec)
507
519
  fail "#{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist!" if instance.nil?
508
520
  # TODO: Support Windoze VMs (see chef-provisioning-vagrant)
509
- Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
521
+ Chef::Provisioning::Machine::UnixMachine.new(machine_spec,
522
+ transport_for(machine_spec, machine_options, instance),
523
+ convergence_strategy_for(machine_spec, machine_options))
510
524
  end
511
525
 
512
526
  def get_ssh_user(machine_spec, machine_options)
@@ -516,33 +530,37 @@ class Chef
516
530
  end
517
531
 
518
532
  def transport_for(machine_spec, machine_options, _instance)
519
- # TODO: Store ssh_options in machine_spec.reference ???
520
533
  ssh_options = {
521
534
  :keys_only => false,
522
535
  :forward_agent => true,
523
536
  :use_agent => true,
524
- :user_known_hosts_file => '/dev/null'
537
+ :user_known_hosts_file => '/dev/null',
538
+ :timeout => 10
525
539
  }.merge(machine_options[:ssh_options] || {})
540
+ ssh_options[:proxy] = Net::SSH::Proxy::Command.new(ssh_options[:proxy]) if ssh_options.key?(:proxy)
541
+
542
+ connection_timeout = machine_options[:connection_timeout] || 300
526
543
  username = get_ssh_user(machine_spec, machine_options)
527
- conf = machine_options[:ssh_config] || config
544
+
528
545
  options = {}
529
546
  if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != 'root')
530
547
  options[:prefix] = 'sudo '
531
548
  end
549
+ options[:ssh_pty_enable] = machine_options[:ssh_pty_enable] || true
550
+ # User provided ssh_gateway takes precedence over machine_spec value
551
+ options[:ssh_gateway] = machine_options[:ssh_gateway] || machine_spec.reference['ssh_gateway']
532
552
 
533
- # Enable pty by default
534
- options[:ssh_pty_enable] = true
535
- options[:ssh_gateway] = machine_spec.reference['ssh_gateway'] if machine_spec.reference.key?('ssh_gateway')
536
-
537
- transport = Chef::Provisioning::Transport::SSH.new(machine_spec.reference['ip'], username, ssh_options, options, conf)
553
+ transport = Chef::Provisioning::Transport::SSH.new(machine_spec.reference['ip'], username, ssh_options, options, config)
538
554
 
539
555
  # wait up to 5 min to establish SSH connection
540
- 100.times do
556
+ connect_sleep = 3
557
+ start = Time.now
558
+ loop do
541
559
  break if transport.available?
542
- sleep 3
543
- Chef::Log.debug("Waiting for SSH server ...")
560
+ fail "Failed to establish SSH connection to '#{machine_spec.name}'" if Time.now > start + connection_timeout.to_i
561
+ Chef::Log.info("Waiting for SSH server ...")
562
+ sleep connect_sleep
544
563
  end
545
- fail "Failed to establish SSH connection to '#{machine_spec.name}'" unless transport.available?
546
564
  transport
547
565
  end
548
566
 
@@ -33,11 +33,11 @@ end
33
33
  #
34
34
  class Chef
35
35
  #
36
- # Module extention.
36
+ # Module extension.
37
37
  #
38
38
  module Provisioning
39
39
  #
40
- # Module extention.
40
+ # Module extension.
41
41
  #
42
42
  module OpenNebulaDriver
43
43
  #
@@ -50,17 +50,63 @@ class Chef
50
50
  # Implementation.
51
51
  #
52
52
  class OneLib
53
- attr_accessor :client
54
-
55
- def initialize(credentials, endpoint, options = {})
53
+ attr_accessor :client, :version_ge_4_14
54
+
55
+ def initialize(args)
56
+ credentials = args[:credentials]
57
+ endpoint = args[:endpoint]
58
+ options = args[:options] || {}
59
+ if args[:driver_url]
60
+ scan = args[:driver_url].match(%r/(opennebula):(https?:\/\/[^:\/]+ (?::[0-9]{2,5})? (?:\/[^:\s]+) ) :?([^:\s]+)?/x)
61
+ endpoint = scan[2]
62
+ profile = scan[3]
63
+ fail "'driver_url' option has invalid format: #{args[:driver_url]}" if endpoint.nil? || profile.nil?
64
+ one_profile = Chef::Provisioning::OpenNebulaDriver::Credentials.new[profile]
65
+ credentials = one_profile[:credentials]
66
+ options = one_profile[:options] || {}
67
+ end
56
68
  @client = OpenNebula::Client.new(credentials, endpoint, options)
57
69
  rc = @client.get_version
58
70
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
71
+
72
+ server_version = rc.split('.').map(&:to_i)
73
+ gem_version = Gem.loaded_specs["opennebula"].version.to_s.split('.').map(&:to_i)
74
+ @version_ge_4_14 = gem_version[0] > 4 || (gem_version[0] == 4 && gem_version[1] >= 14)
75
+
76
+ version_mismatch_warning(server_version, gem_version) if server_version != gem_version
59
77
  end
60
78
 
61
- # TODO: add filtering to pool retrieval (type, start, end, user)
79
+ # This function provides a more readable way to return a
80
+ # OpenNebula::*Pool back to a caller. The caller simply needs
81
+ # to pass the pool type in symbol form to us, and we send back
82
+ # the pool. The idea is that passing :template will give us
83
+ # back OpenNebula::TemplatePool, etc. for consistency with
84
+ # the OpenNebula API calls. Note that we are still supporting
85
+ # the old API, while logging a warning that the old format
86
+ # is deprecated. Users should expect the old format to disappear
87
+ # in a future release.
62
88
  def get_pool(type)
63
89
  fail "pool type must be specified" if type.nil?
90
+ key = type.capitalize
91
+ key = :SecurityGroup if key == :Securitygroup
92
+ key = :VirtualMachine if key == :Virtualmachine
93
+ key = :VirtualNetwork if key == :Virtualnetwork
94
+ if key == :Documentpooljson # Doesn't match the template below
95
+ return OpenNebula::DocumentPoolJSON.new(@client)
96
+ end
97
+
98
+ pool_class = Object.const_get("OpenNebula::#{key}Pool")
99
+ pool_class.new(@client)
100
+ rescue NameError
101
+ _get_pool(type.to_s) # This will raise an exception if invalid.
102
+ end
103
+
104
+ # TODO: add filtering to pool retrieval (type, start, end, user)
105
+ def _get_pool(type)
106
+ Chef::Log.warn("Use of deprecated pool type '#{type}' detected. " \
107
+ "Switch to symbol form; i.e. '[:#{type}]' to use the " \
108
+ "'OpenNebula::#{type}Pool'.")
109
+
64
110
  case type
65
111
  when 'acl'
66
112
  OpenNebula::AclPool.new(@client)
@@ -76,11 +122,11 @@ class Chef
76
122
  OpenNebula::GroupPool.new(@client)
77
123
  when 'host'
78
124
  OpenNebula::HostPool.new(@client)
79
- when 'img'
125
+ when 'image', 'img'
80
126
  OpenNebula::ImagePool.new(@client, -1)
81
127
  when 'secgroup'
82
128
  OpenNebula::SecurityGroupPool.new(@client)
83
- when 'tpl'
129
+ when 'tpl', 'vmtemplate', 'template'
84
130
  OpenNebula::TemplatePool.new(@client, -1)
85
131
  when 'user'
86
132
  OpenNebula::UserPool.new(@client)
@@ -91,30 +137,40 @@ class Chef
91
137
  when 'vnet'
92
138
  OpenNebula::VirtualNetworkPool.new(@client)
93
139
  else
94
- fail "Invalid pool type specified."
140
+ fail "Invalid pool type '#{type}' specified."
95
141
  end
96
142
  end
143
+ private :_get_pool
97
144
 
145
+ # TODO: Always return an array
98
146
  def get_resource(resource_type, filter = {})
147
+ fail "resource_type must be specified" if resource_type.nil?
148
+
149
+ # Ensure the hash key is correct when searching
150
+ hash_key = resource_type.to_s.upcase
151
+ hash_key = 'VMTEMPLATE' if hash_key == 'TPL' || hash_key == 'TEMPLATE'
152
+
99
153
  if filter.empty?
100
154
  Chef::Log.warn("get_resource: 'name' or 'id' must be provided")
101
155
  return nil
102
156
  end
103
157
  pool = get_pool(resource_type)
104
158
 
105
- if resource_type != 'user' && filter[:id] && !filter[:id].nil?
159
+ if resource_type.to_s != 'user' && filter[:id] && !filter[:id].nil?
106
160
  pool.info!(-2, filter[:id].to_i, filter[:id].to_i)
107
161
  return pool.first
108
162
  end
109
163
 
110
- if resource_type == 'user'
164
+ if resource_type.to_s == 'user'
111
165
  pool.info
112
166
  else
113
- pool.info!(-2, -1, -1) if resource_type != 'user'
167
+ pool.info!(-2, -1, -1)
114
168
  end
115
169
  resources = []
116
170
  pool.each do |res|
117
- resources << res if res.name == filter[:name]
171
+ next unless res.name == filter[:name]
172
+ next if filter[:uname] && res.to_hash[hash_key]['UNAME'] != filter[:uname]
173
+ resources << res
118
174
  end
119
175
  return nil if resources.size == 0
120
176
  return resources[0] if resources.size == 1
@@ -133,7 +189,7 @@ class Chef
133
189
 
134
190
  def wait_for_vm(id, end_state = nil)
135
191
  end_state ||= 'RUNNING'
136
- vm = get_resource('vm', :id => id)
192
+ vm = get_resource(:virtualmachine, :id => id)
137
193
  fail "Did not find VM with ID: #{id}" unless vm
138
194
  while vm.lcm_state_str != end_state.upcase
139
195
  vm.info
@@ -144,53 +200,63 @@ class Chef
144
200
  vm
145
201
  end
146
202
 
147
- def upload_img(name, ds_id, path, driver, description, type, prefix, persistent, pub, target, disk_type, source, size, fstype)
203
+ def rename_vm(res, name)
204
+ rc = res.rename(name)
205
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
206
+ end
207
+
208
+ def upload_img(img_config)
148
209
  template = <<-EOTPL
149
- NAME = #{name}
150
- PATH = \"#{path}\"
151
- DRIVER = #{driver}
152
- DESCRIPTION = \"#{description}\"
153
- EOTPL
154
-
155
- template << "TYPE = #{type}\n" unless type.nil?
156
- template << "PERSISTENT = YES\n" if !persistent.nil? && persistent
157
- template << "DEV_PREFIX = #{prefix}\n" unless prefix.nil?
158
- template << "PUBLIC = YES\n" if !pub.nil? && pub
159
- template << "TARGET = #{target}\n" unless target.nil?
160
- template << "DISK_TYPE = #{disk_type}\n" unless disk_type.nil?
161
- template << "SOURCE = #{source}\n" unless source.nil?
162
- template << "SIZE = #{size}" unless size.nil?
163
- template << "FSTYPE = #{fstype}\n" unless fstype.nil?
210
+ NAME = #{img_config[:name]}
211
+ PATH = \"#{img_config[:path]}\"
212
+ DRIVER = #{img_config[:driver]}
213
+ DESCRIPTION = \"#{img_config[:description]}\"
214
+ EOTPL
215
+
216
+ template << "TYPE = #{img_config[:type]}\n" unless img_config[:type].nil?
217
+ template << "DEV_PREFIX = #{img_config[:prefix]}\n" unless img_config[:prefix].nil?
218
+ template << "TARGET = #{img_config[:target]}\n" unless img_config[:target].nil?
219
+ template << "DISK_STYPE = #{img_config[:disk_type]}\n" unless img_config[:disk_type].nil?
220
+ template << "SOURCE = #{img_config[:source]}\n" unless img_config[:source].nil?
221
+ template << "SIZE = #{img_config[:size]}\n" unless img_config[:size].nil?
222
+ template << "FSTYPE = #{img_config[:fs_type]}\n" unless img_config[:fs_type].nil?
223
+ template << "PUBLIC = #{img_config[:public] ? 'YES' : 'NO'}\n" unless img_config[:public].nil?
224
+ template << "PERSISTENT = #{img_config[:persistent] ? 'YES' : 'NO'}\n" unless img_config[:persistent].nil?
164
225
 
165
226
  Chef::Log.debug("\n#{template}")
166
-
167
227
  image = OpenNebula::Image.new(OpenNebula::Image.build_xml, @client)
168
228
  raise OpenNebulaException, image.message if OpenNebula.is_error?(image)
169
- rc = image.allocate(template, ds_id)
229
+ rc = image.allocate(template, img_config[:datastore_id].to_i)
230
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
231
+ Chef::Log.debug("Waiting for image '#{img_config[:name]}' (#{image.id}) to be ready")
232
+ wait_for_img(img_config[:name], image.id)
233
+ chmod_resource(image, img_config[:mode])
234
+ end
235
+
236
+ def chmod_resource(res = nil, octet = nil)
237
+ rc = res.chmod_octet(octet) unless res.nil? || octet.nil?
170
238
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
171
- Chef::Log.debug("Waiting for image '#{name}' (#{image.id}) to be ready")
172
- wait_for_img(name, image.id)
173
239
  end
174
240
 
175
- def allocate_img(name, size, ds_id, type, fstype, driver, prefix, persistent)
241
+ def allocate_img(img_config)
176
242
  template = <<-EOT
177
- NAME = #{name}
178
- TYPE = #{type}
179
- FSTYPE = #{fstype}
180
- SIZE = #{size}
181
- PERSISTENT = #{persistent ? 'YES' : 'NO'}
243
+ NAME = #{img_config[:name]}
244
+ TYPE = #{img_config[:type]}
245
+ FSTYPE = #{img_config[:fstype]}
246
+ SIZE = #{img_config[:size]}
247
+ PERSISTENT = #{img_config[:persistent] ? 'YES' : 'NO'}
182
248
 
183
- DRIVER = #{driver}
184
- DEV_PREFIX = #{prefix}
185
- EOT
249
+ DRIVER = #{img_config[:driver]}
250
+ DEV_PREFIX = #{img_config[:prefix]}
251
+ EOT
186
252
 
187
253
  img = OpenNebula::Image.new(OpenNebula::Image.build_xml, @client)
188
254
  raise OpenNebulaException, img.message if OpenNebula.is_error?(img)
189
255
 
190
- rc = img.allocate(template, ds_id)
256
+ rc = img.allocate(template, img_config[:datastore_id])
191
257
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
192
258
 
193
- Chef::Log.debug("Allocated disk image #{name} (#{img.id})")
259
+ Chef::Log.debug("Allocated disk image #{img_config[:name]} (#{img.id})")
194
260
  img
195
261
  end
196
262
 
@@ -198,7 +264,7 @@ EOT
198
264
  cur_state = nil
199
265
  image = nil
200
266
  state = 'INIT'
201
- pool = get_pool('img')
267
+ pool = get_pool(:image)
202
268
  while state == 'INIT' || state == 'LOCKED'
203
269
  pool.info!(-2, img_id, img_id)
204
270
  pool.each do |img|
@@ -230,6 +296,13 @@ EOT
230
296
  vnet
231
297
  end
232
298
 
299
+ def update_template(template_id, template_str)
300
+ template = OpenNebula::Template.new(OpenNebula::Template.build_xml(template_id), @client)
301
+ rc = template.update(template_str) unless OpenNebula.is_error?(template)
302
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
303
+ rc
304
+ end
305
+
233
306
  def allocate_template(template_str)
234
307
  tpl = OpenNebula::Template.new(OpenNebula::Template.build_xml, @client)
235
308
  rc = tpl.allocate(template_str) unless OpenNebula.is_error?(tpl)
@@ -270,24 +343,39 @@ EOT
270
343
  elsif !options[:template].nil?
271
344
  t_hash = template_from_hash(options)
272
345
  else
273
- fail "To create a VM you must specify one of ':template', ':template_id', ':template_name', or ':template' options in ':bootstrap_options'"
346
+ fail "To create a VM you must specify one of ':template', " \
347
+ "':template_id', or ':template_name' option " \
348
+ "in ':bootstrap_options'"
274
349
  end
275
350
  fail "Inavlid VM template : #{t_hash}" if t_hash.nil? || t_hash.empty?
276
351
  tpl_updates = options[:template_options] || {}
277
352
  if options[:user_variables]
278
- Chef::Log.warn("':user_variables' will be deprecated in next version in favour of ':template_options'") if options.key?(:user_variables)
353
+ Chef::Log.warn("':user_variables' will be deprecated in next " \
354
+ "version in favour of ':template_options'")
279
355
  recursive_merge(tpl_updates, options[:user_variables])
280
356
  end
281
357
  recursive_merge(t_hash, tpl_updates) unless tpl_updates.empty?
282
- t_hash['NAME'] = options[:enforce_chef_fqdn] ? name : name.split('.')[0]
358
+ if options[:enforce_chef_fqdn]
359
+ Chef::Log.warn(':enforce_chef_fqdn has been deprecated. VM name ' \
360
+ 'will be set to the machine resource name.')
361
+ end
362
+ # FQDN is the machine resource name, unless overridden by e.g. cloud-init
363
+ t_hash['NAME'] = name
364
+ unless t_hash['CONTEXT']['SSH_PUBLIC_KEY']
365
+ t_hash['CONTEXT']['SSH_PUBLIC_KEY'] = "$USER[SSH_PUBLIC_KEY]"
366
+ end
367
+ unless t_hash['CONTEXT']['USER_DATA']
368
+ t_hash['CONTEXT']['USER_DATA'] = "#cloud-config\n" \
369
+ "manage_etc_hosts: true\n"
370
+ end
283
371
  tpl = create_template(t_hash)
284
372
  Chef::Log.debug(tpl)
285
373
  tpl
286
374
  end
287
375
 
288
376
  def template_from_one(options)
289
- t = get_resource('tpl', :name => options[:template_name]) if options[:template_name]
290
- t = get_resource('tpl', :id => options[:template_id]) if options[:template_id]
377
+ t = get_resource(:template, :name => options[:template_name]) if options[:template_name]
378
+ t = get_resource(:template, :id => options[:template_id]) if options[:template_id]
291
379
  fail "Template '#{options}' does not exist" if t.nil?
292
380
  t.to_hash["VMTEMPLATE"]["TEMPLATE"]
293
381
  end
@@ -306,7 +394,6 @@ EOT
306
394
  end
307
395
 
308
396
  def template_from_hash(options)
309
- Chef::Log.debug("TEMPLATE_JSON: #{options[:template]}")
310
397
  options[:template]
311
398
  end
312
399
 
@@ -315,6 +402,49 @@ EOT
315
402
  # in the 't' Hash. The hash must have equivalent structure as the
316
403
  # VM template.
317
404
  #
405
+ # We considered using OpenNebulaHelper::create_template for this,
406
+ # however it would require a backwards compatibility shim and/or
407
+ # making breaking changes to the API. In particular, our method is
408
+ # more attractive due to the nested nature of our Hash, versus specifying
409
+ # a long string for the :context attribute with embedded newlines.
410
+ # Our strategy provides a way to override specific values, while this is
411
+ # difficult to accomplish with OpenNebulaHelper::create_template.
412
+ #
413
+ # Current template hash:
414
+ # {
415
+ # "NAME" => "baz"
416
+ # "CPU" => "1",
417
+ # "VCPU" => "1",
418
+ # "MEMORY" => "512",
419
+ # "OS" => {
420
+ # "ARCH" => "x86_64"
421
+ # },
422
+ # "GRAPHICS" => {
423
+ # "LISTEN" => "0.0.0.0",
424
+ # "TYPE" => "vnc"
425
+ # },
426
+ # "CONTEXT" => {
427
+ # "FOO" => "BAR",
428
+ # "BAZ" => "QUX"
429
+ # "HOSTNAME" => "$NAME",
430
+ # "SSH_PUBLIC_KEY" => "$USER[SSH_PUBLIC_KEY]",
431
+ # "NETWORK" => "YES"
432
+ # }
433
+ # }
434
+ #
435
+ # Using OpenNebulaHelper::create_template:
436
+ # {
437
+ # :name => 'baz'
438
+ # :cpu => 1,
439
+ # :vcpu => 1,
440
+ # :memory => 512,
441
+ # :arch => 'x86_64',
442
+ # :vnc => true,
443
+ # :context => "FOO=\"BAR\"\nBAZ=\"QUX\"\nHOSTNAME=\"$NAME\"",
444
+ # :ssh => true,
445
+ # :net_context => true
446
+ # }
447
+ #
318
448
  def create_template(t, level = 0)
319
449
  tpl = ""
320
450
  count = t.size
@@ -340,12 +470,21 @@ EOT
340
470
  else
341
471
  comma = (index < count) && level > 0
342
472
  level.times { tpl << " " }
343
- tpl << "#{k} = \"#{v}\"" << (comma ? ",\n" : "\n")
473
+ # Escape quotation marks to fix MAND-1394:
474
+ # template does not support embedded quotation marks
475
+ # Escaping of " only happens if " is not already escaped, preceded by \\
476
+ tpl << "#{k} = \"#{v.gsub(/(?<!\\)\"/, '\"')}\"" << (comma ? ",\n" : "\n")
344
477
  index += 1
345
478
  end
346
479
  end
347
480
  tpl
348
481
  end
482
+
483
+ def version_mismatch_warning(server, gem)
484
+ Chef::Log.warn('GEM / SERVER VERSION MISMATCH')
485
+ Chef::Log.warn("Your gem version is #{gem.join('.')} and the server version is #{server.join('.')}")
486
+ Chef::Log.warn('Users may experience issues with this gem.')
487
+ end
349
488
  end
350
489
  end
351
490
  end