one_vmm_exec.rb
| 1 |
#!/usr/bin/env ruby
|
|---|---|
| 2 |
|
| 3 |
# -------------------------------------------------------------------------- #
|
| 4 |
# Copyright 2002-2015, OpenNebula Project (OpenNebula.org), C12G Labs #
|
| 5 |
# #
|
| 6 |
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
| 7 |
# not use this file except in compliance with the License. You may obtain #
|
| 8 |
# a copy of the License at #
|
| 9 |
# #
|
| 10 |
# http://www.apache.org/licenses/LICENSE-2.0 #
|
| 11 |
# #
|
| 12 |
# Unless required by applicable law or agreed to in writing, software #
|
| 13 |
# distributed under the License is distributed on an "AS IS" BASIS, #
|
| 14 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
| 15 |
# See the License for the specific language governing permissions and #
|
| 16 |
# limitations under the License. #
|
| 17 |
#--------------------------------------------------------------------------- #
|
| 18 |
|
| 19 |
# Set up the environment for the driver
|
| 20 |
|
| 21 |
ONE_LOCATION = ENV["ONE_LOCATION"] |
| 22 |
|
| 23 |
if !ONE_LOCATION |
| 24 |
RUBY_LIB_LOCATION = "/usr/lib/one/ruby" |
| 25 |
MAD_LOCATION = "/usr/lib/one/mads" |
| 26 |
ETC_LOCATION = "/etc/one/" |
| 27 |
else
|
| 28 |
RUBY_LIB_LOCATION = ONE_LOCATION + "/lib/ruby" |
| 29 |
MAD_LOCATION = ONE_LOCATION + "/lib/mads" |
| 30 |
ETC_LOCATION = ONE_LOCATION + "/etc/" |
| 31 |
end
|
| 32 |
|
| 33 |
$: << RUBY_LIB_LOCATION |
| 34 |
$: << MAD_LOCATION |
| 35 |
|
| 36 |
require "VirtualMachineDriver"
|
| 37 |
require 'one_vnm'
|
| 38 |
require 'one_tm'
|
| 39 |
require 'getoptlong'
|
| 40 |
require 'ssh_stream'
|
| 41 |
require 'rexml/document'
|
| 42 |
|
| 43 |
require 'pp'
|
| 44 |
|
| 45 |
class VmmAction |
| 46 |
# List of xpaths required by the VNM driver actions
|
| 47 |
XPATH_LIST = %w(ID DEPLOY_ID TEMPLATE/NIC HISTORY_RECORDS/HISTORY/HOSTNAME) |
| 48 |
|
| 49 |
attr_reader :data
|
| 50 |
|
| 51 |
# Initialize a VmmAction object
|
| 52 |
# @param[OpenNebula::ExecDriver] Driver to be used for the actions
|
| 53 |
# @param[String] Id of the VM
|
| 54 |
# @param[String] name of the actions as described in the VMM protocol
|
| 55 |
# @param[xml_data] data sent from OpenNebula core
|
| 56 |
def initialize(driver, id, action, xml_data) |
| 57 |
# Initialize object with xml data
|
| 58 |
@vmm = driver
|
| 59 |
@id = id
|
| 60 |
|
| 61 |
@main_action = action
|
| 62 |
@xml_data = @vmm.decode(xml_data) |
| 63 |
|
| 64 |
@data = Hash.new |
| 65 |
|
| 66 |
get_data(:host)
|
| 67 |
get_data(:net_drv)
|
| 68 |
get_data(:deploy_id)
|
| 69 |
get_data(:checkpoint_file)
|
| 70 |
|
| 71 |
get_data(:local_dfile, :LOCAL_DEPLOYMENT_FILE) |
| 72 |
get_data(:remote_dfile, :REMOTE_DEPLOYMENT_FILE) |
| 73 |
|
| 74 |
# For migration
|
| 75 |
get_data(:dest_host, :MIGR_HOST) |
| 76 |
get_data(:dest_driver, :MIGR_NET_DRV) |
| 77 |
|
| 78 |
# For disk hotplugging
|
| 79 |
get_data(:disk_target_path)
|
| 80 |
get_data(:tm_command)
|
| 81 |
|
| 82 |
# VM template
|
| 83 |
vm_template = @xml_data.elements['VM'].to_s |
| 84 |
@data[:vm] = Base64.encode64(vm_template).delete("\n") |
| 85 |
|
| 86 |
# VM data for VNM
|
| 87 |
vm_template_xml = REXML::Document.new(vm_template).root |
| 88 |
vm_vnm_xml = REXML::Document.new('<VM></VM>').root |
| 89 |
|
| 90 |
XPATH_LIST.each do |xpath| |
| 91 |
elements = vm_template_xml.elements.each(xpath) do |element|
|
| 92 |
add_element_to_path(vm_vnm_xml, element, xpath) |
| 93 |
end
|
| 94 |
end
|
| 95 |
|
| 96 |
# Initialize streams and vnm
|
| 97 |
@ssh_src = @vmm.get_ssh_stream(action, @data[:host], @id) |
| 98 |
@vnm_src = VirtualNetworkDriver.new(@data[:net_drv], |
| 99 |
:local_actions => @vmm.options[:local_actions], |
| 100 |
:message => vm_vnm_xml.to_s,
|
| 101 |
:ssh_stream => @ssh_src) |
| 102 |
|
| 103 |
if @data[:dest_host] and !@data[:dest_host].empty? |
| 104 |
@ssh_dst = @vmm.get_ssh_stream(action, @data[:dest_host], @id) |
| 105 |
@vnm_dst = VirtualNetworkDriver.new(@data[:dest_driver], |
| 106 |
:local_actions => @vmm.options[:local_actions], |
| 107 |
:message => vm_vnm_xml.to_s,
|
| 108 |
:ssh_stream => @ssh_dst) |
| 109 |
end
|
| 110 |
|
| 111 |
@tm = TransferManagerDriver.new(nil) |
| 112 |
end
|
| 113 |
|
| 114 |
#Execute a set of steps defined with
|
| 115 |
# - :driver :vmm or :vnm to execute the step
|
| 116 |
# - :action for the step
|
| 117 |
# - :parameters command line paremeters for the action
|
| 118 |
# - :destination use next host
|
| 119 |
# - :fail_action steps to be executed if steps fail
|
| 120 |
# - :stdin for the action
|
| 121 |
# @param [Array] of steps
|
| 122 |
def run(steps, info_on_success = nil) |
| 123 |
result = execute_steps(steps) |
| 124 |
|
| 125 |
@ssh_src.close if @ssh_src |
| 126 |
@ssh_dst.close if @ssh_dst |
| 127 |
|
| 128 |
#Prepare the info for the OpenNebula core
|
| 129 |
if DriverExecHelper.failed?(result) |
| 130 |
info = @data[:failed_info] |
| 131 |
else
|
| 132 |
info = @data["#{@main_action.to_s}_info".to_sym] |
| 133 |
end
|
| 134 |
|
| 135 |
@vmm.send_message(VirtualMachineDriver::ACTION[@main_action], |
| 136 |
result, @id, info)
|
| 137 |
end
|
| 138 |
|
| 139 |
private |
| 140 |
|
| 141 |
DRIVER_NAMES = {
|
| 142 |
:vmm => "virtualization driver", |
| 143 |
:vnm => "network driver", |
| 144 |
:tm => "transfer manager driver" |
| 145 |
} |
| 146 |
|
| 147 |
# Executes a set of steps. If one step fails any recover action is performed
|
| 148 |
# and the step execution breaks.
|
| 149 |
# @param [Array] array of steps to be executed
|
| 150 |
# @return [String, Hash] "SUCCESS/FAILURE" for the step set, and
|
| 151 |
# information associated to each step (by :<action>_info). In case of
|
| 152 |
# failure information is also in [:failed_info]
|
| 153 |
def execute_steps(steps) |
| 154 |
result = DriverExecHelper.const_get(:RESULT)[:failure] |
| 155 |
|
| 156 |
steps.each do |step|
|
| 157 |
# Execute Step
|
| 158 |
case step[:driver] |
| 159 |
when :vmm |
| 160 |
if step[:destination] |
| 161 |
host = @data[:dest_host] |
| 162 |
ssh = @ssh_dst
|
| 163 |
else
|
| 164 |
host = @data[:host] |
| 165 |
ssh = @ssh_src
|
| 166 |
end
|
| 167 |
|
| 168 |
result, info = @vmm.do_action(get_parameters(step[:parameters]), |
| 169 |
@id,
|
| 170 |
host, |
| 171 |
step[:action],
|
| 172 |
:ssh_stream => ssh,
|
| 173 |
:respond => false, |
| 174 |
:stdin => step[:stdin]) |
| 175 |
when :vnm |
| 176 |
if step[:destination] |
| 177 |
vnm = @vnm_dst
|
| 178 |
else
|
| 179 |
vnm = @vnm_src
|
| 180 |
end
|
| 181 |
|
| 182 |
result, info = vnm.do_action(@id, step[:action], |
| 183 |
:parameters => get_parameters(step[:parameters])) |
| 184 |
when :tm |
| 185 |
result, info = @tm.do_transfer_action(@id, step[:parameters]) |
| 186 |
|
| 187 |
else
|
| 188 |
result = DriverExecHelper.const_get(:RESULT)[:failure] |
| 189 |
info = "No driver in #{step[:action]}"
|
| 190 |
end
|
| 191 |
|
| 192 |
# Save the step info
|
| 193 |
@data["#{step[:action]}_info".to_sym] = info.strip |
| 194 |
|
| 195 |
# Roll back steps, store failed info and break steps
|
| 196 |
if DriverExecHelper.failed?(result) |
| 197 |
execute_steps(step[:fail_actions]) if step[:fail_actions] |
| 198 |
@data[:failed_info] = info |
| 199 |
|
| 200 |
@vmm.log(@id, |
| 201 |
"Failed to execute #{DRIVER_NAMES[step[:driver]]} " \
|
| 202 |
"operation: #{step[:action]}.")
|
| 203 |
|
| 204 |
if step[:no_fail] |
| 205 |
result = DriverExecHelper::RESULT[:success] |
| 206 |
else
|
| 207 |
break
|
| 208 |
end
|
| 209 |
else
|
| 210 |
@vmm.log(@id, |
| 211 |
"Successfully execute #{DRIVER_NAMES[step[:driver]]} " \
|
| 212 |
"operation: #{step[:action]}.")
|
| 213 |
end
|
| 214 |
end
|
| 215 |
|
| 216 |
return result
|
| 217 |
end
|
| 218 |
|
| 219 |
# Prepare the parameters for the action step generating a blank separated
|
| 220 |
# list of command arguments
|
| 221 |
# @param [Hash] an action step
|
| 222 |
def get_parameters(step_params) |
| 223 |
parameters = step_params || [] |
| 224 |
|
| 225 |
parameters.map do |param|
|
| 226 |
if Symbol===param |
| 227 |
"\'#{@data[param].to_s}\'"
|
| 228 |
else
|
| 229 |
"\'#{param}\'"
|
| 230 |
end
|
| 231 |
end.join(' ') |
| 232 |
end
|
| 233 |
|
| 234 |
# Extracts data from the XML argument of the VMM action
|
| 235 |
# @param [Symbol] corresponding to a XML element
|
| 236 |
# @param [String] an xpath for the XML element
|
| 237 |
# @return [String] the element value
|
| 238 |
def get_data(name, xml_path=nil) |
| 239 |
if xml_path
|
| 240 |
path=xml_path.to_s |
| 241 |
else
|
| 242 |
path=name.to_s.upcase |
| 243 |
end
|
| 244 |
|
| 245 |
if (elem = @xml_data.elements[path]) |
| 246 |
@data[name]=elem.text
|
| 247 |
end
|
| 248 |
end
|
| 249 |
|
| 250 |
# Adds a REXML node to a specific xpath
|
| 251 |
#
|
| 252 |
# @param [REXML::Element] xml document to add to
|
| 253 |
# @param [REXML::Element] element to add
|
| 254 |
# @param [String] path where the element is inserted in the xml document
|
| 255 |
# @return [REXML::Element]
|
| 256 |
def add_element_to_path(xml, element, path) |
| 257 |
root = xml |
| 258 |
path.split('/')[0..-2].each do |path_element| |
| 259 |
xml = xml.add_element(path_element) if path_element
|
| 260 |
end
|
| 261 |
xml.add_element(element) |
| 262 |
root |
| 263 |
end
|
| 264 |
end
|
| 265 |
|
| 266 |
|
| 267 |
# The main class for the Sh driver
|
| 268 |
class ExecDriver < VirtualMachineDriver |
| 269 |
attr_reader :options
|
| 270 |
|
| 271 |
# Initializes the VMM driver
|
| 272 |
# @param [String] hypervisor name identifies the plugin
|
| 273 |
# @param [OpenNebulaDriver::options]
|
| 274 |
def initialize(hypervisor, options={}) |
| 275 |
@options={
|
| 276 |
:threaded => true |
| 277 |
}.merge!(options) |
| 278 |
|
| 279 |
if options[:shell] |
| 280 |
@shell=options[:shell] |
| 281 |
else
|
| 282 |
@shell='bash' |
| 283 |
end
|
| 284 |
|
| 285 |
super("vmm/#{hypervisor}", @options) |
| 286 |
|
| 287 |
@hypervisor = hypervisor
|
| 288 |
end
|
| 289 |
|
| 290 |
# Creates an SshStream to execute commands on the target host
|
| 291 |
# @param[String] the hostname of the host
|
| 292 |
# @param[String] id of the VM to log messages
|
| 293 |
# @return [SshStreamCommand]
|
| 294 |
def get_ssh_stream(aname, host, id) |
| 295 |
SshStreamCommand.new(host,
|
| 296 |
@remote_scripts_base_path,
|
| 297 |
log_method(id), nil, @shell) |
| 298 |
end
|
| 299 |
|
| 300 |
#---------------------------------------------------------------------------
|
| 301 |
# Virtual Machine Manager Protocol Actions
|
| 302 |
#---------------------------------------------------------------------------
|
| 303 |
#
|
| 304 |
# DEPLOY action, sends the deployment file to remote host
|
| 305 |
#
|
| 306 |
def deploy(id, drv_message) |
| 307 |
action = VmmAction.new(self, id, :deploy, drv_message) |
| 308 |
|
| 309 |
# ----------------------------------------------------------------------
|
| 310 |
# Initialization of deployment data
|
| 311 |
# ----------------------------------------------------------------------
|
| 312 |
local_dfile=action.data[:local_dfile]
|
| 313 |
|
| 314 |
if !local_dfile || File.zero?(local_dfile) |
| 315 |
send_message(ACTION[:deploy],RESULT[:failure],id, |
| 316 |
"Cannot open deployment file #{local_dfile}")
|
| 317 |
return
|
| 318 |
end
|
| 319 |
|
| 320 |
domain = File.read(local_dfile)
|
| 321 |
|
| 322 |
if action_is_local?(:deploy) |
| 323 |
dfile = action.data[:local_dfile]
|
| 324 |
else
|
| 325 |
dfile = action.data[:remote_dfile]
|
| 326 |
end
|
| 327 |
|
| 328 |
# ----------------------------------------------------------------------
|
| 329 |
# Deployment Steps
|
| 330 |
# ----------------------------------------------------------------------
|
| 331 |
|
| 332 |
steps=[ |
| 333 |
# Execute pre-boot networking setup
|
| 334 |
{
|
| 335 |
:driver => :vnm, |
| 336 |
:action => :pre |
| 337 |
}, |
| 338 |
# Boot the Virtual Machine
|
| 339 |
{
|
| 340 |
:driver => :vmm, |
| 341 |
:action => :deploy, |
| 342 |
:parameters => [dfile, :host], |
| 343 |
:stdin => domain,
|
| 344 |
}, |
| 345 |
# Execute post-boot networking setup
|
| 346 |
{
|
| 347 |
:driver => :vnm, |
| 348 |
:action => :post, |
| 349 |
:parameters => [:deploy_info], |
| 350 |
:fail_actions => [
|
| 351 |
{
|
| 352 |
:driver => :vmm, |
| 353 |
:action => :cancel, |
| 354 |
:parameters => [:deploy_info, :host] |
| 355 |
} |
| 356 |
] |
| 357 |
} |
| 358 |
] |
| 359 |
|
| 360 |
action.run(steps) |
| 361 |
end
|
| 362 |
|
| 363 |
#
|
| 364 |
# SHUTDOWN action, graceful shutdown and network clean up
|
| 365 |
#
|
| 366 |
def shutdown(id, drv_message) |
| 367 |
|
| 368 |
action = VmmAction.new(self, id, :shutdown, drv_message) |
| 369 |
|
| 370 |
steps=[ |
| 371 |
# Shutdown the Virtual Machine
|
| 372 |
{
|
| 373 |
:driver => :vmm, |
| 374 |
:action => :shutdown, |
| 375 |
:parameters => [:deploy_id, :host] |
| 376 |
}, |
| 377 |
# Execute networking clean up operations
|
| 378 |
{
|
| 379 |
:driver => :vnm, |
| 380 |
:action => :clean |
| 381 |
} |
| 382 |
] |
| 383 |
|
| 384 |
action.run(steps) |
| 385 |
end
|
| 386 |
|
| 387 |
#
|
| 388 |
# CANCEL action, destroys a VM and network clean up
|
| 389 |
#
|
| 390 |
def cancel(id, drv_message) |
| 391 |
action = VmmAction.new(self, id, :cancel, drv_message) |
| 392 |
|
| 393 |
steps=[ |
| 394 |
# Cancel the Virtual Machine
|
| 395 |
{
|
| 396 |
:driver => :vmm, |
| 397 |
:action => :cancel, |
| 398 |
:parameters => [:deploy_id, :host] |
| 399 |
}, |
| 400 |
# Execute networking clean up operations
|
| 401 |
{
|
| 402 |
:driver => :vnm, |
| 403 |
:action => :clean |
| 404 |
} |
| 405 |
] |
| 406 |
|
| 407 |
action.run(steps) |
| 408 |
end
|
| 409 |
|
| 410 |
#
|
| 411 |
# SAVE action, stops the VM and saves its state, network is cleaned up
|
| 412 |
#
|
| 413 |
def save(id, drv_message) |
| 414 |
action = VmmAction.new(self, id, :save, drv_message) |
| 415 |
|
| 416 |
steps=[ |
| 417 |
# Save the Virtual Machine state
|
| 418 |
{
|
| 419 |
:driver => :vmm, |
| 420 |
:action => :save, |
| 421 |
:parameters => [:deploy_id, :checkpoint_file, :host] |
| 422 |
}, |
| 423 |
# Execute networking clean up operations
|
| 424 |
{
|
| 425 |
:driver => :vnm, |
| 426 |
:action => :clean |
| 427 |
} |
| 428 |
] |
| 429 |
|
| 430 |
action.run(steps) |
| 431 |
end
|
| 432 |
|
| 433 |
#
|
| 434 |
# RESTORE action, restore a VM from a previous state, and restores network
|
| 435 |
#
|
| 436 |
def restore(id, drv_message) |
| 437 |
action=VmmAction.new(self, id, :restore, drv_message) |
| 438 |
|
| 439 |
steps=[ |
| 440 |
# Execute pre-boot networking setup
|
| 441 |
{
|
| 442 |
:driver => :vnm, |
| 443 |
:action => :pre |
| 444 |
}, |
| 445 |
# Restore the Virtual Machine from checkpoint
|
| 446 |
{
|
| 447 |
:driver => :vmm, |
| 448 |
:action => :restore, |
| 449 |
:parameters => [:checkpoint_file, :host, :deploy_id] |
| 450 |
}, |
| 451 |
# Execute post-boot networking setup
|
| 452 |
{
|
| 453 |
:driver => :vnm, |
| 454 |
:action => :post, |
| 455 |
:parameters => [:deploy_id], |
| 456 |
:fail_actions => [
|
| 457 |
{
|
| 458 |
:driver => :vmm, |
| 459 |
:action => :cancel, |
| 460 |
:parameters => [:deploy_id, :host] |
| 461 |
} |
| 462 |
], |
| 463 |
} |
| 464 |
] |
| 465 |
|
| 466 |
action.run(steps) |
| 467 |
end
|
| 468 |
|
| 469 |
#
|
| 470 |
# MIGRATE (live) action, migrates a VM to another host creating network
|
| 471 |
#
|
| 472 |
def migrate(id, drv_message) |
| 473 |
action = VmmAction.new(self, id, :migrate, drv_message) |
| 474 |
pre = "PRE"
|
| 475 |
post = "POST"
|
| 476 |
|
| 477 |
pre << action.data[:tm_command] << " " << action.data[:vm] |
| 478 |
post << action.data[:tm_command] << " " << action.data[:vm] |
| 479 |
|
| 480 |
steps=[ |
| 481 |
# Execute a pre-migrate TM setup
|
| 482 |
{
|
| 483 |
:driver => :tm, |
| 484 |
:action => :tm_premigrate, |
| 485 |
:parameters => pre.split
|
| 486 |
}, |
| 487 |
# Execute pre-boot networking setup on migrating host
|
| 488 |
{
|
| 489 |
:driver => :vnm, |
| 490 |
:action => :pre, |
| 491 |
:destination => true |
| 492 |
}, |
| 493 |
# Migrate the Virtual Machine
|
| 494 |
{
|
| 495 |
:driver => :vmm, |
| 496 |
:action => :migrate, |
| 497 |
:parameters => [:deploy_id, :dest_host, :host] |
| 498 |
}, |
| 499 |
# Execute networking clean up operations
|
| 500 |
# NOTE: VM is now in the new host. If we fail from now on, oned will
|
| 501 |
# assume that the VM is in the previous host but it is in fact
|
| 502 |
# migrated. Log errors will be shown in vm.log
|
| 503 |
{
|
| 504 |
:driver => :vnm, |
| 505 |
:action => :clean, |
| 506 |
:no_fail => true |
| 507 |
}, |
| 508 |
# Execute post-boot networking setup on migrating host
|
| 509 |
{
|
| 510 |
:driver => :vnm, |
| 511 |
:action => :post, |
| 512 |
:parameters => [:deploy_id], |
| 513 |
:destination => :true, |
| 514 |
:no_fail => true |
| 515 |
}, |
| 516 |
{
|
| 517 |
:driver => :tm, |
| 518 |
:action => :tm_postmigrate, |
| 519 |
:parameters => post.split,
|
| 520 |
:no_fail => true |
| 521 |
}, |
| 522 |
] |
| 523 |
|
| 524 |
action.run(steps) |
| 525 |
end
|
| 526 |
|
| 527 |
#
|
| 528 |
# POLL action, gets information of a VM
|
| 529 |
#
|
| 530 |
def poll(id, drv_message) |
| 531 |
data = decode(drv_message) |
| 532 |
host = data.elements['HOST'].text
|
| 533 |
deploy_id = data.elements['DEPLOY_ID'].text
|
| 534 |
|
| 535 |
do_action("#{deploy_id} #{host}", id, host, ACTION[:poll]) |
| 536 |
end
|
| 537 |
|
| 538 |
#
|
| 539 |
# REBOOT action, reboots a running VM
|
| 540 |
#
|
| 541 |
def reboot(id, drv_message) |
| 542 |
data = decode(drv_message) |
| 543 |
host = data.elements['HOST'].text
|
| 544 |
deploy_id = data.elements['DEPLOY_ID'].text
|
| 545 |
|
| 546 |
do_action("#{deploy_id} #{host}", id, host, ACTION[:reboot]) |
| 547 |
end
|
| 548 |
|
| 549 |
#
|
| 550 |
# RESET action, resets a running VM
|
| 551 |
#
|
| 552 |
def reset(id, drv_message) |
| 553 |
data = decode(drv_message) |
| 554 |
host = data.elements['HOST'].text
|
| 555 |
deploy_id = data.elements['DEPLOY_ID'].text
|
| 556 |
|
| 557 |
do_action("#{deploy_id} #{host}", id, host, ACTION[:reset]) |
| 558 |
end
|
| 559 |
|
| 560 |
#
|
| 561 |
# ATTACHDISK action, attaches a disk to a running VM
|
| 562 |
#
|
| 563 |
def attach_disk(id, drv_message) |
| 564 |
action = ACTION[:attach_disk] |
| 565 |
xml_data = decode(drv_message) |
| 566 |
|
| 567 |
tm_command = ensure_xpath(xml_data, id, action, 'TM_COMMAND') || return |
| 568 |
tm_rollback= xml_data.elements['TM_COMMAND_ROLLBACK'].text.strip
|
| 569 |
|
| 570 |
target_xpath = "VM/TEMPLATE/DISK[ATTACH='YES']/TARGET"
|
| 571 |
target = ensure_xpath(xml_data, id, action, target_xpath) || return
|
| 572 |
|
| 573 |
target_index = target.downcase[-1..-1].unpack('c').first - 97 |
| 574 |
|
| 575 |
|
| 576 |
|
| 577 |
action = VmmAction.new(self, id, :attach_disk, drv_message) |
| 578 |
|
| 579 |
# Bug #1355, argument character limitation in ESX
|
| 580 |
# Message not used in vmware anyway
|
| 581 |
if @hypervisor == "vmware" |
| 582 |
drv_message = "drv_message"
|
| 583 |
end
|
| 584 |
|
| 585 |
steps = [ |
| 586 |
# Perform a PROLOG on the disk
|
| 587 |
{
|
| 588 |
:driver => :tm, |
| 589 |
:action => :tm_attach, |
| 590 |
:parameters => tm_command.split
|
| 591 |
}, |
| 592 |
# Run the attach vmm script
|
| 593 |
{
|
| 594 |
:driver => :vmm, |
| 595 |
:action => :attach_disk, |
| 596 |
:parameters => [
|
| 597 |
:deploy_id,
|
| 598 |
:disk_target_path,
|
| 599 |
target, |
| 600 |
target_index, |
| 601 |
drv_message |
| 602 |
], |
| 603 |
:fail_actions => [
|
| 604 |
{
|
| 605 |
:driver => :tm, |
| 606 |
:action => :tm_detach, |
| 607 |
:parameters => tm_rollback.split
|
| 608 |
} |
| 609 |
] |
| 610 |
} |
| 611 |
] |
| 612 |
|
| 613 |
action.run(steps) |
| 614 |
end
|
| 615 |
|
| 616 |
#
|
| 617 |
# DETACHDISK action, attaches a disk to a running VM
|
| 618 |
#
|
| 619 |
def detach_disk(id, drv_message) |
| 620 |
action = ACTION[:detach_disk] |
| 621 |
xml_data = decode(drv_message) |
| 622 |
|
| 623 |
tm_command = ensure_xpath(xml_data, id, action, 'TM_COMMAND') || return |
| 624 |
|
| 625 |
target_xpath = "VM/TEMPLATE/DISK[ATTACH='YES']/TARGET"
|
| 626 |
target = ensure_xpath(xml_data, id, action, target_xpath) || return
|
| 627 |
|
| 628 |
target_index = target.downcase[-1..-1].unpack('c').first - 97 |
| 629 |
|
| 630 |
action = VmmAction.new(self, id, :detach_disk, drv_message) |
| 631 |
|
| 632 |
steps = [ |
| 633 |
# Run the detach vmm script
|
| 634 |
{
|
| 635 |
:driver => :vmm, |
| 636 |
:action => :detach_disk, |
| 637 |
:parameters => [
|
| 638 |
:deploy_id,
|
| 639 |
:disk_target_path,
|
| 640 |
target, |
| 641 |
target_index |
| 642 |
] |
| 643 |
}, |
| 644 |
# Perform an EPILOG on the disk
|
| 645 |
{
|
| 646 |
:driver => :tm, |
| 647 |
:action => :tm_detach, |
| 648 |
:parameters => tm_command.split
|
| 649 |
} |
| 650 |
] |
| 651 |
|
| 652 |
action.run(steps) |
| 653 |
end
|
| 654 |
|
| 655 |
#
|
| 656 |
# SNAPSHOTCREATE action, creates a new system snapshot
|
| 657 |
#
|
| 658 |
def snapshot_create(id, drv_message) |
| 659 |
action = ACTION[:snapshot_create] |
| 660 |
xml_data = decode(drv_message) |
| 661 |
|
| 662 |
host = xml_data.elements['HOST'].text
|
| 663 |
deploy_id = xml_data.elements['DEPLOY_ID'].text
|
| 664 |
|
| 665 |
snap_id_xpath = "VM/TEMPLATE/SNAPSHOT[ACTIVE='YES']/SNAPSHOT_ID"
|
| 666 |
snap_id = xml_data.elements[snap_id_xpath].text.to_i |
| 667 |
|
| 668 |
do_action("#{deploy_id} #{snap_id}",
|
| 669 |
id, |
| 670 |
host, |
| 671 |
ACTION[:snapshot_create], |
| 672 |
:script_name => "snapshot_create") |
| 673 |
end
|
| 674 |
|
| 675 |
#
|
| 676 |
# SNAPSHOTREVERT action, reverts to a system snapshot
|
| 677 |
#
|
| 678 |
def snapshot_revert(id, drv_message) |
| 679 |
action = ACTION[:snapshot_revert] |
| 680 |
xml_data = decode(drv_message) |
| 681 |
|
| 682 |
host = xml_data.elements['HOST'].text
|
| 683 |
deploy_id = xml_data.elements['DEPLOY_ID'].text
|
| 684 |
|
| 685 |
snap_id_xpath = "VM/TEMPLATE/SNAPSHOT[ACTIVE='YES']/HYPERVISOR_ID"
|
| 686 |
snapshot_name = xml_data.elements[snap_id_xpath].text |
| 687 |
|
| 688 |
do_action("#{deploy_id} #{snapshot_name}",
|
| 689 |
id, |
| 690 |
host, |
| 691 |
ACTION[:snapshot_revert], |
| 692 |
:script_name => "snapshot_revert") |
| 693 |
end
|
| 694 |
|
| 695 |
#
|
| 696 |
# SNAPSHOTDELETE action, deletes a system snapshot
|
| 697 |
#
|
| 698 |
def snapshot_delete(id, drv_message) |
| 699 |
action = ACTION[:snapshot_delete] |
| 700 |
xml_data = decode(drv_message) |
| 701 |
|
| 702 |
host = xml_data.elements['HOST'].text
|
| 703 |
deploy_id = xml_data.elements['DEPLOY_ID'].text
|
| 704 |
|
| 705 |
snap_id_xpath = "VM/TEMPLATE/SNAPSHOT[ACTIVE='YES']/HYPERVISOR_ID"
|
| 706 |
snapshot_name = xml_data.elements[snap_id_xpath].text |
| 707 |
|
| 708 |
do_action("#{deploy_id} #{snapshot_name}",
|
| 709 |
id, |
| 710 |
host, |
| 711 |
ACTION[:snapshot_delete], |
| 712 |
:script_name => "snapshot_delete") |
| 713 |
end
|
| 714 |
|
| 715 |
#
|
| 716 |
# CLEANUP action, frees resources allocated in a host: VM and disk images
|
| 717 |
#
|
| 718 |
def cleanup(id, drv_message) |
| 719 |
aname = ACTION[:cleanup] |
| 720 |
xml_data = decode(drv_message) |
| 721 |
|
| 722 |
tm_command = xml_data.elements['TM_COMMAND'].text
|
| 723 |
mhost = xml_data.elements['MIGR_HOST'].text
|
| 724 |
deploy_id = xml_data.elements['DEPLOY_ID'].text
|
| 725 |
|
| 726 |
action = VmmAction.new(self, id, :cleanup, drv_message) |
| 727 |
steps = Array.new
|
| 728 |
|
| 729 |
# Cancel the VM at host (only if we have a valid deploy-id)
|
| 730 |
if deploy_id && !deploy_id.empty?
|
| 731 |
steps << |
| 732 |
{
|
| 733 |
:driver => :vmm, |
| 734 |
:action => :cancel, |
| 735 |
:parameters => [:deploy_id, :host], |
| 736 |
:no_fail => true |
| 737 |
} |
| 738 |
steps << |
| 739 |
{
|
| 740 |
:driver => :vnm, |
| 741 |
:action => :clean, |
| 742 |
:no_fail => true |
| 743 |
} |
| 744 |
end
|
| 745 |
|
| 746 |
# Cancel the VM at the previous host (in case of migration)
|
| 747 |
if mhost && !mhost.empty?
|
| 748 |
steps << |
| 749 |
{
|
| 750 |
:driver => :vmm, |
| 751 |
:action => :cancel, |
| 752 |
:parameters => [:deploy_id, :dest_host], |
| 753 |
:destination => true, |
| 754 |
:no_fail => true |
| 755 |
} |
| 756 |
steps << |
| 757 |
{
|
| 758 |
:driver => :vnm, |
| 759 |
:action => :clean, |
| 760 |
:destination => true, |
| 761 |
:no_fail => true |
| 762 |
} |
| 763 |
end
|
| 764 |
|
| 765 |
# Cleans VM disk images and directory
|
| 766 |
tm_command.each_line { |tc|
|
| 767 |
tc.strip! |
| 768 |
|
| 769 |
steps << |
| 770 |
{
|
| 771 |
:driver => :tm, |
| 772 |
:action => :tm_delete, |
| 773 |
:parameters => tc.split,
|
| 774 |
:no_fail => true |
| 775 |
} if !tc.empty?
|
| 776 |
} if tm_command
|
| 777 |
|
| 778 |
action.run(steps) |
| 779 |
end
|
| 780 |
|
| 781 |
#
|
| 782 |
# ATTACHNIC action to attach a new nic interface
|
| 783 |
#
|
| 784 |
def attach_nic(id, drv_message) |
| 785 |
xml_data = decode(drv_message) |
| 786 |
|
| 787 |
begin
|
| 788 |
source = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/BRIDGE"]
|
| 789 |
mac = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/MAC"]
|
| 790 |
nic_id = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/NIC_ID"]
|
| 791 |
|
| 792 |
source = source.text.strip |
| 793 |
mac = mac.text.strip |
| 794 |
nic_id = nic_id.text.strip |
| 795 |
rescue
|
| 796 |
send_message(action, RESULT[:failure], id, |
| 797 |
"Error in #{ACTION[:attach_nic]}, BRIDGE and MAC needed in NIC")
|
| 798 |
return
|
| 799 |
end
|
| 800 |
|
| 801 |
model = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/MODEL"]
|
| 802 |
|
| 803 |
model = model.text if !model.nil?
|
| 804 |
model = model.strip if !model.nil?
|
| 805 |
model = "-" if model.nil? |
| 806 |
|
| 807 |
|
| 808 |
net_drv = xml_data.elements["NET_DRV"]
|
| 809 |
|
| 810 |
net_drv = net_drv.text if !net_drv.nil?
|
| 811 |
net_drv = net_drv.strip if !net_drv.nil?
|
| 812 |
net_drv = "-" if net_drv.nil? |
| 813 |
|
| 814 |
action = VmmAction.new(self, id, :attach_nic, drv_message) |
| 815 |
|
| 816 |
steps=[ |
| 817 |
# Execute pre-attach networking setup
|
| 818 |
{
|
| 819 |
:driver => :vnm, |
| 820 |
:action => :pre |
| 821 |
}, |
| 822 |
# Attach the new NIC
|
| 823 |
{
|
| 824 |
:driver => :vmm, |
| 825 |
:action => :attach_nic, |
| 826 |
:parameters => [:deploy_id, mac, source, model, net_drv] |
| 827 |
}, |
| 828 |
# Execute post-boot networking setup
|
| 829 |
{
|
| 830 |
:driver => :vnm, |
| 831 |
:action => :post, |
| 832 |
:parameters => [:deploy_id, nic_id], |
| 833 |
:fail_actions => [
|
| 834 |
{
|
| 835 |
:driver => :vmm, |
| 836 |
:action => :detach_nic, |
| 837 |
:parameters => [:deploy_id, mac] |
| 838 |
} |
| 839 |
] |
| 840 |
} |
| 841 |
] |
| 842 |
|
| 843 |
action.run(steps) |
| 844 |
end
|
| 845 |
|
| 846 |
#
|
| 847 |
# DETACHNIC action to detach a nic interface
|
| 848 |
#
|
| 849 |
def detach_nic(id, drv_message) |
| 850 |
xml_data = decode(drv_message) |
| 851 |
|
| 852 |
begin
|
| 853 |
mac = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/MAC"]
|
| 854 |
nic_id = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/NIC_ID"]
|
| 855 |
|
| 856 |
mac = mac.text.strip |
| 857 |
nic_id = nic_id.text.strip |
| 858 |
rescue
|
| 859 |
send_message(action, RESULT[:failure], id, |
| 860 |
"Error in #{ACTION[:detach_nic]}, MAC needed in NIC")
|
| 861 |
return
|
| 862 |
end
|
| 863 |
|
| 864 |
action = VmmAction.new(self, id, :detach_nic, drv_message) |
| 865 |
|
| 866 |
steps=[ |
| 867 |
# Detach the NIC
|
| 868 |
{
|
| 869 |
:driver => :vmm, |
| 870 |
:action => :detach_nic, |
| 871 |
:parameters => [:deploy_id, mac] |
| 872 |
}, |
| 873 |
# Clean networking setup
|
| 874 |
{
|
| 875 |
:driver => :vnm, |
| 876 |
:action => :clean, |
| 877 |
:parameters => [nic_id]
|
| 878 |
} |
| 879 |
] |
| 880 |
|
| 881 |
action.run(steps) |
| 882 |
end
|
| 883 |
|
| 884 |
private |
| 885 |
|
| 886 |
def ensure_xpath(xml_data, id, action, xpath) |
| 887 |
begin
|
| 888 |
value = xml_data.elements[xpath].text.strip |
| 889 |
raise if value.empty?
|
| 890 |
value |
| 891 |
rescue
|
| 892 |
send_message(action, RESULT[:failure], id, |
| 893 |
"Cannot perform #{action}, expecting #{xpath}")
|
| 894 |
nil
|
| 895 |
end
|
| 896 |
end
|
| 897 |
|
| 898 |
end
|
| 899 |
|
| 900 |
################################################################################
|
| 901 |
#
|
| 902 |
# Virtual Machine Manager Execution Driver - Main Program
|
| 903 |
#
|
| 904 |
################################################################################
|
| 905 |
|
| 906 |
opts = GetoptLong.new(
|
| 907 |
[ '--retries', '-r', GetoptLong::OPTIONAL_ARGUMENT ], |
| 908 |
[ '--threads', '-t', GetoptLong::OPTIONAL_ARGUMENT ], |
| 909 |
[ '--local', '-l', GetoptLong::REQUIRED_ARGUMENT ], |
| 910 |
[ '--shell', '-s', GetoptLong::REQUIRED_ARGUMENT ], |
| 911 |
[ '--parallel', '-p', GetoptLong::NO_ARGUMENT ] |
| 912 |
) |
| 913 |
|
| 914 |
hypervisor = ''
|
| 915 |
retries = 0
|
| 916 |
threads = 15
|
| 917 |
shell = 'bash'
|
| 918 |
local_actions = {}
|
| 919 |
single_host = true
|
| 920 |
|
| 921 |
begin
|
| 922 |
opts.each do |opt, arg|
|
| 923 |
case opt
|
| 924 |
when '--retries' |
| 925 |
retries = arg.to_i |
| 926 |
when '--threads' |
| 927 |
threads = arg.to_i |
| 928 |
when '--local' |
| 929 |
local_actions=OpenNebulaDriver.parse_actions_list(arg)
|
| 930 |
when '--shell' |
| 931 |
shell = arg |
| 932 |
when '--parallel' |
| 933 |
single_host = false
|
| 934 |
end
|
| 935 |
end
|
| 936 |
rescue Exception => e |
| 937 |
exit(-1)
|
| 938 |
end
|
| 939 |
|
| 940 |
if ARGV.length >= 1 |
| 941 |
hypervisor = ARGV.shift
|
| 942 |
else
|
| 943 |
exit(-1)
|
| 944 |
end
|
| 945 |
|
| 946 |
exec_driver = ExecDriver.new(hypervisor,
|
| 947 |
:concurrency => threads,
|
| 948 |
:retries => retries,
|
| 949 |
:local_actions => local_actions,
|
| 950 |
:shell => shell,
|
| 951 |
:single_host => single_host)
|
| 952 |
|
| 953 |
exec_driver.start_driver |