Statistics
| Branch: | Tag: | Revision:

one / src / vmm_mad / remotes / lib / vcenter_driver / host.rb @ 9e5bb8b9

History | View | Annotate | Download (34.1 KB)

1
module VCenterDriver
2

    
3
class HostFolder
4
    attr_accessor :item, :items
5

    
6
    def initialize(item)
7
        @item = item
8
        @items = {}
9
    end
10

    
11
    def fetch_clusters!
12
        VIClient.get_entities(@item, 'ClusterComputeResource').each do |item|
13
            item_name = item._ref
14
            @items[item_name.to_sym] = ClusterComputeResource.new(item)
15
        end
16
    end
17

    
18
    def get_cluster(ref)
19
        if !@items[ref.to_sym]
20
            rbvmomi_dc = RbVmomi::VIM::ClusterComputeResource.new(@item._connection, ref)
21
            @items[ref.to_sym] = ClusterComputeResource.new(rbvmomi_dc)
22
        end
23

    
24
        @items[ref.to_sym]
25
    end
26
end # class HostFolder
27

    
28
class ClusterComputeResource
29
    attr_accessor :item
30
    attr_accessor :rp_list
31

    
32
    include Memoize
33

    
34
    def initialize(item, vi_client=nil)
35
        @item = item
36
        @vi_client = vi_client
37
        @rp_list
38
    end
39

    
40
    def fetch_resource_pools(rp, rp_array = [])
41
        rp_array << rp
42

    
43
        rp.resourcePool.each do |child_rp|
44
            fetch_resource_pools(child_rp, rp_array)
45
        end
46

    
47
        rp_array
48
    end
49

    
50
    def resource_pools
51
        if @resource_pools.nil?
52
            @resource_pools = fetch_resource_pools(@item.resourcePool)
53
        end
54

    
55
        @resource_pools
56
    end
57

    
58
    def get_resource_pool_list(rp = @item.resourcePool, parent_prefix = "", rp_array = [])
59
        current_rp = ""
60

    
61
        if !parent_prefix.empty?
62
            current_rp << parent_prefix
63
            current_rp << "/"
64
        end
65

    
66
        resource_pool, name = rp.collect("resourcePool","name")
67
        current_rp << name if name != "Resources"
68

    
69
        resource_pool.each do |child_rp|
70
            get_resource_pool_list(child_rp, current_rp, rp_array)
71
        end
72

    
73
        rp_info = {}
74
        rp_info[:name] = current_rp
75
        rp_info[:ref]  = rp._ref
76
        rp_array << rp_info if !current_rp.empty?
77

    
78
        rp_array
79
    end
80

    
81
    def monitor
82
        total_cpu,
83
        num_cpu_cores,
84
        effective_cpu,
85
        total_memory,
86
        effective_mem,
87
        num_hosts,
88
        num_eff_hosts = @item.collect("summary.totalCpu",
89
                                      "summary.numCpuCores",
90
                                      "summary.effectiveCpu",
91
                                      "summary.totalMemory",
92
                                      "summary.effectiveMemory",
93
                                      "summary.numHosts",
94
                                      "summary.numEffectiveHosts"
95
        )
96

    
97
        mhz_core = total_cpu.to_f / num_cpu_cores.to_f
98
        eff_core = effective_cpu.to_f / mhz_core
99

    
100
        free_cpu  = sprintf('%.2f', eff_core * 100).to_f
101
        total_cpu = num_cpu_cores.to_f * 100
102
        used_cpu  = sprintf('%.2f', total_cpu - free_cpu).to_f
103

    
104
        total_mem = total_memory.to_i / 1024
105
        free_mem  = effective_mem.to_i * 1024
106

    
107
        str_info = ""
108

    
109
        # Get cluster name for informative purposes (replace space with _ if any)
110
        str_info << "VCENTER_NAME=" << self['name'].tr(" ", "_") << "\n"
111

    
112
        # System
113
        str_info << "HYPERVISOR=vcenter\n"
114
        str_info << "TOTALHOST=" << num_hosts.to_s << "\n"
115
        str_info << "AVAILHOST=" << num_eff_hosts.to_s << "\n"
116

    
117
        # CPU
118
        str_info << "CPUSPEED=" << mhz_core.to_s   << "\n"
119
        str_info << "TOTALCPU=" << total_cpu.to_s << "\n"
120
        str_info << "USEDCPU="  << used_cpu.to_s  << "\n"
121
        str_info << "FREECPU="  << free_cpu.to_s << "\n"
122

    
123
        # Memory
124
        str_info << "TOTALMEMORY=" << total_mem.to_s << "\n"
125
        str_info << "FREEMEMORY="  << free_mem.to_s << "\n"
126
        str_info << "USEDMEMORY="  << (total_mem - free_mem).to_s << "\n"
127

    
128
        str_info << monitor_resource_pools(mhz_core)
129
    end
130

    
131
    def monitor_resource_pools(mhz_core)
132

    
133
        @rp_list = get_resource_pool_list
134

    
135
        view = @vi_client.vim.serviceContent.viewManager.CreateContainerView({
136
            container: @item, #View for RPs inside this cluster
137
            type:      ['ResourcePool'],
138
            recursive: true
139
        })
140

    
141
        pc = @vi_client.vim.serviceContent.propertyCollector
142

    
143
        monitored_properties = [
144
            "config.cpuAllocation.expandableReservation",
145
            "config.cpuAllocation.limit",
146
            "config.cpuAllocation.reservation",
147
            "config.cpuAllocation.shares.level",
148
            "config.cpuAllocation.shares.shares",
149
            "config.memoryAllocation.expandableReservation",
150
            "config.memoryAllocation.limit",
151
            "config.memoryAllocation.reservation",
152
            "config.memoryAllocation.shares.level",
153
            "config.memoryAllocation.shares.shares"
154
        ]
155

    
156
        filterSpec = RbVmomi::VIM.PropertyFilterSpec(
157
            :objectSet => [
158
                :obj => view,
159
                :skip => true,
160
                :selectSet => [
161
                RbVmomi::VIM.TraversalSpec(
162
                    :name => 'traverseEntities',
163
                    :type => 'ContainerView',
164
                    :path => 'view',
165
                    :skip => false
166
                )
167
                ]
168
            ],
169
            :propSet => [
170
                { :type => 'ResourcePool', :pathSet => monitored_properties }
171
            ]
172
        )
173

    
174
        result = pc.RetrieveProperties(:specSet => [filterSpec])
175

    
176
        rps = {}
177
        result.each do |r|
178
            hashed_properties = r.to_hash
179
            if r.obj.is_a?(RbVmomi::VIM::ResourcePool)
180
                rps[r.obj._ref] = hashed_properties
181
            end
182
        end
183

    
184
        return "" if rps.empty?
185

    
186
        rp_info = ""
187

    
188
        rps.each{|ref, info|
189

    
190
            # CPU
191
            cpu_expandable   = info["config.cpuAllocation.expandableReservation"] ? "YES" : "NO"
192
            cpu_limit        = info["config.cpuAllocation.limit"] == "-1" ? "UNLIMITED" : info["config.cpuAllocation.limit"]
193
            cpu_reservation  = info["config.cpuAllocation.reservation"]
194
            cpu_num          = cpu_reservation.to_f / mhz_core
195
            cpu_shares_level = info["config.cpuAllocation.shares.level"]
196
            cpu_shares       = info["config.cpuAllocation.shares.shares"]
197

    
198
            # MEMORY
199
            mem_expandable   = info["config.memoryAllocation.expandableReservation"] ? "YES" : "NO"
200
            mem_limit        = info["config.memoryAllocation.limit"] == "-1" ? "UNLIMITED" : info["config.memoryAllocation.limit"]
201
            mem_reservation  = info["config.memoryAllocation.reservation"].to_f
202
            mem_shares_level = info["config.memoryAllocation.shares.level"]
203
            mem_shares       = info["config.memoryAllocation.shares.shares"]
204

    
205
            rp_name = rp_list.select { |item| item[:ref] == ref}.first[:name] rescue ""
206

    
207
            rp_name = "Resources" if rp_name.empty?
208

    
209
            rp_info << "\nVCENTER_RESOURCE_POOL_INFO = ["
210
            rp_info << "NAME=\"#{rp_name}\","
211
            rp_info << "CPU_EXPANDABLE=#{cpu_expandable},"
212
            rp_info << "CPU_LIMIT=#{cpu_limit},"
213
            rp_info << "CPU_RESERVATION=#{cpu_reservation},"
214
            rp_info << "CPU_RESERVATION_NUM_CORES=#{cpu_num},"
215
            rp_info << "CPU_SHARES=#{cpu_shares},"
216
            rp_info << "CPU_SHARES_LEVEL=#{cpu_shares_level},"
217
            rp_info << "MEM_EXPANDABLE=#{mem_expandable},"
218
            rp_info << "MEM_LIMIT=#{mem_limit},"
219
            rp_info << "MEM_RESERVATION=#{mem_reservation},"
220
            rp_info << "MEM_SHARES=#{mem_shares},"
221
            rp_info << "MEM_SHARES_LEVEL=#{mem_shares_level}"
222
            rp_info << "]"
223
        }
224

    
225
        view.DestroyView
226

    
227
        return rp_info
228
    end
229

    
230
    def monitor_host_systems
231
        host_info = ""
232

    
233
        view = @vi_client.vim.serviceContent.viewManager.CreateContainerView({
234
            container: @item, #View for Hosts inside this cluster
235
            type:      ['HostSystem'],
236
            recursive: true
237
        })
238

    
239
        pc = @vi_client.vim.serviceContent.propertyCollector
240

    
241
        monitored_properties = [
242
            "name",
243
            "runtime.connectionState",
244
            "summary.hardware.numCpuCores",
245
            "summary.hardware.memorySize",
246
            "summary.hardware.cpuModel",
247
            "summary.hardware.cpuMhz",
248
            "summary.quickStats.overallCpuUsage",
249
            "summary.quickStats.overallMemoryUsage"
250
        ]
251

    
252
        filterSpec = RbVmomi::VIM.PropertyFilterSpec(
253
            :objectSet => [
254
                :obj => view,
255
                :skip => true,
256
                :selectSet => [
257
                RbVmomi::VIM.TraversalSpec(
258
                    :name => 'traverseEntities',
259
                    :type => 'ContainerView',
260
                    :path => 'view',
261
                    :skip => false
262
                )
263
                ]
264
            ],
265
            :propSet => [
266
                { :type => 'HostSystem', :pathSet => monitored_properties }
267
            ]
268
        )
269

    
270
        result = pc.RetrieveProperties(:specSet => [filterSpec])
271

    
272
        hosts = {}
273
        result.each do |r|
274
            hashed_properties = r.to_hash
275
            if r.obj.is_a?(RbVmomi::VIM::HostSystem)
276
                hosts[r.obj._ref] = hashed_properties
277
            end
278
        end
279

    
280
        hosts.each do |ref, info|
281
            next if info["runtime.connectionState"] != "connected"
282

    
283
            total_cpu = info["summary.hardware.numCpuCores"] * 100
284
            used_cpu  = (info["summary.quickStats.overallCpuUsage"].to_f / info["summary.hardware.cpuMhz"].to_f) * 100
285
            used_cpu  = sprintf('%.2f', used_cpu).to_f # Trim precission
286
            free_cpu  = total_cpu - used_cpu
287

    
288
            total_memory = info["summary.hardware.memorySize"]/1024
289
            used_memory  = info["summary.quickStats.overallMemoryUsage"]*1024
290
            free_memory  = total_memory - used_memory
291

    
292
            host_info << "\nHOST=["
293
            host_info << "STATE=on,"
294
            host_info << "HOSTNAME=\""  << info["name"].to_s       << "\","
295
            host_info << "MODELNAME=\"" << info["summary.hardware.cpuModel"].to_s  << "\","
296
            host_info << "CPUSPEED="    << info["summary.hardware.cpuMhz"].to_s    << ","
297
            host_info << "MAX_CPU="     << total_cpu.to_s    << ","
298
            host_info << "USED_CPU="    << used_cpu.to_s     << ","
299
            host_info << "FREE_CPU="    << free_cpu.to_s     << ","
300
            host_info << "MAX_MEM="     << total_memory.to_s << ","
301
            host_info << "USED_MEM="    << used_memory.to_s  << ","
302
            host_info << "FREE_MEM="    << free_memory.to_s
303
            host_info << "]"
304
        end
305

    
306
        view.DestroyView # Destroy the view
307

    
308
        return host_info
309
    end
310

    
311
    def monitor_vms
312

    
313
        vc_uuid = @vi_client.vim.serviceContent.about.instanceUuid
314
        cluster_name = self["name"]
315
        cluster_ref = self["_ref"]
316

    
317
        # Get info of the host where the VM/template is located
318
        host_id = nil
319
        one_host = VCenterDriver::VIHelper.find_by_ref(OpenNebula::HostPool,
320
                                                       "TEMPLATE/VCENTER_CCR_REF",
321
                                                       cluster_ref,
322
                                                       vc_uuid)
323
        host_id = one_host["ID"] if one_host
324

    
325

    
326
        # Extract CPU info and name for each esx host in cluster
327
        esx_hosts = {}
328
        @item.host.each do |esx_host|
329
            info = {}
330
            info[:name] = esx_host.name
331
            info[:cpu]  = esx_host.summary.hardware.cpuMhz.to_f
332
            esx_hosts[esx_host._ref] = info
333
        end
334

    
335
        @monitored_vms = Set.new
336
        str_info = ""
337

    
338
        view = @vi_client.vim.serviceContent.viewManager.CreateContainerView({
339
            container: @item, #View for VMs inside this cluster
340
            type:      ['VirtualMachine'],
341
            recursive: true
342
        })
343

    
344
        pc = @vi_client.vim.serviceContent.propertyCollector
345

    
346
        monitored_properties = [
347
            "name", #VM name
348
            "config.template", #To filter out templates
349
            "summary.runtime.powerState", #VM power state
350
            "summary.quickStats.hostMemoryUsage", #Memory usage
351
            "summary.quickStats.overallCpuUsage", #CPU used by VM
352
            "runtime.host", #ESX host
353
            "resourcePool", #RP
354
            "guest.guestFullName",
355
            "guest.net", #IP addresses as seen by guest tools,
356
            "guest.guestState",
357
            "guest.toolsVersion",
358
            "guest.toolsRunningStatus",
359
            "guest.toolsVersionStatus2", #IP addresses as seen by guest tools,
360
            "config.extraConfig", #VM extraconfig info e.g opennebula.vm.running
361
            "config.hardware.numCPU",
362
            "config.hardware.memoryMB",
363
            "config.annotation",
364
            "datastore"
365
        ]
366

    
367
        filterSpec = RbVmomi::VIM.PropertyFilterSpec(
368
            :objectSet => [
369
                :obj => view,
370
                :skip => true,
371
                :selectSet => [
372
                RbVmomi::VIM.TraversalSpec(
373
                    :name => 'traverseEntities',
374
                    :type => 'ContainerView',
375
                    :path => 'view',
376
                    :skip => false
377
                )
378
                ]
379
            ],
380
            :propSet => [
381
                { :type => 'VirtualMachine', :pathSet => monitored_properties }
382
            ]
383
        )
384

    
385
        result = pc.RetrieveProperties(:specSet => [filterSpec])
386

    
387
        vms = {}
388
        vm_objects = []
389
        result.each do |r|
390
            hashed_properties = r.to_hash
391
            if r.obj.is_a?(RbVmomi::VIM::VirtualMachine)
392
                #Only take care of VMs, not templates
393
                if !hashed_properties["config.template"]
394
                    vms[r.obj._ref] = hashed_properties
395
                    vm_objects << r.obj
396
                end
397
            end
398
        end
399

    
400
        pm = @vi_client.vim.serviceContent.perfManager
401

    
402
        stats = {}
403

    
404
        max_samples = 9
405
        refresh_rate = 20 #Real time stats takes samples every 20 seconds
406

    
407
        last_mon_time = one_host["TEMPLATE/VCENTER_LAST_PERF_POLL"]
408

    
409
        if last_mon_time
410
            interval = (Time.now.to_i - last_mon_time.to_i)
411
            interval = 3601 if interval < 0
412
            samples = (interval / refresh_rate)
413
            samples = 1 if samples == 0
414
            max_samples =  interval > 3600 ? 9 : samples
415
        end
416

    
417
        if !vm_objects.empty?
418
            stats = pm.retrieve_stats(
419
                    vm_objects,
420
                    ['net.transmitted','net.bytesRx','net.bytesTx','net.received',
421
                    'virtualDisk.numberReadAveraged','virtualDisk.numberWriteAveraged',
422
                    'virtualDisk.read','virtualDisk.write'],
423
                    {max_samples: max_samples}
424
            ) rescue {}
425
        end
426

    
427
        if !stats.empty?
428
            last_mon_time = Time.now.to_i.to_s
429
        end
430

    
431
        get_resource_pool_list if !@rp_list
432

    
433
        vm_pool = VCenterDriver::VIHelper.one_pool(OpenNebula::VirtualMachinePool)
434

    
435
        vms.each do |vm_ref,info|
436
            begin
437
                vm = VCenterDriver::VirtualMachine.new_from_ref(vm_ref, @vi_client)
438
                esx_host = esx_hosts[info["runtime.host"]._ref]
439
                info[:esx_host_name] = esx_host[:name]
440
                info[:esx_host_cpu] = esx_host[:cpu]
441
                info[:cluster_name] = cluster_name
442
                info[:cluster_ref] = cluster_ref
443
                info[:vc_uuid] = vc_uuid
444
                info[:host_id] = host_id
445
                info[:rp_list] = @rp_list
446

    
447
                vm.vm_info = info
448

    
449
                number = -1
450

    
451
                # Check the running flag
452
                running_flag = info["config.extraConfig"].select do |val|
453
                    val[:key] == "opennebula.vm.running"
454
                end
455

    
456
                if !running_flag.empty? && running_flag.first
457
                    running_flag = running_flag[0][:value]
458
                end
459

    
460
                next if running_flag == "no"
461

    
462
                # Extract vmid if possible
463
                matches = info["name"].match(/^one-(\d*)(-(.*))?$/)
464
                number  = matches[1] if matches
465

    
466
                # Extract vmid from ref and vcenter instance uuid if possible
467
                one_vm = nil
468
                if number == -1
469
                    one_vm = VCenterDriver::VIHelper.find_by_ref(OpenNebula::VirtualMachinePool,
470
                                                                "DEPLOY_ID",
471
                                                                vm_ref,
472
                                                                vc_uuid,
473
                                                                vm_pool)
474
                    if one_vm
475
                        number = one_vm["ID"]
476

    
477
                        next if @monitored_vms.include? number
478
                        @monitored_vms << number
479

    
480
                        vm.one_item = one_vm
481
                        vm.vm_id = number
482
                    end
483
                end
484

    
485
                vm.monitor(stats)
486

    
487
                vm_name = "#{info["name"]} - #{cluster_name}"
488

    
489
                str_info << %Q{
490
                VM = [
491
                    ID="#{number}",
492
                    VM_NAME="#{vm_name}",
493
                    DEPLOY_ID="#{vm_ref}",
494
                }
495

    
496
                if number == -1
497
                    vm_template_64 = Base64.encode64(vm.vm_to_one(vm_name)).gsub("\n","")
498
                    str_info << "VCENTER_TEMPLATE=\"YES\","
499
                    str_info << "IMPORT_TEMPLATE=\"#{vm_template_64}\","
500
                end
501

    
502
                str_info << "POLL=\"#{vm.info.gsub('"', "\\\"")}\"]"
503

    
504
            rescue Exception => e
505
                STDERR.puts e.inspect
506
                STDERR.puts e.backtrace
507
            end
508
        end
509

    
510
        view.DestroyView # Destroy the view
511

    
512
        return str_info, last_mon_time
513
    end
514

    
515
    def monitor_customizations
516
        customizations = self['_connection'].serviceContent.customizationSpecManager.info
517

    
518
        text = ''
519

    
520
        customizations.each do |c|
521
            t = "CUSTOMIZATION = [ "
522
            t << %Q<NAME = "#{c.name}", >
523
            t << %Q<TYPE = "#{c.type}" ]\n>
524

    
525
            text << t
526
        end
527

    
528
        text
529
    end
530

    
531
    def get_dc
532
        item = @item
533

    
534
        while !item.instance_of? RbVmomi::VIM::Datacenter
535
            item = item.parent
536
            if item.nil?
537
                raise "Could not find the parent Datacenter"
538
            end
539
        end
540

    
541
        Datacenter.new(item)
542
    end
543

    
544
    def self.to_one(cluster, con_ops, rp, one_cluster_id)
545

    
546
        one_host = VCenterDriver::VIHelper.new_one_item(OpenNebula::Host)
547

    
548
        if OpenNebula.is_error?(one_host)
549
            raise "Could not create host: #{one_host.message}"
550
        end
551

    
552
        one_cluster_id = -1 if !one_cluster_id
553

    
554
        rc = one_host.allocate(cluster[:cluster_name], 'vcenter', 'vcenter', one_cluster_id.to_i)
555

    
556
        if OpenNebula.is_error?(rc)
557
            raise "Could not allocate host: #{rc.message}"
558
        end
559

    
560
        template = "VCENTER_HOST=\"#{con_ops[:host]}\"\n"\
561
                   "VCENTER_PASSWORD=\"#{con_ops[:password]}\"\n"\
562
                   "VCENTER_USER=\"#{con_ops[:user]}\"\n"\
563
                   "VCENTER_CCR_REF=\"#{cluster[:cluster_ref]}\"\n"\
564
                   "VCENTER_INSTANCE_ID=\"#{cluster[:vcenter_uuid]}\"\n"\
565
                   "VCENTER_VERSION=\"#{cluster[:vcenter_version]}\"\n"\
566

    
567
        template << "VCENTER_RESOURCE_POOL=\"#{rp}\"" if rp
568

    
569
        rc = one_host.update(template, false)
570

    
571
        if OpenNebula.is_error?(rc)
572
            update_error = rc.message
573
            rc = one_host.delete
574
            if OpenNebula.is_error?(rc)
575
                raise "Could not update host: #{update_error} "\
576
                      "and could not delete host: #{rc.message}"
577
            else
578
                raise "Could not update host: #{rc.message}"
579
            end
580
        end
581

    
582
        return one_host
583
    end
584

    
585
    def self.new_from_ref(ref, vi_client)
586
        self.new(RbVmomi::VIM::ClusterComputeResource.new(vi_client.vim, ref), vi_client)
587
    end
588
end # class ClusterComputeResource
589

    
590
class ESXHost
591
    attr_accessor :item
592

    
593
    include Memoize
594

    
595
    PG_CREATE_TIMEOUT = 240 # We will wait for 4 minutes for the pg creation
596

    
597
    def initialize(item, vi_client=nil)
598
        @net_rollback = []
599
        @locking = true
600
        @item = item
601
        @vi_client = vi_client
602
    end
603

    
604
    def self.new_from_ref(ref, vi_client)
605
        self.new(RbVmomi::VIM::HostSystem.new(vi_client.vim, ref), vi_client)
606
    end
607

    
608
    # Locking function. Similar to flock
609
    def lock
610
        hostlockname = @item['name'].downcase.tr(" ", "_")
611
        if @locking
612
           @locking_file = File.open("/tmp/vcenter-#{hostlockname}-lock","w")
613
           @locking_file.flock(File::LOCK_EX)
614
        end
615
    end
616

    
617
    # Unlock driver execution mutex
618
    def unlock
619
        if @locking
620
            @locking_file.close
621
        end
622
    end
623

    
624
    ########################################################################
625
    # Check if standard switch exists in host
626
    ########################################################################
627

    
628
    def vss_exists(vswitch_name)
629
        vswitches = @item.configManager.networkSystem.networkInfo.vswitch
630
        return vswitches.select{|vs| vs.name == vswitch_name }.first rescue nil
631
    end
632

    
633
    ########################################################################
634
    # Create a standard vcenter switch in an ESX host
635
    ########################################################################
636

    
637
    def create_vss(name, pnics=nil, num_ports=128, mtu=1500, pnics_available=nil)
638
        # Get NetworkSystem
639
        nws = self['configManager.networkSystem']
640
        vswitchspec = nil
641
        hostbridge = nil
642
        nics = []
643

    
644
        if pnics
645
            pnics = pnics.split(",")
646
            pnics.each do |pnic|
647
                #Add nics if not in use
648
                nics << pnic if pnics_available.include?(pnic)
649
            end
650

    
651
            if !nics.empty?
652
                hostbridge = RbVmomi::VIM::HostVirtualSwitchBondBridge(:nicDevice => nics)
653
            end
654
        end
655

    
656
        #Create spec
657
        vswitchspec = RbVmomi::VIM::HostVirtualSwitchSpec(:bridge => hostbridge, :mtu => mtu, :numPorts => num_ports)
658

    
659
        #add vSwitch to the host
660
        begin
661
            nws.AddVirtualSwitch(:vswitchName => name, :spec => vswitchspec)
662
        rescue Exception => e
663
            raise "The standard vSwitch #{name} could not be created. AddVirtualSwitch failed Reason: #{e.message}."
664
        end
665

    
666
        @net_rollback << {:action => :delete_sw, :name => name}
667

    
668
        return name
669
    end
670

    
671
    ########################################################################
672
    # Update a standard vcenter switch in an ESX host
673
    ########################################################################
674
    def update_vss(switch, name, pnics, num_ports, mtu)
675
        pnics = pnics.split(",") rescue []
676

    
677
        #Backup switch spec for rollback
678
        orig_spec = switch.spec
679

    
680
        #Compare current configuration and return if switch hasn't changed
681
        same_switch = false
682

    
683
        switch_has_pnics = switch.spec.respond_to?(:bridge) && switch.spec.bridge.respond_to?(:nicDevice)
684

    
685

    
686

    
687
        same_switch = switch.spec.respond_to?(:mtu) && switch.spec.mtu == mtu &&
688
                      switch.spec.respond_to?(:numPorts) && switch.spec.numPorts == num_ports &&
689
                      (!switch_has_pnics && pnics.empty? ||
690
                       switch_has_pnics && switch.spec.bridge.nicDevice.uniq.sort == pnics.uniq.sort)
691
        return if same_switch
692

    
693
        # Let's create a new spec and update the switch
694
        vswitchspec = nil
695
        hostbridge = nil
696
        nws = self['configManager.networkSystem']
697
        hostbridge = RbVmomi::VIM::HostVirtualSwitchBondBridge(:nicDevice => pnics) if !pnics.empty?
698
        vswitchspec = RbVmomi::VIM::HostVirtualSwitchSpec(:bridge => hostbridge, :mtu => mtu, :numPorts => num_ports)
699

    
700
        begin
701
            nws.UpdateVirtualSwitch(:vswitchName => name, :spec => vswitchspec)
702
        rescue Exception => e
703
            raise "The standard switch with name #{name} could not be updated. Reason: #{e.message}"
704
        end
705

    
706
        @net_rollback << {:action => :update_sw, :name => name, :spec => orig_spec}
707
    end
708

    
709
    ########################################################################
710
    # Remove a standard vswitch from the host
711
    ########################################################################
712
    def remove_vss(vswitch_name)
713
        nws = self['configManager.networkSystem']
714

    
715
        begin
716
            nws.RemoveVirtualSwitch(:vswitchName => vswitch_name)
717
        rescue RbVmomi::VIM::ResourceInUse
718
            STDERR.puts "The standard switch #{vswitch_name} is in use so it cannot be deleted"
719
            return nil
720
        rescue RbVmomi::VIM::NotFound
721
            STDERR.puts "The standard switch #{vswitch_name} was not found in vCenter"
722
            return nil
723
        rescue Exception => e
724
            raise "There was a failure while deleting a vcenter standard switch #{vswitch_name}. Reason: #{e.message}"
725
        end
726

    
727
        return vswitch_name
728
    end
729

    
730
    ########################################################################
731
    # Get physical nics that are available in a host
732
    ########################################################################
733
    def get_available_pnics
734
        pnics_in_use = []
735
        pnics_available = []
736

    
737
        # Get pnics in use in standard switches
738
        @item.config.network.vswitch.each do |vs|
739
            vs.pnic.each do |pnic|
740
                pnic.slice!("key-vim.host.PhysicalNic-")
741
                pnics_in_use << pnic
742
            end
743
        end
744

    
745
        # Get pnics in host
746
        self['config.network'].pnic.each do |pnic|
747
            pnics_available << pnic.device if !pnics_in_use.include?(pnic.device)
748
        end
749

    
750
        return pnics_available
751
    end
752

    
753
    ########################################################################
754
    # Check if proxy switch exists in host for distributed virtual switch
755
    ########################################################################
756

    
757
    def proxy_switch_exists(switch_name)
758
        nws = self['configManager.networkSystem']
759
        proxy_switches = nws.networkInfo.proxySwitch
760
        return proxy_switches.select{|ps| ps.dvsName == switch_name }.first rescue nil
761
    end
762

    
763
    ########################################################################
764
    # Assign a host to a a distributed vcenter switch (proxy switch)
765
    ########################################################################
766

    
767
    def assign_proxy_switch(dvs, switch_name, pnics, pnics_available)
768
        dvs = dvs.item
769

    
770
        # Return if host is already assigned
771
        return dvs if !dvs['config.host'].select { |host| host.config.host._ref == self['_ref'] }.empty?
772

    
773
        # Prepare spec for DVS reconfiguration
774
        configSpec = RbVmomi::VIM::VMwareDVSConfigSpec.new
775
        configSpec.name = switch_name
776
        configSpec.configVersion = dvs['config.configVersion']
777

    
778
        # Check if host is already assigned to distributed switch
779
        operation = "add"
780
        ##operation = "edit" if !dvs['config.host'].select { |host| host.config.host._ref == self['_ref'] }.empty?
781

    
782
        # Add host members to the distributed virtual switch
783
        host_member_spec = RbVmomi::VIM::DistributedVirtualSwitchHostMemberConfigSpec.new
784
        host_member_spec.host = @item
785
        host_member_spec.operation = operation
786
        host_member_spec.backing = RbVmomi::VIM::DistributedVirtualSwitchHostMemberPnicBacking.new
787
        host_member_spec.backing.pnicSpec = []
788

    
789
        # If pnics are needed assign pnics for uplinks
790
        if pnics
791
            pnics = pnics.split(",")
792
            # Get uplink portgroup from dvswitch
793
            uplink_key = dvs['config.uplinkPortgroup'].select{
794
                |ul| ul.name == "#{switch_name}-uplink-pg"}.first.key rescue nil
795

    
796
            raise "Cannot find the uplink portgroup for #{switch_name}" if !uplink_key
797

    
798
            pnics.each {|pnic|
799
                pnicSpec = RbVmomi::VIM::DistributedVirtualSwitchHostMemberPnicSpec.new
800
                pnicSpec.pnicDevice = pnic
801
                pnicSpec.uplinkPortgroupKey = uplink_key
802
                host_member_spec.backing.pnicSpec << pnicSpec
803
            }
804
        end
805

    
806
        configSpec.host = [host_member_spec]
807

    
808
        # The DVS must be reconfigured
809
        dvs_reconfigure_task = dvs.ReconfigureDvs_Task(:spec => configSpec)
810
        dvs_reconfigure_task.wait_for_completion
811
        if dvs_reconfigure_task.info.state != 'success'
812
            raise "It wasn't possible to assign host #{self["name"]} as a member of #{switch_name}'"
813
        end
814

    
815
        return dvs
816
    end
817

    
818
    ########################################################################
819
    # Create a standard port group
820
    ########################################################################
821

    
822
    def create_pg(pgname, vswitch, vlan=0)
823
        spec = RbVmomi::VIM.HostPortGroupSpec(
824
          :name => pgname,
825
          :vlanId => vlan,
826
          :vswitchName => vswitch,
827
          :policy => RbVmomi::VIM.HostNetworkPolicy
828
        )
829

    
830
        nws = self['configManager.networkSystem']
831

    
832
        begin
833
            nws.AddPortGroup(:portgrp => spec)
834
        rescue Exception => e
835
            raise "A port group with name #{pgname} could not be created. Reason: #{e.message}"
836
        end
837

    
838
        @net_rollback << {:action => :delete_pg, :name => pgname}
839

    
840
        # wait until the network is ready and we have a reference
841
        networks = @item['network'].select{ |net| net.name == pgname }
842
        (0..PG_CREATE_TIMEOUT).each do
843
            break if !networks.empty?
844
            networks = @item['network'].select{ |net| net.name == pgname }
845
            sleep 1
846
        end
847

    
848
        raise "Cannot get VCENTER_NET_REF for new port group" if networks.empty?
849

    
850
        return networks.first._ref
851
    end
852

    
853
    ########################################################################
854
    # Check if standard port group exists in host
855
    ########################################################################
856

    
857
    def pg_exists(pg_name)
858
        nws = self['configManager.networkSystem']
859
        portgroups = nws.networkInfo.portgroup
860
        return portgroups.select{|pg| pg.spec.name == pg_name }.first rescue nil
861
    end
862

    
863

    
864
    ########################################################################
865
    # Is the switch for the pg different?
866
    ########################################################################
867

    
868
    def pg_changes_sw?(pg, switch_name)
869
        return pg.spec.respond_to?(:vswitchName) && pg.spec.vswitchName != switch_name
870
    end
871

    
872
    ########################################################################
873
    # Update a standard port group
874
    ########################################################################
875

    
876
    def update_pg(pg, switch_name, vlan_id)
877

    
878
        if pg.spec.respond_to?(:vlanId) && pg.spec.vlanId != vlan_id
879

    
880
            # Backup original spec
881
            orig_spec = pg.spec
882

    
883
            # Create new spec
884
            pg_name = pg.spec.name
885

    
886
            spec = RbVmomi::VIM.HostPortGroupSpec(
887
                :name => pg_name,
888
                :vlanId => vlan_id,
889
                :vswitchName => switch_name,
890
                :policy => RbVmomi::VIM.HostNetworkPolicy
891
            )
892

    
893
            nws = self['configManager.networkSystem']
894

    
895
            begin
896
                nws.UpdatePortGroup(:pgName => pg_name, :portgrp => spec)
897
            rescue Exception => e
898
                raise "A port group with name #{pg_name} could not be updated. Reason: #{e.message}"
899
            end
900

    
901
            # Set rollback operation
902
            @net_rollback << {:action => :update_pg, :name => pg_name, :spec => orig_spec}
903
        end
904
    end
905

    
906
    ########################################################################
907
    # Remove a standard port group from the host
908
    ########################################################################
909

    
910
    def remove_pg(pgname)
911
        nws = self['configManager.networkSystem']
912

    
913
        swname = nil
914
        begin
915
            portgroups = nws.networkConfig.portgroup
916
            portgroups.each {|pg|
917
                if pg.spec.name == pgname
918
                    swname = pg.spec.vswitchName
919
                    break
920
                end
921
            }
922
            nws.RemovePortGroup(:pgName => pgname)
923
        rescue RbVmomi::VIM::ResourceInUse
924
            STDERR.puts "The standard portgroup #{pgname} is in use so it cannot be deleted"
925
            return nil
926
        rescue RbVmomi::VIM::NotFound
927
            STDERR.puts "The standard portgroup #{pgname} was not found in vCenter"
928
            return nil
929
        rescue Exception => e
930
            raise "There was a failure while deleting a standard portgroup #{pgname} in vCenter. Reason: #{e.message}"
931
        end
932

    
933
        return swname
934
    end
935

    
936
    def network_rollback
937
        nws = self['configManager.networkSystem']
938

    
939
        @net_rollback.reverse_each do |nr|
940

    
941
            case nr[:action]
942
                when :update_pg
943
                    begin
944
                        nws.UpdatePortGroup(:pgName => nr[:name], :portgrp => nr[:spec])
945
                    rescue Exception => e
946
                        raise "A rollback operation for standard port group #{nr[:name]} could not be performed. Reason: #{e.message}"
947
                    end
948
                when :update_sw
949
                    begin
950
                        nws.UpdateVirtualSwitch(:vswitchName => nr[:name], :spec => nr[:spec])
951
                    rescue Exception => e
952
                        raise "A rollback operation for standard switch #{nr[:name]} could not be performed. Reason: #{e.message}"
953
                    end
954
                when :delete_sw
955
                    begin
956
                        nws.RemoveVirtualSwitch(:vswitchName=> nr[:name])
957
                    rescue RbVmomi::VIM::ResourceInUse
958
                        return #Ignore if switch in use
959
                    rescue RbVmomi::VIM::NotFound
960
                        return #Ignore if switch not found
961
                    rescue Exception => e
962
                        raise "A rollback operation for standard switch #{nr[:name]} could not be performed. Reason: #{e.message}"
963
                    end
964
                when :delete_pg
965
                    begin
966
                        nws.RemovePortGroup(:pgName => nr[:name])
967
                    rescue RbVmomi::VIM::ResourceInUse
968
                        return #Ignore if pg in use
969
                    rescue RbVmomi::VIM::NotFound
970
                        return #Ignore if pg not found
971
                    rescue Exception => e
972
                        raise "A rollback operation for standard port group #{nr[:name]} could not be performed. Reason: #{e.message}"
973
                    end
974
            end
975
        end
976
    end
977

    
978

    
979
end # class ESXHost
980

    
981
end # module VCenterDriver