opennebula 4.90.10.rc1 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ActionManager.rb +278 -0
- data/lib/CommandManager.rb +242 -0
- data/lib/DriverExecHelper.rb +215 -0
- data/lib/OpenNebulaDriver.rb +233 -0
- data/lib/VirtualMachineDriver.rb +376 -0
- data/lib/cloud/CloudClient.rb +1 -1
- data/lib/opennebula/virtual_machine.rb +1 -0
- data/lib/opennebula.rb +1 -1
- data/lib/vcenter_driver.rb +2716 -0
- metadata +10 -4
@@ -0,0 +1,215 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2016, OpenNebula Project, OpenNebula Systems #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
# This module provides an abstraction to generate an execution context for
|
18
|
+
# OpenNebula Drivers. The module has been designed to be included as part
|
19
|
+
# of a driver and not to be used standalone.
|
20
|
+
module DriverExecHelper
|
21
|
+
# Action result strings for messages
|
22
|
+
RESULT = {
|
23
|
+
:success => "SUCCESS",
|
24
|
+
:failure => "FAILURE"
|
25
|
+
}
|
26
|
+
|
27
|
+
def self.failed?(rc_str)
|
28
|
+
return rc_str == RESULT[:failure]
|
29
|
+
end
|
30
|
+
|
31
|
+
#Initialize module variables
|
32
|
+
def initialize_helper(directory, options)
|
33
|
+
@config = read_configuration
|
34
|
+
@remote_scripts_base_path = @config['SCRIPTS_REMOTE_DIR']
|
35
|
+
|
36
|
+
@local_actions = options[:local_actions]
|
37
|
+
|
38
|
+
if ENV['ONE_LOCATION'] == nil
|
39
|
+
@local_scripts_base_path = "/var/lib/one/remotes"
|
40
|
+
else
|
41
|
+
@local_scripts_base_path = "#{ENV['ONE_LOCATION']}/var/remotes"
|
42
|
+
end
|
43
|
+
|
44
|
+
# dummy paths
|
45
|
+
@remote_scripts_path = File.join(@remote_scripts_base_path, directory)
|
46
|
+
@local_scripts_path = File.join(@local_scripts_base_path, directory)
|
47
|
+
|
48
|
+
# mutex for logging
|
49
|
+
@send_mutex = Mutex.new
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# METHODS FOR COMMAND LINE & ACTION PATHS
|
54
|
+
#
|
55
|
+
# Given the action name and the parameter returns full path of the script
|
56
|
+
# and appends its parameters. It uses @local_actions hash to know if the
|
57
|
+
# actions is remote or local. If the local actions has defined an special
|
58
|
+
# script name this is used, otherwise the action name in downcase is
|
59
|
+
# used as the script name.
|
60
|
+
#
|
61
|
+
# @param [String, Symbol] action name of the action
|
62
|
+
# @param [String] parameters arguments for the script
|
63
|
+
# @param [String, nil] default_name alternative name for the script
|
64
|
+
# @param [String, ''] directory to append to the scripts path for actions
|
65
|
+
# @return [String] command line needed to execute the action
|
66
|
+
def action_command_line(action, parameters, default_name=nil, directory='')
|
67
|
+
if action_is_local? action
|
68
|
+
script_path=File.join(@local_scripts_path, directory)
|
69
|
+
else
|
70
|
+
script_path=File.join(@remote_scripts_path, directory)
|
71
|
+
end
|
72
|
+
|
73
|
+
File.join(script_path, action_script_name(action, default_name))+
|
74
|
+
" "+parameters
|
75
|
+
end
|
76
|
+
|
77
|
+
# True if the action is meant to be executed locally
|
78
|
+
#
|
79
|
+
# @param [String, Symbol] action name of the action
|
80
|
+
def action_is_local?(action)
|
81
|
+
@local_actions.include? action.to_s.upcase
|
82
|
+
end
|
83
|
+
|
84
|
+
# Name of the script file for the given action
|
85
|
+
#
|
86
|
+
# @param [String, Symbol] action name of the action
|
87
|
+
# @param [String, nil] default_name alternative name for the script
|
88
|
+
def action_script_name(action, default_name=nil)
|
89
|
+
name=@local_actions[action.to_s.upcase]
|
90
|
+
|
91
|
+
if name
|
92
|
+
name
|
93
|
+
else
|
94
|
+
default_name || action.to_s.downcase
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# METHODS FOR LOGS & COMMAND OUTPUT
|
100
|
+
#
|
101
|
+
# Sends a message to the OpenNebula core through stdout
|
102
|
+
def send_message(action="-", result=RESULT[:failure], id="-", info="-")
|
103
|
+
@send_mutex.synchronize {
|
104
|
+
STDOUT.puts "#{action} #{result} #{id} #{info}"
|
105
|
+
STDOUT.flush
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
# Sends a log message to ONE. The +message+ can be multiline, it will
|
110
|
+
# be automatically splitted by lines.
|
111
|
+
def log(number, message)
|
112
|
+
in_error_message=false
|
113
|
+
msg=message.strip
|
114
|
+
msg.each_line {|line|
|
115
|
+
severity='I'
|
116
|
+
|
117
|
+
l=line.strip
|
118
|
+
|
119
|
+
if l=='ERROR MESSAGE --8<------'
|
120
|
+
in_error_message=true
|
121
|
+
next
|
122
|
+
elsif l=='ERROR MESSAGE ------>8--'
|
123
|
+
in_error_message=false
|
124
|
+
next
|
125
|
+
else
|
126
|
+
if in_error_message
|
127
|
+
severity='E'
|
128
|
+
elsif line.match(/^(ERROR|DEBUG|INFO):(.*)$/)
|
129
|
+
line=$2
|
130
|
+
case $1
|
131
|
+
when 'ERROR'
|
132
|
+
severity='E'
|
133
|
+
when 'DEBUG'
|
134
|
+
severity='D'
|
135
|
+
when 'INFO'
|
136
|
+
severity='I'
|
137
|
+
else
|
138
|
+
severity='I'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
send_message("LOG", severity, number, line.strip)
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
# Generates a proc with that calls log with a hardcoded number. It will
|
148
|
+
# be used to add loging to command actions
|
149
|
+
def log_method(num)
|
150
|
+
lambda {|message|
|
151
|
+
log(num, message)
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
#This method returns the result in terms
|
156
|
+
def get_info_from_execution(command_exe)
|
157
|
+
if command_exe.code == 0
|
158
|
+
result = RESULT[:success]
|
159
|
+
info = command_exe.stdout
|
160
|
+
else
|
161
|
+
result = RESULT[:failure]
|
162
|
+
info = command_exe.get_error_message
|
163
|
+
end
|
164
|
+
|
165
|
+
info = "-" if info == nil || info.empty?
|
166
|
+
|
167
|
+
[result, info]
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
#
|
172
|
+
# Simple parser for the config file generated by OpenNebula
|
173
|
+
def read_configuration
|
174
|
+
one_config=nil
|
175
|
+
|
176
|
+
if ENV['ONE_LOCATION']
|
177
|
+
one_config = ENV['ONE_LOCATION']+'/var/config'
|
178
|
+
else
|
179
|
+
one_config = '/var/lib/one/config'
|
180
|
+
end
|
181
|
+
|
182
|
+
config=Hash.new
|
183
|
+
cfg=''
|
184
|
+
|
185
|
+
begin
|
186
|
+
open(one_config) do |file|
|
187
|
+
cfg=file.read
|
188
|
+
end
|
189
|
+
|
190
|
+
cfg.split(/\n/).each do |line|
|
191
|
+
m=line.match(/^([^=]+)=(.*)$/)
|
192
|
+
|
193
|
+
if m
|
194
|
+
name=m[1].strip.upcase
|
195
|
+
value=m[2].strip
|
196
|
+
|
197
|
+
if config[name]
|
198
|
+
if config[name].instance_of? Array
|
199
|
+
config[name] << value
|
200
|
+
else
|
201
|
+
config[name] = [config[name], value]
|
202
|
+
end
|
203
|
+
else
|
204
|
+
config[name]=value
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
rescue Exception => e
|
209
|
+
STDERR.puts "Error reading config: #{e.inspect}"
|
210
|
+
STDERR.flush
|
211
|
+
end
|
212
|
+
|
213
|
+
config
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# -------------------------------------------------------------------------- #
|
2
|
+
# Copyright 2002-2016, OpenNebula Project, OpenNebula Systems #
|
3
|
+
# #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
require "ActionManager"
|
18
|
+
require "CommandManager"
|
19
|
+
|
20
|
+
require "DriverExecHelper"
|
21
|
+
require 'base64'
|
22
|
+
|
23
|
+
# This class provides basic messaging and logging functionality
|
24
|
+
# to implement OpenNebula Drivers. A driver is a program that
|
25
|
+
# specialize the OpenNebula behavior by interfacing with specific
|
26
|
+
# infrastructure functionalities.
|
27
|
+
#
|
28
|
+
# A Driver inherits this class and only has to provide methods
|
29
|
+
# for each action it wants to receive. The method must be associated
|
30
|
+
# with the action name through the register_action function
|
31
|
+
class OpenNebulaDriver < ActionManager
|
32
|
+
include DriverExecHelper
|
33
|
+
|
34
|
+
# @return [String] Base path for scripts
|
35
|
+
attr_reader :local_scripts_base_path, :remote_scripts_base_path
|
36
|
+
# @return [String] Path for scripts
|
37
|
+
attr_reader :local_scripts_path, :remote_scripts_path
|
38
|
+
|
39
|
+
# Initialize OpenNebulaDriver object
|
40
|
+
#
|
41
|
+
# @param [String] directory path inside the remotes directory where the
|
42
|
+
# scripts are located
|
43
|
+
# @param [Hash] options named options to change the object's behaviour
|
44
|
+
# @option options [Number] :concurrency (10) max number of threads
|
45
|
+
# @option options [Boolean] :threaded (true) enables or disables threads
|
46
|
+
# @option options [Number] :retries (0) number of retries to copy scripts
|
47
|
+
# to the remote host
|
48
|
+
# @option options [Hash] :local_actions ({}) hash with the actions
|
49
|
+
# executed locally and the name of the script if it differs from the
|
50
|
+
# default one. This hash can be constructed using {parse_actions_list}
|
51
|
+
def initialize(directory, options={})
|
52
|
+
@options={
|
53
|
+
:concurrency => 10,
|
54
|
+
:threaded => true,
|
55
|
+
:retries => 0,
|
56
|
+
:local_actions => {}
|
57
|
+
}.merge!(options)
|
58
|
+
|
59
|
+
super(@options[:concurrency], @options[:threaded])
|
60
|
+
|
61
|
+
@retries = @options[:retries]
|
62
|
+
|
63
|
+
#Set default values
|
64
|
+
initialize_helper(directory, @options)
|
65
|
+
|
66
|
+
register_action(:INIT, method("init"))
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
# Calls remotes or local action checking the action name and
|
72
|
+
# @local_actions. Optional arguments can be specified as a hash
|
73
|
+
#
|
74
|
+
# @param [String] parameters arguments passed to the script
|
75
|
+
# @param [Number, String] id action identifier
|
76
|
+
# @param [String] host hostname where the action is going to be executed
|
77
|
+
# @param [String, Symbol] aname name of the action
|
78
|
+
# @param [Hash] ops extra options for the command
|
79
|
+
# @option ops [String] :stdin text to be writen to stdin
|
80
|
+
# @option ops [String] :script_name default script name for the action,
|
81
|
+
# action name is used by defaults
|
82
|
+
# @option ops [Bool] :respond if defined will send result to ONE core
|
83
|
+
# @option ops [Bool] :base64 encode the information sent to ONE core
|
84
|
+
def do_action(parameters, id, host, aname, ops={})
|
85
|
+
options={
|
86
|
+
:stdin => nil,
|
87
|
+
:script_name => nil,
|
88
|
+
:respond => true,
|
89
|
+
:ssh_stream => nil,
|
90
|
+
:base64 => false
|
91
|
+
}.merge(ops)
|
92
|
+
|
93
|
+
params = parameters + " #{id} #{host}"
|
94
|
+
command = action_command_line(aname, params, options[:script_name])
|
95
|
+
|
96
|
+
if action_is_local?(aname)
|
97
|
+
execution = LocalCommand.run(command, log_method(id), Base64::encode64(options[:stdin].to_s.gsub("\n","")))
|
98
|
+
elsif options[:ssh_stream]
|
99
|
+
if options[:stdin]
|
100
|
+
cmdin = "cat << EOT | #{command}"
|
101
|
+
stdin = "#{options[:stdin]}\nEOT\n"
|
102
|
+
else
|
103
|
+
cmdin = command
|
104
|
+
stdin = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
execution = options[:ssh_stream].run(cmdin, stdin, command)
|
108
|
+
|
109
|
+
else
|
110
|
+
execution = RemotesCommand.run(command,
|
111
|
+
host,
|
112
|
+
@remote_scripts_base_path,
|
113
|
+
log_method(id),
|
114
|
+
options[:stdin],
|
115
|
+
@retries)
|
116
|
+
end
|
117
|
+
|
118
|
+
result, info = get_info_from_execution(execution)
|
119
|
+
|
120
|
+
if options[:respond]
|
121
|
+
info = Base64::encode64(info).strip.delete("\n") if options[:base64]
|
122
|
+
send_message(aname, result, id, info)
|
123
|
+
end
|
124
|
+
|
125
|
+
[result, info]
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Start the driver. Reads from STDIN and executes methods associated with
|
130
|
+
# the messages
|
131
|
+
def start_driver
|
132
|
+
loop_thread = Thread.new { loop }
|
133
|
+
start_listener
|
134
|
+
loop_thread.kill
|
135
|
+
end
|
136
|
+
|
137
|
+
# This function parses a string with this form:
|
138
|
+
#
|
139
|
+
# 'deploy,shutdown,poll=poll_ganglia, cancel '
|
140
|
+
#
|
141
|
+
# and returns a hash:
|
142
|
+
#
|
143
|
+
# {"POLL"=>"poll_ganglia", "DEPLOY"=>nil, "SHUTDOWN"=>nil,
|
144
|
+
# "CANCEL"=>nil}
|
145
|
+
#
|
146
|
+
# @param [String] str imput string to parse
|
147
|
+
# @return [Hash] parsed actions
|
148
|
+
def self.parse_actions_list(str)
|
149
|
+
actions=Hash.new
|
150
|
+
str_splitted=str.split(/\s*,\s*/).map {|s| s.strip }
|
151
|
+
|
152
|
+
str_splitted.each do |a|
|
153
|
+
m=a.match(/([^=]+)(=(.*))?/)
|
154
|
+
next if !m
|
155
|
+
|
156
|
+
action=m[1].upcase
|
157
|
+
|
158
|
+
if m[2]
|
159
|
+
script=m[3]
|
160
|
+
script.strip! if script
|
161
|
+
else
|
162
|
+
script=nil
|
163
|
+
end
|
164
|
+
|
165
|
+
actions[action]=script
|
166
|
+
end
|
167
|
+
|
168
|
+
actions
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def init
|
174
|
+
send_message("INIT",RESULT[:success])
|
175
|
+
end
|
176
|
+
|
177
|
+
def loop
|
178
|
+
while true
|
179
|
+
exit(-1) if STDIN.eof?
|
180
|
+
|
181
|
+
str=STDIN.gets
|
182
|
+
next if !str
|
183
|
+
|
184
|
+
args = str.split(/\s+/)
|
185
|
+
next if args.length == 0
|
186
|
+
|
187
|
+
if args.first.empty?
|
188
|
+
STDERR.puts "Malformed message: #{str.inspect}"
|
189
|
+
next
|
190
|
+
end
|
191
|
+
|
192
|
+
action = args.shift.upcase.to_sym
|
193
|
+
|
194
|
+
if (args.length == 0) || (!args[0])
|
195
|
+
action_id = 0
|
196
|
+
else
|
197
|
+
action_id = args[0].to_i
|
198
|
+
end
|
199
|
+
|
200
|
+
if action == :DRIVER_CANCEL
|
201
|
+
cancel_action(action_id)
|
202
|
+
log(action_id,"Driver command for #{action_id} cancelled")
|
203
|
+
else
|
204
|
+
trigger_action(action,action_id,*args)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
################################################################
|
211
|
+
################################################################
|
212
|
+
|
213
|
+
if __FILE__ == $0
|
214
|
+
|
215
|
+
class SampleDriver < OpenNebulaDriver
|
216
|
+
def initialize
|
217
|
+
super(15,true)
|
218
|
+
|
219
|
+
register_action(:SLEEP,method("my_sleep"))
|
220
|
+
end
|
221
|
+
|
222
|
+
def my_sleep(num, timeout)
|
223
|
+
log(num,"Sleeping #{timeout} seconds")
|
224
|
+
sleep(timeout.to_i)
|
225
|
+
log(num,"Done with #{num}")
|
226
|
+
|
227
|
+
send_message("SLEEP",RESULT[:success],num.to_s)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
sd = SampleDriver.new
|
232
|
+
sd.start_driver
|
233
|
+
end
|