ganeti_client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +75 -0
- data/lib/ganeti_client.rb +14 -0
- data/lib/ganeti_client/client.rb +562 -0
- data/lib/ganeti_client/ganeti_object.rb +16 -0
- metadata +70 -0
data/README
ADDED
@@ -0,0 +1,75 @@
|
|
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
|
+
== Todo
|
7
|
+
|
8
|
+
1. Manually test the following methods + fix when bugs found
|
9
|
+
1.1. redistribute_config
|
10
|
+
1.2. instance_create
|
11
|
+
1.3. instance_delete
|
12
|
+
1.4. instance_reboot
|
13
|
+
1.5. instance_shutdown
|
14
|
+
1.6. instance_startup
|
15
|
+
1.7. instance_reinstall
|
16
|
+
1.8. instance_replace_disks
|
17
|
+
1.9. instance_activate_disks
|
18
|
+
1.10. instance_create_tags
|
19
|
+
1.11. instance_delete_tags
|
20
|
+
1.12. job_get
|
21
|
+
1.13. job_delete
|
22
|
+
1.14. node_evaluate
|
23
|
+
1.15. node_migrate
|
24
|
+
1.16. node_change_role
|
25
|
+
1.17. node_get_storage
|
26
|
+
1.18. node_modify_storage
|
27
|
+
1.19. node_repair_storage
|
28
|
+
1.20. node_get_tags
|
29
|
+
1.21. node_create_tags
|
30
|
+
1.22. node_delete_tags
|
31
|
+
1.22. tags_create
|
32
|
+
1.23. tags_delete
|
33
|
+
2. Add some error handling
|
34
|
+
3. Make some code improvements
|
35
|
+
4. Add better comments
|
36
|
+
5. Improve this README with better docs
|
37
|
+
6. Write tests!
|
38
|
+
|
39
|
+
|
40
|
+
== About
|
41
|
+
|
42
|
+
|
43
|
+
== Installation
|
44
|
+
|
45
|
+
gem install ganeti_client
|
46
|
+
|
47
|
+
|
48
|
+
== Usage
|
49
|
+
|
50
|
+
# the last parameter is a boolean (true|false) to indicate if you want to display the response.
|
51
|
+
# this might be handy for debugging purposes
|
52
|
+
client = GanetiClient::Client.new("your-host-and-port", "username", "password", boolean)
|
53
|
+
|
54
|
+
# now you should be able to access the api resources by using the client instance.
|
55
|
+
# example:
|
56
|
+
info = client.get_info
|
57
|
+
=> #<GanetiInfo:0x10151bb78>
|
58
|
+
|
59
|
+
# most methods return an object. When you use .to_json on an object, you get the json object returned
|
60
|
+
# then you can see all the attributes available
|
61
|
+
info.name
|
62
|
+
=> "hostname"
|
63
|
+
|
64
|
+
== Contributing
|
65
|
+
|
66
|
+
1. Fork the project
|
67
|
+
2. Add your changes
|
68
|
+
3. Write tests
|
69
|
+
4. Send a pull request
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
@@ -0,0 +1,562 @@
|
|
1
|
+
module GanetiClient
|
2
|
+
class Client
|
3
|
+
|
4
|
+
attr_accessor :conn, :headers, :version, :show_response
|
5
|
+
|
6
|
+
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)
|
14
|
+
|
15
|
+
self.version = self.get_version
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# Cluster information resource
|
20
|
+
# Returns cluster information
|
21
|
+
def info_get
|
22
|
+
url = get_url("info")
|
23
|
+
response_body = JSON.parse(send_request("GET", url))
|
24
|
+
|
25
|
+
create_class("GanetiInfo")
|
26
|
+
|
27
|
+
return GanetiInfo.new(response_body)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Redistrite configuration to all nodes
|
31
|
+
# Redistribute configuration to all nodes. The result will be a job id.
|
32
|
+
def redistribute_config
|
33
|
+
url = get_url("redistribute-config")
|
34
|
+
response_body = send_request("PUT", url)
|
35
|
+
|
36
|
+
return response_body
|
37
|
+
end
|
38
|
+
|
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.
|
42
|
+
def instances_get(bulk = 0)
|
43
|
+
url = get_url("instances", {"bulk" => bulk})
|
44
|
+
response_body = JSON.parse(send_request("GET", url))
|
45
|
+
|
46
|
+
create_class("GanetiInstance")
|
47
|
+
|
48
|
+
list = Array.new
|
49
|
+
response_body.each { |item| list << GanetiInstance.new(item) }
|
50
|
+
|
51
|
+
return list
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates an instance
|
55
|
+
# 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
|
+
# 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
|
+
#
|
59
|
+
# Example of instance info:
|
60
|
+
#
|
61
|
+
def instance_create(info, dry_run = 0)
|
62
|
+
url = get_url("instances", {"dry-run" => dry_run})
|
63
|
+
body = JSON.generate(info)
|
64
|
+
response_body = send_request("POST", url, body)
|
65
|
+
|
66
|
+
return response_body
|
67
|
+
end
|
68
|
+
|
69
|
+
# Instance-specific resource
|
70
|
+
# Returns information about an instance, similar to the bulk output from the instance list
|
71
|
+
def instance_get(name)
|
72
|
+
url = get_url("instances/#{name}")
|
73
|
+
response_body = JSON.parse(send_request("GET", url))
|
74
|
+
|
75
|
+
create_class("GanetiInstance")
|
76
|
+
|
77
|
+
return GanetiInstance.new(response_body)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Instance-specific resource
|
81
|
+
# Deletes an instance
|
82
|
+
# It supports the dry-run argument
|
83
|
+
def instance_delete(name, dry_run)
|
84
|
+
url = get_url("instances/#{name}", {"dry-run" => dry_run})
|
85
|
+
response_body = send_request("DELETE", url)
|
86
|
+
|
87
|
+
return response_body
|
88
|
+
end
|
89
|
+
|
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
|
93
|
+
def instance_get_info(name, static = 0)
|
94
|
+
url = get_url("instances/#{name}/info", {"static" => static})
|
95
|
+
response_body = send_request("GET", url)
|
96
|
+
|
97
|
+
return response_body
|
98
|
+
end
|
99
|
+
|
100
|
+
# Reboots URI for an instance
|
101
|
+
# Reboots the instance
|
102
|
+
# The URI takes optional type=soft|hard|full and ignore_secondaries=0|1 parameters
|
103
|
+
#
|
104
|
+
# type defines the reboot type.
|
105
|
+
# soft is just a normal reboot, without terminating the hypervisor.
|
106
|
+
# hard means full shutdown (including terminating the hypervisor process) and startup again
|
107
|
+
# 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
|
+
#
|
109
|
+
# it supports the dry-run argument
|
110
|
+
def instance_reboot(name, type = "soft", ignore_secondaries = 0, dry_run = 0)
|
111
|
+
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")
|
115
|
+
|
116
|
+
return GanetiIntanceReboot.new(response_body)
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Instance shutdown URI
|
121
|
+
# Shutdowns an instance
|
122
|
+
# It supports the dry-run argument
|
123
|
+
def instance_shutdown(name, dry_run = 0)
|
124
|
+
url = get_url("instances/#{name}/shutdown", {"dry-run" => dry_run})
|
125
|
+
response_body = JSON.parse(send_request("PUT", url))
|
126
|
+
|
127
|
+
create_class("GanetiInstanceShutdown")
|
128
|
+
|
129
|
+
return GanetiInstanceShutdown.new(response_body)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Instance startup URI
|
133
|
+
# Startup an instance
|
134
|
+
# 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
|
136
|
+
def instance_startup(name, force = 0, dry_run=0)
|
137
|
+
url = get_url("instances/#{name}/startup", {"force" => force, "dry-run" => dry_run})
|
138
|
+
body = "" # force parameter
|
139
|
+
response_body = send_request("PUT", url, body)
|
140
|
+
|
141
|
+
create_class("GanetiInstanceStartup")
|
142
|
+
|
143
|
+
return GanetiInstanceStartup.new(response_body)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Install the operating system again
|
147
|
+
# Takes the parameters os (OS template name) and nostartup (bool)
|
148
|
+
def instance_reinstall(name, os_name, nostartup)
|
149
|
+
url = get_url("instances/#{name}/reinstall", {"os" => os_name, "nostartup" => nostartup})
|
150
|
+
response_body = send_request("POST", url)
|
151
|
+
|
152
|
+
create_class("GanetiInstanceReinstall")
|
153
|
+
|
154
|
+
return GanetiInstanceReinstall.new(response_body)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Replaces disks on an instance
|
158
|
+
# 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
|
+
# Either remote_node or iallocator needs to be defined when using mode=replace_new_secondary
|
160
|
+
# mode is a mandatory parameter. replace_auto tries to determine the broken disk(s) on its own and replacing it
|
161
|
+
def instance_replace_disks(name, mode = "replace_auto", iallocator = "", remote_node = "", disks = "")
|
162
|
+
url = get_url("instances/#{name}/replace-disks", {"mode" => mode, "iallocator" => iallocator, "remote_node" => remote_node, "disks" => disks})
|
163
|
+
response_body = send_request("POST", url)
|
164
|
+
|
165
|
+
return "?"
|
166
|
+
end
|
167
|
+
|
168
|
+
# Activate disks on an instance
|
169
|
+
# Takes the bool parameter ignore_size. When set ignore the recorded size (useful for forcing activation when recoreded size is wrong)
|
170
|
+
def intance_activate_disks(name, ignore_size = 0)
|
171
|
+
url = get_url("instances/#{name}/activate-disks", {"ignore_size" => ignore_size})
|
172
|
+
response_body = send_request("PUT", url)
|
173
|
+
|
174
|
+
return "?"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Deactivate disks on an instance
|
178
|
+
# Takes no parameters
|
179
|
+
def instance_deactivate_disks(name)
|
180
|
+
url = get_url("instances/#{name}/deactivate-disks")
|
181
|
+
response_body = send_request("PUT", url)
|
182
|
+
|
183
|
+
return "?"
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a list of tags
|
187
|
+
#
|
188
|
+
# Example:
|
189
|
+
# ["tag1", "tag2", "tag3"]
|
190
|
+
def instance_get_tags(name)
|
191
|
+
url = get_url("instances/#{name}/tags")
|
192
|
+
response_body = JSON.parse(send_request("GET", url))
|
193
|
+
|
194
|
+
return response_body
|
195
|
+
end
|
196
|
+
|
197
|
+
# 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})
|
202
|
+
response_body = send_request("PUT", url)
|
203
|
+
|
204
|
+
return response_body
|
205
|
+
end
|
206
|
+
|
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})
|
213
|
+
response_body = send_request("DELETE", url)
|
214
|
+
|
215
|
+
return "?"
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns a dictionary of jobs
|
219
|
+
# Returns: a dictionary with jobs id and uri
|
220
|
+
def jobs_get
|
221
|
+
url = get_url("jobs")
|
222
|
+
response_body = JSON.parse(send_request("GET", url))
|
223
|
+
|
224
|
+
create_class("GanetiJob")
|
225
|
+
|
226
|
+
list = Array.new
|
227
|
+
response_body.each { |item| list << GanetiJob.new(item) }
|
228
|
+
|
229
|
+
return list
|
230
|
+
end
|
231
|
+
|
232
|
+
# Individual job URI
|
233
|
+
# Return a job status
|
234
|
+
# Returns: a dictionary with job parameters
|
235
|
+
#
|
236
|
+
# The result includes:
|
237
|
+
# id: job ID as number
|
238
|
+
# status: current job status as a string
|
239
|
+
# ops: involved OpCodes as a list of dictionaries for each opcodes in the job
|
240
|
+
# opstatus: OpCodes status as a list
|
241
|
+
# opresult: OpCodes results as a list
|
242
|
+
#
|
243
|
+
# For a successful opcode, the opresult field corresponding to it will contain the raw result from its LogicalUnit. In case an opcode has failed, its element in the opresult list will be a list of two elements:
|
244
|
+
# first element the error type (the Ganeti internal error name)
|
245
|
+
# second element a list of either one or two elements:
|
246
|
+
# the first element is the textual error description
|
247
|
+
# the second element, if any, will hold an error classification
|
248
|
+
#
|
249
|
+
# The error classification is most useful for the OpPrereqError error type - these errors happen before the OpCode has started executing, so it’s possible to retry the
|
250
|
+
# OpCode without side effects. But whether it make sense to retry depends on the error classification:
|
251
|
+
#
|
252
|
+
# resolver_error
|
253
|
+
# Resolver errors. This usually means that a name doesn’t exist in DNS, so if it’s a case of slow DNS propagation the operation can be retried later.
|
254
|
+
#
|
255
|
+
# insufficient_resources
|
256
|
+
# Not enough resources (iallocator failure, disk space, memory, etc.). If the resources on the cluster increase, the operation might succeed.
|
257
|
+
#
|
258
|
+
# wrong_input
|
259
|
+
# Wrong arguments (at syntax level). The operation will not ever be accepted unless the arguments change.
|
260
|
+
#
|
261
|
+
# wrong_state
|
262
|
+
# Wrong entity state. For example, live migration has been requested for a down instance, or instance creation on an offline node. The operation can be retried once the resource has changed state.
|
263
|
+
#
|
264
|
+
# unknown_entity
|
265
|
+
# Entity not found. For example, information has been requested for an unknown instance.
|
266
|
+
#
|
267
|
+
# already_exists
|
268
|
+
# Entity already exists. For example, instance creation has been requested for an already-existing instance.
|
269
|
+
#
|
270
|
+
# resource_not_unique
|
271
|
+
# Resource not unique (e.g. MAC or IP duplication).
|
272
|
+
#
|
273
|
+
# internal_error
|
274
|
+
# Internal cluster error. For example, a node is unreachable but not set offline, or the ganeti node daemons are not working, etc. A gnt-cluster verify should be run.
|
275
|
+
#
|
276
|
+
# environment_error
|
277
|
+
# Environment error (e.g. node disk error). A gnt-cluster verify should be run.
|
278
|
+
#
|
279
|
+
# Note that in the above list, by entity we refer to a node or instance, while by a resource we refer to an instance’s disk, or NIC, etc.
|
280
|
+
def job_get(job_id)
|
281
|
+
url = get_url("jobs/#{job_id}")
|
282
|
+
response_body = JSON.parse(send_request("GET", url))
|
283
|
+
|
284
|
+
create_class("GanetiJob")
|
285
|
+
|
286
|
+
return GanetiJob.new(response_body)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Cancel a not-yet-started job
|
290
|
+
def job_delete(job_id)
|
291
|
+
url = get_url("jobs/#{job_id}")
|
292
|
+
response = send_request("DELETE", url)
|
293
|
+
|
294
|
+
return "?"
|
295
|
+
end
|
296
|
+
|
297
|
+
# Nodes resource
|
298
|
+
# Returns a list of all nodes
|
299
|
+
# If the optional ‘bulk’ argument is provided and set to ‘true’ value (i.e ‘?bulk=1’).
|
300
|
+
#
|
301
|
+
# Returns detailed information about nodes as a list.
|
302
|
+
def nodes_get(bulk = 0)
|
303
|
+
url = get_url("nodes", {"bulk", bulk})
|
304
|
+
response_body = JSON.parse(send_request("GET", url))
|
305
|
+
|
306
|
+
create_class("GanetiNode")
|
307
|
+
|
308
|
+
list = Array.new
|
309
|
+
response_body.each { |item| list << GanetiNode.new(item) }
|
310
|
+
|
311
|
+
return list
|
312
|
+
end
|
313
|
+
|
314
|
+
# Returns information about a node
|
315
|
+
def node_get(name)
|
316
|
+
url = get_url("nodes/#{name}")
|
317
|
+
response_body = JSON.parse(send_request("GET", url))
|
318
|
+
|
319
|
+
create_class("GanetiNode")
|
320
|
+
|
321
|
+
return GanetiNode.new(response_body)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Evacuates all secondary instances off a node.
|
325
|
+
# To evacuate a node, either one of the iallocator or remote_node parameters must be passed:
|
326
|
+
#
|
327
|
+
# Example:
|
328
|
+
# evacuate?iallocator=[iallocator]
|
329
|
+
# evacuate?remote_node=[nodeX.example.com]
|
330
|
+
def node_evauate(name, iallocator = "", remote_node = "")
|
331
|
+
url = get_url("nodes/#{name}/evacuate", {"iallocator" => iallocator, "remote_node" => remote_node})
|
332
|
+
response_body = send_request("POST", url)
|
333
|
+
|
334
|
+
return "?"
|
335
|
+
end
|
336
|
+
|
337
|
+
# Migrates all primary instances of a node
|
338
|
+
# No parameters are required, but the bool parameter live can be set to use live migration (if available)
|
339
|
+
#
|
340
|
+
# Example:
|
341
|
+
# migrate?live=[0|1]
|
342
|
+
def node_migrate(name, live = 0)
|
343
|
+
url = get_url("nodes/#{name}/migrate", {"live" => live})
|
344
|
+
response_body = send_request("POST", url)
|
345
|
+
|
346
|
+
return "?"
|
347
|
+
end
|
348
|
+
|
349
|
+
# Get the node role
|
350
|
+
# Returns the current node role
|
351
|
+
#
|
352
|
+
# Example:
|
353
|
+
# "master-candidate"
|
354
|
+
#
|
355
|
+
# The rol is always one of the following:
|
356
|
+
# drained
|
357
|
+
# master
|
358
|
+
# master-candidate
|
359
|
+
# offline
|
360
|
+
# regular
|
361
|
+
def node_get_role(name)
|
362
|
+
url = get_url("nodes/#{name}/role")
|
363
|
+
response_body = send_request("GET", url)
|
364
|
+
|
365
|
+
return response_body
|
366
|
+
end
|
367
|
+
|
368
|
+
# Change the node role
|
369
|
+
# the request is a string which shoud be PUT to this URI. The result will be a job id
|
370
|
+
# It supports the bool force argument
|
371
|
+
#
|
372
|
+
# The rol is always one of the following:
|
373
|
+
# drained
|
374
|
+
# master
|
375
|
+
# master-candidate
|
376
|
+
# offline
|
377
|
+
# regular
|
378
|
+
def node_change_role(name, role, force = 0)
|
379
|
+
url = get_url("nodes/#{name}/role", {"role" => role, "force" => foce})
|
380
|
+
response_body = send_request("PUT", url)
|
381
|
+
|
382
|
+
return response_body
|
383
|
+
end
|
384
|
+
|
385
|
+
# Manages storage units on the node
|
386
|
+
# Requests a list of storage units on a node. Requires the parameters storage_type (one of file, lvm-pv or lvm-vg) and output_fields. The result will be a job id, using which the result can be retrieved
|
387
|
+
def node_get_storage(name, storage_type = "", output_fields = "")
|
388
|
+
url = get_url("nodes/#{name}/storage", {"storage_type" => storage_type, "output_fields" => output_fields})
|
389
|
+
response_body = send_request("GET", url)
|
390
|
+
|
391
|
+
return response_body
|
392
|
+
end
|
393
|
+
|
394
|
+
# Modify storage units on the node
|
395
|
+
# Mofifies parameters of storage units on the node. Requires the parameters storage_type (one of file, lvm-pv or lvm-vg) and name (name of the storage unit).
|
396
|
+
# Parameters can be passed additionally. Currently only allocatable (bool) is supported.
|
397
|
+
#
|
398
|
+
# The result will be a job id.
|
399
|
+
def node_modify_storage(name, storage_type, allocatable = 0)
|
400
|
+
url = get_url("nodes/#{name}/storage/modify", {"storage_type" => storage_type, "allocatable" => allocatable})
|
401
|
+
response_body = send_request("PUT", url)
|
402
|
+
|
403
|
+
return response_body
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
# Repairs a storage unit on the node. Requires the parameters storage_type (currently only lvm-vg can be repaired) and name (name of the storage unit).
|
408
|
+
#
|
409
|
+
# The result will be a job id
|
410
|
+
def node_repair_storage(name, storage_type = "lvm-vg")
|
411
|
+
url = get_url("nodes/#{name}/storage/repair", {"storage_type" => storage_type})
|
412
|
+
reponse_body = send_request("PUT", url)
|
413
|
+
|
414
|
+
return response_body
|
415
|
+
end
|
416
|
+
|
417
|
+
|
418
|
+
# Manages per-node tags
|
419
|
+
# Returns a list of tags
|
420
|
+
#
|
421
|
+
# Example:
|
422
|
+
# ["tag1","tag2", "tag3"]
|
423
|
+
def node_get_tags(name)
|
424
|
+
url = get_url("nodes/#{name}/tags")
|
425
|
+
response_body = send_request("GET", url)
|
426
|
+
|
427
|
+
return response_body
|
428
|
+
end
|
429
|
+
|
430
|
+
# Add a set of tags
|
431
|
+
# The request as a list of strings should be PUT to this URI.
|
432
|
+
# It supports the dry-run argument
|
433
|
+
#
|
434
|
+
# The result will be a job id
|
435
|
+
def node_create_tags(name, tags, dry_run = 0)
|
436
|
+
url = get_url("nodes/#{name}/tags", {"tags" => tags, "dry-run" => dry_run})
|
437
|
+
response_body = send_request("PUT", url)
|
438
|
+
|
439
|
+
return response_body
|
440
|
+
end
|
441
|
+
|
442
|
+
# Deletes tags
|
443
|
+
# In order to delete a set of tags, the DELETE request should be addressed to URI like:
|
444
|
+
# /tags?tag=[tag]&tag=[tag]
|
445
|
+
#
|
446
|
+
# It supports the dry-run argument
|
447
|
+
def node_delete_tags(name, tags, dry_run = 0)
|
448
|
+
url = get_url("nodes/#{name}/tags", {"tags" => targs, "dry-run" => dry_run})
|
449
|
+
response_body = send_request("DELETE", url)
|
450
|
+
|
451
|
+
return response_body
|
452
|
+
end
|
453
|
+
|
454
|
+
# OS resource
|
455
|
+
# Returns a list of all OSes
|
456
|
+
#
|
457
|
+
# Can return error 500 in case of a problem. Since this is a costly operation for Ganeti 2.0, it is not recommented to execute it too often
|
458
|
+
#
|
459
|
+
# Example:
|
460
|
+
# ["debian-etch"]
|
461
|
+
def os_list_get
|
462
|
+
url = get_url("os")
|
463
|
+
response_body = JSON.parse(send_request("GET", url))
|
464
|
+
|
465
|
+
return response_body
|
466
|
+
end
|
467
|
+
|
468
|
+
# Manages cluster tags
|
469
|
+
# Returns the cluster tags
|
470
|
+
#
|
471
|
+
# Example:
|
472
|
+
# ["tag1", "tag2", "tag3"]
|
473
|
+
def tags_get
|
474
|
+
url = get_url("tags")
|
475
|
+
response_body = JSON.parse(send_request("GET", url))
|
476
|
+
|
477
|
+
return response_body
|
478
|
+
end
|
479
|
+
|
480
|
+
# Adds a set of tags
|
481
|
+
# The request as a list of strings should be PUT to this URI. The result will be a job id
|
482
|
+
#
|
483
|
+
# It supports the dry-run argument
|
484
|
+
def tags_create(tags, dry_run = 0)
|
485
|
+
url = get_url("tags", {"tags" => tags, "dry-run" => dry_run})
|
486
|
+
response_body = send_request("PUT", url)
|
487
|
+
|
488
|
+
return response_body
|
489
|
+
end
|
490
|
+
|
491
|
+
# Deletes tags
|
492
|
+
# In order to delete a set of tags, the DELETE request should be addressed to URI like:
|
493
|
+
# /tags?tag=[tag]&tag=[tag]
|
494
|
+
#
|
495
|
+
# It supports the dry-run argument
|
496
|
+
def tags_delete(tags, dry_run = 0)
|
497
|
+
url = get_url("tags", {"tags" => tags, "dry-run" => dry_run})
|
498
|
+
response_body = send_request("DELETE", url)
|
499
|
+
|
500
|
+
return response_body
|
501
|
+
end
|
502
|
+
|
503
|
+
|
504
|
+
# The version resource
|
505
|
+
# This resource should be used to determine the remote API version and to adapt client accordingly
|
506
|
+
# Returns the remote API version. Ganeti 1.2 returns 1 and Ganeti 2.0 returns 2
|
507
|
+
def version_get
|
508
|
+
url = get_url("version")
|
509
|
+
response_body = send_request("GET", url)
|
510
|
+
|
511
|
+
return response_body
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
private
|
516
|
+
|
517
|
+
def authenticate(username, password)
|
518
|
+
basic = Base64.b64encode("#{username}:#{password}")
|
519
|
+
headers = {'Authorization' => "Basic #{basic}"}
|
520
|
+
|
521
|
+
return headers
|
522
|
+
end
|
523
|
+
|
524
|
+
def get_url(path, params = nil)
|
525
|
+
param_string = ""
|
526
|
+
|
527
|
+
if params
|
528
|
+
params.each do |key, value|
|
529
|
+
if value.kind_of?(Array)
|
530
|
+
value.each do |svalue|
|
531
|
+
param_string += "#{key}=#{svalue}"
|
532
|
+
end
|
533
|
+
else
|
534
|
+
param_string += "#{key}=#{value}&"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
return (self.version)? "/#{self.version}/#{path}?#{param_string}" : "/#{path}?#{param_string}"
|
539
|
+
end
|
540
|
+
|
541
|
+
def send_request(method, url, body = nil)
|
542
|
+
response = self.conn.send_request(method, url, body)
|
543
|
+
|
544
|
+
puts "Response #{response.code} #{response.message}: #{response.body}" if self.show_reponse
|
545
|
+
return response.body.strip
|
546
|
+
end
|
547
|
+
|
548
|
+
def create_class(class_name)
|
549
|
+
unless(class_exists?(class_name))
|
550
|
+
klass = Class.new GanetiClient::GanetiObject
|
551
|
+
Object.const_set(class_name, klass)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
def class_exists?(class_name)
|
556
|
+
klass = Module.const_get(class_name)
|
557
|
+
return klass.is_a?(Class)
|
558
|
+
rescue NameError
|
559
|
+
return false
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module GanetiClient
|
2
|
+
class GanetiObject
|
3
|
+
|
4
|
+
attr_accessor :json_object
|
5
|
+
|
6
|
+
def initialize(json = {})
|
7
|
+
self.json_object = json
|
8
|
+
|
9
|
+
json.each { |attr_name, attr_value| self.class.send(:define_method, attr_name.to_sym){ return attr_value } }
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_json
|
13
|
+
return self.json_object
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ganeti_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Micha\xC3\xABl Rigart"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-15 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Google Ganeti RAPI client for Ruby
|
23
|
+
email: michael@netronix.be
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- lib/ganeti_client.rb
|
33
|
+
- lib/ganeti_client/client.rb
|
34
|
+
- lib/ganeti_client/ganeti_object.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://www.netronix.be
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --charset=UTF-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
hash: 3
|
50
|
+
segments:
|
51
|
+
- 0
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project: nowarning
|
65
|
+
rubygems_version: 1.3.7
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Google Ganeti Client
|
69
|
+
test_files: []
|
70
|
+
|