ucloudstack 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,633 @@
1
+ #
2
+ # Author:: Ryan Holmes (<rholmes@edmunds.com>)
3
+ # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
4
+ # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'rubygems'
21
+ require 'base64'
22
+ require 'openssl'
23
+ require 'uri'
24
+ require 'cgi'
25
+ require 'net/http'
26
+ require 'net/https'
27
+ require 'json'
28
+
29
+ module CloudstackClient
30
+ class Connection
31
+
32
+ ASYNC_POLL_INTERVAL = 5.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
+ end
40
+
41
+ ##
42
+ # Finds the server with the specified name.
43
+
44
+ def get_server(name)
45
+ params = {
46
+ 'command' => 'listVirtualMachines',
47
+ 'name' => name
48
+ }
49
+ json = send_request(params)
50
+ machines = json['virtualmachine']
51
+
52
+ if !machines || machines.empty? then
53
+ return nil
54
+ end
55
+
56
+ machines.first
57
+ end
58
+
59
+ ##
60
+ # Finds the public ip for a server
61
+
62
+ def get_server_public_ip(server, cached_rules=nil)
63
+ return nil unless server
64
+
65
+ # find the public ip
66
+ nic = get_server_default_nic(server) || {}
67
+ if nic['type'] == 'Virtual' then
68
+ ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
69
+ ssh_rule ? ssh_rule['ipaddress'] : nil
70
+ else
71
+ nic['ipaddress']
72
+ end
73
+ end
74
+
75
+ ##
76
+ # Returns the fully qualified domain name for a server.
77
+
78
+ def get_server_fqdn(server)
79
+ return nil unless server
80
+
81
+ nic = get_server_default_nic(server) || {}
82
+ networks = list_networks || {}
83
+
84
+ id = nic['networkid']
85
+ network = networks.select { |net|
86
+ net['id'] == id
87
+ }.first
88
+ return nil unless network
89
+
90
+ "#{server['name']}.#{network['networkdomain']}"
91
+ end
92
+
93
+ def get_server_default_nic(server)
94
+ server['nic'].each do |nic|
95
+ return nic if nic['isdefault']
96
+ end
97
+ end
98
+
99
+ ##
100
+ # Lists all the servers in your account.
101
+
102
+ def list_servers
103
+ params = {
104
+ 'command' => 'listVirtualMachines'
105
+ }
106
+ json = send_request(params)
107
+ json['virtualmachine'] || []
108
+ end
109
+
110
+ ##
111
+ # Deploys a new server using the specified parameters.
112
+
113
+ def create_server(host_name, service_name, template_name, zone_name=nil, network_names=[])
114
+
115
+ if host_name then
116
+ if get_server(host_name) then
117
+ puts "Error: Server '#{host_name}' already exists."
118
+ exit 1
119
+ end
120
+ end
121
+
122
+ #service = get_service_offering(service_name)
123
+ #if !service then
124
+ # puts "Error: Service offering '#{service_name}' is invalid"
125
+ # exit 1
126
+ #end
127
+
128
+ #template = get_template(template_name)
129
+ #if !template then
130
+ # puts "Error: Template '#{template_name}' is invalid"
131
+ # exit 1
132
+ #end
133
+
134
+ #zone = zone_name ? get_zone(zone_name) : get_default_zone
135
+ #if !zone then
136
+ # msg = zone_name ? "Zone '#{zone_name}' is invalid" : "No default zone found"
137
+ # puts "Error: #{msg}"
138
+ # exit 1
139
+ #end
140
+
141
+ #networks = []
142
+ #network_names.each do |name|
143
+ # network = get_network(name)
144
+ # if !network then
145
+ # puts "Error: Network '#{name}' not found"
146
+ # exit 1
147
+ # end
148
+ # networks << get_network(name)
149
+ #end
150
+ #if networks.empty? then
151
+ # networks << get_default_network
152
+ #end
153
+ #if networks.empty? then
154
+ # puts "No default network found"
155
+ # exit 1
156
+ #end
157
+ #network_ids = networks.map { |network|
158
+ # network['id']
159
+ #}
160
+
161
+ params = {
162
+ 'command' => 'deployVirtualMachine',
163
+ 'serviceOfferingId' => service_name,
164
+ #'serviceOfferingId' => service['id'],
165
+ 'templateId' => template_name,
166
+ #'templateId' => template['id'],
167
+ 'zoneId' => zone_name
168
+ #'zoneId' => zone['id'],
169
+ #'networkids' => network_ids.join(',')
170
+ }
171
+ params['name'] = host_name if host_name
172
+
173
+ json = send_async_request(params)
174
+ json['virtualmachine']
175
+ end
176
+
177
+ ##
178
+ # Deletes the server with the specified name.
179
+ #
180
+
181
+ def delete_server(name)
182
+ server = get_server(name)
183
+ if !server || !server['id'] then
184
+ puts "Error: Virtual machine '#{name}' does not exist"
185
+ exit 1
186
+ end
187
+
188
+ params = {
189
+ 'command' => 'destroyVirtualMachine',
190
+ 'id' => server['id']
191
+ }
192
+
193
+ json = send_async_request(params)
194
+ json['virtualmachine']
195
+ end
196
+
197
+ ##
198
+ # Stops the server with the specified name.
199
+ #
200
+
201
+ def stop_server(name, forced=nil)
202
+ server = get_server(name)
203
+ if !server || !server['id'] then
204
+ puts "Error: Virtual machine '#{name}' does not exist"
205
+ exit 1
206
+ end
207
+
208
+ params = {
209
+ 'command' => 'stopVirtualMachine',
210
+ 'id' => server['id']
211
+ }
212
+ params['forced'] = true if forced
213
+
214
+ json = send_async_request(params)
215
+ json['virtualmachine']
216
+ end
217
+
218
+ ##
219
+ # Start the server with the specified name.
220
+ #
221
+
222
+ def start_server(name)
223
+ server = get_server(name)
224
+ if !server || !server['id'] then
225
+ puts "Error: Virtual machine '#{name}' does not exist"
226
+ exit 1
227
+ end
228
+
229
+ params = {
230
+ 'command' => 'startVirtualMachine',
231
+ 'id' => server['id']
232
+ }
233
+
234
+ json = send_async_request(params)
235
+ json['virtualmachine']
236
+ end
237
+
238
+ ##
239
+ # Reboot the server with the specified name.
240
+ #
241
+
242
+ def reboot_server(name)
243
+ server = get_server(name)
244
+ if !server || !server['id'] then
245
+ puts "Error: Virtual machine '#{name}' does not exist"
246
+ exit 1
247
+ end
248
+
249
+ params = {
250
+ 'command' => 'rebootVirtualMachine',
251
+ 'id' => server['id']
252
+ }
253
+
254
+ json = send_async_request(params)
255
+ json['virtualmachine']
256
+ end
257
+
258
+ ##
259
+ # Finds the service offering with the specified name.
260
+
261
+ def get_service_offering(name)
262
+
263
+ # TODO: use name parameter
264
+ # listServiceOfferings in CloudStack 2.2 doesn't seem to work
265
+ # when the name parameter is specified. When this is fixed,
266
+ # the name parameter should be added to the request.
267
+ params = {
268
+ 'command' => 'listServiceOfferings'
269
+ }
270
+ json = send_request(params)
271
+
272
+ services = json['serviceoffering']
273
+ return nil unless services
274
+
275
+ services.each { |s|
276
+ if s['name'] == name then
277
+ return s
278
+ end
279
+ }
280
+
281
+ nil
282
+ end
283
+
284
+ ##
285
+ # Lists all available service offerings.
286
+
287
+ def list_service_offerings
288
+ params = {
289
+ 'command' => 'listServiceOfferings'
290
+ }
291
+ json = send_request(params)
292
+ json['serviceoffering'] || []
293
+ end
294
+
295
+ ##
296
+ # Finds the template with the specified name.
297
+
298
+ def get_template(name)
299
+
300
+ # TODO: use name parameter
301
+ # listTemplates in CloudStack 2.2 doesn't seem to work
302
+ # when the name parameter is specified. When this is fixed,
303
+ # the name parameter should be added to the request.
304
+ params = {
305
+ 'command' => 'listTemplates',
306
+ 'templateFilter' => 'executable'
307
+ }
308
+ json = send_request(params)
309
+
310
+ templates = json['template']
311
+ if !templates then
312
+ return nil
313
+ end
314
+
315
+ templates.each { |t|
316
+ if t['name'] == name then
317
+ return t
318
+ end
319
+ }
320
+
321
+ nil
322
+ end
323
+
324
+ ##
325
+ # Lists all templates that match the specified filter.
326
+ #
327
+ # Allowable filter values are:
328
+ #
329
+ # * featured - templates that are featured and are public
330
+ # * self - templates that have been registered/created by the owner
331
+ # * self-executable - templates that have been registered/created by the owner that can be used to deploy a new VM
332
+ # * executable - all templates that can be used to deploy a new VM
333
+ # * community - templates that are public
334
+
335
+ def list_templates(filter)
336
+ filter ||= 'featured'
337
+ params = {
338
+ 'command' => 'listTemplates',
339
+ 'templatefilter' => filter
340
+ }
341
+ json = send_request(params)
342
+ json['template'] || []
343
+ end
344
+
345
+ ##
346
+ # Finds the network with the specified name.
347
+
348
+ def get_network(name)
349
+ params = {
350
+ 'command' => 'listNetworks'
351
+ }
352
+ json = send_request(params)
353
+
354
+ networks = json['network']
355
+ return nil unless networks
356
+
357
+ networks.each { |n|
358
+ if n['name'] == name then
359
+ return n
360
+ end
361
+ }
362
+
363
+ nil
364
+ end
365
+
366
+ ##
367
+ # Finds the default network.
368
+
369
+ def get_default_network
370
+ params = {
371
+ 'command' => 'listNetworks',
372
+ 'isDefault' => true
373
+ }
374
+ json = send_request(params)
375
+
376
+ networks = json['network']
377
+ return nil if !networks || networks.empty?
378
+
379
+ default = networks.first
380
+ return default if networks.length == 1
381
+
382
+ networks.each { |n|
383
+ if n['type'] == 'Direct' then
384
+ default = n
385
+ break
386
+ end
387
+ }
388
+
389
+ default
390
+ end
391
+
392
+ ##
393
+ # Lists all available networks.
394
+
395
+ def list_networks
396
+ params = {
397
+ 'command' => 'listNetworks'
398
+ }
399
+ json = send_request(params)
400
+ json['network'] || []
401
+ end
402
+
403
+ ##
404
+ # Finds the zone with the specified name.
405
+
406
+ def get_zone(name)
407
+ params = {
408
+ 'command' => 'listZones',
409
+ 'available' => 'true'
410
+ }
411
+ json = send_request(params)
412
+
413
+ networks = json['zone']
414
+ return nil unless networks
415
+
416
+ networks.each { |z|
417
+ if z['name'] == name then
418
+ return z
419
+ end
420
+ }
421
+
422
+ nil
423
+ end
424
+
425
+ ##
426
+ # Finds the default zone for your account.
427
+
428
+ def get_default_zone
429
+ params = {
430
+ 'command' => 'listZones',
431
+ 'available' => 'true'
432
+ }
433
+ json = send_request(params)
434
+
435
+ zones = json['zone']
436
+ return nil unless zones
437
+
438
+ zones.first
439
+ end
440
+
441
+ ##
442
+ # Lists all available zones.
443
+
444
+ def list_zones
445
+ params = {
446
+ 'command' => 'listZones',
447
+ 'available' => 'true'
448
+ }
449
+ json = send_request(params)
450
+ json['zone'] || []
451
+ end
452
+
453
+ ##
454
+ # Finds the public ip address for a given ip address string.
455
+
456
+ def get_public_ip_address(ip_address)
457
+ params = {
458
+ 'command' => 'listPublicIpAddresses',
459
+ 'ipaddress' => ip_address
460
+ }
461
+ json = send_request(params)
462
+ json['publicipaddress'].first
463
+ end
464
+
465
+
466
+ ##
467
+ # Acquires and associates a public IP to an account.
468
+
469
+ def associate_ip_address(zone_id)
470
+ params = {
471
+ 'command' => 'associateIpAddress',
472
+ 'zoneId' => zone_id
473
+ }
474
+
475
+ json = send_async_request(params)
476
+ json['ipaddress']
477
+ end
478
+
479
+ ##
480
+ # Disassociates an ip address from the account.
481
+ #
482
+ # Returns true if successful, false otherwise.
483
+
484
+ def disassociate_ip_address(id)
485
+ params = {
486
+ 'command' => 'disassociateIpAddress',
487
+ 'id' => id
488
+ }
489
+ json = send_async_request(params)
490
+ json['success']
491
+ end
492
+
493
+ ##
494
+ # Lists all port forwarding rules.
495
+
496
+ def list_port_forwarding_rules(ip_address_id=nil)
497
+ params = {
498
+ 'command' => 'listPortForwardingRules'
499
+ }
500
+ params['ipAddressId'] = ip_address_id if ip_address_id
501
+ json = send_request(params)
502
+ json['portforwardingrule']
503
+ end
504
+
505
+ ##
506
+ # Gets the SSH port forwarding rule for the specified server.
507
+
508
+ def get_ssh_port_forwarding_rule(server, cached_rules=nil)
509
+ rules = cached_rules || list_port_forwarding_rules || []
510
+ rules.find_all { |r|
511
+ r['virtualmachineid'] == server['id'] &&
512
+ r['privateport'] == '22'&&
513
+ r['publicport'] == '22'
514
+ }.first
515
+ end
516
+
517
+ ##
518
+ # Creates a port forwarding rule.
519
+
520
+ def create_port_forwarding_rule(ip_address_id, private_port, protocol, public_port, virtual_machine_id)
521
+ params = {
522
+ 'command' => 'createPortForwardingRule',
523
+ 'ipAddressId' => ip_address_id,
524
+ 'privatePort' => private_port,
525
+ 'protocol' => protocol,
526
+ 'publicPort' => public_port,
527
+ 'virtualMachineId' => virtual_machine_id
528
+ }
529
+ json = send_async_request(params)
530
+ json['portforwardingrule']
531
+ end
532
+
533
+ ##
534
+ # Lists all the available products list in your account.
535
+
536
+ def list_products
537
+ params = {
538
+ 'command' => 'listAvailableProductTypes'
539
+ }
540
+
541
+ json = send_request(params)
542
+
543
+ json["producttypes"] || []
544
+
545
+ #json['virtualmachine'] || []
546
+ end
547
+
548
+
549
+ ##
550
+ # Sends a synchronous request to the CloudStack API and returns the response as a Hash.
551
+ #
552
+ # The wrapper element of the response (e.g. mycommandresponse) is discarded and the
553
+ # contents of that element are returned.
554
+
555
+ def send_request(params)
556
+ params['response'] = 'json'
557
+ params['apiKey'] = @api_key
558
+
559
+ params_arr = []
560
+ params.sort.each { |elem|
561
+ params_arr << elem[0].to_s + '=' + elem[1].to_s
562
+ }
563
+ data = params_arr.join('&')
564
+ encoded_data = URI.encode(data.downcase).gsub('+', '%20').gsub(',', '%2c')
565
+ signature = OpenSSL::HMAC.digest('sha1', @secret_key, encoded_data)
566
+ signature = Base64.encode64(signature).chomp
567
+ signature = CGI.escape(signature)
568
+
569
+ raw_url = "#{@api_url}?#{data}&signature=#{signature}"
570
+
571
+ #puts "request url : " + raw_url
572
+ url = URI.parse(raw_url)
573
+
574
+ http = Net::HTTP.new(url.host, url.port)
575
+
576
+ http.use_ssl = (url.scheme == 'https')
577
+ request = Net::HTTP::Get.new(url.to_s)
578
+ response = http.request(request)
579
+
580
+ #response = Net::HTTP.get_response(URI.parse(url))
581
+
582
+ if !response.is_a?(Net::HTTPOK) then
583
+ puts "Error #{response.code}: #{response.message}"
584
+ puts JSON.pretty_generate(JSON.parse(response.body))
585
+ puts "URL: #{url}"
586
+ exit 1
587
+ end
588
+
589
+ json = JSON.parse(response.body)
590
+ json[params['command'].downcase + 'response']
591
+ end
592
+
593
+ ##
594
+ # Sends an asynchronous request and waits for the response.
595
+ #
596
+ # The contents of the 'jobresult' element are returned upon completion of the command.
597
+
598
+ def send_async_request(params)
599
+
600
+ json = send_request(params)
601
+ #puts json
602
+ params = {
603
+ 'command' => 'queryAsyncJobResult',
604
+ 'jobId' => json['jobid']
605
+ }
606
+
607
+ max_tries = (ASYNC_TIMEOUT / ASYNC_POLL_INTERVAL).round
608
+ max_tries.times do
609
+ json = send_request(params)
610
+ status = json['jobstatus']
611
+ #puts json
612
+ print "."
613
+
614
+ if status == 1 then
615
+ return json['jobresult']
616
+ elsif status == 2 then
617
+ print "\n"
618
+ puts "Request failed (#{json['jobresultcode']}): #{json['jobresult']}"
619
+ exit 1
620
+ end
621
+
622
+ STDOUT.flush
623
+ sleep ASYNC_POLL_INTERVAL
624
+ end
625
+
626
+ print "\n"
627
+ puts "Error: Asynchronous request timed out"
628
+ exit 1
629
+ end
630
+
631
+ end # class
632
+ end
633
+
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ucloudstack
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Holmes
9
+ - KC Braunschweig
10
+ - John E. Vincent
11
+ - SeongSik. Kim
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2012-08-20 00:00:00 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: chef
20
+ prerelease: false
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.0
27
+ type: :runtime
28
+ version_requirements: *id001
29
+ description: A Knife plugin to create, list and manage ucloud servers
30
+ email:
31
+ - rholmes@edmunds.com
32
+ - kcbraunschweig@gmail.com
33
+ - lusis.org+github.com@gmail.com
34
+ - kssminus@gmail.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.rdoc
41
+ - CHANGES.rdoc
42
+ - LICENSE
43
+ files:
44
+ - CHANGES.rdoc
45
+ - README.rdoc
46
+ - LICENSE
47
+ - lib/knife-cloudstack/connection.rb
48
+ - lib/chef/knife/cs_service_list.rb
49
+ - lib/chef/knife/cs_server_create.rb
50
+ - lib/chef/knife/cs_hosts.rb
51
+ - lib/chef/knife/cs_stack_create.rb
52
+ - lib/chef/knife/cs_product_list.rb
53
+ - lib/chef/knife/cs_network_list.rb
54
+ - lib/chef/knife/cs_template_list.rb
55
+ - lib/chef/knife/cs_zone_list.rb
56
+ - lib/chef/knife/cs_server_list.rb
57
+ - lib/chef/knife/cs_server_delete.rb
58
+ - lib/chef/knife/cs_server_reboot.rb
59
+ - lib/chef/knife/cs_server_start.rb
60
+ - lib/chef/knife/cs_server_stop.rb
61
+ - lib/chef/knife/cs_stack_delete.rb
62
+ homepage: http://cloudstack.org/
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.8.24
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: A knife plugin for the ucloud API
89
+ test_files: []
90
+