Merge lp:~themue/juju-core/021-deployer-lxc-context into lp:~juju/juju-core/trunk

Proposed by Frank Mueller
Status: Work in progress
Proposed branch: lp:~themue/juju-core/021-deployer-lxc-context
Merge into: lp:~juju/juju-core/trunk
Diff against target: 890 lines (+856/-0)
5 files modified
worker/deployer/export_test.go (+20/-0)
worker/deployer/lxc/context.go (+355/-0)
worker/deployer/lxc/export_test.go (+27/-0)
worker/deployer/lxc/lxc.go (+213/-0)
worker/deployer/lxc/lxc_test.go (+241/-0)
To merge this branch: bzr merge lp:~themue/juju-core/021-deployer-lxc-context
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+163237@code.launchpad.net

Description of the change

lxc: adding lxc context for local provider

The LxcContext is the second context beside the currently used
SimpleContext to be used by the Deployer to deploy and recall
units. It uses a small subset of the LXC commands by calling
the binaries/scripts. Currently the new context isn't used. This
has to be done by cmd/jujud/agent.go when creating a new Deployer
instance.

https://codereview.appspot.com/9336045/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :
Download full text (4.6 KiB)

NOT LGTM -- I would be prepared to let the unit-specific stuff slide,
but there's quite a lot to address here.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go
File worker/deployer/lxc/context.go (right):

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode26
worker/deployer/lxc/context.go:26: environSeries string
An environment does not have a series, other than default-series, which
is not relevant.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode39
worker/deployer/lxc/context.go:39: // initDir specifies the directory
used by upstart on the local system.
If LXC autoconf works, we don't need upstart on the local system. We
*do* need it relative to a container's root though.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode45
worker/deployer/lxc/context.go:45: dataDir string
Why not const? We're completely in control here.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode53
worker/deployer/lxc/context.go:53: logDir string
Similarly, let's just make that const. No need to ever swap it out that
I can see.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode57
worker/deployer/lxc/context.go:57: lxcContainerDir string
Hmm, I think I saw serge explaining that we can use whatever dir we
want. If we can, I'd really like us to just put them directly inside the
deploying agent's data dir... possible?

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode67
worker/deployer/lxc/context.go:67: // entity specified in info, that
deploys unit agents as upstart jobs in
FWIW, this can't be unit-specific any more. All containers are
"machines" and get their own machine agents.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode77
worker/deployer/lxc/context.go:77: hostDataDir = "/var/lib/juju"
I don't think we should default this. We should always have the data dir
available, right?

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode104
worker/deployer/lxc/context.go:104: if err :=
ctx.createContainer(containerName); err != nil {
Shouldn't we trash it and try again? Failure partway through the first
attempt should not make a container undeployable on subsequent
attempts...

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode127
worker/deployer/lxc/context.go:127: }
If we fail here, the unit'll keep running forever.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode146
worker/deployer/lxc/context.go:146: if groups[2] != ctx.deployerTag {
This is broken unless we also take into account environ name. But I'm
not sure we should care about environ names...

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode167
worker/deployer/lxc/context.go:167: svc.InitDir = ctx.initDir
Why are we using the host upstart dir? ISTM that this will cause us to
start containers that don't run agents, and start agents outside
containers.

https://codereview.appspot.com/9336045/diff/1/work...

Read more...

Revision history for this message
Frank Mueller (themue) wrote :
Download full text (4.5 KiB)

Closed.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go
File worker/deployer/lxc/context.go (right):

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode26
worker/deployer/lxc/context.go:26: environSeries string
On 2013/05/17 06:46:27, fwereade wrote:
> An environment does not have a series, other than default-series,
which is not
> relevant.

Renamed in defaultSeries, it's needed for the lxc container creation.

PyJuju gets this value by reading $JUJU_ENV, which we don't have so far.
But could also be a solution.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode39
worker/deployer/lxc/context.go:39: // initDir specifies the directory
used by upstart on the local system.
On 2013/05/17 06:46:27, fwereade wrote:
> If LXC autoconf works, we don't need upstart on the local system. We
*do* need
> it relative to a container's root though.

Wrong comment, but removed it due to wrong concept.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode45
worker/deployer/lxc/context.go:45: dataDir string
On 2013/05/17 06:46:27, fwereade wrote:
> Why not const? We're completely in control here.

Done.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode53
worker/deployer/lxc/context.go:53: logDir string
On 2013/05/17 06:46:27, fwereade wrote:
> Similarly, let's just make that const. No need to ever swap it out
that I can
> see.

Done.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode57
worker/deployer/lxc/context.go:57: lxcContainerDir string
On 2013/05/17 06:46:27, fwereade wrote:
> Hmm, I think I saw serge explaining that we can use whatever dir we
want. If we
> can, I'd really like us to just put them directly inside the deploying
agent's
> data dir... possible?

Seems to be possible by generating the directory in the container
configuration. Still one question left, will check.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode77
worker/deployer/lxc/context.go:77: hostDataDir = "/var/lib/juju"
On 2013/05/17 06:46:27, fwereade wrote:
> I don't think we should default this. We should always have the data
dir
> available, right?

Done.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode104
worker/deployer/lxc/context.go:104: if err :=
ctx.createContainer(containerName); err != nil {
On 2013/05/17 06:46:27, fwereade wrote:
> Shouldn't we trash it and try again? Failure partway through the first
attempt
> should not make a container undeployable on subsequent attempts...

Done.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode127
worker/deployer/lxc/context.go:127: }
On 2013/05/17 06:46:27, fwereade wrote:
> If we fail here, the unit'll keep running forever.

Done.

https://codereview.appspot.com/9336045/diff/1/worker/deployer/lxc/context.go#newcode167
worker/deployer/lxc/context.go:167: svc.InitDir = ctx.initDir
On 2013/05/17 06:46:27, fwereade wrote:
> Why are we using the host upstart dir? ISTM that this will cause us to
start
> containers that don't run age...

Read more...

Unmerged revisions

972. By Frank Mueller

lxc: merged trunk

971. By Frank Mueller

lxc: refactored into own package; first network code

970. By Frank Mueller

lxc: improved context

969. By Frank Mueller

deployer: merged trunk and done review changes

968. By Frank Mueller

deployer/lxc: merged after review changes

967. By Frank Mueller

deployer/lxc: changes after reviews

966. By Frank Mueller

deployer: merged trunk and renamed manager to context

965. By Frank Mueller

deployer: added LXC manager

964. By Francesco Banconi

Add a CharmInfo API command.

Add a CharmInfo API command to get information
about a charm, given its URL. It is needed by the
Juju GUI, modeled upon the similar one in pyJuju.

R=dimitern, rog
CC=
https://codereview.appspot.com/7444052

963. By Martin Packman

Correct url for openstack info on metadata service

The location on the metadata service we were using to retrieve the openstack
specific json file was misnamed and lacked the 'openstack' path component,
which was causing the legacy id to be used in all cases. That results in a
suicidal state server, which also hints we need to worry more about the
other cases when we may run into non-canonical server ids.

R=dfc, jameinel
CC=
https://codereview.appspot.com/7527043

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'worker/deployer/export_test.go'
2--- worker/deployer/export_test.go 2013-03-29 14:54:20 +0000
3+++ worker/deployer/export_test.go 2013-05-09 23:22:32 +0000
4@@ -1,5 +1,9 @@
5 package deployer
6
7+import (
8+ "launchpad.net/juju-core/version"
9+)
10+
11 type fakeAddresser struct{}
12
13 func (*fakeAddresser) Addresses() []string {
14@@ -17,3 +21,19 @@
15 syslogConfigDir: syslogConfigDir,
16 }
17 }
18+
19+func NewTestLxcContext(deployerTag, initDir, hostDataDir, lxcContainerDir, lxcAutoDir string) *LxcContext {
20+ return &LxcContext{
21+ environName: "lxc-test",
22+ environSeries: version.Current.Series,
23+ dataDir: "/var/lib/juju",
24+ hostDataDir: hostDataDir,
25+ addresser: &fakeAddresser{},
26+ caCert: []byte("test-cert"),
27+ deployerTag: deployerTag,
28+ initDir: initDir,
29+ logDir: "/var/log/juju",
30+ lxcContainerDir: lxcContainerDir,
31+ lxcAutoDir: lxcAutoDir,
32+ }
33+}
34
35=== added directory 'worker/deployer/lxc'
36=== added file 'worker/deployer/lxc/context.go'
37--- worker/deployer/lxc/context.go 1970-01-01 00:00:00 +0000
38+++ worker/deployer/lxc/context.go 2013-05-09 23:22:32 +0000
39@@ -0,0 +1,355 @@
40+package lxc
41+
42+import (
43+ "fmt"
44+ "io"
45+ "io/ioutil"
46+ "os"
47+ "path/filepath"
48+ "regexp"
49+ "strings"
50+
51+ "launchpad.net/juju-core/environs/agent"
52+ "launchpad.net/juju-core/log"
53+ "launchpad.net/juju-core/state"
54+ "launchpad.net/juju-core/upstart"
55+ "launchpad.net/juju-core/version"
56+ "launchpad.net/juju-core/worker/deployer"
57+)
58+
59+// LxcContext stores details of the LXC environment on the local system.
60+type LxcContext struct {
61+ // environName identifies the environment the context is running in.
62+ environName string
63+
64+ // environSeries identifies the series of the environment.
65+ environSeries string
66+
67+ // addresser is used to get the current state server addresses at the time
68+ // the given unit is deployed.
69+ addresser deployer.Addresser
70+
71+ // caCert holds the CA certificate that will be used
72+ // to validate the state server's certificate, in PEM format.
73+ caCert []byte
74+
75+ // deployerTag identifies the agent on whose behalf this context is running.
76+ deployerTag string
77+
78+ // initDir specifies the directory used by upstart on the local system.
79+ // It is typically set to "/etc/init".
80+ initDir string
81+
82+ // dataDir specifies the directory used by juju to store its state. It
83+ // is typically set to "/var/lib/juju".
84+ dataDir string
85+
86+ // hostDataDir specifies the directory used by juju on the host system to
87+ // store its tools. It is typically set to "/var/lib/juju".
88+ hostDataDir string
89+
90+ // logDir specifies the directory to which installed units will write
91+ // their log files. It is typically set to "/var/log/juju".
92+ logDir string
93+
94+ // lxcContainerDir specifies the base directory for all containers. It
95+ // is typically set to "/var/lib/lxc".
96+ lxcContainerDir string
97+
98+ // lxcAutoDir specifies the directory for the links to confguration files
99+ // needed for autostarting containers. It is typically set to "/etc/lxc/auto".
100+ lxcAutoDir string
101+}
102+
103+var _ deployer.Context = (*LxcContext)(nil)
104+
105+// NewLxcContext returns a new LXC context, acting on behalf of the
106+// entity specified in info, that deploys unit agents as upstart jobs in
107+// "/etc/init" logging to "/var/log/juju". Paths to which agents and tools
108+// are installed are relative to dataDir; if dataDir or hostDataDir are empty,
109+// they will be set to "/var/lib/juju".
110+func NewLxcContext(environName, environSeries, dataDir, hostDataDir string, caCert []byte,
111+ deployerTag string, addresser deployer.Addresser) *LxcContext {
112+ if dataDir == "" {
113+ dataDir = "/var/lib/juju"
114+ }
115+ if hostDataDir == "" {
116+ hostDataDir = "/var/lib/juju"
117+ }
118+ return &LxcContext{
119+ environName: environName,
120+ environSeries: environSeries,
121+ dataDir: dataDir,
122+ hostDataDir: hostDataDir,
123+ addresser: addresser,
124+ caCert: caCert,
125+ deployerTag: deployerTag,
126+ initDir: "/etc/init",
127+ logDir: "/var/log/juju",
128+ lxcContainerDir: "/var/lib/lxc",
129+ lxcAutoDir: "/etc/lxc/auto",
130+ }
131+}
132+
133+// DeployUnit creates a container for a unit and installs the required
134+// upstart job in it.
135+func (ctx *LxcContext) DeployUnit(unitName, initialPassword string) error {
136+ if ctx.configLinkExists(unitName) {
137+ return fmt.Errorf("unit %q is already deployed", unitName)
138+ }
139+
140+ // Create container and initialize it.
141+ containerName := ctx.containerName(unitName)
142+ unitTag := state.UnitTag(unitName)
143+ if err := ctx.createContainer(containerName); err != nil {
144+ return err
145+ }
146+ if err := ctx.initContainer(containerName, unitName, unitTag, initialPassword); err != nil {
147+ if e := ctx.destroyContainer(containerName); e != nil {
148+ return fmt.Errorf("cannot destroy container after init error '%v': %v", err, e)
149+ }
150+ return err
151+ }
152+ if err := ctx.createConfigLink(unitName); err != nil {
153+ return fmt.Errorf("cannot create auto start link: %v", err)
154+ }
155+ return startContainer(containerName)
156+}
157+
158+// RecallUnit destroys the container and removes the link in the
159+// /etc/lxc/auto folder.
160+func (ctx *LxcContext) RecallUnit(unitName string) error {
161+ if !ctx.configLinkExists(unitName) {
162+ return fmt.Errorf("unit %q is not deployed", unitName)
163+ }
164+ if err := ctx.destroyConfigLink(unitName); err != nil {
165+ return err
166+ }
167+
168+ // Destroy container.
169+ containerName := ctx.containerName(unitName)
170+ return ctx.destroyContainer(containerName)
171+}
172+
173+var autoRe = regexp.MustCompile("^juju-([a-z0-9-]+):([a-z0-9-]+):unit-([a-z0-9-]+)-([0-9]+)\\.conf$")
174+
175+// DeployedUnits returns which units are deployed right now.
176+func (ctx *LxcContext) DeployedUnits() ([]string, error) {
177+ fis, err := ioutil.ReadDir(ctx.lxcAutoDir)
178+ if err != nil {
179+ return nil, err
180+ }
181+ var installed []string
182+ for _, fi := range fis {
183+ groups := autoRe.FindStringSubmatch(fi.Name())
184+ if len(groups) == 5 {
185+ if groups[2] != ctx.deployerTag {
186+ continue
187+ }
188+ unitName := groups[3] + "/" + groups[4]
189+ if !state.IsUnitName(unitName) {
190+ continue
191+ }
192+ installed = append(installed, unitName)
193+ }
194+ }
195+ return installed, nil
196+}
197+
198+// upstartService returns an upstart.Service corresponding to the specified
199+// unit. Its name is badged according to the entity responsible for the
200+// context, so as to distinguish its own jobs from those installed by other
201+// means.
202+func (ctx *LxcContext) upstartService(unitName string) *upstart.Service {
203+ unitTag := state.UnitTag(unitName)
204+ svcName := "jujud-" + ctx.deployerTag + ":" + unitTag
205+ svc := upstart.NewService(svcName)
206+ svc.InitDir = ctx.initDir
207+ return svc
208+}
209+
210+// createContainer creates a new LXC container with the given name.
211+func (ctx *LxcContext) createContainer(containerName string) error {
212+ if ctx.containerExists(containerName) {
213+ return fmt.Errorf("container %q is already created", containerName)
214+ }
215+ return createContainer(containerName, ctx.environSeries)
216+}
217+
218+// initContainer creates the needed directories inside the container
219+// and copies the tools.
220+func (ctx *LxcContext) initContainer(containerName, unitName, unitTag, initialPassword string) error {
221+ hostToolsDir := agent.SharedToolsDir(ctx.hostDataDir, version.Current)
222+ toolsDir := ctx.containerDir(containerName, agent.ToolsDir(ctx.dataDir, unitTag))
223+ err := os.MkdirAll(toolsDir, 0777)
224+ if err != nil {
225+ return err
226+ }
227+ defer removeOnErr(&err, toolsDir)
228+
229+ agentsDir := ctx.containerDir(containerName, ctx.dataDir, "agents", unitTag)
230+ err = os.MkdirAll(agentsDir, 0777)
231+ if err != nil {
232+ return err
233+ }
234+ defer removeOnErr(&err, agentsDir)
235+
236+ logDir := ctx.containerDir(containerName, ctx.logDir)
237+ err = os.MkdirAll(ctx.containerDir(containerName, ctx.logDir), 0777)
238+ if err != nil {
239+ return err
240+ }
241+ defer removeOnErr(&err, logDir)
242+
243+ if err = copyTools(hostToolsDir, toolsDir); err != nil {
244+ return err
245+ }
246+ if _, err = agent.ChangeAgentTools(ctx.hostDataDir, unitTag, version.Current); err != nil {
247+ return err
248+ }
249+
250+ // Prepare the agent's configuration data.
251+ info := state.Info{
252+ Addrs: ctx.addresser.Addresses(),
253+ Tag: unitTag,
254+ CACert: ctx.caCert,
255+ }
256+ confDataDir := ctx.containerDir(containerName, ctx.dataDir)
257+ conf := &agent.Conf{
258+ DataDir: confDataDir,
259+ OldPassword: initialPassword,
260+ StateInfo: &info,
261+ }
262+ conf.StateInfo.Tag = unitTag
263+ conf.StateInfo.Password = ""
264+ if err = conf.Write(); err != nil {
265+ return err
266+ }
267+ defer removeOnErr(&err, conf.Dir())
268+
269+ // Install an upstart job that runs the unit agent.
270+ jujudPath := filepath.Join(toolsDir, "jujud")
271+ logPath := filepath.Join(ctx.logDir, unitTag+".log")
272+ cmd := strings.Join([]string{
273+ jujudPath, "unit",
274+ "--data-dir", ctx.dataDir,
275+ "--unit-name", unitName,
276+ "--debug", // TODO: propagate debug state sensibly
277+ }, " ")
278+ svc := ctx.upstartService(unitName)
279+ uconf := &upstart.Conf{
280+ Service: *svc,
281+ Desc: "juju unit agent for " + unitName,
282+ Cmd: cmd,
283+ Out: logPath,
284+ }
285+ return uconf.Install()
286+}
287+
288+// destroyContainer stops and removes the container.
289+func (ctx *LxcContext) destroyContainer(containerName string) error {
290+ if !ctx.containerExists(containerName) {
291+ return fmt.Errorf("container %q does not exist", containerName)
292+ }
293+ if err := stopContainer(containerName); err != nil {
294+ return err
295+ }
296+ if err := waitContainerStopped(containerName); err != nil {
297+ return err
298+ }
299+ return destroyContainer(containerName)
300+}
301+
302+// containerName returns a unique name for the container and its upstart
303+// script based on the unit name.
304+func (ctx *LxcContext) containerName(unitName string) string {
305+ unitTag := state.UnitTag(unitName)
306+ return "juju-" + ctx.environName + ":" + ctx.deployerTag + ":" + unitTag
307+}
308+
309+// configLinkExists checks if the configuration link for an automatic start exists.
310+func (ctx *LxcContext) configLinkExists(unitName string) bool {
311+ containerName := ctx.containerName(unitName)
312+ autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
313+ containerConfigPath := filepath.Join(ctx.lxcContainerDir, containerName, "config")
314+ targetPath, err := os.Readlink(autoConfigPath)
315+ return err == nil && targetPath == containerConfigPath
316+}
317+
318+// createConfigLink links from a container config to the LXC auto-start folder.
319+func (ctx *LxcContext) createConfigLink(unitName string) error {
320+ containerName := ctx.containerName(unitName)
321+ autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
322+ containerConfigPath := filepath.Join(ctx.lxcContainerDir, containerName, "config")
323+ return os.Symlink(containerConfigPath, autoConfigPath)
324+}
325+
326+// destroyConfigLink removes the link from a container config to the LXC auto-start folder.
327+func (ctx *LxcContext) destroyConfigLink(unitName string) error {
328+ containerName := ctx.containerName(unitName)
329+ autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
330+ return os.Remove(autoConfigPath)
331+}
332+
333+// containerDir returns the full qualified name of a directory
334+// inside the container.
335+func (ctx *LxcContext) containerDir(containerName string, dirs ...string) string {
336+ rootfs := []string{ctx.lxcContainerDir, containerName, "rootfs"}
337+ return filepath.Join(append(rootfs, dirs...)...)
338+}
339+
340+// containerExists checks if the container is created.
341+func (ctx *LxcContext) containerExists(containerName string) bool {
342+ fi, err := os.Stat(ctx.containerDir(containerName))
343+ if err != nil {
344+ if os.IsNotExist(err) {
345+ return false
346+ }
347+ panic(err)
348+ }
349+ return fi.IsDir()
350+}
351+
352+// copyTools copies the hosts juju tools into the container.
353+func copyTools(hostToolsDir, containerToolsDir string) error {
354+ hostLen := len(hostToolsDir)
355+ copyTool := func(hostPath string, info os.FileInfo, err error) error {
356+ if err != nil {
357+ return err
358+ }
359+ containerPath := filepath.Join(containerToolsDir, hostPath[hostLen:])
360+ if info.IsDir() {
361+ // Create directory.
362+ if err := os.MkdirAll(containerPath, info.Mode().Perm()); err != nil {
363+ return err
364+ }
365+ } else {
366+ // Copy file.
367+ sourceFile, err := os.Open(hostPath)
368+ if err != nil {
369+ return err
370+ }
371+ defer sourceFile.Close()
372+ destFile, err := os.Create(containerPath)
373+ if err != nil {
374+ return err
375+ }
376+ defer destFile.Close()
377+ if _, err := io.Copy(destFile, sourceFile); err != nil {
378+ return err
379+ }
380+ return destFile.Chmod(info.Mode())
381+ }
382+ return nil
383+ }
384+ return filepath.Walk(hostToolsDir, copyTool)
385+}
386+
387+// removeOnErr removes a path in case of an error.
388+func removeOnErr(err *error, path string) {
389+ if *err != nil {
390+ if e := os.Remove(path); e != nil {
391+ log.Warningf("installer: cannot remove %q: %v", path, e)
392+ }
393+ }
394+}
395
396=== added file 'worker/deployer/lxc/export_test.go'
397--- worker/deployer/lxc/export_test.go 1970-01-01 00:00:00 +0000
398+++ worker/deployer/lxc/export_test.go 2013-05-09 23:22:32 +0000
399@@ -0,0 +1,27 @@
400+package lxc
401+
402+import (
403+ "launchpad.net/juju-core/version"
404+)
405+
406+type fakeAddresser struct{}
407+
408+func (*fakeAddresser) Addresses() []string {
409+ return []string{"s1:123", "s2:123"}
410+}
411+
412+func NewTestLxcContext(deployerTag, initDir, hostDataDir, lxcContainerDir, lxcAutoDir string) *LxcContext {
413+ return &LxcContext{
414+ environName: "lxc-test",
415+ environSeries: version.Current.Series,
416+ dataDir: "/var/lib/juju",
417+ hostDataDir: hostDataDir,
418+ addresser: &fakeAddresser{},
419+ caCert: []byte("test-cert"),
420+ deployerTag: deployerTag,
421+ initDir: initDir,
422+ logDir: "/var/log/juju",
423+ lxcContainerDir: lxcContainerDir,
424+ lxcAutoDir: lxcAutoDir,
425+ }
426+}
427
428=== added file 'worker/deployer/lxc/lxc.go'
429--- worker/deployer/lxc/lxc.go 1970-01-01 00:00:00 +0000
430+++ worker/deployer/lxc/lxc.go 2013-05-09 23:22:32 +0000
431@@ -0,0 +1,213 @@
432+package lxc
433+
434+import (
435+ "fmt"
436+ "io/ioutil"
437+ "os/exec"
438+ "regexp"
439+ "strings"
440+)
441+
442+const (
443+ defaultAddr = "10.0.3.1"
444+ defaultBridge = "lxcbr0"
445+)
446+
447+var (
448+ confPath string = "/etc/default/lxc"
449+ addrRE *regexp.Regexp = regexp.MustCompile(`\n\s*LXC_ADDR="(\d+.\d+.\d+.\d+)"`)
450+ bridgeRE *regexp.Regexp = regexp.MustCompile(`\n\s*LXC_BRIDGE="(\w+)"`)
451+)
452+
453+// Error reports the failure of a LXC command.
454+type Error struct {
455+ Name string
456+ Err error
457+ Output []string
458+}
459+
460+func (e Error) Error() string {
461+ if e.Output == nil {
462+ return fmt.Sprintf("error executing %q: %v", e.Name, e.Err)
463+ }
464+ if len(e.Output) == 1 {
465+ return fmt.Sprintf("error executing %q: %v", e.Name, e.Output[0])
466+ }
467+ return fmt.Sprintf("error executing %q: %s", e.Name, strings.Join(e.Output, "; "))
468+}
469+
470+// createContainer creates a new ubuntu cloud container.
471+func createContainer(containerName, series string) error {
472+ hostId := strings.Replace(containerName, ":", "-", -1)
473+ _, err := run("lxc-create",
474+ "-n", containerName,
475+ "-t", "ubuntu-cloud",
476+ "--",
477+ "-d",
478+ "-i", hostId,
479+ "-r", series)
480+ return err
481+}
482+
483+// startContainer starts a container.
484+func startContainer(containerName string) error {
485+ _, err := run("lxc-start", "-n", containerName)
486+ return err
487+}
488+
489+// stopContainer stops a container.
490+func stopContainer(containerName string) error {
491+ _, err := run("lxc-stop", "-n", containerName)
492+ return err
493+}
494+
495+// waitContainerStopped waits until a container stopped.
496+func waitContainerStopped(containerName string) error {
497+ _, err := run("lxc-wait", "-n", containerName, "-s", "STOPPED")
498+ return err
499+}
500+
501+// destroyContainer destroys a container.
502+func destroyContainer(containerName string) error {
503+ _, err := run("lxc-destroy", "-n", containerName)
504+ return err
505+}
506+
507+// startNetwork starts the lxc network subsystem.
508+func startNetwork() error {
509+ goal, _, err := networkStatus()
510+ if err != nil {
511+ return err
512+ }
513+ if goal != "start" {
514+ _, err := run("start", "lxc-net")
515+ if err != nil {
516+ return err
517+ }
518+ }
519+ return nil
520+}
521+
522+// stopNetwork stops the lxc network subsystem.
523+func stopNetwork() error {
524+ goal, _, err := networkStatus()
525+ if err != nil {
526+ return err
527+ }
528+ if goal != "stop" {
529+ _, err := run("stop", "lxc-net")
530+ if err != nil {
531+ return err
532+ }
533+ }
534+ return nil
535+}
536+
537+// isNotworkRunning checks if the lxc network subsystem
538+// is running.
539+func isNetworkRunning() (bool, error) {
540+ _, status, err := networkStatus()
541+ if err != nil {
542+ return false, err
543+ }
544+ return status == "running", nil
545+}
546+
547+// networkAttributes returns the lxc network attributes:
548+// starting IP address and bridge name.
549+func networkAttributes() (addr, bridge string, err error) {
550+ config, err := readConf()
551+ if err != nil {
552+ return "", "", err
553+ }
554+ addr = config["address"]
555+ if addr == "" {
556+ addr = defaultAddr
557+ }
558+ bridge = config["bridge"]
559+ if bridge == "" {
560+ bridge = defaultBridge
561+ }
562+ return addr, bridge, nil
563+}
564+
565+// networkStatus returns the status of the lxc network subsystem.
566+func networkStatus() (goal, status string, err error) {
567+ output, err := run("status", "lxc-net")
568+ if err != nil {
569+ return "", "", err
570+ }
571+ fields := strings.Fields(output)
572+ if len(fields) != 2 {
573+ return "", "", fmt.Errorf("unexpected status output: %q", output)
574+ }
575+ fields = strings.Split(fields[1], "/")
576+ if len(fields) != 2 {
577+ return "", "", fmt.Errorf("unexpected status output: %q", output)
578+ }
579+ return fields[0], fields[1], nil
580+}
581+
582+// run executes the passed command and returns the out.
583+func run(name string, args ...string) (string, error) {
584+ cmd := exec.Command(name, args...)
585+ // LXC tools do not use stdout and stderr in a predictable
586+ // way; based on experimentation, the most convenient
587+ // solution is to combine them and leave the client to
588+ // determine sanity as best it can.
589+ out, err := cmd.CombinedOutput()
590+ if err != nil {
591+ return "", runError(name, err, out)
592+ }
593+ return string(out), nil
594+}
595+
596+// runError creates an error if run fails.
597+func runError(name string, err error, out []byte) error {
598+ e := &Error{name, err, nil}
599+ for _, l := range strings.Split(string(out), "\n") {
600+ if strings.HasPrefix(l, name+": ") {
601+ // LXC tools do not always print their output with
602+ // the command name as prefix. The name is part of
603+ // the error struct, so stip it from the output if
604+ // printed.
605+ l = l[len(name)+2:]
606+ }
607+ if l != "" {
608+ e.Output = append(e.Output, l)
609+ }
610+ }
611+ return e
612+}
613+
614+// keyValues retrieves key/value pairs out of a command out.
615+func keyValues(raw string, sep string) map[string]string {
616+ kv := map[string]string{}
617+ lines := strings.Split(raw, "\n")
618+ for _, line := range lines {
619+ parts := strings.SplitN(line, sep, 2)
620+ if len(parts) == 2 {
621+ kv[parts[0]] = strings.TrimSpace(parts[1])
622+ }
623+ }
624+ return kv
625+}
626+
627+// readConf reads the LXC network address and bridge interface
628+// out of the configuration file /etc/default/lxc.
629+func readConf() (map[string]string, error) {
630+ conf := make(map[string]string)
631+ confData, err := ioutil.ReadFile(confPath)
632+ if err != nil {
633+ return nil, err
634+ }
635+ fetchValue := func(field string, re *regexp.Regexp) {
636+ groups := re.FindStringSubmatch(string(confData))
637+ if len(groups) == 2 {
638+ conf[field] = groups[1]
639+ }
640+ }
641+ fetchValue("address", addrRE)
642+ fetchValue("bridge", bridgeRE)
643+ return conf, nil
644+}
645
646=== added file 'worker/deployer/lxc/lxc_test.go'
647--- worker/deployer/lxc/lxc_test.go 1970-01-01 00:00:00 +0000
648+++ worker/deployer/lxc/lxc_test.go 2013-05-09 23:22:32 +0000
649@@ -0,0 +1,241 @@
650+package lxc_test
651+
652+import (
653+ "bytes"
654+ "fmt"
655+ "io/ioutil"
656+ "os"
657+ "path/filepath"
658+ stdtesting "testing"
659+ "text/template"
660+
661+ . "launchpad.net/gocheck"
662+ "launchpad.net/juju-core/environs/agent"
663+ "launchpad.net/juju-core/state"
664+ coretesting "launchpad.net/juju-core/testing"
665+ "launchpad.net/juju-core/version"
666+ "launchpad.net/juju-core/worker/deployer/lxc"
667+)
668+
669+func TestPackage(t *stdtesting.T) {
670+ coretesting.MgoTestPackage(t)
671+}
672+
673+type LxcContextSuite struct {
674+ LxcToolsFixture
675+}
676+
677+var _ = Suite(&LxcContextSuite{})
678+
679+func (s *LxcContextSuite) SetUpTest(c *C) {
680+ s.LxcToolsFixture.SetUp(c)
681+}
682+
683+func (s *LxcContextSuite) TearDownTest(c *C) {
684+ s.LxcToolsFixture.TearDown(c)
685+}
686+
687+func (s *LxcContextSuite) TestDeployRecall(c *C) {
688+ ctx0 := s.getContext(c, "test-entity-0")
689+ units, err := ctx0.DeployedUnits()
690+ c.Assert(err, IsNil)
691+ c.Assert(units, HasLen, 0)
692+ s.assertUpstartCount(c, 0)
693+
694+ err = ctx0.DeployUnit("foo/123", "some-password")
695+ c.Assert(err, IsNil)
696+ units, err = ctx0.DeployedUnits()
697+ c.Assert(err, IsNil)
698+ c.Assert(units, DeepEquals, []string{"foo/123"})
699+ s.assertUpstartCount(c, 1)
700+ s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
701+
702+ err = ctx0.RecallUnit("foo/123")
703+ c.Assert(err, IsNil)
704+ units, err = ctx0.DeployedUnits()
705+ c.Assert(err, IsNil)
706+ c.Assert(units, HasLen, 0)
707+ s.assertUpstartCount(c, 0)
708+ s.checkUnitRemoved(c, "test-entity-0", "foo/123")
709+}
710+
711+func (s *LxcContextSuite) TestIndependentManagers(c *C) {
712+ ctx0 := s.getContext(c, "test-entity-0")
713+ err := ctx0.DeployUnit("foo/123", "some-password")
714+ c.Assert(err, IsNil)
715+
716+ ctx1 := s.getContext(c, "test-entity-1")
717+ units, err := ctx1.DeployedUnits()
718+ c.Assert(err, IsNil)
719+ c.Assert(units, HasLen, 0)
720+
721+ err = ctx1.RecallUnit("foo/123")
722+ c.Assert(err, ErrorMatches, `unit "foo/123" is not deployed`)
723+ s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
724+
725+ units, err = ctx0.DeployedUnits()
726+ c.Assert(err, IsNil)
727+ c.Assert(units, DeepEquals, []string{"foo/123"})
728+ s.assertUpstartCount(c, 1)
729+ s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
730+}
731+
732+type LxcToolsFixture struct {
733+ origPath string
734+ hostDataDir string
735+ initDir string
736+ logDir string
737+ lxcContainerDir string
738+ lxcAutoDir string
739+ binDir string
740+}
741+
742+var fakeJujud = "#!/bin/bash\n# fake-jujud\nexit 0\n"
743+
744+func (fix *LxcToolsFixture) SetUp(c *C) {
745+ // Create some needed temporary directory and manipulate the path
746+ // for the fake binaries.
747+ fix.initDir = c.MkDir()
748+ fix.logDir = c.MkDir()
749+ fix.hostDataDir = c.MkDir()
750+ fix.lxcContainerDir = c.MkDir()
751+ fix.lxcAutoDir = c.MkDir()
752+ fix.binDir = c.MkDir()
753+ fix.origPath = os.Getenv("PATH")
754+ os.Setenv("PATH", fix.binDir+":"+fix.origPath)
755+ // Create the faked tools and binaries.
756+ toolsDir := agent.SharedToolsDir(fix.hostDataDir, version.Current)
757+ err := os.MkdirAll(toolsDir, 0755)
758+ c.Assert(err, IsNil)
759+ jujudPath := filepath.Join(toolsDir, "jujud")
760+ err = ioutil.WriteFile(jujudPath, []byte(fakeJujud), 0755)
761+ c.Assert(err, IsNil)
762+ urlPath := filepath.Join(toolsDir, "downloaded-url.txt")
763+ err = ioutil.WriteFile(urlPath, []byte("http://example.com/tools"), 0644)
764+ c.Assert(err, IsNil)
765+ fix.makeLxcBins(c)
766+ fix.makeBin(c, "status", `echo "blah stop/waiting"`)
767+ fix.makeBin(c, "stopped-status", `echo "blah stop/waiting"`)
768+ fix.makeBin(c, "started-status", `echo "blah start/running, process 666"`)
769+ fix.makeBin(c, "start", "cp $(which started-status) $(which status)")
770+ fix.makeBin(c, "stop", "cp $(which stopped-status) $(which status)")
771+}
772+
773+func (fix *LxcToolsFixture) TearDown(c *C) {
774+ os.Setenv("PATH", fix.origPath)
775+}
776+
777+var LxcBins = []struct {
778+ Name string
779+ Code string
780+}{
781+ {
782+ Name: "lxc-create",
783+ Code: `#!/bin/bash
784+# lxc-create
785+mkdir -p {{.LxcContainerDir}}/$2/rootfs
786+touch {{.LxcContainerDir}}/$2/config
787+exit 0`,
788+ }, {
789+ Name: "lxc-start",
790+ Code: `#!/bin/bash
791+# lxc-start
792+exit 0`,
793+ }, {
794+ Name: "lxc-stop",
795+ Code: `#!/bin/bash
796+# lxc-stop
797+exit 0`,
798+ }, {
799+ Name: "lxc-wait",
800+ Code: `#!/bin/bash
801+# lxc-wait
802+exit 0`,
803+ }, {
804+ Name: "lxc-destroy",
805+ Code: `#!/bin/bash
806+# lxc-destroy
807+rm -R {{.LxcContainerDir}}/$2
808+exit 0`,
809+ },
810+}
811+
812+func (fix *LxcToolsFixture) makeLxcBins(c *C) {
813+ conf := struct {
814+ LxcContainerDir string
815+ LxcAutoDir string
816+ }{
817+ LxcContainerDir: fix.lxcContainerDir,
818+ LxcAutoDir: fix.lxcAutoDir,
819+ }
820+ for _, lxcBin := range LxcBins {
821+ var buf bytes.Buffer
822+ binT := template.Must(template.New("").Parse(lxcBin.Code))
823+ err := binT.Execute(&buf, conf)
824+ c.Assert(err, IsNil)
825+ binPath := filepath.Join(fix.binDir, lxcBin.Name)
826+ err = ioutil.WriteFile(binPath, buf.Bytes(), 0755)
827+ c.Assert(err, IsNil)
828+ }
829+}
830+
831+func (fix *LxcToolsFixture) makeBin(c *C, name, script string) {
832+ path := filepath.Join(fix.binDir, name)
833+ err := ioutil.WriteFile(path, []byte("#!/bin/bash\n"+script), 0755)
834+ c.Assert(err, IsNil)
835+}
836+
837+func (fix *LxcToolsFixture) assertUpstartCount(c *C, count int) {
838+ fis, err := ioutil.ReadDir(fix.lxcAutoDir)
839+ c.Assert(err, IsNil)
840+ c.Assert(fis, HasLen, count)
841+}
842+
843+func (fix *LxcToolsFixture) getContext(c *C, deployerTag string) *lxc.LxcContext {
844+ return lxc.NewTestLxcContext(deployerTag, fix.initDir, fix.hostDataDir, fix.lxcContainerDir, fix.lxcAutoDir)
845+}
846+
847+func (fix *LxcToolsFixture) checkUnitInstalled(c *C, dname, uname, password string) {
848+ unitTag := state.UnitTag(uname)
849+ lxcContainerName := "juju-lxc-test:" + dname + ":" + unitTag
850+ lxcContainerPath := filepath.Join(fix.lxcContainerDir, lxcContainerName)
851+ lxcConfName := fmt.Sprintf("%s.conf", lxcContainerName)
852+ lxcConfPath := filepath.Join(fix.lxcAutoDir, lxcConfName)
853+
854+ _, err := os.Stat(lxcContainerPath)
855+ c.Assert(err, IsNil)
856+ _, err = os.Stat(lxcConfPath)
857+ c.Assert(err, IsNil)
858+
859+ dataDir := filepath.Join(fix.lxcContainerDir, lxcContainerName, "rootfs/var/lib/juju")
860+ conf, err := agent.ReadConf(dataDir, unitTag)
861+ c.Assert(err, IsNil)
862+ c.Assert(conf, DeepEquals, &agent.Conf{
863+ DataDir: dataDir,
864+ OldPassword: password,
865+ StateInfo: &state.Info{
866+ Addrs: []string{"s1:123", "s2:123"},
867+ CACert: []byte("test-cert"),
868+ Tag: unitTag,
869+ },
870+ })
871+
872+ toolsDir := agent.ToolsDir(dataDir, unitTag)
873+ jujudPath := filepath.Join(toolsDir, "jujud")
874+ jujudData, err := ioutil.ReadFile(jujudPath)
875+ c.Assert(err, IsNil)
876+ c.Assert(string(jujudData), Equals, fakeJujud)
877+}
878+
879+func (fix *LxcToolsFixture) checkUnitRemoved(c *C, ename, uname string) {
880+ unitTag := state.UnitTag(uname)
881+ lxcContainerName := "juju-lxc-test:" + ename + ":" + unitTag
882+ lxcContainerPath := filepath.Join(fix.lxcContainerDir, lxcContainerName)
883+ lxcConfName := fmt.Sprintf("%s.conf", lxcContainerName)
884+ lxcConfPath := filepath.Join(fix.lxcAutoDir, lxcConfName)
885+
886+ _, err := os.Stat(lxcContainerPath)
887+ c.Assert(os.IsNotExist(err), Equals, true)
888+ _, err = os.Stat(lxcConfPath)
889+ c.Assert(os.IsNotExist(err), Equals, true)
890+}

Subscribers

People subscribed via source and target branches