1 /*
2 * storage_backend_zfs.c: storage backend for ZFS handling
3 *
4 * Copyright (C) 2014 Roman Bogorodskiy
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #include <config.h>
23
24 #include "viralloc.h"
25 #include "virerror.h"
26 #include "virfile.h"
27 #include "storage_backend_zfs.h"
28 #include "virlog.h"
29 #include "virstring.h"
30 #include "storage_util.h"
31
32 #define VIR_FROM_THIS VIR_FROM_STORAGE
33
34 VIR_LOG_INIT("storage.storage_backend_zfs");
35
36 #define ZFS "zfs"
37 #define ZPOOL "zpool"
38
39 /*
40 * Some common flags of zfs and zpool commands we use:
41 * -H -- don't print headers and separate fields by tab
42 * -p -- show exact numbers instead of human-readable, i.e.
43 * for size, show just a number instead of 2G etc
44 */
45
46 /**
47 * virStorageBackendZFSVolModeNeeded:
48 *
49 * Checks if it's necessary to specify 'volmode' (i.e. that
50 * we're working with BSD ZFS implementation).
51 *
52 * Returns 1 if 'volmode' is need, 0 if not needed, -1 on error
53 */
54 static int
virStorageBackendZFSVolModeNeeded(void)55 virStorageBackendZFSVolModeNeeded(void)
56 {
57 int ret = -1, exit_code = -1;
58 g_autofree char *error = NULL;
59 g_autoptr(virCommand) cmd = NULL;
60
61 /* 'zfs get' without arguments prints out
62 * usage information to stderr, including
63 * list of supported options, and exits with
64 * exit code 2
65 */
66 cmd = virCommandNewArgList(ZFS, "get", NULL);
67 virCommandAddEnvString(cmd, "LC_ALL=C");
68 virCommandSetErrorBuffer(cmd, &error);
69
70 ret = virCommandRun(cmd, &exit_code);
71 if ((ret < 0) || (exit_code != 2)) {
72 VIR_WARN("Command 'zfs get' either failed "
73 "to run or exited with unexpected status");
74 return ret;
75 }
76
77 if (strstr(error, " volmode "))
78 return 1;
79 else
80 return 0;
81 }
82
83 static int
virStorageBackendZFSCheckPool(virStoragePoolObj * pool G_GNUC_UNUSED,bool * isActive)84 virStorageBackendZFSCheckPool(virStoragePoolObj *pool G_GNUC_UNUSED,
85 bool *isActive)
86 {
87 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
88 g_autofree char *devpath = NULL;
89
90 devpath = g_strdup_printf("/dev/zvol/%s", def->source.name);
91 *isActive = virFileIsDir(devpath);
92
93 return 0;
94 }
95
96 static int
virStorageBackendZFSParseVol(virStoragePoolObj * pool,virStorageVolDef * vol,const char * volume_string)97 virStorageBackendZFSParseVol(virStoragePoolObj *pool,
98 virStorageVolDef *vol,
99 const char *volume_string)
100 {
101 int ret = -1;
102 char *vol_name;
103 bool is_new_vol = false;
104 virStorageVolDef *volume = NULL;
105 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
106 g_auto(GStrv) tokens = NULL;
107 char *tmp;
108
109 if (!(tokens = g_strsplit(volume_string, "\t", 0)))
110 return -1;
111
112 if (g_strv_length(tokens) != 3)
113 goto cleanup;
114
115 vol_name = tokens[0];
116 if ((tmp = strrchr(vol_name, '/')))
117 vol_name = tmp + 1;
118
119 if (vol == NULL)
120 volume = virStorageVolDefFindByName(pool, vol_name);
121 else
122 volume = vol;
123
124 if (volume == NULL) {
125 volume = g_new0(virStorageVolDef, 1);
126
127 is_new_vol = true;
128 volume->type = VIR_STORAGE_VOL_BLOCK;
129
130 volume->name = g_strdup(vol_name);
131 }
132
133 if (!volume->key)
134 volume->key = g_strdup(tokens[0]);
135
136 if (volume->target.path == NULL) {
137 volume->target.path = g_strdup_printf("%s/%s", def->target.path,
138 volume->name);
139 }
140
141 if (virStrToLong_ull(tokens[1], NULL, 10, &volume->target.capacity) < 0) {
142 virReportError(VIR_ERR_INTERNAL_ERROR,
143 "%s", _("malformed volsize reported"));
144 goto cleanup;
145 }
146
147 if (virStrToLong_ull(tokens[2], NULL, 10, &volume->target.allocation) < 0) {
148 virReportError(VIR_ERR_INTERNAL_ERROR,
149 "%s", _("malformed refreservation reported"));
150 goto cleanup;
151 }
152
153 if (volume->target.allocation < volume->target.capacity)
154 volume->target.sparse = true;
155
156 if (is_new_vol && virStoragePoolObjAddVol(pool, volume) < 0)
157 goto cleanup;
158 volume = NULL;
159
160 ret = 0;
161 cleanup:
162 if (is_new_vol)
163 virStorageVolDefFree(volume);
164 return ret;
165 }
166
167 static int
virStorageBackendZFSFindVols(virStoragePoolObj * pool,virStorageVolDef * vol)168 virStorageBackendZFSFindVols(virStoragePoolObj *pool,
169 virStorageVolDef *vol)
170 {
171 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
172 size_t i;
173 g_auto(GStrv) lines = NULL;
174 g_autoptr(virCommand) cmd = NULL;
175 g_autofree char *volumes_list = NULL;
176
177 /**
178 * $ zfs list -Hp -t volume -o name,volsize -r test
179 * test/vol1 5368709120
180 * test/vol3 1073741824
181 * test/vol4 1572864000
182 * $
183 *
184 * Arguments description:
185 * -t volume -- we want to see only volumes
186 * -o name,volsize -- limit output to name and volume size
187 * -r -- we want to see all the childer of our pool
188 */
189 cmd = virCommandNewArgList(ZFS,
190 "list", "-Hp",
191 "-t", "volume", "-r",
192 "-o", "name,volsize,refreservation",
193 def->source.name,
194 NULL);
195 virCommandSetOutputBuffer(cmd, &volumes_list);
196 if (virCommandRun(cmd, NULL) < 0)
197 return -1;
198
199 if (!(lines = g_strsplit(volumes_list, "\n", 0)))
200 return -1;
201
202 for (i = 0; lines[i]; i++) {
203 if (STREQ(lines[i], ""))
204 continue;
205
206 if (virStorageBackendZFSParseVol(pool, vol, lines[i]) < 0)
207 continue;
208 }
209
210 return 0;
211 }
212
213 static int
virStorageBackendZFSRefreshPool(virStoragePoolObj * pool G_GNUC_UNUSED)214 virStorageBackendZFSRefreshPool(virStoragePoolObj *pool G_GNUC_UNUSED)
215 {
216 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
217 char *zpool_props = NULL;
218 size_t i;
219 g_autoptr(virCommand) cmd = NULL;
220 g_auto(GStrv) lines = NULL;
221 g_autofree char *name = g_strdup(def->source.name);
222 char *tmp;
223
224 /**
225 * $ zpool get -Hp health,size,free,allocated test
226 * test health ONLINE -
227 * test size 199715979264 -
228 * test free 198899976704 -
229 * test allocated 816002560 -
230 * $
231 *
232 * Here we just provide a list of properties we want to see
233 */
234 if ((tmp = strchr(name, '/')))
235 *tmp = '\0';
236
237 cmd = virCommandNewArgList(ZPOOL,
238 "get", "-Hp",
239 "health,size,free,allocated",
240 name,
241 NULL);
242 virCommandSetOutputBuffer(cmd, &zpool_props);
243 if (virCommandRun(cmd, NULL) < 0)
244 goto cleanup;
245
246 if (!(lines = g_strsplit(zpool_props, "\n", 0)))
247 goto cleanup;
248
249 for (i = 0; lines[i]; i++) {
250 g_auto(GStrv) tokens = NULL;
251 char *prop_name;
252
253 if (STREQ(lines[i], ""))
254 continue;
255
256 if (!(tokens = g_strsplit(lines[i], "\t", 0)))
257 goto cleanup;
258
259 if (g_strv_length(tokens) != 4)
260 continue;
261
262 prop_name = tokens[1];
263
264 if (STREQ(prop_name, "free") || STREQ(prop_name, "size") ||
265 STREQ(prop_name, "allocated")) {
266 unsigned long long value;
267 if (virStrToLong_ull(tokens[2], NULL, 10, &value) < 0)
268 goto cleanup;
269
270 if (STREQ(prop_name, "free"))
271 def->available = value;
272 else if (STREQ(prop_name, "size"))
273 def->capacity = value;
274 else if (STREQ(prop_name, "allocated"))
275 def->allocation = value;
276 }
277 }
278
279 /* Obtain a list of volumes */
280 if (virStorageBackendZFSFindVols(pool, NULL) < 0)
281 goto cleanup;
282
283 cleanup:
284 VIR_FREE(zpool_props);
285
286 return 0;
287 }
288
289 static int
virStorageBackendZFSCreateVol(virStoragePoolObj * pool,virStorageVolDef * vol)290 virStorageBackendZFSCreateVol(virStoragePoolObj *pool,
291 virStorageVolDef *vol)
292 {
293 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
294 int volmode_needed = -1;
295 g_autoptr(virCommand) cmd = NULL;
296
297 if (vol->target.encryption != NULL) {
298 virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
299 "%s", _("storage pool does not support encrypted "
300 "volumes"));
301 return -1;
302 }
303
304 vol->type = VIR_STORAGE_VOL_BLOCK;
305
306 VIR_FREE(vol->target.path);
307 vol->target.path = g_strdup_printf("%s/%s", def->target.path, vol->name);
308
309 vol->key = g_strdup(vol->target.path);
310
311 volmode_needed = virStorageBackendZFSVolModeNeeded();
312 if (volmode_needed < 0)
313 return -1;
314 /**
315 * $ zfs create -o volmode=dev -V 10240K test/volname
316 * $ zfs create -o volmode=dev -s -V 10240K test/volname
317 * $ zfs create -o volmode=dev -s -o refreservation=1024K -V 10240K test/volname
318 *
319 * -o volmode=dev -- we want to get volumes exposed as cdev
320 * devices. If we don't specify that zfs
321 * will lookup vfs.zfs.vol.mode sysctl value
322 * -s -- create a sparse volume
323 * -o refreservation -- reserve the specified amount of space
324 * -V -- tells to create a volume with the specified size
325 */
326 cmd = virCommandNewArgList(ZFS, "create", NULL);
327 if (volmode_needed)
328 virCommandAddArgList(cmd, "-o", "volmode=dev", NULL);
329 if (vol->target.capacity != vol->target.allocation) {
330 virCommandAddArg(cmd, "-s");
331 if (vol->target.allocation > 0) {
332 virCommandAddArg(cmd, "-o");
333 virCommandAddArgFormat(cmd, "refreservation=%lluK",
334 VIR_DIV_UP(vol->target.allocation, 1024));
335 }
336 vol->target.sparse = true;
337 }
338 virCommandAddArg(cmd, "-V");
339 virCommandAddArgFormat(cmd, "%lluK",
340 VIR_DIV_UP(vol->target.capacity, 1024));
341 virCommandAddArgFormat(cmd, "%s/%s", def->source.name, vol->name);
342
343 if (virCommandRun(cmd, NULL) < 0)
344 return -1;
345
346 if (virStorageBackendZFSFindVols(pool, vol) < 0)
347 return -1;
348
349 return 0;
350 }
351
352 static int
virStorageBackendZFSDeleteVol(virStoragePoolObj * pool,virStorageVolDef * vol,unsigned int flags)353 virStorageBackendZFSDeleteVol(virStoragePoolObj *pool,
354 virStorageVolDef *vol,
355 unsigned int flags)
356 {
357 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
358 g_autoptr(virCommand) destroy_cmd = NULL;
359
360 virCheckFlags(0, -1);
361
362 destroy_cmd = virCommandNewArgList(ZFS, "destroy", NULL);
363
364 virCommandAddArgFormat(destroy_cmd, "%s/%s",
365 def->source.name, vol->name);
366
367 return virCommandRun(destroy_cmd, NULL);
368 }
369
370 static int
virStorageBackendZFSBuildPool(virStoragePoolObj * pool,unsigned int flags)371 virStorageBackendZFSBuildPool(virStoragePoolObj *pool,
372 unsigned int flags)
373 {
374 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
375 size_t i;
376 g_autoptr(virCommand) cmd = NULL;
377 int ret = -1;
378 char *tmp;
379
380 virCheckFlags(0, -1);
381
382 tmp = strstr(def->source.name, "/");
383 if (tmp) {
384 cmd = virCommandNewArgList(ZFS, "create", "-o", "mountpoint=none",
385 def->source.name, NULL);
386 } else {
387 if (def->source.ndevice == 0) {
388 virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
389 "%s", _("missing source devices"));
390 return -1;
391 }
392
393 cmd = virCommandNewArgList(ZPOOL, "create",
394 def->source.name, NULL);
395
396 for (i = 0; i < def->source.ndevice; i++)
397 virCommandAddArg(cmd, def->source.devices[i].path);
398 }
399
400 virObjectUnlock(pool);
401 ret = virCommandRun(cmd, NULL);
402 virObjectLock(pool);
403
404 return ret;
405 }
406
407 static int
virStorageBackendZFSDeletePool(virStoragePoolObj * pool,unsigned int flags)408 virStorageBackendZFSDeletePool(virStoragePoolObj *pool,
409 unsigned int flags)
410 {
411 virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
412 g_autoptr(virCommand) cmd = NULL;
413 char *tmp;
414
415 virCheckFlags(0, -1);
416
417 tmp = strstr(def->source.name, "/");
418 if (tmp) {
419 cmd = virCommandNewArgList(ZFS, "destroy", "-r",
420 def->source.name, NULL);
421 } else {
422 cmd = virCommandNewArgList(ZPOOL, "destroy",
423 def->source.name, NULL);
424 }
425
426 return virCommandRun(cmd, NULL);
427 }
428
429 virStorageBackend virStorageBackendZFS = {
430 .type = VIR_STORAGE_POOL_ZFS,
431
432 .checkPool = virStorageBackendZFSCheckPool,
433 .refreshPool = virStorageBackendZFSRefreshPool,
434 .createVol = virStorageBackendZFSCreateVol,
435 .deleteVol = virStorageBackendZFSDeleteVol,
436 .buildPool = virStorageBackendZFSBuildPool,
437 .deletePool = virStorageBackendZFSDeletePool,
438 .uploadVol = virStorageBackendVolUploadLocal,
439 .downloadVol = virStorageBackendVolDownloadLocal,
440 };
441
442
443 int
virStorageBackendZFSRegister(void)444 virStorageBackendZFSRegister(void)
445 {
446 return virStorageBackendRegister(&virStorageBackendZFS);
447 }
448