pve 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +70 -0
- data/README.adoc +22 -0
- data/bin/pvecli +5 -0
- data/lib/pve.rb +4 -0
- data/lib/pve/cli.rb +159 -0
- data/lib/pve/cli/base.rb +187 -0
- data/lib/pve/cli/ct.rb +115 -0
- data/lib/pve/cli/ha.rb +78 -0
- data/lib/pve/cli/node.rb +46 -0
- data/lib/pve/cli/qm.rb +32 -0
- data/lib/pve/cli/task.rb +26 -0
- data/lib/pve/helper.rb +167 -0
- data/lib/pve/proxmox.rb +597 -0
- data/lib/pve/qm.rb +32 -0
- data/lib/pve/templates.rb +154 -0
- data/lib/pve/version.rb +3 -0
- data/pve.gemspec +37 -0
- metadata +150 -0
data/lib/pve/proxmox.rb
ADDED
@@ -0,0 +1,597 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'cgi'
|
3
|
+
require 'json'
|
4
|
+
require 'ipaddress'
|
5
|
+
require 'shellwords'
|
6
|
+
require 'active_support/all'
|
7
|
+
|
8
|
+
module Proxmox
|
9
|
+
class Exception < ::Exception
|
10
|
+
end
|
11
|
+
class NotFound < Exception
|
12
|
+
end
|
13
|
+
class AlreadyExists < Exception
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.connect username, password, realm: nil, verify_tls: nil, uri: nil
|
17
|
+
uri ||= 'https://localhost:8006/api2/json'
|
18
|
+
cred =
|
19
|
+
{
|
20
|
+
username: username,
|
21
|
+
password: password,
|
22
|
+
realm: realm || 'pve'
|
23
|
+
}
|
24
|
+
@@connection =
|
25
|
+
RestClient::Resource.new( uri, verify_ssl: ! ! verify_tls).tap do |rs|
|
26
|
+
resp = rs['/access/ticket'].post cred
|
27
|
+
case resp.code
|
28
|
+
when 200
|
29
|
+
data = JSON.parse resp.body, symbolize_names: true
|
30
|
+
@@api_ticket =
|
31
|
+
{
|
32
|
+
CSRFPreventionToken: data[:data][:CSRFPreventionToken],
|
33
|
+
cookie: "PVEAuthCookie=#{CGI.escape data[:data][:ticket]}"
|
34
|
+
}
|
35
|
+
else
|
36
|
+
raise Exception, "Authentication failed"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.connection
|
42
|
+
@@connection
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.api_ticket
|
46
|
+
@@api_ticket
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find_by_name name
|
50
|
+
Proxmox::LXC.find_by_name( name) || Proxmox::Qemu.find_by_name( name) || Proxmox::Node.find_by_name( name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.find_by_vmid vmid
|
54
|
+
Proxmox::LXC.find_by_vmid( vmid) || Proxmox::Qemu.find_by_vmid( vmid) || Proxmox::Node.find_by_vmid( vmid)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.find name_or_id
|
58
|
+
Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find( name_or_id) || Proxmox::Node.find( name_or_id)
|
59
|
+
end
|
60
|
+
|
61
|
+
module RestConnection
|
62
|
+
def __response__ resp
|
63
|
+
case resp.code
|
64
|
+
when 200
|
65
|
+
JSON.parse( resp.body, symbolize_names: true)[:data]
|
66
|
+
when 500
|
67
|
+
nil
|
68
|
+
else
|
69
|
+
raise "Request failed of #{req.url} [#{resp.code}]: #{resp.body.inspect}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def __headers__ **hdrs
|
74
|
+
Proxmox.api_ticket.merge( 'Accept' => 'application/json').merge( hdrs)
|
75
|
+
end
|
76
|
+
|
77
|
+
def __data__ **data
|
78
|
+
case data
|
79
|
+
when String
|
80
|
+
data
|
81
|
+
else
|
82
|
+
data.to_json
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private :__response__, :__headers__, :__data__
|
87
|
+
|
88
|
+
def bench path
|
89
|
+
#t = Time.now
|
90
|
+
r = yield
|
91
|
+
#p path => Time.now-t
|
92
|
+
r
|
93
|
+
end
|
94
|
+
|
95
|
+
def rest_get path, **data, &exe
|
96
|
+
data = data.delete_if {|k,v|v.nil?}
|
97
|
+
path += "#{path.include?( ??) ? ?& : ??}#{data.map{|k,v|"#{CGI.escape k.to_s}=#{CGI.escape v.to_s}"}.join '&'}" unless data.empty?
|
98
|
+
__response__ Proxmox.connection[path].get( __headers__( :'Content-Type' => 'application/json'))
|
99
|
+
end
|
100
|
+
|
101
|
+
def rest_put path, **data, &exe
|
102
|
+
__response__ Proxmox.connection[path].put( __data__( data), __headers__( :'Content-Type' => 'application/json'))
|
103
|
+
end
|
104
|
+
|
105
|
+
def rest_del path, &exe
|
106
|
+
__response__ Proxmox.connection[path].delete( __headers__( :'Content-Type' => 'application/json'))
|
107
|
+
end
|
108
|
+
|
109
|
+
def rest_post path, **data, &exe
|
110
|
+
__response__ Proxmox.connection[path].post( __data__( data), __headers__( :'Content-Type' => 'application/json'))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Base
|
115
|
+
include RestConnection
|
116
|
+
extend RestConnection
|
117
|
+
|
118
|
+
attr_reader :sid
|
119
|
+
|
120
|
+
class <<self
|
121
|
+
def __new__ data
|
122
|
+
n = allocate
|
123
|
+
n.send :__update__, data
|
124
|
+
end
|
125
|
+
private :__new__
|
126
|
+
end
|
127
|
+
|
128
|
+
def respond_to? method
|
129
|
+
super or instance_variable_defined?( "@#{method}")
|
130
|
+
end
|
131
|
+
|
132
|
+
def method_missing method, *args, &exe
|
133
|
+
if instance_variable_defined? "@#{method}"
|
134
|
+
instance_variable_get "@#{method}"
|
135
|
+
else
|
136
|
+
super
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def __update__ data
|
141
|
+
instance_variables.each do |k|
|
142
|
+
remove_instance_variable k
|
143
|
+
end
|
144
|
+
data.each do |k,v|
|
145
|
+
instance_variable_set "@#{k}", v
|
146
|
+
end
|
147
|
+
initialize
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
def refresh!
|
152
|
+
__update__ rest_get( @rest_prefix)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Node < Base
|
157
|
+
class <<self
|
158
|
+
def find_by_name name
|
159
|
+
all.each do |node|
|
160
|
+
return node if node.node == name
|
161
|
+
end
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_by_name! name
|
166
|
+
find_by_name( name) or raise( Proxmox::NotFound, "Node not found: #{name}")
|
167
|
+
end
|
168
|
+
|
169
|
+
def all
|
170
|
+
rest_get( '/nodes').map {|d| __new__ d.merge( t: 'nd') }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
attr_reader :name
|
175
|
+
|
176
|
+
def === t
|
177
|
+
@name =~ t or @vmid.to_s =~ t or @sid =~ t
|
178
|
+
end
|
179
|
+
|
180
|
+
def initialize
|
181
|
+
@rest_prefix = "/nodes/#{@node}"
|
182
|
+
@sid = "nd:#{@node}"
|
183
|
+
@name = @node
|
184
|
+
end
|
185
|
+
|
186
|
+
def offline?() @status.nil? or 'offline' == @status end
|
187
|
+
def online?() 'online' == @status end
|
188
|
+
|
189
|
+
def lxc
|
190
|
+
return [] if offline?
|
191
|
+
rest_get( "#{@rest_prefix}/lxc").map {|d| LXC.send :__new__, d.merge( node: self, t: 'ct') }
|
192
|
+
end
|
193
|
+
|
194
|
+
def qemu
|
195
|
+
return [] if offline?
|
196
|
+
rest_get( "#{@rest_prefix}/qemu").map {|d| Qemu.send :__new__, d.merge( node: self, t: 'qm') }
|
197
|
+
end
|
198
|
+
|
199
|
+
def tasks
|
200
|
+
return [] if offline?
|
201
|
+
rest_get( "/#{@rest_prefix}/tasks").map {|d| Task.send :__new__, d.merge( node: self, t: 'task') }
|
202
|
+
end
|
203
|
+
|
204
|
+
def enter *command
|
205
|
+
Kernel.system 'ssh', '-t', node, command.map( &:to_s).shelljoin
|
206
|
+
end
|
207
|
+
|
208
|
+
def exec *command
|
209
|
+
Kernel.system 'ssh', node, command.map( &:to_s).shelljoin
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class Task < Base
|
214
|
+
def initialize
|
215
|
+
@rest_prefix = "/nodes/#{@node.node}/tasks/#{upid}"
|
216
|
+
@sid = upid
|
217
|
+
end
|
218
|
+
|
219
|
+
def inspect
|
220
|
+
"#<#{self.class.name} #{upid}>"
|
221
|
+
end
|
222
|
+
|
223
|
+
#def finished?
|
224
|
+
# rest_get( "/nodes/#{node}/tasks/")
|
225
|
+
#end
|
226
|
+
|
227
|
+
def status
|
228
|
+
rest_get( "#{@rest_prefix}/status")
|
229
|
+
end
|
230
|
+
|
231
|
+
def log start: nil, limit: nil
|
232
|
+
rest_get( "#{@rest_prefix}/log", start: start, limit: limit)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class Hosted < Base
|
237
|
+
def refresh!
|
238
|
+
node, t = @node, @t
|
239
|
+
__update__ rest_get( "#{@rest_prefix}/status/current").merge( node: node, t: t)
|
240
|
+
end
|
241
|
+
|
242
|
+
def === t
|
243
|
+
@name =~ t or @vmid.to_s =~ t or @sid =~ t
|
244
|
+
end
|
245
|
+
|
246
|
+
def migrate node
|
247
|
+
node =
|
248
|
+
case node
|
249
|
+
when Node then node
|
250
|
+
else Node.find!( node.to_s)
|
251
|
+
end
|
252
|
+
Task.send :__new__, node: @node, host: self, upid: rest_post( "#{@rest_prefix}/migrate", target: node.node)
|
253
|
+
end
|
254
|
+
|
255
|
+
def start
|
256
|
+
Task.send :__new__, node: @node, host: self, upid: rest_post( "#{@rest_prefix}/status/start")
|
257
|
+
end
|
258
|
+
|
259
|
+
def stop
|
260
|
+
Task.send :__new__, node: @node, host: self, upid: rest_post( "#{@rest_prefix}/status/stop")
|
261
|
+
end
|
262
|
+
|
263
|
+
def destroy
|
264
|
+
Task.send :__new__, node: @node, host: self, upid: rest_del( "#{@rest_prefix}")
|
265
|
+
end
|
266
|
+
|
267
|
+
def current_status
|
268
|
+
rest_get "#{@rest_prefix}/status/current"
|
269
|
+
end
|
270
|
+
|
271
|
+
def running?
|
272
|
+
current_status[:status] == 'running'
|
273
|
+
end
|
274
|
+
|
275
|
+
def stopped?
|
276
|
+
current_status[:status] == 'stopped'
|
277
|
+
end
|
278
|
+
|
279
|
+
def wait forstatus, timeout: nil, secs: nil, lock: nil, &exe
|
280
|
+
forstatus = forstatus.to_s
|
281
|
+
secs ||= 0.2
|
282
|
+
b = Time.now + timeout.to_i + secs
|
283
|
+
loop do
|
284
|
+
d = current_status
|
285
|
+
return true if d[:status] == forstatus and (not lock or lock == d[:lock])
|
286
|
+
exe.call d[:status], d[:lock] if exe
|
287
|
+
return false if timeout and b <= Time.now
|
288
|
+
sleep secs
|
289
|
+
end
|
290
|
+
nil
|
291
|
+
end
|
292
|
+
|
293
|
+
def config
|
294
|
+
cnf = rest_get "#{@rest_prefix}/config"
|
295
|
+
cnf[:network] =
|
296
|
+
cnf.
|
297
|
+
keys.
|
298
|
+
map( &:to_s).
|
299
|
+
grep( /\Anet\d+\z/).
|
300
|
+
map do |k|
|
301
|
+
nc = {card: k}
|
302
|
+
cnf.delete( k.to_sym).
|
303
|
+
split( ',').
|
304
|
+
each do |f|
|
305
|
+
k, v = f.split( '=', 2)
|
306
|
+
nc[k.to_sym] = v
|
307
|
+
end
|
308
|
+
nc[:ip] &&= IPAddress::IPv4.new nc[:ip]
|
309
|
+
nc[:gw] &&= IPAddress::IPv4.new nc[:gw]
|
310
|
+
nc[:mtu] &&= nc[:mtu].to_i
|
311
|
+
nc[:tag] &&= nc[:tag].to_i
|
312
|
+
nc[:firewall] &&= 1 == nc[:firewall].to_i
|
313
|
+
nc
|
314
|
+
end
|
315
|
+
cnf[:unprivileged] &&= 1 == cnf[:unprivileged]
|
316
|
+
cnf[:memory] &&= cnf[:memory].to_i
|
317
|
+
cnf[:cores] &&= cnf[:cores].to_i
|
318
|
+
cnf
|
319
|
+
end
|
320
|
+
|
321
|
+
def cnfset **cnf
|
322
|
+
r = {delete: []}
|
323
|
+
cnf.each do |k,v|
|
324
|
+
case v
|
325
|
+
when true then r[k] = 1
|
326
|
+
when false then r[k] = 0
|
327
|
+
when nil then r[:delete].push k
|
328
|
+
else r[k] = v
|
329
|
+
end
|
330
|
+
end
|
331
|
+
r.delete :delete if r[:delete].empty?
|
332
|
+
rest_put "#{@rest_prefix}/config", r
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
class Qemu < Hosted
|
337
|
+
class <<self
|
338
|
+
def all
|
339
|
+
Node.all.flat_map {|n| n.qemu }
|
340
|
+
end
|
341
|
+
|
342
|
+
def find_by_vmid vmid
|
343
|
+
vmid = vmid.to_s
|
344
|
+
Node.all.each do |n|
|
345
|
+
n.qemu.each do |l|
|
346
|
+
return l if l.vmid == vmid
|
347
|
+
end
|
348
|
+
end
|
349
|
+
nil
|
350
|
+
end
|
351
|
+
|
352
|
+
def find_by_name name
|
353
|
+
Node.all.each do |n|
|
354
|
+
n.qemu.each do |l|
|
355
|
+
return l if l.name == name
|
356
|
+
end
|
357
|
+
end
|
358
|
+
nil
|
359
|
+
end
|
360
|
+
|
361
|
+
def find name_or_id = nil, name: nil, vmid: nil
|
362
|
+
if (name and vmid) or (name and name_or_id) or (name_or_id and vmid)
|
363
|
+
raise Proxmox::NotFound, "name xor vmid needed to find CT, not both."
|
364
|
+
elsif name
|
365
|
+
find_by_name name
|
366
|
+
elsif vmid
|
367
|
+
find_by_vmid vmid.to_i
|
368
|
+
elsif name_or_id =~ /\D/
|
369
|
+
find_by_name name_or_id
|
370
|
+
elsif name_or_id.is_a?( Numeric) or name_or_id =~ /\d/
|
371
|
+
find_by_vmid name_or_id.to_i
|
372
|
+
else
|
373
|
+
raise Proxmox::NotFound, "name xor vmid needed to find CT."
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def find_by_vmid! name
|
378
|
+
find_by_vmid( name) or raise( Proxmox::NotFound, "Virtual Machine not found: #{name}")
|
379
|
+
end
|
380
|
+
|
381
|
+
def find_by_name! name
|
382
|
+
find_by_name( name) or raise( Proxmox::NotFound, "Virtual Machine not found: #{name}")
|
383
|
+
end
|
384
|
+
|
385
|
+
def find! name
|
386
|
+
find( name) or raise( Proxmox::NotFound, "Virtual Machine not found: #{name}")
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def initialize
|
391
|
+
@rest_prefix = "/nodes/%s/qemu/%d" % [@node.node, @vmid]
|
392
|
+
@sid = "qm:#{@vmid}"
|
393
|
+
end
|
394
|
+
|
395
|
+
def ha
|
396
|
+
HA.find self
|
397
|
+
end
|
398
|
+
|
399
|
+
def exec *args
|
400
|
+
node.exec 'qm', 'guest', 'exec', vmid, '--', *args
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
class LXC < Hosted
|
405
|
+
class <<self
|
406
|
+
def all
|
407
|
+
Node.all.flat_map {|n| n.lxc }
|
408
|
+
end
|
409
|
+
|
410
|
+
def find_by_vmid vmid
|
411
|
+
vmid = vmid.to_s
|
412
|
+
Node.all.each do |n|
|
413
|
+
n.lxc.each do |l|
|
414
|
+
return l if l.vmid == vmid
|
415
|
+
end
|
416
|
+
end
|
417
|
+
nil
|
418
|
+
end
|
419
|
+
|
420
|
+
def find_by_name name
|
421
|
+
Node.all.each do |n|
|
422
|
+
n.lxc.each do |l|
|
423
|
+
return l if l.name == name
|
424
|
+
end
|
425
|
+
end
|
426
|
+
nil
|
427
|
+
end
|
428
|
+
|
429
|
+
def find name_or_id = nil, name: nil, vmid: nil
|
430
|
+
if (name and vmid) or (name and name_or_id) or (name_or_id and vmid)
|
431
|
+
raise ArgumentError, "name xor vmid needed to find CT, not both."
|
432
|
+
elsif name
|
433
|
+
find_by_name name
|
434
|
+
elsif vmid
|
435
|
+
find_by_vmid vmid.to_i
|
436
|
+
elsif name_or_id =~ /\D/
|
437
|
+
find_by_name name_or_id
|
438
|
+
elsif name_or_id.is_a?( Numeric) or name_or_id =~ /\d/
|
439
|
+
find_by_vmid name_or_id.to_i
|
440
|
+
else
|
441
|
+
raise ArgumentError, "name xor vmid needed to find CT."
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def find_by_vmid! name
|
446
|
+
find_by_vmid( name) or raise( Proxmox::NotFound, "Container not found: #{name}")
|
447
|
+
end
|
448
|
+
|
449
|
+
def find_by_name! name
|
450
|
+
find_by_name( name) or raise( Proxmox::NotFound, "Container not found: #{name}")
|
451
|
+
end
|
452
|
+
|
453
|
+
def find! name
|
454
|
+
find( name) or raise( Proxmox::NotFound, "Container not found: #{name}")
|
455
|
+
end
|
456
|
+
|
457
|
+
def create template, **options
|
458
|
+
tmplt = PVE::CTTemplate.const_get( template&.classify || 'Default').new **options
|
459
|
+
name = tmplt.name
|
460
|
+
virts = Proxmox::LXC.all + Proxmox::Qemu.all
|
461
|
+
vmid = tmplt.vmid
|
462
|
+
if virt = virts.find {|v| v.vmid == vmid }
|
463
|
+
raise Proxmox::AlreadyExists, "VT/VM with vmid [#{vmid}] already exists: #{virt.name}"
|
464
|
+
end
|
465
|
+
already_exists = virts.select {|v| v.name == name }
|
466
|
+
unless already_exists.empty?
|
467
|
+
raise Proxmox::AlreadyExists, "CT/VM named [#{name}] already exists: vmid: #{already_exists.map( &:vmid).inspect}"
|
468
|
+
end
|
469
|
+
node = Proxmox::Node.find_by_name! tmplt.node
|
470
|
+
|
471
|
+
options = {
|
472
|
+
ostemplate: tmplt.ostemplate,
|
473
|
+
vmid: vmid.to_s,
|
474
|
+
arch: tmplt.arch,
|
475
|
+
cmode: tmplt.cmode,
|
476
|
+
cores: tmplt.cores.to_i,
|
477
|
+
description: tmplt.description,
|
478
|
+
hostname: tmplt.hostname,
|
479
|
+
memory: tmplt.memory,
|
480
|
+
net0: tmplt.net0&.map {|k,v| "#{k}=#{v}" }&.join(','),
|
481
|
+
net1: tmplt.net1&.map {|k,v| "#{k}=#{v}" }&.join(','),
|
482
|
+
net2: tmplt.net2&.map {|k,v| "#{k}=#{v}" }&.join(','),
|
483
|
+
net3: tmplt.net3&.map {|k,v| "#{k}=#{v}" }&.join(','),
|
484
|
+
ostype: tmplt.ostype,
|
485
|
+
:'ssh-public-keys' => tmplt.ssh_public_keys,
|
486
|
+
storage: tmplt.storage,
|
487
|
+
#rootfs: {
|
488
|
+
# volume: "root:vm-#{vmid}-disk-0", size: '4G'
|
489
|
+
#}.map {|k,v| "#{k}=#{v}" }.join( ','),
|
490
|
+
swap: tmplt.swap,
|
491
|
+
unprivileged: tmplt.unprivileged,
|
492
|
+
}.delete_if {|k,v| v.nil? }
|
493
|
+
@temp = LXC.send :__new__, node: node, vmid: options[:vmid], name: name, hostname: options[:hostname]
|
494
|
+
upid = rest_post( "/nodes/%s/lxc" % node.node, **options)
|
495
|
+
Task.send :__new__, node: node, host: @temp, upid: upid
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def initialize
|
500
|
+
@rest_prefix = "/nodes/%s/lxc/%d" % [@node.node, @vmid]
|
501
|
+
@sid = "ct:#{@vmid}"
|
502
|
+
end
|
503
|
+
|
504
|
+
def ha
|
505
|
+
HA.find self
|
506
|
+
end
|
507
|
+
|
508
|
+
def exec *args
|
509
|
+
node.exec 'pct', 'exec', vmid, '--', *args
|
510
|
+
end
|
511
|
+
|
512
|
+
def enter
|
513
|
+
node.enter 'pct', 'enter', vmid
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
class HA < Base
|
518
|
+
def initialize
|
519
|
+
@rest_prefix = "/cluster/ha/resources/#{virt.sid}"
|
520
|
+
end
|
521
|
+
|
522
|
+
class <<self
|
523
|
+
def find host
|
524
|
+
ha = HA.send :__new__, digest: nil, state: nil, virt: host
|
525
|
+
ha.refresh!
|
526
|
+
end
|
527
|
+
|
528
|
+
def create host, **options
|
529
|
+
find( host).create **options
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
def refresh!
|
534
|
+
virt = @virt
|
535
|
+
__update__( {state: nil}.merge( rest_get( "/cluster/ha/resources/#{virt.sid}")).merge( virt: virt))
|
536
|
+
self
|
537
|
+
rescue RestClient::InternalServerError
|
538
|
+
__update__ digest: nil, state: nil, virt: virt
|
539
|
+
end
|
540
|
+
|
541
|
+
def create group: nil, comment: nil, max_relocate: nil, max_restart: nil, state: nil
|
542
|
+
options = {
|
543
|
+
sid: virt.sid,
|
544
|
+
group: group,
|
545
|
+
comment: comment,
|
546
|
+
max_relocate: max_relocate,
|
547
|
+
max_restart: max_restart,
|
548
|
+
state: state
|
549
|
+
}
|
550
|
+
options.delete_if {|k,v| v.nil? }
|
551
|
+
rest_post "/cluster/ha/resources", **options
|
552
|
+
refresh!
|
553
|
+
end
|
554
|
+
|
555
|
+
def delete
|
556
|
+
rest_del "#{@rest_prefix}"
|
557
|
+
end
|
558
|
+
|
559
|
+
def state= state
|
560
|
+
rest_put "#{@rest_prefix}", state: state.to_s
|
561
|
+
refresh!
|
562
|
+
end
|
563
|
+
|
564
|
+
def started!
|
565
|
+
self.state = :started
|
566
|
+
self
|
567
|
+
end
|
568
|
+
|
569
|
+
def stopped!
|
570
|
+
self.state = :stopped
|
571
|
+
self
|
572
|
+
end
|
573
|
+
|
574
|
+
def disabled!
|
575
|
+
self.state = :disabled
|
576
|
+
self
|
577
|
+
end
|
578
|
+
|
579
|
+
def started?
|
580
|
+
'started' == self.state
|
581
|
+
end
|
582
|
+
|
583
|
+
def stopped?
|
584
|
+
'stopped' == self.state
|
585
|
+
end
|
586
|
+
|
587
|
+
def error?
|
588
|
+
'error' == self.state
|
589
|
+
end
|
590
|
+
|
591
|
+
def active?
|
592
|
+
! ! digest
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
require_relative 'templates'
|