Statistics
| Branch: | Tag: | Revision:

one / src / cli / one_helper / onehost_helper.rb @ 11bebb6e

History | View | Annotate | Download (16.4 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
    VERSION_XPATH   = "#{TEMPLATE_XPATH}/VERSION"
24

    
25
    def self.rname
26
        "HOST"
27
    end
28

    
29
    def self.conf_file
30
        "onehost.yaml"
31
    end
32

    
33
    def self.state_to_str(id)
34
        id        = id.to_i
35
        state_str = Host::HOST_STATES[id]
36

    
37
        return Host::SHORT_HOST_STATES[state_str]
38
    end
39

    
40
    def format_pool(options)
41
        config_file = self.class.table_conf
42

    
43
        table = CLIHelper::ShowTable.new(config_file, self) do
44
            column :ID, "ONE identifier for Host", :size=>4 do |d|
45
                d["ID"]
46
            end
47

    
48
            column :NAME, "Name of the Host", :left, :size=>15 do |d|
49
                d["NAME"]
50
            end
51

    
52
            column :CLUSTER, "Name of the Cluster", :left, :size=>9 do |d|
53
                OpenNebulaHelper.cluster_str(d["CLUSTER"])
54
            end
55

    
56
            column :RVM, "Number of Virtual Machines running", :size=>3 do |d|
57
                d["HOST_SHARE"]["RUNNING_VMS"]
58
            end
59

    
60
            column :ZVM, "Number of Virtual Machine zombies", :size=>3 do |d|
61
                d["TEMPLATE"]["TOTAL_ZOMBIES"] || 0
62
            end
63

    
64
            column :TCPU, "Total CPU percentage", :size=>4 do |d|
65
                d["HOST_SHARE"]["MAX_CPU"]
66
            end
67

    
68
            column :FCPU, "Free CPU percentage", :size=>4 do |d|
69
                d["HOST_SHARE"]["MAX_CPU"].to_i-
70
                    d["HOST_SHARE"]["USED_CPU"].to_i
71
            end
72

    
73
            column :ACPU, "Available cpu percentage (not reserved by VMs)",
74
                    :size=>4 do |d|
75
                max_cpu=d["HOST_SHARE"]["MAX_CPU"].to_i
76
                max_cpu=100 if max_cpu==0
77
                max_cpu-d["HOST_SHARE"]["CPU_USAGE"].to_i
78
            end
79

    
80
            column :TMEM, "Total Memory", :size=>7 do |d|
81
                OpenNebulaHelper.unit_to_str(
82
                    d["HOST_SHARE"]["MAX_MEM"].to_i,
83
                    options)
84
            end
85

    
86
            column :FMEM, "Free Memory", :size=>7 do |d|
87
                OpenNebulaHelper.unit_to_str(
88
                    d["HOST_SHARE"]["FREE_MEM"].to_i,
89
                    options)
90
            end
91

    
92
            column :AMEM, "Available Memory (not reserved by VMs)",
93
                    :size=>7 do |d|
94
                acpu=d["HOST_SHARE"]["MAX_MEM"].to_i-
95
                    d["HOST_SHARE"]["MEM_USAGE"].to_i
96
                OpenNebulaHelper.unit_to_str(acpu,options)
97
            end
98

    
99
            column :REAL_CPU, "Real CPU", :size=>18 do |d|
100
                max_cpu  = d["HOST_SHARE"]["MAX_CPU"].to_i
101

    
102
                if max_cpu != 0
103
                    used_cpu = d["HOST_SHARE"]["USED_CPU"].to_i
104
                    ratio    = (used_cpu*100) / max_cpu
105
                    "#{used_cpu} / #{max_cpu} (#{ratio}%)"
106
                else
107
                    '-'
108
                end
109
            end
110

    
111
            column :ALLOCATED_CPU, "Allocated CPU)", :size=>18 do |d|
112
                max_cpu  = d["HOST_SHARE"]["MAX_CPU"].to_i
113
                cpu_usage = d["HOST_SHARE"]["CPU_USAGE"].to_i
114

    
115
                if max_cpu == 0 && cpu_usage == 0
116
                    '-'
117
                else
118
                    cpu_usage = d["HOST_SHARE"]["CPU_USAGE"].to_i
119

    
120
                    if max_cpu != 0
121
                        ratio    = (cpu_usage*100) / max_cpu
122
                        "#{cpu_usage} / #{max_cpu} (#{ratio}%)"
123
                    else
124
                        "#{cpu_usage} / -"
125
                    end
126
                end
127
            end
128

    
129
            column :REAL_MEM, "Real MEM", :size=>18 do |d|
130
                max_mem  = d["HOST_SHARE"]["MAX_MEM"].to_i
131

    
132
                if max_mem != 0
133
                    used_mem = d["HOST_SHARE"]["USED_MEM"].to_i
134
                    ratio    = (used_mem*100) / max_mem
135
                    "#{OpenNebulaHelper.unit_to_str(used_mem,options)} / #{OpenNebulaHelper.unit_to_str(max_mem,options)} (#{ratio}%)"
136
                else
137
                    '-'
138
                end
139
            end
140

    
141
            column :ALLOCATED_MEM, "Allocated MEM", :size=>18 do |d|
142
                max_mem  = d["HOST_SHARE"]["MAX_MEM"].to_i
143
                mem_usage = d["HOST_SHARE"]["MEM_USAGE"].to_i
144

    
145
                if max_mem == 0 && mem_usage == 0
146
                    '-'
147
                else
148
                    if max_mem != 0
149
                        ratio    = (mem_usage*100) / max_mem
150
                        "#{OpenNebulaHelper.unit_to_str(mem_usage,options)} / #{OpenNebulaHelper.unit_to_str(max_mem,options)} (#{ratio}%)"
151
                    else
152
                        "#{OpenNebulaHelper.unit_to_str(mem_usage,options)} / -"
153
                    end
154
                end
155
            end
156

    
157
            column :STAT, "Host status", :left, :size=>6 do |d|
158
                OneHostHelper.state_to_str(d["STATE"])
159
            end
160

    
161
            default :ID, :NAME, :CLUSTER, :RVM, :ALLOCATED_CPU, :ALLOCATED_MEM, :STAT
162
        end
163

    
164
        table
165
    end
166

    
167

    
168
    NUM_THREADS = 15
169
    def sync(host_ids, options)
170
        if `id -u`.to_i == 0 || `id -G`.split.collect{|e| e.to_i}.include?(0)
171
            STDERR.puts("Cannot run 'onehost sync' as root")
172
            exit -1
173
        end
174

    
175
        begin
176
            current_version = File.read(REMOTES_LOCATION+'/VERSION').strip
177
        rescue
178
            STDERR.puts("Could not read #{REMOTES_LOCATION}/VERSION")
179
            exit(-1)
180
        end
181

    
182
        if current_version.empty?
183
            STDERR.puts "Remotes version can not be empty"
184
            exit(-1)
185
        end
186

    
187
        begin
188
            current_version = Gem::Version.new(current_version)
189
        rescue
190
            STDERR.puts "VERSION file is malformed, use semantic versioning."
191
        end
192

    
193
        cluster_id = options[:cluster]
194

    
195
        # Get remote_dir (implies oneadmin group)
196
        rc = OpenNebula::System.new(@client).get_configuration
197
        return -1, rc.message if OpenNebula.is_error?(rc)
198

    
199
        conf = rc
200
        remote_dir = conf['SCRIPTS_REMOTE_DIR']
201

    
202
        # Verify the existence of REMOTES_LOCATION
203
        if !File.directory? REMOTES_LOCATION
204
            error_msg = "'#{REMOTES_LOCATION}' does not exist. " <<
205
                            "This command must be run in the frontend."
206
            return -1,error_msg
207
        end
208

    
209
        # Touch the update file
210
        FileUtils.touch(File.join(REMOTES_LOCATION,'.update'))
211

    
212
        # Get the Host pool
213
        filter_flag ||= OpenNebula::Pool::INFO_ALL
214

    
215
        pool = factory_pool(filter_flag)
216

    
217
        rc = pool.info
218
        return -1, rc.message if OpenNebula.is_error?(rc)
219

    
220
        # Assign hosts to threads
221
        i = 0
222
        queue = Array.new
223

    
224
        pool.each do |host|
225
            if host_ids
226
                next if !host_ids.include?(host['ID'].to_i)
227
            elsif cluster_id
228
                next if host['CLUSTER_ID'].to_i != cluster_id
229
            end
230

    
231
            vm_mad = host['VM_MAD'].downcase
232
            remote_remotes = host['TEMPLATE/REMOTE_REMOTES']
233
            state = host['STATE']
234

    
235
            # Skip this host from remote syncing if it's a PUBLIC_CLOUD host
236
            next if host['TEMPLATE/PUBLIC_CLOUD'] == 'YES'
237

    
238
            # Skip this host from remote syncing if it's OFFLINE
239
            next if Host::HOST_STATES[state.to_i] == 'OFFLINE'
240

    
241
            host_version=host['TEMPLATE/VERSION']
242

    
243
            begin
244
                host_version = Gem::Version.new(host_version)
245
            rescue
246
            end
247

    
248
            if !options[:force]
249
                begin
250
                    next if host_version && host_version >= current_version
251
                rescue
252
                    STDERR.puts "Error comparing versions for host #{host['NAME']}."
253
                end
254
            end
255

    
256
            puts "* Adding #{host['NAME']} to upgrade"
257

    
258
            queue << host
259
        end
260

    
261
        # Run the jobs in threads
262
        host_errors = Array.new
263
        queue_lock = Mutex.new
264
        error_lock = Mutex.new
265
        total = queue.length
266

    
267
        if total==0
268
            puts "No hosts are going to be updated."
269
            exit(0)
270
        end
271

    
272
        ts = (1..NUM_THREADS).map do |t|
273
            Thread.new do
274
                while true do
275
                    host = nil
276
                    size = 0
277

    
278
                    queue_lock.synchronize do
279
                        host=queue.shift
280
                        size=queue.length
281
                    end
282

    
283
                    break if !host
284

    
285
                    print_update_info(total-size, total, host['NAME'])
286

    
287
                    if options[:rsync]
288
                        sync_cmd = "rsync -Laz --delete #{REMOTES_LOCATION}" <<
289
                            " #{host['NAME']}:#{remote_dir}"
290
                    else
291
                        sync_cmd = "scp -rp #{REMOTES_LOCATION}/. " <<
292
                            "#{host['NAME']}:#{remote_dir} 2> /dev/null"
293
                    end
294

    
295
                    `#{sync_cmd} 2>/dev/null`
296

    
297
                    if !$?.success?
298
                        error_lock.synchronize {
299
                            host_errors << host['NAME']
300
                        }
301
                    else
302
                        update_version(host, current_version)
303
                    end
304
                end
305
            end
306
        end
307

    
308
        # Wait for threads to finish
309
        ts.each{|t| t.join}
310

    
311
        puts
312

    
313
        if host_errors.empty?
314
            puts "All hosts updated successfully."
315
            0
316
        else
317
            STDERR.puts "Failed to update the following hosts:"
318
            host_errors.each{|h| STDERR.puts "* #{h}"}
319
            -1
320
        end
321
    end
322

    
323
    private
324

    
325
    def print_update_info(current, total, host)
326
        bar_length=40
327

    
328
        percentage=current.to_f/total.to_f
329
        done=(percentage*bar_length).floor
330

    
331
        bar="["
332
        bar+="="*done
333
        bar+="-"*(bar_length-done)
334
        bar+="]"
335

    
336
        info="#{current}/#{total}"
337

    
338
        str="#{bar} #{info} "
339
        name=host[0..(79-str.length)]
340
        str=str+name
341
        str=str+" "*(80-str.length)
342

    
343
        print "#{str}\r"
344
        STDOUT.flush
345
    end
346

    
347
    def update_version(host, version)
348
        if host.has_elements?(VERSION_XPATH)
349
            host.delete_element(VERSION_XPATH)
350
        end
351

    
352
        host.add_element(TEMPLATE_XPATH, 'VERSION' => version)
353

    
354
        template=host.template_str
355
        host.update(template)
356
    end
357

    
358
    def factory(id=nil)
359
        if id
360
            OpenNebula::Host.new_with_id(id, @client)
361
        else
362
            xml=OpenNebula::Host.build_xml
363
            OpenNebula::Host.new(xml, @client)
364
        end
365
    end
366

    
367
    def factory_pool(user_flag=-2)
368
        #TBD OpenNebula::HostPool.new(@client, user_flag)
369
        OpenNebula::HostPool.new(@client)
370
    end
371

    
372
    def format_resource(host, options = {})
373
        str    = "%-22s: %-20s"
374
        str_h1 = "%-80s"
375

    
376
        CLIHelper.print_header(
377
            str_h1 % "HOST #{host.id.to_s} INFORMATION", true)
378

    
379
        puts str % ["ID", host.id.to_s]
380
        puts str % ["NAME", host.name]
381
        puts str % ["CLUSTER", OpenNebulaHelper.cluster_str(host['CLUSTER'])]
382
        puts str % ["STATE", host.state_str]
383
        puts str % ["IM_MAD", host['IM_MAD']]
384
        puts str % ["VM_MAD", host['VM_MAD']]
385
        puts str % ["LAST MONITORING TIME", OpenNebulaHelper.time_to_str(host['LAST_MON_TIME'])]
386
        puts
387

    
388
        CLIHelper.print_header(str_h1 % "HOST SHARES", false)
389

    
390
        puts str % ["TOTAL MEM", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/MAX_MEM'].to_i, {})]
391
        puts str % ["USED MEM (REAL)", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/USED_MEM'].to_i, {})]
392
        puts str % ["USED MEM (ALLOCATED)", OpenNebulaHelper.unit_to_str(host['HOST_SHARE/MEM_USAGE'].to_i, {})]
393
        puts str % ["TOTAL CPU", host['HOST_SHARE/MAX_CPU']]
394
        puts str % ["USED CPU (REAL)", host['HOST_SHARE/USED_CPU']]
395
        puts str % ["USED CPU (ALLOCATED)", host['HOST_SHARE/CPU_USAGE']]
396
        puts str % ["RUNNING VMS", host['HOST_SHARE/RUNNING_VMS']]
397
        puts
398

    
399
        datastores = host.to_hash['HOST']['HOST_SHARE']['DATASTORES']['DS']
400

    
401
        if datastores.nil?
402
            datastores = []
403
        else
404
            datastores = [datastores].flatten
405
        end
406

    
407
        datastores.each do |datastore|
408
            CLIHelper.print_header(str_h1 % "LOCAL SYSTEM DATASTORE ##{datastore['ID']} CAPACITY", false)
409
            puts str % ["TOTAL:", OpenNebulaHelper.unit_to_str(datastore['TOTAL_MB'].to_i, {},'M')]
410
            puts str % ["USED: ", OpenNebulaHelper.unit_to_str(datastore['USED_MB'].to_i, {},'M')]
411
            puts str % ["FREE:",  OpenNebulaHelper.unit_to_str(datastore['FREE_MB'].to_i, {},'M')]
412
            puts
413
        end
414

    
415
        CLIHelper.print_header(str_h1 % "MONITORING INFORMATION", false)
416

    
417
        wilds = host.wilds
418

    
419
        begin
420
            pcis = [host.to_hash['HOST']['HOST_SHARE']['PCI_DEVICES']['PCI']]
421
            pcis = pcis.flatten.compact
422
        rescue
423
            pcis = nil
424
        end
425

    
426
        host.delete_element("TEMPLATE/VM")
427
        host.delete_element("TEMPLATE_WILDS")
428

    
429
        puts host.template_str
430

    
431
        if pcis && !pcis.empty?
432
            print_pcis(pcis)
433
        end
434

    
435
        puts
436
        CLIHelper.print_header("WILD VIRTUAL MACHINES", false)
437
        puts
438

    
439
        format = "%-30.30s %36s %4s %10s"
440
        CLIHelper.print_header(format % ["NAME", "IMPORT_ID", "CPU", "MEMORY"],
441
                               true)
442

    
443
        wilds.each do |wild|
444
          if wild['IMPORT_TEMPLATE']
445
            wild_tmplt = Base64::decode64(wild['IMPORT_TEMPLATE']).split("\n")
446
            name   = wild['VM_NAME']
447
            import = wild_tmplt.select { |line|
448
                       line[/^IMPORT_VM_ID/]
449
                     }[0].split("=")[1].gsub("\"", " ").strip
450
            memory = wild_tmplt.select { |line|
451
                       line[/^MEMORY/]
452
                     }[0].split("=")[1].gsub("\"", " ").strip
453
            cpu    = wild_tmplt.select { |line|
454
                        line[/^CPU/]
455
                     }[0].split("=")[1].gsub("\"", " ").strip
456
          else
457
            name     = wild['DEPLOY_ID']
458
            import   = memory = cpu = "-"
459
          end
460

    
461
          puts format % [name, import, cpu, memory]
462
        end
463

    
464
        puts
465
        CLIHelper.print_header("VIRTUAL MACHINES", false)
466
        puts
467

    
468
        onevm_helper=OneVMHelper.new
469
        onevm_helper.client=@client
470
        onevm_helper.list_pool({:filter=>["HOST=#{host.name}"]}, false)
471
    end
472

    
473
    def print_pcis(pcis)
474
        puts
475
        CLIHelper.print_header("PCI DEVICES", false)
476
        puts
477

    
478
        table=CLIHelper::ShowTable.new(nil, self) do
479
            column :VM, "Used by VM", :size => 5, :left => false do |d|
480
                if d["VMID"] == "-1"
481
                    ""
482
                else
483
                    d["VMID"]
484
                end
485
            end
486

    
487
            column :ADDR, "PCI Address", :size => 7, :left => true do |d|
488
                d["SHORT_ADDRESS"]
489
            end
490

    
491
            column :TYPE, "Type", :size => 14, :left => true do |d|
492
                d["TYPE"]
493
            end
494

    
495
            column :CLASS, "Class", :size => 12, :left => true do |d|
496
                d["CLASS_NAME"]
497
            end
498

    
499
            column :NAME, "Name", :size => 50, :left => true do |d|
500
                d["DEVICE_NAME"]
501
            end
502

    
503
            column :VENDOR, "Vendor", :size => 8, :left => true do |d|
504
                d["VENDOR_NAME"]
505
            end
506

    
507
            default :VM, :ADDR, :TYPE, :NAME
508

    
509
        end
510

    
511
        table.show(pcis)
512
    end
513
end