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