ganeti_client 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.
- 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
|
+
|