cloudstack-cli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module CloudstackCli
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,871 @@
1
+ # CloudstackClient by Nik Wolfgramm (<nik.wolfgramm@swisstxt.ch>) based on
2
+ # knife-cloudstack by Ryan Holmes (<rholmes@edmunds.com>), KC Braunschweig (<kcbraunschweig@gmail.com>)
3
+ #
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'rubygems'
20
+ require 'base64'
21
+ require 'openssl'
22
+ require 'uri'
23
+ require 'cgi'
24
+ require 'net/http'
25
+ require 'net/https'
26
+ require 'json'
27
+ require 'yaml'
28
+
29
+ module CloudstackClient
30
+ class Connection
31
+
32
+ @@async_poll_interval = 2.0
33
+ @@async_timeout = 300
34
+
35
+ def initialize(api_url, api_key, secret_key)
36
+ @api_url = api_url
37
+ @api_key = api_key
38
+ @secret_key = secret_key
39
+ @use_ssl = api_url.start_with? "https"
40
+ end
41
+
42
+ ##
43
+ # Finds the server with the specified name.
44
+
45
+ def get_server(name, project_id=nil)
46
+ params = {
47
+ 'command' => 'listVirtualMachines',
48
+ 'name' => name
49
+ }
50
+ params['projectid'] = project_id if project_id
51
+ json = send_request(params)
52
+ machines = json['virtualmachine']
53
+
54
+ if !machines || machines.empty? then
55
+ return nil
56
+ end
57
+
58
+ machines.select {|m| m['name'] == name }.first
59
+ end
60
+
61
+ def get_server_state(id)
62
+ params = {
63
+ 'command' => 'listVirtualMachines',
64
+ 'id' => id
65
+ }
66
+ json = send_request(params)
67
+ machine_state = json['virtualmachine'][0]['state']
68
+
69
+ if !machine_state || machine_state.empty? then
70
+ return nil
71
+ end
72
+
73
+ machine_state
74
+ end
75
+
76
+ def wait_for_server_state(id, state)
77
+ while get_server_state(id) != state
78
+ print '..'
79
+ sleep 5
80
+ end
81
+ state
82
+ end
83
+
84
+ ##
85
+ # Finds the public ip for a server
86
+
87
+ def get_server_public_ip(server, cached_rules=nil)
88
+ return nil unless server
89
+
90
+ # find the public ip
91
+ nic = get_server_default_nic(server) || {}
92
+ if nic['type'] == 'Virtual' then
93
+ ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
94
+ ssh_rule ? ssh_rule['ipaddress'] : nil
95
+ else
96
+ nic['ipaddress']
97
+ end
98
+ end
99
+
100
+ ##
101
+ # Returns the fully qualified domain name for a server.
102
+
103
+ def get_server_fqdn(server)
104
+ return nil unless server
105
+
106
+ nic = get_server_default_nic(server) || {}
107
+ networks = list_networks(server['projectid']) || {}
108
+
109
+ id = nic['networkid']
110
+ network = networks.select { |net|
111
+ net['id'] == id
112
+ }.first
113
+ return nil unless network
114
+
115
+ "#{server['name']}.#{network['networkdomain']}"
116
+ end
117
+
118
+ def get_server_default_nic(server)
119
+ server['nic'].each do |nic|
120
+ return nic if nic['isdefault']
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Lists all the servers in your account.
126
+
127
+ def list_servers(options = {})
128
+ params = {
129
+ 'command' => 'listVirtualMachines',
130
+ }
131
+ if options[:listall]
132
+ params['listAll'] = true
133
+ params['projectId'] = -1
134
+ options[:account] = nil
135
+ end
136
+ params['projectid'] = options[:project_id] if options[:project_id]
137
+ if options[:account]
138
+ params['domainid'] = list_accounts({name: options[:account]}).first["domainid"]
139
+ params['account'] = options[:account]
140
+ end
141
+
142
+ json = send_request(params)
143
+ json['virtualmachine'] || []
144
+ end
145
+
146
+ ##
147
+ # Deploys a new server using the specified parameters.
148
+
149
+ def create_server(host_name, service_name, template_name, zone_name=nil, network_names=[], project_name=nil)
150
+ if host_name then
151
+ if get_server(host_name) then
152
+ puts "Error: Server '#{host_name}' already exists."
153
+ exit 1
154
+ end
155
+ end
156
+
157
+ service = get_service_offering(service_name)
158
+ if !service then
159
+ puts "Error: Service offering '#{service_name}' is invalid"
160
+ exit 1
161
+ end
162
+
163
+ template = get_template(template_name)
164
+ if !template then
165
+ puts "Error: Template '#{template_name}' is invalid"
166
+ exit 1
167
+ end
168
+
169
+ zone = zone_name ? get_zone(zone_name) : get_default_zone
170
+ if !zone then
171
+ msg = zone_name ? "Zone '#{zone_name}' is invalid" : "No default zone found"
172
+ puts "Error: #{msg}"
173
+ exit 1
174
+ end
175
+
176
+ if project_name
177
+ project = get_project(project_name)
178
+ if !project then
179
+ msg = "Project '#{project_name}' is invalid"
180
+ puts "Error: #{msg}"
181
+ exit 1
182
+ end
183
+ end
184
+
185
+ networks = []
186
+ network_names.each do |name|
187
+ network = project_name ? get_network(name, project['id']) : get_network(name)
188
+ if !network then
189
+ puts "Error: Network '#{name}' not found"
190
+ exit 1
191
+ end
192
+ networks << network
193
+ end
194
+ if networks.empty? then
195
+ networks << get_default_network
196
+ end
197
+ if networks.empty? then
198
+ puts "No default network found"
199
+ exit 1
200
+ end
201
+ network_ids = networks.map { |network|
202
+ network['id']
203
+ }
204
+
205
+ params = {
206
+ 'command' => 'deployVirtualMachine',
207
+ 'serviceOfferingId' => service['id'],
208
+ 'templateId' => template['id'],
209
+ 'zoneId' => zone['id'],
210
+ 'networkids' => network_ids.join(',')
211
+ }
212
+ params['name'] = host_name if host_name
213
+ params['projectid'] = project['id'] if project_name
214
+
215
+ json = send_async_request(params)
216
+ json['virtualmachine']
217
+ end
218
+
219
+ ##
220
+ # Stops the server with the specified name.
221
+ #
222
+
223
+ def stop_server(name, forced=nil)
224
+ server = get_server(name)
225
+ if !server || !server['id'] then
226
+ puts "Error: Virtual machine '#{name}' does not exist"
227
+ exit 1
228
+ end
229
+
230
+ params = {
231
+ 'command' => 'stopVirtualMachine',
232
+ 'id' => server['id']
233
+ }
234
+ params['forced'] = true if forced
235
+
236
+ json = send_async_request(params)
237
+ json['virtualmachine']
238
+ end
239
+
240
+ ##
241
+ # Start the server with the specified name.
242
+ #
243
+
244
+ def start_server(name)
245
+ server = get_server(name)
246
+ if !server || !server['id'] then
247
+ puts "Error: Virtual machine '#{name}' does not exist"
248
+ exit 1
249
+ end
250
+
251
+ params = {
252
+ 'command' => 'startVirtualMachine',
253
+ 'id' => server['id']
254
+ }
255
+
256
+ json = send_async_request(params)
257
+ json['virtualmachine']
258
+ end
259
+
260
+ ##
261
+ # Reboot the server with the specified name.
262
+ #
263
+
264
+ def reboot_server(name)
265
+ server = get_server(name)
266
+ if !server || !server['id'] then
267
+ puts "Error: Virtual machine '#{name}' does not exist"
268
+ exit 1
269
+ end
270
+
271
+ params = {
272
+ 'command' => 'rebootVirtualMachine',
273
+ 'id' => server['id']
274
+ }
275
+
276
+ json = send_async_request(params)
277
+ json['virtualmachine']
278
+ end
279
+
280
+ ##
281
+ # Finds the service offering with the specified name.
282
+
283
+ def get_service_offering(name)
284
+
285
+ # TODO: use name parameter
286
+ # listServiceOfferings in CloudStack 2.2 doesn't seem to work
287
+ # when the name parameter is specified. When this is fixed,
288
+ # the name parameter should be added to the request.
289
+ params = {
290
+ 'command' => 'listServiceOfferings'
291
+ }
292
+ json = send_request(params)
293
+
294
+ services = json['serviceoffering']
295
+ return nil unless services
296
+
297
+ services.each { |s|
298
+ if s['name'] == name then
299
+ return s
300
+ end
301
+ }
302
+
303
+ nil
304
+ end
305
+
306
+ ##
307
+ # Lists all available service offerings.
308
+
309
+ def list_service_offerings(domain = nil)
310
+ params = {
311
+ 'command' => 'listServiceOfferings'
312
+ }
313
+
314
+ if domain
315
+ params['domainid'] = list_domains(domain).first["id"]
316
+ end
317
+
318
+ json = send_request(params)
319
+ json['serviceoffering'] || []
320
+ end
321
+
322
+ ##
323
+ # Create a service offering.
324
+
325
+ def create_offering(args)
326
+ params = {
327
+ 'command' => 'createServiceOffering',
328
+ 'name' => args[:name],
329
+ 'cpunumber' => args[:cpunumber],
330
+ 'cpuspeed' => args[:cpuspeed],
331
+ 'displaytext' => args[:displaytext],
332
+ 'memory' => args[:memory]
333
+ }
334
+
335
+ if args['domain']
336
+ params['domainid'] = list_domains(args['domain']).first["id"]
337
+ end
338
+
339
+ params['tags'] = args[:tags] if args[:tags]
340
+ params['offerha'] = 'true' if args[:ha]
341
+
342
+ json = send_request(params)
343
+ json['serviceoffering'].first
344
+ end
345
+
346
+ ##
347
+ # Delete a service offering.
348
+
349
+ def delete_offering(id)
350
+ params = {
351
+ 'command' => 'deleteServiceOffering',
352
+ 'id' => id
353
+ }
354
+
355
+ json = send_request(params)
356
+ json['success']
357
+ end
358
+
359
+ def update_offering(args)
360
+ params = {
361
+ 'command' => 'updateServiceOffering',
362
+ 'id' => args['id']
363
+ }
364
+ params['name'] = args['name'] if args['name']
365
+ params['displaytext'] = args['displaytext'] if args['displaytext']
366
+ params['sortkey'] = args['sortkey'] if args['sortkey']
367
+
368
+ json = send_request(params)
369
+ json['serviceoffering']
370
+ end
371
+
372
+ ##
373
+ # Finds the template with the specified name.
374
+
375
+ def get_template(name)
376
+
377
+ # TODO: use name parameter
378
+ # listTemplates in CloudStack 2.2 doesn't seem to work
379
+ # when the name parameter is specified. When this is fixed,
380
+ # the name parameter should be added to the request.
381
+ params = {
382
+ 'command' => 'listTemplates',
383
+ 'templateFilter' => 'executable'
384
+ }
385
+ json = send_request(params)
386
+
387
+ templates = json['template']
388
+ if !templates then
389
+ return nil
390
+ end
391
+
392
+ templates.each { |t|
393
+ if t['name'] == name then
394
+ return t
395
+ end
396
+ }
397
+
398
+ nil
399
+ end
400
+
401
+ ##
402
+ # Lists all templates that match the specified filter.
403
+ #
404
+ # Allowable filter values are:
405
+ #
406
+ # * featured - templates that are featured and are public
407
+ # * self - templates that have been registered/created by the owner
408
+ # * self-executable - templates that have been registered/created by the owner that can be used to deploy a new VM
409
+ # * executable - all templates that can be used to deploy a new VM
410
+ # * community - templates that are public
411
+
412
+ def list_templates(filter, project_id = nil)
413
+ filter ||= 'featured'
414
+ params = {
415
+ 'command' => 'listTemplates',
416
+ 'templateFilter' => filter
417
+ }
418
+ params['projectid'] = project_id if project_id
419
+
420
+ json = send_request(params)
421
+ json['template'] || []
422
+ end
423
+
424
+ ##
425
+ # Finds the network with the specified name.
426
+
427
+ def get_network(name, project_id = nil)
428
+ params = {
429
+ 'command' => 'listNetworks',
430
+ 'listall' => true
431
+ }
432
+ params['projectid'] = project_id if project_id
433
+ json = send_request(params)
434
+
435
+ networks = json['network']
436
+ return nil unless networks
437
+
438
+ networks.each { |n|
439
+ if n['name'] == name then
440
+ return n
441
+ end
442
+ }
443
+
444
+ nil
445
+ end
446
+
447
+ ##
448
+ # Finds the default network.
449
+
450
+ def get_default_network
451
+ params = {
452
+ 'command' => 'listNetworks',
453
+ 'isDefault' => true
454
+ }
455
+ json = send_request(params)
456
+
457
+ networks = json['network']
458
+ return nil if !networks || networks.empty?
459
+
460
+ default = networks.first
461
+ return default if networks.length == 1
462
+
463
+ networks.each { |n|
464
+ if n['type'] == 'Direct' then
465
+ default = n
466
+ break
467
+ end
468
+ }
469
+
470
+ default
471
+ end
472
+
473
+ ##
474
+ # Lists all available networks.
475
+
476
+ def list_networks(project_id = nil)
477
+ params = {
478
+ 'command' => 'listNetworks',
479
+ 'listall' => true,
480
+ }
481
+ params['projectid'] = project_id if project_id
482
+ json = send_request(params)
483
+ json['network'] || []
484
+ end
485
+
486
+ ##
487
+ # Lists all physical networks.
488
+
489
+ def list_physical_networks
490
+ params = {
491
+ 'command' => 'listPhysicalNetworks',
492
+ }
493
+ json = send_request(params)
494
+ json['physicalnetwork'] || []
495
+ end
496
+
497
+ ##
498
+ # Lists all volumes.
499
+
500
+ def list_volumes(project_id = nil)
501
+ params = {
502
+ 'command' => 'listVolumes',
503
+ 'listall' => true,
504
+ }
505
+ params['projectid'] = project_id if project_id
506
+ json = send_request(params)
507
+ json['network'] || []
508
+ end
509
+
510
+ ##
511
+ # Finds the zone with the specified name.
512
+
513
+ def get_zone(name)
514
+ params = {
515
+ 'command' => 'listZones',
516
+ 'available' => 'true'
517
+ }
518
+ json = send_request(params)
519
+
520
+ networks = json['zone']
521
+ return nil unless networks
522
+
523
+ networks.each { |z|
524
+ if z['name'] == name then
525
+ return z
526
+ end
527
+ }
528
+
529
+ nil
530
+ end
531
+
532
+ ##
533
+ # Finds the default zone for your account.
534
+
535
+ def get_default_zone
536
+ params = {
537
+ 'command' => 'listZones',
538
+ 'available' => 'true'
539
+ }
540
+ json = send_request(params)
541
+
542
+ zones = json['zone']
543
+ return nil unless zones
544
+
545
+ zones.first
546
+ end
547
+
548
+ ##
549
+ # Lists all available zones.
550
+
551
+ def list_zones
552
+ params = {
553
+ 'command' => 'listZones',
554
+ 'available' => 'true'
555
+ }
556
+ json = send_request(params)
557
+ json['zone'] || []
558
+ end
559
+
560
+ ##
561
+ # Finds the public ip address for a given ip address string.
562
+
563
+ def get_public_ip_address(ip_address)
564
+ params = {
565
+ 'command' => 'listPublicIpAddresses',
566
+ 'ipaddress' => ip_address
567
+ }
568
+ json = send_request(params)
569
+ ip_address = json['publicipaddress']
570
+
571
+ return nil unless ip_address
572
+ ip_address.first
573
+ end
574
+
575
+
576
+ ##
577
+ # Acquires and associates a public IP to an account.
578
+
579
+ def associate_ip_address(network_id)
580
+ params = {
581
+ 'command' => 'associateIpAddress',
582
+ 'networkid' => network_id
583
+ }
584
+
585
+ json = send_async_request(params)
586
+ json['ipaddress']
587
+ end
588
+
589
+ ##
590
+ # Disassociates an ip address from the account.
591
+ #
592
+ # Returns true if successful, false otherwise.
593
+
594
+ def disassociate_ip_address(id)
595
+ params = {
596
+ 'command' => 'disassociateIpAddress',
597
+ 'id' => id
598
+ }
599
+ json = send_async_request(params)
600
+ json['success']
601
+ end
602
+
603
+ ##
604
+ # Lists all port forwarding rules.
605
+
606
+ def list_port_forwarding_rules(ip_address_id=nil)
607
+ params = {
608
+ 'command' => 'listPortForwardingRules'
609
+ }
610
+ params['ipAddressId'] = ip_address_id if ip_address_id
611
+ json = send_request(params)
612
+ json['portforwardingrule']
613
+ end
614
+
615
+ ##
616
+ # Gets the SSH port forwarding rule for the specified server.
617
+
618
+ def get_ssh_port_forwarding_rule(server, cached_rules=nil)
619
+ rules = cached_rules || list_port_forwarding_rules || []
620
+ rules.find_all { |r|
621
+ r['virtualmachineid'] == server['id'] &&
622
+ r['privateport'] == '22'&&
623
+ r['publicport'] == '22'
624
+ }.first
625
+ end
626
+
627
+ ##
628
+ # Creates a port forwarding rule.
629
+
630
+ def create_port_forwarding_rule(ip_address_id, private_port, protocol, public_port, virtual_machine_id)
631
+ params = {
632
+ 'command' => 'createPortForwardingRule',
633
+ 'ipAddressId' => ip_address_id,
634
+ 'privatePort' => private_port,
635
+ 'protocol' => protocol,
636
+ 'publicPort' => public_port,
637
+ 'virtualMachineId' => virtual_machine_id
638
+ }
639
+ json = send_async_request(params)
640
+ json['portforwardingrule']
641
+ end
642
+
643
+ ##
644
+ # Get project by name.
645
+
646
+ def get_project(name)
647
+ params = {
648
+ 'command' => 'listProjects',
649
+ 'name' => name,
650
+ 'listall' => true,
651
+ }
652
+ json = send_request(params)
653
+ json['project'] ? json['project'].first : nil
654
+ end
655
+
656
+ ##
657
+ # Lists projects.
658
+
659
+ def list_projects
660
+ params = {
661
+ 'command' => 'listProjects',
662
+ 'listall' => true,
663
+ }
664
+ json = send_request(params)
665
+ json['project'] || []
666
+ end
667
+
668
+ ##
669
+ # List loadbalancer rules
670
+
671
+ def list_load_balancer_rules(name = nil, project_name = nil)
672
+ params = {
673
+ 'command' => 'listLoadBalancerRules',
674
+ }
675
+ params['name'] = name if name
676
+
677
+ if project_name
678
+ project = get_project(project_name)
679
+ params['projectid'] = project['id']
680
+ end
681
+
682
+ json = send_request(params)
683
+ json['loadbalancerrule'] || []
684
+ end
685
+
686
+ ##
687
+ # Creates a load balancing rule.
688
+
689
+ def create_load_balancer_rule(name, publicip, private_port, public_port, options = {})
690
+ params = {
691
+ 'command' => 'createLoadBalancerRule',
692
+ 'name' => name,
693
+ 'privateport' => private_port,
694
+ 'publicport' => public_port,
695
+ 'publicipid' => get_public_ip_address(publicip)['id']
696
+ }
697
+ params['algorithm'] = options[:algorithm] || 'roundrobin'
698
+ params['openfirewall'] = options[:openfirewall] || true
699
+
700
+ json = send_async_request(params)
701
+ json['LoadBalancerRule']
702
+ end
703
+
704
+ ##
705
+ # Assigns virtual machine or a list of virtual machines to a load balancer rule.
706
+
707
+ def assign_to_load_balancer_rule(name, vm_names)
708
+ id = list_load_balancer_rules(name).first['id']
709
+
710
+ vm_ids = vm_names.map do |vm|
711
+ get_server(vm)['id']
712
+ end
713
+
714
+ params = {
715
+ 'command' => 'assignToLoadBalancerRule',
716
+ 'id' => id,
717
+ 'virtualmachineids' => vm_ids.join(',')
718
+ }
719
+ json = send_async_request(params)
720
+ end
721
+
722
+ ##
723
+ # Lists all virtual routers.
724
+
725
+ def list_routers(args = {:account => nil, :zone => nil, :projectid => nil, :status => nil, :name => nil})
726
+ params = {
727
+ 'command' => 'listRouters',
728
+ 'listall' => 'true',
729
+ 'isrecursive' => 'true'
730
+ }
731
+ params['zone'] = args[:zone] if args[:zone]
732
+ params['projectid'] = args[:projectid] if args[:projectid]
733
+ params['state'] = args[:status] if args[:status]
734
+ params['name'] = args[:name] if args[:name]
735
+ if args[:account]
736
+ params['domainid'] = list_accounts({name: args[:account]}).first["domainid"]
737
+ params['account'] = args[:account]
738
+ end
739
+
740
+ json = send_request(params)
741
+ json['router'] || []
742
+ end
743
+
744
+ ##
745
+ # Destroy virtual router.
746
+
747
+ def destroy_router(id)
748
+ params = {
749
+ 'command' => 'destroyRouter',
750
+ 'id' => id
751
+ }
752
+
753
+ json = send_request(params)
754
+ json['router'].first
755
+ end
756
+
757
+ ##
758
+ # Lists accounts.
759
+
760
+ def list_accounts(args = { :name => nil })
761
+ params = {
762
+ 'command' => 'listAccounts',
763
+ 'listall' => 'true',
764
+ 'isrecursive' => 'true'
765
+ }
766
+ params['name'] = args[:name] if args[:name]
767
+
768
+ json = send_request(params)
769
+ json['account'] || []
770
+ end
771
+
772
+ ##
773
+ # List domains.
774
+
775
+ def list_domains(name = nil)
776
+ params = {
777
+ 'command' => 'listDomains',
778
+ 'listall' => 'true',
779
+ 'isrecursive' => 'true'
780
+ }
781
+ params['name'] = name if name
782
+
783
+ json = send_request(params)
784
+ json['domain'] || []
785
+ end
786
+
787
+ ##
788
+ # Sends a synchronous request to the CloudStack API and returns the response as a Hash.
789
+ #
790
+ # The wrapper element of the response (e.g. mycommandresponse) is discarded and the
791
+ # contents of that element are returned.
792
+
793
+ def send_request(params)
794
+ params['response'] = 'json'
795
+ params['apiKey'] = @api_key
796
+
797
+ params_arr = []
798
+ params.sort.each { |elem|
799
+ params_arr << elem[0].to_s + '=' + CGI.escape(elem[1].to_s).gsub('+', '%20').gsub(' ','%20')
800
+ }
801
+ data = params_arr.join('&')
802
+ signature = OpenSSL::HMAC.digest('sha1', @secret_key, data.downcase)
803
+ signature = Base64.encode64(signature).chomp
804
+ signature = CGI.escape(signature)
805
+
806
+ url = "#{@api_url}?#{data}&signature=#{signature}"
807
+
808
+ uri = URI.parse(url)
809
+ http = Net::HTTP.new(uri.host, uri.port)
810
+ http.use_ssl = @use_ssl
811
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
812
+
813
+ begin
814
+ response = http.request(Net::HTTP::Get.new(uri.request_uri))
815
+ rescue
816
+ puts "Error connecting to API:"
817
+ puts "#{@api_url} is not reachable"
818
+ exit 1
819
+ end
820
+
821
+ if !response.is_a?(Net::HTTPOK) then
822
+ puts "Error #{response.code}: #{response.message}"
823
+ puts JSON.pretty_generate(JSON.parse(response.body))
824
+ puts "URL: #{url}"
825
+ exit 1
826
+ end
827
+
828
+ json = JSON.parse(response.body)
829
+ json[params['command'].downcase + 'response']
830
+ end
831
+
832
+ ##
833
+ # Sends an asynchronous request and waits for the response.
834
+ #
835
+ # The contents of the 'jobresult' element are returned upon completion of the command.
836
+
837
+ def send_async_request(params)
838
+
839
+ json = send_request(params)
840
+
841
+ params = {
842
+ 'command' => 'queryAsyncJobResult',
843
+ 'jobId' => json['jobid']
844
+ }
845
+
846
+ max_tries = (@@async_timeout / @@async_poll_interval).round
847
+ max_tries.times do
848
+ json = send_request(params)
849
+ status = json['jobstatus']
850
+
851
+ print "."
852
+
853
+ if status == 1 then
854
+ return json['jobresult']
855
+ elsif status == 2 then
856
+ print "\n"
857
+ puts "Request failed (#{json['jobresultcode']}): #{json['jobresult']}"
858
+ exit 1
859
+ end
860
+
861
+ STDOUT.flush
862
+ sleep @@async_poll_interval
863
+ end
864
+
865
+ print "\n"
866
+ puts "Error: Asynchronous request timed out"
867
+ exit 1
868
+ end
869
+
870
+ end # class
871
+ end