[lxc-devel] [RFC 1/1] snapshots in api: define lxcapi_snapshot

Serge Hallyn serge.hallyn at ubuntu.com
Wed Sep 4 16:05:02 UTC 2013


Hi,

before I go on with the snapshots-in-api patchset, I wanted to floar
this and ask a few questions, as I'm having a hard time deciding how
we want to talk over the API.

(Though as I've been typing this out, I think I now see how I want it
to look.)

First, the basics:  if you have lxcpath=/var/lib/lxc and take a first
snapshot of container c1, then the container will be
/var/lib/lxcsnaps/c1/snap0.  Next snapshot will be /var/lib/lxcsnaps/c1/snap1.
You can pass a text file containing a commit comment, which will simply be
stored at /var/lib/lxc/lxcsnaps/c0/snap0/comment, and a timestamp is
created at /var/lib/lxc/lxcsnaps/c0/snap0/tx.

To restore that snap1 as container c2, I'm thinking you would

	c = lxc_container_new("c0", "/var/lib/lxc");
	c2 = c->restore(c, "snap1", "c2");
	lxc_container_put(c2);
	lxc_container_put(c);

(Note this doesn't match what's in the patch below).  There are other
ways it could be done.  For instance we could open the snapshot as
its own container and call restore on that, i.e.

	c = lxc_container_new("snap1", "/var/lib/lxcsnaps/c1");
	c2 = c->restore(c, "c2");

I think I like the first option better though as it keeps callers
from digging into the storage details.  Thoughts?

In addition, I'll add lxcapi_snapshot_destroy(), which will look like:

	c = lxc_container_new("c0", "/var/lib/lxc");
	c->snapshot_destroy(c, "snap1");
	lxc_container_put(c);

As for snapshot_list, I'm thinking it will just look like:

	c = lxc_container_new("c0", "/var/lib/lxc");
	ns = c->snapshot_entries(c, NULL, 0);
	for (i=0; i<ns; i++) {
		c2 = c->get_snapshot(c, i);
		printf("name is %s, lxcpath %s\n", c->name, c->config_path);
		lxc_container_put(c2);
	}
	lxc_container_put(c);

with 'timestamp' and 'comment_file' fields being added to struct
container_struct, usually both NULL.

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>

---
 src/lxc/lxccontainer.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/lxc/lxccontainer.h |  14 +++++++
 src/tests/Makefile.am  |   7 +++-
 src/tests/snapshot.c   |  97 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 222 insertions(+), 2 deletions(-)
 create mode 100644 src/tests/snapshot.c

diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index f4d1f8e..65e7ebe 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -68,6 +68,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
@@ -2189,6 +2196,101 @@ 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) + strlen(cname) + 20);
+	while (1) {
+		sprintf(fname, "%s/%s_%d", lxcpath, cname, i);
+		ret = stat(fname, &sb);
+		if (ret != 0)
+			return i;
+		i++;
+	}
+}
+
+static bool 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 false;
+	i = get_next_index(snappath, c->name);
+
+	if (mkdir_p(snappath, 0755) < 0) {
+		ERROR("Failed to create snapshot directory %s", snappath);
+		return false;
+	}
+
+	ret = snprintf(newname, 20, "snap%d", i);
+	if (ret < 0 || ret >= 20)
+		return false;
+
+	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 false;
+	}
+
+	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 false;
+	}
+	if (fprintf(f, "%s", buffer) < 0) {
+		SYSERROR("Writing timestamp");
+		fclose(f);
+		return false;
+	}
+	if (fclose(f) != 0) {
+		SYSERROR("Writing timestamp");
+		return false;
+	}
+
+	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 ? false : true;
+	}
+
+	return true;
+}
+
+static bool lxcapi_snapshot_list(struct lxc_container *c, char *dest, int *len)
+{
+	return false; // not yet implemented
+}
+
+static bool lxcapi_snapshot_restore(struct lxc_container *c, char *newname)
+{
+	return false;
+
+}
+
 static int lxcapi_attach_run_waitl(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char *arg, ...)
 {
 	va_list ap;
@@ -2234,6 +2336,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");
@@ -2302,6 +2405,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)) {
diff --git a/src/lxc/lxccontainer.h b/src/lxc/lxccontainer.h
index ad6afa1..121eede 100644
--- a/src/lxc/lxccontainer.h
+++ b/src/lxc/lxccontainer.h
@@ -177,6 +177,20 @@ 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 api:
+	* If you have /var/lib/lxc/c1 and ask to create the first snapshot, it will
+	* be created as /var/lib/lxcsnaps/c1/snap1
+	* The second will be /var/lib/lxcsnaps/c1/snap2, etc.
+	*
+	 * This will want some extensions but until we use it I'm not sure what
+	 * those will be.  We'll want at least a way to get comment details for
+	 * snapshots
+	 */
+	bool (*snapshot)(struct lxc_container *c, char *commentfile);
+	bool (*snapshot_list)(struct lxc_container *c, char *dest, int *len);
+	bool (*snapshot_restore)(struct lxc_container *c, char *newname);
 };
 
 struct lxc_container *lxc_container_new(const char *name, const char *configpath);
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 76b38f9..a6dacab 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -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
diff --git a/src/tests/snapshot.c b/src/tests/snapshot.c
new file mode 100644
index 0000000..11e905e
--- /dev/null
+++ b/src/tests/snapshot.c
@@ -0,0 +1,97 @@
+/* liblxcapi
+ *
+ * Copyright © 2013 Serge Hallyn <serge.hallyn at 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"
+
+void try_to_remove()
+{
+	struct lxc_container *c;
+	char snappath[1024];
+	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()
+{
+	struct lxc_container *c;
+
+	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, "busybox", NULL, NULL, 0, NULL)) {
+		fprintf(stderr, "%s: %d: failed to create busybox container\n", __FILE__, __LINE__);
+		goto err;
+	}
+	c->load_config(c, NULL);
+
+	if (!c->snapshot(c, NULL)) {
+		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;
+	}
+
+	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);
+}
-- 
1.8.3.2





More information about the lxc-devel mailing list