Statistics
| Branch: | Tag: | Revision:

one / src / vmm_mad / exec / one_vmm_exec.rb @ 40086e5c

History | View | Annotate | Download (35.6 KB)

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(
48
        ID DEPLOY_ID
49
        TEMPLATE/NIC
50
        TEMPLATE/SECURITY_GROUP_RULE
51
        HISTORY_RECORDS/HISTORY/HOSTNAME
52
    )
53

    
54
    attr_reader :data
55

    
56
    # Initialize a VmmAction object
57
    # @param[OpenNebula::ExecDriver] Driver to be used for the actions
58
    # @param[String] Id of the VM
59
    # @param[String] name of the actions as described in the VMM protocol
60
    # @param[xml_data] data sent from OpenNebula core
61
    def initialize(driver, id, action, xml_data)
62
        # Initialize object with xml data
63
        @vmm = driver
64
        @id  = id
65

    
66
        @main_action = action
67
        @xml_data    = @vmm.decode(xml_data)
68

    
69
        @data = Hash.new
70

    
71
        get_data(:host)
72
        get_data(:net_drv)
73
        get_data(:deploy_id)
74
        get_data(:checkpoint_file)
75

    
76
        get_data(:local_dfile, :LOCAL_DEPLOYMENT_FILE)
77
        get_data(:remote_dfile, :REMOTE_DEPLOYMENT_FILE)
78

    
79
        # For migration
80
        get_data(:dest_host, :MIGR_HOST)
81
        get_data(:dest_driver, :MIGR_NET_DRV)
82

    
83
        # For disk hotplugging
84
        get_data(:disk_target_path)
85
        get_data(:tm_command)
86

    
87
        # VM template
88
        vm_template = @xml_data.elements['VM'].to_s
89
        @data[:vm]  = Base64.encode64(vm_template).delete("\n")
90

    
91
        # VM data for VNM
92
        vm_template_xml = REXML::Document.new(vm_template).root
93
        vm_vnm_xml = REXML::Document.new('<VM></VM>').root
94

    
95
        XPATH_LIST.each do |xpath|
96
            elements = vm_template_xml.elements.each(xpath) do |element|
97
                add_element_to_path(vm_vnm_xml, element, xpath)
98
            end
99
        end
100

    
101
        # Initialize streams and vnm
102
        @ssh_src = @vmm.get_ssh_stream(action, @data[:host], @id)
103
        @vnm_src = VirtualNetworkDriver.new(@data[:net_drv],
104
                            :local_actions  => @vmm.options[:local_actions],
105
                            :message        => vm_vnm_xml.to_s,
106
                            :ssh_stream     => @ssh_src)
107

    
108
        if @data[:dest_host] and !@data[:dest_host].empty?
109
            @ssh_dst = @vmm.get_ssh_stream(action, @data[:dest_host], @id)
110
            @vnm_dst = VirtualNetworkDriver.new(@data[:dest_driver],
111
                            :local_actions  => @vmm.options[:local_actions],
112
                            :message        => vm_vnm_xml.to_s,
113
                            :ssh_stream     => @ssh_dst)
114
        end
115

    
116
        @tm = TransferManagerDriver.new(nil)
117
    end
118

    
119
    #Execute a set of steps defined with
120
    #  - :driver :vmm or :vnm to execute the step
121
    #  - :action for the step
122
    #  - :parameters command line paremeters for the action
123
    #  - :destination use next host
124
    #  - :fail_action steps to be executed if steps fail
125
    #  - :stdin for the action
126
    #  @param [Array] of steps
127
    def run(steps, info_on_success = nil)
128
        result = execute_steps(steps)
129

    
130
        @ssh_src.close if @ssh_src
131
        @ssh_dst.close if @ssh_dst
132

    
133
        #Prepare the info for the OpenNebula core
134
        if DriverExecHelper.failed?(result)
135
            info = @data[:failed_info]
136
        else
137
            info = @data["#{@main_action.to_s}_info".to_sym]
138
        end
139

    
140
        @vmm.send_message(VirtualMachineDriver::ACTION[@main_action],
141
                          result, @id, info)
142
    end
143

    
144
    private
145

    
146
    DRIVER_NAMES = {
147
        :vmm => "virtualization driver",
148
        :vnm => "network driver",
149
        :tm => "transfer manager driver"
150
    }
151

    
152
    # Executes a set of steps. If one step fails any recover action is performed
153
    # and the step execution breaks.
154
    # @param [Array] array of steps to be executed
155
    # @return [String, Hash] "SUCCESS/FAILURE" for the step set, and
156
    # information associated to each step (by :<action>_info). In case of
157
    # failure information is also in [:failed_info]
158
    def execute_steps(steps)
159
        result = DriverExecHelper.const_get(:RESULT)[:failure]
160

    
161
        steps.each do |step|
162
            # Execute Step
163
            case step[:driver]
164
            when :vmm
165
                if step[:destination]
166
                    host = @data[:dest_host]
167
                    ssh  = @ssh_dst
168
                else
169
                    host = @data[:host]
170
                    ssh  = @ssh_src
171
                end
172

    
173
                result, info = @vmm.do_action(get_parameters(step[:parameters]),
174
                                              @id,
175
                                              host,
176
                                              step[:action],
177
                                              :ssh_stream => ssh,
178
                                              :respond => false,
179
                                              :stdin => step[:stdin])
180
            when :vnm
181
                if step[:destination]
182
                    vnm = @vnm_dst
183
                else
184
                    vnm = @vnm_src
185
                end
186

    
187
                result, info = vnm.do_action(@id, step[:action],
188
                            :parameters => get_parameters(step[:parameters]))
189
            when :tm
190
                result, info = @tm.do_transfer_action(@id, step[:parameters])
191

    
192
            else
193
                result = DriverExecHelper.const_get(:RESULT)[:failure]
194
                info   = "No driver in #{step[:action]}"
195
            end
196

    
197
            # Save the step info
198
            @data["#{step[:action]}_info".to_sym] = info.strip
199

    
200
            # Roll back steps, store failed info and break steps
201
            if DriverExecHelper.failed?(result)
202
                execute_steps(step[:fail_actions]) if step[:fail_actions]
203
                @data[:failed_info] = info
204

    
205
                @vmm.log(@id,
206
                         "Failed to execute #{DRIVER_NAMES[step[:driver]]} " \
207
                         "operation: #{step[:action]}.")
208

    
209
                if step[:no_fail]
210
                    result = DriverExecHelper::RESULT[:success]
211
                else
212
                    break
213
                end
214
            else
215
                @vmm.log(@id,
216
                         "Successfully execute #{DRIVER_NAMES[step[:driver]]} " \
217
                         "operation: #{step[:action]}.")
218
            end
219
        end
220

    
221
        return result
222
    end
223

    
224
    # Prepare the parameters for the action step generating a blank separated
225
    # list of command arguments
226
    # @param [Hash] an action step
227
    def get_parameters(step_params)
228
        parameters = step_params || []
229

    
230
        parameters.map do |param|
231
            if Symbol===param
232
               "\'#{@data[param].to_s}\'"
233
            else
234
               "\'#{param}\'"
235
            end
236
        end.join(' ')
237
    end
238

    
239
    # Extracts data from the XML argument of the VMM action
240
    # @param [Symbol] corresponding to a XML element
241
    # @param [String] an xpath for the XML element
242
    # @return [String] the element value
243
    def get_data(name, xml_path=nil)
244
        if xml_path
245
            path=xml_path.to_s
246
        else
247
            path=name.to_s.upcase
248
        end
249

    
250
        if (elem = @xml_data.elements[path])
251
            @data[name]=elem.text
252
        end
253
    end
254

    
255
    # Adds a REXML node to a specific xpath
256
    #
257
    # @param [REXML::Element] xml document to add to
258
    # @param [REXML::Element] element to add
259
    # @param [String] path where the element is inserted in the xml document
260
    # @return [REXML::Element]
261
    def add_element_to_path(xml, element, path)
262
        root = xml
263
        path.split('/')[0..-2].each do |path_element|
264
            xml = xml.add_element(path_element) if path_element
265
        end
266
        xml.add_element(element)
267
        root
268
    end
269
end
270

    
271

    
272
# The main class for the Sh driver
273
class ExecDriver < VirtualMachineDriver
274
    attr_reader :options
275

    
276
    # Initializes the VMM driver
277
    # @param [String] hypervisor name identifies the plugin
278
    # @param [OpenNebulaDriver::options]
279
    def initialize(hypervisor, options={})
280
        @options={
281
            :threaded => true
282
        }.merge!(options)
283

    
284
        if options[:shell]
285
            @shell=options[:shell]
286
        else
287
            @shell='bash'
288
        end
289

    
290
        super("vmm/#{hypervisor}", @options)
291

    
292
        @hypervisor  = hypervisor
293
    end
294

    
295
    # Creates an SshStream to execute commands on the target host
296
    # @param[String] the hostname of the host
297
    # @param[String] id of the VM to log messages
298
    # @return [SshStreamCommand]
299
    def get_ssh_stream(aname, host, id)
300
        SshStreamCommand.new(host,
301
                            @remote_scripts_base_path,
302
                            log_method(id), nil, @shell)
303
    end
304

    
305
    #---------------------------------------------------------------------------
306
    #  Virtual Machine Manager Protocol Actions
307
    #---------------------------------------------------------------------------
308
    #
309
    # DEPLOY action, sends the deployment file to remote host
310
    #
311
    def deploy(id, drv_message)
312
        action = VmmAction.new(self, id, :deploy, drv_message)
313

    
314
        # ----------------------------------------------------------------------
315
        #  Initialization of deployment data
316
        # ----------------------------------------------------------------------
317
        local_dfile=action.data[:local_dfile]
318

    
319
        if !local_dfile || File.zero?(local_dfile)
320
            send_message(ACTION[:deploy],RESULT[:failure],id,
321
                "Cannot open deployment file #{local_dfile}")
322
            return
323
        end
324

    
325
        domain = File.read(local_dfile)
326

    
327
        if action_is_local?(:deploy)
328
            dfile = action.data[:local_dfile]
329
        else
330
            dfile = action.data[:remote_dfile]
331
        end
332

    
333
        # ----------------------------------------------------------------------
334
        #  Deployment Steps
335
        # ----------------------------------------------------------------------
336

    
337
        steps=[
338
            # Execute pre-boot networking setup
339
            {
340
                :driver   => :vnm,
341
                :action   => :pre
342
            },
343
            # Boot the Virtual Machine
344
            {
345
                :driver       => :vmm,
346
                :action       => :deploy,
347
                :parameters   => [dfile, :host],
348
                :stdin        => domain,
349
            },
350
            # Execute post-boot networking setup
351
            {
352
                :driver       => :vnm,
353
                :action       => :post,
354
                :parameters   => [:deploy_info],
355
                :fail_actions => [
356
                    {
357
                        :driver     => :vmm,
358
                        :action     => :cancel,
359
                        :parameters => [:deploy_info, :host]
360
                    }
361
                ]
362
            }
363
        ]
364

    
365
        action.run(steps)
366
    end
367

    
368
    #
369
    # SHUTDOWN action, graceful shutdown and network clean up
370
    #
371
    def shutdown(id, drv_message)
372

    
373
        action = VmmAction.new(self, id, :shutdown, drv_message)
374

    
375
        steps=[
376
            # Shutdown the Virtual Machine
377
            {
378
                :driver     => :vmm,
379
                :action     => :shutdown,
380
                :parameters => [:deploy_id, :host]
381
            },
382
            # Execute networking clean up operations
383
            {
384
                :driver   => :vnm,
385
                :action   => :clean
386
            }
387
        ]
388

    
389
        action.run(steps)
390
    end
391

    
392
    #
393
    # CANCEL action, destroys a VM and network clean up
394
    #
395
    def cancel(id, drv_message)
396
        action = VmmAction.new(self, id, :cancel, drv_message)
397

    
398
        steps=[
399
            # Cancel the Virtual Machine
400
            {
401
                :driver     => :vmm,
402
                :action     => :cancel,
403
                :parameters => [:deploy_id, :host]
404
            },
405
            # Execute networking clean up operations
406
            {
407
                :driver   => :vnm,
408
                :action   => :clean
409
            }
410
        ]
411

    
412
        action.run(steps)
413
    end
414

    
415
    #
416
    # SAVE action, stops the VM and saves its state, network is cleaned up
417
    #
418
    def save(id, drv_message)
419
        action = VmmAction.new(self, id, :save, drv_message)
420

    
421
        steps=[
422
            # Save the Virtual Machine state
423
            {
424
                :driver     => :vmm,
425
                :action     => :save,
426
                :parameters => [:deploy_id, :checkpoint_file, :host]
427
            },
428
            # Execute networking clean up operations
429
            {
430
                :driver   => :vnm,
431
                :action   => :clean
432
            }
433
        ]
434

    
435
        action.run(steps)
436
    end
437

    
438
    #
439
    # RESTORE action, restore a VM from a previous state, and restores network
440
    #
441
    def restore(id, drv_message)
442
        action=VmmAction.new(self, id, :restore, drv_message)
443

    
444
        steps=[
445
            # Execute pre-boot networking setup
446
            {
447
                :driver     => :vnm,
448
                :action     => :pre
449
            },
450
            # Restore the Virtual Machine from checkpoint
451
            {
452
                :driver     => :vmm,
453
                :action     => :restore,
454
                :parameters => [:checkpoint_file, :host, :deploy_id]
455
            },
456
            # Execute post-boot networking setup
457
            {
458
                :driver       => :vnm,
459
                :action       => :post,
460
                :parameters   => [:deploy_id],
461
                :fail_actions => [
462
                    {
463
                        :driver     => :vmm,
464
                        :action     => :cancel,
465
                        :parameters => [:deploy_id, :host]
466
                    }
467
                ],
468
            }
469
        ]
470

    
471
        action.run(steps)
472
    end
473

    
474
    #
475
    # MIGRATE (live) action, migrates a VM to another host creating network
476
    #
477
    def migrate(id, drv_message)
478
        action = VmmAction.new(self, id, :migrate, drv_message)
479
        pre    = "PRE"
480
        post   = "POST"
481
        failed = "FAIL"
482

    
483
        pre  << action.data[:tm_command] << " " << action.data[:vm]
484
        post << action.data[:tm_command] << " " << action.data[:vm]
485
        failed << action.data[:tm_command] << " " << action.data[:vm]
486

    
487
        steps=[
488
            # Execute a pre-migrate TM setup
489
            {
490
                :driver     => :tm,
491
                :action     => :tm_premigrate,
492
                :parameters => pre.split
493
            },
494
            # Execute pre-boot networking setup on migrating host
495
            {
496
                :driver      => :vnm,
497
                :action      => :pre,
498
                :destination => true
499
            },
500
            # Migrate the Virtual Machine
501
            {
502
                :driver     => :vmm,
503
                :action     => :migrate,
504
                :parameters => [:deploy_id, :dest_host, :host],
505
                :fail_actions => [
506
                    {
507
                        :driver     => :tm,
508
                        :action     => :tm_failmigrate,
509
                        :parameters => failed.split,
510
                        :no_fail    => true
511
                    }
512
                ]
513
            },
514
            # Execute networking clean up operations
515
            # NOTE: VM is now in the new host. If we fail from now on, oned will
516
            # assume that the VM is in the previous host but it is in fact
517
            # migrated. Log errors will be shown in vm.log
518
            {
519
                :driver       => :vnm,
520
                :action       => :clean,
521
                :no_fail      => true
522
            },
523
            # Execute post-boot networking setup on migrating host
524
            {
525
                :driver       => :vnm,
526
                :action       => :post,
527
                :parameters   => [:deploy_id],
528
                :destination  => :true,
529
                :no_fail      => true
530
            },
531
            {
532
                :driver     => :tm,
533
                :action     => :tm_postmigrate,
534
                :parameters => post.split,
535
                :no_fail    => true
536
            },
537
        ]
538

    
539
        action.run(steps)
540
    end
541

    
542
    #
543
    # POLL action, gets information of a VM
544
    #
545
    def poll(id, drv_message)
546
        data      = decode(drv_message)
547
        host      = data.elements['HOST'].text
548
        deploy_id = data.elements['DEPLOY_ID'].text
549

    
550
        do_action("#{deploy_id} #{host}", id, host, ACTION[:poll])
551
    end
552

    
553
    #
554
    # REBOOT action, reboots a running VM
555
    #
556
    def reboot(id, drv_message)
557
        data      = decode(drv_message)
558
        host      = data.elements['HOST'].text
559
        deploy_id = data.elements['DEPLOY_ID'].text
560

    
561
        do_action("#{deploy_id} #{host}", id, host, ACTION[:reboot])
562
    end
563

    
564
    #
565
    # RESET action, resets a running VM
566
    #
567
    def reset(id, drv_message)
568
        data      = decode(drv_message)
569
        host      = data.elements['HOST'].text
570
        deploy_id = data.elements['DEPLOY_ID'].text
571

    
572
        do_action("#{deploy_id} #{host}", id, host, ACTION[:reset])
573
    end
574

    
575
    #
576
    # ATTACHDISK action, attaches a disk to a running VM
577
    #
578
    def attach_disk(id, drv_message)
579
        action   = ACTION[:attach_disk]
580
        xml_data = decode(drv_message)
581

    
582
        tm_command = ensure_xpath(xml_data, id, action, 'TM_COMMAND') || return
583
        tm_rollback= xml_data.elements['TM_COMMAND_ROLLBACK'].text.strip
584

    
585
        target_xpath = "VM/TEMPLATE/DISK[ATTACH='YES']/TARGET"
586
        target     = ensure_xpath(xml_data, id, action, target_xpath) || return
587

    
588
        target_index = target.downcase[-1..-1].unpack('c').first - 97
589

    
590
        action = VmmAction.new(self, id, :attach_disk, drv_message)
591

    
592
        # Bug #1355, argument character limitation in ESX
593
        # Message not used in vmware anyway
594
        if @hypervisor == "vmware"
595
            drv_message = "drv_message"
596
        end
597

    
598
        steps = [
599
            # Perform a PROLOG on the disk
600
            {
601
                :driver     => :tm,
602
                :action     => :tm_attach,
603
                :parameters => tm_command.split
604
            },
605
            # Run the attach vmm script
606
            {
607
                :driver       => :vmm,
608
                :action       => :attach_disk,
609
                :parameters   => [
610
                        :deploy_id,
611
                        :disk_target_path,
612
                        target,
613
                        target_index,
614
                        drv_message
615
                ],
616
                :fail_actions => [
617
                    {
618
                        :driver     => :tm,
619
                        :action     => :tm_detach,
620
                        :parameters => tm_rollback.split
621
                    }
622
                ]
623
            }
624
        ]
625

    
626
        action.run(steps)
627
    end
628

    
629
    #
630
    # DETACHDISK action, attaches a disk to a running VM
631
    #
632
    def detach_disk(id, drv_message)
633
        action   = ACTION[:detach_disk]
634
        xml_data = decode(drv_message)
635

    
636
        tm_command = ensure_xpath(xml_data, id, action, 'TM_COMMAND') || return
637

    
638
        target_xpath = "VM/TEMPLATE/DISK[ATTACH='YES']/TARGET"
639
        target     = ensure_xpath(xml_data, id, action, target_xpath) || return
640

    
641
        target_index = target.downcase[-1..-1].unpack('c').first - 97
642

    
643
        action = VmmAction.new(self, id, :detach_disk, drv_message)
644

    
645
        steps = [
646
            # Run the detach vmm script
647
            {
648
                :driver       => :vmm,
649
                :action       => :detach_disk,
650
                :parameters   => [
651
                        :deploy_id,
652
                        :disk_target_path,
653
                        target,
654
                        target_index
655
                ]
656
            },
657
            # Perform an EPILOG on the disk
658
            {
659
                :driver     => :tm,
660
                :action     => :tm_detach,
661
                :parameters => tm_command.split
662
            }
663
        ]
664

    
665
        action.run(steps)
666
    end
667

    
668
    #
669
    # SNAPSHOTCREATE action, creates a new system snapshot
670
    #
671
    def snapshot_create(id, drv_message)
672
        xml_data = decode(drv_message)
673

    
674
        host      = xml_data.elements['HOST'].text
675
        deploy_id = xml_data.elements['DEPLOY_ID'].text
676

    
677
        snap_id_xpath = "VM/TEMPLATE/SNAPSHOT[ACTIVE='YES']/SNAPSHOT_ID"
678
        snap_id       = xml_data.elements[snap_id_xpath].text.to_i
679

    
680
        do_action("#{deploy_id} #{snap_id}",
681
                    id,
682
                    host,
683
                    ACTION[:snapshot_create],
684
                    :script_name => "snapshot_create")
685
    end
686

    
687
    #
688
    # SNAPSHOTREVERT action, reverts to a system snapshot
689
    #
690
    def snapshot_revert(id, drv_message)
691
        xml_data = decode(drv_message)
692

    
693
        host      = xml_data.elements['HOST'].text
694
        deploy_id = xml_data.elements['DEPLOY_ID'].text
695

    
696
        snap_id_xpath = "VM/TEMPLATE/SNAPSHOT[ACTIVE='YES']/HYPERVISOR_ID"
697
        snapshot_name = xml_data.elements[snap_id_xpath].text
698

    
699
        do_action("#{deploy_id} #{snapshot_name}",
700
                    id,
701
                    host,
702
                    ACTION[:snapshot_revert],
703
                    :script_name => "snapshot_revert")
704
    end
705

    
706
    #
707
    # SNAPSHOTDELETE action, deletes a system snapshot
708
    #
709
    def snapshot_delete(id, drv_message)
710
        xml_data = decode(drv_message)
711

    
712
        host      = xml_data.elements['HOST'].text
713
        deploy_id = xml_data.elements['DEPLOY_ID'].text
714

    
715
        snap_id_xpath = "VM/TEMPLATE/SNAPSHOT[ACTIVE='YES']/HYPERVISOR_ID"
716
        snapshot_name = xml_data.elements[snap_id_xpath].text
717

    
718
        do_action("#{deploy_id} #{snapshot_name}",
719
                    id,
720
                    host,
721
                    ACTION[:snapshot_delete],
722
                    :script_name => "snapshot_delete")
723
    end
724

    
725
    #
726
    # CLEANUP action, frees resources allocated in a host: VM and disk images
727
    #
728
    def cleanup(id, drv_message)
729
        xml_data = decode(drv_message)
730

    
731
        tm_command = xml_data.elements['TM_COMMAND'].text
732
        mhost      = xml_data.elements['MIGR_HOST'].text
733
        deploy_id  = xml_data.elements['DEPLOY_ID'].text
734

    
735
        action = VmmAction.new(self, id, :cleanup, drv_message)
736
        steps  = Array.new
737

    
738
        # Cancel the VM at host (only if we have a valid deploy-id)
739
        if deploy_id && !deploy_id.empty?
740
            steps <<
741
            {
742
                :driver     => :vmm,
743
                :action     => :cancel,
744
                :parameters => [:deploy_id, :host],
745
                :no_fail    => true
746
            }
747
            steps <<
748
            {
749
                :driver  => :vnm,
750
                :action  => :clean,
751
                :no_fail => true
752
            }
753
        end
754

    
755
        # Cancel the VM at the previous host (in case of migration)
756
        if mhost && !mhost.empty?
757
            steps <<
758
            {
759
                :driver      => :vmm,
760
                :action      => :cancel,
761
                :parameters  => [:deploy_id, :dest_host],
762
                :destination => true,
763
                :no_fail     => true
764
            }
765
            steps <<
766
            {
767
                :driver  => :vnm,
768
                :action  => :clean,
769
                :destination => true,
770
                :no_fail => true
771
            }
772
        end
773

    
774
        # Cleans VM disk images and directory
775
        tm_command.each_line { |tc|
776
            tc.strip!
777

    
778
            steps <<
779
            {
780
                :driver     => :tm,
781
                :action     => :tm_delete,
782
                :parameters => tc.split,
783
                :no_fail    => true
784
            } if !tc.empty?
785
        } if tm_command
786

    
787
        action.run(steps)
788
    end
789

    
790
    #
791
    #  ATTACHNIC action to attach a new nic interface
792
    #
793
    def attach_nic(id, drv_message)
794
        xml_data = decode(drv_message)
795

    
796
        begin
797
            source = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/BRIDGE"]
798
            mac    = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/MAC"]
799

    
800
            source = source.text.strip
801
            mac    = mac.text.strip
802
        rescue
803
            send_message(action, RESULT[:failure], id,
804
                "Error in #{ACTION[:attach_nic]}, BRIDGE and MAC needed in NIC")
805
            return
806
        end
807

    
808
        model = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/MODEL"]
809

    
810
        model = model.text if !model.nil?
811
        model = model.strip if !model.nil?
812
        model = "-" if model.nil?
813

    
814

    
815
        net_drv = xml_data.elements["NET_DRV"]
816

    
817
        net_drv = net_drv.text if !net_drv.nil?
818
        net_drv = net_drv.strip if !net_drv.nil?
819
        net_drv = "-" if net_drv.nil?
820

    
821
        action = VmmAction.new(self, id, :attach_nic, drv_message)
822

    
823
        steps=[
824
            # Execute pre-attach networking setup
825
            {
826
                :driver   => :vnm,
827
                :action   => :pre
828
            },
829
            # Attach the new NIC
830
            {
831
                :driver     => :vmm,
832
                :action     => :attach_nic,
833
                :parameters => [:deploy_id, mac, source, model, net_drv]
834
            },
835
            # Execute post-boot networking setup
836
            {
837
                :driver       => :vnm,
838
                :action       => :post,
839
                :parameters   => [:deploy_id],
840
                :fail_actions => [
841
                    {
842
                        :driver     => :vmm,
843
                        :action     => :detach_nic,
844
                        :parameters => [:deploy_id, mac]
845
                    }
846
                ]
847
            }
848
        ]
849

    
850
        action.run(steps)
851
    end
852

    
853
    #
854
    #  DETACHNIC action to detach a nic interface
855
    #
856
    def detach_nic(id, drv_message)
857
        xml_data = decode(drv_message)
858

    
859
        begin
860
            mac = xml_data.elements["VM/TEMPLATE/NIC[ATTACH='YES']/MAC"]
861
            mac = mac.text.strip
862
        rescue
863
            send_message(action, RESULT[:failure], id,
864
                "Error in #{ACTION[:detach_nic]}, MAC needed in NIC")
865
            return
866
        end
867

    
868
        action = VmmAction.new(self, id, :detach_nic, drv_message)
869

    
870
        steps=[
871
            # Detach the NIC
872
            {
873
                :driver     => :vmm,
874
                :action     => :detach_nic,
875
                :parameters => [:deploy_id, mac]
876
            },
877
            # Clean networking setup
878
            {
879
                :driver       => :vnm,
880
                :action       => :clean
881
            }
882
        ]
883

    
884
        action.run(steps)
885
    end
886

    
887
    #
888
    # DISKSNAPSHOTCREATE action, takes a snapshot of a disk
889
    #
890
    def disk_snapshot_create(id, drv_message)
891
        snap_action  = prepare_snap_action(id, drv_message, ACTION[:disk_snapshot_create])
892
        action       = snap_action[:action]
893
        strategy     = snap_action[:strategy]
894
        drv_message  = snap_action[:drv_message]
895
        target       = snap_action[:target]
896
        target_index = snap_action[:target_index]
897
        xml_data     = snap_action[:xml_data]
898

    
899
        # Get TM command
900
        tm_command = ensure_xpath(xml_data, id, action, 'TM_COMMAND') || return
901
        tm_rollback= xml_data.elements['TM_COMMAND_ROLLBACK'].text.strip
902

    
903
        # Build the process
904
        if strategy == :live
905
            tm_command_split = tm_command.split
906
            tm_command_split[0] += "_LIVE"
907

    
908
            steps = [
909
                {
910
                    :driver     => :tm,
911
                    :action     => :tm_snap_create_live,
912
                    :parameters => tm_command_split,
913
                    :no_fail    => true
914
                }
915
            ]
916
        else
917
            if strategy == :detach
918
                pre_action = :detach_disk
919
                pre_params = [:deploy_id, :disk_target_path, target, target_index]
920

    
921
                post_action = :attach_disk
922
                post_params = [:deploy_id, :disk_target_path, target, target_index,
923
                               drv_message]
924
            else # suspend
925
                pre_action = :save
926
                pre_params = [:deploy_id, :checkpoint_file, :host]
927

    
928
                post_action = :restore
929
                post_params = [:checkpoint_file, :host, :deploy_id]
930
            end
931

    
932
            steps = [
933
                {
934
                    :driver     => :vmm,
935
                    :action     => pre_action,
936
                    :parameters => pre_params
937
                },
938
                {
939
                    :driver     => :tm,
940
                    :action     => :tm_snap_create,
941
                    :parameters => tm_command.split,
942
                    :no_fail    => true
943
                },
944
                {
945
                    :driver     => :vmm,
946
                    :action     => post_action,
947
                    :parameters => post_params,
948
                    :fail_actions => [
949
                        {
950
                            :driver     => :tm,
951
                            :action     => :tm_snap_delete,
952
                            :parameters => tm_rollback.split
953
                        }
954
                    ]
955
                }
956
            ]
957
        end
958

    
959
        action.run(steps)
960
    end
961

    
962
    #
963
    # DISKSNAPSHOTREVERT action, takes a snapshot of a disk
964
    #
965
    def disk_snapshot_revert(id, drv_message)
966
        snap_action  = prepare_snap_action(id, drv_message, ACTION[:disk_snapshot_revert])
967
        action       = snap_action[:action]
968
        strategy     = @options[:snapshots_strategy]
969
        drv_message  = snap_action[:drv_message]
970
        target       = snap_action[:target]
971
        target_index = snap_action[:target_index]
972
        xml_data     = snap_action[:xml_data]
973

    
974
        # Get TM command
975
        tm_command = ensure_xpath(xml_data, id, action, 'TM_COMMAND') || return
976

    
977
        # Build the process
978
        if strategy == :detach
979
          pre_action = :detach_disk
980
          pre_params = [:deploy_id, :disk_target_path, target, target_index]
981

    
982
          post_action = :attach_disk
983
          post_params = [:deploy_id, :disk_target_path, target, target_index,
984
                         drv_message]
985
        else # suspend
986
          pre_action = :save
987
          pre_params = [:deploy_id, :checkpoint_file, :host]
988

    
989
          post_action = :restore
990
          post_params = [:checkpoint_file, :host, :deploy_id]
991
        end
992

    
993
        steps = [
994
            # Save VM state / detach the disk
995
            {
996
                :driver     => :vmm,
997
                :action     => pre_action,
998
                :parameters => pre_params
999
            },
1000
            # Do the snapshot
1001
            {
1002
                :driver     => :tm,
1003
                :action     => :tm_snap_revert,
1004
                :parameters => tm_command.split,
1005
                :no_fail    => true,
1006
            },
1007
            # Restore VM / attach the disk
1008
            {
1009
                :driver     => :vmm,
1010
                :action     => post_action,
1011
                :parameters => post_params
1012
            }
1013
        ]
1014

    
1015
        action.run(steps)
1016
    end
1017

    
1018
private
1019

    
1020
    def ensure_xpath(xml_data, id, action, xpath)
1021
        begin
1022
            value = xml_data.elements[xpath].text.strip
1023
            raise if value.empty?
1024
            value
1025
        rescue
1026
            send_message(action, RESULT[:failure], id,
1027
                "Cannot perform #{action}, expecting #{xpath}")
1028
            nil
1029
        end
1030
    end
1031

    
1032
    def prepare_snap_action(id, drv_message, action)
1033
        xml_data = decode(drv_message)
1034

    
1035
        # Make sure disk target has been defined
1036
        target_xpath = "VM/TEMPLATE/DISK[DISK_SNAPSHOT_ACTIVE='YES']/TARGET"
1037
        target       = ensure_xpath(xml_data, id, action, target_xpath) || return
1038
        target_index = target.downcase[-1..-1].unpack('c').first - 97
1039

    
1040
        # Always send ATTACH='YES' for the selected target in case it will end
1041
        # up being a 'detach' strategy
1042
        disk   = xml_data.elements[target_xpath].parent
1043
        attach = REXML::Element.new('ATTACH')
1044

    
1045
        attach.add_text('YES')
1046
        disk.add(attach)
1047

    
1048
        drv_message = Base64.encode64(xml_data.to_s)
1049
        action = VmmAction.new(self, id, :disk_snapshot_create, drv_message)
1050

    
1051
        # Determine the strategy
1052
        vmm_driver_path = 'VM/HISTORY_RECORDS/HISTORY/VMMMAD'
1053
        tm_driver_path  = "VM/TEMPLATE/DISK[DISK_SNAPSHOT_ACTIVE='YES']/TM_MAD"
1054

    
1055
        vmm_driver = ensure_xpath(xml_data, id, action, vmm_driver_path) || return
1056
        tm_driver  = ensure_xpath(xml_data, id, action, tm_driver_path)  || return
1057

    
1058
        strategy = @options[:snapshots_strategy]
1059
        if @options[:live_snapshots] && LIVE_DISK_SNAPSHOTS.include?("#{vmm_driver}-#{tm_driver}")
1060
            strategy = :live
1061
        end
1062

    
1063
        {
1064
            :action       => action,
1065
            :strategy     => strategy,
1066
            :drv_message  => drv_message,
1067
            :target       => target,
1068
            :target_index => target_index,
1069
            :xml_data     => xml_data
1070
        }
1071
    end
1072
end
1073

    
1074
################################################################################
1075
#
1076
# Virtual Machine Manager Execution Driver - Main Program
1077
#
1078
################################################################################
1079

    
1080
LIVE_DISK_SNAPSHOTS = ENV['LIVE_DISK_SNAPSHOTS'].split rescue []
1081

    
1082
opts = GetoptLong.new(
1083
    [ '--retries',           '-r', GetoptLong::OPTIONAL_ARGUMENT ],
1084
    [ '--threads',           '-t', GetoptLong::OPTIONAL_ARGUMENT ],
1085
    [ '--local',             '-l', GetoptLong::REQUIRED_ARGUMENT ],
1086
    [ '--shell',             '-s', GetoptLong::REQUIRED_ARGUMENT ],
1087
    [ '--parallel',          '-p', GetoptLong::NO_ARGUMENT ],
1088
    [ '--live-snapshots',    '-i', GetoptLong::NO_ARGUMENT ],
1089
    [ '--default-snapshots', '-d', GetoptLong::REQUIRED_ARGUMENT ]
1090
)
1091

    
1092
hypervisor         = ''
1093
retries            = 0
1094
threads            = 15
1095
shell              = 'bash'
1096
local_actions      = {}
1097
single_host        = true
1098
live_snapshots     = false
1099
snapshots_strategy = :suspend # Either :detach or :suspend
1100

    
1101
begin
1102
    opts.each do |opt, arg|
1103
        case opt
1104
            when '--retries'
1105
                retries = arg.to_i
1106
            when '--threads'
1107
                threads = arg.to_i
1108
            when '--local'
1109
                local_actions = OpenNebulaDriver.parse_actions_list(arg)
1110
            when '--shell'
1111
                shell = arg
1112
            when '--parallel'
1113
                single_host = false
1114
            when '--default-snapshots'
1115
                snapshots_strategy = arg.to_sym
1116
            when '--live-snapshots'
1117
                live_snapshots = true
1118
        end
1119
    end
1120
rescue Exception => e
1121
    exit(-1)
1122
end
1123

    
1124
if ARGV.length >= 1
1125
    hypervisor = ARGV.shift
1126
else
1127
    exit(-1)
1128
end
1129

    
1130
exec_driver = ExecDriver.new(hypervisor,
1131
                :concurrency        => threads,
1132
                :retries            => retries,
1133
                :local_actions      => local_actions,
1134
                :shell              => shell,
1135
                :single_host        => single_host,
1136
                :snapshots_strategy => snapshots_strategy,
1137
                :live_snapshots     => live_snapshots)
1138

    
1139
exec_driver.start_driver