ganeti_client 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,8 +1,3 @@
1
- == License
2
-
3
- This Ruby Ganeti Client is release under AGPL licence (http://www.gnu.org/licenses/agpl-3.0.html)
4
-
5
-
6
1
  == Todo
7
2
 
8
3
  1. Manually test the following methods + fix when bugs found
@@ -17,7 +12,6 @@
17
12
  1.9. instance_activate_disks
18
13
  1.10. instance_create_tags
19
14
  1.11. instance_delete_tags
20
- 1.12. job_get
21
15
  1.13. job_delete
22
16
  1.14. node_evaluate
23
17
  1.15. node_migrate
@@ -61,6 +55,7 @@
61
55
  info.name
62
56
  => "hostname"
63
57
 
58
+
64
59
  == Contributing
65
60
 
66
61
  1. Fork the project
@@ -69,6 +64,18 @@
69
64
  4. Send a pull request
70
65
 
71
66
 
67
+ == Author
68
+
69
+ Michaël Rigart
70
+
71
+
72
+ == License
73
+
74
+ This Ruby Ganeti Client is release under AGPL licence (http://www.gnu.org/licenses/agpl-3.0.html)
75
+
76
+
77
+
78
+
72
79
 
73
80
 
74
81
 
@@ -1,23 +1,61 @@
1
+ # The program makes use of the Google Ganeti RAPI to access diffrent resources.
2
+ #
3
+ # The Client is mainly developed for usage with the Ganeti RAPI version 2
4
+ #
5
+ # The protocol used is JSON over HTTP designed afther the REST principle. HTTP Basic authentication as per RFC2617 is supported
6
+ #
7
+ # A few generic refered parameter types and the values they allow:
8
+ #
9
+ # bool:
10
+ # A boolean option will accept 1 or 0 as numbers but not i.e. True or False
11
+ #
12
+ # A few parameter mean the same thing accross all resources which implement it:
13
+ #
14
+ # bulk:
15
+ # Bulk-mode means that for the resources which usually return just a list of child resources (e.g. /2/instances which returns just instance names),
16
+ # the output will instead contain detailed data for all these subresources. This is more efficient than query-ing the sub-resources themselves.
17
+ #
18
+ # dry-run:
19
+ # The boolean dry-run argument, if provided and set, signals to Ganeti that the job should not be executed, only the pre-execution checks will be done.
20
+ # This is useful in trying to determine (without guarantees though, as in the meantime the cluster state could have changed) if the operation
21
+ # is likely to succeed or at least start executing.
22
+ #
23
+ # force:
24
+ # Force operation to continue even if it will cause the cluster to become inconsistent (e.g. because there are not enough master candidates).
25
+ #
26
+ # Author:: Michaël Rigart (mailto:michael@netronix.be)
27
+ # Copyright:: Copyright (c) 2010 Michaël Rigart
28
+ # License:: Distributes under AGPL Licence
29
+
1
30
  module GanetiClient
31
+
32
+ # This class contains all active resources available in Ganeti RAPI
2
33
  class Client
3
34
 
4
- attr_accessor :conn, :headers, :version, :show_response
35
+ attr_accessor :host, :username, :password, :version, :show_response
5
36
 
37
+ # Create the client object
38
+ #
39
+ # Parameters:
40
+ # host: hostname and port
41
+ # username: username that has access to RAPI
42
+ # password: password of the user provided
43
+ # show_response: show response data (optional)
6
44
  def initialize(host, username, password, show_response = false)
7
- self.show_output = show_output
8
- uri = URI.parse(host)
9
-
10
- self.conn = Net::HTTP.new(uri.host, uri.port)
11
- self.conn.use_ssl = (uri.scheme == "http")? false : true
12
-
13
- self.headers = authenticate(username, password)
45
+ self.host = host
46
+ self.username = username
47
+ self.password = password
48
+
49
+ self.show_response = show_response
14
50
 
15
- self.version = self.get_version
51
+ self.version = self.version_get
16
52
  end
17
53
 
18
54
 
19
- # Cluster information resource
20
- # Returns cluster information
55
+ # Get the cluster information
56
+ #
57
+ # Return:
58
+ # GanetiInfo object
21
59
  def info_get
22
60
  url = get_url("info")
23
61
  response_body = JSON.parse(send_request("GET", url))
@@ -28,7 +66,9 @@ module GanetiClient
28
66
  end
29
67
 
30
68
  # Redistrite configuration to all nodes
31
- # Redistribute configuration to all nodes. The result will be a job id.
69
+ #
70
+ # Return:
71
+ # job id.
32
72
  def redistribute_config
33
73
  url = get_url("redistribute-config")
34
74
  response_body = send_request("PUT", url)
@@ -36,9 +76,13 @@ module GanetiClient
36
76
  return response_body
37
77
  end
38
78
 
39
- # The instance resource
40
- # Returns a list of all available instances
41
- # If the optional bool bulk argument is provided and set to a true value (i.e ?bulk=1), the output contains detailed information about instances as a list.
79
+ # Get all instances on the cluster
80
+ #
81
+ # Parameters:
82
+ # bulk: 0|1 (optional)
83
+ #
84
+ # Return:
85
+ # Array of all available instances. The array items contain a GanetiInstance object
42
86
  def instances_get(bulk = 0)
43
87
  url = get_url("instances", {"bulk" => bulk})
44
88
  response_body = JSON.parse(send_request("GET", url))
@@ -51,23 +95,57 @@ module GanetiClient
51
95
  return list
52
96
  end
53
97
 
54
- # Creates an instance
98
+ # Create an instance
55
99
  # If the options bool dry-run argument is provided, the job will not be actually executed, only the pre-execution checks will be done.
56
100
  # Query-ing the job result will return, in boty dry-run and normal case, the list of nodes selected for the instance
57
- # Returns: a job ID that can be used later for polling
58
101
  #
59
- # Example of instance info:
60
- #
102
+ # Build parameters dict, optional parameters need to be
103
+ # excluded to not cause issues with rapi.
104
+ #
105
+ # Example:
106
+ # info = {
107
+ # 'hypervisor' => 'kvm' , 'disk_template' => 'plain',
108
+ # 'pnode' => 'node.netronix.be', 'name' => 'vm1.netronix.be', 'os' => 'debootstrap+lucid',
109
+ # 'vcpus' => '4', 'memory' => '4096', 'disks' => ['25600'],
110
+ # 'kernel-path' => '/boot/vmlinuz-2.6-kvmU'
111
+ # }
112
+ #
113
+ # Parameters:
114
+ # info: hash of data needed for the instance creation
115
+ # dry_run: 0|1 (optional)
116
+ #
117
+ # Return:
118
+ # job_id
61
119
  def instance_create(info, dry_run = 0)
120
+ params = {
121
+ 'hypervisor' => info['hypervisor'], 'disk_template' => info['disk_template'],
122
+ 'pnode' => info['pnode'], 'name' => info['iname'], 'os' => info['os'],
123
+ 'vcpus' => info['vcpus'], 'memory' => info['memory'], 'disks' => [info['size']]
124
+ }
125
+
126
+ # Add secondary node
127
+ params['snode'] = info['snode'] if info['disk_template'] == 'drbd' && info['snode']
128
+
129
+ # Add PVM parameters
130
+ if info['hypervisor']
131
+ params['kernel_path'] = info['kernel_path'] if info['kernel_path']
132
+ params['initrd_path'] = info['initrd_path'] if info['initrd_path']
133
+ end
134
+
62
135
  url = get_url("instances", {"dry-run" => dry_run})
63
- body = JSON.generate(info)
136
+ body = JSON.generate(params)
64
137
  response_body = send_request("POST", url, body)
65
138
 
66
139
  return response_body
67
140
  end
68
141
 
69
- # Instance-specific resource
70
- # Returns information about an instance, similar to the bulk output from the instance list
142
+ # Get instance specific information, similar to the bulk output from the instance list
143
+ #
144
+ # Parameters:
145
+ # name: name of the instance
146
+ #
147
+ # Return
148
+ # GanetiInstance object
71
149
  def instance_get(name)
72
150
  url = get_url("instances/#{name}")
73
151
  response_body = JSON.parse(send_request("GET", url))
@@ -77,19 +155,29 @@ module GanetiClient
77
155
  return GanetiInstance.new(response_body)
78
156
  end
79
157
 
80
- # Instance-specific resource
81
- # Deletes an instance
82
- # It supports the dry-run argument
83
- def instance_delete(name, dry_run)
158
+ # Delete a specific instance
159
+ #
160
+ # Parameters:
161
+ # name: name of the instance
162
+ # dry_run: 0|1 (optional)
163
+ #
164
+ # Return:
165
+ # ?
166
+ def instance_delete(name, dry_run = 0)
84
167
  url = get_url("instances/#{name}", {"dry-run" => dry_run})
85
168
  response_body = send_request("DELETE", url)
86
169
 
87
170
  return response_body
88
171
  end
89
172
 
90
- # Requests detailed information about an instance.
91
- # An optional parameter, static (bool), can be set to return only static information from the configuration without querying the instance's nodes
92
- # The result will be a job id
173
+ # Get detailed information about an instance. Static parameter can be set to return only static information from the configuration without querying the instance's nodes
174
+ #
175
+ # Parameters:
176
+ # name: name of the instance
177
+ # static: 0|1 (optional)
178
+ #
179
+ # Return:
180
+ # job id
93
181
  def instance_get_info(name, static = 0)
94
182
  url = get_url("instances/#{name}/info", {"static" => static})
95
183
  response_body = send_request("GET", url)
@@ -97,8 +185,7 @@ module GanetiClient
97
185
  return response_body
98
186
  end
99
187
 
100
- # Reboots URI for an instance
101
- # Reboots the instance
188
+ # Reboot a specific instance
102
189
  # The URI takes optional type=soft|hard|full and ignore_secondaries=0|1 parameters
103
190
  #
104
191
  # type defines the reboot type.
@@ -107,86 +194,130 @@ module GanetiClient
107
194
  # full is like hard but also recreates the configuration from ground up as if you would have don a gnt-instance shutdown and gnt-instance start on it
108
195
  #
109
196
  # it supports the dry-run argument
197
+ #
198
+ # Parameters:
199
+ # name: name of the instance
200
+ # type: soft|hard|full (optional)
201
+ # ignore_secondaries: 0|1 (optional)
202
+ # dry_run: 0|1 (optional)
203
+ #
204
+ # Return:
205
+ # ?
110
206
  def instance_reboot(name, type = "soft", ignore_secondaries = 0, dry_run = 0)
111
207
  url = get_url("instances/#{name}/reboot", {"type" => type, "ignore_secondaries" => ignore_secondaries, "dry_run" => 0})
112
- response_body = JSON.parse(send_request("POST", url))
113
-
114
- create_class("GanetiInstanceReboot")
208
+ response_body = send_request("POST", url)
115
209
 
116
- return GanetiIntanceReboot.new(response_body)
210
+ return response_body
117
211
  end
118
212
 
119
213
 
120
- # Instance shutdown URI
121
- # Shutdowns an instance
122
- # It supports the dry-run argument
214
+ # Shutdown an instance
215
+ #
216
+ # Parameters:
217
+ # name: name of the instance
218
+ # dry_run: 0|1 (optional)
219
+ #
220
+ # Return:
221
+ # ?
123
222
  def instance_shutdown(name, dry_run = 0)
124
223
  url = get_url("instances/#{name}/shutdown", {"dry-run" => dry_run})
125
- response_body = JSON.parse(send_request("PUT", url))
126
-
127
- create_class("GanetiInstanceShutdown")
224
+ response_body = send_request("PUT", url)
128
225
 
129
- return GanetiInstanceShutdown.new(response_body)
226
+ return response_body
130
227
  end
131
228
 
132
- # Instance startup URI
133
229
  # Startup an instance
134
230
  # The URI takes an optional force=1|0 parameter to start the instance even if secondary disks are failing
135
- # It supports the dry-run argument
231
+ #
232
+ # Parameters:
233
+ # name: name of the instance
234
+ # force: 0|1 (optional)
235
+ # dry_run: 0|1 (optional)
236
+ #
237
+ # Return:
238
+ # ?
136
239
  def instance_startup(name, force = 0, dry_run=0)
137
240
  url = get_url("instances/#{name}/startup", {"force" => force, "dry-run" => dry_run})
138
241
  body = "" # force parameter
139
242
  response_body = send_request("PUT", url, body)
140
243
 
141
- create_class("GanetiInstanceStartup")
142
-
143
- return GanetiInstanceStartup.new(response_body)
244
+ return response_body
144
245
  end
145
246
 
146
247
  # Install the operating system again
147
- # Takes the parameters os (OS template name) and nostartup (bool)
148
- def instance_reinstall(name, os_name, nostartup)
248
+ #
249
+ # Parameters:
250
+ # name: name of the instance
251
+ # os_name: name of the os
252
+ # nostartup: 0|1 (optional)
253
+ #
254
+ # Return:
255
+ # ?
256
+ def instance_reinstall(name, os_name, nostartup = 0)
149
257
  url = get_url("instances/#{name}/reinstall", {"os" => os_name, "nostartup" => nostartup})
150
258
  response_body = send_request("POST", url)
151
259
 
152
- create_class("GanetiInstanceReinstall")
153
-
154
- return GanetiInstanceReinstall.new(response_body)
260
+ return response_body
155
261
  end
156
262
 
157
263
  # Replaces disks on an instance
158
264
  # Takes the parameters mode (one of replace_on_primary, replace_on_secondary or replace_auto), disks (comma seperated list of disk indexes), remote_node and iallocator
159
265
  # Either remote_node or iallocator needs to be defined when using mode=replace_new_secondary
160
266
  # mode is a mandatory parameter. replace_auto tries to determine the broken disk(s) on its own and replacing it
267
+ #
268
+ # Parameters:
269
+ # name: name of the instance
270
+ # mode replace_on_primary|replace_on_secondary|replace_auto (optional)
271
+ # ialllocator:
272
+ # remote_node:
273
+ # disks: comma seperated list of disk indexes
274
+ #
275
+ # Return:
276
+ # ?
161
277
  def instance_replace_disks(name, mode = "replace_auto", iallocator = "", remote_node = "", disks = "")
162
278
  url = get_url("instances/#{name}/replace-disks", {"mode" => mode, "iallocator" => iallocator, "remote_node" => remote_node, "disks" => disks})
163
279
  response_body = send_request("POST", url)
164
280
 
165
- return "?"
281
+ return response_body
166
282
  end
167
283
 
168
284
  # Activate disks on an instance
169
285
  # Takes the bool parameter ignore_size. When set ignore the recorded size (useful for forcing activation when recoreded size is wrong)
286
+ #
287
+ # Parameters:
288
+ # name: name of the instance
289
+ # ignore_size: 0|1 (optional)
290
+ #
291
+ # Return:
292
+ # ?
170
293
  def intance_activate_disks(name, ignore_size = 0)
171
294
  url = get_url("instances/#{name}/activate-disks", {"ignore_size" => ignore_size})
172
295
  response_body = send_request("PUT", url)
173
296
 
174
- return "?"
297
+ return response_body
175
298
  end
176
299
 
177
300
  # Deactivate disks on an instance
178
- # Takes no parameters
301
+ #
302
+ # Parameters:
303
+ # name: name of the instance
304
+ #
305
+ # Return:
306
+ # ?
179
307
  def instance_deactivate_disks(name)
180
308
  url = get_url("instances/#{name}/deactivate-disks")
181
309
  response_body = send_request("PUT", url)
182
310
 
183
- return "?"
311
+ return response_body
184
312
  end
185
313
 
186
314
  # Returns a list of tags
187
315
  #
188
- # Example:
189
- # ["tag1", "tag2", "tag3"]
316
+ # Parameters:
317
+ # name: name of the instance
318
+ #
319
+ # Return:
320
+ # Array of tags
190
321
  def instance_get_tags(name)
191
322
  url = get_url("instances/#{name}/tags")
192
323
  response_body = JSON.parse(send_request("GET", url))
@@ -195,28 +326,41 @@ module GanetiClient
195
326
  end
196
327
 
197
328
  # Add a set of tags
198
- # The request as a list of strings shoud be PUT tot this URI. The result will be a job id
199
- # It supports the dry-run argument
200
- def instance_create_tags(name, dry_run = 0)
201
- url = get_url("instances/#{name}/tags", {"dry-run" => dry_run})
329
+ #
330
+ # Parameters:
331
+ # name: name of the instance
332
+ # tags: Array of tags
333
+ # dry_run: 0|1 (optional)
334
+ #
335
+ # Return:
336
+ # ?
337
+ def instance_create_tags(name, tags, dry_run = 0)
338
+ url = get_url("instances/#{name}/tags", {"tags" => tags, "dry-run" => dry_run})
202
339
  response_body = send_request("PUT", url)
203
340
 
204
341
  return response_body
205
342
  end
206
343
 
207
- # Delete a tag
208
- # In order to delete a set of tags, the DELETE request sould be addressed to URI LIKE:
209
- # /tags?tag=[tag]&tag=[tag]
210
- # It supports the dry-run argument
211
- def instance_delete_tags(name, dry_run = 0)
212
- url = get_url("instances/#{name}/tags", {"dry-run" => dry_run})
344
+ # Delete (a) tag(s) on an instance
345
+ #
346
+ # Parameters:
347
+ # name: name of the instance
348
+ # tags: Array of tags
349
+ # dry_run: 0|1 (optional)
350
+ #
351
+ # Return:
352
+ # ?
353
+ def instance_delete_tags(name, tags, dry_run = 0)
354
+ url = get_url("instances/#{name}/tags", {"tags" => tags, "dry-run" => dry_run})
213
355
  response_body = send_request("DELETE", url)
214
356
 
215
- return "?"
357
+ return response_body
216
358
  end
217
359
 
218
360
  # Returns a dictionary of jobs
219
- # Returns: a dictionary with jobs id and uri
361
+ #
362
+ # Return:
363
+ # Array of GanetiJob objects
220
364
  def jobs_get
221
365
  url = get_url("jobs")
222
366
  response_body = JSON.parse(send_request("GET", url))
@@ -287,11 +431,17 @@ module GanetiClient
287
431
  end
288
432
 
289
433
  # Cancel a not-yet-started job
434
+ #
435
+ # Parameters:
436
+ # job_id: id of a job
437
+ #
438
+ # Return:
439
+ # ?
290
440
  def job_delete(job_id)
291
441
  url = get_url("jobs/#{job_id}")
292
442
  response = send_request("DELETE", url)
293
443
 
294
- return "?"
444
+ return response_body
295
445
  end
296
446
 
297
447
  # Nodes resource
@@ -331,7 +481,7 @@ module GanetiClient
331
481
  url = get_url("nodes/#{name}/evacuate", {"iallocator" => iallocator, "remote_node" => remote_node})
332
482
  response_body = send_request("POST", url)
333
483
 
334
- return "?"
484
+ return response_body
335
485
  end
336
486
 
337
487
  # Migrates all primary instances of a node
@@ -343,7 +493,7 @@ module GanetiClient
343
493
  url = get_url("nodes/#{name}/migrate", {"live" => live})
344
494
  response_body = send_request("POST", url)
345
495
 
346
- return "?"
496
+ return response_body
347
497
  end
348
498
 
349
499
  # Get the node role
@@ -515,9 +665,9 @@ module GanetiClient
515
665
  private
516
666
 
517
667
  def authenticate(username, password)
518
- basic = Base64.b64encode("#{username}:#{password}")
668
+ basic = Base64.encode64("#{username}:#{password}").strip
519
669
  headers = {'Authorization' => "Basic #{basic}"}
520
-
670
+
521
671
  return headers
522
672
  end
523
673
 
@@ -528,20 +678,33 @@ module GanetiClient
528
678
  params.each do |key, value|
529
679
  if value.kind_of?(Array)
530
680
  value.each do |svalue|
531
- param_string += "#{key}=#{svalue}"
681
+ param_string += "#{key}=#{svalue}&"
532
682
  end
533
683
  else
534
684
  param_string += "#{key}=#{value}&"
535
685
  end
536
686
  end
537
687
  end
538
- return (self.version)? "/#{self.version}/#{path}?#{param_string}" : "/#{path}?#{param_string}"
688
+
689
+ url = (self.version)? "/#{self.version}/#{path}?#{param_string}" : "/#{path}?#{param_string}"
690
+
691
+ return url.chop
539
692
  end
540
693
 
541
694
  def send_request(method, url, body = nil)
542
- response = self.conn.send_request(method, url, body)
695
+ uri = URI.parse(host)
696
+
697
+ http = Net::HTTP.new(uri.host, uri.port)
698
+ http.use_ssl = (uri.scheme == "http")? false : true
699
+
700
+ headers = {}
701
+ headers = authenticate(self.username, self.password) if method != 'GET'
702
+
703
+ response = http.send_request(method, url, body, headers)
704
+
705
+
706
+ puts "Response #{response.code} #{response.message}: #{response.body}" if self.show_response
543
707
 
544
- puts "Response #{response.code} #{response.message}: #{response.body}" if self.show_reponse
545
708
  return response.body.strip
546
709
  end
547
710
 
data/lib/ganeti_client.rb CHANGED
@@ -1,3 +1,27 @@
1
+ #--
2
+ # Copyright (c) 2010 Michaël Rigart
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+
1
25
  require 'net/http'
2
26
  require 'net/https'
3
27
  require 'uri'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ganeti_client
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - "Micha\xC3\xABl Rigart"