Statistics
| Branch: | Tag: | Revision:

one / src / cli / one_helper / onehost_helper.rb @ f3b5f20a

History | View | Annotate | Download (17.8 KB)

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

    
17
require 'one_helper'
18
require 'one_helper/onevm_helper'
19
require 'rubygems'
20

    
21
class OneHostHelper < OpenNebulaHelper::OneHelper
22
    TEMPLATE_XPATH  = '//HOST/TEMPLATE'
23
    HYBRID = {
24
        :ec2 => {
25
            :help => <<-EOT.unindent,
26
                #-----------------------------------------------------------------------
27
                # Supported EC2 AUTH ATTRIBUTTES:
28
                #
29
                #  REGION_NAME = <the name of the ec2 region>
30
                #
31
                #  EC2_ACCESS = <Your ec2 access key id>
32
                #  EC2_SECRET = <Your ec2 secret key>
33
                #
34
                #  CAPACITY = [
35
                #    M1SMALL  = <number of machines m1.small>,
36
                #    M1XLARGE = <number of machines m1.xlarge>,
37
                #    M1LARGE  = <number of machines m1.large>
38
                #  ]
39
                #
40
                # You can set any machine type supported by ec2
41
                # See your ec2_driver.conf for more information
42
                #
43
                #-----------------------------------------------------------------------
44
                EOT
45
        }
46
    }
47

    
48

    
49
    VERSION_XPATH   = "#{TEMPLATE_XPATH}/VERSION"
50

    
51
    def self.rname
52
        "HOST"
53
    end
54

    
55
    def self.conf_file
56
        "onehost.yaml"
57
    end
58

    
59
    def self.state_to_str(id)
60
        id        = id.to_i
61
        state_str = Host::HOST_STATES[id]
62

    
63
        return Host::SHORT_HOST_STATES[state_str]
64
    end
65

    
66
    def format_pool(options)
67
        config_file = self.class.table_conf
68

    
69
        table = CLIHelper::ShowTable.new(config_file, self) do
70
            column :ID, "ONE identifier for Host", :size=>4 do |d|
71
                d["ID"]
72
            end
73

    
74
            column :NAME, "Name of the Host", :left, :size=>15 do |d|
75
                d["NAME"]
76
            end
77

    
78
            column :CLUSTER, "Name of the Cluster", :left, :size=>9 do |d|
79
                OpenNebulaHelper.cluster_str(d["CLUSTER"])
80
            end
81

    
82
            column :RVM, "Number of Virtual Machines running", :size=>3 do |d|
83
                d["HOST_SHARE"]["RUNNING_VMS"]
84
            end
85

    
86
            column :ZVM, "Number of Virtual Machine zombies", :size=>3 do |d|
87
                d["TEMPLATE"]["TOTAL_ZOMBIES"] || 0
88
            end
89

    
90
            column :TCPU, "Total CPU percentage", :size=>4 do |d|
91
                d["HOST_SHARE"]["MAX_CPU"]
92
            end
93

    
94
            column :FCPU, "Free CPU percentage", :size=>4 do |d|
95
                d["HOST_SHARE"]["MAX_CPU"].to_i-
96
                    d["HOST_SHARE"]["USED_CPU"].to_i
97
            end
98

    
99
            column :ACPU, "Available cpu percentage (not reserved by VMs)",
100
                    :size=>4 do |d|
101
                max_cpu=d["HOST_SHARE"]["MAX_CPU"].to_i
102
                max_cpu=100 if max_cpu==0
103
                max_cpu-d["HOST_SHARE"]["CPU_USAGE"].to_i
104
            end
105

    
106
            column :TMEM, "Total Memory", :size=>7 do |d|
107
                OpenNebulaHelper.unit_to_str(
108
                    d["HOST_SHARE"]["MAX_MEM"].to_i,
109
                    options)
110
            end
111

    
112
            column :FMEM, "Free Memory", :size=>7 do |d|
113
                OpenNebulaHelper.unit_to_str(
114
                    d["HOST_SHARE"]["FREE_MEM"].to_i,
115
                    options)
116
            end
117

    
118
            column :AMEM, "Available Memory (not reserved by VMs)",
119
                    :size=>7 do |d|
120
                acpu=d["HOST_SHARE"]["MAX_MEM"].to_i-
121
                    d["HOST_SHARE"]["MEM_USAGE"].to_i
122
                OpenNebulaHelper.unit_to_str(acpu,options)
123
            end
124

    
125
            column :REAL_CPU, "Real CPU", :size=>18 do |d|
126
                max_cpu  = d["HOST_SHARE"]["MAX_CPU"].to_i
127

    
128
                if max_cpu != 0
129
                    used_cpu = d["HOST_SHARE"]["USED_CPU"].to_i
130
                    ratio    = (used_cpu*100) / max_cpu
131
                    "#{used_cpu} / #{max_cpu} (#{ratio}%)"
132
                else
133
                    '-'
134
                end
135
            end
136

    
137
            column :ALLOCATED_CPU, "Allocated CPU)", :size=>18 do |d|
138
                max_cpu  = d["HOST_SHARE"]["MAX_CPU"].to_i
139
                cpu_usage = d["HOST_SHARE"]["CPU_USAGE"].to_i
140

    
141
                if max_cpu == 0 && cpu_usage == 0
142
                    '-'
143
                else
144
                    cpu_usage = d["HOST_SHARE"]["CPU_USAGE"].to_i
145

    
146
                    if max_cpu != 0
147
                        ratio    = (cpu_usage*100) / max_cpu
148
                        "#{cpu_usage} / #{max_cpu} (#{ratio}%)"
149
                    else
150
                        "#{cpu_usage} / -"
151
                    end
152
                end
153
            end
154

    
155
            column :REAL_MEM, "Real MEM", :size=>18 do |d|
156
                max_mem  = d["HOST_SHARE"]["MAX_MEM"].to_i
157

    
158
                if max_mem != 0
159
                    used_mem = d["HOST_SHARE"]["USED_MEM"].to_i
160
                    ratio    = (used_mem*100) / max_mem
161
                    "#{OpenNebulaHelper.unit_to_str(used_mem,options)} / #{OpenNebulaHelper.unit_to_str(max_mem,options)} (#{ratio}%)"
162
                else
163
                    '-'
164
                end
165
            end
166

    
167
            column :ALLOCATED_MEM, "Allocated MEM", :size=>18 do |d|
168
                max_mem  = d["HOST_SHARE"]["MAX_MEM"].to_i
169
                mem_usage = d["HOST_SHARE"]["MEM_USAGE"].to_i
170

    
171
                if max_mem == 0 && mem_usage == 0
172
                    '-'
173
                else
174
                    if max_mem != 0
175
                        ratio    = (mem_usage*100) / max_mem
176
                        "#{OpenNebulaHelper.unit_to_str(mem_usage,options)} / #{OpenNebulaHelper.unit_to_str(max_mem,options)} (#{ratio}%)"
177
                    else
178
                        "#{OpenNebulaHelper.unit_to_str(mem_usage,options)} / -"
179
                    end
180
                end
181
            end
182

    
183
            column :STAT, "Host status", :left, :size=>6 do |d|
184
                OneHostHelper.state_to_str(d["STATE"])
185
            end
186

    
187
            default :ID, :NAME, :CLUSTER, :RVM, :ALLOCATED_CPU, :ALLOCATED_MEM, :STAT
188
        end
189

    
190
        table
191
    end
192

    
193
    def set_hybrid(type, path)
194
        k = type.to_sym
195
        if HYBRID.key?(k)
196
            str = path.nil? ?  OpenNebulaHelper.editor_input(HYBRID[k][:help]): File.read(path)
197
        end
198
    end
199

    
200
    NUM_THREADS = 15
201
    def sync(host_ids, options)
202
        if `id -u`.to_i == 0 || `id -G`.split.collect{|e| e.to_i}.include?(0)
203
            STDERR.puts("Cannot run 'onehost sync' as root")
204
            exit -1
205
        end
206

    
207
        begin
208
            current_version = File.read(REMOTES_LOCATION+'/VERSION').strip
209
        rescue
210
            STDERR.puts("Could not read #{REMOTES_LOCATION}/VERSION")
211
            exit(-1)
212
        end
213

    
214
        if current_version.empty?
215
            STDERR.puts "Remotes version can not be empty"
216
            exit(-1)
217
        end
218

    
219
        begin
220
            current_version = Gem::Version.new(current_version)
221
        rescue
222
            STDERR.puts "VERSION file is malformed, use semantic versioning."
223
        end
224

    
225
        cluster_id = options[:cluster]
226

    
227
        # Get remote_dir (implies oneadmin group)
228
        rc = OpenNebula::System.new(@client).get_configuration
229
        return -1, rc.message if OpenNebula.is_error?(rc)
230

    
231
        conf = rc
232
        remote_dir = conf['SCRIPTS_REMOTE_DIR']
233

    
234
        # Verify the existence of REMOTES_LOCATION
235
        if !File.directory? REMOTES_LOCATION
236
            error_msg = "'#{REMOTES_LOCATION}' does not exist. " <<
237
                            "This command must be run in the frontend."
238
            return -1,error_msg
239
        end
240

    
241
        # Touch the update file
242
        FileUtils.touch(File.join(REMOTES_LOCATION,'.update'))
243

    
244
        # Get the Host pool
245
        filter_flag ||= OpenNebula::Pool::INFO_ALL
246

    
247
        pool = factory_pool(filter_flag)
248

    
249
        rc = pool.info
250
        return -1, rc.message if OpenNebula.is_error?(rc)
251

    
252
        # Assign hosts to threads
253
        i = 0
254
        queue = Array.new
255

    
256
        pool.each do |host|
257
            if host_ids
258
                next if !host_ids.include?(host['ID'].to_i)
259
            elsif cluster_id
260
                next if host['CLUSTER_ID'].to_i != cluster_id
261
            end
262

    
263
            vm_mad = host['VM_MAD'].downcase
264
            remote_remotes = host['TEMPLATE/REMOTE_REMOTES']
265
            state = host['STATE']
266

    
267
            # Skip this host from remote syncing if it's a PUBLIC_CLOUD host
268
            next if host['TEMPLATE/PUBLIC_CLOUD'] == 'YES'
269

    
270
            # Skip this host from remote syncing if it's OFFLINE
271
            next if Host::HOST_STATES[state.to_i] == 'OFFLINE'
272

    
273
            host_version=host['TEMPLATE/VERSION']
274

    
275
            begin
276
                host_version = Gem::Version.new(host_version)
277
            rescue
278
            end
279

    
280
            if !options[:force]
281
                begin
282
                    next if host_version && host_version >= current_version
283
                rescue
284
                    STDERR.puts "Error comparing versions for host #{host['NAME']}."
285
                end
286
            end
287

    
288
            puts "* Adding #{host['NAME']} to upgrade"
289

    
290
            queue << host
291
        end
292

    
293
        # Run the jobs in threads
294
        host_errors = Array.new
295
        queue_lock = Mutex.new
296
        error_lock = Mutex.new
297
        total = queue.length
298

    
299
        if total==0
300
            puts "No hosts are going to be updated."
301
            exit(0)
302
        end
303

    
304
        ts = (1..NUM_THREADS).map do |t|
305
            Thread.new do
306
                while true do
307
                    host = nil
308
                    size = 0
309

    
310
                    queue_lock.synchronize do
311
                        host=queue.shift
312
                        size=queue.length
313
                    end
314

    
315
                    break if !host
316

    
317
                    print_update_info(total-size, total, host['NAME'])
318

    
319
                    if options[:rsync]
320
                        sync_cmd = "rsync -Laz --delete #{REMOTES_LOCATION}" <<
321
                            " #{host['NAME']}:#{remote_dir}"
322
                    else
323
                        sync_cmd = "scp -rp #{REMOTES_LOCATION}/. " <<
324
                            "#{host['NAME']}:#{remote_dir} 2> /dev/null"
325
                    end
326

    
327
                    `#{sync_cmd} 2>/dev/null`
328

    
329
                    if !$?.success?
330
                        error_lock.synchronize {
331
                            host_errors << host['NAME']
332
                        }
333
                    else
334
                        update_version(host, current_version)
335
                    end
336
                end
337
            end
338
        end
339

    
340
        # Wait for threads to finish
341
        ts.each{|t| t.join}
342

    
343
        puts
344

    
345
        if host_errors.empty?
346
            puts "All hosts updated successfully."
347
            0
348
        else
349
            STDERR.puts "Failed to update the following hosts:"
350
            host_errors.each{|h| STDERR.puts "* #{h}"}
351
            -1
352
        end
353
    end
354

    
355
    private
356

    
357
    def print_update_info(current, total, host)
358
        bar_length=40
359

    
360
        percentage=current.to_f/total.to_f
361
        done=(percentage*bar_length).floor
362

    
363
        bar="["
364
        bar+="="*done
365
        bar+="-"*(bar_length-done)
366
        bar+="]"
367

    
368
        info="#{current}/#{total}"
369

    
370
        str="#{bar} #{info} "
371
        name=host[0..(79-str.length)]
372
        str=str+name
373
        str=str+" "*(80-str.length)
374

    
375
        print "#{str}\r"
376
        STDOUT.flush
377
    end
378

    
379
    def update_version(host, version)
380
        if host.has_elements?(VERSION_XPATH)
381
            host.delete_element(VERSION_XPATH)
382
        end
383

    
384
        host.add_element(TEMPLATE_XPATH, 'VERSION' => version)
385

    
386
        template=host.template_str
387
        host.update(template)
388
    end
389

    
390
    def factory(id=nil)
391
        if id
392
            OpenNebula::Host.new_with_id(id, @client)
393
        else
394
            xml=OpenNebula::Host.build_xml
395
            OpenNebula::Host.new(xml, @client)
396
        end
397
    end
398

    
399
    def factory_pool(user_flag=-2)
400
        #TBD OpenNebula::HostPool.new(@client, user_flag)
401
        OpenNebula::HostPool.new(@client)
402
    end
403

    
404
    def format_resource(host, options = {})
405
        str    = "%-22s: %-20s"
406
        str_h1 = "%-80s"
407

    
408
        CLIHelper.print_header(
409
            str_h1 % "HOST #{host.id.to_s} INFORMATION", true)
410

    
411
        puts str % ["ID", host.id.to_s]
412
        puts str % ["NAME", host.name]
413
        puts str % ["CLUSTER", OpenNebulaHelper.cluster_str(host['CLUSTER'])]
414
        puts str % ["STATE", host.state_str]
415
        puts str % ["IM_MAD", host['IM_MAD']]
416
        puts str % ["VM_MAD", host['VM_MAD']]
417
        puts str % ["LAST MONITORING TIME", OpenNebulaHelper.time_to_str(host['LAST_MON_TIME'])]
418
        puts
419

    
420
        CLIHelper.print_header(str_h1 % "HOST SHARES", false)
421
        puts str % ["RUNNING VMS", host['HOST_SHARE/RUNNING_VMS']]
422

    
423
        CLIHelper.print_header(str_h1 % "MEMORY", false)
424
          puts str % ["  TOTAL", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/TOTAL_MEM'].to_i, {})]
425
          puts str % ["  TOTAL +/- RESERVED", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/MAX_MEM'].to_i, {})]
426
          puts str % ["  USED (REAL)", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/USED_MEM'].to_i, {})]
427
          puts str % ["  USED (ALLOCATED)", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/MEM_USAGE'].to_i, {})]
428

    
429
        CLIHelper.print_header(str_h1 % "CPU", false)
430
          puts str % ["  TOTAL", host['HOST_SHARE/TOTAL_CPU']]
431
          puts str % ["  TOTAL +/- RESERVED", host['HOST_SHARE/MAX_CPU']]
432
          puts str % ["  USED (REAL)", host['HOST_SHARE/USED_CPU']]
433
          puts str % ["  USED (ALLOCATED)", host['HOST_SHARE/CPU_USAGE']]
434
        puts
435

    
436
        datastores = host.to_hash['HOST']['HOST_SHARE']['DATASTORES']['DS']
437

    
438
        if datastores.nil?
439
            datastores = []
440
        else
441
            datastores = [datastores].flatten
442
        end
443

    
444
        datastores.each do |datastore|
445
            CLIHelper.print_header(str_h1 % "LOCAL SYSTEM DATASTORE ##{datastore['ID']} CAPACITY", false)
446
            puts str % ["TOTAL:", OpenNebulaHelper.unit_to_str(datastore['TOTAL_MB'].to_i, {},'M')]
447
            puts str % ["USED: ", OpenNebulaHelper.unit_to_str(datastore['USED_MB'].to_i, {},'M')]
448
            puts str % ["FREE:",  OpenNebulaHelper.unit_to_str(datastore['FREE_MB'].to_i, {},'M')]
449
            puts
450
        end
451

    
452
        CLIHelper.print_header(str_h1 % "MONITORING INFORMATION", false)
453

    
454
        wilds = host.wilds
455

    
456
        begin
457
            pcis = [host.to_hash['HOST']['HOST_SHARE']['PCI_DEVICES']['PCI']]
458
            pcis = pcis.flatten.compact
459
        rescue
460
            pcis = nil
461
        end
462

    
463
        host.delete_element("TEMPLATE/VM")
464
        host.delete_element("TEMPLATE_WILDS")
465

    
466
        puts host.template_str
467

    
468
        if pcis && !pcis.empty?
469
            print_pcis(pcis)
470
        end
471

    
472
        puts
473
        CLIHelper.print_header("WILD VIRTUAL MACHINES", false)
474
        puts
475

    
476
        format = "%-30.30s %36s %4s %10s"
477
        CLIHelper.print_header(format % ["NAME", "IMPORT_ID", "CPU", "MEMORY"],
478
                               true)
479

    
480
        wilds.each do |wild|
481
          if wild['IMPORT_TEMPLATE']
482
            wild_tmplt = Base64::decode64(wild['IMPORT_TEMPLATE']).split("\n")
483
            name   = wild['VM_NAME']
484
            import = wild_tmplt.select { |line|
485
                       line[/^IMPORT_VM_ID/]
486
                     }[0].split("=")[1].gsub("\"", " ").strip
487
            memory = wild_tmplt.select { |line|
488
                       line[/^MEMORY/]
489
                     }[0].split("=")[1].gsub("\"", " ").strip
490
            cpu    = wild_tmplt.select { |line|
491
                        line[/^CPU/]
492
                     }[0].split("=")[1].gsub("\"", " ").strip
493
          else
494
            name     = wild['DEPLOY_ID']
495
            import   = memory = cpu = "-"
496
          end
497

    
498
          puts format % [name, import, cpu, memory]
499
        end
500

    
501
        puts
502
        CLIHelper.print_header("VIRTUAL MACHINES", false)
503
        puts
504

    
505
        onevm_helper=OneVMHelper.new
506
        onevm_helper.client=@client
507
        onevm_helper.list_pool({:filter=>["HOST=#{host.name}"]}, false)
508
    end
509

    
510
    def print_pcis(pcis)
511
        puts
512
        CLIHelper.print_header("PCI DEVICES", false)
513
        puts
514

    
515
        table=CLIHelper::ShowTable.new(nil, self) do
516
            column :VM, "Used by VM", :size => 5, :left => false do |d|
517
                if d["VMID"] == "-1"
518
                    ""
519
                else
520
                    d["VMID"]
521
                end
522
            end
523

    
524
            column :ADDR, "PCI Address", :size => 7, :left => true do |d|
525
                d["SHORT_ADDRESS"]
526
            end
527

    
528
            column :TYPE, "Type", :size => 14, :left => true do |d|
529
                d["TYPE"]
530
            end
531

    
532
            column :CLASS, "Class", :size => 12, :left => true do |d|
533
                d["CLASS_NAME"]
534
            end
535

    
536
            column :NAME, "Name", :size => 50, :left => true do |d|
537
                d["DEVICE_NAME"]
538
            end
539

    
540
            column :VENDOR, "Vendor", :size => 8, :left => true do |d|
541
                d["VENDOR_NAME"]
542
            end
543

    
544
            default :VM, :ADDR, :TYPE, :NAME
545

    
546
        end
547

    
548
        table.show(pcis)
549
    end
550
end