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

Proposed by Frank Mueller on 2013-03-08
Status: Work in progress
Proposed branch: lp:~themue/juju-core/014-deployer-lxc-context
Merge into: lp:~juju/juju-core/trunk
Diff against target: 616 lines (+599/-0)
3 files modified
worker/deployer/export_test.go (+15/-0)
worker/deployer/lxc.go (+356/-0)
worker/deployer/lxc_test.go (+228/-0)
To merge this branch: bzr merge lp:~themue/juju-core/014-deployer-lxc-context
Reviewer Review Type Date Requested Status
Juju Engineering 2013-03-08 Pending
Review via email: mp+152444@code.launchpad.net

Description of the change

deployer: added context for LXC

Added an implementation of the Context interface for
LXC. It is using the host machine as machine 0 and creates
LXC containers when deploying units. Also the link in
/etc/lxc/auto to the configuration for the automatic
upstart is created. Recalling a unit destroys the container
and removes the link.

https://codereview.appspot.com/7637045/

To post a comment you must log in.
Dimiter Naydenov (dimitern) wrote :
Download full text (4.0 KiB)

Looking good in general, but I think it needs some polishing. Some
suggestions below.

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

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode23
worker/deployer/lxc.go:23: // EnvironName identifies the environment the
manager is running in.
s/manager/context/ ?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode68
worker/deployer/lxc.go:68: dataDir = "/var/lib/juju"
s/dataDir/hostDataDir/

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode82
worker/deployer/lxc.go:82: // Deploy unit creates a container for a unit
and installs the
// DeployUnit creates a container for a unit and installs the required
upstart job in it.

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode92
worker/deployer/lxc.go:92: err := ctx.createContainer(containerName)
if err := ctx.createContainer(); err != nil { ...

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode103
worker/deployer/lxc.go:103: // RecallUnit destroys the container and
removes the autostart link.
s/autostart/upstart/ ?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode108
worker/deployer/lxc.go:108: err := ctx.destroyConfigLink(unitName)
if err := ..; err != nil { ...

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode120
worker/deployer/lxc.go:120: // DeployedUnits checks which units are
deployed right now.
s/checks/returns/ ?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode165
worker/deployer/lxc.go:165: if err != nil {
if _, err := ..; err != nil { ...

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode196
worker/deployer/lxc.go:196: err = copyTools(hostToolsDir, toolsDir)
ditto

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode218
worker/deployer/lxc.go:218: defer removeOnErr(&err, conf.Dir())
did you mean the err from line 200 or 215 here?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode227
worker/deployer/lxc.go:227: "--debug", // TODO: propagate debug state
sensibly
what does this mean?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode242
worker/deployer/lxc.go:242: return fmt.Errorf("container %q is not yet
created", containerName)
s/is not yet created/does not exist/ seems better

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode247
worker/deployer/lxc.go:247: _, err := lxcDo("lxc-stop", args...)
next 3 ifs: if err := ..; err != nil { return err } ?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode262
worker/deployer/lxc.go:262: // containerName creates a unique name for
the container and its upstart
creates or just returns?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode273
worker/deployer/lxc.go:273: _, err := os.Stat(autoConfigPath)
shouldn't we at least log the error?

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode278
worker/deployer/lxc.go:278: func (ctx *Lxc...

Read more...

967. By Frank Mueller on 2013-03-08

deployer/lxc: changes after reviews

968. By Frank Mueller on 2013-03-08

deployer/lxc: merged after review changes

Dimiter Naydenov (dimitern) wrote :

Much better, thanks. There are a few quirks which I still cannot quite
get though.

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

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode278
worker/deployer/lxc.go:278: func (ctx *LxcContext)
createConfigLink(unitName string) error {
On 2013/03/08 21:25:28, TheMue wrote:
> On 2013/03/08 20:25:32, dimitern wrote:
> > combine common parts of this, previous and next method in a single
helper
> > please.
> > Maybe even make container its own type with methods?

> Will think about it. As it not reusable I do not see much value right
now. But
> any other opinions showing me advantages are welcome.

Well, you have these 2 exact lines repeated 3 times:
containerName := ctx.containerName(unitName)
autoConfigPath := filepath.Join(ctx.AutoDir, containerName+".conf")

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc.go#newcode328
worker/deployer/lxc.go:328: fcopy := func(fi os.FileInfo) error {
On 2013/03/08 21:25:28, TheMue wrote:
> On 2013/03/08 20:25:32, dimitern wrote:
> > it might be better to extract this into a separate free func.

> Why would this help?

Just a thought, it might make the code a bit easier to read. But I don't
insist on it.

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc_test.go
File worker/deployer/lxc_test.go (right):

https://codereview.appspot.com/7637045/diff/1/worker/deployer/lxc_test.go#newcode10
worker/deployer/lxc_test.go:10:
On 2013/03/08 21:25:28, TheMue wrote:
> On 2013/03/08 20:25:32, dimitern wrote:
> > d

> IMHO it helps to get a quicker view of what project and what standard
or 3rd
> party packages we use. But yes, gocheck is 3rd party, so I'll move it.

Fair enough, it's a matter of taste.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go
File worker/deployer/lxc.go (right):

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode190
worker/deployer/lxc.go:190: if err = copyTools(hostToolsDir, toolsDir);
err != nil {
I see no point in reusing err here - err := should be just fine.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode193
worker/deployer/lxc.go:193: if _, err =
agent.ChangeAgentTools(ctx.HostDataDir, entityName, version.Current);
err != nil {
ditto

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode207
worker/deployer/lxc.go:207: if err = conf.Write(); err != nil {
ditto

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode210
worker/deployer/lxc.go:210: defer removeOnErr(&err, conf.Dir())
I don't think it's clearer now - maybe it deserves a comment or you
should call the defer earlier?

https://codereview.appspot.com/7637045/

William Reade (fwereade) wrote :
Download full text (3.3 KiB)

A few initial thoughts, probably not all actually relevant at this
stage, but I want to record them. I'll do this properly when I get home.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go
File worker/deployer/lxc.go (right):

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode20
worker/deployer/lxc.go:20: // TODO(mue) Integrate it into the deployer.
The Deployer shouldn't need any changes to use this context. Can we drop
this TODO?

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode23
worker/deployer/lxc.go:23: EnvironName string
Please make these private, to match SimpleContext.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode28
worker/deployer/lxc.go:28: StateInfo *state.Info
I guess we can use a state.Info here, but it'd be nice to be consistent.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode95
worker/deployer/lxc.go:95: ctx.destroyContainer(containerName)
Can this not error?

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode156
worker/deployer/lxc.go:156: return fmt.Errorf("container %q is already
created", containerName)
Surely we should destroy the pre-existing container?

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode210
worker/deployer/lxc.go:210: defer removeOnErr(&err, conf.Dir())
On 2013/03/08 21:58:04, TheMue wrote:
> On 2013/03/08 21:43:11, dimitern wrote:
> > I don't think it's clearer now - maybe it deserves a comment or you
should
> call
> > the defer earlier?

> It's a re-usage of the mechanism which SimpleContext has introduced.
Once the
> one err here in the method all defers clean up the created
directories. Using
> individual err's would let this logic fail, because only the dir where
the error
> has been caused would be removed.

I think this is risky, because every future editor needs o remember
never to return an "err" variable in its own scope.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode239
worker/deployer/lxc.go:239: if err := lxcRun("lxc-stop", args...); err
!= nil {
Does this always work even if the container's not running?

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode252
worker/deployer/lxc.go:252: return "juju-" + ctx.EnvironName + ":" +
ctx.StateInfo.EntityName + ":" + entityName
This StateInfo.EntityName thing is ridiculous -- I know it's my fault,
but please break out a distinct deployerName as in SimpleContext.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode260
worker/deployer/lxc.go:260: return err == nil
I don't think this is right.

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode289
worker/deployer/lxc.go:289: return false
Shouldn't we be a bit more careful about actual errors here?

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode311
worker/deployer/lxc.go:311: fcopy := func(fi os.FileInfo) error {
What if there are subdirectories?

https://codereview.appspot.com/7637045/diff/7001/worker/deployer/lxc.go#newcode319
worker/deployer/lx...

Read more...

969. By Frank Mueller on 2013-03-11

deployer: merged trunk and done review changes

William Reade (fwereade) wrote :

I may be confused, but I don't think this works how it should... it
seems to be running the units on the host system.

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go
File worker/deployer/lxc.go (right):

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode184
worker/deployer/lxc.go:184: defer removeOnErr(&err, toolsDir)
Why are we bothering to remove directories inside the container? Can't
we just treat a half-inited container as useless crack and trash the
whole thing?

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode191
worker/deployer/lxc.go:191: defer removeOnErr(&err, agentsDir)
Ditto.

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode198
worker/deployer/lxc.go:198: defer removeOnErr(&err, logDir)
Ditto.

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode203
worker/deployer/lxc.go:203: if _, err =
agent.ChangeAgentTools(ctx.hostDataDir, entityName, version.Current);
err != nil {
Why are we doing this in the host data dir instead of the container one?

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode224
worker/deployer/lxc.go:224: defer removeOnErr(&err, conf.Dir())
Why remove this on failure?

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode242
worker/deployer/lxc.go:242: return uconf.Install()
Doesn't this run the upstart job on the host system? AFAICT the initDir
used is always /etc/init; and if it weren't, woudln't this be guaranteed
to fail (because we'd be trying to start a job that doesn't exist?)

Note that, as part of fixing this, you'll want to tweak the upstart
package so that Install doesn't also Start.

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode263
worker/deployer/lxc.go:263: // script based on the unit name.
These aren't the same. There's no reason to give the upstart script a
name based on the environment -- it should go inside its container
(which *should* indeed have an env-differentiated name).

https://codereview.appspot.com/7637045/diff/15001/worker/deployer/lxc.go#newcode337
worker/deployer/lxc.go:337: // Copy file.
I think we should probably handle symlinks here -- they do sometimes
live in the tools directory, even if the situations are *currently*
non-overlapping.

https://codereview.appspot.com/7637045/

William Reade (fwereade) wrote :

Moved to WIP to keep queue tidy while waiting for discussion.

Unmerged revisions

969. By Frank Mueller on 2013-03-11

deployer: merged trunk and done review changes

968. By Frank Mueller on 2013-03-08

deployer/lxc: merged after review changes

967. By Frank Mueller on 2013-03-08

deployer/lxc: changes after reviews

966. By Frank Mueller on 2013-03-08

deployer: merged trunk and renamed manager to context

965. By Frank Mueller on 2013-03-08

deployer: added LXC manager

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-07 16:05:22 +0000
3+++ worker/deployer/export_test.go 2013-03-11 20:52:18 +0000
4@@ -16,3 +16,18 @@
5 logDir: logDir,
6 }
7 }
8+
9+func NewTestLxcContext(deployerName, initDir, hostDataDir, lxcContainerDir, lxcAutoDir string) *LxcContext {
10+ return &LxcContext{
11+ environName: "lxc-test",
12+ dataDir: "/var/lib/juju",
13+ hostDataDir: hostDataDir,
14+ addresser: &fakeAddresser{},
15+ caCert: []byte("test-cert"),
16+ deployerName: deployerName,
17+ initDir: initDir,
18+ logDir: "/var/log/juju",
19+ lxcContainerDir: lxcContainerDir,
20+ lxcAutoDir: lxcAutoDir,
21+ }
22+}
23
24=== added file 'worker/deployer/lxc.go'
25--- worker/deployer/lxc.go 1970-01-01 00:00:00 +0000
26+++ worker/deployer/lxc.go 2013-03-11 20:52:18 +0000
27@@ -0,0 +1,356 @@
28+package deployer
29+
30+import (
31+ "fmt"
32+ "io"
33+ "io/ioutil"
34+ "os"
35+ "os/exec"
36+ "path/filepath"
37+ "regexp"
38+ "strings"
39+
40+ "launchpad.net/juju-core/environs/agent"
41+ "launchpad.net/juju-core/state"
42+ "launchpad.net/juju-core/upstart"
43+ "launchpad.net/juju-core/version"
44+)
45+
46+// LxcContext stores details of the LXC environment on the local system.
47+type LxcContext struct {
48+ // environName identifies the environment the context is running in.
49+ environName string
50+
51+ // addresser is used to get the current state server addresses at the time
52+ // the given unit is deployed.
53+ addresser Addresser
54+
55+ // caCert holds the CA certificate that will be used
56+ // to validate the state server's certificate, in PEM format.
57+ caCert []byte
58+
59+ // deployerName identifies the agent on whose behalf this context is running.
60+ deployerName string
61+
62+ // initDir specifies the directory used by upstart on the local system.
63+ // It is typically set to "/etc/init".
64+ initDir string
65+
66+ // dataDir specifies the directory used by juju to store its state. It
67+ // is typically set to "/var/lib/juju".
68+ dataDir string
69+
70+ // hostDataDir specifies the directory used by juju on the host system to
71+ // store its tools. It is typically set to "/var/lib/juju".
72+ hostDataDir string
73+
74+ // logDir specifies the directory to which installed units will write
75+ // their log files. It is typically set to "/var/log/juju".
76+ logDir string
77+
78+ // lxcContainerDir specifies the base directory for all containers. It
79+ // is typically set to "/var/lib/lxc".
80+ lxcContainerDir string
81+
82+ // lxcAutoDir specifies the directory for the links to confguration files
83+ // needed for autostarting containers. It is typically set to "/etc/lxc/auto".
84+ lxcAutoDir string
85+}
86+
87+var _ Context = (*LxcContext)(nil)
88+
89+// NewLxcContext returns a new LXC context, acting on behalf of the
90+// entity specified in info, that deploys unit agents as upstart jobs in
91+// "/etc/init" logging to "/var/log/juju". Paths to which agents and tools
92+// are installed are relative to dataDir; if dataDir or hostDataDir are empty,
93+// they will be set to "/var/lib/juju".
94+func NewLxcContext(environName, dataDir, hostDataDir string, caCert []byte,
95+ deployerName string, addresser Addresser) *LxcContext {
96+ if dataDir == "" {
97+ dataDir = "/var/lib/juju"
98+ }
99+ if hostDataDir == "" {
100+ hostDataDir = "/var/lib/juju"
101+ }
102+ return &LxcContext{
103+ environName: environName,
104+ dataDir: dataDir,
105+ hostDataDir: hostDataDir,
106+ addresser: addresser,
107+ caCert: caCert,
108+ deployerName: deployerName,
109+ initDir: "/etc/init",
110+ logDir: "/var/log/juju",
111+ lxcContainerDir: "/var/lib/lxc",
112+ lxcAutoDir: "/etc/lxc/auto",
113+ }
114+}
115+
116+// DeployUnit creates a container for a unit and installs the required
117+// upstart job in it.
118+func (ctx *LxcContext) DeployUnit(unitName, initialPassword string) error {
119+ if ctx.configLinkExists(unitName) {
120+ return fmt.Errorf("unit %q is already deployed", unitName)
121+ }
122+
123+ // Create container and initialize it.
124+ containerName := ctx.containerName(unitName)
125+ entityName := state.UnitEntityName(unitName)
126+ if err := ctx.createContainer(containerName); err != nil {
127+ return err
128+ }
129+ if err := ctx.initContainer(containerName, unitName, entityName, initialPassword); err != nil {
130+ if e := ctx.destroyContainer(containerName); e != nil {
131+ return fmt.Errorf("cannot destroy container after init error '%v': %v", err, e)
132+ }
133+ return err
134+ }
135+ return ctx.createConfigLink(unitName)
136+}
137+
138+// RecallUnit destroys the container and removes the link in the
139+// /etc/lxc/auto folder.
140+func (ctx *LxcContext) RecallUnit(unitName string) error {
141+ if !ctx.configLinkExists(unitName) {
142+ return fmt.Errorf("unit %q is not deployed", unitName)
143+ }
144+ if err := ctx.destroyConfigLink(unitName); err != nil {
145+ return err
146+ }
147+
148+ // Destroy container.
149+ containerName := ctx.containerName(unitName)
150+ return ctx.destroyContainer(containerName)
151+}
152+
153+var autoRe = regexp.MustCompile("^juju-([a-z0-9-]+):([a-z0-9-]+):unit-([a-z0-9-]+)-([0-9]+)\\.conf$")
154+
155+// DeployedUnits returns which units are deployed right now.
156+func (ctx *LxcContext) DeployedUnits() ([]string, error) {
157+ fis, err := ioutil.ReadDir(ctx.lxcAutoDir)
158+ if err != nil {
159+ return nil, err
160+ }
161+ var installed []string
162+ for _, fi := range fis {
163+ groups := autoRe.FindStringSubmatch(fi.Name())
164+ if len(groups) == 5 {
165+ if groups[2] != ctx.deployerName {
166+ continue
167+ }
168+ unitName := groups[3] + "/" + groups[4]
169+ if !state.IsUnitName(unitName) {
170+ continue
171+ }
172+ installed = append(installed, unitName)
173+ }
174+ }
175+ return installed, nil
176+}
177+
178+// upstartService returns an upstart.Service corresponding to the specified
179+// unit. Its name is badged according to the entity responsible for the
180+// context, so as to distinguish its own jobs from those installed by other
181+// means.
182+func (ctx *LxcContext) upstartService(unitName string) *upstart.Service {
183+ entityName := state.UnitEntityName(unitName)
184+ svcName := "jujud-" + ctx.deployerName + ":" + entityName
185+ svc := upstart.NewService(svcName)
186+ svc.InitDir = ctx.initDir
187+ return svc
188+}
189+
190+// createContainer creates a new LXC container with the given name.
191+func (ctx *LxcContext) createContainer(containerName string) error {
192+ if ctx.containerExists(containerName) {
193+ return fmt.Errorf("container %q is already created", containerName)
194+ }
195+ args := []string{
196+ "-n", containerName, // Name of the container.
197+ "-t", "ubuntu", // Standard ubuntu template.
198+ }
199+ return lxcRun("lxc-create", args...)
200+}
201+
202+// initContainer creates the needed directories inside the container
203+// and copies the tools.
204+func (ctx *LxcContext) initContainer(containerName, unitName, entityName, initialPassword string) error {
205+ hostToolsDir := agent.SharedToolsDir(ctx.hostDataDir, version.Current)
206+ toolsDir := ctx.containerDir(containerName, agent.ToolsDir(ctx.dataDir, entityName))
207+ err := os.MkdirAll(toolsDir, 0777)
208+ if err != nil {
209+ return err
210+ }
211+ defer removeOnErr(&err, toolsDir)
212+
213+ agentsDir := ctx.containerDir(containerName, ctx.dataDir, "agents", entityName)
214+ err = os.MkdirAll(agentsDir, 0777)
215+ if err != nil {
216+ return err
217+ }
218+ defer removeOnErr(&err, agentsDir)
219+
220+ logDir := ctx.containerDir(containerName, ctx.logDir)
221+ err = os.MkdirAll(ctx.containerDir(containerName, ctx.logDir), 0777)
222+ if err != nil {
223+ return err
224+ }
225+ defer removeOnErr(&err, logDir)
226+
227+ if err = copyTools(hostToolsDir, toolsDir); err != nil {
228+ return err
229+ }
230+ if _, err = agent.ChangeAgentTools(ctx.hostDataDir, entityName, version.Current); err != nil {
231+ return err
232+ }
233+
234+ // Prepare the agent's configuration data.
235+ info := state.Info{
236+ Addrs: ctx.addresser.Addresses(),
237+ EntityName: entityName,
238+ CACert: ctx.caCert,
239+ }
240+ confDataDir := ctx.containerDir(containerName, ctx.dataDir)
241+ conf := &agent.Conf{
242+ DataDir: confDataDir,
243+ OldPassword: initialPassword,
244+ StateInfo: &info,
245+ }
246+ conf.StateInfo.EntityName = entityName
247+ conf.StateInfo.Password = ""
248+ if err = conf.Write(); err != nil {
249+ return err
250+ }
251+ defer removeOnErr(&err, conf.Dir())
252+
253+ // Install an upstart job that runs the unit agent.
254+ jujudPath := filepath.Join(toolsDir, "jujud")
255+ logPath := filepath.Join(ctx.logDir, entityName+".log")
256+ cmd := strings.Join([]string{
257+ jujudPath, "unit",
258+ "--data-dir", ctx.dataDir,
259+ "--unit-name", unitName,
260+ "--debug", // TODO: propagate debug state sensibly
261+ }, " ")
262+ svc := ctx.upstartService(unitName)
263+ uconf := &upstart.Conf{
264+ Service: *svc,
265+ Desc: "juju unit agent for " + unitName,
266+ Cmd: cmd,
267+ Out: logPath,
268+ }
269+ return uconf.Install()
270+}
271+
272+// destroyContainer stops and removes the container.
273+func (ctx *LxcContext) destroyContainer(containerName string) error {
274+ if !ctx.containerExists(containerName) {
275+ return fmt.Errorf("container %q does not exist", containerName)
276+ }
277+ args := []string{
278+ "-n", containerName,
279+ }
280+ if err := lxcRun("lxc-stop", args...); err != nil {
281+ return err
282+ }
283+ if err := lxcRun("lxc-wait", "-n", containerName, "-s", "STOPPED"); err != nil {
284+ return err
285+ }
286+ return lxcRun("lxc-destroy", "-n", containerName)
287+}
288+
289+// containerName returns a unique name for the container and its upstart
290+// script based on the unit name.
291+func (ctx *LxcContext) containerName(unitName string) string {
292+ entityName := state.UnitEntityName(unitName)
293+ return "juju-" + ctx.environName + ":" + ctx.deployerName + ":" + entityName
294+}
295+
296+// configLinkExists checks if the configuration link for an automatic start exists.
297+func (ctx *LxcContext) configLinkExists(unitName string) bool {
298+ containerName := ctx.containerName(unitName)
299+ autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
300+ containerConfigPath := filepath.Join(ctx.lxcContainerDir, containerName, "config")
301+ targetPath, err := os.Readlink(autoConfigPath)
302+ return err == nil && targetPath == containerConfigPath
303+}
304+
305+// createConfigLink links from a container config to the LXC auto-start folder.
306+func (ctx *LxcContext) createConfigLink(unitName string) error {
307+ containerName := ctx.containerName(unitName)
308+ autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
309+ containerConfigPath := filepath.Join(ctx.lxcContainerDir, containerName, "config")
310+ return os.Symlink(containerConfigPath, autoConfigPath)
311+}
312+
313+// destroyConfigLink removes the link from a container config to the LXC auto-start folder.
314+func (ctx *LxcContext) destroyConfigLink(unitName string) error {
315+ containerName := ctx.containerName(unitName)
316+ autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
317+ return os.Remove(autoConfigPath)
318+}
319+
320+// containerDir returns the full qualified name of a directory
321+// inside the container.
322+func (ctx *LxcContext) containerDir(containerName string, dirs ...string) string {
323+ rootfs := []string{ctx.lxcContainerDir, containerName, "rootfs"}
324+ return filepath.Join(append(rootfs, dirs...)...)
325+}
326+
327+// containerExists checks if the container is created.
328+func (ctx *LxcContext) containerExists(containerName string) bool {
329+ fi, err := os.Stat(ctx.containerDir(containerName))
330+ if err != nil {
331+ if os.IsNotExist(err) {
332+ return false
333+ }
334+ panic(err)
335+ }
336+ return fi.IsDir()
337+}
338+
339+// lxcRun executes the passed command.
340+func lxcRun(name string, args ...string) error {
341+ cmd := exec.Command(name, args...)
342+ // LXC tools do not use stdout and stderr in a predictable
343+ // way; based on experimentation, the most convenient
344+ // solution is to combine them and leave the client to
345+ // determine sanity as best it can.
346+ _, err := cmd.CombinedOutput()
347+ return err
348+}
349+
350+// copyTools copies the hosts juju tools into the container.
351+func copyTools(hostToolsDir, containerToolsDir string) error {
352+ hostLen := len(hostToolsDir)
353+ copyTool := func(hostPath string, info os.FileInfo, err error) error {
354+ if err != nil {
355+ return err
356+ }
357+ containerPath := filepath.Join(containerToolsDir, hostPath[hostLen:])
358+ if info.IsDir() {
359+ // Create directory.
360+ if err := os.MkdirAll(containerPath, info.Mode().Perm()); err != nil {
361+ return err
362+ }
363+ } else {
364+ // Copy file.
365+ sourceFile, err := os.Open(hostPath)
366+ if err != nil {
367+ return err
368+ }
369+ defer sourceFile.Close()
370+ destFile, err := os.Create(containerPath)
371+ if err != nil {
372+ return err
373+ }
374+ defer destFile.Close()
375+ if _, err := io.Copy(destFile, sourceFile); err != nil {
376+ return err
377+ }
378+ return destFile.Chmod(info.Mode())
379+ }
380+ return nil
381+ }
382+ return filepath.Walk(hostToolsDir, copyTool)
383+}
384
385=== added file 'worker/deployer/lxc_test.go'
386--- worker/deployer/lxc_test.go 1970-01-01 00:00:00 +0000
387+++ worker/deployer/lxc_test.go 2013-03-11 20:52:18 +0000
388@@ -0,0 +1,228 @@
389+package deployer_test
390+
391+import (
392+ "bytes"
393+ "fmt"
394+ "io/ioutil"
395+ . "launchpad.net/gocheck"
396+ "os"
397+ "path/filepath"
398+ "text/template"
399+
400+ "launchpad.net/juju-core/environs/agent"
401+ "launchpad.net/juju-core/state"
402+ "launchpad.net/juju-core/version"
403+ "launchpad.net/juju-core/worker/deployer"
404+)
405+
406+type LxcContextSuite struct {
407+ LxcToolsFixture
408+}
409+
410+var _ = Suite(&LxcContextSuite{})
411+
412+func (s *LxcContextSuite) SetUpTest(c *C) {
413+ s.LxcToolsFixture.SetUp(c)
414+}
415+
416+func (s *LxcContextSuite) TearDownTest(c *C) {
417+ s.LxcToolsFixture.TearDown(c)
418+}
419+
420+func (s *LxcContextSuite) TestDeployRecall(c *C) {
421+ ctx0 := s.getContext(c, "test-entity-0")
422+ units, err := ctx0.DeployedUnits()
423+ c.Assert(err, IsNil)
424+ c.Assert(units, HasLen, 0)
425+ s.assertUpstartCount(c, 0)
426+
427+ err = ctx0.DeployUnit("foo/123", "some-password")
428+ c.Assert(err, IsNil)
429+ units, err = ctx0.DeployedUnits()
430+ c.Assert(err, IsNil)
431+ c.Assert(units, DeepEquals, []string{"foo/123"})
432+ s.assertUpstartCount(c, 1)
433+ s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
434+
435+ err = ctx0.RecallUnit("foo/123")
436+ c.Assert(err, IsNil)
437+ units, err = ctx0.DeployedUnits()
438+ c.Assert(err, IsNil)
439+ c.Assert(units, HasLen, 0)
440+ s.assertUpstartCount(c, 0)
441+ s.checkUnitRemoved(c, "test-entity-0", "foo/123")
442+}
443+
444+func (s *LxcContextSuite) TestIndependentManagers(c *C) {
445+ ctx0 := s.getContext(c, "test-entity-0")
446+ err := ctx0.DeployUnit("foo/123", "some-password")
447+ c.Assert(err, IsNil)
448+
449+ ctx1 := s.getContext(c, "test-entity-1")
450+ units, err := ctx1.DeployedUnits()
451+ c.Assert(err, IsNil)
452+ c.Assert(units, HasLen, 0)
453+
454+ err = ctx1.RecallUnit("foo/123")
455+ c.Assert(err, ErrorMatches, `unit "foo/123" is not deployed`)
456+ s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
457+
458+ units, err = ctx0.DeployedUnits()
459+ c.Assert(err, IsNil)
460+ c.Assert(units, DeepEquals, []string{"foo/123"})
461+ s.assertUpstartCount(c, 1)
462+ s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
463+}
464+
465+type LxcToolsFixture struct {
466+ origPath string
467+ hostDataDir string
468+ initDir string
469+ logDir string
470+ lxcContainerDir string
471+ lxcAutoDir string
472+ binDir string
473+}
474+
475+func (fix *LxcToolsFixture) SetUp(c *C) {
476+ // Create some needed temporary directory and manipulate the path
477+ // for the fake binaries.
478+ fix.initDir = c.MkDir()
479+ fix.logDir = c.MkDir()
480+ fix.hostDataDir = c.MkDir()
481+ fix.lxcContainerDir = c.MkDir()
482+ fix.lxcAutoDir = c.MkDir()
483+ fix.binDir = c.MkDir()
484+ fix.origPath = os.Getenv("PATH")
485+ os.Setenv("PATH", fix.binDir+":"+fix.origPath)
486+ // Create the faked tools and binaries.
487+ toolsDir := agent.SharedToolsDir(fix.hostDataDir, version.Current)
488+ err := os.MkdirAll(toolsDir, 0755)
489+ c.Assert(err, IsNil)
490+ jujudPath := filepath.Join(toolsDir, "jujud")
491+ err = ioutil.WriteFile(jujudPath, []byte(fakeJujud), 0755)
492+ c.Assert(err, IsNil)
493+ urlPath := filepath.Join(toolsDir, "downloaded-url.txt")
494+ err = ioutil.WriteFile(urlPath, []byte("http://example.com/tools"), 0644)
495+ c.Assert(err, IsNil)
496+ fix.makeLxcBins(c)
497+ fix.makeBin(c, "status", `echo "blah stop/waiting"`)
498+ fix.makeBin(c, "stopped-status", `echo "blah stop/waiting"`)
499+ fix.makeBin(c, "started-status", `echo "blah start/running, process 666"`)
500+ fix.makeBin(c, "start", "cp $(which started-status) $(which status)")
501+ fix.makeBin(c, "stop", "cp $(which stopped-status) $(which status)")
502+}
503+
504+func (fix *LxcToolsFixture) TearDown(c *C) {
505+ os.Setenv("PATH", fix.origPath)
506+}
507+
508+var LxcBins = []struct {
509+ Name string
510+ Code string
511+}{
512+ {
513+ Name: "lxc-create",
514+ Code: `#!/bin/bash
515+# lxc-create
516+mkdir -p {{.LxcContainerDir}}/$2/rootfs
517+touch {{.LxcContainerDir}}/$2/config
518+exit 0`,
519+ }, {
520+ Name: "lxc-stop",
521+ Code: `#!/bin/bash
522+# lxc-stop
523+exit 0`,
524+ }, {
525+ Name: "lxc-wait",
526+ Code: `#!/bin/bash
527+# lxc-wait
528+exit 0`,
529+ }, {
530+ Name: "lxc-destroy",
531+ Code: `#!/bin/bash
532+# lxc-destroy
533+rm -R {{.LxcContainerDir}}/$2
534+exit 0`,
535+ },
536+}
537+
538+func (fix *LxcToolsFixture) makeLxcBins(c *C) {
539+ conf := struct {
540+ LxcContainerDir string
541+ LxcAutoDir string
542+ }{
543+ LxcContainerDir: fix.lxcContainerDir,
544+ LxcAutoDir: fix.lxcAutoDir,
545+ }
546+ for _, lxcBin := range LxcBins {
547+ var buf bytes.Buffer
548+ binT := template.Must(template.New("").Parse(lxcBin.Code))
549+ err := binT.Execute(&buf, conf)
550+ c.Assert(err, IsNil)
551+ binPath := filepath.Join(fix.binDir, lxcBin.Name)
552+ err = ioutil.WriteFile(binPath, buf.Bytes(), 0755)
553+ c.Assert(err, IsNil)
554+ }
555+}
556+
557+func (fix *LxcToolsFixture) makeBin(c *C, name, script string) {
558+ path := filepath.Join(fix.binDir, name)
559+ err := ioutil.WriteFile(path, []byte("#!/bin/bash\n"+script), 0755)
560+ c.Assert(err, IsNil)
561+}
562+
563+func (fix *LxcToolsFixture) assertUpstartCount(c *C, count int) {
564+ fis, err := ioutil.ReadDir(fix.lxcAutoDir)
565+ c.Assert(err, IsNil)
566+ c.Assert(fis, HasLen, count)
567+}
568+
569+func (fix *LxcToolsFixture) getContext(c *C, deployerName string) *deployer.LxcContext {
570+ return deployer.NewTestLxcContext(deployerName, fix.initDir, fix.hostDataDir, fix.lxcContainerDir, fix.lxcAutoDir)
571+}
572+
573+func (fix *LxcToolsFixture) checkUnitInstalled(c *C, dname, uname, password string) {
574+ entityName := state.UnitEntityName(uname)
575+ lxcContainerName := "juju-lxc-test:" + dname + ":" + entityName
576+ lxcContainerPath := filepath.Join(fix.lxcContainerDir, lxcContainerName)
577+ lxcConfName := fmt.Sprintf("%s.conf", lxcContainerName)
578+ lxcConfPath := filepath.Join(fix.lxcAutoDir, lxcConfName)
579+
580+ _, err := os.Stat(lxcContainerPath)
581+ c.Assert(err, IsNil)
582+ _, err = os.Stat(lxcConfPath)
583+ c.Assert(err, IsNil)
584+
585+ dataDir := filepath.Join(fix.lxcContainerDir, lxcContainerName, "rootfs/var/lib/juju")
586+ conf, err := agent.ReadConf(dataDir, entityName)
587+ c.Assert(err, IsNil)
588+ c.Assert(conf, DeepEquals, &agent.Conf{
589+ DataDir: dataDir,
590+ OldPassword: password,
591+ StateInfo: &state.Info{
592+ Addrs: []string{"s1:123", "s2:123"},
593+ CACert: []byte("test-cert"),
594+ EntityName: entityName,
595+ },
596+ })
597+
598+ toolsDir := agent.ToolsDir(dataDir, entityName)
599+ jujudPath := filepath.Join(toolsDir, "jujud")
600+ jujudData, err := ioutil.ReadFile(jujudPath)
601+ c.Assert(err, IsNil)
602+ c.Assert(string(jujudData), Equals, fakeJujud)
603+}
604+
605+func (fix *LxcToolsFixture) checkUnitRemoved(c *C, ename, uname string) {
606+ entityName := state.UnitEntityName(uname)
607+ lxcContainerName := "juju-lxc-test:" + ename + ":" + entityName
608+ lxcContainerPath := filepath.Join(fix.lxcContainerDir, lxcContainerName)
609+ lxcConfName := fmt.Sprintf("%s.conf", lxcContainerName)
610+ lxcConfPath := filepath.Join(fix.lxcAutoDir, lxcConfName)
611+
612+ _, err := os.Stat(lxcContainerPath)
613+ c.Assert(os.IsNotExist(err), Equals, true)
614+ _, err = os.Stat(lxcConfPath)
615+ c.Assert(os.IsNotExist(err), Equals, true)
616+}

Subscribers

People subscribed via source and target branches