Statistics
| Branch: | Tag: | Revision:

one / src / oca / ruby / opennebula / virtual_machine.rb @ 05ed37c9

History | View | Annotate | Download (31.3 KB)

1
# -------------------------------------------------------------------------- #
2
# Copyright 2002-2015, OpenNebula Project (OpenNebula.org), C12G Labs        #
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

    
18
require 'opennebula/pool_element'
19

    
20
module OpenNebula
21
    class VirtualMachine < PoolElement
22
        #######################################################################
23
        # Constants and Class Methods
24
        #######################################################################
25

    
26
        VM_METHODS = {
27
            :info           => "vm.info",
28
            :allocate       => "vm.allocate",
29
            :action         => "vm.action",
30
            :migrate        => "vm.migrate",
31
            :deploy         => "vm.deploy",
32
            :chown          => "vm.chown",
33
            :chmod          => "vm.chmod",
34
            :monitoring     => "vm.monitoring",
35
            :attach         => "vm.attach",
36
            :detach         => "vm.detach",
37
            :rename         => "vm.rename",
38
            :update         => "vm.update",
39
            :resize         => "vm.resize",
40
            :snapshotcreate => "vm.snapshotcreate",
41
            :snapshotrevert => "vm.snapshotrevert",
42
            :snapshotdelete => "vm.snapshotdelete",
43
            :attachnic      => "vm.attachnic",
44
            :detachnic      => "vm.detachnic",
45
            :recover        => "vm.recover",
46
            :disksaveas     => "vm.disksaveas",
47
            :disksnapshotcreate => "vm.disksnapshotcreate",
48
            :disksnapshotrevert => "vm.disksnapshotrevert",
49
            :disksnapshotdelete => "vm.disksnapshotdelete"
50
        }
51

    
52
        VM_STATE=%w{INIT PENDING HOLD ACTIVE STOPPED SUSPENDED DONE FAILED
53
            POWEROFF UNDEPLOYED}
54

    
55
        LCM_STATE=%w{
56
            LCM_INIT
57
            PROLOG
58
            BOOT
59
            RUNNING
60
            MIGRATE
61
            SAVE_STOP
62
            SAVE_SUSPEND
63
            SAVE_MIGRATE
64
            PROLOG_MIGRATE
65
            PROLOG_RESUME
66
            EPILOG_STOP
67
            EPILOG
68
            SHUTDOWN
69
            CANCEL
70
            FAILURE
71
            CLEANUP_RESUBMIT
72
            UNKNOWN
73
            HOTPLUG
74
            SHUTDOWN_POWEROFF
75
            BOOT_UNKNOWN
76
            BOOT_POWEROFF
77
            BOOT_SUSPENDED
78
            BOOT_STOPPED
79
            CLEANUP_DELETE
80
            HOTPLUG_SNAPSHOT
81
            HOTPLUG_NIC
82
            HOTPLUG_SAVEAS
83
            HOTPLUG_SAVEAS_POWEROFF
84
            HOTPLUG_SAVEAS_SUSPENDED
85
            SHUTDOWN_UNDEPLOY
86
            EPILOG_UNDEPLOY
87
            PROLOG_UNDEPLOY
88
            BOOT_UNDEPLOY
89
            HOTPLUG_PROLOG_POWEROFF
90
            HOTPLUG_EPILOG_POWEROFF
91
            BOOT_MIGRATE
92
            BOOT_FAILURE
93
            BOOT_MIGRATE_FAILURE
94
            PROLOG_MIGRATE_FAILURE
95
            PROLOG_FAILURE
96
            EPILOG_FAILURE
97
            EPILOG_STOP_FAILURE
98
            EPILOG_UNDEPLOY_FAILURE
99
            PROLOG_MIGRATE_POWEROFF
100
            PROLOG_MIGRATE_POWEROFF_FAILURE
101
            PROLOG_MIGRATE_SUSPEND
102
            PROLOG_MIGRATE_SUSPEND_FAILURE
103
            BOOT_UNDEPLOY_FAILURE
104
            BOOT_STOPPED_FAILURE
105
            PROLOG_RESUME_FAILURE
106
            PROLOG_UNDEPLOY_FAILURE
107
            DISK_SNAPSHOT_POWEROFF
108
            DISK_SNAPSHOT_REVERT_POWEROFF
109
            DISK_SNAPSHOT_DELETE_POWEROFF
110
            DISK_SNAPSHOT_SUSPENDED
111
            DISK_SNAPSHOT_REVERT_SUSPENDED
112
            DISK_SNAPSHOT_DELETE_SUSPENDED
113
            DISK_SNAPSHOT
114
            DISK_SNAPSHOT_REVERT
115
            DISK_SNAPSHOT_DELETE
116
        }
117

    
118
        SHORT_VM_STATES={
119
            "INIT"      => "init",
120
            "PENDING"   => "pend",
121
            "HOLD"      => "hold",
122
            "ACTIVE"    => "actv",
123
            "STOPPED"   => "stop",
124
            "SUSPENDED" => "susp",
125
            "DONE"      => "done",
126
            "FAILED"    => "fail",
127
            "POWEROFF"  => "poff",
128
            "UNDEPLOYED"=> "unde"
129
        }
130

    
131
        SHORT_LCM_STATES={
132
            "PROLOG"            => "prol",
133
            "BOOT"              => "boot",
134
            "RUNNING"           => "runn",
135
            "MIGRATE"           => "migr",
136
            "SAVE_STOP"         => "save",
137
            "SAVE_SUSPEND"      => "save",
138
            "SAVE_MIGRATE"      => "save",
139
            "PROLOG_MIGRATE"    => "migr",
140
            "PROLOG_RESUME"     => "prol",
141
            "EPILOG_STOP"       => "epil",
142
            "EPILOG"            => "epil",
143
            "SHUTDOWN"          => "shut",
144
            "CANCEL"            => "shut",
145
            "FAILURE"           => "fail",
146
            "CLEANUP_RESUBMIT"  => "clea",
147
            "UNKNOWN"           => "unkn",
148
            "HOTPLUG"           => "hotp",
149
            "SHUTDOWN_POWEROFF" => "shut",
150
            "BOOT_UNKNOWN"      => "boot",
151
            "BOOT_POWEROFF"     => "boot",
152
            "BOOT_SUSPENDED"    => "boot",
153
            "BOOT_STOPPED"      => "boot",
154
            "CLEANUP_DELETE"    => "clea",
155
            "HOTPLUG_SNAPSHOT"  => "snap",
156
            "HOTPLUG_NIC"       => "hotp",
157
            "HOTPLUG_SAVEAS"           => "hotp",
158
            "HOTPLUG_SAVEAS_POWEROFF"  => "hotp",
159
            "HOTPLUG_SAVEAS_SUSPENDED" => "hotp",
160
            "SHUTDOWN_UNDEPLOY" => "shut",
161
            "EPILOG_UNDEPLOY"   => "epil",
162
            "PROLOG_UNDEPLOY"   => "prol",
163
            "BOOT_UNDEPLOY"     => "boot",
164
            "HOTPLUG_PROLOG_POWEROFF"   => "hotp",
165
            "HOTPLUG_EPILOG_POWEROFF"   => "hotp",
166
            "BOOT_MIGRATE"              => "boot",
167
            "BOOT_FAILURE"              => "fail",
168
            "BOOT_MIGRATE_FAILURE"      => "fail",
169
            "PROLOG_MIGRATE_FAILURE"    => "fail",
170
            "PROLOG_FAILURE"            => "fail",
171
            "EPILOG_FAILURE"            => "fail",
172
            "EPILOG_STOP_FAILURE"       => "fail",
173
            "EPILOG_UNDEPLOY_FAILURE"   => "fail",
174
            "PROLOG_MIGRATE_POWEROFF"   => "migr",
175
            "PROLOG_MIGRATE_POWEROFF_FAILURE"   => "fail",
176
            "PROLOG_MIGRATE_SUSPEND"            => "migr",
177
            "PROLOG_MIGRATE_SUSPEND_FAILURE"    => "fail",
178
            "BOOT_UNDEPLOY_FAILURE"     => "fail",
179
            "BOOT_STOPPED_FAILURE"      => "fail",
180
            "PROLOG_RESUME_FAILURE"     => "fail",
181
            "PROLOG_UNDEPLOY_FAILURE"   => "fail",
182
            "DISK_SNAPSHOT_POWEROFF"        => "snap",
183
            "DISK_SNAPSHOT_REVERT_POWEROFF" => "snap",
184
            "DISK_SNAPSHOT_DELETE_POWEROFF" => "snap",
185
            "DISK_SNAPSHOT_SUSPENDED"       => "snap",
186
            "DISK_SNAPSHOT_REVERT_SUSPENDED"=> "snap",
187
            "DISK_SNAPSHOT_DELETE_SUSPENDED"=> "snap",
188
            "DISK_SNAPSHOT"        => "snap",
189
            "DISK_SNAPSHOT_REVERT" => "snap",
190
            "DISK_SNAPSHOT_DELETE" => "snap"
191
        }
192

    
193
        MIGRATE_REASON=%w{NONE ERROR USER}
194

    
195
        SHORT_MIGRATE_REASON={
196
            "NONE"          => "none",
197
            "ERROR"         => "erro",
198
            "USER"          => "user"
199
        }
200

    
201
        HISTORY_ACTION=%w{none migrate live-migrate shutdown shutdown-hard
202
            undeploy undeploy-hard hold release stop suspend resume boot delete
203
            delete-recreate reboot reboot-hard resched unresched poweroff
204
            poweroff-hard disk-attach disk-detach nic-attach nic-detach
205
            snap-create snap-delete}
206

    
207
        EXTERNAL_IP_ATTRS = [
208
            'GUEST_IP',
209
            'AWS_IP_ADDRESS',
210
            'AZ_IPADDRESS',
211
            'SL_PRIMARYIPADDRESS'
212
        ]
213

    
214
        # Creates a VirtualMachine description with just its identifier
215
        # this method should be used to create plain VirtualMachine objects.
216
        # +id+ the id of the vm
217
        #
218
        # Example:
219
        #   vnet = VirtualMachine.new(VirtualMachine.build_xml(3),rpc_client)
220
        #
221
        def VirtualMachine.build_xml(pe_id=nil)
222
            if pe_id
223
                vm_xml = "<VM><ID>#{pe_id}</ID></VM>"
224
            else
225
                vm_xml = "<VM></VM>"
226
            end
227

    
228
            XMLElement.build_xml(vm_xml, 'VM')
229
        end
230

    
231
        def VirtualMachine.get_reason(reason)
232
            reason=MIGRATE_REASON[reason.to_i]
233
            reason_str=SHORT_MIGRATE_REASON[reason]
234

    
235
            reason_str
236
        end
237

    
238
        def VirtualMachine.get_history_action(action)
239
            return HISTORY_ACTION[action.to_i]
240
        end
241

    
242
        # Class constructor
243
        def initialize(xml, client)
244
            super(xml,client)
245
        end
246

    
247
        #######################################################################
248
        # XML-RPC Methods for the Virtual Machine Object
249
        #######################################################################
250

    
251
        # Retrieves the information of the given VirtualMachine.
252
        def info()
253
            super(VM_METHODS[:info], 'VM')
254
        end
255

    
256
        alias_method :info!, :info
257

    
258
        # Allocates a new VirtualMachine in OpenNebula
259
        #
260
        # @param description [String] A string containing the template of
261
        #   the VirtualMachine.
262
        # @param hold [true,false] false to create the VM in pending state,
263
        #   true to create it on hold
264
        #
265
        # @return [nil, OpenNebula::Error] nil in case of success, Error
266
        #   otherwise
267
        def allocate(description, hold=false)
268
            super(VM_METHODS[:allocate], description, hold)
269
        end
270

    
271
        # Replaces the template contents
272
        #
273
        # @param new_template [String] New template contents
274
        # @param append [true, false] True to append new attributes instead of
275
        #   replace the whole template
276
        #
277
        # @return [nil, OpenNebula::Error] nil in case of success, Error
278
        #   otherwise
279
        def update(new_template=nil, append=false)
280
            super(VM_METHODS[:update], new_template, append ? 1 : 0)
281
        end
282

    
283
        # Returns the <USER_TEMPLATE> element in text form
284
        #
285
        # @param indent [true,false] indents the resulting string, defaults to true
286
        #
287
        # @return [String] The USER_TEMPLATE
288
        def user_template_str(indent=true)
289
            template_like_str('USER_TEMPLATE', indent)
290
        end
291

    
292
        # Returns the <USER_TEMPLATE> element in XML form
293
        #
294
        # @return [String] The USER_TEMPLATE
295
        def user_template_xml
296
            if NOKOGIRI
297
                @xml.xpath('USER_TEMPLATE').to_s
298
            else
299
                @xml.elements['USER_TEMPLATE'].to_s
300
            end
301
        end
302

    
303

    
304
        # Initiates the instance of the VM on the target host.
305
        #
306
        # @param host_id [Interger] The host id (hid) of the target host where
307
        #   the VM will be instantiated.
308
        # @param enforce [true|false] If it is set to true, the host capacity
309
        #   will be checked, and the deployment will fail if the host is
310
        #   overcommited. Defaults to false
311
        # @param ds_id [Integer] The System Datastore where to deploy the VM. To
312
        #   use the default, set it to -1
313
        #
314
        # @return [nil, OpenNebula::Error] nil in case of success, Error
315
        #   otherwise
316
        def deploy(host_id, enforce=false, ds_id=-1)
317
            enforce ||= false
318
            ds_id ||= -1
319
            return call(VM_METHODS[:deploy],
320
                        @pe_id,
321
                        host_id.to_i,
322
                        enforce,
323
                        ds_id.to_i)
324
        end
325

    
326
        # Shutdowns an already deployed VM
327
        def shutdown(hard=false)
328
            action(hard ? 'shutdown-hard' : 'shutdown')
329
        end
330

    
331
        # Shuts down an already deployed VM, saving its state in the system DS
332
        def undeploy(hard=false)
333
            action(hard ? 'undeploy-hard' : 'undeploy')
334
        end
335

    
336
        # Powers off a running VM
337
        def poweroff(hard=false)
338
            action(hard ? 'poweroff-hard' : 'poweroff')
339
        end
340

    
341
        # Reboots an already deployed VM
342
        def reboot(hard=false)
343
            action(hard ? 'reboot-hard' : 'reboot')
344
        end
345

    
346
        # @deprecated use {#reboot}
347
        def reset
348
            reboot(true)
349
        end
350

    
351
        # @deprecated use {#shutdown}
352
        def cancel
353
            shutdown(true)
354
        end
355

    
356
        # Sets a VM to hold state, scheduler will not deploy it
357
        def hold
358
            action('hold')
359
        end
360

    
361
        # Releases a VM from hold state
362
        def release
363
            action('release')
364
        end
365

    
366
        # Stops a running VM
367
        def stop
368
            action('stop')
369
        end
370

    
371
        # Saves a running VM
372
        def suspend
373
            action('suspend')
374
        end
375

    
376
        # Resumes the execution of a saved VM
377
        def resume
378
            action('resume')
379
        end
380

    
381
        # Attaches a disk to a running VM
382
        #
383
        # @param disk_template [String] Template containing a DISK element
384
        # @return [nil, OpenNebula::Error] nil in case of success, Error
385
        #   otherwise
386
        def disk_attach(disk_template)
387
            return call(VM_METHODS[:attach], @pe_id, disk_template)
388
        end
389

    
390
        alias_method :attachdisk, :disk_attach
391

    
392
        # Detaches a disk from a running VM
393
        #
394
        # @param disk_id [Integer] Id of the disk to be detached
395
        # @return [nil, OpenNebula::Error] nil in case of success, Error
396
        #   otherwise
397
        def disk_detach(disk_id)
398
            return call(VM_METHODS[:detach], @pe_id, disk_id)
399
        end
400

    
401
        alias_method :detachdisk, :disk_detach
402

    
403
        # Attaches a NIC to a running VM
404
        #
405
        # @param nic_template [String] Template containing a NIC element
406
        # @return [nil, OpenNebula::Error] nil in case of success, Error
407
        #   otherwise
408
        def nic_attach(nic_template)
409
            return call(VM_METHODS[:attachnic], @pe_id, nic_template)
410
        end
411

    
412
        # Detaches a NIC from a running VM
413
        #
414
        # @param nic_id [Integer] Id of the NIC to be detached
415
        # @return [nil, OpenNebula::Error] nil in case of success, Error
416
        #   otherwise
417
        def nic_detach(nic_id)
418
            return call(VM_METHODS[:detachnic], @pe_id, nic_id)
419
        end
420

    
421
        # Deletes a VM from the pool
422
        def delete(recreate=false)
423
            if recreate
424
                action('delete-recreate')
425
            else
426
                action('delete')
427
            end
428
        end
429

    
430
        # @deprecated use {#delete} instead
431
        def finalize(recreate=false)
432
            delete(recreate)
433
        end
434

    
435
        # @deprecated use {#delete} instead
436
        def resubmit
437
            action('delete-recreate')
438
        end
439

    
440
        # Sets the re-scheduling flag for the VM
441
        def resched
442
            action('resched')
443
        end
444

    
445
        # Unsets the re-scheduling flag for the VM
446
        def unresched
447
            action('unresched')
448
        end
449

    
450
        # Moves a running VM to the specified host. With live=true the
451
        # migration is done withdout downtime.
452
        #
453
        # @param host_id [Interger] The host id (hid) of the target host where
454
        #   the VM will be migrated.
455
        # @param live [true|false] If true the migration is done without
456
        #   downtime. Defaults to false
457
        # @param enforce [true|false] If it is set to true, the host capacity
458
        #   will be checked, and the deployment will fail if the host is
459
        #   overcommited. Defaults to false
460
        # @param ds_id [Integer] The System Datastore where to migrate the VM.
461
        #   To use the current one, set it to -1
462
        #
463
        # @return [nil, OpenNebula::Error] nil in case of success, Error
464
        #   otherwise
465
        def migrate(host_id, live=false, enforce=false, ds_id=-1)
466
            call(VM_METHODS[:migrate], @pe_id, host_id.to_i, live==true,
467
                enforce, ds_id.to_i)
468
        end
469

    
470
        # @deprecated use {#migrate} instead
471
        def live_migrate(host_id, enforce=false)
472
            migrate(host_id, true, enforce)
473
        end
474

    
475
        # Set the specified vm's disk to be saved as a new image
476
        #
477
        # @param disk_id [Integer] ID of the disk to be saved
478
        # @param image_name [String] Name for the new image where the
479
        #   disk will be saved
480
        # @param image_type [String] Type of the new image. Set to empty string
481
        #   to use the default type
482
        # @param snap_id [Integer] ID of the snapshot to save, -1 to use the
483
        # current disk image state
484
        #
485
        # @return [Integer, OpenNebula::Error] the new Image ID in case of
486
        #   success, error otherwise
487
        def disk_saveas(disk_id, image_name, image_type="", snap_id=-1)
488
            return Error.new('ID not defined') if !@pe_id
489

    
490
            rc = @client.call(VM_METHODS[:disksaveas],
491
                              @pe_id,
492
                              disk_id,
493
                              image_name,
494
                              image_type,
495
                              snap_id)
496
            return rc
497
        end
498

    
499
        # Resize the VM
500
        #
501
        # @param capacity_template [String] Template containing the new capacity
502
        #   elements CPU, VCPU, MEMORY. If one of them is not present, or its
503
        #   value is 0, it will not be resized
504
        # @param enforce [true|false] If it is set to true, the host capacity
505
        #   will be checked. This will only affect oneadmin requests, regular users
506
        #   resize requests will always be enforced
507
        #
508
        # @return [nil, OpenNebula::Error] nil in case of success, Error
509
        #   otherwise
510
        def resize(capacity_template, enforce)
511
            return call(VM_METHODS[:resize], @pe_id, capacity_template, enforce)
512
        end
513

    
514
        # Changes the owner/group
515
        # uid:: _Integer_ the new owner id. Set to -1 to leave the current one
516
        # gid:: _Integer_ the new group id. Set to -1 to leave the current one
517
        # [return] nil in case of success or an Error object
518
        def chown(uid, gid)
519
            super(VM_METHODS[:chown], uid, gid)
520
        end
521

    
522
        # Changes the permissions.
523
        #
524
        # @param octet [String] Permissions octed , e.g. 640
525
        # @return [nil, OpenNebula::Error] nil in case of success, Error
526
        #   otherwise
527
        def chmod_octet(octet)
528
            super(VM_METHODS[:chmod], octet)
529
        end
530

    
531
        # Changes the permissions.
532
        # Each [Integer] argument must be 1 to allow, 0 deny, -1 do not change
533
        #
534
        # @return [nil, OpenNebula::Error] nil in case of success, Error
535
        #   otherwise
536
        def chmod(owner_u, owner_m, owner_a, group_u, group_m, group_a, other_u,
537
                other_m, other_a)
538
            super(VM_METHODS[:chmod], owner_u, owner_m, owner_a, group_u,
539
                group_m, group_a, other_u, other_m, other_a)
540
        end
541

    
542
        # Retrieves this VM's monitoring data from OpenNebula
543
        #
544
        # @param [Array<String>] xpath_expressions Elements to retrieve.
545
        #
546
        # @return [Hash<String, Array<Array<int>>>, OpenNebula::Error] Hash with
547
        #   the requested xpath expressions, and an Array of 'timestamp, value'.
548
        #
549
        # @example
550
        #   vm.monitoring( ['MONITORING/CPU', 'MONITORING/NETTX'] )
551
        #
552
        #   {
553
        #    "MONITORING/CPU"=>[["1435085098", "47"], ["1435085253", "5"],
554
        #      ["1435085410", "48"], ["1435085566", "3"], ["1435088136", "2"]],
555
        #    "MONITORING/NETTX"=>[["1435085098", "0"], ["1435085253", "50"],
556
        #      ["1435085410", "50"], ["1435085566", "50"], ["1435085723", "50"]]
557
        #   }
558
        #
559
        def monitoring(xpath_expressions)
560
            return super(VM_METHODS[:monitoring], 'VM',
561
                'LAST_POLL', xpath_expressions)
562
        end
563

    
564
        # Retrieves this VM's monitoring data from OpenNebula, in XML
565
        #
566
        # @return [String] VM monitoring data, in XML
567
        def monitoring_xml()
568
            return Error.new('ID not defined') if !@pe_id
569

    
570
            return @client.call(VM_METHODS[:monitoring], @pe_id)
571
        end
572

    
573
        # Renames this VM
574
        #
575
        # @param name [String] New name for the VM.
576
        #
577
        # @return [nil, OpenNebula::Error] nil in case of success, Error
578
        #   otherwise
579
        def rename(name)
580
            return call(VM_METHODS[:rename], @pe_id, name)
581
        end
582

    
583
        # Creates a new VM snapshot
584
        #
585
        # @param name [String] Name for the snapshot.
586
        #
587
        # @return [Integer, OpenNebula::Error] The new snaphost ID in case
588
        #   of success, Error otherwise
589
        def snapshot_create(name="")
590
            return Error.new('ID not defined') if !@pe_id
591

    
592
            name ||= ""
593
            return @client.call(VM_METHODS[:snapshotcreate], @pe_id, name)
594
        end
595

    
596
        # Reverts to a snapshot
597
        #
598
        # @param snap_id [Integer] Id of the snapshot
599
        #
600
        # @return [nil, OpenNebula::Error] nil in case of success, Error
601
        #   otherwise
602
        def snapshot_revert(snap_id)
603
            return call(VM_METHODS[:snapshotrevert], @pe_id, snap_id)
604
        end
605

    
606
        # Deletes a  VM snapshot
607
        #
608
        # @param snap_id [Integer] Id of the snapshot
609
        #
610
        # @return [nil, OpenNebula::Error] nil in case of success, Error
611
        #   otherwise
612
        def snapshot_delete(snap_id)
613
            return call(VM_METHODS[:snapshotdelete], @pe_id, snap_id)
614
        end
615

    
616
        # Takes a new snapshot of a disk
617
        #
618
        # @param disk_id [Integer] Id of the disk
619
        # @param name [String] description for the snapshot
620
        #
621
        # @return [Integer, OpenNebula::Error] The new snapshot ID or error
622
        def disk_snapshot_create(disk_id, name)
623
          return call(VM_METHODS[:disksnapshotcreate], @pe_id, disk_id, name)
624
        end
625

    
626
        # Reverts disk state to a previously taken snapshot
627
        #
628
        # @param disk_id [Integer] Id of the disk
629
        # @param snap_id [Integer] Id of the snapshot
630
        #
631
        # @return [nil, OpenNebula::Error] nil in case of success, Error
632
        #   otherwise
633
        def disk_snapshot_revert(disk_id, snap_id)
634
          return call(VM_METHODS[:disksnapshotrevert], @pe_id, disk_id, snap_id)
635
        end
636

    
637
        # Deletes a disk snapshot
638
        #
639
        # @param disk_id [Integer] Id of the disk
640
        # @param snap_id [Integer] Id of the snapshot
641
        #
642
        # @return [nil, OpenNebula::Error] nil in case of success, Error
643
        #   otherwise
644
        def disk_snapshot_delete(disk_id, snap_id)
645
          return call(VM_METHODS[:disksnapshotdelete], @pe_id, disk_id, snap_id)
646
        end
647

    
648
        # Recovers an ACTIVE VM
649
        #
650
        # @param result [Integer] Recover with failure (0), success (1) or
651
        # retry (2)
652
        # @param result [info] Additional information needed to recover the VM
653
        # @return [nil, OpenNebula::Error] nil in case of success, Error
654
        #   otherwise
655
        def recover(result)
656
            return call(VM_METHODS[:recover], @pe_id, result)
657
        end
658

    
659
        ########################################################################
660
        # Helpers to get VirtualMachine information
661
        ########################################################################
662

    
663
        # Returns the VM state of the VirtualMachine (numeric value)
664
        def state
665
            self['STATE'].to_i
666
        end
667

    
668
        # Returns the VM state of the VirtualMachine (string value)
669
        def state_str
670
            VM_STATE[state]
671
        end
672

    
673
        # Returns the LCM state of the VirtualMachine (numeric value)
674
        def lcm_state
675
            self['LCM_STATE'].to_i
676
        end
677

    
678
        # Returns the LCM state of the VirtualMachine (string value)
679
        def lcm_state_str
680
            LCM_STATE[lcm_state]
681
        end
682

    
683
        # Returns the short status string for the VirtualMachine
684
        def status
685
            short_state_str=SHORT_VM_STATES[state_str]
686

    
687
            if short_state_str=="actv"
688
                short_state_str=SHORT_LCM_STATES[lcm_state_str]
689
            end
690

    
691
            short_state_str
692
        end
693

    
694
        # Returns the group identifier
695
        # [return] _Integer_ the element's group ID
696
        def gid
697
            self['GID'].to_i
698
        end
699

    
700
        # Returns the deploy_id of the VirtualMachine (numeric value)
701
        def deploy_id
702
            self['DEPLOY_ID']
703
        end
704

    
705
        # Returns the deploy_id of the VirtualMachine (numeric value)
706
        def keep_disks?
707
            !self['USER_TEMPLATE/KEEP_DISKS_ON_DONE'].nil? &&
708
                self['USER_TEMPLATE/KEEP_DISKS_ON_DONE'].downcase=="yes"
709
        end
710

    
711
        # Clones the VM's source Template, replacing the disks with live snapshots
712
        # of the current disks. The VM capacity and NICs are also preserved
713
        #
714
        # @param name [String] Name for the new Template
715
        #
716
        # @return [Integer, OpenNebula::Error] the new Template ID in case of
717
        #   success, error otherwise
718
        def save_as_template(name)
719
            img_ids = []
720
            new_tid = nil
721

    
722
            begin
723
                rc = info()
724
                raise if OpenNebula.is_error?(rc)
725

    
726
                tid = self['TEMPLATE/TEMPLATE_ID']
727
                if tid.nil? || tid.empty?
728
                    rc = Error.new('VM has no template to be saved')
729
                    raise
730
                end
731

    
732
                if state_str() != "POWEROFF"
733
                    rc = Error.new("VM state must be POWEROFF, "<<
734
                        "current state is #{state_str()}, #{lcm_state_str()}")
735
                    raise
736
                end
737

    
738
                # Clone the source template
739
                rc = OpenNebula::Template.new_with_id(tid, @client).clone(name)
740
                raise if OpenNebula.is_error?(rc)
741

    
742
                new_tid = rc
743

    
744
                # Replace the original template's capacity with the actual VM values
745
                replace = ""
746

    
747
                cpu = self['TEMPLATE/CPU']
748
                if !cpu.nil? && !cpu.empty?
749
                    replace << "CPU = #{cpu}\n"
750
                end
751

    
752
                vcpu = self['TEMPLATE/VCPU']
753
                if !vcpu.nil? && !vcpu.empty?
754
                    replace << "VCPU = #{vcpu}\n"
755
                end
756

    
757
                mem = self['TEMPLATE/MEMORY']
758
                if !mem.nil? && !mem.empty?
759
                    replace << "MEMORY = #{mem}\n"
760
                end
761

    
762
                self.each('TEMPLATE/DISK') do |disk|
763
                    # While the previous snapshot is still in progress, we wait
764
                    # indefinitely
765
                    rc = info()
766
                    raise if OpenNebula.is_error?(rc)
767

    
768
                    steps = 0
769
                    while lcm_state_str() == "HOTPLUG_SAVEAS_POWEROFF"
770
                        if steps < 30
771
                            sleep 1
772
                        else
773
                            sleep 15
774
                        end
775

    
776
                        rc = info()
777
                        raise if OpenNebula.is_error?(rc)
778

    
779
                        steps += 1
780
                    end
781

    
782
                    # If the VM is not busy with a previous disk snapshot, we wait
783
                    # but this time with a timeout
784
                    rc = wait_state("POWEROFF")
785
                    raise if OpenNebula.is_error?(rc)
786

    
787
                    disk_id = disk["DISK_ID"]
788
                    if disk_id.nil? || disk_id.empty?
789
                        rc = Error.new('The DISK_ID is missing from the VM template')
790
                        raise
791
                    end
792

    
793
                    image_id = disk["IMAGE_ID"]
794

    
795
                    if !image_id.nil? && !image_id.empty?
796
                        rc = disk_saveas(disk_id.to_i,"#{name}-disk-#{disk_id}","",-1)
797

    
798
                        raise if OpenNebula.is_error?(rc)
799

    
800
                        img_ids << rc.to_i
801

    
802
                        replace << "DISK = [ IMAGE_ID = #{rc} ]\n"
803
                    else
804
                        # Volatile disks cannot be saved, so the definition is copied
805
                        replace << self.template_like_str(
806
                            "TEMPLATE", true, "DISK[DISK_ID=#{disk_id}]") << "\n"
807
                    end
808
                end
809

    
810
                self.each('TEMPLATE/NIC') do |nic|
811
                    nic_id = nic["NIC_ID"]
812
                    if nic_id.nil? || nic_id.empty?
813
                        rc = Error.new('The NIC_ID is missing from the VM template')
814
                        raise
815
                    end
816

    
817
                    net_id = nic["NETWORK_ID"]
818

    
819
                    if !net_id.nil? && !net_id.empty?
820
                        replace << "NIC = [ NETWORK_ID = #{net_id} ]\n"
821
                    else
822
                        # This NIC does not use a Virtual Network
823
                        replace << self.template_like_str(
824
                            "TEMPLATE", true, "NIC[NIC_ID=#{nic_id}]") << "\n"
825
                    end
826
                end
827

    
828
                # Required by the Sunstone Cloud View
829
                replace << "SAVED_TEMPLATE_ID = #{tid}\n"
830

    
831
                new_tmpl = OpenNebula::Template.new_with_id(new_tid, @client)
832

    
833
                rc = new_tmpl.update(replace, true)
834
                raise if OpenNebula.is_error?(rc)
835

    
836
                return new_tid
837

    
838
            rescue
839
                # Rollback. Delete the template and the images created
840
                if !new_tid.nil?
841
                    new_tmpl = OpenNebula::Template.new_with_id(new_tid, @client)
842
                    new_tmpl.delete()
843
                end
844

    
845
                img_ids.each do |id|
846
                    img = OpenNebula::Image.new_with_id(id, @client)
847
                    img.delete()
848
                end
849

    
850
                return rc
851
            end
852
        end
853

    
854
    private
855
        def action(name)
856
            return Error.new('ID not defined') if !@pe_id
857

    
858
            rc = @client.call(VM_METHODS[:action], name, @pe_id)
859
            rc = nil if !OpenNebula.is_error?(rc)
860

    
861
            return rc
862
        end
863

    
864
        def wait_state(state, timeout=10)
865
            vm_state = ""
866
            lcm_state = ""
867

    
868
            timeout.times do
869
                rc = info()
870
                return rc if OpenNebula.is_error?(rc)
871

    
872
                vm_state = state_str()
873
                lcm_state = lcm_state_str()
874

    
875
                if vm_state == state
876
                    return true
877
                end
878

    
879
                sleep 1
880
            end
881

    
882
            return Error.new("Timeout expired for state #{state}. "<<
883
                "VM is in state #{vm_state}, #{lcm_state}")
884
        end
885

    
886
        def wait_lcm_state(state, timeout=10)
887
            vm_state = ""
888
            lcm_state = ""
889

    
890
            timeout.times do
891
                rc = info()
892
                return rc if OpenNebula.is_error?(rc)
893

    
894
                vm_state = state_str()
895
                lcm_state = lcm_state_str()
896

    
897
                if lcm_state == state
898
                    return true
899
                end
900

    
901
                sleep 1
902
            end
903

    
904
            return Error.new("Timeout expired for state #{state}. "<<
905
                "VM is in state #{vm_state}, #{lcm_state}")
906
        end
907
    end
908
end