Statistics
| Branch: | Tag: | Revision:

one / share / onegate / onegate @ 05241124

History | View | Annotate | Download (19 KB)

1
#!/usr/bin/env ruby
2

    
3
require 'rubygems'
4
require 'uri'
5
require 'net/https'
6
require 'json'
7
require 'pp'
8

    
9
###############################################################################
10
# The CloudClient module contains general functionality to implement a
11
# Cloud Client
12
###############################################################################
13
module CloudClient
14

    
15
    # OpenNebula version
16
    VERSION = '5.3.90'
17

    
18
    # #########################################################################
19
    # Default location for the authentication file
20
    # #########################################################################
21

    
22
    if ENV["HOME"]
23
        DEFAULT_AUTH_FILE = ENV["HOME"]+"/.one/one_auth"
24
    else
25
        DEFAULT_AUTH_FILE = "/var/lib/one/.one/one_auth"
26
    end
27

    
28
    # #########################################################################
29
    # Gets authorization credentials from ONE_AUTH or default
30
    # auth file.
31
    #
32
    # Raises an error if authorization is not found
33
    # #########################################################################
34
    def self.get_one_auth
35
        if ENV["ONE_AUTH"] and !ENV["ONE_AUTH"].empty? and
36
            File.file?(ENV["ONE_AUTH"])
37
            one_auth=File.read(ENV["ONE_AUTH"]).strip.split(':')
38
        elsif File.file?(DEFAULT_AUTH_FILE)
39
            one_auth=File.read(DEFAULT_AUTH_FILE).strip.split(':')
40
        else
41
            raise "No authorization data present"
42
        end
43

    
44
        raise "Authorization data malformed" if one_auth.length < 2
45

    
46
        one_auth
47
    end
48

    
49
    # #########################################################################
50
    # Starts an http connection and calls the block provided. SSL flag
51
    # is set if needed.
52
    # #########################################################################
53
    def self.http_start(url, timeout, &block)
54
        host = nil
55
        port = nil
56

    
57
        if ENV['http_proxy']
58
            uri_proxy  = URI.parse(ENV['http_proxy'])
59
            host = uri_proxy.host
60
            port = uri_proxy.port
61
        end
62

    
63
        http = Net::HTTP::Proxy(host, port).new(url.host, url.port)
64

    
65
        if timeout
66
            http.read_timeout = timeout.to_i
67
        end
68

    
69
        if url.scheme=='https'
70
            http.use_ssl = true
71
            http.verify_mode=OpenSSL::SSL::VERIFY_NONE
72
        end
73

    
74
        begin
75
            res = http.start do |connection|
76
                block.call(connection)
77
            end
78
        rescue Errno::ECONNREFUSED => e
79
            str =  "Error connecting to server (#{e.to_s}).\n"
80
            str << "Server: #{url.host}:#{url.port}"
81

    
82
            return CloudClient::Error.new(str,"503")
83
        rescue Errno::ETIMEDOUT => e
84
            str =  "Error timeout connecting to server (#{e.to_s}).\n"
85
            str << "Server: #{url.host}:#{url.port}"
86

    
87
            return CloudClient::Error.new(str,"504")
88
        rescue Timeout::Error => e
89
            str =  "Error timeout while connected to server (#{e.to_s}).\n"
90
            str << "Server: #{url.host}:#{url.port}"
91

    
92
            return CloudClient::Error.new(str,"504")
93
        rescue SocketError => e
94
            str =  "Error timeout while connected to server (#{e.to_s}).\n"
95

    
96
            return CloudClient::Error.new(str,"503")
97
        rescue
98
            return CloudClient::Error.new($!.to_s,"503")
99
        end
100

    
101
        if res.is_a?(Net::HTTPSuccess)
102
            res
103
        else
104
            CloudClient::Error.new(res.body, res.code)
105
        end
106
    end
107

    
108
    # #########################################################################
109
    # The Error Class represents a generic error in the Cloud Client
110
    # library. It contains a readable representation of the error.
111
    # #########################################################################
112
    class Error
113
        attr_reader :message
114
        attr_reader :code
115

    
116
        # +message+ a description of the error
117
        def initialize(message=nil, code="500")
118
            @message=message
119
            @code=code
120
        end
121

    
122
        def to_s()
123
            @message
124
        end
125
    end
126

    
127
    # #########################################################################
128
    # Returns true if the object returned by a method of the OpenNebula
129
    # library is an Error
130
    # #########################################################################
131
    def self.is_error?(value)
132
        value.class==CloudClient::Error
133
    end
134
end
135

    
136
module OneGate
137
    module VirtualMachine
138
        VM_STATE=%w{INIT PENDING HOLD ACTIVE STOPPED SUSPENDED DONE FAILED
139
            POWEROFF UNDEPLOYED CLONING CLONING_FAILURE}
140

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

    
209
        SHORT_VM_STATES={
210
            "INIT"              => "init",
211
            "PENDING"           => "pend",
212
            "HOLD"              => "hold",
213
            "ACTIVE"            => "actv",
214
            "STOPPED"           => "stop",
215
            "SUSPENDED"         => "susp",
216
            "DONE"              => "done",
217
            "FAILED"            => "fail",
218
            "POWEROFF"          => "poff",
219
            "UNDEPLOYED"        => "unde",
220
            "CLONING"           => "clon",
221
            "CLONING_FAILURE"   => "fail"
222
        }
223

    
224
        SHORT_LCM_STATES={
225
            "PROLOG"            => "prol",
226
            "BOOT"              => "boot",
227
            "RUNNING"           => "runn",
228
            "MIGRATE"           => "migr",
229
            "SAVE_STOP"         => "save",
230
            "SAVE_SUSPEND"      => "save",
231
            "SAVE_MIGRATE"      => "save",
232
            "PROLOG_MIGRATE"    => "migr",
233
            "PROLOG_RESUME"     => "prol",
234
            "EPILOG_STOP"       => "epil",
235
            "EPILOG"            => "epil",
236
            "SHUTDOWN"          => "shut",
237
            "CANCEL"            => "shut",
238
            "FAILURE"           => "fail",
239
            "CLEANUP_RESUBMIT"  => "clea",
240
            "UNKNOWN"           => "unkn",
241
            "HOTPLUG"           => "hotp",
242
            "SHUTDOWN_POWEROFF" => "shut",
243
            "BOOT_UNKNOWN"      => "boot",
244
            "BOOT_POWEROFF"     => "boot",
245
            "BOOT_SUSPENDED"    => "boot",
246
            "BOOT_STOPPED"      => "boot",
247
            "CLEANUP_DELETE"    => "clea",
248
            "HOTPLUG_SNAPSHOT"  => "snap",
249
            "HOTPLUG_NIC"       => "hotp",
250
            "HOTPLUG_SAVEAS"           => "hotp",
251
            "HOTPLUG_SAVEAS_POWEROFF"  => "hotp",
252
            "HOTPLUG_SAVEAS_SUSPENDED" => "hotp",
253
            "SHUTDOWN_UNDEPLOY" => "shut",
254
            "EPILOG_UNDEPLOY"   => "epil",
255
            "PROLOG_UNDEPLOY"   => "prol",
256
            "BOOT_UNDEPLOY"     => "boot",
257
            "HOTPLUG_PROLOG_POWEROFF"   => "hotp",
258
            "HOTPLUG_EPILOG_POWEROFF"   => "hotp",
259
            "BOOT_MIGRATE"              => "boot",
260
            "BOOT_FAILURE"              => "fail",
261
            "BOOT_MIGRATE_FAILURE"      => "fail",
262
            "PROLOG_MIGRATE_FAILURE"    => "fail",
263
            "PROLOG_FAILURE"            => "fail",
264
            "EPILOG_FAILURE"            => "fail",
265
            "EPILOG_STOP_FAILURE"       => "fail",
266
            "EPILOG_UNDEPLOY_FAILURE"   => "fail",
267
            "PROLOG_MIGRATE_POWEROFF"   => "migr",
268
            "PROLOG_MIGRATE_POWEROFF_FAILURE"   => "fail",
269
            "PROLOG_MIGRATE_SUSPEND"            => "migr",
270
            "PROLOG_MIGRATE_SUSPEND_FAILURE"    => "fail",
271
            "BOOT_UNDEPLOY_FAILURE"     => "fail",
272
            "BOOT_STOPPED_FAILURE"      => "fail",
273
            "PROLOG_RESUME_FAILURE"     => "fail",
274
            "PROLOG_UNDEPLOY_FAILURE"   => "fail",
275
            "DISK_SNAPSHOT_POWEROFF"        => "snap",
276
            "DISK_SNAPSHOT_REVERT_POWEROFF" => "snap",
277
            "DISK_SNAPSHOT_DELETE_POWEROFF" => "snap",
278
            "DISK_SNAPSHOT_SUSPENDED"       => "snap",
279
            "DISK_SNAPSHOT_REVERT_SUSPENDED"=> "snap",
280
            "DISK_SNAPSHOT_DELETE_SUSPENDED"=> "snap",
281
            "DISK_SNAPSHOT"        => "snap",
282
            "DISK_SNAPSHOT_DELETE" => "snap",
283
            "PROLOG_MIGRATE_UNKNOWN" => "migr",
284
            "PROLOG_MIGRATE_UNKNOWN_FAILURE" => "fail",
285
            "DISK_RESIZE"            => "drsz",
286
            "DISK_RESIZE_POWEROFF"   => "drsz",
287
            "DISK_RESIZE_UNDEPLOYED" => "drsz"
288
        }
289

    
290
        def self.state_to_str(id, lcm_id)
291
            id = id.to_i
292
            state_str = VM_STATE[id]
293

    
294
            if state_str=="ACTIVE"
295
                lcm_id = lcm_id.to_i
296
                return LCM_STATE[lcm_id]
297
            end
298

    
299
            return state_str
300
        end
301

    
302
        def self.print(json_hash)
303
            OneGate.print_header("VM " + json_hash["VM"]["ID"])
304
            OneGate.print_key_value("NAME", json_hash["VM"]["NAME"])
305
            OneGate.print_key_value(
306
                "STATE",
307
                self.state_to_str(
308
                    json_hash["VM"]["STATE"],
309
                    json_hash["VM"]["LCM_STATE"]))
310

    
311
            vm_nics = [json_hash['VM']['TEMPLATE']['NIC']].flatten
312
            vm_nics.each { |nic|
313
                # TODO: IPv6
314
                OneGate.print_key_value("IP", nic["IP"])
315
            }
316
        end
317
    end
318

    
319
    module Service
320
        STATE = {
321
            'PENDING'            => 0,
322
            'DEPLOYING'          => 1,
323
            'RUNNING'            => 2,
324
            'UNDEPLOYING'        => 3,
325
            'WARNING'            => 4,
326
            'DONE'               => 5,
327
            'FAILED_UNDEPLOYING' => 6,
328
            'FAILED_DEPLOYING'   => 7,
329
            'SCALING'            => 8,
330
            'FAILED_SCALING'     => 9,
331
            'COOLDOWN'           => 10
332
        }
333

    
334
        STATE_STR = [
335
            'PENDING',
336
            'DEPLOYING',
337
            'RUNNING',
338
            'UNDEPLOYING',
339
            'WARNING',
340
            'DONE',
341
            'FAILED_UNDEPLOYING',
342
            'FAILED_DEPLOYING',
343
            'SCALING',
344
            'FAILED_SCALING',
345
            'COOLDOWN'
346
        ]
347

    
348
        # Returns the string representation of the service state
349
        # @param [String] state String number representing the state
350
        # @return the state string
351
        def self.state_str(state_number)
352
            return STATE_STR[state_number.to_i]
353
        end
354

    
355
        def self.print(json_hash)
356
            OneGate.print_header("SERVICE " + json_hash["SERVICE"]["id"])
357
            OneGate.print_key_value("NAME", json_hash["SERVICE"]["name"])
358
            OneGate.print_key_value("STATE", Service.state_str(json_hash["SERVICE"]['state']))
359
            puts
360

    
361
            roles = [json_hash['SERVICE']['roles']].flatten
362
            roles.each { |role|
363
                OneGate.print_header("ROLE " + role["name"], false)
364

    
365
                if role["nodes"]
366
                    role["nodes"].each{ |node|
367
                        OneGate::VirtualMachine.print(node["vm_info"])
368
                    }
369
                end
370

    
371
                puts
372
            }
373
        end
374
    end
375

    
376
    class Client
377
        def initialize(opts={})
378
            @vmid = ENV["VMID"]
379
            @token = ENV["TOKENTXT"]
380

    
381
            url = opts[:url] || ENV['ONEGATE_ENDPOINT']
382
            @uri = URI.parse(url)
383

    
384
            @user_agent = "OpenNebula #{CloudClient::VERSION} " <<
385
                "(#{opts[:user_agent]||"Ruby"})"
386

    
387
            @host = nil
388
            @port = nil
389

    
390
            if ENV['http_proxy']
391
                uri_proxy  = URI.parse(ENV['http_proxy'])
392
                @host = uri_proxy.host
393
                @port = uri_proxy.port
394
            end
395
        end
396

    
397
        def get(path)
398
            req = Net::HTTP::Proxy(@host, @port)::Get.new(path)
399

    
400
            do_request(req)
401
        end
402

    
403
        def delete(path)
404
            req =Net::HTTP::Proxy(@host, @port)::Delete.new(path)
405

    
406
            do_request(req)
407
        end
408

    
409
        def post(path, body)
410
            req = Net::HTTP::Proxy(@host, @port)::Post.new(path)
411
            req.body = body
412

    
413
            do_request(req)
414
        end
415

    
416
        def put(path, body)
417
            req = Net::HTTP::Proxy(@host, @port)::Put.new(path)
418
            req.body = body
419

    
420
            do_request(req)
421
        end
422

    
423
        def login
424
            req = Net::HTTP::Proxy(@host, @port)::Post.new('/login')
425

    
426
            do_request(req)
427
        end
428

    
429
        def logout
430
            req = Net::HTTP::Proxy(@host, @port)::Post.new('/logout')
431

    
432
            do_request(req)
433
        end
434

    
435
        private
436

    
437
        def do_request(req)
438
            req.basic_auth @username, @password
439

    
440
            req['User-Agent'] = @user_agent
441
            req['X-ONEGATE-TOKEN'] = @token
442
            req['X-ONEGATE-VMID'] = @vmid
443

    
444
            res = CloudClient::http_start(@uri, @timeout) do |http|
445
                http.request(req)
446
            end
447

    
448
            res
449
        end
450
    end
451

    
452
    def self.parse_json(response)
453
        if CloudClient::is_error?(response)
454
            puts "ERROR: "
455
            puts response.message
456
            exit -1
457
        else
458
            return JSON.parse(response.body)
459
        end
460
    end
461

    
462
    # Sets bold font
463
    def self.scr_bold
464
        print "\33[1m"
465
    end
466

    
467
    # Sets underline
468
    def self.scr_underline
469
        print "\33[4m"
470
    end
471

    
472
    # Restore normal font
473
    def self.scr_restore
474
        print "\33[0m"
475
    end
476

    
477
    # Print header
478
    def self.print_header(str, underline=true)
479
        if $stdout.tty?
480
            scr_bold
481
            scr_underline if underline
482
            print "%-80s" % str
483
            scr_restore
484
        else
485
            print str
486
        end
487
        puts
488
    end
489

    
490
    def self.print_key_value(key, value)
491
        puts "%-20s: %-20s" % [key, value]
492
    end
493

    
494
    def self.help_str
495
        return <<-EOT
496
Available commands
497
    $ onegate vm show [VMID] [--json]
498

    
499
    $ onegate vm update [VMID] --data KEY=VALUE[\\nKEY2=VALUE2]
500

    
501
    $ onegate vm ACTION VMID
502
        $ onegate resume [VMID]
503
        $ onegate stop [VMID]
504
        $ onegate suspend [VMID]
505
        $ onegate terminate [VMID] [--hard]
506
        $ onegate reboot [VMID] [--hard]
507
        $ onegate poweroff [VMID] [--hard]
508
        $ onegate resched [VMID]
509
        $ onegate unresched [VMID]
510
        $ onegate hold [VMID]
511
        $ onegate release [VMID]
512

    
513
    $ onegate service show [--json]
514

    
515
    $ onegate service scale --role ROLE --cardinality CARDINALITY
516
EOT
517
    end
518
end
519

    
520

    
521
require 'optparse'
522

    
523
options = {}
524
OptionParser.new do |opts|
525
  opts.on("-d", "--data DATA", "Data to be included in the VM") do |data|
526
    options[:data] = data
527
  end
528

    
529
  opts.on("-r", "--role ROLE", "Service role") do |role|
530
    options[:role] = role
531
  end
532

    
533
  opts.on("-c", "--cardinality CARD", "Service cardinality") do |cardinality|
534
    options[:cardinality] = cardinality
535
  end
536

    
537
  opts.on("-j", "--json", "Print resource information in JSON") do |json|
538
    options[:json] = json
539
  end
540

    
541
  opts.on("-f", "--hard", "Hard option for power off operations") do |hard|
542
    options[:hard] = hard
543
  end
544

    
545
  opts.on("-h", "--help", "Show this message") do
546
    puts OneGate.help_str
547
    exit
548
  end
549
end.parse!
550

    
551
client = OneGate::Client.new()
552

    
553
case ARGV[0]
554
when "vm"
555
    case ARGV[1]
556
    when "show"
557
        if ARGV[2]
558
            response = client.get("/vms/"+ARGV[2])
559
        else
560
            response = client.get("/vm")
561
        end
562

    
563
        json_hash = OneGate.parse_json(response)
564
        if options[:json]
565
            puts JSON.pretty_generate(json_hash)
566
        else
567
            OneGate::VirtualMachine.print(json_hash)
568
        end
569
    when "update"
570
        if !options[:data]
571
            puts "You have to provide the data as a param (--data)"
572
            exit -1
573
        end
574

    
575
        if ARGV[2]
576
            response = client.put("/vms/"+ARGV[2], options[:data])
577
        else
578
            response = client.put("/vm", options[:data])
579
        end
580

    
581
        if CloudClient::is_error?(response)
582
            puts "ERROR: "
583
            puts response.message
584
            exit -1
585
        end
586
    when "resume",
587
         "stop",
588
         "suspend",
589
         "terminate",
590
         "reboot",
591
         "poweroff",
592
         "resched",
593
         "unresched",
594
         "hold",
595
         "release",
596
         # Compatibility with 4.x
597
         "delete",
598
         "shutdown",
599
        if ARGV[2]
600
            action_hash = {
601
                "action" => {
602
                    "perform" => ARGV[1]
603
                }
604
            }
605

    
606
            if options[:hard]
607
                action_hash["action"]["params"] = true
608
            end
609

    
610
            response = client.post("/vms/"+ARGV[2]+"/action", action_hash.to_json)
611

    
612
            if CloudClient::is_error?(response)
613
                puts "ERROR: "
614
                puts response.message
615
                exit -1
616
            end
617
        else
618
            puts "You have to provide a VM ID"
619
            exit -1
620
        end
621
    else
622
        puts OneGate.help_str
623
        puts
624
        puts "Action #{ARGV[1]} not supported"
625
        exit -1
626
    end
627
when "service"
628
    case ARGV[1]
629
    when "show"
630
        response = client.get("/service")
631
        json_hash = OneGate.parse_json(response)
632
        #pp json_hash
633
        if options[:json]
634
            puts JSON.pretty_generate(json_hash)
635
        else
636
            OneGate::Service.print(json_hash)
637
        end
638
    when "scale"
639
        response = client.put(
640
            "/service/role/" + options[:role],
641
            {
642
                :cardinality => options[:cardinality]
643
            }.to_json)
644

    
645
        if CloudClient::is_error?(response)
646
            puts "ERROR: "
647
            puts response.message
648
            exit -1
649
        end
650
    else
651
        puts OneGate.help_str
652
        puts
653
        puts "Action #{ARGV[1]} not supported"
654
        exit -1
655
    end
656
else
657
    puts OneGate.help_str
658
    exit -1
659
end
660