[lxc-devel] [lxc/master] POC: container live patching

brauner on Github lxc-bot at linuxcontainers.org
Wed Oct 11 10:21:54 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1425 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171011/15f8c065/attachment.bin>
-------------- next part --------------
From 64e664cd913d5153df77540353d2dcdcf0dc93eb Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 11 Oct 2017 11:13:53 +0200
Subject: [PATCH 1/2] lxccontainer: implement container live patching

This adds set_running_config_item() which is the analogue of
get_running_config_item(). In essence it allows a caller to livepatch the
container's in-memory configuration. This POC is severly limited. Here are the
most obvious ones:
- Only the container's in-memory config can be updated but no further actions
  (e.g. on-disk actions) are made.
- Only keys in the "lxc.net." namespace can be changed. This POC also allows
  updating an existing network. For example it allows to change the network
  type of an existing network. This is obviously nonsense and in a non-POC
  implementation this should be blocked.

Use Case:
Callers can hotplug a new network for the container. For example, LXD can
create a pair of veth devices in the host and in the container and add it to
the container's in-memory config. This means, the container can later be
queried for the name of the device later on etc. Note that liblxc will
currently not delete hotplugged network devices on container shutdown since it
won't have the ifindex of the container.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 src/lxc/commands.c     | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/lxc/commands.h     |  9 +++++++
 src/lxc/lxccontainer.c | 21 +++++++++++++++
 src/lxc/lxccontainer.h | 11 ++++++++
 4 files changed, 110 insertions(+)

diff --git a/src/lxc/commands.c b/src/lxc/commands.c
index 68fbd387c..bacffdb19 100644
--- a/src/lxc/commands.c
+++ b/src/lxc/commands.c
@@ -90,6 +90,7 @@ static const char *lxc_cmd_str(lxc_cmd_t cmd)
 		[LXC_CMD_GET_NAME]         = "get_name",
 		[LXC_CMD_GET_LXCPATH]      = "get_lxcpath",
 		[LXC_CMD_ADD_STATE_CLIENT] = "add_state_client",
+		[LXC_CMD_SET_CONFIG_ITEM]  = "set_config_item",
 	};
 
 	if (cmd >= LXC_CMD_MAX)
@@ -539,6 +540,73 @@ static int lxc_cmd_get_config_item_callback(int fd, struct lxc_cmd_req *req,
 }
 
 /*
+ * lxc_cmd_set_config_item: Get config item the running container
+ *
+ * @name     : name of container to connect to
+ * @item     : the configuration item to set (ex: lxc.net.0.veth.pair)
+ * @value    : the value to set (ex: "eth0")
+ * @lxcpath  : the lxcpath in which the container is running
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int lxc_cmd_set_config_item(const char *name, const char *item,
+			    const char *value, const char *lxcpath)
+{
+	int ret, stopped;
+	struct lxc_cmd_set_config_item_req_data data;
+	struct lxc_cmd_rr cmd;
+
+	/* pre-validate request
+	   Currently we only support live-patching network configurations.
+	 */
+	if (strncmp(item, "lxc.net.", 8))
+		return -EINVAL;
+
+	data.item = item;
+	data.value = (void *)value;
+
+	cmd.req.cmd = LXC_CMD_SET_CONFIG_ITEM;
+	cmd.req.data = &data;
+	cmd.req.datalen = sizeof(data);
+
+	ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
+	if (ret < 0)
+		return ret;
+
+	return cmd.rsp.ret;
+}
+
+static int lxc_cmd_set_config_item_callback(int fd, struct lxc_cmd_req *req,
+					    struct lxc_handler *handler)
+{
+	int ret;
+	const char *key, *value;
+	struct lxc_config_t *item;
+	struct lxc_cmd_rsp rsp;
+	const struct lxc_cmd_set_config_item_req_data *data;
+
+	data = req->data;
+	key = data->item;
+	value = data->value;
+
+	memset(&rsp, 0, sizeof(rsp));
+	rsp.ret = -EINVAL;
+
+	item = lxc_get_config(key);
+	if (!item)
+		goto on_error;
+
+	ret = item->set(key, value, handler->conf, NULL);
+	if (ret < 0)
+		goto on_error;
+
+	rsp.ret = 0;
+
+on_error:
+	return lxc_cmd_rsp_send(fd, &rsp);
+}
+
+/*
  * lxc_cmd_get_state: Get current state of the container
  *
  * @name      : name of container to connect to
@@ -949,6 +1017,7 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req,
 		[LXC_CMD_GET_NAME]         = lxc_cmd_get_name_callback,
 		[LXC_CMD_GET_LXCPATH]      = lxc_cmd_get_lxcpath_callback,
 		[LXC_CMD_ADD_STATE_CLIENT] = lxc_cmd_add_state_client_callback,
+		[LXC_CMD_SET_CONFIG_ITEM]  = lxc_cmd_set_config_item_callback,
 	};
 
 	if (req->cmd >= LXC_CMD_MAX) {
diff --git a/src/lxc/commands.h b/src/lxc/commands.h
index 28428c774..cc5eec3c5 100644
--- a/src/lxc/commands.h
+++ b/src/lxc/commands.h
@@ -48,6 +48,7 @@ typedef enum {
 	LXC_CMD_GET_NAME,
 	LXC_CMD_GET_LXCPATH,
 	LXC_CMD_ADD_STATE_CLIENT,
+	LXC_CMD_SET_CONFIG_ITEM,
 	LXC_CMD_MAX,
 } lxc_cmd_t;
 
@@ -73,6 +74,11 @@ struct lxc_cmd_console_rsp_data {
 	int ttynum;
 };
 
+struct lxc_cmd_set_config_item_req_data {
+	const char *item;
+	void *value;
+};
+
 extern int lxc_cmd_console_winch(const char *name, const char *lxcpath);
 extern int lxc_cmd_console(const char *name, int *ttynum, int *fd,
 			   const char *lxcpath);
@@ -116,4 +122,7 @@ extern int lxc_cmd_mainloop_add(const char *name, struct lxc_epoll_descr *descr,
 				    struct lxc_handler *handler);
 extern int lxc_try_cmd(const char *name, const char *lxcpath);
 
+extern int lxc_cmd_set_config_item(const char *name, const char *item,
+				   const char *value, const char *lxcpath);
+
 #endif /* __commands_h */
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 5e8ad00f9..224447e68 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -2808,6 +2808,26 @@ static bool do_lxcapi_set_config_item(struct lxc_container *c, const char *key,
 
 WRAP_API_2(bool, lxcapi_set_config_item, const char *, const char *)
 
+static bool do_lxcapi_set_running_config_item(struct lxc_container *c, const char *key, const char *v)
+{
+	int ret;
+
+	if (!c)
+		return false;
+
+	if (container_mem_lock(c))
+		return false;
+
+	ret = lxc_cmd_set_config_item(c->name, key, v, do_lxcapi_get_config_path(c));
+	if (ret < 0)
+		ERROR("Failed to live patch container");
+
+	container_mem_unlock(c);
+	return ret == 0;
+}
+
+WRAP_API_2(bool, lxcapi_set_running_config_item, const char *, const char *)
+
 static char *lxcapi_config_file_name(struct lxc_container *c)
 {
 	if (!c || !c->configfile)
@@ -4544,6 +4564,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
 	c->clear_config_item = lxcapi_clear_config_item;
 	c->get_config_item = lxcapi_get_config_item;
 	c->get_running_config_item = lxcapi_get_running_config_item;
+	c->set_running_config_item = lxcapi_set_running_config_item;
 	c->get_cgroup_item = lxcapi_get_cgroup_item;
 	c->set_cgroup_item = lxcapi_set_cgroup_item;
 	c->get_config_path = lxcapi_get_config_path;
diff --git a/src/lxc/lxccontainer.h b/src/lxc/lxccontainer.h
index 3aee440e4..083c2ca34 100644
--- a/src/lxc/lxccontainer.h
+++ b/src/lxc/lxccontainer.h
@@ -280,6 +280,17 @@ struct lxc_container {
 	bool (*set_config_item)(struct lxc_container *c, const char *key, const char *value);
 
 	/*!
+	 * \brief Set a key/value configuration option.
+	 *
+	 * \param c Container.
+	 * \param key Name of option to set.
+	 * \param value Value of \p name to set.
+	 *
+	 * \return \c true on success, else \c false.
+	 */
+	bool (*set_running_config_item)(struct lxc_container *c, const char *key, const char *value);
+
+	/*!
 	 * \brief Delete the container.
 	 *
 	 * \param c Container.

From 0412adc9335831422437db1bd1c1e7fb24eaacc5 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 11 Oct 2017 12:13:08 +0200
Subject: [PATCH 2/2] tests: test container live patching

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 .gitignore             |   1 +
 src/lxc/lxccontainer.c |   2 +-
 src/tests/Makefile.am  |   4 +-
 src/tests/livepatch.c  | 176 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 181 insertions(+), 2 deletions(-)
 create mode 100644 src/tests/livepatch.c

diff --git a/.gitignore b/.gitignore
index 80f6889b7..3930b11fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,6 +84,7 @@ src/tests/lxc-test-destroytest
 src/tests/lxc-test-get_item
 src/tests/lxc-test-getkeys
 src/tests/lxc-test-list
+src/tests/lxc-test-livepatch
 src/tests/lxc-test-locktests
 src/tests/lxc-test-lxcpath
 src/tests/lxc-test-may-control
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 224447e68..14b6482e5 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -2820,7 +2820,7 @@ static bool do_lxcapi_set_running_config_item(struct lxc_container *c, const cha
 
 	ret = lxc_cmd_set_config_item(c->name, key, v, do_lxcapi_get_config_path(c));
 	if (ret < 0)
-		ERROR("Failed to live patch container");
+		SYSERROR("%s - Failed to live patch container", strerror(-ret));
 
 	container_mem_unlock(c);
 	return ret == 0;
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 59a52b737..1fd58b2cb 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -27,6 +27,7 @@ lxc_test_utils_SOURCES = lxc-test-utils.c lxctest.h
 lxc_test_parse_config_file_SOURCES = parse_config_file.c lxctest.h
 lxc_test_config_jump_table_SOURCES = config_jump_table.c lxctest.h
 lxc_test_shortlived_SOURCES = shortlived.c
+lxc_test_livepatch_SOURCES = livepatch.c lxctest.h
 
 AM_CFLAGS=-DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \
 	-DLXCPATH=\"$(LXCPATH)\" \
@@ -55,7 +56,7 @@ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
 	lxc-test-snapshot lxc-test-concurrent lxc-test-may-control \
 	lxc-test-reboot lxc-test-list lxc-test-attach lxc-test-device-add-remove \
 	lxc-test-apparmor lxc-test-utils lxc-test-parse-config-file \
-	lxc-test-config-jump-table lxc-test-shortlived
+	lxc-test-config-jump-table lxc-test-shortlived lxc-test-livepatch
 
 bin_SCRIPTS = lxc-test-automount \
 	      lxc-test-autostart \
@@ -91,6 +92,7 @@ EXTRA_DIST = \
 	get_item.c \
 	getkeys.c \
 	list.c \
+	livepatch.c \
 	locktests.c \
 	lxcpath.c \
 	lxc-test-lxc-attach \
diff --git a/src/tests/livepatch.c b/src/tests/livepatch.c
new file mode 100644
index 000000000..9d8ffdb52
--- /dev/null
+++ b/src/tests/livepatch.c
@@ -0,0 +1,176 @@
+/* liblxcapi
+ *
+ * Copyright © 2017 Christian Brauner <christian.brauner at ubuntu.com>.
+ *
+ * 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 <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <lxc/lxccontainer.h>
+
+#include "lxctest.h"
+
+int main(int argc, char *argv[])
+{
+	char *value;
+	struct lxc_container *c;
+	int ret = EXIT_FAILURE;
+
+	c = lxc_container_new("livepatch", NULL);
+	if (!c) {
+		lxc_error("%s", "Failed to create container \"livepatch\"");
+		exit(ret);
+	}
+
+	if (c->is_defined(c)) {
+		lxc_error("%s\n", "Container \"livepatch\" is not defined");
+		goto on_error_put;
+	}
+
+	if (!c->set_config_item(c, "lxc.net.0.type", "veth")) {
+		lxc_error("%s\n", "Failed to set network item \"lxc.net.0.type\"");
+		goto on_error_put;
+	}
+
+	if (!c->set_config_item(c, "lxc.net.0.link", "lxcbr0")) {
+		lxc_error("%s\n", "Failed to set network item \"lxc.net.0.link\"");
+		goto on_error_put;
+	}
+
+	if (!c->set_config_item(c, "lxc.net.0.flags", "up")) {
+		lxc_error("%s\n", "Failed to set network item \"lxc.net.0.flags\"");
+		goto on_error_put;
+	}
+
+	if (!c->createl(c, "busybox", NULL, NULL, 0, NULL)) {
+		lxc_error("%s\n", "Failed to create busybox container \"livepatch\"");
+		goto on_error_put;
+	}
+
+	if (!c->is_defined(c)) {
+		lxc_error("%s\n", "Container \"livepatch\" is not defined");
+		goto on_error_put;
+	}
+
+	c->clear_config(c);
+
+	if (!c->load_config(c, NULL)) {
+		lxc_error("%s\n", "Failed to load config for container \"livepatch\"");
+		goto on_error_destroy;
+	}
+
+	if (!c->want_daemonize(c, true)) {
+		lxc_error("%s\n", "Failed to mark container \"livepatch\" daemonized");
+		goto on_error_destroy;
+	}
+
+	if (!c->startl(c, 0, NULL)) {
+		lxc_error("%s\n", "Failed to start container \"livepatch\" daemonized");
+		goto on_error_destroy;
+	}
+
+	/* Test whether the current value is ok. */
+	value = c->get_running_config_item(c, "lxc.net.0.type");
+	if (!value) {
+		lxc_error("%s\n", "Failed to retrieve running config item \"lxc.net.0.type\"");
+		goto on_error_destroy;
+	}
+
+	if (strcmp(value, "veth")) {
+		lxc_error("Retrieved unexpected value for config item "
+			  "\"lxc.net.0.type\": veth != %s", value);
+		free(value);
+		goto on_error_destroy;
+	}
+	free(value);
+
+	/* Change current in-memory value. */
+	if (!c->set_running_config_item(c, "lxc.net.0.type", "macvlan")) {
+		lxc_error("%s\n", "Failed to set running config item "
+				  "\"lxc.net.0.type\" to \"macvlan\"");
+		goto on_error_destroy;
+	}
+
+	/* Verify change. */
+	value = c->get_running_config_item(c, "lxc.net.0.type");
+	if (!value) {
+		lxc_error("%s\n", "Failed to retrieve running config item \"lxc.net.0.type\"");
+		goto on_error_destroy;
+	}
+
+	if (strcmp(value, "macvlan")) {
+		lxc_error("Retrieved unexpected value for config item "
+			  "\"lxc.net.0.type\": macvlan != %s", value);
+		free(value);
+		goto on_error_destroy;
+	}
+	free(value);
+
+	/* Change current in-memory value. */
+	if (!c->set_running_config_item(c, "lxc.net.0.type", "veth")) {
+		lxc_error("%s\n", "Failed to set running config item "
+				  "\"lxc.net.0.type\" to \"veth\"");
+		goto on_error_destroy;
+	}
+
+	/* Add new in-memory value. */
+	if (!c->set_running_config_item(c, "lxc.net.1.type", "veth")) {
+		lxc_error("%s\n", "Failed to set running config item "
+				  "\"lxc.net.1.type\" to \"veth\"");
+		goto on_error_destroy;
+	}
+
+	/* Verify change. */
+	value = c->get_running_config_item(c, "lxc.net.1.type");
+	if (!value) {
+		lxc_error("%s\n", "Failed to retrieve running config item \"lxc.net.1.type\"");
+		goto on_error_destroy;
+	}
+
+	if (strcmp(value, "veth")) {
+		lxc_error("Retrieved unexpected value for config item "
+			  "\"lxc.net.1.type\": veth != %s", value);
+		free(value);
+		goto on_error_destroy;
+	}
+	free(value);
+
+	/* Remove in-memory value. */
+	if (!c->set_running_config_item(c, "lxc.net.1.type", "")) {
+		lxc_error("%s\n", "Failed to clear running config item "
+				  "\"lxc.net.1.type\"");
+		goto on_error_destroy;
+	}
+
+	ret = 0;
+
+on_error_destroy:
+	if (c->is_running(c) && !c->stop(c))
+		lxc_error("%s\n", "Failed to stop container \"livepatch\"");
+
+	if (!c->destroy(c))
+		lxc_error("%s\n", "Failed to destroy container \"livepatch\"");
+
+on_error_put:
+	lxc_container_put(c);
+	exit(ret);
+}


More information about the lxc-devel mailing list