NetApp.rb 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.
- data/lib/netapp.rb +692 -0
- metadata +48 -0
data/lib/netapp.rb
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
# NetApp.rb:
|
|
4
|
+
# * Ruby library for NetApp filer administration via NetApp NMSDK
|
|
5
|
+
# * https://github.com/azet/NetApp.rb
|
|
6
|
+
#
|
|
7
|
+
# LICENSE:
|
|
8
|
+
# MIT License (http://opensource.org/licenses/MIT)
|
|
9
|
+
#
|
|
10
|
+
# AUTHORS:
|
|
11
|
+
# Aaron <azet@azet.org> Zauner
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
# Include NetApp Manageability SDK for Ruby (change name if neccessary)
|
|
15
|
+
$: << File.expand_path(File.dirname(__FILE__) + "/../lib/NMSDK-Ruby")
|
|
16
|
+
require 'NaServer'
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# "Style is what gives value and currency to thoughts." -- Schopenhauer
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
# connect to filer, assign object
|
|
23
|
+
class Filer
|
|
24
|
+
def initialize(filer, username, password, secure=true, type=filer)
|
|
25
|
+
@@filer = NaServer.new(filer, 1, 17) # specifies API version (1.17)
|
|
26
|
+
if secure
|
|
27
|
+
# connect via SSL/TLS
|
|
28
|
+
@@filer.set_transport_type("HTTPS")
|
|
29
|
+
raise 'insecure connection!' unless @@filer.use_https
|
|
30
|
+
@@filer.set_admin_user(username, password)
|
|
31
|
+
else
|
|
32
|
+
# non-encrypted connection
|
|
33
|
+
@@filer.set_admin_user(username, password)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# TODO: implement NaServer::set_server_type for NetApp DFM/Filer
|
|
37
|
+
# TODO: implement different login styles (usr,pwd - cert - ...)
|
|
38
|
+
# see also - NaServer::set_style
|
|
39
|
+
end
|
|
40
|
+
def self.is_secure?
|
|
41
|
+
https = @@filer.use_https
|
|
42
|
+
return true if https
|
|
43
|
+
end
|
|
44
|
+
def self.is_clustered?
|
|
45
|
+
sys_version = @@filer.invoke("system-get-version")
|
|
46
|
+
raise sys_version.results_reason \
|
|
47
|
+
if sys_version.results_status == 'failed'
|
|
48
|
+
return sys_version.child_get_string("version") =~ /Cluster-Mode/ ? true : false
|
|
49
|
+
end
|
|
50
|
+
def self.is_ha?
|
|
51
|
+
cf_status = @@filer.invoke("cf-status")
|
|
52
|
+
return false if cf_status.results_status == 'failed' \
|
|
53
|
+
and cf_status.results_reason == 'monitor not initialiazed'
|
|
54
|
+
raise cf_status.results_reason if cf_status.results_status == 'failed'
|
|
55
|
+
return result = cf_status.child_get_string("is-enabled")
|
|
56
|
+
end
|
|
57
|
+
def self.set_vfiler(vfilername)
|
|
58
|
+
return true if @@filer.set_vfiler(vfilername)
|
|
59
|
+
end
|
|
60
|
+
def self.info
|
|
61
|
+
system_info = @@filer.invoke("system-get-info")
|
|
62
|
+
raise system_info.results_reason \
|
|
63
|
+
if system_info.results_status == 'failed'
|
|
64
|
+
result = {}
|
|
65
|
+
system_info.child_get("system-info").children_get.each do |key|
|
|
66
|
+
result = {
|
|
67
|
+
systemid: key.child_get_string("system-id"),
|
|
68
|
+
systemname: key.child_get_string("system-name"),
|
|
69
|
+
systemmodel: key.child_get_string("system-model"),
|
|
70
|
+
systemmachinetype: key.child_get_string("system-machine-type"),
|
|
71
|
+
systemrev: key.child_get_string("system-revision"),
|
|
72
|
+
systemserialno: key.child_get_string("system-serial-number"),
|
|
73
|
+
vendorid: key.child_get_string("vendor-id"),
|
|
74
|
+
prodtype: key.child_get_string("prod-type"),
|
|
75
|
+
partnersystemid: key.child_get_string("partner-system-id"),
|
|
76
|
+
partnersystemname: key.child_get_string("partner-system-name"),
|
|
77
|
+
partnersystemserialno: key.child_get_string("partner-system-serial-number"),
|
|
78
|
+
backplanepartno: key.child_get_string("backplane-part-number"),
|
|
79
|
+
backplanerev: key.child_get_string("backplane-revision"),
|
|
80
|
+
processorsno: key.child_get_string("number-of-processors"),
|
|
81
|
+
memorysize: key.child_get_string("memory-size"),
|
|
82
|
+
cpuserialno: key.child_get_string("cpu-serial-number"),
|
|
83
|
+
cpurev: key.child_get_string("cpu-revision"),
|
|
84
|
+
cputype: key.child_get_string("cpu-processor-type"),
|
|
85
|
+
cpuid: key.child_get_string("cpu-processor-id"),
|
|
86
|
+
cpupartno: key.child_get_string("cpu-part-number"),
|
|
87
|
+
cpumicrocodeversion: key.child_get_string("cpu-microcode-version"),
|
|
88
|
+
cpufirmwarerel: key.child_get_string("cpu-firmware-release"),
|
|
89
|
+
cpuciobrevid: key.child_get_string("cpu-ciob-revision-id"),
|
|
90
|
+
supportsraidarray: key.child_get_string("supports-raid-array"),
|
|
91
|
+
controlleraddress: key.child_get_string("controller-address"),
|
|
92
|
+
boardtype: key.child_get_string("board-type"),
|
|
93
|
+
boardspeed: key.child_get_string("board-speed")
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
return result
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# function definitions to interface with NetApp filers
|
|
101
|
+
class Aggregate < Filer
|
|
102
|
+
def self.create(aggr, diskcount, raidtype="raid_dp")
|
|
103
|
+
aggr_create = @@filer.invoke("aggr-create",
|
|
104
|
+
"aggregate", aggr,
|
|
105
|
+
"disk-count", diskcount,
|
|
106
|
+
"raid-type", raidtype)
|
|
107
|
+
raise aggr_create.results_reason \
|
|
108
|
+
if aggr_create.results_status == 'failed'
|
|
109
|
+
return true
|
|
110
|
+
end
|
|
111
|
+
def self.purge(aggr)
|
|
112
|
+
aggr_destroy = @@filer.invoke("aggr-destroy",
|
|
113
|
+
"aggregate", aggr)
|
|
114
|
+
raise aggr_destroy.results_reason \
|
|
115
|
+
if aggr_destroy.results_status == 'failed'
|
|
116
|
+
return true
|
|
117
|
+
end
|
|
118
|
+
def self.add(aggr)
|
|
119
|
+
# implement me!
|
|
120
|
+
return false
|
|
121
|
+
end
|
|
122
|
+
def self.online(aggr)
|
|
123
|
+
aggr_online = @@filer.invoke("aggr-online",
|
|
124
|
+
"aggregate", aggr)
|
|
125
|
+
raise aggr_online.results_reason \
|
|
126
|
+
if aggr_online.results_status == 'failed'
|
|
127
|
+
return true
|
|
128
|
+
end
|
|
129
|
+
def self.offline(aggr)
|
|
130
|
+
aggr_offline = @@filer.invoke("aggr-offline",
|
|
131
|
+
"aggregate", aggr)
|
|
132
|
+
raise aggr_offline.results_reason \
|
|
133
|
+
if aggr_offline.results_status == 'failed'
|
|
134
|
+
return true
|
|
135
|
+
end
|
|
136
|
+
def self.rename(aggr, newname)
|
|
137
|
+
aggr_rename = @@filer.invoke("aggr-rename",
|
|
138
|
+
"aggregate", aggr,
|
|
139
|
+
"new-aggregate-name", newname)
|
|
140
|
+
raise aggr_rename.results_reason \
|
|
141
|
+
if aggr_rename.results_status == 'failed'
|
|
142
|
+
return true
|
|
143
|
+
end
|
|
144
|
+
def self.list
|
|
145
|
+
aggr_list_info = @@filer.invoke("aggr-list-info")
|
|
146
|
+
raise aggr_list_info.results_reason \
|
|
147
|
+
if aggr_list_info.results_status == 'failed'
|
|
148
|
+
result = []
|
|
149
|
+
aggr_list_info.child_get("aggregates").children_get.each do |key|
|
|
150
|
+
result << key.child_get_string("name")
|
|
151
|
+
end
|
|
152
|
+
return result
|
|
153
|
+
end
|
|
154
|
+
def self.info(aggr, verbose=true)
|
|
155
|
+
aggr_list_info = @@filer.invoke("aggr-list-info",
|
|
156
|
+
"aggregate", aggr,
|
|
157
|
+
"verbose", verbose)
|
|
158
|
+
raise aggr_list_info.results_reason \
|
|
159
|
+
if aggr_list_info.results_status == 'failed'
|
|
160
|
+
result = {}
|
|
161
|
+
aggr_list_info.child_get("aggregates").children_get.each do |key|
|
|
162
|
+
volumes = []
|
|
163
|
+
key.child_get("volumes").children_get.each { |vol|
|
|
164
|
+
volumes << vol.child_get_string("name")
|
|
165
|
+
}
|
|
166
|
+
plexes = {}
|
|
167
|
+
key.child_get("plexes").children_get.each { |plx|
|
|
168
|
+
plexes[name: plx.child_get_string("name")] = {
|
|
169
|
+
isonline: plx.child_get_string("is-online"),
|
|
170
|
+
isresyncing: plx.child_get_string("is-resyncing"),
|
|
171
|
+
resyncpercentage: plx.child_get_string("resyncing-percentage")
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
result = {
|
|
175
|
+
name: key.child_get_string("name"),
|
|
176
|
+
uuid: key.child_get_string("uuid"),
|
|
177
|
+
state: key.child_get_string("state"),
|
|
178
|
+
type: key.child_get_string("type"),
|
|
179
|
+
haslocalroot: key.child_get_string("has-local-root"),
|
|
180
|
+
haspartnerroot: key.child_get_string("has-partner-root"),
|
|
181
|
+
checksumstatus: key.child_get_string("checksum-status"),
|
|
182
|
+
isinconsistent: key.child_get_string("is-inconsistent"),
|
|
183
|
+
sizetotal: key.child_get_string("size-total"),
|
|
184
|
+
sizeused: key.child_get_string("size-used"),
|
|
185
|
+
sizeavail: key.child_get_string("size-available"),
|
|
186
|
+
sizepercentage: key.child_get_string("size-percentage-used"),
|
|
187
|
+
filestotal: key.child_get_string("files-total"),
|
|
188
|
+
filesused: key.child_get_string("files-used"),
|
|
189
|
+
isnaplock: key.child_get_string("is-snaplock"),
|
|
190
|
+
snaplocktype: key.child_get_string("snaplock-type"),
|
|
191
|
+
mirrorstatus: key.child_get_string("mirror-status"),
|
|
192
|
+
raidsize: key.child_get_string("raid-size"),
|
|
193
|
+
raidstatus: key.child_get_string("raid-status"),
|
|
194
|
+
diskcount: key.child_get_string("disk-count"),
|
|
195
|
+
volumecount: key.child_get_string("volume-count"),
|
|
196
|
+
volstripeddvcount: key.child_get_string("volume-count-striped-dv"),
|
|
197
|
+
volstripedmdvcount: key.child_get_string("volume-count-striped-mdv"),
|
|
198
|
+
volumes: volumes,
|
|
199
|
+
plexcount: key.child_get_string("plex-count"),
|
|
200
|
+
plexes: plexes
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
return result
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class Volume < Filer
|
|
208
|
+
def self.create(aggr, volname, size)
|
|
209
|
+
vol_create = @@filer.invoke("volume-create",
|
|
210
|
+
"containing-aggr-name", aggr,
|
|
211
|
+
"volume", volname,
|
|
212
|
+
"size", size)
|
|
213
|
+
raise vol_create.results_reason \
|
|
214
|
+
if vol_create.results_status == 'failed'
|
|
215
|
+
return true
|
|
216
|
+
end
|
|
217
|
+
def self.purge(volname)
|
|
218
|
+
vol_destroy = @@filer.invoke("volume-destroy",
|
|
219
|
+
"name", volname)
|
|
220
|
+
raise vol_destroy.results_reason \
|
|
221
|
+
if vol_destroy.results_status == 'failed'
|
|
222
|
+
return true
|
|
223
|
+
end
|
|
224
|
+
def self.add(volname)
|
|
225
|
+
# implement me!
|
|
226
|
+
return false
|
|
227
|
+
end
|
|
228
|
+
def self.online(volname)
|
|
229
|
+
vol_online = @@filer.invoke("volume-online",
|
|
230
|
+
"name", volname)
|
|
231
|
+
raise vol_online.results_reason \
|
|
232
|
+
if vol_online.results_status == 'failed'
|
|
233
|
+
return true
|
|
234
|
+
end
|
|
235
|
+
def self.offline(volname)
|
|
236
|
+
vol_offline = @@filer.invoke("volume-offline",
|
|
237
|
+
"name", volname)
|
|
238
|
+
raise vol_offline.results_reason \
|
|
239
|
+
if vol_offline.results_status == 'failed'
|
|
240
|
+
return true
|
|
241
|
+
end
|
|
242
|
+
def self.container(volname)
|
|
243
|
+
vol_container = @@filer.invoke("volume-container",
|
|
244
|
+
"volume", volname)
|
|
245
|
+
raise vol_container.results_reason \
|
|
246
|
+
if vol_container.results_status == 'failed'
|
|
247
|
+
return result = vol_container.child_get_string("containing-aggregate")
|
|
248
|
+
end
|
|
249
|
+
def self.rename(volname, newname)
|
|
250
|
+
vol_rename = @@filer.invoke("volume-rename",
|
|
251
|
+
"volume", volname,
|
|
252
|
+
"new-volume-name", newname)
|
|
253
|
+
raise vol_rename.results_reason \
|
|
254
|
+
if vol_rename.results_status == 'failed'
|
|
255
|
+
return true
|
|
256
|
+
end
|
|
257
|
+
def self.list
|
|
258
|
+
vol_list_info = @@filer.invoke("volume-list-info")
|
|
259
|
+
raise vol_list_info.results_reason \
|
|
260
|
+
if vol_list_info.results_status == 'failed'
|
|
261
|
+
result = []
|
|
262
|
+
vol_list_info.child_get("volumes").children_get.each do |key|
|
|
263
|
+
result << key.child_get_string("name")
|
|
264
|
+
end
|
|
265
|
+
return result
|
|
266
|
+
end
|
|
267
|
+
def self.info(volname, verbose=true)
|
|
268
|
+
vol_list_info = @@filer.invoke("volume-list-info",
|
|
269
|
+
"volume", volname,
|
|
270
|
+
"verbose", verbose)
|
|
271
|
+
raise vol_list_info.results_reason \
|
|
272
|
+
if vol_list_info.results_status == 'failed'
|
|
273
|
+
result = {}
|
|
274
|
+
vol_list_info.child_get("volumes").children_get.each do |key|
|
|
275
|
+
plexes = {}
|
|
276
|
+
key.child_get("plexes").children_get.each { |plx|
|
|
277
|
+
plexes[name: plx.child_get_string("name")] = {
|
|
278
|
+
isonline: plx.child_get_string("is-online"),
|
|
279
|
+
isresyncing: plx.child_get_string("is-resyncing"),
|
|
280
|
+
resyncpercentage: plx.child_get_string("resyncing-percentage")
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
result = {
|
|
284
|
+
name: key.child_get_string("name"),
|
|
285
|
+
uuid: key.child_get_string("uuid"),
|
|
286
|
+
type: key.child_get_string("type"),
|
|
287
|
+
containingaggr: key.child_get_string("containing-aggregate"),
|
|
288
|
+
sizetotal: key.child_get_string("size-total"),
|
|
289
|
+
sizeused: key.child_get_string("size-used"),
|
|
290
|
+
sizeavail: key.child_get_string("size-available"),
|
|
291
|
+
percentageused: key.child_get_string("percentage-used"),
|
|
292
|
+
filestotal: key.child_get_string("files-total"),
|
|
293
|
+
filesused: key.child_get_string("files-used"),
|
|
294
|
+
cloneparent: key.child_get_string("clone-parent"),
|
|
295
|
+
clonechildren: key.child_get_string("clone-children"),
|
|
296
|
+
ischecksumenabled: key.child_get_string("is-checksum-enabled"),
|
|
297
|
+
checksumstyle: key.child_get_string("checksum-style"),
|
|
298
|
+
compression: key.child_get_string("compression"),
|
|
299
|
+
isinconsistent: key.child_get_string("is-inconsistent"),
|
|
300
|
+
isinvalid: key.child_get_string("is-invalid"),
|
|
301
|
+
isunrecoverable: key.child_get_string("is-unrecoverable"),
|
|
302
|
+
iswraparound: key.child_get_string("is-wraparound"),
|
|
303
|
+
issnaplock: key.child_get_string("is-snaplock"),
|
|
304
|
+
expirydate: key.child_get_string("expiry-date"),
|
|
305
|
+
mirrorstatus: key.child_get_string("mirror-status"),
|
|
306
|
+
raidsize: key.child_get_string("raid-size"),
|
|
307
|
+
raidstatus: key.child_get_string("raid-status"),
|
|
308
|
+
owningvfiler: key.child_get_string("owning-vfiler"),
|
|
309
|
+
quotainit: key.child_get_string("quota-init"),
|
|
310
|
+
remotelocation: key.child_get_string("remote-location"),
|
|
311
|
+
reserve: key.child_get_string("reserve"),
|
|
312
|
+
reserverequired: key.child_get_string("reserve-required"),
|
|
313
|
+
reserveused: key.child_get_string("reserve-used"),
|
|
314
|
+
reservedusedact: key.child_get_string("reserve-used-actual"),
|
|
315
|
+
snaplocktype: key.child_get_string("snaplock-type"),
|
|
316
|
+
snapshotblkreserved: key.child_get_string("snapshot-blocks-reserved"),
|
|
317
|
+
snapshotperreserved: key.child_get_string("snapshot-percent-reserved"),
|
|
318
|
+
spacereserveenabled: key.child_get_string("space-reserve-enabled"),
|
|
319
|
+
spacereserve: key.child_get_string("space-reserve"),
|
|
320
|
+
diskcount: key.child_get_string("disk-count"),
|
|
321
|
+
plexcount: key.child_get_string("plex-count"),
|
|
322
|
+
plexes: plexes
|
|
323
|
+
# add SIS and snaplock data
|
|
324
|
+
}
|
|
325
|
+
end
|
|
326
|
+
return result
|
|
327
|
+
end
|
|
328
|
+
def self.size(volname)
|
|
329
|
+
vol_size = @@filer.invoke("volume-size",
|
|
330
|
+
"volume", volname)
|
|
331
|
+
raise vol_size.results_reason \
|
|
332
|
+
if vol_size.results_status == 'failed'
|
|
333
|
+
return result = vol_size.child_get_string("volume-size")
|
|
334
|
+
end
|
|
335
|
+
def self.resize(volname, newsize)
|
|
336
|
+
vol_resize = @@filer.invoke("volume-size",
|
|
337
|
+
"volume", volname,
|
|
338
|
+
"new-size", newsize)
|
|
339
|
+
raise vol_resize.results_reason \
|
|
340
|
+
if vol_resize.results_status == 'failed'
|
|
341
|
+
return true
|
|
342
|
+
end
|
|
343
|
+
# TODO:
|
|
344
|
+
# implement volume-move-*
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
class Snapshot < Filer
|
|
348
|
+
def self.create(name, volname)
|
|
349
|
+
snapshot_create = @@filer.invoke("snapshot-create",
|
|
350
|
+
"snapshot", name,
|
|
351
|
+
"volume", volname)
|
|
352
|
+
raise snapshot_create.results_reason \
|
|
353
|
+
if snapshot_create.results_status == 'failed'
|
|
354
|
+
return true
|
|
355
|
+
end
|
|
356
|
+
def self.purge(name, volname)
|
|
357
|
+
snapshot_delete = @@filer.invoke("snapshot-delete",
|
|
358
|
+
"snapshot", name,
|
|
359
|
+
"volume", volname)
|
|
360
|
+
raise snapshot_delete.results_reason \
|
|
361
|
+
if snapshot_delete.results_status == 'failed'
|
|
362
|
+
return true
|
|
363
|
+
end
|
|
364
|
+
def self.rename(volume, name, newname)
|
|
365
|
+
snapshot_rename = @@filer.invoke("snapshot-rename",
|
|
366
|
+
"volume", volname,
|
|
367
|
+
"current-name", name,
|
|
368
|
+
"new-name", newname)
|
|
369
|
+
raise snapshot_rename.results_reason \
|
|
370
|
+
if snapshot_rename.results_status == 'failed'
|
|
371
|
+
return true
|
|
372
|
+
end
|
|
373
|
+
def self.delta(snap1, snap2, volname)
|
|
374
|
+
snapshot_delta = @@filer.invoke("snapshot-delta-info",
|
|
375
|
+
"volume", volname,
|
|
376
|
+
"snapshot1", snap1,
|
|
377
|
+
"snapshot2", snap2)
|
|
378
|
+
raise snapshot_delta.results_reason \
|
|
379
|
+
if snapshot_delta.results_status == 'failed'
|
|
380
|
+
result = {}
|
|
381
|
+
return result = {
|
|
382
|
+
consumedsize: snapshot_delta.child_get_string("consumed-size"),
|
|
383
|
+
elapsedtime: snapshot_delta.child_get_string("elapsed-time")
|
|
384
|
+
}
|
|
385
|
+
end
|
|
386
|
+
def self.delta_to_volume(snap, volname)
|
|
387
|
+
snapshot_delta = @@filer.invoke("snapshot-delta-info",
|
|
388
|
+
"volume", volname,
|
|
389
|
+
"snapshot1", snap)
|
|
390
|
+
raise snapshot_delta.results_reason \
|
|
391
|
+
if snapshot_delta.results_status == 'failed'
|
|
392
|
+
result = {}
|
|
393
|
+
return result = {
|
|
394
|
+
consumedsize: snapshot_delta.child_get_string("consumed-size"),
|
|
395
|
+
elapsedtime: snapshot_delta.child_get_string("elapsed-time")
|
|
396
|
+
}
|
|
397
|
+
end
|
|
398
|
+
def self.reserve(volname)
|
|
399
|
+
snapshot_reserve = @@filer.invoke("snapshot-get-reserve",
|
|
400
|
+
"volume", volname)
|
|
401
|
+
raise snapshot_reserve.results_reason \
|
|
402
|
+
if snapshot_reserve.results_status == 'failed'
|
|
403
|
+
result = {}
|
|
404
|
+
return result = {
|
|
405
|
+
blocksreserved: snapshot_reserve.child_get_string("blocks-reserved"),
|
|
406
|
+
percentreserved: snapshot_reserve.child_get_string("percent-reserved")
|
|
407
|
+
}
|
|
408
|
+
end
|
|
409
|
+
def self.schedule(volname)
|
|
410
|
+
snapshot_schedule = @@filer.invoke("snapshot-get-schedule",
|
|
411
|
+
"volume", volname)
|
|
412
|
+
raise snapshot_schedule.results_reason \
|
|
413
|
+
if snapshot_schedule.results_status == 'failed'
|
|
414
|
+
result = {}
|
|
415
|
+
return result = {
|
|
416
|
+
days: snapshot_schedule.child_get_string("days"),
|
|
417
|
+
hours: snapshot_schedule.child_get_string("hours"),
|
|
418
|
+
minutes: snapshot_schedule.child_get_string("minutes"),
|
|
419
|
+
weeks: snapshot_schedule.child_get_string("weeks"),
|
|
420
|
+
whichhours: snapshot_schedule.child_get_string("which-hours"),
|
|
421
|
+
whichminutes: snapshot_schedule.child_get_string("which-minutes")
|
|
422
|
+
}
|
|
423
|
+
end
|
|
424
|
+
def self.info(volname)
|
|
425
|
+
snapshot_info = @@filer.invoke("snapshot-list-info",
|
|
426
|
+
"volume", volname)
|
|
427
|
+
raise snapshot_info.results_reason \
|
|
428
|
+
if snapshot_info.results_status == 'failed'
|
|
429
|
+
result = {}
|
|
430
|
+
snapshot_info.child_get("snapshots").children_get.each do |key|
|
|
431
|
+
result = {
|
|
432
|
+
name: key.child_get_string("name"),
|
|
433
|
+
accesstime: key.child_get_string("access-time"),
|
|
434
|
+
busy: key.child_get_string("busy"),
|
|
435
|
+
containslunclones: key.child_get_string("contains-lun-clones"),
|
|
436
|
+
cumpercentageblockstotal: key.child_get_string("cumulative-percentage-of-total-blocks"),
|
|
437
|
+
cumpercentageblocksused: key.child_get_string("cumulative-percentage-of-used-blocks"),
|
|
438
|
+
cumtotal: key.child_get_string("cumulative-total"),
|
|
439
|
+
dependency: key.child_get_string("dependency"),
|
|
440
|
+
percentageblockstotal: key.child_get_string("percentage-of-total-blocks"),
|
|
441
|
+
percentageblocksused: key.child_get_string("percentage-of-used-blocks"),
|
|
442
|
+
total: key.child_get_string("total")
|
|
443
|
+
}
|
|
444
|
+
end
|
|
445
|
+
return result
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
class Qtree < Filer
|
|
450
|
+
def self.create(qtreename, volname)
|
|
451
|
+
qtree_create = @@filer.invoke("qtree-create",
|
|
452
|
+
"qtree", qtreename,
|
|
453
|
+
"volume", volname)
|
|
454
|
+
raise qtree_create.results_reason \
|
|
455
|
+
if qtree_create.results_status == 'failed'
|
|
456
|
+
return true
|
|
457
|
+
end
|
|
458
|
+
def self.purge(qtreename)
|
|
459
|
+
qtree_delete = @@filer.invoke("qtree-delete",
|
|
460
|
+
"qtree", qtreename)
|
|
461
|
+
raise qtree_delete.results_reason \
|
|
462
|
+
if qtree_delete.results_status == 'failed'
|
|
463
|
+
return true
|
|
464
|
+
end
|
|
465
|
+
def self.list
|
|
466
|
+
qtree_list = @@filer.invoke("qtree-list")
|
|
467
|
+
raise qtree_list.results_reason \
|
|
468
|
+
if qtree_list.results_status == 'failed'
|
|
469
|
+
result = {}
|
|
470
|
+
qtree_list.child_get("qtrees").children_get.each do |key|
|
|
471
|
+
result[qtree: key.child_get_string("qtree")] = {
|
|
472
|
+
volume: key.child_get_string("volume")
|
|
473
|
+
}
|
|
474
|
+
end
|
|
475
|
+
return result
|
|
476
|
+
end
|
|
477
|
+
def self.info(volname)
|
|
478
|
+
qtree_list = @@filer.invoke("qtree-list",
|
|
479
|
+
"volume", volname)
|
|
480
|
+
raise qtree_list.results_reason \
|
|
481
|
+
if qtree_list.results_status == 'failed'
|
|
482
|
+
result = {}
|
|
483
|
+
qtree_list.child_get("qtrees").children_get.each do |key|
|
|
484
|
+
result[id: key.child_get_string("id")] = {
|
|
485
|
+
qtree: key.child_get_string("qtree"),
|
|
486
|
+
volume: key.child_get_string("volume"),
|
|
487
|
+
status: key.child_get_string("status"),
|
|
488
|
+
oplocks: key.child_get_string("oplocks"),
|
|
489
|
+
owningvfiler: key.child_get_string("owning-vfiler"),
|
|
490
|
+
securitystyle: key.child_get_string("security-style")
|
|
491
|
+
}
|
|
492
|
+
end
|
|
493
|
+
return result
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
class Quota < Filer
|
|
498
|
+
def self.create(qtreename="", volname, path, quotasize, type)
|
|
499
|
+
quota_create = @@filer.invoke("quota-add-entry",
|
|
500
|
+
"qtree", qtreename,
|
|
501
|
+
"volume", volname,
|
|
502
|
+
"quota-target", path,
|
|
503
|
+
"soft-disk-limit", quotasize,
|
|
504
|
+
"quota-type", type)
|
|
505
|
+
raise quota_create.results_reason \
|
|
506
|
+
if quota_create.results_status == 'failed'
|
|
507
|
+
return true
|
|
508
|
+
end
|
|
509
|
+
def self.purge(qtreename="", volname, path, type)
|
|
510
|
+
quota_delete = @@filer.invoke("quota-delete-entry",
|
|
511
|
+
"qtree", qtreename,
|
|
512
|
+
"volume", volname,
|
|
513
|
+
"quota-target", path,
|
|
514
|
+
"quota-type", type)
|
|
515
|
+
raise quota_delete.results_reason \
|
|
516
|
+
if quota_delete.results_status == 'failed'
|
|
517
|
+
return true
|
|
518
|
+
end
|
|
519
|
+
def self.on(volname)
|
|
520
|
+
quota_on = @@filer.invoke("quota-on",
|
|
521
|
+
"volume", volname)
|
|
522
|
+
raise quota_on.results_reason \
|
|
523
|
+
if quota_on.results_status == 'failed'
|
|
524
|
+
return true
|
|
525
|
+
end
|
|
526
|
+
def self.off(volname)
|
|
527
|
+
quota_off = @@filer.invoke("quota-off",
|
|
528
|
+
"volume", volname)
|
|
529
|
+
raise quota_off.results_reason \
|
|
530
|
+
if quota_off.results_status == 'failed'
|
|
531
|
+
return true
|
|
532
|
+
end
|
|
533
|
+
def self.get_entry(qtreename, volname, path, type)
|
|
534
|
+
quota_get_entry = @@filer.invoke("quota-get-entry",
|
|
535
|
+
"qtree", qtreename,
|
|
536
|
+
"volume", volname,
|
|
537
|
+
"quota-target", path,
|
|
538
|
+
"quota-type", type)
|
|
539
|
+
raise quota_get_entry.results_reason \
|
|
540
|
+
if quota_get_entry.results_status == 'failed'
|
|
541
|
+
return true
|
|
542
|
+
end
|
|
543
|
+
def self.list
|
|
544
|
+
quota_list_entries = @@filer.invoke("quota-list-entries")
|
|
545
|
+
raise quota_list_entries.results_reason \
|
|
546
|
+
if quota_list_entries.results_status == 'failed'
|
|
547
|
+
result = {}
|
|
548
|
+
quota_list_entries.child_get("quota-entries").children_get.each do |key|
|
|
549
|
+
result[qtree: key.child_get_string("qtree")] = {
|
|
550
|
+
line: key.child_get_string("line"),
|
|
551
|
+
volume: key.child_get_string("volume"),
|
|
552
|
+
quotaerror: key.child_get_string("quota-error"),
|
|
553
|
+
quotatarget: key.child_get_string("quota-target"),
|
|
554
|
+
quotatype: key.child_get_string("quota-type")
|
|
555
|
+
}
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
def self.status(volname)
|
|
559
|
+
quota_status = @@filer.invoke("quota-status",
|
|
560
|
+
"volume", volname)
|
|
561
|
+
raise quota_status.results_reason \
|
|
562
|
+
if quota_status.results_status == 'failed'
|
|
563
|
+
return result = quota_status.child_get_string("status")
|
|
564
|
+
end
|
|
565
|
+
# TODO: no longer supported in NMSDK API as it seems
|
|
566
|
+
#def self.user(userid, username, usertype)
|
|
567
|
+
# quota_user = @@filer.invoke("quota-user",
|
|
568
|
+
# "quota-user-id", userid,
|
|
569
|
+
# "quota-user-name", username,
|
|
570
|
+
# "quota-user-type", usertype)
|
|
571
|
+
# if quota_user.results_status == 'failed'
|
|
572
|
+
# raise quota_user.results_reason
|
|
573
|
+
# end
|
|
574
|
+
#end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
class NFS < Filer
|
|
578
|
+
def self.on
|
|
579
|
+
nfs_on = @@filer.invoke("nfs-enable")
|
|
580
|
+
raise nfs_on.results_reason \
|
|
581
|
+
if nfs_on.results_status == 'failed'
|
|
582
|
+
return true
|
|
583
|
+
end
|
|
584
|
+
def self.off
|
|
585
|
+
nfs_off = @@filer.invoke("nfs-disable")
|
|
586
|
+
raise nfs_off.results_reason \
|
|
587
|
+
if nfs_off.results_status == 'failed'
|
|
588
|
+
return true
|
|
589
|
+
end
|
|
590
|
+
def self.add_export(pathname, type, anon=false, nosuid=false, allhosts=false, exports)
|
|
591
|
+
#
|
|
592
|
+
# - type = read-only || read-write || root
|
|
593
|
+
# - exports = string (hostname, IP, subnet [CIDR])
|
|
594
|
+
|
|
595
|
+
raise "unkown argument in type" unless type == "read-only" or \
|
|
596
|
+
type == "read-write" or \
|
|
597
|
+
type == "root"
|
|
598
|
+
raise "empty pathname" if pathname.empty?
|
|
599
|
+
|
|
600
|
+
nfs_exports_rule_info = NaElement.new("exports-rule-info")
|
|
601
|
+
nfs_exports_rule_info.child_add_string("anon", anon) if anon
|
|
602
|
+
nfs_exports_rule_info.child_add_string("nosuid", nosuid) if nosuid
|
|
603
|
+
nfs_exports_rule_info.child_add_string("pathname", pathname)
|
|
604
|
+
|
|
605
|
+
nfs_exports = NaElement.new(type)
|
|
606
|
+
nfs_exports_host = NaElement.new("exports-hostname-info")
|
|
607
|
+
nfs_exports_host.child_add_string("all-hosts", true) if allhosts == true
|
|
608
|
+
nfs_exports_host.child_add_string("name", exports) if exports
|
|
609
|
+
|
|
610
|
+
nfs_exports.child_add(nfs_exports_host)
|
|
611
|
+
nfs_exports_rule_info.child_add(nfs_exports)
|
|
612
|
+
|
|
613
|
+
nfs_rules = NaElement.new("rules")
|
|
614
|
+
nfs_rules.child_add(nfs_exports_rule_info)
|
|
615
|
+
|
|
616
|
+
nfs_exports_invoke = NaElement.new("nfs-exportfs-append-rules")
|
|
617
|
+
nfs_exports_invoke.child_add(nfs_rules)
|
|
618
|
+
nfs_exports_invoke.child_add_string("verbose", true)
|
|
619
|
+
|
|
620
|
+
nfs_add_export = @@filer.invoke_elem(nfs_exports_invoke)
|
|
621
|
+
raise nfs_add_export.results_reason \
|
|
622
|
+
if nfs_add_export.results_status == 'failed'
|
|
623
|
+
return true
|
|
624
|
+
end
|
|
625
|
+
def self.del_export(pathname)
|
|
626
|
+
nfs_exports_path_del = NaElement.new("pathname-info")
|
|
627
|
+
nfs_exports_path_del.child_add_string("name", pathname)
|
|
628
|
+
|
|
629
|
+
nfs_pathnames = NaElement.new("pathnames")
|
|
630
|
+
nfs_pathnames.child_add(nfs_exports_path_del)
|
|
631
|
+
|
|
632
|
+
nfs_exports_invoke = NaElement.new("nfs-exportfs-delete-rules")
|
|
633
|
+
nfs_exports_invoke.child_add(nfs_pathnames)
|
|
634
|
+
nfs_exports_invoke.child_add_string("verbose", true)
|
|
635
|
+
|
|
636
|
+
nfs_del_export = @@filer.invoke_elem(nfs_exports_invoke)
|
|
637
|
+
raise nfs_del_export.results_reason \
|
|
638
|
+
if nfs_del_export.results_status == 'failed'
|
|
639
|
+
return true
|
|
640
|
+
end
|
|
641
|
+
def self.status
|
|
642
|
+
nfs_status = @@filer.invoke("nfs-status")
|
|
643
|
+
raise nfs_status.results_reason \
|
|
644
|
+
if nfs_status.results_status == 'failed'
|
|
645
|
+
return result = {
|
|
646
|
+
isdrained: nfs_status.child_get_string("is-drained"),
|
|
647
|
+
isenabled: nfs_status.child_get_string("is-enabled")
|
|
648
|
+
}
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
class Vfiler < Filer
|
|
653
|
+
def self.create(name, ipaddr, storage)
|
|
654
|
+
vfiler_create = @@filer.invoke("vfiler-create",
|
|
655
|
+
"vfiler", name,
|
|
656
|
+
"ip-addresses", ipaddr,
|
|
657
|
+
"storage-units", storage)
|
|
658
|
+
raise vfiler_create.results_reason \
|
|
659
|
+
if vfiler_create.results_status == 'failed'
|
|
660
|
+
return true
|
|
661
|
+
end
|
|
662
|
+
def self.purge(name)
|
|
663
|
+
vfiler_delete = @@filer.invoke("vfiler-destroy",
|
|
664
|
+
"vfiler", name)
|
|
665
|
+
raise vfiler_delete.results_reason \
|
|
666
|
+
if vfiler_delete.results_status == 'failed'
|
|
667
|
+
return true
|
|
668
|
+
end
|
|
669
|
+
def self.add_storage(name, storage)
|
|
670
|
+
vfiler_add_stroage = @@filer.invoke("vfiler-add-storage",
|
|
671
|
+
"vfiler", name,
|
|
672
|
+
"storage-path", storage)
|
|
673
|
+
raise vfiler_add_stroage.results_reason \
|
|
674
|
+
if vfiler_add_stroage.results_status == 'failed'
|
|
675
|
+
return true
|
|
676
|
+
end
|
|
677
|
+
# vfiler-add-ipaddress, setup, start, stop, migrate, status, list
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
class Diag < Filer
|
|
681
|
+
def self.status
|
|
682
|
+
# "Overall system health (ok,ok-with-suppressed,degraded,
|
|
683
|
+
# unreachable) as determined by the diagnosis framework"
|
|
684
|
+
diag_status = @@filer.invoke("diagnosis-status-get")
|
|
685
|
+
raise diag_status.results_reason \
|
|
686
|
+
if diag_status.results_status == 'failed'
|
|
687
|
+
stat = diag_status.child_get("attributes").children_get
|
|
688
|
+
stat.each { |k| return k.child_get_string("status") }
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
#EOF
|
metadata
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: NetApp.rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Aaron Zauner
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Rubyesque library to interface with NetApp Filers (via NMSDK)
|
|
15
|
+
email: azet@azet.org
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- lib/netapp.rb
|
|
21
|
+
homepage: https://github.com/Gregor-Mendel-Institute/NetApp.rb
|
|
22
|
+
licenses:
|
|
23
|
+
- MIT
|
|
24
|
+
post_install_message:
|
|
25
|
+
rdoc_options: []
|
|
26
|
+
require_paths:
|
|
27
|
+
- lib
|
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
29
|
+
none: false
|
|
30
|
+
requirements:
|
|
31
|
+
- - ! '>='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
|
+
none: false
|
|
36
|
+
requirements:
|
|
37
|
+
- - ! '>='
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
requirements:
|
|
41
|
+
- Proprietary NMSDK as provided by NetApp
|
|
42
|
+
rubyforge_project:
|
|
43
|
+
rubygems_version: 1.8.23
|
|
44
|
+
signing_key:
|
|
45
|
+
specification_version: 3
|
|
46
|
+
summary: NetApp.rb
|
|
47
|
+
test_files: []
|
|
48
|
+
has_rdoc:
|