Statistics
| Branch: | Tag: | Revision:

one / src / oca / ruby / opennebula / virtual_machine.rb @ fdf66ebc

History | View | Annotate | Download (33.3 KB)

1
# -------------------------------------------------------------------------- #
2
# Copyright 2002-2017, OpenNebula Project, OpenNebula Systems                #
3
#                                                                            #
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
5
# not use this file except in compliance with the License. You may obtain    #
6
# a copy of the License at                                                   #
7
#                                                                            #
8
# http://www.apache.org/licenses/LICENSE-2.0                                 #
9
#                                                                            #
10
# Unless required by applicable law or agreed to in writing, software        #
11
# distributed under the License is distributed on an "AS IS" BASIS,          #
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
13
# See the License for the specific language governing permissions and        #
14
# limitations under the License.                                             #
15
#--------------------------------------------------------------------------- #
16

    
17
require 'opennebula/pool_element'
18

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

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

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

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

    
124
        SHORT_VM_STATES={
125
            "INIT"              => "init",
126
            "PENDING"           => "pend",
127
            "HOLD"              => "hold",
128
            "ACTIVE"            => "actv",
129
            "STOPPED"           => "stop",
130
            "SUSPENDED"         => "susp",
131
            "DONE"              => "done",
132
            "FAILED"            => "fail",
133
            "POWEROFF"          => "poff",
134
            "UNDEPLOYED"        => "unde",
135
            "CLONING"           => "clon",
136
            "CLONING_FAILURE"   => "fail"
137
        }
138

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

    
205
        HISTORY_ACTION=%w{none migrate live-migrate shutdown shutdown-hard
206
            undeploy undeploy-hard hold release stop suspend resume boot delete
207
            delete-recreate reboot reboot-hard resched unresched poweroff
208
            poweroff-hard disk-attach disk-detach nic-attach nic-detach
209
            disk-snapshot-create disk-snapshot-delete terminate terminate-hard
210
            disk-resize deploy chown chmod updateconf rename resize update
211
            snapshot-resize snapshot-delete snapshot-revert disk-saveas
212
            disk-snapshot-revert recover retry monitor}
213

    
214
        EXTERNAL_IP_ATTRS = [
215
            'GUEST_IP',
216
            'AWS_IP_ADDRESS',
217
            'AWS_PUBLIC_IP_ADDRESS',
218
            'AWS_PRIVATE_IP_ADDRESS',
219
            'AZ_IPADDRESS',
220
            'SL_PRIMARYIPADDRESS'
221
        ]
222

    
223
        # VirtualMachineDriver constants
224
        module Driver
225
            POLL_ATTRIBUTE = {
226
                :memory          => "MEMORY",
227
                :cpu             => "CPU",
228
                :nettx           => "NETTX",
229
                :netrx           => "NETRX",
230
                :state           => "STATE",
231
                :disk_size       => "DISK_SIZE",
232
                :snapshot_size   => "SNAPSHOT_SIZE"
233
            }
234

    
235
            VM_STATE = {
236
                :active  => 'a',
237
                :paused  => 'p',
238
                :error   => 'e',
239
                :deleted => 'd',
240
                :unknown => '-'
241
            }
242
        end
243

    
244
        # Creates a VirtualMachine description with just its identifier
245
        # this method should be used to create plain VirtualMachine objects.
246
        # +id+ the id of the vm
247
        #
248
        # Example:
249
        #   vnet = VirtualMachine.new(VirtualMachine.build_xml(3),rpc_client)
250
        #
251
        def VirtualMachine.build_xml(pe_id=nil)
252
            if pe_id
253
                vm_xml = "<VM><ID>#{pe_id}</ID></VM>"
254
            else
255
                vm_xml = "<VM></VM>"
256
            end
257

    
258
            XMLElement.build_xml(vm_xml, 'VM')
259
        end
260

    
261
        def VirtualMachine.get_history_action(action)
262
            return HISTORY_ACTION[action.to_i]
263
        end
264

    
265
        # Class constructor
266
        def initialize(xml, client)
267
            super(xml,client)
268
        end
269

    
270
        #######################################################################
271
        # XML-RPC Methods for the Virtual Machine Object
272
        #######################################################################
273

    
274
        # Retrieves the information of the given VirtualMachine.
275
        def info()
276
            super(VM_METHODS[:info], 'VM')
277
        end
278

    
279
        alias_method :info!, :info
280

    
281
        # Allocates a new VirtualMachine in OpenNebula
282
        #
283
        # @param description [String] A string containing the template of
284
        #   the VirtualMachine.
285
        # @param hold [true,false] false to create the VM in pending state,
286
        #   true to create it on hold
287
        #
288
        # @return [nil, OpenNebula::Error] nil in case of success, Error
289
        #   otherwise
290
        def allocate(description, hold=false)
291
            super(VM_METHODS[:allocate], description, hold)
292
        end
293

    
294
        # Replaces the template contents
295
        #
296
        # @param new_template [String] New template contents
297
        # @param append [true, false] True to append new attributes instead of
298
        #   replace the whole template
299
        #
300
        # @return [nil, OpenNebula::Error] nil in case of success, Error
301
        #   otherwise
302
        def update(new_template=nil, append=false)
303
            super(VM_METHODS[:update], new_template, append ? 1 : 0)
304
        end
305

    
306
        # Returns the <USER_TEMPLATE> element in text form
307
        #
308
        # @param indent [true,false] indents the resulting string, defaults to true
309
        #
310
        # @return [String] The USER_TEMPLATE
311
        def user_template_str(indent=true)
312
            template_like_str('USER_TEMPLATE', indent)
313
        end
314

    
315
        # Returns the <USER_TEMPLATE> element in XML form
316
        #
317
        # @return [String] The USER_TEMPLATE
318
        def user_template_xml
319
            if NOKOGIRI
320
                @xml.xpath('USER_TEMPLATE').to_s
321
            else
322
                @xml.elements['USER_TEMPLATE'].to_s
323
            end
324
        end
325

    
326

    
327
        # Initiates the instance of the VM on the target host.
328
        #
329
        # @param host_id [Interger] The host id (hid) of the target host where
330
        #   the VM will be instantiated.
331
        # @param enforce [true|false] If it is set to true, the host capacity
332
        #   will be checked, and the deployment will fail if the host is
333
        #   overcommited. Defaults to false
334
        # @param ds_id [Integer] The System Datastore where to deploy the VM. To
335
        #   use the default, set it to -1
336
        #
337
        # @return [nil, OpenNebula::Error] nil in case of success, Error
338
        #   otherwise
339
        def deploy(host_id, enforce=false, ds_id=-1)
340
            enforce ||= false
341
            ds_id ||= -1
342

    
343
            self.info
344

    
345
            return call(VM_METHODS[:deploy],
346
                        @pe_id,
347
                        host_id.to_i,
348
                        enforce,
349
                        ds_id.to_i)
350
        end
351

    
352
        # Shutdowns an already deployed VM
353
        def terminate(hard=false)
354
            action(hard ? 'terminate-hard' : 'terminate')
355
        end
356

    
357
        alias_method :shutdown, :terminate
358

    
359
        # Shuts down an already deployed VM, saving its state in the system DS
360
        def undeploy(hard=false)
361
            action(hard ? 'undeploy-hard' : 'undeploy')
362
        end
363

    
364
        # Powers off a running VM
365
        def poweroff(hard=false)
366
            action(hard ? 'poweroff-hard' : 'poweroff')
367
        end
368

    
369
        # Reboots an already deployed VM
370
        def reboot(hard=false)
371
            action(hard ? 'reboot-hard' : 'reboot')
372
        end
373

    
374
        # Sets a VM to hold state, scheduler will not deploy it
375
        def hold
376
            action('hold')
377
        end
378

    
379
        # Releases a VM from hold state
380
        def release
381
            action('release')
382
        end
383

    
384
        # Stops a running VM
385
        def stop
386
            action('stop')
387
        end
388

    
389
        # Saves a running VM
390
        def suspend
391
            action('suspend')
392
        end
393

    
394
        # Resumes the execution of a saved VM
395
        def resume
396
            action('resume')
397
        end
398

    
399
        # Attaches a disk to a running VM
400
        #
401
        # @param disk_template [String] Template containing a DISK element
402
        # @return [nil, OpenNebula::Error] nil in case of success, Error
403
        #   otherwise
404
        def disk_attach(disk_template)
405
            return call(VM_METHODS[:attach], @pe_id, disk_template)
406
        end
407

    
408
        alias_method :attachdisk, :disk_attach
409

    
410
        # Detaches a disk from a running VM
411
        #
412
        # @param disk_id [Integer] Id of the disk to be detached
413
        # @return [nil, OpenNebula::Error] nil in case of success, Error
414
        #   otherwise
415
        def disk_detach(disk_id)
416
            return call(VM_METHODS[:detach], @pe_id, disk_id)
417
        end
418

    
419
        alias_method :detachdisk, :disk_detach
420

    
421
        # Attaches a NIC to a running VM
422
        #
423
        # @param nic_template [String] Template containing a NIC element
424
        # @return [nil, OpenNebula::Error] nil in case of success, Error
425
        #   otherwise
426
        def nic_attach(nic_template)
427
            return call(VM_METHODS[:attachnic], @pe_id, nic_template)
428
        end
429

    
430
        # Detaches a NIC from a running VM
431
        #
432
        # @param nic_id [Integer] Id of the NIC to be detached
433
        # @return [nil, OpenNebula::Error] nil in case of success, Error
434
        #   otherwise
435
        def nic_detach(nic_id)
436
            return call(VM_METHODS[:detachnic], @pe_id, nic_id)
437
        end
438

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
647
        # Changes the size of a disk
648
        #
649
        # @param disk_id [Integer] Id of the disk
650
        # @param size [Integer] new size in MiB
651
        #
652
        # @return [nil, OpenNebula::Error] nil in case of success or error
653
        def disk_resize(disk_id, size)
654
            return call(VM_METHODS[:diskresize], @pe_id, disk_id, size.to_s)
655
        end
656

    
657
        # Recovers an ACTIVE VM
658
        #
659
        # @param result [Integer] Recover with failure (0), success (1),
660
        # retry (2), delete (3), delete-recreate (4)
661
        # @param result [info] Additional information needed to recover the VM
662
        # @return [nil, OpenNebula::Error] nil in case of success, Error
663
        #   otherwise
664
        def recover(result)
665
            return call(VM_METHODS[:recover], @pe_id, result)
666
        end
667

    
668
        # Deletes a VM from the pool
669
        def delete(recreate=false)
670
            if recreate
671
                recover(4)
672
            else
673
                recover(3)
674
            end
675
        end
676

    
677
                #  Changes the attributes of a VM in power off, failure and undeploy
678
                #  states
679
                #  @param new_conf, string describing the new attributes. Each attribute
680
        #  will replace the existing ones or delete it if empty. Attributes that
681
        #  can be updated are: INPUT/{TYPE, BUS}; RAW/{TYPE, DATA, DATA_VMX},
682
        #  OS/{BOOT, BOOTLOADER, ARCH, MACHINE, KERNEL, INITRD},
683
        #  FEATURES/{ACPI, APIC, PAE, LOCALTIME, HYPERV, GUEST_AGENT},
684
        #  and GRAPHICS/{TYPE, LISTEN, PASSWD, KEYMAP}
685
        # @return [nil, OpenNebula::Error] nil in case of success, Error
686
        #   otherwise
687
                def updateconf(new_conf)
688
            return call(VM_METHODS[:updateconf], @pe_id, new_conf)
689
                end
690

    
691
        ########################################################################
692
        # Helpers to get VirtualMachine information
693
        ########################################################################
694

    
695
        # Returns the VM state of the VirtualMachine (numeric value)
696
        def state
697
            self['STATE'].to_i
698
        end
699

    
700
        # Returns the VM state of the VirtualMachine (string value)
701
        def state_str
702
            VM_STATE[state]
703
        end
704

    
705
        # Returns the LCM state of the VirtualMachine (numeric value)
706
        def lcm_state
707
            self['LCM_STATE'].to_i
708
        end
709

    
710
        # Returns the LCM state of the VirtualMachine (string value)
711
        def lcm_state_str
712
            LCM_STATE[lcm_state]
713
        end
714

    
715
        # Returns the short status string for the VirtualMachine
716
        def status
717
            short_state_str=SHORT_VM_STATES[state_str]
718

    
719
            if short_state_str=="actv"
720
                short_state_str=SHORT_LCM_STATES[lcm_state_str]
721
            end
722

    
723
            short_state_str
724
        end
725

    
726
        # Returns the group identifier
727
        # [return] _Integer_ the element's group ID
728
        def gid
729
            self['GID'].to_i
730
        end
731

    
732
        # Returns the deploy_id of the VirtualMachine (numeric value)
733
        def deploy_id
734
            self['DEPLOY_ID']
735
        end
736

    
737
        # Clones the VM's source Template, replacing the disks with live snapshots
738
        # of the current disks. The VM capacity and NICs are also preserved
739
        #
740
        # @param name [String] Name for the new Template
741
        # @param name [true,false,nil] Optional, true to make the saved images
742
        # persistent, false make them non-persistent
743
        #
744
        # @return [Integer, OpenNebula::Error] the new Template ID in case of
745
        #   success, error otherwise
746
        REMOVE_VNET_ATTRS = %w{AR_ID BRIDGE CLUSTER_ID IP MAC TARGET NIC_ID NETWORK_ID VN_MAD SECURITY_GROUPS}
747
        def save_as_template(name,description, persistent=nil)
748
            img_ids = []
749
            new_tid = nil
750
            begin
751
                rc = info()
752
                raise if OpenNebula.is_error?(rc)
753

    
754
                tid = self['TEMPLATE/TEMPLATE_ID']
755
                if tid.nil? || tid.empty?
756
                    rc = Error.new('VM has no template to be saved')
757
                    raise
758
                end
759

    
760
                if state_str() != "POWEROFF"
761
                    rc = Error.new("VM state must be POWEROFF, "<<
762
                        "current state is #{state_str()}, #{lcm_state_str()}")
763
                    raise
764
                end
765

    
766
                # Clone the source template
767
                rc = OpenNebula::Template.new_with_id(tid, @client).clone(name)
768
                raise if OpenNebula.is_error?(rc)
769

    
770
                new_tid = rc
771

    
772
                # Replace the original template's capacity with the actual VM values
773
                replace = ""
774
                if !description.nil?
775
                    replace << "DESCRIPTION = #{description}\n"
776
                end
777
                cpu = self['TEMPLATE/CPU']
778
                if !cpu.nil? && !cpu.empty?
779
                    replace << "CPU = #{cpu}\n"
780
                end
781

    
782
                vcpu = self['TEMPLATE/VCPU']
783
                if !vcpu.nil? && !vcpu.empty?
784
                    replace << "VCPU = #{vcpu}\n"
785
                end
786

    
787
                mem = self['TEMPLATE/MEMORY']
788
                if !mem.nil? && !mem.empty?
789
                    replace << "MEMORY = #{mem}\n"
790
                end
791

    
792
                self.each('TEMPLATE/DISK') do |disk|
793
                    # While the previous snapshot is still in progress, we wait
794
                    # indefinitely
795
                    rc = info()
796
                    raise if OpenNebula.is_error?(rc)
797

    
798
                    steps = 0
799
                    while lcm_state_str() == "HOTPLUG_SAVEAS_POWEROFF"
800
                        if steps < 30
801
                            sleep 1
802
                        else
803
                            sleep 15
804
                        end
805

    
806
                        rc = info()
807
                        raise if OpenNebula.is_error?(rc)
808

    
809
                        steps += 1
810
                    end
811

    
812
                    # If the VM is not busy with a previous disk snapshot, we wait
813
                    # but this time with a timeout
814
                    rc = wait_state("POWEROFF")
815
                    raise if OpenNebula.is_error?(rc)
816

    
817
                    disk_id = disk["DISK_ID"]
818
                    if disk_id.nil? || disk_id.empty?
819
                        rc = Error.new('The DISK_ID is missing from the VM template')
820
                        raise
821
                    end
822

    
823
                    image_id = disk["IMAGE_ID"]
824

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

    
828
                        raise if OpenNebula.is_error?(rc)
829

    
830
                        if persistent == true
831
                            OpenNebula::Image.new_with_id(rc.to_i, @client).persistent()
832
                        end
833

    
834
                        img_ids << rc.to_i
835

    
836
                        replace << "DISK = [ IMAGE_ID = #{rc} ]\n"
837
                    else
838
                        # Volatile disks cannot be saved, so the definition is copied
839
                        replace << self.template_like_str(
840
                            "TEMPLATE", true, "DISK[DISK_ID=#{disk_id}]") << "\n"
841
                    end
842
                end
843

    
844
                self.each('TEMPLATE/NIC') do |nic|
845

    
846
                    nic_id = nic["NIC_ID"]
847
                    if nic_id.nil? || nic_id.empty?
848
                        rc = Error.new('The NIC_ID is missing from the VM template')
849
                        raise
850
                    end
851
                     REMOVE_VNET_ATTRS.each do |attr|
852
                        nic.delete_element(attr)
853
                    end
854

    
855
                    replace << self.template_like_str(
856
                        "TEMPLATE", true, "NIC[#{nic}]") << "\n"
857
                end
858

    
859
                # Required by the Sunstone Cloud View
860
                replace << "SAVED_TEMPLATE_ID = #{tid}\n"
861

    
862
                new_tmpl = OpenNebula::Template.new_with_id(new_tid, @client)
863

    
864
                rc = new_tmpl.update(replace, true)
865
                raise if OpenNebula.is_error?(rc)
866

    
867
                return new_tid
868

    
869
            rescue
870
                # Rollback. Delete the template and the images created
871
                if !new_tid.nil?
872
                    new_tmpl = OpenNebula::Template.new_with_id(new_tid, @client)
873
                    new_tmpl.delete()
874
                end
875

    
876
                img_ids.each do |id|
877
                    img = OpenNebula::Image.new_with_id(id, @client)
878
                    img.delete()
879
                end
880

    
881
                return rc
882
            end
883
        end
884

    
885
    private
886
        def action(name)
887
            return Error.new('ID not defined') if !@pe_id
888

    
889
            rc = @client.call(VM_METHODS[:action], name, @pe_id)
890
            rc = nil if !OpenNebula.is_error?(rc)
891

    
892
            return rc
893
        end
894

    
895
        def wait_state(state, timeout=10)
896
            vm_state = ""
897
            lcm_state = ""
898

    
899
            timeout.times do
900
                rc = info()
901
                return rc if OpenNebula.is_error?(rc)
902

    
903
                vm_state = state_str()
904
                lcm_state = lcm_state_str()
905

    
906
                if vm_state == state
907
                    return true
908
                end
909

    
910
                sleep 1
911
            end
912

    
913
            return Error.new("Timeout expired for state #{state}. "<<
914
                "VM is in state #{vm_state}, #{lcm_state}")
915
        end
916

    
917
        def wait_lcm_state(state, timeout=10)
918
            vm_state = ""
919
            lcm_state = ""
920

    
921
            timeout.times do
922
                rc = info()
923
                return rc if OpenNebula.is_error?(rc)
924

    
925
                vm_state = state_str()
926
                lcm_state = lcm_state_str()
927

    
928
                if lcm_state == state
929
                    return true
930
                end
931

    
932
                sleep 1
933
            end
934

    
935
            return Error.new("Timeout expired for state #{state}. "<<
936
                "VM is in state #{vm_state}, #{lcm_state}")
937
        end
938
    end
939
end