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

Proposed by Frank Mueller
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 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.
Revision history for this message
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

deployer/lxc: changes after reviews

968. By Frank Mueller

deployer/lxc: merged after review changes

Revision history for this message
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/

Revision history for this message
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

deployer: merged trunk and done review changes

Revision history for this message
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/

Revision history for this message
William Reade (fwereade) wrote :

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

Unmerged revisions

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

962. By Roger Peppe

state/api/params: new package

This enables us to break the cycle:
 launchpad.net/juju-core/juju
 launchpad.net/juju-core/environs
 launchpad.net/juju-core/state/api
 launchpad.net/juju-core/state/statecmd
 launchpad.net/juju-core/juju

R=bac, matthew.scott
CC=
https://codereview.appspot.com/7519043

961. By Roger Peppe

state/apiserver: new package

This is necessary to break the import cycle that happens
when the api server imports launchpad.net/juju.

This change is purely mechanical - no logic changes.

R=dimitern, TheMue
CC=
https://codereview.appspot.com/7499043

960. By Gary Poster

Add Delta and MarshalJSON for megawatcher

This code is largely from Roger, with tests from Benji and me. This simply
adds a Delta object that can marshal in the same way as PyJuju. It includes
stubs for the four types of entity information we should need.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'worker/deployer/export_test.go'
--- worker/deployer/export_test.go 2013-03-07 16:05:22 +0000
+++ worker/deployer/export_test.go 2013-03-11 20:52:18 +0000
@@ -16,3 +16,18 @@
16 logDir: logDir,16 logDir: logDir,
17 }17 }
18}18}
19
20func NewTestLxcContext(deployerName, initDir, hostDataDir, lxcContainerDir, lxcAutoDir string) *LxcContext {
21 return &LxcContext{
22 environName: "lxc-test",
23 dataDir: "/var/lib/juju",
24 hostDataDir: hostDataDir,
25 addresser: &fakeAddresser{},
26 caCert: []byte("test-cert"),
27 deployerName: deployerName,
28 initDir: initDir,
29 logDir: "/var/log/juju",
30 lxcContainerDir: lxcContainerDir,
31 lxcAutoDir: lxcAutoDir,
32 }
33}
1934
=== added file 'worker/deployer/lxc.go'
--- worker/deployer/lxc.go 1970-01-01 00:00:00 +0000
+++ worker/deployer/lxc.go 2013-03-11 20:52:18 +0000
@@ -0,0 +1,356 @@
1package deployer
2
3import (
4 "fmt"
5 "io"
6 "io/ioutil"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "regexp"
11 "strings"
12
13 "launchpad.net/juju-core/environs/agent"
14 "launchpad.net/juju-core/state"
15 "launchpad.net/juju-core/upstart"
16 "launchpad.net/juju-core/version"
17)
18
19// LxcContext stores details of the LXC environment on the local system.
20type LxcContext struct {
21 // environName identifies the environment the context is running in.
22 environName string
23
24 // addresser is used to get the current state server addresses at the time
25 // the given unit is deployed.
26 addresser Addresser
27
28 // caCert holds the CA certificate that will be used
29 // to validate the state server's certificate, in PEM format.
30 caCert []byte
31
32 // deployerName identifies the agent on whose behalf this context is running.
33 deployerName string
34
35 // initDir specifies the directory used by upstart on the local system.
36 // It is typically set to "/etc/init".
37 initDir string
38
39 // dataDir specifies the directory used by juju to store its state. It
40 // is typically set to "/var/lib/juju".
41 dataDir string
42
43 // hostDataDir specifies the directory used by juju on the host system to
44 // store its tools. It is typically set to "/var/lib/juju".
45 hostDataDir string
46
47 // logDir specifies the directory to which installed units will write
48 // their log files. It is typically set to "/var/log/juju".
49 logDir string
50
51 // lxcContainerDir specifies the base directory for all containers. It
52 // is typically set to "/var/lib/lxc".
53 lxcContainerDir string
54
55 // lxcAutoDir specifies the directory for the links to confguration files
56 // needed for autostarting containers. It is typically set to "/etc/lxc/auto".
57 lxcAutoDir string
58}
59
60var _ Context = (*LxcContext)(nil)
61
62// NewLxcContext returns a new LXC context, acting on behalf of the
63// entity specified in info, that deploys unit agents as upstart jobs in
64// "/etc/init" logging to "/var/log/juju". Paths to which agents and tools
65// are installed are relative to dataDir; if dataDir or hostDataDir are empty,
66// they will be set to "/var/lib/juju".
67func NewLxcContext(environName, dataDir, hostDataDir string, caCert []byte,
68 deployerName string, addresser Addresser) *LxcContext {
69 if dataDir == "" {
70 dataDir = "/var/lib/juju"
71 }
72 if hostDataDir == "" {
73 hostDataDir = "/var/lib/juju"
74 }
75 return &LxcContext{
76 environName: environName,
77 dataDir: dataDir,
78 hostDataDir: hostDataDir,
79 addresser: addresser,
80 caCert: caCert,
81 deployerName: deployerName,
82 initDir: "/etc/init",
83 logDir: "/var/log/juju",
84 lxcContainerDir: "/var/lib/lxc",
85 lxcAutoDir: "/etc/lxc/auto",
86 }
87}
88
89// DeployUnit creates a container for a unit and installs the required
90// upstart job in it.
91func (ctx *LxcContext) DeployUnit(unitName, initialPassword string) error {
92 if ctx.configLinkExists(unitName) {
93 return fmt.Errorf("unit %q is already deployed", unitName)
94 }
95
96 // Create container and initialize it.
97 containerName := ctx.containerName(unitName)
98 entityName := state.UnitEntityName(unitName)
99 if err := ctx.createContainer(containerName); err != nil {
100 return err
101 }
102 if err := ctx.initContainer(containerName, unitName, entityName, initialPassword); err != nil {
103 if e := ctx.destroyContainer(containerName); e != nil {
104 return fmt.Errorf("cannot destroy container after init error '%v': %v", err, e)
105 }
106 return err
107 }
108 return ctx.createConfigLink(unitName)
109}
110
111// RecallUnit destroys the container and removes the link in the
112// /etc/lxc/auto folder.
113func (ctx *LxcContext) RecallUnit(unitName string) error {
114 if !ctx.configLinkExists(unitName) {
115 return fmt.Errorf("unit %q is not deployed", unitName)
116 }
117 if err := ctx.destroyConfigLink(unitName); err != nil {
118 return err
119 }
120
121 // Destroy container.
122 containerName := ctx.containerName(unitName)
123 return ctx.destroyContainer(containerName)
124}
125
126var autoRe = regexp.MustCompile("^juju-([a-z0-9-]+):([a-z0-9-]+):unit-([a-z0-9-]+)-([0-9]+)\\.conf$")
127
128// DeployedUnits returns which units are deployed right now.
129func (ctx *LxcContext) DeployedUnits() ([]string, error) {
130 fis, err := ioutil.ReadDir(ctx.lxcAutoDir)
131 if err != nil {
132 return nil, err
133 }
134 var installed []string
135 for _, fi := range fis {
136 groups := autoRe.FindStringSubmatch(fi.Name())
137 if len(groups) == 5 {
138 if groups[2] != ctx.deployerName {
139 continue
140 }
141 unitName := groups[3] + "/" + groups[4]
142 if !state.IsUnitName(unitName) {
143 continue
144 }
145 installed = append(installed, unitName)
146 }
147 }
148 return installed, nil
149}
150
151// upstartService returns an upstart.Service corresponding to the specified
152// unit. Its name is badged according to the entity responsible for the
153// context, so as to distinguish its own jobs from those installed by other
154// means.
155func (ctx *LxcContext) upstartService(unitName string) *upstart.Service {
156 entityName := state.UnitEntityName(unitName)
157 svcName := "jujud-" + ctx.deployerName + ":" + entityName
158 svc := upstart.NewService(svcName)
159 svc.InitDir = ctx.initDir
160 return svc
161}
162
163// createContainer creates a new LXC container with the given name.
164func (ctx *LxcContext) createContainer(containerName string) error {
165 if ctx.containerExists(containerName) {
166 return fmt.Errorf("container %q is already created", containerName)
167 }
168 args := []string{
169 "-n", containerName, // Name of the container.
170 "-t", "ubuntu", // Standard ubuntu template.
171 }
172 return lxcRun("lxc-create", args...)
173}
174
175// initContainer creates the needed directories inside the container
176// and copies the tools.
177func (ctx *LxcContext) initContainer(containerName, unitName, entityName, initialPassword string) error {
178 hostToolsDir := agent.SharedToolsDir(ctx.hostDataDir, version.Current)
179 toolsDir := ctx.containerDir(containerName, agent.ToolsDir(ctx.dataDir, entityName))
180 err := os.MkdirAll(toolsDir, 0777)
181 if err != nil {
182 return err
183 }
184 defer removeOnErr(&err, toolsDir)
185
186 agentsDir := ctx.containerDir(containerName, ctx.dataDir, "agents", entityName)
187 err = os.MkdirAll(agentsDir, 0777)
188 if err != nil {
189 return err
190 }
191 defer removeOnErr(&err, agentsDir)
192
193 logDir := ctx.containerDir(containerName, ctx.logDir)
194 err = os.MkdirAll(ctx.containerDir(containerName, ctx.logDir), 0777)
195 if err != nil {
196 return err
197 }
198 defer removeOnErr(&err, logDir)
199
200 if err = copyTools(hostToolsDir, toolsDir); err != nil {
201 return err
202 }
203 if _, err = agent.ChangeAgentTools(ctx.hostDataDir, entityName, version.Current); err != nil {
204 return err
205 }
206
207 // Prepare the agent's configuration data.
208 info := state.Info{
209 Addrs: ctx.addresser.Addresses(),
210 EntityName: entityName,
211 CACert: ctx.caCert,
212 }
213 confDataDir := ctx.containerDir(containerName, ctx.dataDir)
214 conf := &agent.Conf{
215 DataDir: confDataDir,
216 OldPassword: initialPassword,
217 StateInfo: &info,
218 }
219 conf.StateInfo.EntityName = entityName
220 conf.StateInfo.Password = ""
221 if err = conf.Write(); err != nil {
222 return err
223 }
224 defer removeOnErr(&err, conf.Dir())
225
226 // Install an upstart job that runs the unit agent.
227 jujudPath := filepath.Join(toolsDir, "jujud")
228 logPath := filepath.Join(ctx.logDir, entityName+".log")
229 cmd := strings.Join([]string{
230 jujudPath, "unit",
231 "--data-dir", ctx.dataDir,
232 "--unit-name", unitName,
233 "--debug", // TODO: propagate debug state sensibly
234 }, " ")
235 svc := ctx.upstartService(unitName)
236 uconf := &upstart.Conf{
237 Service: *svc,
238 Desc: "juju unit agent for " + unitName,
239 Cmd: cmd,
240 Out: logPath,
241 }
242 return uconf.Install()
243}
244
245// destroyContainer stops and removes the container.
246func (ctx *LxcContext) destroyContainer(containerName string) error {
247 if !ctx.containerExists(containerName) {
248 return fmt.Errorf("container %q does not exist", containerName)
249 }
250 args := []string{
251 "-n", containerName,
252 }
253 if err := lxcRun("lxc-stop", args...); err != nil {
254 return err
255 }
256 if err := lxcRun("lxc-wait", "-n", containerName, "-s", "STOPPED"); err != nil {
257 return err
258 }
259 return lxcRun("lxc-destroy", "-n", containerName)
260}
261
262// containerName returns a unique name for the container and its upstart
263// script based on the unit name.
264func (ctx *LxcContext) containerName(unitName string) string {
265 entityName := state.UnitEntityName(unitName)
266 return "juju-" + ctx.environName + ":" + ctx.deployerName + ":" + entityName
267}
268
269// configLinkExists checks if the configuration link for an automatic start exists.
270func (ctx *LxcContext) configLinkExists(unitName string) bool {
271 containerName := ctx.containerName(unitName)
272 autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
273 containerConfigPath := filepath.Join(ctx.lxcContainerDir, containerName, "config")
274 targetPath, err := os.Readlink(autoConfigPath)
275 return err == nil && targetPath == containerConfigPath
276}
277
278// createConfigLink links from a container config to the LXC auto-start folder.
279func (ctx *LxcContext) createConfigLink(unitName string) error {
280 containerName := ctx.containerName(unitName)
281 autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
282 containerConfigPath := filepath.Join(ctx.lxcContainerDir, containerName, "config")
283 return os.Symlink(containerConfigPath, autoConfigPath)
284}
285
286// destroyConfigLink removes the link from a container config to the LXC auto-start folder.
287func (ctx *LxcContext) destroyConfigLink(unitName string) error {
288 containerName := ctx.containerName(unitName)
289 autoConfigPath := filepath.Join(ctx.lxcAutoDir, containerName+".conf")
290 return os.Remove(autoConfigPath)
291}
292
293// containerDir returns the full qualified name of a directory
294// inside the container.
295func (ctx *LxcContext) containerDir(containerName string, dirs ...string) string {
296 rootfs := []string{ctx.lxcContainerDir, containerName, "rootfs"}
297 return filepath.Join(append(rootfs, dirs...)...)
298}
299
300// containerExists checks if the container is created.
301func (ctx *LxcContext) containerExists(containerName string) bool {
302 fi, err := os.Stat(ctx.containerDir(containerName))
303 if err != nil {
304 if os.IsNotExist(err) {
305 return false
306 }
307 panic(err)
308 }
309 return fi.IsDir()
310}
311
312// lxcRun executes the passed command.
313func lxcRun(name string, args ...string) error {
314 cmd := exec.Command(name, args...)
315 // LXC tools do not use stdout and stderr in a predictable
316 // way; based on experimentation, the most convenient
317 // solution is to combine them and leave the client to
318 // determine sanity as best it can.
319 _, err := cmd.CombinedOutput()
320 return err
321}
322
323// copyTools copies the hosts juju tools into the container.
324func copyTools(hostToolsDir, containerToolsDir string) error {
325 hostLen := len(hostToolsDir)
326 copyTool := func(hostPath string, info os.FileInfo, err error) error {
327 if err != nil {
328 return err
329 }
330 containerPath := filepath.Join(containerToolsDir, hostPath[hostLen:])
331 if info.IsDir() {
332 // Create directory.
333 if err := os.MkdirAll(containerPath, info.Mode().Perm()); err != nil {
334 return err
335 }
336 } else {
337 // Copy file.
338 sourceFile, err := os.Open(hostPath)
339 if err != nil {
340 return err
341 }
342 defer sourceFile.Close()
343 destFile, err := os.Create(containerPath)
344 if err != nil {
345 return err
346 }
347 defer destFile.Close()
348 if _, err := io.Copy(destFile, sourceFile); err != nil {
349 return err
350 }
351 return destFile.Chmod(info.Mode())
352 }
353 return nil
354 }
355 return filepath.Walk(hostToolsDir, copyTool)
356}
0357
=== added file 'worker/deployer/lxc_test.go'
--- worker/deployer/lxc_test.go 1970-01-01 00:00:00 +0000
+++ worker/deployer/lxc_test.go 2013-03-11 20:52:18 +0000
@@ -0,0 +1,228 @@
1package deployer_test
2
3import (
4 "bytes"
5 "fmt"
6 "io/ioutil"
7 . "launchpad.net/gocheck"
8 "os"
9 "path/filepath"
10 "text/template"
11
12 "launchpad.net/juju-core/environs/agent"
13 "launchpad.net/juju-core/state"
14 "launchpad.net/juju-core/version"
15 "launchpad.net/juju-core/worker/deployer"
16)
17
18type LxcContextSuite struct {
19 LxcToolsFixture
20}
21
22var _ = Suite(&LxcContextSuite{})
23
24func (s *LxcContextSuite) SetUpTest(c *C) {
25 s.LxcToolsFixture.SetUp(c)
26}
27
28func (s *LxcContextSuite) TearDownTest(c *C) {
29 s.LxcToolsFixture.TearDown(c)
30}
31
32func (s *LxcContextSuite) TestDeployRecall(c *C) {
33 ctx0 := s.getContext(c, "test-entity-0")
34 units, err := ctx0.DeployedUnits()
35 c.Assert(err, IsNil)
36 c.Assert(units, HasLen, 0)
37 s.assertUpstartCount(c, 0)
38
39 err = ctx0.DeployUnit("foo/123", "some-password")
40 c.Assert(err, IsNil)
41 units, err = ctx0.DeployedUnits()
42 c.Assert(err, IsNil)
43 c.Assert(units, DeepEquals, []string{"foo/123"})
44 s.assertUpstartCount(c, 1)
45 s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
46
47 err = ctx0.RecallUnit("foo/123")
48 c.Assert(err, IsNil)
49 units, err = ctx0.DeployedUnits()
50 c.Assert(err, IsNil)
51 c.Assert(units, HasLen, 0)
52 s.assertUpstartCount(c, 0)
53 s.checkUnitRemoved(c, "test-entity-0", "foo/123")
54}
55
56func (s *LxcContextSuite) TestIndependentManagers(c *C) {
57 ctx0 := s.getContext(c, "test-entity-0")
58 err := ctx0.DeployUnit("foo/123", "some-password")
59 c.Assert(err, IsNil)
60
61 ctx1 := s.getContext(c, "test-entity-1")
62 units, err := ctx1.DeployedUnits()
63 c.Assert(err, IsNil)
64 c.Assert(units, HasLen, 0)
65
66 err = ctx1.RecallUnit("foo/123")
67 c.Assert(err, ErrorMatches, `unit "foo/123" is not deployed`)
68 s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
69
70 units, err = ctx0.DeployedUnits()
71 c.Assert(err, IsNil)
72 c.Assert(units, DeepEquals, []string{"foo/123"})
73 s.assertUpstartCount(c, 1)
74 s.checkUnitInstalled(c, "test-entity-0", "foo/123", "some-password")
75}
76
77type LxcToolsFixture struct {
78 origPath string
79 hostDataDir string
80 initDir string
81 logDir string
82 lxcContainerDir string
83 lxcAutoDir string
84 binDir string
85}
86
87func (fix *LxcToolsFixture) SetUp(c *C) {
88 // Create some needed temporary directory and manipulate the path
89 // for the fake binaries.
90 fix.initDir = c.MkDir()
91 fix.logDir = c.MkDir()
92 fix.hostDataDir = c.MkDir()
93 fix.lxcContainerDir = c.MkDir()
94 fix.lxcAutoDir = c.MkDir()
95 fix.binDir = c.MkDir()
96 fix.origPath = os.Getenv("PATH")
97 os.Setenv("PATH", fix.binDir+":"+fix.origPath)
98 // Create the faked tools and binaries.
99 toolsDir := agent.SharedToolsDir(fix.hostDataDir, version.Current)
100 err := os.MkdirAll(toolsDir, 0755)
101 c.Assert(err, IsNil)
102 jujudPath := filepath.Join(toolsDir, "jujud")
103 err = ioutil.WriteFile(jujudPath, []byte(fakeJujud), 0755)
104 c.Assert(err, IsNil)
105 urlPath := filepath.Join(toolsDir, "downloaded-url.txt")
106 err = ioutil.WriteFile(urlPath, []byte("http://example.com/tools"), 0644)
107 c.Assert(err, IsNil)
108 fix.makeLxcBins(c)
109 fix.makeBin(c, "status", `echo "blah stop/waiting"`)
110 fix.makeBin(c, "stopped-status", `echo "blah stop/waiting"`)
111 fix.makeBin(c, "started-status", `echo "blah start/running, process 666"`)
112 fix.makeBin(c, "start", "cp $(which started-status) $(which status)")
113 fix.makeBin(c, "stop", "cp $(which stopped-status) $(which status)")
114}
115
116func (fix *LxcToolsFixture) TearDown(c *C) {
117 os.Setenv("PATH", fix.origPath)
118}
119
120var LxcBins = []struct {
121 Name string
122 Code string
123}{
124 {
125 Name: "lxc-create",
126 Code: `#!/bin/bash
127# lxc-create
128mkdir -p {{.LxcContainerDir}}/$2/rootfs
129touch {{.LxcContainerDir}}/$2/config
130exit 0`,
131 }, {
132 Name: "lxc-stop",
133 Code: `#!/bin/bash
134# lxc-stop
135exit 0`,
136 }, {
137 Name: "lxc-wait",
138 Code: `#!/bin/bash
139# lxc-wait
140exit 0`,
141 }, {
142 Name: "lxc-destroy",
143 Code: `#!/bin/bash
144# lxc-destroy
145rm -R {{.LxcContainerDir}}/$2
146exit 0`,
147 },
148}
149
150func (fix *LxcToolsFixture) makeLxcBins(c *C) {
151 conf := struct {
152 LxcContainerDir string
153 LxcAutoDir string
154 }{
155 LxcContainerDir: fix.lxcContainerDir,
156 LxcAutoDir: fix.lxcAutoDir,
157 }
158 for _, lxcBin := range LxcBins {
159 var buf bytes.Buffer
160 binT := template.Must(template.New("").Parse(lxcBin.Code))
161 err := binT.Execute(&buf, conf)
162 c.Assert(err, IsNil)
163 binPath := filepath.Join(fix.binDir, lxcBin.Name)
164 err = ioutil.WriteFile(binPath, buf.Bytes(), 0755)
165 c.Assert(err, IsNil)
166 }
167}
168
169func (fix *LxcToolsFixture) makeBin(c *C, name, script string) {
170 path := filepath.Join(fix.binDir, name)
171 err := ioutil.WriteFile(path, []byte("#!/bin/bash\n"+script), 0755)
172 c.Assert(err, IsNil)
173}
174
175func (fix *LxcToolsFixture) assertUpstartCount(c *C, count int) {
176 fis, err := ioutil.ReadDir(fix.lxcAutoDir)
177 c.Assert(err, IsNil)
178 c.Assert(fis, HasLen, count)
179}
180
181func (fix *LxcToolsFixture) getContext(c *C, deployerName string) *deployer.LxcContext {
182 return deployer.NewTestLxcContext(deployerName, fix.initDir, fix.hostDataDir, fix.lxcContainerDir, fix.lxcAutoDir)
183}
184
185func (fix *LxcToolsFixture) checkUnitInstalled(c *C, dname, uname, password string) {
186 entityName := state.UnitEntityName(uname)
187 lxcContainerName := "juju-lxc-test:" + dname + ":" + entityName
188 lxcContainerPath := filepath.Join(fix.lxcContainerDir, lxcContainerName)
189 lxcConfName := fmt.Sprintf("%s.conf", lxcContainerName)
190 lxcConfPath := filepath.Join(fix.lxcAutoDir, lxcConfName)
191
192 _, err := os.Stat(lxcContainerPath)
193 c.Assert(err, IsNil)
194 _, err = os.Stat(lxcConfPath)
195 c.Assert(err, IsNil)
196
197 dataDir := filepath.Join(fix.lxcContainerDir, lxcContainerName, "rootfs/var/lib/juju")
198 conf, err := agent.ReadConf(dataDir, entityName)
199 c.Assert(err, IsNil)
200 c.Assert(conf, DeepEquals, &agent.Conf{
201 DataDir: dataDir,
202 OldPassword: password,
203 StateInfo: &state.Info{
204 Addrs: []string{"s1:123", "s2:123"},
205 CACert: []byte("test-cert"),
206 EntityName: entityName,
207 },
208 })
209
210 toolsDir := agent.ToolsDir(dataDir, entityName)
211 jujudPath := filepath.Join(toolsDir, "jujud")
212 jujudData, err := ioutil.ReadFile(jujudPath)
213 c.Assert(err, IsNil)
214 c.Assert(string(jujudData), Equals, fakeJujud)
215}
216
217func (fix *LxcToolsFixture) checkUnitRemoved(c *C, ename, uname string) {
218 entityName := state.UnitEntityName(uname)
219 lxcContainerName := "juju-lxc-test:" + ename + ":" + entityName
220 lxcContainerPath := filepath.Join(fix.lxcContainerDir, lxcContainerName)
221 lxcConfName := fmt.Sprintf("%s.conf", lxcContainerName)
222 lxcConfPath := filepath.Join(fix.lxcAutoDir, lxcConfName)
223
224 _, err := os.Stat(lxcContainerPath)
225 c.Assert(os.IsNotExist(err), Equals, true)
226 _, err = os.Stat(lxcConfPath)
227 c.Assert(os.IsNotExist(err), Equals, true)
228}

Subscribers

People subscribed via source and target branches