Commit f5dd1d53 by Serge Hallyn Committed by Stéphane Graber

API support for container snapshots (v2)

The api allows for creating, listing, and restoring of container snapshots. Snapshots are created as snapshot clones of the original container - i.e. btrfs and lvm will be done as snapshot, a directory-backed container will have overlayfs snapshots. A restore is a copy-clone, using the same backing store as the original container had. Changelog: . remove lxcapi_snap_open, which wasn't defined anyway. . rename get_comment to get_commentpath . if no newname is specified at restore, use c->name (as we meant to) rather than segving. . when choosing a snapshot index, use the correct path to check for. Signed-off-by: 's avatarSerge Hallyn <serge.hallyn@ubuntu.com> Acked-by: 's avatarStéphane Graber <stgraber@ubuntu.com>
parent 98f5f7e2
......@@ -27,6 +27,7 @@
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <dirent.h>
#include "config.h"
#include "lxc.h"
#include "state.h"
......@@ -68,6 +69,13 @@ static bool file_exists(char *f)
return stat(f, &statbuf) == 0;
}
static void remove_trailing_slashes(char *p)
{
int l = strlen(p);
while (--l >= 0 && (p[l] == '/' || p[l] == '\n'))
p[l] = '\0';
}
/*
* A few functions to help detect when a container creation failed.
* If a container creation was killed partway through, then trying
......@@ -2192,6 +2200,272 @@ static int lxcapi_attach_run_wait(struct lxc_container *c, lxc_attach_options_t
return lxc_wait_for_pid_status(pid);
}
int get_next_index(const char *lxcpath, char *cname)
{
char *fname;
struct stat sb;
int i = 0, ret;
fname = alloca(strlen(lxcpath) + 20);
while (1) {
sprintf(fname, "%s/snap%d", lxcpath, i);
ret = stat(fname, &sb);
if (ret != 0)
return i;
i++;
}
}
static int lxcapi_snapshot(struct lxc_container *c, char *commentfile)
{
int i, flags, ret;
struct lxc_container *c2;
char snappath[MAXPATHLEN], newname[20];
// /var/lib/lxc -> /var/lib/lxcsnaps \0
ret = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (ret < 0 || ret >= MAXPATHLEN)
return -1;
i = get_next_index(snappath, c->name);
if (mkdir_p(snappath, 0755) < 0) {
ERROR("Failed to create snapshot directory %s", snappath);
return -1;
}
ret = snprintf(newname, 20, "snap%d", i);
if (ret < 0 || ret >= 20)
return -1;
flags = LXC_CLONE_SNAPSHOT | LXC_CLONE_KEEPMACADDR | LXC_CLONE_KEEPNAME;
c2 = c->clone(c, newname, snappath, flags, NULL, NULL, 0, NULL);
if (!c2) {
ERROR("clone of %s:%s failed\n", c->config_path, c->name);
return -1;
}
lxc_container_put(c2);
// Now write down the creation time
time_t timer;
char buffer[25];
struct tm* tm_info;
time(&timer);
tm_info = localtime(&timer);
strftime(buffer, 25, "%Y:%m:%d %H:%M:%S", tm_info);
char *dfnam = alloca(strlen(snappath) + strlen(newname) + 5);
sprintf(dfnam, "%s/%s/ts", snappath, newname);
FILE *f = fopen(dfnam, "w");
if (!f) {
ERROR("Failed to open %s\n", dfnam);
return -1;
}
if (fprintf(f, "%s", buffer) < 0) {
SYSERROR("Writing timestamp");
fclose(f);
return -1;
}
if (fclose(f) != 0) {
SYSERROR("Writing timestamp");
return -1;
}
if (commentfile) {
// $p / $name / comment \0
int len = strlen(snappath) + strlen(newname) + 10;
char *path = alloca(len);
sprintf(path, "%s/%s/comment", snappath, newname);
return copy_file(commentfile, path) < 0 ? -1 : i;
}
return i;
}
static void lxcsnap_free(struct lxc_snapshot *s)
{
if (s->name)
free(s->name);
if (s->comment_pathname)
free(s->comment_pathname);
if (s->timestamp)
free(s->timestamp);
if (s->lxcpath)
free(s->lxcpath);
}
static char *get_snapcomment_path(char* snappath, char *name)
{
// $snappath/$name/comment
int ret, len = strlen(snappath) + strlen(name) + 10;
char *s = malloc(len);
if (s) {
ret = snprintf(s, len, "%s/%s/comment", snappath, name);
if (ret < 0 || ret >= len) {
free(s);
s = NULL;
}
}
return s;
}
static char *get_timestamp(char* snappath, char *name)
{
char path[MAXPATHLEN], *s = NULL;
int ret, len;
FILE *fin;
ret = snprintf(path, MAXPATHLEN, "%s/%s/ts", snappath, name);
if (ret < 0 || ret >= MAXPATHLEN)
return NULL;
if ((fin = fopen(path, "r")) == NULL)
return NULL;
(void) fseek(fin, 0, SEEK_END);
len = ftell(fin);
(void) fseek(fin, 0, SEEK_SET);
if (len > 0) {
s = malloc(len+1);
if (s) {
s[len] = '\0';
if (fread(s, 1, len, fin) != len) {
SYSERROR("reading timestamp");
free(s);
s = NULL;
}
}
}
fclose(fin);
return s;
}
static int lxcapi_snapshot_list(struct lxc_container *c, struct lxc_snapshot **ret_snaps)
{
char snappath[MAXPATHLEN], path2[MAXPATHLEN];
int dirlen, count = 0, ret;
struct dirent dirent, *direntp;
struct lxc_snapshot *snaps =NULL, *nsnaps;
DIR *dir;
if (!c || !lxcapi_is_defined(c))
return -1;
// snappath is ${lxcpath}snaps/${lxcname}/
dirlen = snprintf(snappath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (dirlen < 0 || dirlen >= MAXPATHLEN) {
ERROR("path name too long");
return -1;
}
if (!(dir = opendir(snappath))) {
INFO("failed to open %s - assuming no snapshots", snappath);
return 0;
}
while (!readdir_r(dir, &dirent, &direntp)) {
if (!direntp)
break;
if (!strcmp(direntp->d_name, "."))
continue;
if (!strcmp(direntp->d_name, ".."))
continue;
ret = snprintf(path2, MAXPATHLEN, "%s/%s/config", snappath, direntp->d_name);
if (ret < 0 || ret >= MAXPATHLEN) {
ERROR("pathname too long");
goto out_free;
}
if (!file_exists(path2))
continue;
nsnaps = realloc(snaps, (count + 1)*sizeof(*snaps));
if (!nsnaps) {
SYSERROR("Out of memory");
goto out_free;
}
snaps = nsnaps;
snaps[count].free = lxcsnap_free;
snaps[count].name = strdup(direntp->d_name);
if (!snaps[count].name)
goto out_free;
snaps[count].lxcpath = strdup(snappath);
if (!snaps[count].lxcpath) {
free(snaps[count].name);
goto out_free;
}
snaps[count].comment_pathname = get_snapcomment_path(snappath, direntp->d_name);
snaps[count].timestamp = get_timestamp(snappath, direntp->d_name);
count++;
}
if (closedir(dir))
WARN("failed to close directory");
*ret_snaps = snaps;
return count;
out_free:
if (snaps) {
int i;
for (i=0; i<count; i++)
lxcsnap_free(&snaps[i]);
free(snaps);
}
return -1;
}
static bool lxcapi_snapshot_restore(struct lxc_container *c, char *snapname, char *newname)
{
char clonelxcpath[MAXPATHLEN];
int ret;
struct lxc_container *snap, *rest;
struct bdev *bdev;
bool b = false;
if (!c || !c->name || !c->config_path)
return false;
bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
if (!bdev) {
ERROR("Failed to find original backing store type");
return false;
}
if (!newname)
newname = c->name;
if (strcmp(c->name, newname) == 0) {
if (!lxcapi_destroy(c)) {
ERROR("Could not destroy existing container %s", newname);
bdev_put(bdev);
return false;
}
}
ret = snprintf(clonelxcpath, MAXPATHLEN, "%ssnaps/%s", c->config_path, c->name);
if (ret < 0 || ret >= MAXPATHLEN) {
bdev_put(bdev);
return false;
}
// how should we lock this?
snap = lxc_container_new(snapname, clonelxcpath);
if (!snap || !lxcapi_is_defined(snap)) {
ERROR("Could not open snapshot %s", snapname);
if (snap) lxc_container_put(snap);
bdev_put(bdev);
return false;
}
rest = lxcapi_clone(snap, newname, c->config_path, 0, bdev->type, NULL, 0, NULL);
bdev_put(bdev);
if (rest && lxcapi_is_defined(rest))
b = true;
if (rest)
lxc_container_put(rest);
lxc_container_put(snap);
return b;
}
static int lxcapi_attach_run_waitl(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char *arg, ...)
{
va_list ap;
......@@ -2237,6 +2511,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
goto err;
}
remove_trailing_slashes(c->config_path);
c->name = malloc(strlen(name)+1);
if (!c->name) {
fprintf(stderr, "Error allocating lxc_container name\n");
......@@ -2305,6 +2580,9 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
c->attach = lxcapi_attach;
c->attach_run_wait = lxcapi_attach_run_wait;
c->attach_run_waitl = lxcapi_attach_run_waitl;
c->snapshot = lxcapi_snapshot;
c->snapshot_list = lxcapi_snapshot_list;
c->snapshot_restore = lxcapi_snapshot_restore;
/* we'll allow the caller to update these later */
if (lxc_log_init(NULL, "none", NULL, "lxc_container", 0, c->config_path)) {
......
......@@ -38,6 +38,8 @@
struct bdev_specs;
struct lxc_snapshot;
struct lxc_container {
// private fields
char *name;
......@@ -177,6 +179,54 @@ struct lxc_container {
/* run program in container, wait for it to exit */
int (*attach_run_wait)(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char * const argv[]);
int (*attach_run_waitl)(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char *arg, ...);
/*
* snapshot:
* If you have /var/lib/lxc/c1 and call c->snapshot() the firs time, it
* will return 0, and the container will be /var/lib/lxcsnaps/c1/snap0.
* The second call will return 1, and the snapshot will be
* /var/lib/lxcsnaps/c1/snap1.
*
* On error, returns -1.
*/
int (*snapshot)(struct lxc_container *c, char *commentfile);
/*
* snapshot_list() will return a description of all snapshots of c in
* a simple array. See src/tests/snapshot.c for the proper way to
* free the allocated results.
*
* Returns the number of snapshots.
*/
int (*snapshot_list)(struct lxc_container *, struct lxc_snapshot **);
/*
* snapshot_restore() will create a new container based on a snapshot.
* c is the container whose snapshot we look for, and snapname is the
* specific snapshot name (i.e. "snap0"). newname is the name to be
* used for the restored container. If newname is the same as
* c->name, then c will first be destroyed. That will fail if the
* snapshot is overlayfs-based, since the snapshots will pin the
* original container.
*
* The restored container will be a copy (not snapshot) of the snapshot,
* and restored in the lxcpath of the original container.
*
* As an example, c might be /var/lib/lxc/c1, snapname might be 'snap0'
* which stands for /var/lib/lxcsnaps/c1/snap0. If newname is c2,
* then snap0 will be copied to /var/lib/lxc/c2.
*
* Returns true on success, false on failure.
*/
bool (*snapshot_restore)(struct lxc_container *c, char *snapname, char *newname);
};
struct lxc_snapshot {
char *name;
char *comment_pathname;
char *timestamp;
char *lxcpath;
void (*free)(struct lxc_snapshot *);
};
struct lxc_container *lxc_container_new(const char *name, const char *configpath);
......
......@@ -17,6 +17,7 @@ lxc_test_clonetest_SOURCES = clonetest.c
lxc_test_console_SOURCES = console.c
lxc_usernic_test_SOURCES = ../lxc/lxc_user_nic.c ../lxc/nl.c
lxc_usernic_test_CFLAGS = -DISTEST
lxc_test_snapshot_SOURCES = snapshot.c
AM_CFLAGS=-I$(top_srcdir)/src \
-DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \
......@@ -28,7 +29,8 @@ AM_CFLAGS=-I$(top_srcdir)/src \
bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
lxc-test-destroytest lxc-test-saveconfig lxc-test-createtest \
lxc-test-shutdowntest lxc-test-get_item lxc-test-getkeys lxc-test-lxcpath \
lxc-test-cgpath lxc-test-clonetest lxc-test-console lxc-usernic-test
lxc-test-cgpath lxc-test-clonetest lxc-test-console lxc-usernic-test \
lxc-test-snapshot
bin_SCRIPTS = lxc-test-usernic
......@@ -48,4 +50,5 @@ EXTRA_DIST = \
clonetest.c \
startone.c \
console.c \
lxc-test-usernic
lxc-test-usernic \
snapshot.c
/* liblxcapi
*
* Copyright 2013 Serge Hallyn <serge.hallyn@ubuntu.com>.
* Copyright 2013 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../lxc/lxccontainer.h"
#include <errno.h>
#include <stdlib.h>
#include "../lxc/lxc.h"
#define MYNAME "snapxxx1"
#define RESTNAME "snapxxx2"
void try_to_remove()
{
struct lxc_container *c;
char snappath[1024];
c = lxc_container_new(RESTNAME, NULL);
if (c) {
if (c->is_defined(c))
c->destroy(c);
lxc_container_put(c);
}
snprintf(snappath, 1024, "%ssnaps/%s", lxc_get_default_config_path(), MYNAME);
c = lxc_container_new("snap0", snappath);
if (c) {
if (c->is_defined(c))
c->destroy(c);
lxc_container_put(c);
}
c = lxc_container_new(MYNAME, NULL);
if (c) {
if (c->is_defined(c))
c->destroy(c);
lxc_container_put(c);
}
}
int main(int argc, char *argv[])
{
struct lxc_container *c;
char *template = "busybox";
if (argc > 1)
template = argv[1];
try_to_remove();
c = lxc_container_new(MYNAME, NULL);
if (!c) {
fprintf(stderr, "%s: %d: failed to load first container\n", __FILE__, __LINE__);
exit(1);
}
if (c->is_defined(c)) {
fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME);
(void) c->destroy(c);
}
if (!c->set_config_item(c, "lxc.network.type", "empty")) {
fprintf(stderr, "%s: %d: failed to set network type\n", __FILE__, __LINE__);
goto err;
}
c->save_config(c, NULL);
if (!c->createl(c, template, NULL, NULL, 0, NULL)) {
fprintf(stderr, "%s: %d: failed to create %s container\n", __FILE__, __LINE__, template);
goto err;
}
c->load_config(c, NULL);
if (c->snapshot(c, NULL) != 0) {
fprintf(stderr, "%s: %d: failed to create snapsot\n", __FILE__, __LINE__);
goto err;
}
// rootfs should be ${lxcpath}snaps/${lxcname}/snap0/rootfs
struct stat sb;
int ret;
char path[1024];
snprintf(path, 1024, "%ssnaps/%s/snap0/rootfs", lxc_get_default_config_path(), MYNAME);
ret = stat(path, &sb);
if (ret != 0) {
fprintf(stderr, "%s: %d: snapshot was not actually created\n", __FILE__, __LINE__);
goto err;
}
struct lxc_snapshot *s;
int i, n;
n = c->snapshot_list(c, &s);
if (n < 1) {
fprintf(stderr, "%s: %d: failed listing containers\n", __FILE__, __LINE__);
goto err;
}
if (strcmp(s->name, "snap0") != 0) {
fprintf(stderr, "%s: %d: snapshot had bad name\n", __FILE__, __LINE__);
goto err;
}
for (i=0; i<n; i++) {
s[i].free(&s[i]);
}
free(s);
if (!c->snapshot_restore(c, "snap0", RESTNAME)) {
fprintf(stderr, "%s: %d: failed to restore snapshot\n", __FILE__, __LINE__);
goto err;
}
printf("All tests passed\n");
lxc_container_put(c);
exit(0);
err:
lxc_container_put(c);
fprintf(stderr, "Exiting on error\n");
try_to_remove();
exit(1);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment