Merge lp:~bcsaller/pyjuju/lxc-clone-support into lp:pyjuju

Proposed by Benjamin Saller
Status: Rejected
Rejected by: Kapil Thangavelu
Proposed branch: lp:~bcsaller/pyjuju/lxc-clone-support
Merge into: lp:pyjuju
Diff against target: 281 lines (+136/-27)
2 files modified
ensemble/lib/lxc/__init__.py (+79/-19)
ensemble/lib/lxc/tests/test_lxc.py (+57/-8)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/lxc-clone-support
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+75453@code.launchpad.net

Description of the change

lxc-clone support

This branch includes support for lxc-clone which is later used to speed the deployment of containers in the scope of a bootstrap.

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

i believe this merge is out of date, the relevant functionality has already been merged, setting it to rejected to clean out the lp review queue.

Unmerged revisions

323. By Benjamin Saller

fix wait condition in cleanup, how did that ever work

322. By Benjamin Saller

lxc-clone support in library

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ensemble/lib/lxc/__init__.py'
2--- ensemble/lib/lxc/__init__.py 2011-09-08 23:53:09 +0000
3+++ ensemble/lib/lxc/__init__.py 2011-09-15 00:54:25 +0000
4@@ -18,13 +18,14 @@
5
6
7 def _cmd(args):
8- p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
9+ p = subprocess.Popen(
10+ args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
11 stdout_data, _ = p.communicate()
12 r = p.returncode
13 if r != 0:
14 # read the stdout/err streams and show the user
15 print >>sys.stderr, stdout_data
16- raise LXCError(stdout_data)
17+ raise LXCError("%s: %s" % (" ".join(args), stdout_data))
18 return (r, stdout_data)
19
20
21@@ -42,11 +43,21 @@
22 return _cmd(args)
23
24
25-def _lxc_start(container_name, console_log=None):
26+def _lxc_clone(orig_container, container_name):
27+ return _cmd(["sudo", "lxc-clone", "-o", orig_container, "-n",
28+ container_name])
29+
30+
31+def _lxc_start(container_name, debug_log=None, console_log=None):
32 args = ["sudo", "lxc-start", "--daemon", "-n", container_name]
33 if console_log:
34 args.append("-c")
35 args.append(console_log)
36+ if debug_log:
37+ args.append("-l")
38+ args.append("DEBUG")
39+ args.append("-o")
40+ args.append(debug_log)
41 return _cmd(args)
42
43
44@@ -76,7 +87,7 @@
45 return deferToThread(wait, container_name)
46
47
48-def _customize_container(customize_script, container_root):
49+def _customize_container(customize_script, container_root, *args):
50 if not os.path.isdir(container_root):
51 raise LXCError("Expect container root directory: %s" %
52 container_root)
53@@ -88,9 +99,11 @@
54 os.close(fd)
55 os.chmod(in_path, 0755)
56
57- args = ["sudo", "chroot", container_root,
58- os.path.join("/tmp", os.path.basename(in_path))]
59- return _cmd(args)
60+ cmd = ["sudo", "chroot", container_root,
61+ os.path.join("/tmp", os.path.basename(in_path))]
62+ if args:
63+ cmd.extend(args)
64+ return _cmd(cmd)
65
66
67 def validate_path(pathname):
68@@ -124,8 +137,11 @@
69
70 class LXCContainer(object):
71 def __init__(self,
72- container_name, configuration=None, network_name="virbr0",
73- customize_script=None):
74+ container_name, configuration=None,
75+ network_name="virbr0",
76+ template_name=None,
77+ customize_script=None,
78+ debug_log=None, console_log=None):
79 """Create an LXCContainer
80
81 :param container_name: should be unique within the system
82@@ -138,9 +154,15 @@
83
84 :param customize_script: script used inside container to make
85 it ensemble ready
86+
87+ :param template_name: name of an existing lxc template to use
88+ a a basis for clone operations (rather than create). This will
89+ still customize the resulting container.
90 """
91
92 self.container_name = container_name
93+ self.debug_log = debug_log
94+ self.console_log = console_log
95
96 if customize_script is None:
97 customize_script = os.path.join(DATA_PATH,
98@@ -148,8 +170,13 @@
99 self.customize_script = customize_script
100 validate_path(self.customize_script)
101
102+ if not configuration:
103+ configuration = {}
104+ configuration["ensemble_container_name"] = container_name
105+
106 self.configuration = configuration
107 self.network_name = network_name
108+ self.template_name = template_name
109 self.running = False
110
111 @property
112@@ -175,20 +202,31 @@
113
114 @inlineCallbacks
115 def _customize_container(self):
116+ if not self.configuration:
117+ return
118+
119 config_dir = self._p("etc/ensemble")
120 if not os.path.exists(config_dir):
121 _cmd(["sudo", "mkdir", config_dir])
122
123- if not self.configuration:
124- return
125-
126 _, fn = tempfile.mkstemp()
127+
128 with open(fn, "w") as fp:
129+ target = self._p("etc/ensemble/ensemble.conf")
130+
131+ if self.template_name:
132+ # there may exist a config in the client already we
133+ # copy its values to the new file and then append any
134+ # additional settings even though its possible the
135+ # file will have redundant entries they will override
136+ # the templates due to order of operations
137+ if os.path.exists(target):
138+ fp.write(open(target, "r").read())
139+
140 for name, value in self.configuration.items():
141 fp.write("%s=%s\n" % (name.upper(), pipes.quote(value)))
142
143- _cmd(["sudo", "mv", fn,
144- self._p("etc/ensemble/ensemble.conf")])
145+ _cmd(["sudo", "mv", fn, target])
146
147 yield deferToThread(
148 _customize_container, self.customize_script, self.rootfs)
149@@ -205,18 +243,40 @@
150 def create(self):
151 # open the template file and create a new temp processed
152 # version
153- lxc_config = self._make_lxc_config(self.network_name)
154- yield deferToThread(_lxc_create, self.container_name,
155- config_file=lxc_config)
156+ if self.template_name is not None:
157+ # we are cloning from a template
158+ yield deferToThread(
159+ _lxc_clone, self.template_name, self.container_name)
160+ else:
161+ lxc_config = self._make_lxc_config(self.network_name)
162+ yield deferToThread(_lxc_create, self.container_name,
163+ config_file=lxc_config)
164+ os.unlink(lxc_config)
165+
166 yield self._customize_container()
167- os.unlink(lxc_config)
168+
169+ def clone(self, container_name):
170+ if self.configuration:
171+ config = dict(self.configuration)
172+ else:
173+ config = None
174+
175+ return LXCContainer(container_name,
176+ template_name=self.container_name,
177+ configuration=config,
178+ debug_log=self.debug_log,
179+ console_log=self.console_log,
180+ customize_script=self.customize_script,
181+ network_name=self.network_name)
182
183 @inlineCallbacks
184 def run(self):
185 if not os.path.exists(self.rootfs):
186 yield self.create()
187
188- yield deferToThread(_lxc_start, self.container_name)
189+ yield deferToThread(
190+ _lxc_start, self.container_name,
191+ debug_log=self.debug_log, console_log=self.console_log)
192 self.running = yield _lxc_wait(self.container_name, "RUNNING")
193
194 @inlineCallbacks
195
196=== modified file 'ensemble/lib/lxc/tests/test_lxc.py'
197--- ensemble/lib/lxc/tests/test_lxc.py 2011-09-08 23:53:09 +0000
198+++ ensemble/lib/lxc/tests/test_lxc.py 2011-09-15 00:54:25 +0000
199@@ -49,6 +49,7 @@
200 def cleanContainer(self, container_name):
201 if os.path.exists("/var/lib/lxc/%s" % container_name):
202 _lxc_stop(container_name)
203+ _lxc_wait(container_name, "STOPPED")
204 _lxc_destroy(container_name)
205
206 def test_lxc_create(self):
207@@ -96,11 +97,6 @@
208 # verify we have a path into the container
209 self.assertTrue(os.path.exists(c.rootfs))
210
211- # expect a processed config file above the rootfs
212- # -- this requires root to read
213- #network = open(os.path.join(c.rootfs, "..", "config"), "r").read()
214- #self.assertIn("lxc.network.link=virbr0", network)
215-
216 # check that some of the customize modifications were made
217 self.assertTrue(os.path.exists(os.path.join(
218 c.rootfs, "etc", "ensemble")))
219@@ -145,7 +141,60 @@
220 yield _lxc_wait(DEFAULT_CONTAINER, "STOPPED")
221 _lxc_destroy(DEFAULT_CONTAINER)
222
223-
224-
225-
226+ @inlineCallbacks
227+ def test_container_clone(self):
228+ d = dict(ensemble_zookeeper=get_test_zookeeper_address(),
229+ ensemble_origin="distro")
230+ master_container = LXCContainer(DEFAULT_CONTAINER,
231+ configuration=d)
232+ yield master_container.create()
233+
234+ # Clone a child container from the template
235+ child_name = DEFAULT_CONTAINER + "_child"
236+ c = master_container.clone(child_name)
237+
238+ expected = dict(d)
239+ expected["ensemble_container_name"] = DEFAULT_CONTAINER + "_child"
240+ self.assertEqual(c.configuration, expected)
241+ self.assertFalse(c.running)
242+ yield c.run()
243+ self.assertTrue(c.running)
244+
245+ output = _lxc_ls()
246+ self.assertIn(DEFAULT_CONTAINER, output)
247+
248+ # verify we have a path into the container
249+ self.assertTrue(os.path.exists(c.rootfs))
250+
251+ # expect a processed config file above the rootfs
252+ # -- this requires root to read
253+ #network = open(os.path.join(c.rootfs, "..", "config"), "r").read()
254+ #self.assertIn("lxc.network.link=virbr0", network)
255+
256+ # check that some of the customize modifications were made
257+ self.assertTrue(os.path.exists(os.path.join(
258+ c.rootfs, "etc", "ensemble")))
259+ self.assertTrue(os.path.exists(os.path.join(
260+ c.rootfs, "usr", "bin", "ensemble")))
261+
262+ # verify that we have shell variables in rootfs/etc/ensemble.conf
263+ config = open(os.path.join(c.rootfs, "etc", "ensemble", "ensemble.conf"), "r").read()
264+ self.assertIn("ENSEMBLE_ZOOKEEPER=%s" % get_test_zookeeper_address(), config)
265+
266+ # verify that we are in containers
267+ containers = yield get_containers(None)
268+ self.assertEqual(containers[child_name], True)
269+
270+ # tear it down
271+ yield c.destroy()
272+ self.assertFalse(c.running)
273+
274+ containers = yield get_containers(None)
275+ self.assertNotIn(child_name, containers)
276+
277+ # and its gone
278+ output = _lxc_ls()
279+ self.assertNotIn(child_name, output)
280+
281+ yield master_container.destroy()
282

Subscribers

People subscribed via source and target branches

to status/vote changes: