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