ganeti_client 0.0.1 → 0.0.2

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.
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"