chef-provisioning-opennebula 0.3.4
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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +532 -0
- data/Rakefile +6 -0
- data/lib/chef/provider/one_image.rb +244 -0
- data/lib/chef/provider/one_template.rb +95 -0
- data/lib/chef/provider/one_user.rb +105 -0
- data/lib/chef/provider/one_vnet.rb +122 -0
- data/lib/chef/provider/one_vnet_lease.rb +133 -0
- data/lib/chef/provisioning/driver_init/opennebula.rb +17 -0
- data/lib/chef/provisioning/opennebula_driver.rb +18 -0
- data/lib/chef/provisioning/opennebula_driver/credentials.rb +105 -0
- data/lib/chef/provisioning/opennebula_driver/driver.rb +572 -0
- data/lib/chef/provisioning/opennebula_driver/one_lib.rb +352 -0
- data/lib/chef/provisioning/opennebula_driver/resources.rb +20 -0
- data/lib/chef/provisioning/opennebula_driver/version.rb +30 -0
- data/lib/chef/resource/one_image.rb +65 -0
- data/lib/chef/resource/one_template.rb +46 -0
- data/lib/chef/resource/one_user.rb +50 -0
- data/lib/chef/resource/one_vnet.rb +51 -0
- data/lib/chef/resource/one_vnet_lease.rb +46 -0
- data/spec/integration/test_all_integration_spec.rb +102 -0
- data/spec/recipes/attach_back_one_vm_spec.rb +20 -0
- data/spec/recipes/attach_back_two_vm_spec.rb +20 -0
- data/spec/recipes/attach_one_image_spec.rb +20 -0
- data/spec/recipes/converge_back_one_vm_spec.rb +19 -0
- data/spec/recipes/converge_back_two_vm_spec.rb +19 -0
- data/spec/recipes/converge_bootstrap_vm_spec.rb +34 -0
- data/spec/recipes/create_back_one_vm_spec.rb +20 -0
- data/spec/recipes/create_back_two_vm_spec.rb +20 -0
- data/spec/recipes/create_bootstrap_vm_spec.rb +34 -0
- data/spec/recipes/create_one_image_spec.rb +21 -0
- data/spec/recipes/create_one_template_spec.rb +52 -0
- data/spec/recipes/delete_all_spec.rb +47 -0
- data/spec/recipes/driver_options_spec.rb +70 -0
- data/spec/recipes/instantiate_one_template_spec.rb +35 -0
- data/spec/recipes/snapshot_one_image_spec.rb +21 -0
- data/spec/recipes/snapshot_two_image_spec.rb +21 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/opennebula_support.rb +64 -0
- metadata +168 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
# Copyright 2015, BlackBerry, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
#
|
16
|
+
# Implementation of Provider class.
|
17
|
+
#
|
18
|
+
class Chef
|
19
|
+
#
|
20
|
+
# Implementation of Provider class.
|
21
|
+
#
|
22
|
+
class Provider
|
23
|
+
#
|
24
|
+
# Implementation of Provider class.
|
25
|
+
#
|
26
|
+
class OneVnet < Chef::Provider::LWRPBase
|
27
|
+
use_inline_resources
|
28
|
+
|
29
|
+
provides :one_vnet
|
30
|
+
|
31
|
+
attr_reader :current_vnet
|
32
|
+
|
33
|
+
def action_handler
|
34
|
+
@action_handler ||= Chef::Provisioning::ChefProviderActionHandler.new(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def exists?(filter)
|
38
|
+
new_driver = driver
|
39
|
+
@current_vnet = new_driver.one.get_resource('vnet', filter)
|
40
|
+
Chef::Log.debug("VNET '#{filter}' exists: #{!@current_vnet.nil?}")
|
41
|
+
!@current_vnet.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
action :create do
|
45
|
+
fail "Missing attribute 'template_file'" unless @new_resource.template_file
|
46
|
+
fail "Missing attribute 'cluster_id'" unless @new_resource.cluster_id
|
47
|
+
|
48
|
+
if exists?(:name => @new_resource.name)
|
49
|
+
action_handler.report_progress "vnet '#{@new_resource.name}' already exists - nothing to do"
|
50
|
+
else
|
51
|
+
action_handler.perform_action "created vnet '#{@new_resource.name}' from '#{@new_resource.template_file}'" do
|
52
|
+
template_str = ::File.read(@new_resource.template_file) + "\nNAME=\"#{@new_resource.name}\""
|
53
|
+
vnet = new_driver.one.allocate_vnet(template_str, @new_resource.cluster_id)
|
54
|
+
Chef::Log.debug(template_str)
|
55
|
+
fail "failed to create vnet '#{@new_resource.name}': #{vnet.message}" if OpenNebula.is_error?(vnet)
|
56
|
+
@new_resource.updated_by_last_action(true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
action :delete do
|
62
|
+
if exists?(:id => @new_resource.vnet_id, :name => @new_resource.name)
|
63
|
+
action_handler.perform_action "deleted vnet '#{new_resource.name}' (#{@current_vnet.id})" do
|
64
|
+
rc = @current_vnet.delete
|
65
|
+
fail "failed to delete vnet '#{@new_resource.name}': #{rc.message}" if OpenNebula.is_error?(rc)
|
66
|
+
@new_resource.updated_by_last_action(true)
|
67
|
+
end
|
68
|
+
else
|
69
|
+
action_handler.report_progress "vnet '#{new_resource.name}' does not exists - nothing to do"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
action :reserve do
|
74
|
+
fail "Missing attribute 'network'" unless @new_resource.network
|
75
|
+
|
76
|
+
if exists?(:name => @new_resource.name)
|
77
|
+
hash = @current_vnet.to_hash
|
78
|
+
ar_pool = [hash['VNET']['AR_POOL']].flatten
|
79
|
+
Chef::Log.debug(@current_vnet.to_hash)
|
80
|
+
same = false
|
81
|
+
if @new_resource.ar_id && @new_resource.ar_id > -1
|
82
|
+
ar_pool.each do |ar|
|
83
|
+
same = true if ar['AR']['AR_ID'] == @new_resource.ar_id.to_s && ar['AR']['SIZE'].to_i == @new_resource.size
|
84
|
+
end
|
85
|
+
else
|
86
|
+
same = ar_pool[0]['AR']['SIZE'].to_i == @new_resource.size
|
87
|
+
end
|
88
|
+
if same
|
89
|
+
action_handler.report_progress "vnet '#{@new_resource.name}' already exists - nothing to do"
|
90
|
+
else
|
91
|
+
fail "vnet '#{@new_resource.name}' exists with different configuration"
|
92
|
+
end
|
93
|
+
else
|
94
|
+
fail "parent network '#{@new_resource.network}' does not exist" unless exists?(:id => @new_resource.network)
|
95
|
+
action_handler.perform_action "reserved vnet '#{@new_resource.name}'" do
|
96
|
+
rc = @current_vnet.reserve(@new_resource.name, @new_resource.size.to_s, @new_resource.ar_id.to_s, @new_resource.mac_ip, nil)
|
97
|
+
fail "Failed to reserve new vnet in network (#{@new_resource.network}): #{rc.message}" if OpenNebula.is_error?(rc)
|
98
|
+
@new_resource.updated_by_last_action(true)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def driver
|
106
|
+
if current_driver && current_driver.driver_url != new_driver.driver_url
|
107
|
+
fail "Cannot move '#{machine_spec.name}' from #{current_driver.driver_url} to #{new_driver.driver_url}: machine moving is not supported. Destroy and recreate."
|
108
|
+
end
|
109
|
+
fail "Driver not specified for one_image #{new_resource.name}" unless new_driver
|
110
|
+
new_driver
|
111
|
+
end
|
112
|
+
|
113
|
+
def new_driver
|
114
|
+
run_context.chef_provisioning.driver_for(new_resource.driver)
|
115
|
+
end
|
116
|
+
|
117
|
+
def current_driver
|
118
|
+
run_context.chef_provisioning.driver_for(run_context.chef_provisioning.current_driver) if run_context.chef_provisioning.current_driver
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Copyright 2015, BlackBerry, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
#
|
16
|
+
# Implementation of Provider class.
|
17
|
+
#
|
18
|
+
class Chef
|
19
|
+
#
|
20
|
+
# Implementation of Provider class.
|
21
|
+
#
|
22
|
+
class Provider
|
23
|
+
#
|
24
|
+
# Implementation of Provider class.
|
25
|
+
#
|
26
|
+
class OneVnetLease < Chef::Provider::LWRPBase
|
27
|
+
use_inline_resources
|
28
|
+
|
29
|
+
provides :one_vnet_lease
|
30
|
+
|
31
|
+
attr_reader :current_vnet
|
32
|
+
|
33
|
+
def action_handler
|
34
|
+
@action_handler ||= Chef::Provisioning::ChefProviderActionHandler.new(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def exists?
|
38
|
+
new_driver = driver
|
39
|
+
filter = { @new_resource.vnet.is_a?(Integer) ? :id : :name => @new_resource.vnet }
|
40
|
+
@current_vnet = new_driver.one.get_resource('vnet', filter)
|
41
|
+
fail "vnet '#{@new_resource.vnet}' does not exist" if @current_vnet.nil?
|
42
|
+
@current_vnet.info!
|
43
|
+
hash = @current_vnet.to_hash
|
44
|
+
|
45
|
+
lookup = @new_resource.name.include?(':') ? 'MAC' : 'IP'
|
46
|
+
ar_pool = [hash['VNET']['AR_POOL']].flatten
|
47
|
+
|
48
|
+
if @new_resource.ar_id && @new_resource.ar_id > -1
|
49
|
+
ar_pool = get_ar_pool(ar_pool, @new_resource.ar_id.to_s)
|
50
|
+
fail "ar_id not found '#{@new_resource.ar_id}'" if ar_pool.nil?
|
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)
|
68
|
+
end
|
69
|
+
|
70
|
+
action :hold do
|
71
|
+
if exists?
|
72
|
+
action_handler.report_progress("#{@new_resource.name} is already on hold and not used")
|
73
|
+
else
|
74
|
+
action_handler.perform_action "hold '#{@new_resource.name}'" do
|
75
|
+
rc = @current_vnet.hold(@new_resource.name, @new_resource.ar_id || -1)
|
76
|
+
fail "Failed to put a hold on '#{@new_resource.name}': #{rc.message}" if OpenNebula.is_error?(rc)
|
77
|
+
@new_resource.updated_by_last_action(true)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
action :release do
|
83
|
+
if exists?
|
84
|
+
action_handler.perform_action "released '#{@new_resource.name}'" do
|
85
|
+
rc = @current_vnet.release(@new_resource.name, @new_resource.ar_id || -1)
|
86
|
+
fail "Failed to release '#{@new_resource.name}': #{rc.message}" if OpenNebula.is_error?(rc)
|
87
|
+
@new_resource.updated_by_last_action(true)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
action_handler.report_progress("#{@new_resource.name} is not present - nothing do to")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def get_ar_pool(ar_pool, ar_id)
|
97
|
+
ar_pool.each { |a| return [a] if a['AR']['AR_ID'] == ar_id }
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def lease_available?(ar_pool, lookup)
|
102
|
+
exists = false
|
103
|
+
vm = -2
|
104
|
+
ar_pool.each do |a|
|
105
|
+
next unless a['AR']['LEASES']['LEASE']
|
106
|
+
[a['AR']['LEASES']['LEASE']].flatten.each do |l|
|
107
|
+
next unless l[lookup] && l[lookup] == @new_resource.name
|
108
|
+
exists = true
|
109
|
+
vm = l['VM'].to_i
|
110
|
+
break
|
111
|
+
end
|
112
|
+
end
|
113
|
+
(exists && vm == -1)
|
114
|
+
end
|
115
|
+
|
116
|
+
def driver
|
117
|
+
if current_driver && current_driver.driver_url != new_driver.driver_url
|
118
|
+
fail "Cannot move '#{machine_spec.name}' from #{current_driver.driver_url} to #{new_driver.driver_url}: machine moving is not supported. Destroy and recreate."
|
119
|
+
end
|
120
|
+
fail "Driver not specified for one_image #{new_resource.name}" unless new_driver
|
121
|
+
new_driver
|
122
|
+
end
|
123
|
+
|
124
|
+
def new_driver
|
125
|
+
run_context.chef_provisioning.driver_for(new_resource.driver)
|
126
|
+
end
|
127
|
+
|
128
|
+
def current_driver
|
129
|
+
run_context.chef_provisioning.driver_for(run_context.chef_provisioning.current_driver) if run_context.chef_provisioning.current_driver
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2015, BlackBerry, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'chef/provisioning/opennebula_driver/driver'
|
16
|
+
|
17
|
+
Chef::Provisioning.register_driver_class("opennebula", Chef::Provisioning::OpenNebulaDriver::Driver)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Copyright 2015, BlackBerry, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'chef/provisioning'
|
16
|
+
require 'chef/provisioning/opennebula_driver/driver'
|
17
|
+
require 'chef/provisioning/opennebula_driver/resources'
|
18
|
+
require 'chef/provisioning/opennebula_driver/credentials'
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# Copyright 2015, BlackBerry, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'json'
|
16
|
+
|
17
|
+
#
|
18
|
+
# Implementation of Provider class.
|
19
|
+
#
|
20
|
+
class Chef
|
21
|
+
#
|
22
|
+
# Extending module.
|
23
|
+
#
|
24
|
+
module Provisioning
|
25
|
+
#
|
26
|
+
# Extending module.
|
27
|
+
#
|
28
|
+
module OpenNebulaDriver
|
29
|
+
#
|
30
|
+
# Implementation of Provider class.
|
31
|
+
#
|
32
|
+
class Credentials
|
33
|
+
def initialize(options = {})
|
34
|
+
@credentials = {}
|
35
|
+
load_default(options)
|
36
|
+
load_profiles
|
37
|
+
end
|
38
|
+
|
39
|
+
def default
|
40
|
+
fail 'No credentials loaded! Do you have a ~/.one/one_auth file?' if @credentials.size == 0
|
41
|
+
@credentials[ENV['ONE_DEFAULT_PROFILE'] || 'default'] || @credentials.first[1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](name)
|
45
|
+
fail "Profile '#{name}' does not exist" unless @credentials[name]
|
46
|
+
@credentials[name]
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_default(options = {})
|
50
|
+
oneauth_file = ENV['ONE_AUTH'] || File.expand_path('~/.one/one_auth')
|
51
|
+
begin
|
52
|
+
creds = File.read(oneauth_file).strip
|
53
|
+
@credentials['default'] = { :credentials => creds, :options => options }
|
54
|
+
end if File.file?(oneauth_file)
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_profiles
|
58
|
+
file = nil
|
59
|
+
if ENV['ONE_CONFIG'] && !ENV['ONE_CONFIG'].empty? && File.file?(ENV['ONE_CONFIG'])
|
60
|
+
file = ENV['ONE_CONFIG']
|
61
|
+
elsif ENV['HOME'] && File.file?("#{ENV['HOME']}/.one/one_config")
|
62
|
+
file = "#{ENV['HOME']}/.one/one_config"
|
63
|
+
elsif File.file?("/var/lib/one/.one/one_config")
|
64
|
+
file = "/var/lib/one/.one/one_config"
|
65
|
+
else
|
66
|
+
Chef::Log.info("No ONE_CONFIG file found, will use default profile")
|
67
|
+
end
|
68
|
+
json = {}
|
69
|
+
begin
|
70
|
+
content_hash = JSON.parse(File.read(file), :symbolize_names => true)
|
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}")
|
74
|
+
end
|
75
|
+
@credentials.merge!(json)
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_plain(creds, options = {})
|
79
|
+
@credentials['default'] = {
|
80
|
+
:credentials => creds,
|
81
|
+
:options => options
|
82
|
+
} unless creds.nil?
|
83
|
+
@credentials
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_file(filename, options = {})
|
87
|
+
creds = File.read(filename).strip if File.file?(filename)
|
88
|
+
@credentials['default'] = {
|
89
|
+
:credentials => creds,
|
90
|
+
:options => options
|
91
|
+
} unless creds.nil?
|
92
|
+
@credentials
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.method_missing(name, *args, &block)
|
96
|
+
singleton.send(name, *args, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.singleton
|
100
|
+
@one_credentials ||= Credentials.new
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,572 @@
|
|
1
|
+
# Copyright 2015, BlackBerry, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'chef/provisioning/driver'
|
16
|
+
require 'chef/provisioning/machine/unix_machine'
|
17
|
+
require 'chef/provisioning/convergence_strategy/install_cached'
|
18
|
+
require 'chef/provisioning/convergence_strategy/install_sh'
|
19
|
+
require 'chef/provisioning/convergence_strategy/no_converge'
|
20
|
+
require 'chef/provisioning/transport/ssh'
|
21
|
+
require 'chef/provisioning/opennebula_driver/version'
|
22
|
+
require 'chef/provisioning/opennebula_driver/one_lib'
|
23
|
+
require 'chef/provisioning/opennebula_driver/credentials'
|
24
|
+
|
25
|
+
class Chef
|
26
|
+
module Provisioning
|
27
|
+
module OpenNebulaDriver
|
28
|
+
#
|
29
|
+
# A Driver instance represents a place where machines can be created
|
30
|
+
# and found, and contains methods to create, delete, start, stop, and
|
31
|
+
# find them.
|
32
|
+
#
|
33
|
+
# For AWS, a Driver instance corresponds to a single account.
|
34
|
+
# For Vagrant, it is a directory where VM files are found.
|
35
|
+
#
|
36
|
+
# = How to Make a Driver
|
37
|
+
#
|
38
|
+
# To implement a Driver, you must implement the following methods:
|
39
|
+
#
|
40
|
+
# * initialize(driver_url) - create a new driver with the given URL
|
41
|
+
# * driver_url - a URL representing everything unique about your
|
42
|
+
# driver. (NOT credentials)
|
43
|
+
# * allocate_machine - ask the driver to allocate a machine to you.
|
44
|
+
# * ready_machine - get the machine "ready" - wait for it to be booted
|
45
|
+
# and accessible (for example, accessible via SSH
|
46
|
+
# transport).
|
47
|
+
# * stop_machine - stop the machine.
|
48
|
+
# * destroy_machine - delete the machine.
|
49
|
+
# * connect_to_machine - connect to the given machine.
|
50
|
+
#
|
51
|
+
# Optionally, you can also implement:
|
52
|
+
# * allocate_machines - allocate an entire group of machines.
|
53
|
+
# * ready_machines - get a group of machines warm and booted.
|
54
|
+
# * stop_machines - stop a group of machines.
|
55
|
+
# * destroy_machines - delete a group of machines.
|
56
|
+
#
|
57
|
+
# Additionally, you must create a file named
|
58
|
+
# `chef/provisioning/driver_init/<scheme>.rb`,
|
59
|
+
# where <scheme> is the name of the scheme you chose for your driver_url.
|
60
|
+
# This file, when required, must call
|
61
|
+
# Chef::Provisioning.add_registered_driver(<scheme>, <class>).
|
62
|
+
# The given <class>.from_url(url, config) will be called with a
|
63
|
+
# driver_url and configuration.
|
64
|
+
#
|
65
|
+
# All of these methods must be idempotent - if the work is already done,
|
66
|
+
# they just don't do anything.
|
67
|
+
#
|
68
|
+
class Driver < Chef::Provisioning::Driver
|
69
|
+
attr_reader :one
|
70
|
+
|
71
|
+
#
|
72
|
+
# OpenNebula by default reads the following information:
|
73
|
+
#
|
74
|
+
# username:password from ENV['ONE_AUTH'] or ENV['HOME']/.one/one_auth
|
75
|
+
# endpoint from ENV['ONE_XMLRPC']
|
76
|
+
#
|
77
|
+
# driver_url format:
|
78
|
+
# opennebula:<endpoint>:<profile>
|
79
|
+
# where <profile> points to a one_auth file in ENV['HOME']/.one/<profile>
|
80
|
+
#
|
81
|
+
# driver_options:
|
82
|
+
# credentials: bbsl-auto:text_pass credentials has precedence over secret_file
|
83
|
+
# secret_file: local_path_to_one_auth_file
|
84
|
+
#
|
85
|
+
def initialize(driver_url, config)
|
86
|
+
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] || {})
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.from_url(driver_url, config)
|
98
|
+
Driver.new(driver_url, config)
|
99
|
+
end
|
100
|
+
|
101
|
+
# URL _must_ have an endpoint to prevent machine moving, which
|
102
|
+
# is not possible today between different endpoints.
|
103
|
+
def self.canonicalize_url(driver_url, config)
|
104
|
+
[driver_url, config]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Allocate a machine from the underlying service. This method
|
108
|
+
# does not need to wait for the machine to boot or have an IP, but
|
109
|
+
# it must store enough information in machine_spec.location to find
|
110
|
+
# the machine later in ready_machine.
|
111
|
+
#
|
112
|
+
# If a machine is powered off or otherwise unusable, this method may
|
113
|
+
# start it, but does not need to wait until it is started. The
|
114
|
+
# idea is to get the gears moving, but the job doesn't need to be
|
115
|
+
# done :)
|
116
|
+
#
|
117
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
118
|
+
# The action_handler object that is calling this method
|
119
|
+
# @param [Chef::Provisioning::MachineSpec] machine_spec
|
120
|
+
# A machine specification representing this machine.
|
121
|
+
# @param [Hash] machine_options
|
122
|
+
# A set of options representing the desired options when
|
123
|
+
# constructing the machine
|
124
|
+
#
|
125
|
+
# @return [Chef::Provisioning::MachineSpec]
|
126
|
+
# Modifies the passed-in machine_spec. Anything in here will be
|
127
|
+
# saved back after allocate_machine completes.
|
128
|
+
#
|
129
|
+
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
|
+
instance = instance_for(machine_spec)
|
137
|
+
|
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)
|
146
|
+
end
|
147
|
+
Chef::Log.debug(machine_spec.reference)
|
148
|
+
else
|
149
|
+
Chef::Log.info("vm '#{machine_spec.name}' already exists - nothing to do")
|
150
|
+
end
|
151
|
+
machine_spec
|
152
|
+
end
|
153
|
+
|
154
|
+
# Ready a machine, to the point where it is running and accessible
|
155
|
+
# via a transport. This will NOT allocate a machine, but may kick
|
156
|
+
# it if it is down. This method waits for the machine to be usable,
|
157
|
+
# returning a Machine object pointing at the machine, allowing useful
|
158
|
+
# actions like setup, converge, execute, file and directory.
|
159
|
+
#
|
160
|
+
#
|
161
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
162
|
+
# The action_handler object that is calling this method
|
163
|
+
# @param [Chef::Provisioning::MachineSpec] machine_spec
|
164
|
+
# A machine specification representing this machine.
|
165
|
+
# @param [Hash] machine_options
|
166
|
+
# A set of options representing the desired state of the machine
|
167
|
+
#
|
168
|
+
# @return [Machine] A machine object pointing at the machine, allowing
|
169
|
+
# useful actions like setup, converge, execute, file and directory.
|
170
|
+
#
|
171
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
172
|
+
instance = instance_for(machine_spec)
|
173
|
+
fail "Machine '#{machine_spec.name}' does not have an instance associated with it, or instance does not exist." if instance.nil?
|
174
|
+
|
175
|
+
# TODO: Currently it does not start stopped VMs, it only waits for new VMs to be in RUNNING state
|
176
|
+
machine = nil
|
177
|
+
action_handler.perform_action "vm '#{machine_spec.name}' is ready" do
|
178
|
+
deployed = @one.wait_for_vm(instance.id)
|
179
|
+
machine_spec.reference['name'] = deployed.name
|
180
|
+
machine_spec.reference['state'] = deployed.state_str
|
181
|
+
nic_hash = deployed.to_hash
|
182
|
+
ip = [nic_hash['VM']['TEMPLATE']['NIC']].flatten[0]['IP']
|
183
|
+
fail "Could not get IP from VM '#{deployed.name}'" if ip.nil? || ip.to_s.empty?
|
184
|
+
machine_spec.reference['ip'] = ip
|
185
|
+
machine = machine_for(machine_spec, machine_options)
|
186
|
+
end
|
187
|
+
machine
|
188
|
+
end
|
189
|
+
|
190
|
+
# Connect to a machine without allocating or readying it. This method will
|
191
|
+
# NOT make any changes to anything, or attempt to wait.
|
192
|
+
#
|
193
|
+
# @param [Chef::Provisioning::MachineSpec] machine_spec
|
194
|
+
# MachineSpec representing this machine.
|
195
|
+
# @param [Hash] machine_options
|
196
|
+
# @return [Machine] A machine object pointing at the machine, allowing
|
197
|
+
# useful actions like setup, converge, execute, file and directory.
|
198
|
+
#
|
199
|
+
def connect_to_machine(machine_spec, machine_options)
|
200
|
+
machine_for(machine_spec, machine_options)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Delete the given machine -- destroy the machine,
|
204
|
+
# returning things to the state before allocate_machine was called.
|
205
|
+
#
|
206
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
207
|
+
# The action_handler object that is calling this method
|
208
|
+
# @param [Chef::Provisioning::MachineSpec] machine_spec
|
209
|
+
# A machine specification representing this machine.
|
210
|
+
# @param [Hash] machine_options
|
211
|
+
# A set of options representing the desired state of the machine
|
212
|
+
def destroy_machine(action_handler, machine_spec, machine_options)
|
213
|
+
instance = instance_for(machine_spec)
|
214
|
+
if !instance.nil?
|
215
|
+
action_handler.perform_action "destroyed machine #{machine_spec.name} (#{machine_spec.reference['instance_id']})" do
|
216
|
+
instance.delete
|
217
|
+
1.upto(10) do
|
218
|
+
instance.info
|
219
|
+
break if instance.state_str == 'DONE'
|
220
|
+
Chef::Log.debug("Waiting for VM '#{instance.id}' to be in 'DONE' state: '#{instance.state_str}'")
|
221
|
+
sleep(2)
|
222
|
+
end
|
223
|
+
fail "Failed to destroy '#{instance.name}'. Current state: #{instance.state_str}" if instance.state_str != 'DONE'
|
224
|
+
end
|
225
|
+
else
|
226
|
+
Chef::Log.info("vm #{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist - nothing to do")
|
227
|
+
end
|
228
|
+
strategy = convergence_strategy_for(machine_spec, machine_options)
|
229
|
+
strategy.cleanup_convergence(action_handler, machine_spec)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Stop the given machine.
|
233
|
+
#
|
234
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
235
|
+
# The action_handler object that is calling this method
|
236
|
+
# @param [Chef::Provisioning::MachineSpec] machine_spec
|
237
|
+
# A machine specification representing this machine.
|
238
|
+
# @param [Hash] machine_options
|
239
|
+
# A set of options representing the desired state of the machine
|
240
|
+
def stop_machine(action_handler, machine_spec, machine_options)
|
241
|
+
instance = instance_for(machine_spec)
|
242
|
+
if !instance.nil?
|
243
|
+
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
|
+
instance.shutdown(hard)
|
247
|
+
else
|
248
|
+
instance.stop
|
249
|
+
end
|
250
|
+
end
|
251
|
+
else
|
252
|
+
Chef::Log.info("vm #{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist - nothing to do")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Allocate an image. Returns quickly with an ID that tracks the image.
|
257
|
+
#
|
258
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
259
|
+
# The action_handler object that is calling this method
|
260
|
+
# @param [Chef::Provisioning::ImageSpec] image_spec
|
261
|
+
# A machine specification representing this machine.
|
262
|
+
# @param [Hash] image_options
|
263
|
+
# A set of options representing the desired state of the machine
|
264
|
+
def allocate_image(action_handler, image_spec, image_options, machine_spec)
|
265
|
+
if image_spec.reference
|
266
|
+
# check if image already exists
|
267
|
+
image = @one.get_resource('img', :id => image_spec.reference['image_id'].to_i)
|
268
|
+
action_handler.report_progress "image #{image_spec.name} (ID: #{image_spec.reference['image_id']}) already exists" unless image.nil?
|
269
|
+
else
|
270
|
+
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'])
|
272
|
+
fail "allocate_image: VM does not exist" if vm.nil?
|
273
|
+
# set default disk ID
|
274
|
+
disk_id = 1
|
275
|
+
if image_options.disk_id
|
276
|
+
disk_id = image_options.disk_id.is_a?(Integer) ? image_options.disk_id : @one.get_disk_id(vm, new_resource.disk_id)
|
277
|
+
end
|
278
|
+
|
279
|
+
new_img = vm.disk_snapshot(disk_id, image_spec.name, "", true)
|
280
|
+
fail "Failed to create snapshot '#{new_resource.name}': #{new_img.message}" if OpenNebula.is_error?(new_img)
|
281
|
+
populate_img_object(image_spec, new_image)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Ready an image, waiting till the point where it is ready to be used.
|
287
|
+
#
|
288
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
289
|
+
# The action_handler object that is calling this method
|
290
|
+
# @param [Chef::Provisioning::ImageSpec] image_spec
|
291
|
+
# A machine specification representing this machine.
|
292
|
+
# @param [Hash] image_options
|
293
|
+
# A set of options representing the desired state of the machine
|
294
|
+
def ready_image(action_handler, image_spec, _image_options)
|
295
|
+
img = @one.get_resource('img', :id => image_spec.reference['image_id'].to_i)
|
296
|
+
fail "Image #{image_spec.name} (#{image_spec.reference['image_id']}) does not exist" if img.nil?
|
297
|
+
action_handler.perform_action "image #{image_spec.name} is ready" do
|
298
|
+
deployed = @one.wait_for_img(img.name, img.id)
|
299
|
+
image_spec.reference['state'] = deployed.state_str
|
300
|
+
end
|
301
|
+
img
|
302
|
+
end
|
303
|
+
|
304
|
+
# Destroy an image using this service.
|
305
|
+
#
|
306
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
307
|
+
# The action_handler object that is calling this method
|
308
|
+
# @param [Chef::Provisioning::ImageSpec] image_spec
|
309
|
+
# A machine specification representing this machine.
|
310
|
+
# @param [Hash] image_options
|
311
|
+
# A set of options representing the desired state of the machine
|
312
|
+
def destroy_image(action_handler, image_spec, _image_options)
|
313
|
+
img = @one.get_resource('img', :id => image_spec.location['image_id'].to_i)
|
314
|
+
if img.nil?
|
315
|
+
action_handler.report_progress "image #{image_spec.name} (#{image_spec.location['image_id']}) does not exist - nothing to do"
|
316
|
+
else
|
317
|
+
action_handler.perform_action "deleted image #{image_spec.name} (#{image_spec.location['image_id']})" do
|
318
|
+
rc = img.delete
|
319
|
+
fail "Failed to delete image '#{image_spec.name}' : #{rc.message}" if OpenNebula.is_error?(rc)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
#
|
325
|
+
# Optional interface methods
|
326
|
+
#
|
327
|
+
|
328
|
+
#
|
329
|
+
# Allocate a set of machines. This should have the same effect as
|
330
|
+
# running allocate_machine on all machine_specs.
|
331
|
+
#
|
332
|
+
# Drivers do not need to implement this; the default implementation
|
333
|
+
# calls acquire_machine in parallel.
|
334
|
+
#
|
335
|
+
# == Parallelizing
|
336
|
+
#
|
337
|
+
# The parallelizer must implement #parallelize
|
338
|
+
# @example Example parallelizer
|
339
|
+
# parallelizer.parallelize(specs_and_options) do |machine_spec|
|
340
|
+
# allocate_machine(action_handler, machine_spec)
|
341
|
+
# end.to_a
|
342
|
+
# # The to_a at the end causes you to wait until the
|
343
|
+
# parallelization is done
|
344
|
+
#
|
345
|
+
# This object is shared among other chef-provisioning actions, ensuring
|
346
|
+
# that you do not go over parallelization limits set by the user. Use
|
347
|
+
# of the parallelizer to parallelizer machines is not required.
|
348
|
+
#
|
349
|
+
# == Passing a block
|
350
|
+
#
|
351
|
+
# If you pass a block to this function, each machine will be yielded
|
352
|
+
# to you as it completes, and then the function will return when
|
353
|
+
# all machines are yielded.
|
354
|
+
#
|
355
|
+
# @example Passing a block
|
356
|
+
# allocate_machines(
|
357
|
+
# action_handler,
|
358
|
+
# specs_and_options,
|
359
|
+
# parallelizer) do |machine_spec|
|
360
|
+
# ...
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# @param [Chef::Provisioning::ActionHandler] action_handler
|
364
|
+
# The action_handler object that is calling this method; this
|
365
|
+
# is generally a driver, but could be anything that can support
|
366
|
+
# the interface (i.e., in the case of the test kitchen
|
367
|
+
# provisioning driver for acquiring and destroying VMs).
|
368
|
+
# @param [Hash] specs_and_options
|
369
|
+
# A hash of machine_spec -> machine_options representing the
|
370
|
+
# machines to allocate.
|
371
|
+
# @param [Parallelizer] parallelizer an object with a
|
372
|
+
# parallelize() method that works like this:
|
373
|
+
# @return [Array<Machine>] An array of machine objects created
|
374
|
+
def allocate_machines(action_handler, specs_and_options, parallelizer)
|
375
|
+
parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
|
376
|
+
allocate_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
|
377
|
+
yield machine_spec if block_given?
|
378
|
+
machine_spec
|
379
|
+
end.to_a
|
380
|
+
end
|
381
|
+
|
382
|
+
# Ready machines in batch, in parallel if possible.
|
383
|
+
def ready_machines(action_handler, specs_and_options, parallelizer)
|
384
|
+
parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
|
385
|
+
machine = ready_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
|
386
|
+
yield machine if block_given?
|
387
|
+
machine
|
388
|
+
end.to_a
|
389
|
+
end
|
390
|
+
|
391
|
+
# Stop machines in batch, in parallel if possible.
|
392
|
+
def stop_machines(action_handler, specs_and_options, parallelizer)
|
393
|
+
parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
|
394
|
+
stop_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
|
395
|
+
yield machine_spec if block_given?
|
396
|
+
end.to_a
|
397
|
+
end
|
398
|
+
|
399
|
+
# Delete machines in batch, in parallel if possible.
|
400
|
+
def destroy_machines(action_handler, specs_and_options, parallelizer)
|
401
|
+
parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
|
402
|
+
destroy_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
|
403
|
+
yield machine_spec if block_given?
|
404
|
+
end.to_a
|
405
|
+
end
|
406
|
+
|
407
|
+
# Allocate a load balancer
|
408
|
+
# @param [ChefMetal::ActionHandler] action_handler The action handler
|
409
|
+
# @param [ChefMetal::LoadBalancerSpec] lb_spec Frozen LB specification
|
410
|
+
# @param [Hash] lb_options A hash of options to pass the LB
|
411
|
+
# @param [Array[ChefMetal::MachineSpec]] machine_specs
|
412
|
+
# An array of machine specs the load balancer should have
|
413
|
+
def allocate_load_balancer(_action_handler, _lb_spec, _lb_options, _machine_specs)
|
414
|
+
end
|
415
|
+
|
416
|
+
# Make the load balancer ready
|
417
|
+
# @param [ChefMetal::ActionHandler] action_handler The action handler
|
418
|
+
# @param [ChefMetal::LoadBalancerSpec] lb_spec Frozen LB specification
|
419
|
+
# @param [Hash] lb_options A hash of options to pass the LB
|
420
|
+
def ready_load_balancer(_action_handler, _lb_spec, _lb_options, _machine_specs)
|
421
|
+
end
|
422
|
+
|
423
|
+
# Destroy the load balancer
|
424
|
+
# @param [ChefMetal::ActionHandler] action_handler The action handler
|
425
|
+
# @param [ChefMetal::LoadBalancerSpec] lb_spec Frozen LB specification
|
426
|
+
# @param [Hash] lb_options A hash of options to pass the LB
|
427
|
+
def destroy_load_balancer(_action_handler, _lb_spec, _lb_options)
|
428
|
+
end
|
429
|
+
|
430
|
+
protected
|
431
|
+
|
432
|
+
def one_credentials
|
433
|
+
@one_credentials ||= begin
|
434
|
+
credentials = Credentials.new(driver_options[:one_options] || {})
|
435
|
+
if driver_options[:credentials]
|
436
|
+
credentials.load_plain(driver_options[:credentials], driver_options[:one_options] || {})
|
437
|
+
elsif driver_options[:secret_file]
|
438
|
+
credentials.load_file(driver_options[:secret_file], driver_options[:one_options] || {})
|
439
|
+
end
|
440
|
+
credentials
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
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
|
+
end
|
447
|
+
|
448
|
+
def populate_node_object(machine_spec, machine_options, vm)
|
449
|
+
machine_spec.driver_url = driver_url
|
450
|
+
machine_spec.reference = {
|
451
|
+
'driver_version' => Chef::Provisioning::OpenNebulaDriver::VERSION,
|
452
|
+
'allocated_at' => Time.now.utc.to_s,
|
453
|
+
'image_id' => machine_options.bootstrap_options[:image_id] || nil,
|
454
|
+
'is_shutdown' => machine_options.bootstrap_options[:is_shutdown] || false,
|
455
|
+
'shutdown_hard' => machine_options.bootstrap_options[:shutdown_hard] || false,
|
456
|
+
'instance_id' => vm.id,
|
457
|
+
'name' => vm.name,
|
458
|
+
'state' => vm.state_str
|
459
|
+
}
|
460
|
+
# handle ssh_user and ssh_username for backward compatibility
|
461
|
+
Chef::Log.warn("':ssh_user' will be deprecated in next version in favour of ':ssh_username'") if machine_options.key?(:ssh_user)
|
462
|
+
machine_spec.reference['ssh_username'] = get_ssh_user(machine_spec, machine_options)
|
463
|
+
%w(is_windows sudo transport_address_location ssh_gateway).each do |key|
|
464
|
+
machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def populate_img_object(image_spec, new_image)
|
469
|
+
image_spec.driver_url = driver_url
|
470
|
+
image_spec.reference = {
|
471
|
+
'driver_version' => Chef::Provisioning::OpenNebulaDriver::VERSION,
|
472
|
+
'image_id' => new_image,
|
473
|
+
'allocated_at' => Time.now.to_i,
|
474
|
+
'state' => 'none'
|
475
|
+
}
|
476
|
+
image_spec.machine_options ||= {}
|
477
|
+
image_spec.machine_options.merge!(:bootstrap_options => { :image_id => new_image })
|
478
|
+
end
|
479
|
+
|
480
|
+
def instance_for(machine_spec)
|
481
|
+
instance = nil
|
482
|
+
if machine_spec.reference
|
483
|
+
fail "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not supported!" \
|
484
|
+
" 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)
|
486
|
+
elsif machine_spec.location
|
487
|
+
fail "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not supported!" \
|
488
|
+
" 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)
|
490
|
+
unless instance.nil?
|
491
|
+
# Convert from previous driver
|
492
|
+
machine_spec.reference = {
|
493
|
+
'driver_version' => machine_spec.location['driver_version'],
|
494
|
+
'allocated_at' => machine_spec.location['allocated_at'],
|
495
|
+
'image_id' => machine_spec.location['image_id'],
|
496
|
+
'instance_id' => machine_spec.location['server_id'],
|
497
|
+
'name' => machine_spec.location['name'],
|
498
|
+
'state' => machine_spec.location['state']
|
499
|
+
}
|
500
|
+
end
|
501
|
+
end
|
502
|
+
instance
|
503
|
+
end
|
504
|
+
|
505
|
+
def machine_for(machine_spec, machine_options)
|
506
|
+
instance = instance_for(machine_spec)
|
507
|
+
fail "#{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist!" if instance.nil?
|
508
|
+
# 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))
|
510
|
+
end
|
511
|
+
|
512
|
+
def get_ssh_user(machine_spec, machine_options)
|
513
|
+
# handle ssh_user and ssh_username for backward compatibility
|
514
|
+
Chef::Log.warn("':ssh_user' will be deprecated in next version in favour of ':ssh_username'") if machine_options.key?(:ssh_user)
|
515
|
+
machine_spec.reference['ssh_username'] || machine_options[:ssh_username] || machine_options[:ssh_user] || 'local'
|
516
|
+
end
|
517
|
+
|
518
|
+
def transport_for(machine_spec, machine_options, _instance)
|
519
|
+
# TODO: Store ssh_options in machine_spec.reference ???
|
520
|
+
ssh_options = {
|
521
|
+
:keys_only => false,
|
522
|
+
:forward_agent => true,
|
523
|
+
:use_agent => true,
|
524
|
+
:user_known_hosts_file => '/dev/null'
|
525
|
+
}.merge(machine_options[:ssh_options] || {})
|
526
|
+
username = get_ssh_user(machine_spec, machine_options)
|
527
|
+
conf = machine_options[:ssh_config] || config
|
528
|
+
options = {}
|
529
|
+
if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != 'root')
|
530
|
+
options[:prefix] = 'sudo '
|
531
|
+
end
|
532
|
+
|
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)
|
538
|
+
|
539
|
+
# wait up to 5 min to establish SSH connection
|
540
|
+
100.times do
|
541
|
+
break if transport.available?
|
542
|
+
sleep 3
|
543
|
+
Chef::Log.debug("Waiting for SSH server ...")
|
544
|
+
end
|
545
|
+
fail "Failed to establish SSH connection to '#{machine_spec.name}'" unless transport.available?
|
546
|
+
transport
|
547
|
+
end
|
548
|
+
|
549
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
550
|
+
# TODO: Support Windoze VMs (see chef-provisioning-vagrant)
|
551
|
+
convergence_options = Cheffish::MergedConfig.new(machine_options[:convergence_options] || {})
|
552
|
+
|
553
|
+
if !machine_spec.reference
|
554
|
+
Chef::Provisioning::ConvergenceStrategy::NoConverge.new(convergence_options, config)
|
555
|
+
elsif machine_options[:cached_installer] == true
|
556
|
+
Chef::Provisioning::ConvergenceStrategy::InstallCached.new(convergence_options, config)
|
557
|
+
else
|
558
|
+
Chef::Provisioning::ConvergenceStrategy::InstallSh.new(convergence_options, config)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
def add_prefix(machine_spec, action_handler)
|
563
|
+
AddPrefixActionHandler.new(action_handler, "[#{machine_spec.name}] ")
|
564
|
+
end
|
565
|
+
|
566
|
+
def get_private_key(name)
|
567
|
+
Cheffish.get_private_key(name, config)
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|