cloudstack-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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