Revision 5ce7c87c

View differences:

share/onegate/init.sh
1
#mkdir /mnt/context
2
#mount /dev/disk/by-label/CONTEXT /mnt/context/
3

  
4
cp /mnt/onegate /usr/bin
5
chmod +x /usr/bin/onegate
6

  
7
TOKENTXT=`cat /mnt/token.txt`
8
echo "export TOKENTXT=$TOKENTXT" >> /root/.bashrc
9

  
10
function export_rc_vars
11
{
12
  if [ -f $1 ] ; then
13
    ONE_VARS=`cat $1 | egrep -e '^[a-zA-Z\-\_0-9]*=' | sed 's/=.*$//'`
14

  
15
    . $1
16

  
17
    for v in $ONE_VARS; do
18
        echo "export $v=\"${!v}\"" >> /root/.bashrc
19
    done
20
  fi
21
}
22

  
23
export_rc_vars /mnt/context.sh
share/onegate/onegate
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 = '4.8.0'
17

  
18
    # #########################################################################
19
    # Default location for the authentication file
20
    # #########################################################################
21
    DEFAULT_AUTH_FILE = ENV["HOME"]+"/.one/one_auth"
22

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

  
39
        raise "Authorization data malformed" if one_auth.length < 2
40

  
41
        one_auth
42
    end
43

  
44
    # #########################################################################
45
    # Starts an http connection and calls the block provided. SSL flag
46
    # is set if needed.
47
    # #########################################################################
48
    def self.http_start(url, timeout, &block)
49
        host = nil
50
        port = nil
51

  
52
        if ENV['http_proxy']
53
            uri_proxy  = URI.parse(ENV['http_proxy'])
54
            host = uri_proxy.host
55
            port = uri_proxy.port
56
        end
57

  
58
        http = Net::HTTP::Proxy(host, port).new(url.host, url.port)
59

  
60
        if timeout
61
            http.read_timeout = timeout.to_i
62
        end
63

  
64
        if url.scheme=='https'
65
            http.use_ssl = true
66
            http.verify_mode=OpenSSL::SSL::VERIFY_NONE
67
        end
68

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

  
77
            return CloudClient::Error.new(str,"503")
78
        rescue Errno::ETIMEDOUT => e
79
            str =  "Error timeout connecting to server (#{e.to_s}).\n"
80
            str << "Server: #{url.host}:#{url.port}"
81

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

  
87
            return CloudClient::Error.new(str,"504")
88
        rescue SocketError => e
89
            str =  "Error timeout while connected to server (#{e.to_s}).\n"
90

  
91
            return CloudClient::Error.new(str,"503")
92
        rescue
93
            return CloudClient::Error.new($!.to_s,"503")
94
        end
95

  
96
        if res.is_a?(Net::HTTPSuccess)
97
            res
98
        else
99
            CloudClient::Error.new(res.body, res.code)
100
        end
101
    end
102

  
103
    # #########################################################################
104
    # The Error Class represents a generic error in the Cloud Client
105
    # library. It contains a readable representation of the error.
106
    # #########################################################################
107
    class Error
108
        attr_reader :message
109
        attr_reader :code
110

  
111
        # +message+ a description of the error
112
        def initialize(message=nil, code="500")
113
            @message=message
114
            @code=code
115
        end
116

  
117
        def to_s()
118
            @message
119
        end
120
    end
121

  
122
    # #########################################################################
123
    # Returns true if the object returned by a method of the OpenNebula
124
    # library is an Error
125
    # #########################################################################
126
    def self.is_error?(value)
127
        value.class==CloudClient::Error
128
    end
129
end
130

  
131
module OneGate
132
    module VirtualMachine
133
        VM_STATE=%w{INIT PENDING HOLD ACTIVE STOPPED SUSPENDED DONE FAILED
134
            POWEROFF UNDEPLOYED}
135

  
136
        LCM_STATE=%w{LCM_INIT PROLOG BOOT RUNNING MIGRATE SAVE_STOP SAVE_SUSPEND
137
            SAVE_MIGRATE PROLOG_MIGRATE PROLOG_RESUME EPILOG_STOP EPILOG
138
            SHUTDOWN CANCEL FAILURE CLEANUP_RESUBMIT UNKNOWN HOTPLUG SHUTDOWN_POWEROFF
139
            BOOT_UNKNOWN BOOT_POWEROFF BOOT_SUSPENDED BOOT_STOPPED CLEANUP_DELETE
140
            HOTPLUG_SNAPSHOT HOTPLUG_NIC HOTPLUG_SAVEAS HOTPLUG_SAVEAS_POWEROFF
141
            HOTPLUG_SAVEAS_SUSPENDED SHUTDOWN_UNDEPLOY EPILOG_UNDEPLOY
142
            PROLOG_UNDEPLOY BOOT_UNDEPLOY}
143

  
144
        SHORT_VM_STATES={
145
            "INIT"      => "init",
146
            "PENDING"   => "pend",
147
            "HOLD"      => "hold",
148
            "ACTIVE"    => "actv",
149
            "STOPPED"   => "stop",
150
            "SUSPENDED" => "susp",
151
            "DONE"      => "done",
152
            "FAILED"    => "fail",
153
            "POWEROFF"  => "poff",
154
            "UNDEPLOYED"=> "unde"
155
        }
156

  
157
        SHORT_LCM_STATES={
158
            "PROLOG"            => "prol",
159
            "BOOT"              => "boot",
160
            "RUNNING"           => "runn",
161
            "MIGRATE"           => "migr",
162
            "SAVE_STOP"         => "save",
163
            "SAVE_SUSPEND"      => "save",
164
            "SAVE_MIGRATE"      => "save",
165
            "PROLOG_MIGRATE"    => "migr",
166
            "PROLOG_RESUME"     => "prol",
167
            "EPILOG_STOP"       => "epil",
168
            "EPILOG"            => "epil",
169
            "SHUTDOWN"          => "shut",
170
            "CANCEL"            => "shut",
171
            "FAILURE"           => "fail",
172
            "CLEANUP_RESUBMIT"  => "clea",
173
            "UNKNOWN"           => "unkn",
174
            "HOTPLUG"           => "hotp",
175
            "SHUTDOWN_POWEROFF" => "shut",
176
            "BOOT_UNKNOWN"      => "boot",
177
            "BOOT_POWEROFF"     => "boot",
178
            "BOOT_SUSPENDED"    => "boot",
179
            "BOOT_STOPPED"      => "boot",
180
            "CLEANUP_DELETE"    => "clea",
181
            "HOTPLUG_SNAPSHOT"  => "snap",
182
            "HOTPLUG_NIC"       => "hotp",
183
            "HOTPLUG_SAVEAS"           => "hotp",
184
            "HOTPLUG_SAVEAS_POWEROFF"  => "hotp",
185
            "HOTPLUG_SAVEAS_SUSPENDED" => "hotp",
186
            "SHUTDOWN_UNDEPLOY" => "shut",
187
            "EPILOG_UNDEPLOY"   => "epil",
188
            "PROLOG_UNDEPLOY"   => "prol",
189
            "BOOT_UNDEPLOY"     => "boot"
190
        }
191

  
192
        def self.state_to_str(id, lcm_id)
193
            id = id.to_i
194
            state_str = VM_STATE[id]
195

  
196
            if state_str=="ACTIVE"
197
                lcm_id = lcm_id.to_i
198
                return LCM_STATE[lcm_id]
199
            end
200

  
201
            return state_str
202
        end
203

  
204
        def self.print(json_hash)
205
            OneGate.print_header("VM " + json_hash["VM"]["ID"])
206
            OneGate.print_key_value("NAME", json_hash["VM"]["NAME"])
207
            OneGate.print_key_value(
208
                "STATE",
209
                self.state_to_str(
210
                    json_hash["VM"]["STATE"],
211
                    json_hash["VM"]["LCM_STATE"]))
212

  
213
            vm_nics = [json_hash['VM']['TEMPLATE']['NIC']].flatten
214
            vm_nics.each { |nic|
215
                # TODO: IPv6
216
                OneGate.print_key_value("IP", nic["IP"])
217
            }
218
        end
219
    end
220

  
221
    module Service
222
        STATE = {
223
            'PENDING'            => 0,
224
            'DEPLOYING'          => 1,
225
            'RUNNING'            => 2,
226
            'UNDEPLOYING'        => 3,
227
            'WARNING'            => 4,
228
            'DONE'               => 5,
229
            'FAILED_UNDEPLOYING' => 6,
230
            'FAILED_DEPLOYING'   => 7,
231
            'SCALING'            => 8,
232
            'FAILED_SCALING'     => 9,
233
            'COOLDOWN'           => 10
234
        }
235

  
236
        STATE_STR = [
237
            'PENDING',
238
            'DEPLOYING',
239
            'RUNNING',
240
            'UNDEPLOYING',
241
            'WARNING',
242
            'DONE',
243
            'FAILED_UNDEPLOYING',
244
            'FAILED_DEPLOYING',
245
            'SCALING',
246
            'FAILED_SCALING',
247
            'COOLDOWN'
248
        ]
249

  
250
        # Returns the string representation of the service state
251
        # @param [String] state String number representing the state
252
        # @return the state string
253
        def self.state_str(state_number)
254
            return STATE_STR[state_number.to_i]
255
        end
256

  
257
        def self.print(json_hash)
258
            OneGate.print_header("SERVICE " + json_hash["SERVICE"]["id"])
259
            OneGate.print_key_value("NAME", json_hash["SERVICE"]["name"])
260
            OneGate.print_key_value("STATE", Service.state_str(json_hash["SERVICE"]['state']))
261
            puts
262

  
263
            roles = [json_hash['SERVICE']['roles']].flatten
264
            roles.each { |role|
265
                OneGate.print_header("ROLE " + role["name"], false)
266

  
267
                if role["nodes"]
268
                    role["nodes"].each{ |node|
269
                        OneGate::VirtualMachine.print(node["vm_info"])
270
                    }
271
                end
272

  
273
                puts
274
            }
275
        end
276
    end
277

  
278
    class Client
279
        def initialize(opts={})
280
            @vmid = ENV["VMID"]
281
            @token = ENV["TOKENTXT"]
282

  
283
            url = opts[:url] || ENV['ONEGATE_ENDPOINT']
284
            @uri = URI.parse(url)
285

  
286
            @user_agent = "OpenNebula #{CloudClient::VERSION} " <<
287
                "(#{opts[:user_agent]||"Ruby"})"
288

  
289
            @host = nil
290
            @port = nil
291

  
292
            if ENV['http_proxy']
293
                uri_proxy  = URI.parse(ENV['http_proxy'])
294
                @host = uri_proxy.host
295
                @port = uri_proxy.port
296
            end
297
        end
298

  
299
        def get(path)
300
            req = Net::HTTP::Proxy(@host, @port)::Get.new(path)
301

  
302
            do_request(req)
303
        end
304

  
305
        def delete(path)
306
            req =Net::HTTP::Proxy(@host, @port)::Delete.new(path)
307

  
308
            do_request(req)
309
        end
310

  
311
        def post(path, body)
312
            req = Net::HTTP::Proxy(@host, @port)::Post.new(path)
313
            req.body = body
314

  
315
            do_request(req)
316
        end
317

  
318
        def put(path, body)
319
            req = Net::HTTP::Proxy(@host, @port)::Put.new(path)
320
            req.body = body
321

  
322
            do_request(req)
323
        end
324

  
325
        def login
326
            req = Net::HTTP::Proxy(@host, @port)::Post.new('/login')
327

  
328
            do_request(req)
329
        end
330

  
331
        def logout
332
            req = Net::HTTP::Proxy(@host, @port)::Post.new('/logout')
333

  
334
            do_request(req)
335
        end
336

  
337
        private
338

  
339
        def do_request(req)
340
            req.basic_auth @username, @password
341

  
342
            req['User-Agent'] = @user_agent
343
            req['X-ONEGATE-TOKEN'] = @token
344
            req['X-ONEGATE-VMID'] = @vmid
345

  
346
            res = CloudClient::http_start(@uri, @timeout) do |http|
347
                http.request(req)
348
            end
349

  
350
            res
351
        end
352
    end
353

  
354
    def self.parse_json(response)
355
        if CloudClient::is_error?(response)
356
            puts "ERROR: "
357
            puts response.message
358
            exit -1
359
        else
360
            return JSON.parse(response.body)
361
        end
362
    end
363

  
364
    # Sets bold font
365
    def self.scr_bold
366
        print "\33[1m"
367
    end
368

  
369
    # Sets underline
370
    def self.scr_underline
371
        print "\33[4m"
372
    end
373

  
374
    # Restore normal font
375
    def self.scr_restore
376
        print "\33[0m"
377
    end
378

  
379
    # Print header
380
    def self.print_header(str, underline=true)
381
        if $stdout.tty?
382
            scr_bold
383
            scr_underline if underline
384
            print "%-80s" % str
385
            scr_restore
386
        else
387
            print str
388
        end
389
        puts
390
    end
391

  
392
    def self.print_key_value(key, value)
393
        puts "%-20s: %-20s" % [key, value]
394
    end
395

  
396
    def self.help_str
397
        return <<-EOT
398
Available commands
399
    $ onegate vm show [VMID] [--json]
400

  
401
    $ onegate vm update [VMID] --data KEY=VALUE[\\nKEY2=VALUE2]
402

  
403
    $ onegate vm ACTION VMID
404
        $ onegate resume [VMID]
405
        $ onegate stop [VMID]
406
        $ onegate suspend [VMID]
407
        $ onegate delete [VMID] [--hard]
408
        $ onegate shutdown [VMID] [--hard]
409
        $ onegate reboot [VMID] [--hard]
410
        $ onegate poweroff [VMID] [--hard]
411
        $ onegate resubmit [VMID]
412
        $ onegate resched [VMID]
413
        $ onegate unresched [VMID]
414
        $ onegate hold [VMID]
415
        $ onegate release [VMID]
416

  
417
    $ onegate service show [--json]
418

  
419
    $ onegate service scale --role ROLE --cardinality CARDINALITY
420
EOT
421
    end
422
end
423

  
424

  
425
require 'optparse'
426

  
427
options = {}
428
OptionParser.new do |opts|
429
  opts.on("-d", "--data DATA", "Data to be included in the VM") do |data|
430
    options[:data] = data
431
  end
432

  
433
  opts.on("-r", "--role ROLE", "Service role") do |role|
434
    options[:role] = role
435
  end
436

  
437
  opts.on("-c", "--cardinality CARD", "Service cardinality") do |cardinality|
438
    options[:cardinality] = cardinality
439
  end
440

  
441
  opts.on("-j", "--json", "Print resource information in JSON") do |json|
442
    options[:json] = json
443
  end
444

  
445
  opts.on("-f", "--hard", "Hard option for power off operations") do |hard|
446
    options[:hard] = hard
447
  end
448

  
449
  opts.on("-h", "--help", "Show this message") do
450
    puts OneGate.help_str
451
    exit
452
  end
453
end.parse!
454

  
455
client = OneGate::Client.new()
456

  
457
case ARGV[0]
458
when "vm"
459
    case ARGV[1]
460
    when "show"
461
        if ARGV[2]
462
            response = client.get("/vms/"+ARGV[2])
463
        else
464
            response = client.get("/vm")
465
        end
466

  
467
        json_hash = OneGate.parse_json(response)
468
        if options[:json]
469
            puts JSON.pretty_generate(json_hash)
470
        else
471
            OneGate::VirtualMachine.print(json_hash)
472
        end
473
    when "update"
474
        if !options[:data]
475
            puts "You have to provide the data as a param (--data)"
476
            exit -1
477
        end
478

  
479
        if ARGV[2]
480
            response = client.put("/vms/"+ARGV[2], options[:data])
481
        else
482
            response = client.put("/vm", options[:data])
483
        end
484

  
485
        if CloudClient::is_error?(response)
486
            puts "ERROR: "
487
            puts response.message
488
            exit -1
489
        end
490
    when "resume",
491
         "stop",
492
         "suspend",
493
         "delete",
494
         "shutdown",
495
         "reboot",
496
         "poweroff",
497
         "resubmit",
498
         "resched",
499
         "unresched",
500
         "hold",
501
         "release"
502
        if ARGV[2]
503
            action_hash = {
504
                "action" => {
505
                    "perform" => ARGV[1]
506
                }
507
            }
508

  
509
            if options[:hard]
510
                action_hash["action"]["params"] = true
511
            end
512

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

  
515
            if CloudClient::is_error?(response)
516
                puts "ERROR: "
517
                puts response.message
518
                exit -1
519
            end
520
        else
521
            puts "You have to provide a VM ID"
522
            exit -1
523
        end
524
    else
525
        puts OneGate.help_str
526
        puts
527
        puts "Action #{ARGV[1]} not supported"
528
        exit -1
529
    end
530
when "service"
531
    case ARGV[1]
532
    when "show"
533
        response = client.get("/service")
534
        json_hash = OneGate.parse_json(response)
535
        #pp json_hash
536
        if options[:json]
537
            puts JSON.pretty_generate(json_hash)
538
        else
539
            OneGate::Service.print(json_hash)
540
        end
541
    when "scale"
542
        response = client.put(
543
            "/service/role/" + options[:role],
544
            {
545
                :cardinality => options[:cardinality]
546
            }.to_json)
547

  
548
        if CloudClient::is_error?(response)
549
            puts "ERROR: "
550
            puts response.message
551
            exit -1
552
        end
553
    else
554
        puts OneGate.help_str
555
        puts
556
        puts "Action #{ARGV[1]} not supported"
557
        exit -1
558
    end
559
else
560
    puts OneGate.help_str
561
    exit -1
562
end
563

  

Also available in: Unified diff