chef-provisioning-opennebula 0.3.4 → 0.4.3

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