Statistics
| Branch: | Tag: | Revision:

one / src / vmm_mad / remotes / vcenter / vcenter_driver.rb @ f22ca784

History | View | Annotate | Download (52.5 KB)

1
# ---------------------------------------------------------------------------- #
2
# Copyright 2010-2015, C12G Labs S.L                                           #
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
# Set up the environment for the driver                                    #
19
# -------------------------------------------------------------------------#
20
ONE_LOCATION = ENV["ONE_LOCATION"] if !defined?(ONE_LOCATION)
21

    
22
if !ONE_LOCATION
23
   BIN_LOCATION = "/usr/bin" if !defined?(BIN_LOCATION)
24
   LIB_LOCATION = "/usr/lib/one" if !defined?(LIB_LOCATION)
25
   ETC_LOCATION = "/etc/one/" if !defined?(ETC_LOCATION)
26
   VAR_LOCATION = "/var/lib/one" if !defined?(VAR_LOCATION)
27
else
28
   BIN_LOCATION = ONE_LOCATION + "/bin" if !defined?(BIN_LOCATION)
29
   LIB_LOCATION = ONE_LOCATION + "/lib" if !defined?(LIB_LOCATION)
30
   ETC_LOCATION = ONE_LOCATION  + "/etc/" if !defined?(ETC_LOCATION)
31
   VAR_LOCATION = ONE_LOCATION + "/var/" if !defined?(VAR_LOCATION)
32
end
33

    
34
ENV['LANG'] = 'C'
35

    
36
$: << LIB_LOCATION+'/ruby/vendors/rbvmomi/lib'
37
$: << LIB_LOCATION+'/ruby'
38

    
39
require 'rbvmomi'
40
require 'yaml'
41
require 'opennebula'
42
require 'base64'
43
require 'openssl'
44

    
45
module VCenterDriver
46

    
47
################################################################################
48
# This class represents a VCenter connection and an associated OpenNebula client
49
# The connection is associated to the VCenter backing a given OpenNebula host.
50
# For the VCenter driver each OpenNebula host represents a VCenter cluster
51
################################################################################
52
class VIClient
53
    attr_reader :vim, :one, :root, :cluster, :user, :pass, :host
54

    
55
    def get_entities(folder, type, entities=[])
56
        return nil if folder == []
57

    
58
        folder.childEntity.each do |child|
59
            name, junk = child.to_s.split('(')
60

    
61
            case name
62
            when "Folder"
63
                get_entities(child, type, entities)
64
            when type
65
                entities.push(child)
66
            end
67
        end
68

    
69
        return entities
70
    end
71

    
72

    
73
    ############################################################################
74
    # Initializr the VIClient, and creates an OpenNebula client. The parameters
75
    # are obtained from the associated OpenNebula host
76
    # @param hid [Integer] The OpenNebula host id with VCenter attributes
77
    ############################################################################
78
    def initialize(hid)
79

    
80
        initialize_one
81

    
82
        @one_host = ::OpenNebula::Host.new_with_id(hid, @one)
83
        rc = @one_host.info
84

    
85
        if ::OpenNebula.is_error?(rc)
86
            raise "Error getting host information: #{rc.message}"
87
        end
88

    
89
        password = @one_host["TEMPLATE/VCENTER_PASSWORD"]
90

    
91
        if !@token.nil?
92
            begin
93
                cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
94

    
95
                cipher.decrypt
96
                cipher.key = @token
97

    
98
                password =  cipher.update(Base64::decode64(password))
99
                password << cipher.final
100
            rescue
101
                raise "Error decrypting vCenter password"
102
            end
103
        end
104

    
105
        connection = {
106
            :host     => @one_host["TEMPLATE/VCENTER_HOST"],
107
            :user     => @one_host["TEMPLATE/VCENTER_USER"],
108
            :password => password
109
        }
110

    
111
        initialize_vim(connection)
112

    
113
        datacenters = get_entities(@root, 'Datacenter')
114

    
115
        datacenters.each {|dc|
116
            ccrs = get_entities(dc.hostFolder, 'ClusterComputeResource')
117

    
118
            next if ccrs.nil?
119

    
120
            @cluster = ccrs.find{ |ccr| @one_host.name == ccr.name }
121

    
122
            (@dc = dc; break) if @cluster
123
        }
124

    
125
        if @dc.nil? || @cluster.nil?
126
            raise "Cannot find DataCenter or ClusterComputeResource for host."
127
        end
128
    end
129

    
130
    ########################################################################
131
    # Initialize a VIConnection based just on the VIM parameters. The
132
    # OpenNebula client is also initilialized
133
    ########################################################################
134
    def self.new_connection(user_opts)
135

    
136
        conn = allocate
137

    
138
        conn.initialize_one
139

    
140
        conn.initialize_vim(user_opts)
141

    
142
        return conn
143
    end
144

    
145
    ########################################################################
146
    # The associated resource pool for this connection
147
    ########################################################################
148
    def resource_pool
149
        rp_name = @one_host["TEMPLATE/VCENTER_RESOURCE_POOL"]
150

    
151
       if rp_name.nil?
152
          @cluster.resourcePool
153
       else
154
          find_resource_pool(rp_name)
155
       end
156
    end
157

    
158
    ########################################################################
159
    # Searches the desired ResourcePool of the DataCenter for the current
160
    # connection. Returns a RbVmomi::VIM::ResourcePool or the default pool
161
    # if not found
162
    # @param rpool [String] the ResourcePool name
163
    ########################################################################
164
    def find_resource_pool(poolName)
165
        baseEntity = @cluster
166
        entityArray = poolName.split('/')
167
        entityArray.each do |entityArrItem|
168
          if entityArrItem != ''
169
            if baseEntity.is_a? RbVmomi::VIM::Folder
170
                baseEntity = baseEntity.childEntity.find { |f|
171
                                  f.name == entityArrItem
172
                              } or return @cluster.resourcePool
173
            elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource
174
                baseEntity = baseEntity.resourcePool.resourcePool.find { |f|
175
                                  f.name == entityArrItem
176
                              } or return @cluster.resourcePool
177
            elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
178
                baseEntity = baseEntity.resourcePool.find { |f|
179
                                  f.name == entityArrItem
180
                              } or return @cluster.resourcePool
181
            else
182
                return @cluster.resourcePool
183
            end
184
          end
185
        end
186

    
187
        if !baseEntity.is_a?(RbVmomi::VIM::ResourcePool) and
188
            baseEntity.respond_to?(:resourcePool)
189
              baseEntity = baseEntity.resourcePool
190
        end
191

    
192
        baseEntity
193
    end
194

    
195
    ########################################################################
196
    # Searches the associated vmFolder of the DataCenter for the current
197
    # connection. Returns a RbVmomi::VIM::VirtualMachine or nil if not found
198
    # @param uuid [String] the UUID of the VM or VM Template
199
    ########################################################################
200
    def find_vm_template(uuid)
201
        vms = get_entities(@dc.vmFolder, 'VirtualMachine')
202

    
203
        return vms.find do |v|
204
            begin
205
                v.config && v.config.uuid == uuid
206
            rescue ManagedObjectNotFound
207
                false
208
            end
209
        end
210
    end
211

    
212
    ########################################################################
213
    # Searches the associated vmFolder of the DataCenter for the current
214
    # connection. Returns a RbVmomi::VIM::VirtualMachine or nil if not found
215
    # @param vm_name [String] the UUID of the VM or VM Template
216
    ########################################################################
217
    def find_vm(vm_name)
218
        vms = get_entities(@dc.vmFolder, 'VirtualMachine')
219

    
220
        return vms.find do |v|
221
            begin
222
                v.name == vm_name
223
            rescue ManagedObjectNotFound
224
                false
225
            end
226
        end
227
    end
228

    
229
    ########################################################################
230
    # Builds a hash with the DataCenter / ClusterComputeResource hierarchy
231
    # for this VCenter.
232
    # @return [Hash] in the form
233
    #   {dc_name [String] => ClusterComputeResources Names [Array - String]}
234
    ########################################################################
235
    def hierarchy(one_client=nil)
236
        vc_hosts = {}
237

    
238
        datacenters = get_entities(@root, 'Datacenter')
239

    
240
        hpool = OpenNebula::HostPool.new((one_client||@one))
241
        rc    = hpool.info
242

    
243
        datacenters.each { |dc|
244
            ccrs = get_entities(dc.hostFolder, 'ClusterComputeResource')
245
            vc_hosts[dc.name] = []
246
            ccrs.each { |c|
247
                puts c.name
248
                if !hpool["HOST[NAME=\"c.name\"]"]
249
                    vc_hosts[dc.name] << c.name
250
                end
251
              }
252
        }
253

    
254
        return vc_hosts
255
    end
256

    
257
    ########################################################################
258
    # Builds a hash with the Datacenter / VM Templates for this VCenter
259
    # @param one_client [OpenNebula::Client] Use this client instead of @one
260
    # @return [Hash] in the form
261
    #   { dc_name [String] => }
262
    ########################################################################
263
    def vm_templates(one_client=nil)
264
        vm_templates = {}
265

    
266
        tpool = OpenNebula::TemplatePool.new(
267
            (one_client||@one), OpenNebula::Pool::INFO_ALL)
268
        rc = tpool.info
269
        # TODO check error
270

    
271
        datacenters = get_entities(@root, 'Datacenter')
272

    
273
        datacenters.each { |dc|
274
            vms = get_entities(dc.vmFolder, 'VirtualMachine')
275

    
276
            tmp = vms.select { |v| v.config && (v.config.template == true) }
277

    
278
            one_tmp = []
279

    
280
            tmp.each { |t|
281
                vi_tmp = VCenterVm.new(self, t)
282

    
283
                if !tpool["VMTEMPLATE/TEMPLATE/PUBLIC_CLOUD[\
284
                        TYPE=\"vcenter\" \
285
                        and VM_TEMPLATE=\"#{vi_tmp.vm.config.uuid}\"]"]
286
                    one_tmp << {
287
                        :name => vi_tmp.vm.name,
288
                        :uuid => vi_tmp.vm.config.uuid,
289
                        :host => vi_tmp.vm.runtime.host.parent.name,
290
                        :one  => vi_tmp.to_one
291
                    }
292
                end
293
            }
294

    
295
            vm_templates[dc.name] = one_tmp
296
        }
297

    
298
        return vm_templates
299
    end
300

    
301
    ########################################################################
302
    # Builds a hash with the Datacenter / Virtual Machines for this VCenter
303
    # @param one_client [OpenNebula::Client] Use this client instead of @one
304
    # @return [Hash] in the form
305
    #   { dc_name [String] => }
306
    ########################################################################
307
    def running_vms(one_client=nil)
308
        running_vms = {}
309

    
310
        vmpool = OpenNebula::VirtualMachinePool.new(
311
            (one_client||@one), OpenNebula::Pool::INFO_ALL)
312
        rc = vmpool.info
313

    
314
        hostpool = OpenNebula::HostPool.new((one_client||@one))
315
        rc = hostpool.info
316
        # TODO check error
317

    
318
        datacenters = get_entities(@root, 'Datacenter')
319

    
320
        datacenters.each { |dc|
321
            vms     = get_entities(dc.vmFolder, 'VirtualMachine')
322
            ccrs    = get_entities(dc.hostFolder, 'ClusterComputeResource')
323

    
324
            vm_list = vms.select { |v|
325
                # Get rid of VM Templates and VMs not in running state
326
                v.config &&
327
                v.config.template != true &&
328
                v.summary.runtime.powerState == "poweredOn"
329
            }
330

    
331
            one_tmp = []
332

    
333
            vm_list.each { |v|
334
                vi_tmp = VCenterVm.new(self, v)
335

    
336
                # Do not reimport VMs deployed by OpenNebula
337
                # since the core will get confused with the IDs
338
                next if vi_tmp.vm.name.match(/one-\d/)
339

    
340
                container_hostname = vi_tmp.vm.runtime.host.parent.name
341

    
342
                cluster_name = ccrs.collect { |c|
343
                  found_host=c.host.select {|h|
344
                           h.parent.name == container_hostname}
345
                   found_host.first.parent.name if found_host.size > 0
346
                }.first
347

    
348
                if !vmpool["VM/USER_TEMPLATE/PUBLIC_CLOUD[\
349
                        TYPE=\"vcenter\" \
350
                        and VM_TEMPLATE=\"#{vi_tmp.vm.config.uuid}\"]"]
351

    
352
                    host_id = name_to_id(container_hostname,hostpool,"HOST")[1]
353

    
354
                    one_tmp << {
355
                        :name => vi_tmp.vm.name,
356
                        :uuid => vi_tmp.vm.config.uuid,
357
                        :host => container_hostname,
358
                        :host_id => host_id,
359
                        :one  => vi_tmp.vm_to_one
360
                    }
361
                end
362
            }
363

    
364
            running_vms[dc.name] = one_tmp
365
        }
366

    
367
        return running_vms
368
    end
369

    
370
    def name_to_id(name, pool, ename)
371
            objects=pool.select {|object| object.name==name }
372

    
373
            if objects.length>0
374
                if objects.length>1
375
                    return -1, "There are multiple #{ename}s with name #{name}."
376
                else
377
                    result = objects.first.id
378
                end
379
            else
380
                return -1, "#{ename} named #{name} not found."
381
            end
382

    
383
            return 0, result
384
    end
385

    
386
    ########################################################################
387
    # Builds a hash with the Datacenter / CCR (Distributed)Networks
388
    # for this VCenter
389
    # @param one_client [OpenNebula::Client] Use this client instead of @one
390
    # @return [Hash] in the form
391
    #   { dc_name [String] => Networks [Array] }
392
    ########################################################################
393
    def vcenter_networks(one_client=nil)
394
        vcenter_networks = {}
395

    
396
        vnpool = OpenNebula::VirtualNetworkPool.new(
397
            (one_client||@one), OpenNebula::Pool::INFO_ALL)
398
        rc = vnpool.info
399
        # TODO check error
400
        #
401
        datacenters = get_entities(@root, 'Datacenter')
402

    
403
        datacenters.each { |dc|
404
            networks = get_entities(dc.networkFolder, 'Network' )
405
            one_nets = []
406

    
407
            networks.each { |n|
408
                if !vnpool["VNET[BRIDGE=\"#{n[:name]}\"]/\
409
                        TEMPLATE[VCENTER_TYPE=\"Port Group\"]"]
410
                    one_nets << {
411
                        :name   => n.name,
412
                        :bridge => n.name,
413
                        :type   => "Port Group",
414
                        :one    => "NAME   = \"#{n[:name]}\"\n" \
415
                                   "BRIDGE = \"#{n[:name]}\"\n" \
416
                                   "VCENTER_TYPE = \"Port Group\""
417
                    }
418
                end
419
            }
420

    
421
            networks = get_entities(dc.networkFolder,
422
                                    'DistributedVirtualPortgroup' )
423

    
424
            networks.each { |n|
425
                if !vnpool["VNET[BRIDGE=\"#{n[:name]}\"]/\
426
                        TEMPLATE[VCENTER_TYPE=\"Distributed Port Group\"]"]
427
                    vnet_template = "NAME   = \"#{n[:name]}\"\n" \
428
                                    "BRIDGE = \"#{n[:name]}\"\n" \
429
                                    "VCENTER_TYPE = \"Distributed Port Group\""
430

    
431

    
432
                    default_pc = n.config.defaultPortConfig
433

    
434
                    has_vlan = false
435
                    vlan_str = ""
436

    
437
                    if default_pc.methods.include? :vlan
438
                       has_vlan = default_pc.vlan.methods.include? :vlanId
439
                    end
440

    
441
                    if has_vlan
442
                        vlan     = n.config.defaultPortConfig.vlan.vlanId
443

    
444
                        if vlan != 0
445
                            if vlan.is_a? Array
446
                                vlan.each{|v|
447
                                    vlan_str += v.start.to_s + ".." +
448
                                                v.end.to_s + ","
449
                                }
450
                                vlan_str.chop!
451
                            else
452
                                vlan_str = vlan.to_s
453
                            end
454
                        end
455
                    end
456

    
457
                    if !vlan_str.empty?
458
                        vnet_template << "VLAN=\"YES\"\n" \
459
                                         "VLAN_ID=#{vlan_str}\n"
460
                    end
461

    
462
                    one_net = {:name   => n.name,
463
                               :bridge => n.name,
464
                               :type   => "Distributed Port Group",
465
                               :one    => vnet_template}
466

    
467
                    one_net[:vlan] = vlan_str if !vlan_str.empty?
468

    
469
                    one_nets << one_net
470
                end
471
            }
472

    
473
            vcenter_networks[dc.name] = one_nets
474
        }
475

    
476
        return vcenter_networks
477
    end
478

    
479
    def self.translate_hostname(hostname)
480
        host_pool = OpenNebula::HostPool.new(::OpenNebula::Client.new())
481
        rc        = host_pool.info
482
        raise "Could not find host #{hostname}" if OpenNebula.is_error?(rc)
483

    
484
        host = host_pool.select {|host_element| host_element.name==hostname }
485
        return host.first.id
486
    end
487

    
488
    ############################################################################
489
    # Initialize an OpenNebula connection with the default ONE_AUTH
490
    ############################################################################
491
    def initialize_one
492
        begin
493
            @one   = ::OpenNebula::Client.new()
494
            system = ::OpenNebula::System.new(@one)
495

    
496
            config = system.get_configuration()
497

    
498
            if ::OpenNebula.is_error?(config)
499
                raise "Error getting oned configuration : #{config.message}"
500
            end
501

    
502
            @token = config["ONE_KEY"]
503
        rescue Exception => e
504
            raise "Error initializing OpenNebula client: #{e.message}"
505
        end
506
    end
507

    
508
    ############################################################################
509
    # Initialize a connection with vCenter. Options
510
    # @param options[Hash] with:
511
    #    :user => The vcenter user
512
    #    :password => Password for the user
513
    #    :host => vCenter hostname or IP
514
    #    :insecure => SSL (optional, defaults to true)
515
    ############################################################################
516
    def initialize_vim(user_opts={})
517
        opts = {
518
            :insecure => true
519
        }.merge(user_opts)
520

    
521
        @user = opts[:user]
522
        @pass = opts[:password]
523
        @host = opts[:host]
524

    
525
        begin
526
            @vim  = RbVmomi::VIM.connect(opts)
527
            @root = @vim.root
528
        rescue Exception => e
529
            raise "Error connecting to #{@host}: #{e.message}"
530
        end
531
    end
532
end
533

    
534
################################################################################
535
# This class is an OpenNebula hosts that abstracts a vCenter cluster. It
536
# includes the functionality needed to monitor the cluster and report the ESX
537
# hosts and VM status of the cluster.
538
################################################################################
539
class VCenterHost < ::OpenNebula::Host
540
    attr_reader :vc_client, :vc_root, :cluster, :host, :client
541

    
542
    ############################################################################
543
    # Initialize the VCenterHost by looking for the associated objects of the
544
    # VIM hierarchy
545
    # client [VIClient] to interact with the associated vCenter
546
    ############################################################################
547
    def initialize(client)
548
        @client  = client
549
        @cluster = client.cluster
550

    
551
        @resource_pool = client.resource_pool
552
    end
553

    
554
    ########################################################################
555
    #  Creates an OpenNebula host representing a cluster in this VCenter
556
    #  @param cluster_name[String] the name of the cluster in the vcenter
557
    #  @param client [VIClient] to create the host
558
    #  @return In case of success [0, host_id] or [-1, error_msg]
559
    ########################################################################
560
    def self.to_one(cluster_name, client)
561
        one_host = ::OpenNebula::Host.new(::OpenNebula::Host.build_xml,
562
            client.one)
563

    
564
        rc = one_host.allocate(cluster_name, 'vcenter', 'vcenter', 'dummy',
565
                ::OpenNebula::ClusterPool::NONE_CLUSTER_ID)
566

    
567
        return -1, rc.message if ::OpenNebula.is_error?(rc)
568

    
569
        template = "VCENTER_HOST=\"#{client.host}\"\n"\
570
                   "VCENTER_PASSWORD=\"#{client.pass}\"\n"\
571
                   "VCENTER_USER=\"#{client.user}\"\n"
572

    
573
        rc = one_host.update(template, false)
574

    
575
        if ::OpenNebula.is_error?(rc)
576
            error = rc.message
577

    
578
            rc = one_host.delete
579

    
580
            if ::OpenNebula.is_error?(rc)
581
                error << ". Host #{cluster_name} could not be"\
582
                    " deleted: #{rc.message}."
583
            end
584

    
585
            return -1, error
586
        end
587

    
588
        return 0, one_host.id
589
    end
590

    
591
    ############################################################################
592
    # Generate an OpenNebula monitor string for this host. Reference:
593
    # https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/Reference
594
    # Guide/vim.ComputeResource.Summary.html
595
    #   - effectiveCpu: Effective CPU resources (in MHz) available to run
596
    #     VMs. This is the aggregated from all running hosts excluding hosts in
597
    #     maintenance mode or unresponsive are not counted.
598
    #   - effectiveMemory: Effective memory resources (in MB) available to run
599
    #     VMs. Equivalente to effectiveCpu.
600
    #   - numCpuCores: Number of physical CPU cores.
601
    #   - numEffectiveHosts: Total number of effective hosts.
602
    #   - numHosts:Total number of hosts.
603
    #   - totalCpu: Aggregated CPU resources of all hosts, in MHz.
604
    #   - totalMemory: Aggregated memory resources of all hosts, in bytes.
605
    ############################################################################
606
    def monitor_cluster
607
        #Load the host systems
608
        summary = @cluster.summary
609

    
610
        mhz_core = summary.totalCpu.to_f / summary.numCpuCores.to_f
611
        eff_core = summary.effectiveCpu.to_f / mhz_core
612

    
613
        free_cpu  = sprintf('%.2f', eff_core * 100).to_f
614
        total_cpu = summary.numCpuCores.to_f * 100
615
        used_cpu  = sprintf('%.2f', total_cpu - free_cpu).to_f
616

    
617
        total_mem = summary.totalMemory.to_i / 1024
618
        free_mem  = summary.effectiveMemory.to_i * 1024
619

    
620
        str_info = ""
621

    
622
        # System
623
        str_info << "HYPERVISOR=vcenter\n"
624
        str_info << "PUBLIC_CLOUD=YES\n"
625
        str_info << "TOTALHOST=" << summary.numHosts.to_s << "\n"
626
        str_info << "AVAILHOST=" << summary.numEffectiveHosts.to_s << "\n"
627

    
628
        # CPU
629
        str_info << "CPUSPEED=" << mhz_core.to_s   << "\n"
630
        str_info << "TOTALCPU=" << total_cpu.to_s << "\n"
631
        str_info << "USEDCPU="  << used_cpu.to_s  << "\n"
632
        str_info << "FREECPU="  << free_cpu.to_s << "\n"
633

    
634
        # Memory
635
        str_info << "TOTALMEMORY=" << total_mem.to_s << "\n"
636
        str_info << "FREEMEMORY="  << free_mem.to_s << "\n"
637
        str_info << "USEDMEMORY="  << (total_mem - free_mem).to_s
638
    end
639

    
640
    ############################################################################
641
    # Generate a template with information for each ESX Host. Reference:
642
    # http://pubs.vmware.com/vi-sdk/visdk250/ReferenceGuide/vim.HostSystem.html
643
    #   - Summary: Basic information about the host, including connection state
644
    #     - hardware: Hardware configuration of the host. This might not be
645
    #       available for a disconnected host.
646
    #     - quickStats: Basic host statistics.
647
    ############################################################################
648
    def monitor_host_systems
649
        host_info = ""
650

    
651
        @cluster.host.each{|h|
652
            next if h.runtime.connectionState != "connected"
653

    
654
            summary = h.summary
655
            hw      = summary.hardware
656
            stats   = summary.quickStats
657

    
658
            total_cpu = hw.numCpuCores * 100
659
            used_cpu  = (stats.overallCpuUsage.to_f / hw.cpuMhz.to_f) * 100
660
            used_cpu  = sprintf('%.2f', used_cpu).to_f # Trim precission
661
            free_cpu  = total_cpu - used_cpu
662

    
663
            total_memory = hw.memorySize/1024
664
            used_memory  = stats.overallMemoryUsage*1024
665
            free_memory  = total_memory - used_memory
666

    
667
            host_info << "\nHOST=["
668
            host_info << "STATE=on,"
669
            host_info << "HOSTNAME=\""  << h.name.to_s  << "\","
670
            host_info << "MODELNAME=\"" << hw.cpuModel.to_s  << "\","
671
            host_info << "CPUSPEED="    << hw.cpuMhz.to_s    << ","
672
            host_info << "MAX_CPU="    << total_cpu.to_s << ","
673
            host_info << "USED_CPU="     << used_cpu.to_s  << ","
674
            host_info << "FREE_CPU="     << free_cpu.to_s << ","
675
            host_info << "MAX_MEM=" << total_memory.to_s << ","
676
            host_info << "USED_MEM="  << used_memory.to_s  << ","
677
            host_info << "FREE_MEM="  << free_memory.to_s
678
            host_info << "]"
679
        }
680

    
681
        return host_info
682
    end
683

    
684
    def monitor_vms
685
        str_info = ""
686
        @resource_pool.vm.each { |v|
687
            name   = v.name
688
            number = -1
689
            number = name.split('-').last if (name =~ /^one-\d*$/)
690

    
691
            vm = VCenterVm.new(@client, v)
692
            vm.monitor
693

    
694
            next if !vm.vm.config
695

    
696
            str_info << "\nVM = ["
697
            str_info << "ID=#{number},"
698
            str_info << "DEPLOY_ID=\"#{vm.vm.config.uuid}\","
699
            str_info << "VM_NAME=\"#{name}\","
700
            if number == -1
701
             vm_template_to_one = Base64.encode64(vm.vm_to_one).gsub("\n","")
702
             str_info << "IMPORT_TEMPLATE=\"#{vm_template_to_one}\","
703
            end
704
            str_info << "POLL=\"#{vm.info}\"]"
705
        }
706

    
707
        return str_info
708
    end
709
end
710

    
711
################################################################################
712
# This class is a high level abstraction of a VI VirtualMachine class with
713
# OpenNebula semantics.
714
################################################################################
715

    
716
class VCenterVm
717
    attr_reader :vm
718

    
719
    ############################################################################
720
    #  Creates a new VIVm using a RbVmomi::VirtualMachine object
721
    #    @param client [VCenterClient] client to connect to vCenter
722
    #    @param vm_vi [RbVmomi::VirtualMachine] it will be used if not nil
723
    ########################################################################
724
    def initialize(client, vm_vi )
725
        @vm     = vm_vi
726
        @client = client
727

    
728
        @used_cpu    = 0
729
        @used_memory = 0
730

    
731
        @netrx = 0
732
        @nettx = 0
733
    end
734

    
735
    ############################################################################
736
    # Deploys a VM
737
    #  @xml_text XML repsentation of the VM
738
    ############################################################################
739
    def self.deploy(xml_text, lcm_state, deploy_id, hostname)
740
        if lcm_state == "BOOT" || lcm_state == "BOOT_FAILURE"
741
            return clone_vm(xml_text)
742
        else
743
            hid         = VIClient::translate_hostname(hostname)
744
            connection  = VIClient.new(hid)
745
            vm          = connection.find_vm_template(deploy_id)
746

    
747
            # Find out if we need to reconfigure capacity
748
            xml = REXML::Document.new xml_text
749

    
750
            expected_cpu    = xml.root.elements["//TEMPLATE/VCPU"].text
751
            expected_memory = xml.root.elements["//TEMPLATE/MEMORY"].text
752
            current_cpu     = vm.config.hardware.numCPU
753
            current_memory  = vm.config.hardware.memoryMB
754

    
755
            if current_cpu != expected_cpu or current_memory != expected_memory
756
                capacity_hash = {:numCPUs  => expected_cpu.to_i,
757
                                 :memoryMB => expected_memory }
758
                spec = RbVmomi::VIM.VirtualMachineConfigSpec(capacity_hash)
759
                vm.ReconfigVM_Task(:spec => spec).wait_for_completion
760
            end
761

    
762
            vm.PowerOnVM_Task.wait_for_completion
763
            return vm.config.uuid
764
        end
765
    end
766

    
767
    ############################################################################
768
    # Cancels a VM
769
    #  @param deploy_id vcenter identifier of the VM
770
    #  @param hostname name of the host (equals the vCenter cluster)
771
    ############################################################################
772
    def self.cancel(deploy_id, hostname, lcm_state)
773
        case lcm_state
774
            when "SHUTDOWN_POWEROFF", "SHUTDOWN_UNDEPLOY"
775
                shutdown(deploy_id, hostname, lcm_state)
776
            when "CANCEL", "LCM_INIT", "CLEANUP_RESUBMIT"
777
                hid         = VIClient::translate_hostname(hostname)
778
                connection  = VIClient.new(hid)
779
                vm          = connection.find_vm_template(deploy_id)
780

    
781
                begin
782
                    if vm.summary.runtime.powerState == "poweredOn"
783
                        vm.PowerOffVM_Task.wait_for_completion
784
                    end
785
                rescue
786
                end
787
                vm.Destroy_Task.wait_for_completion
788
        end
789
    end
790

    
791
    ############################################################################
792
    # Saves a VM
793
    #  @param deploy_id vcenter identifier of the VM
794
    #  @param hostname name of the host (equals the vCenter cluster)
795
    ############################################################################
796
    def self.save(deploy_id, hostname, lcm_state)
797
        case lcm_state
798
            when "SAVE_MIGRATE"
799
                raise "Migration between vCenters cluster not supported"
800
            when "SAVE_SUSPEND", "SAVE_STOP"
801
                hid         = VIClient::translate_hostname(hostname)
802
                connection  = VIClient.new(hid)
803
                vm          = connection.find_vm_template(deploy_id)
804

    
805
                vm.SuspendVM_Task.wait_for_completion
806
        end
807
    end
808

    
809
    ############################################################################
810
    # Resumes a VM
811
    #  @param deploy_id vcenter identifier of the VM
812
    #  @param hostname name of the host (equals the vCenter cluster)
813
    ############################################################################
814
    def self.resume(deploy_id, hostname)
815
        hid         = VIClient::translate_hostname(hostname)
816
        connection  = VIClient.new(hid)
817
        vm          = connection.find_vm_template(deploy_id)
818

    
819
        vm.PowerOnVM_Task.wait_for_completion
820
    end
821

    
822
    ############################################################################
823
    # Reboots a VM
824
    #  @param deploy_id vcenter identifier of the VM
825
    #  @param hostname name of the host (equals the vCenter cluster)
826
    ############################################################################
827
    def self.reboot(deploy_id, hostname)
828
        hid         = VIClient::translate_hostname(hostname)
829
        connection  = VIClient.new(hid)
830

    
831
        vm          = connection.find_vm_template(deploy_id)
832

    
833
        vm.RebootGuest.wait_for_completion
834
    end
835

    
836
    ############################################################################
837
    # Resets a VM
838
    #  @param deploy_id vcetranslate_hostnamnter identifier of the VM
839
    #  @param hostname name of the host (equals the vCenter cluster)
840
    ############################################################################
841
    def self.reset(deploy_id, hostname)
842
        hid         = VIClient::translate_hostname(hostname)
843
        connection  = VIClient.new(hid)
844

    
845
        vm          = connection.find_vm_template(deploy_id)
846

    
847
        vm.ResetVM_Task.wait_for_completion
848
    end
849

    
850
    ############################################################################
851
    # Shutdown a VM
852
    #  @param deploy_id vcenter identifier of the VM
853
    #  @param hostname name of the host (equals the vCenter cluster)
854
    ############################################################################
855
    def self.shutdown(deploy_id, hostname, lcm_state)
856
        hid         = VIClient::translate_hostname(hostname)
857
        connection  = VIClient.new(hid)
858

    
859
        vm          = connection.find_vm_template(deploy_id)
860

    
861
        case lcm_state
862
            when "SHUTDOWN"
863
                begin
864
                    vm.ShutdownGuest.wait_for_completion
865
                rescue
866
                end
867
                vm.PowerOffVM_Task.wait_for_completion
868
                vm.Destroy_Task.wait_for_completion
869
            when "SHUTDOWN_POWEROFF", "SHUTDOWN_UNDEPLOY"
870
                begin
871
                    vm.ShutdownGuest.wait_for_completion
872
                rescue
873
                end
874
                vm.PowerOffVM_Task.wait_for_completion
875
        end
876
    end
877

    
878
    ############################################################################
879
    # Create VM snapshot
880
    #  @param deploy_id vcenter identifier of the VM
881
    #  @param hostname name of the host (equals the vCenter cluster)
882
    #  @param snaphot_name name of the snapshot
883
    ############################################################################
884
    def self.create_snapshot(deploy_id, hostname, snapshot_name)
885
        hid         = VIClient::translate_hostname(hostname)
886
        connection  = VIClient.new(hid)
887

    
888
        snapshot_hash = {
889
            :name => snapshot_name,
890
            :description => "OpenNebula Snapshot of VM #{deploy_id}",
891
            :memory => true,
892
            :quiesce => true
893
        }
894

    
895
        vm          = connection.find_vm_template(deploy_id)
896

    
897
        vm.CreateSnapshot_Task(snapshot_hash).wait_for_completion
898

    
899
        return snapshot_name
900
    end
901

    
902
    ############################################################################
903
    # Find VM snapshot
904
    #  @param list root list of VM snapshots
905
    #  @param snaphot_name name of the snapshot
906
    ############################################################################
907
    def self.find_snapshot(list, snapshot_name)
908
        list.each do |i|
909
            if i.name == snapshot_name
910
                return i.snapshot
911
            elsif !i.childSnapshotList.empty?
912
                snap = find_snapshot(i.childSnapshotList, snapshot_name)
913
                return snap if snap
914
            end
915
        end
916

    
917
        nil
918
    end
919

    
920
    ############################################################################
921
    # Delete VM snapshot
922
    #  @param deploy_id vcenter identifier of the VM
923
    #  @param hostname name of the host (equals the vCenter cluster)
924
    #  @param snaphot_name name of the snapshot
925
    ############################################################################
926
    def self.delete_snapshot(deploy_id, hostname, snapshot_name)
927
        hid         = VIClient::translate_hostname(hostname)
928
        connection  = VIClient.new(hid)
929

    
930
        vm          = connection.find_vm_template(deploy_id)
931

    
932
        list = vm.snapshot.rootSnapshotList
933

    
934
        snapshot = find_snapshot(list, snapshot_name)
935
        return nil if !snapshot
936

    
937
        delete_snapshot_hash = {
938
            :_this => snapshot,
939
            :removeChildren => false
940
        }
941

    
942
        snapshot.RemoveSnapshot_Task(delete_snapshot_hash).wait_for_completion
943
    end
944

    
945
    ############################################################################
946
    # Revert VM snapshot
947
    #  @param deploy_id vcenter identifier of the VM
948
    #  @param hostname name of the host (equals the vCenter cluster)
949
    #  @param snaphot_name name of the snapshot
950
    ############################################################################
951
    def self.revert_snapshot(deploy_id, hostname, snapshot_name)
952
        hid         = VIClient::translate_hostname(hostname)
953
        connection  = VIClient.new(hid)
954

    
955
        vm          = connection.find_vm_template(deploy_id)
956

    
957
        list = vm.snapshot.rootSnapshotList
958

    
959
        snapshot = find_snapshot(list, snapshot_name)
960
        return nil if !snapshot
961

    
962
        revert_snapshot_hash = {
963
            :_this => snapshot
964
        }
965

    
966
        snapshot.RevertToSnapshot_Task(revert_snapshot_hash).wait_for_completion
967
    end
968

    
969
    ############################################################################
970
    # Attach NIC to a VM
971
    #  @param deploy_id vcenter identifier of the VM
972
    #  @param mac MAC address of the NIC to be attached
973
    #  @param bridge name of the Network in vCenter
974
    #  @param model model of the NIC to be attached
975
    #  @param host hostname of the ESX where the VM is running
976
    ############################################################################
977
    def self.attach_nic(deploy_id, mac, bridge, model, host)
978
        hid         = VIClient::translate_hostname(host)
979
        connection  = VIClient.new(hid)
980

    
981
        vm          = connection.find_vm_template(deploy_id)
982

    
983
        spec_hash   = calculate_addnic_spec(vm, mac, bridge, model)
984

    
985
        spec        = RbVmomi::VIM.VirtualMachineConfigSpec({:deviceChange =>
986
                                                              [spec_hash]})
987

    
988
        vm.ReconfigVM_Task(:spec => spec).wait_for_completion
989
    end
990

    
991
    ############################################################################
992
    # Detach NIC from a VM
993
    ############################################################################
994
    def self.detach_nic(deploy_id, mac, host)
995
        hid         = VIClient::translate_hostname(host)
996
        connection  = VIClient.new(hid)
997

    
998
        vm   = connection.find_vm_template(deploy_id)
999

    
1000
        nic  = vm.config.hardware.device.find { |d|
1001
                is_nic?(d) && (d.macAddress ==  mac)
1002
        }
1003

    
1004
        raise "Could not find NIC with mac address #{mac}" if nic.nil?
1005

    
1006
        spec = {
1007
            :deviceChange => [
1008
                :operation => :remove,
1009
                :device => nic
1010
            ]
1011
        }
1012

    
1013
        vm.ReconfigVM_Task(:spec => spec).wait_for_completion
1014
    end
1015

    
1016
    ########################################################################
1017
    #  Initialize the vm monitor information
1018
    ########################################################################
1019
    def monitor
1020
        @summary = @vm.summary
1021
        @state   = state_to_c(@summary.runtime.powerState)
1022

    
1023
        if @state != 'a'
1024
            @used_cpu    = 0
1025
            @used_memory = 0
1026

    
1027
            @netrx = 0
1028
            @nettx = 0
1029

    
1030
            return
1031
        end
1032

    
1033
        @used_memory = @summary.quickStats.hostMemoryUsage * 1024
1034

    
1035
        host   = @vm.runtime.host
1036
        cpuMhz = host.summary.hardware.cpuMhz.to_f
1037

    
1038
        @used_cpu   =
1039
                ((@summary.quickStats.overallCpuUsage.to_f / cpuMhz) * 100).to_s
1040
        @used_cpu   = sprintf('%.2f',@used_cpu).to_s
1041

    
1042
        # Check for negative values
1043
        @used_memory = 0 if @used_memory.to_i < 0
1044
        @used_cpu    = 0 if @used_cpu.to_i < 0
1045

    
1046
        @esx_host       = @vm.summary.runtime.host.name
1047
        @guest_ip       = @vm.guest.ipAddress
1048
        @guest_state    = @vm.guest.guestState
1049
        @vmware_tools   = @vm.guest.toolsRunningStatus
1050
        @vmtools_ver    = @vm.guest.toolsVersion
1051
        @vmtools_verst  = @vm.guest.toolsVersionStatus
1052

    
1053

    
1054
    end
1055

    
1056
    ########################################################################
1057
    #  Generates a OpenNebula IM Driver valid string with the monitor info
1058
    ########################################################################
1059
    def info
1060
      return 'STATE=d' if @state == 'd'
1061

    
1062
      str_info = ""
1063

    
1064
      str_info << "GUEST_IP=" << @guest_ip.to_s << " " if @guest_ip
1065
      str_info << "STATE="                      << @state                << " "
1066
      str_info << "CPU="                        << @used_cpu.to_s        << " "
1067
      str_info << "MEMORY="                     << @used_memory.to_s     << " "
1068
      str_info << "NETRX="                      << @netrx.to_s          << " "
1069
      str_info << "NETTX="                      << @nettx.to_s          << " "
1070
      str_info << "ESX_HOST="                   << @esx_host.to_s        << " "
1071
      str_info << "GUEST_STATE="                << @guest_state.to_s     << " "
1072
      str_info << "VMWARETOOLS_RUNNING_STATUS=" << @vmware_tools.to_s    << " "
1073
      str_info << "VMWARETOOLS_VERSION="        << @vmtools_ver.to_s     << " "
1074
      str_info << "VMWARETOOLS_VERSION_STATUS=" << @vmtools_verst.to_s   << " "
1075
    end
1076

    
1077
    ########################################################################
1078
    # Generates an OpenNebula Template for this VCenterVm
1079
    #
1080
    #
1081
    ########################################################################
1082
    def to_one
1083
        str = "NAME   = \"#{@vm.name}\"\n"\
1084
              "CPU    = \"#{@vm.config.hardware.numCPU}\"\n"\
1085
              "vCPU   = \"#{@vm.config.hardware.numCPU}\"\n"\
1086
              "MEMORY = \"#{@vm.config.hardware.memoryMB}\"\n"\
1087
              "HYPERVISOR = \"vcenter\"\n"\
1088
              "PUBLIC_CLOUD = [\n"\
1089
              "  TYPE        =\"vcenter\",\n"\
1090
              "  VM_TEMPLATE =\"#{@vm.config.uuid}\"\n"\
1091
              "]\n"\
1092
              "GRAPHICS = [\n"\
1093
              "  TYPE     =\"vnc\",\n"\
1094
              "  LISTEN   =\"0.0.0.0\"\n"\
1095
              "]\n"\
1096
         "SCHED_REQUIREMENTS=\"NAME=\\\"#{@vm.runtime.host.parent.name}\\\"\"\n"
1097

    
1098
        if @vm.config.annotation.nil? || @vm.config.annotation.empty?
1099
            str << "DESCRIPTION = \"vCenter Template imported by OpenNebula"\
1100
                " from Cluster #{@vm.runtime.host.parent.name}\"\n"
1101
        else
1102
            notes = @vm.config.annotation.gsub("\\", "\\\\").gsub("\"", "\\\"")
1103
            str << "DESCRIPTION = \"#{notes}\"\n"
1104
        end
1105

    
1106
        case @vm.guest.guestFullName
1107
            when /CentOS/i
1108
                str << "LOGO=images/logos/centos.png"
1109
            when /Debian/i
1110
                str << "LOGO=images/logos/debian.png"
1111
            when /Red Hat/i
1112
                str << "LOGO=images/logos/redhat.png"
1113
            when /Ubuntu/i
1114
                str << "LOGO=images/logos/ubuntu.png"
1115
            when /Windows XP/i
1116
                str << "LOGO=images/logos/windowsxp.png"
1117
            when /Windows/i
1118
                str << "LOGO=images/logos/windows8.png"
1119
            when /Linux/i
1120
                str << "LOGO=images/logos/linux.png"
1121
        end
1122

    
1123
        return str
1124
    end
1125

    
1126
    ########################################################################
1127
    # Generates an OpenNebula VirtualMachine for this VCenterVm
1128
    #
1129
    #
1130
    ########################################################################
1131
    def vm_to_one
1132
        host_name = @vm.runtime.host.parent.name
1133

    
1134
        str = "NAME   = \"#{@vm.name}\"\n"\
1135
              "CPU    = \"#{@vm.config.hardware.numCPU}\"\n"\
1136
              "vCPU   = \"#{@vm.config.hardware.numCPU}\"\n"\
1137
              "MEMORY = \"#{@vm.config.hardware.memoryMB}\"\n"\
1138
              "HYPERVISOR = \"vcenter\"\n"\
1139
              "PUBLIC_CLOUD = [\n"\
1140
              "  TYPE        =\"vcenter\",\n"\
1141
              "  VM_TEMPLATE =\"#{@vm.config.uuid}\"\n"\
1142
              "]\n"\
1143
              "IMPORT_VM_ID    = \"#{@vm.config.uuid}\"\n"\
1144
              "SCHED_REQUIREMENTS=\"NAME=\\\"#{host_name}\\\"\"\n"
1145

    
1146
        vp     = @vm.config.extraConfig.select{|v|
1147
                                           v[:key]=="remotedisplay.vnc.port"}
1148
        keymap = @vm.config.extraConfig.select{|v|
1149
                                           v[:key]=="remotedisplay.vnc.keymap"}
1150

    
1151
        if vp.size > 0
1152
            str << "GRAPHICS = [\n"\
1153
                   "  TYPE     =\"vnc\",\n"\
1154
                   "  LISTEN   =\"0.0.0.0\",\n"\
1155
                   "  PORT     =\"#{vp[0][:value]}\"\n"
1156
            str << " ,KEYMAP   =\"#{keymap[0][:value]}\"\n" if keymap[0]
1157
            str << "]\n"
1158
        end
1159

    
1160
        if @vm.config.annotation.nil? || @vm.config.annotation.empty?
1161
            str << "DESCRIPTION = \"vCenter Virtual Machine imported by"\
1162
                " OpenNebula from Cluster #{@vm.runtime.host.parent.name}\"\n"
1163
        else
1164
            notes = @vm.config.annotation.gsub("\\", "\\\\").gsub("\"", "\\\"")
1165
            str << "DESCRIPTION = \"#{notes}\"\n"
1166
        end
1167

    
1168
        return str
1169
    end
1170

    
1171
private
1172

    
1173
    ########################################################################
1174
    # Converts the VI string state to OpenNebula state convention
1175
    # Guest states are:
1176
    # - poweredOff   The virtual machine is currently powered off.
1177
    # - poweredOn    The virtual machine is currently powered on.
1178
    # - suspended    The virtual machine is currently suspended.
1179
    ########################################################################
1180
    def state_to_c(state)
1181
        case state
1182
            when 'poweredOn'
1183
                'a'
1184
            when 'suspended'
1185
                'p'
1186
            when 'poweredOff'
1187
                'd'
1188
            else
1189
                '-'
1190
        end
1191
    end
1192

    
1193
    ########################################################################
1194
    #  Checks if a RbVmomi::VIM::VirtualDevice is a network interface
1195
    ########################################################################
1196
    def self.is_nic?(device)
1197
        !device.class.ancestors.index(RbVmomi::VIM::VirtualEthernetCard).nil?
1198
    end
1199

    
1200
    ########################################################################
1201
    # Returns the spec to reconfig a VM and add a NIC
1202
    ########################################################################
1203
    def self.calculate_addnic_spec(vm, mac, bridge, model)
1204
        model       = model.nil? ? nil : model.downcase
1205
        network     = vm.runtime.host.network.select{|n| n.name==bridge}
1206
        backing     = nil
1207

    
1208
        if network.empty?
1209
            raise "Network #{bridge} not found in host #{vm.runtime.host.name}"
1210
        else
1211
            network = network[0]
1212
        end
1213

    
1214
        card_num = 1 # start in one, we want the next avaliable id
1215

    
1216
        vm.config.hardware.device.each{ |dv|
1217
            card_num = card_num + 1 if is_nic?(dv)
1218
        }
1219

    
1220
        nic_card = case model
1221
                        when "virtuale1000", "e1000"
1222
                            RbVmomi::VIM::VirtualE1000
1223
                        when "virtuale1000e", "e1000e"
1224
                            RbVmomi::VIM::VirtualE1000e
1225
                        when "virtualpcnet32", "pcnet32"
1226
                            RbVmomi::VIM::VirtualPCNet32
1227
                        when "virtualsriovethernetcard", "sriovethernetcard"
1228
                            RbVmomi::VIM::VirtualSriovEthernetCard
1229
                        when "virtualvmxnetm", "vmxnetm"
1230
                            RbVmomi::VIM::VirtualVmxnetm
1231
                        when "virtualvmxnet2", "vmnet2"
1232
                            RbVmomi::VIM::VirtualVmxnet2
1233
                        when "virtualvmxnet3", "vmxnet3"
1234
                            RbVmomi::VIM::VirtualVmxnet3
1235
                        else # If none matches, use VirtualE1000
1236
                            RbVmomi::VIM::VirtualE1000
1237
                   end
1238

    
1239
        if network.class == RbVmomi::VIM::Network
1240
            backing = RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo(
1241
                        :deviceName => bridge,
1242
                        :network => network)
1243
        else
1244
            port    = RbVmomi::VIM::DistributedVirtualSwitchPortConnection(
1245
                        :switchUuid =>
1246
                                network.config.distributedVirtualSwitch.uuid,
1247
                        :portgroupKey => network.key)
1248
            backing =
1249
              RbVmomi::VIM.VirtualEthernetCardDistributedVirtualPortBackingInfo(
1250
                 :port => port)
1251
        end
1252

    
1253
        return {:operation => :add,
1254
                :device => nic_card.new(
1255
                            :key => 0,
1256
                            :deviceInfo => {
1257
                                :label => "net" + card_num.to_s,
1258
                                :summary => bridge
1259
                            },
1260
                            :backing => backing,
1261
                            :addressType => mac ? 'manual' : 'generated',
1262
                            :macAddress  => mac
1263
                           )
1264
               }
1265
    end
1266

    
1267
    ########################################################################
1268
    #  Clone a vCenter VM Template and leaves it powered on
1269
    ########################################################################
1270
    def self.clone_vm(xml_text)
1271

    
1272
        xml = REXML::Document.new xml_text
1273
        pcs = xml.root.get_elements("//USER_TEMPLATE/PUBLIC_CLOUD")
1274

    
1275
        raise "Cannot find VCenter element in VM template." if pcs.nil?
1276

    
1277
        template = pcs.find { |t|
1278
            type = t.elements["TYPE"]
1279
            !type.nil? && type.text.downcase == "vcenter"
1280
        }
1281

    
1282
        raise "Cannot find vCenter element in VM template." if template.nil?
1283

    
1284
        uuid = template.elements["VM_TEMPLATE"]
1285

    
1286
        raise "Cannot find VM_TEMPLATE in VCenter element." if uuid.nil?
1287

    
1288
        uuid = uuid.text
1289
        vmid = xml.root.elements["/VM/ID"].text
1290
        hid = xml.root.elements["//HISTORY_RECORDS/HISTORY/HID"]
1291

    
1292
        raise "Cannot find host id in deployment file history." if hid.nil?
1293

    
1294
        context = xml.root.elements["//TEMPLATE/CONTEXT"]
1295
        connection  = VIClient.new(hid)
1296
        vc_template = connection.find_vm_template(uuid)
1297

    
1298
        relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(
1299
                          :diskMoveType => :moveChildMostDiskBacking,
1300
                          :pool         => connection.resource_pool)
1301

    
1302
        clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(
1303
                      :location => relocate_spec,
1304
                      :powerOn  => false,
1305
                      :template => false)
1306

    
1307
        begin
1308
            vm = vc_template.CloneVM_Task(
1309
                   :folder => vc_template.parent,
1310
                   :name   => "one-#{vmid}",
1311
                   :spec   => clone_spec).wait_for_completion
1312
        rescue Exception => e
1313

    
1314
            if !e.message.start_with?('DuplicateName')
1315
                raise "Cannot clone VM Template: #{e.message}"
1316
            end
1317

    
1318
            vm = connection.find_vm("one-#{vmid}")
1319

    
1320
            raise "Cannot clone VM Template" if vm.nil?
1321

    
1322
            vm.Destroy_Task.wait_for_completion
1323
            vm = vc_template.CloneVM_Task(
1324
                :folder => vc_template.parent,
1325
                :name   => "one-#{vmid}",
1326
                :spec   => clone_spec).wait_for_completion
1327
        end
1328

    
1329
        vm_uuid = vm.config.uuid
1330

    
1331
        # VNC Section
1332

    
1333
        vnc_port   = xml.root.elements["/VM/TEMPLATE/GRAPHICS/PORT"]
1334
        vnc_listen = xml.root.elements["/VM/TEMPLATE/GRAPHICS/LISTEN"]
1335
        vnc_keymap = xml.root.elements["/VM/TEMPLATE/GRAPHICS/KEYMAP"]
1336

    
1337
        if !vnc_listen
1338
            vnc_listen = "0.0.0.0"
1339
        else
1340
            vnc_listen = vnc_listen.text
1341
        end
1342

    
1343
        config_array     = []
1344
        context_vnc_spec = {}
1345

    
1346
        if vnc_port
1347
            config_array +=
1348
                     [{:key=>"remotedisplay.vnc.enabled",:value=>"TRUE"},
1349
                      {:key=>"remotedisplay.vnc.port",   :value=>vnc_port.text},
1350
                      {:key=>"remotedisplay.vnc.ip",     :value=>vnc_listen}]
1351
        end
1352

    
1353
        config_array += [{:key=>"remotedisplay.vnc.keymap",
1354
                          :value=>vnc_keymap.text}] if vnc_keymap
1355

    
1356
        # Context section
1357

    
1358
        if context
1359
            # Remove <CONTEXT> (9) and </CONTEXT>\n (11)
1360
            context_text = "# Context variables generated by OpenNebula\n"
1361
            context.elements.each{|context_element|
1362
                context_text += context_element.name + "='" +
1363
                                context_element.text.gsub("'", "\\'") + "'\n"
1364
            }
1365
            context_text = Base64.encode64(context_text.chop)
1366
            config_array +=
1367
                     [{:key=>"guestinfo.opennebula.context",
1368
                       :value=>context_text}]
1369
        end
1370

    
1371
        if config_array != []
1372
            context_vnc_spec = {:extraConfig =>config_array}
1373
        end
1374

    
1375
        # NIC section, build the reconfig hash
1376

    
1377
        nics     = xml.root.get_elements("//TEMPLATE/NIC")
1378
        nic_spec = {}
1379

    
1380
        if !nics.nil?
1381
            nic_array = []
1382
            nics.each{|nic|
1383
               mac    = nic.elements["MAC"].text
1384
               bridge = nic.elements["BRIDGE"].text
1385
               model  = nic.elements["MODEL"] ? nic.elements["MODEL"].text : nil
1386
               nic_array << calculate_addnic_spec(vm, mac, bridge, model)
1387
            }
1388

    
1389
            nic_spec = {:deviceChange => nic_array}
1390
        end
1391

    
1392
        # Capacity section
1393

    
1394
        cpu           = xml.root.elements["//TEMPLATE/VCPU"].text
1395
        memory        = xml.root.elements["//TEMPLATE/MEMORY"].text
1396
        capacity_spec = {:numCPUs  => cpu.to_i,
1397
                         :memoryMB => memory }
1398

    
1399
        # Perform the VM reconfiguration
1400
        spec_hash = context_vnc_spec.merge(nic_spec).merge(capacity_spec)
1401
        spec      = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
1402
        vm.ReconfigVM_Task(:spec => spec).wait_for_completion
1403

    
1404
        # Power on the VM
1405
        vm.PowerOnVM_Task.wait_for_completion
1406

    
1407
        return vm_uuid
1408
    end
1409
end
1410
end