Merge lp:~thumper/juju-core/local-provider into lp:~go-bot/juju-core/trunk
- local-provider
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1476 |
Proposed branch: | lp:~thumper/juju-core/local-provider |
Merge into: | lp:~go-bot/juju-core/trunk |
Prerequisite: | lp:~thumper/juju-core/selectively-install-lxc |
Diff against target: |
704 lines (+353/-76) 9 files modified
cmd/jujud/machine.go (+20/-5) environs/all/all.go (+1/-0) environs/local/config.go (+47/-7) environs/local/environ.go (+148/-57) environs/local/environprovider.go (+20/-3) environs/local/environprovider_test.go (+9/-1) environs/local/export_test.go (+14/-3) environs/local/net.go (+27/-0) environs/local/storage/worker.go (+67/-0) |
To merge this branch: | bzr merge lp:~thumper/juju-core/local-provider |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+174930@code.launchpad.net |
Commit message
Enable the local provider.
This branch changes how the local storage is handled.
It really wasn't possible to have the client keep serving
the storage on dynamic ports and expect other running
services to work.
Also we need something always acting as the storage. After
discussion with William last night, we agreed that this
should be a worker in the machine agent.
Description of the change
Enable the local provider.
This branch changes how the local storage is handled.
It really wasn't possible to have the client keep serving
the storage on dynamic ports and expect other running
services to work.
Also we need something always acting as the storage. After
discussion with William last night, we agreed that this
should be a worker in the machine agent.
Tim Penhey (thumper) wrote : | # |
Frank Mueller (themue) wrote : | # |
LGTM, great work, but some comments.
https:/
File cmd/jujud/
https:/
cmd/jujud/
m.ContainerType() != instance.LXC {
Make "local" a const.
https:/
cmd/jujud/
Also a const with a speaking name for the "0".
https:/
File environs/
https:/
environs/
haven't yet bootstrapped an
Interesting hurdle. ;) But I would like a construct like
if !config.
return env.<findAGoodN
}
return nil
more. So that logical block of code has its own method.
https:/
environs/
setupLocalStorage() error {
Appreciate comment event for private methods.
https:/
environs/
Again I like a const here. In the error you name it "bootstrap
instance". So
if inst.Id() == bootstrapInstance { ... }
would read better.
https:/
environs/
&localInstance{
Const, see above.
Martin Packman (gz) wrote : | # |
Unless I'm missing something, having both config and JUJU_ envvars
appears to be redundant. I would strongly prefer not having the storage
ports configurable, and just dynamically assign them and pass through.
Have yet to actually play with this, but looks pretty close to useful.
https:/
File environs/
https:/
environs/
Any reasoning behind these default ports?
https:/
File environs/
https:/
environs/
[]instance.Id) ([]instance.
It's now possible to implement this correctly, by for instance calling
AllInstances then filtering.
Tim Penhey (thumper) wrote : | # |
Please take a look.
https:/
File cmd/jujud/
https:/
cmd/jujud/
m.ContainerType() != instance.LXC {
On 2013/07/16 10:16:35, mue wrote:
> Make "local" a const.
Done.
https:/
cmd/jujud/
On 2013/07/16 10:16:35, mue wrote:
> Also a const with a speaking name for the "0".
Done.
https:/
File environs/
https:/
environs/
On 2013/07/16 10:44:18, gz wrote:
> Any reasoning behind these default ports?
None at all, but I needed some defaults.
8000 and 8080 are often taken by other web thingies, and in the 8000
range is often thought of as http type ones, so I thought it reasonable.
https:/
File environs/
https:/
environs/
setupLocalStorage() error {
On 2013/07/16 10:16:35, mue wrote:
> Appreciate comment event for private methods.
Done.
https:/
environs/
On 2013/07/16 10:16:35, mue wrote:
> Again I like a const here. In the error you name it "bootstrap
instance". So
> if inst.Id() == bootstrapInstance { ... }
> would read better.
Done.
https:/
environs/
&localInstance{
On 2013/07/16 10:16:35, mue wrote:
> Const, see above.
Done.
Tim Penhey (thumper) wrote : | # |
On 2013/07/16 10:44:18, gz wrote:
> Unless I'm missing something, having both config and JUJU_ envvars
appears to be
> redundant. I would strongly prefer not having the storage ports
configurable,
> and just dynamically assign them and pass through.
Perhaps this was just being a little lazy as it meant that the worker
didn't
need to watch for environ changes in the configuration. It made the
worker
simpler, and that was what I was going for initially.
https:/
> environs/
> []instance.Id) ([]instance.
> It's now possible to implement this correctly, by for instance calling
> AllInstances then filtering.
I still kinda argue the point about caring for this.
Ian Booth (wallyworld) wrote : | # |
I shared Martin's concerns about the env variables. But we could land
this and fix it before the next release.
LGTM
https:/
File environs/
https:/
environs/
Should we try and do as much removal as we can here even if something
errors?
Tim Penhey (thumper) wrote : | # |
https:/
File environs/
https:/
environs/
On 2013/07/17 03:40:55, wallyworld wrote:
> Should we try and do as much removal as we can here even if something
errors?
While I don't expect errors here, it seems weird to try to special case
it.
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/local-provider into lp:juju-core failed. Below is the output from the failed tests.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
-------
FAIL: environ_test.go:35: environSuite.
[LOG] 42.62519 INFO juju.environs.local opening environment "test"
[LOG] 42.67884 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.67907 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.67912 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.67916 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.67937 ERROR juju.environs.local cannot find network interface "lxcbr0": net: no such interface
[LOG] 42.67938 ERROR juju.environs.local failure setting config: net: no such interface
environ_test.go:38:
c.Assert(err, gc.IsNil)
... value *errors.errorString = &errors.
-------
FAIL: environ_test.go:54: localJujuTestSu
[LOG] 42.86084 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.86103 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.86107 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.86111 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 42.89145 INFO juju.environs.local opening environment "test"
[LOG] 42.94455 ERROR juju.environs.local cannot find network interface "lxcbr0": net: no such interface
[LOG] 42.94458 ERROR juju.environs.local failure setting config: net: no such in...
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/local-provider into lp:juju-core failed. Below is the output from the failed tests.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
-------
FAIL: environ_test.go:71: localJujuTestSu
environ_test.go:72:
s.Tests.
/home/tarmac/
c.Check(err, IsNil)
... value *golxc.Error = &golxc.
-------
PANIC: environ_
[LOG] 62.75077 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 62.75101 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 62.75105 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 62.75109 TRACE juju.environs.local creating directory /tmp/gocheck-
[LOG] 62.78159 INFO juju.environs.local opening environment "test"
[LOG] 62.83570 DEBUG juju.environs.local getAddressForIn
[LOG] 62.83574 DEBUG juju.environs.local found "127.0.0.1" as address for "lxcbr0"
[LOG] 62.88695 DEBUG juju.environs.local checking 127.0.0.1:8040 to see if machine agent running storage listener
[LOG] 62.88740 DEBUG juju.environs.local yes, don't start local storage listeners
[LOG] 62.91520 ERROR juju.container.lxc failed getting all instances: error executing "lxc-ls": exec: "lxc-ls": executable file not found in $PATH
... Panic: Fixture has panicked (see related PANIC)
OOPS: 9 passed, 2 skipped, 1 FAILED, 3 MISSED
--- FAIL: TestLocal (0.78 seconds)
FAIL
FAIL launchpad.ne...
Preview Diff
1 | === modified file 'cmd/jujud/machine.go' |
2 | --- cmd/jujud/machine.go 2013-07-16 06:18:45 +0000 |
3 | +++ cmd/jujud/machine.go 2013-07-17 05:56:24 +0000 |
4 | @@ -5,9 +5,17 @@ |
5 | |
6 | import ( |
7 | "fmt" |
8 | + "os" |
9 | + "path/filepath" |
10 | + "time" |
11 | + |
12 | "launchpad.net/gnuflag" |
13 | + "launchpad.net/tomb" |
14 | + |
15 | "launchpad.net/juju-core/charm" |
16 | "launchpad.net/juju-core/cmd" |
17 | + localstorage "launchpad.net/juju-core/environs/local/storage" |
18 | + "launchpad.net/juju-core/environs/provider" |
19 | "launchpad.net/juju-core/instance" |
20 | "launchpad.net/juju-core/log" |
21 | "launchpad.net/juju-core/state" |
22 | @@ -21,11 +29,10 @@ |
23 | "launchpad.net/juju-core/worker/machiner" |
24 | "launchpad.net/juju-core/worker/provisioner" |
25 | "launchpad.net/juju-core/worker/resumer" |
26 | - "launchpad.net/tomb" |
27 | - "path/filepath" |
28 | - "time" |
29 | ) |
30 | |
31 | +const bootstrapMachineId = "0" |
32 | + |
33 | var retryDelay = 3 * time.Second |
34 | |
35 | // MachineAgent is a cmd.Command responsible for running a machine agent. |
36 | @@ -100,7 +107,7 @@ |
37 | return a.StateWorker() |
38 | }) |
39 | } |
40 | - if a.MachineId == "0" { |
41 | + if a.MachineId == bootstrapMachineId { |
42 | // If we're bootstrapping, we don't have an API |
43 | // server to connect to, so start the state worker regardless. |
44 | |
45 | @@ -181,12 +188,20 @@ |
46 | // containers, it is likely that we will want an LXC provisioner on a KVM |
47 | // machine, and once we get nested LXC containers, we can remove this |
48 | // check. |
49 | - if m.ContainerType() != instance.LXC { |
50 | + providerType := os.Getenv("JUJU_PROVIDER_TYPE") |
51 | + if providerType != provider.Local && m.ContainerType() != instance.LXC { |
52 | workerName := fmt.Sprintf("%s-provisioner", provisioner.LXC) |
53 | runner.StartWorker(workerName, func() (worker.Worker, error) { |
54 | return provisioner.NewProvisioner(provisioner.LXC, st, a.MachineId, dataDir), nil |
55 | }) |
56 | } |
57 | + // Take advantage of special knowledge here in that we will only ever want |
58 | + // the storage provider on one machine, and that is the "bootstrap" node. |
59 | + if providerType == provider.Local && m.Id() == bootstrapMachineId { |
60 | + runner.StartWorker("local-storage", func() (worker.Worker, error) { |
61 | + return localstorage.NewWorker(), nil |
62 | + }) |
63 | + } |
64 | for _, job := range m.Jobs() { |
65 | switch job { |
66 | case state.JobHostUnits: |
67 | |
68 | === modified file 'environs/all/all.go' |
69 | --- environs/all/all.go 2013-07-16 03:33:07 +0000 |
70 | +++ environs/all/all.go 2013-07-17 05:56:24 +0000 |
71 | @@ -7,6 +7,7 @@ |
72 | import ( |
73 | _ "launchpad.net/juju-core/environs/azure" |
74 | _ "launchpad.net/juju-core/environs/ec2" |
75 | + _ "launchpad.net/juju-core/environs/local" |
76 | _ "launchpad.net/juju-core/environs/maas" |
77 | _ "launchpad.net/juju-core/environs/openstack" |
78 | ) |
79 | |
80 | === modified file 'environs/local/config.go' |
81 | --- environs/local/config.go 2013-07-16 21:56:23 +0000 |
82 | +++ environs/local/config.go 2013-07-17 05:56:24 +0000 |
83 | @@ -17,13 +17,23 @@ |
84 | return os.Getuid() == 0 |
85 | } |
86 | |
87 | -var configChecker = schema.StrictFieldMap( |
88 | - schema.Fields{ |
89 | - "root-dir": schema.String(), |
90 | - }, |
91 | - schema.Defaults{ |
92 | - "root-dir": "", |
93 | - }, |
94 | +var ( |
95 | + configFields = schema.Fields{ |
96 | + "root-dir": schema.String(), |
97 | + "bootstrap-ip": schema.String(), |
98 | + "storage-port": schema.Int(), |
99 | + "shared-storage-port": schema.Int(), |
100 | + } |
101 | + // The port defaults below are not entirely arbitrary. Local user web |
102 | + // frameworks often use 8000 or 8080, so I didn't want to use either of |
103 | + // these, but did want the familiarity of using something in the 8000 |
104 | + // range. |
105 | + configDefaults = schema.Defaults{ |
106 | + "root-dir": "", |
107 | + "bootstrap-ip": schema.Omit, |
108 | + "storage-port": 8040, |
109 | + "shared-storage-port": 8041, |
110 | + } |
111 | ) |
112 | |
113 | type environConfig struct { |
114 | @@ -77,6 +87,36 @@ |
115 | return filepath.Join(c.rootDir(), "log") |
116 | } |
117 | |
118 | +// A config is bootstrapped if the bootstrap-ip address has been set. |
119 | +func (c *environConfig) bootstrapped() bool { |
120 | + _, found := c.attrs["bootstrap-ip"] |
121 | + return found |
122 | +} |
123 | + |
124 | +func (c *environConfig) bootstrapIPAddress() string { |
125 | + addr, found := c.attrs["bootstrap-ip"] |
126 | + if found { |
127 | + return addr.(string) |
128 | + } |
129 | + return "" |
130 | +} |
131 | + |
132 | +func (c *environConfig) storagePort() int { |
133 | + return int(c.attrs["storage-port"].(int64)) |
134 | +} |
135 | + |
136 | +func (c *environConfig) sharedStoragePort() int { |
137 | + return int(c.attrs["shared-storage-port"].(int64)) |
138 | +} |
139 | + |
140 | +func (c *environConfig) storageAddr() string { |
141 | + return fmt.Sprintf("%s:%d", c.bootstrapIPAddress(), c.storagePort()) |
142 | +} |
143 | + |
144 | +func (c *environConfig) sharedStorageAddr() string { |
145 | + return fmt.Sprintf("%s:%d", c.bootstrapIPAddress(), c.sharedStoragePort()) |
146 | +} |
147 | + |
148 | func (c *environConfig) configFile(filename string) string { |
149 | return filepath.Join(c.rootDir(), filename) |
150 | } |
151 | |
152 | === modified file 'environs/local/environ.go' |
153 | --- environs/local/environ.go 2013-07-17 01:04:36 +0000 |
154 | +++ environs/local/environ.go 2013-07-17 05:56:24 +0000 |
155 | @@ -15,6 +15,7 @@ |
156 | |
157 | "launchpad.net/juju-core/agent" |
158 | "launchpad.net/juju-core/constraints" |
159 | + "launchpad.net/juju-core/container/lxc" |
160 | "launchpad.net/juju-core/environs" |
161 | "launchpad.net/juju-core/environs/config" |
162 | "launchpad.net/juju-core/environs/localstorage" |
163 | @@ -31,6 +32,10 @@ |
164 | // containers being created are able to communicate with it simply. |
165 | const lxcBridgeName = "lxcbr0" |
166 | |
167 | +// boostrapInstanceId is just the name we give to the bootstrap machine. |
168 | +// Using "localhost" because it is, and it makes sense. |
169 | +const boostrapInstanceId = "localhost" |
170 | + |
171 | // upstartScriptLocation is parameterised purely for testing purposes as we |
172 | // don't really want to be installing and starting scripts as root for |
173 | // testing. |
174 | @@ -55,6 +60,7 @@ |
175 | name string |
176 | sharedStorageListener net.Listener |
177 | storageListener net.Listener |
178 | + containerManager lxc.ContainerManager |
179 | } |
180 | |
181 | // Name is specified in the Environ interface. |
182 | @@ -119,16 +125,9 @@ |
183 | return err |
184 | } |
185 | |
186 | - // Work out the ip address of the lxc bridge, and use that for the mongo config. |
187 | - bridgeAddress, err := env.findBridgeAddress() |
188 | - if err != nil { |
189 | - return err |
190 | - } |
191 | - logger.Debugf("found %q as address for %q", bridgeAddress, lxcBridgeName) |
192 | - |
193 | // Before we write the agent config file, we need to make sure the |
194 | // instance is saved in the StateInfo. |
195 | - bootstrapId := instance.Id("localhost") |
196 | + bootstrapId := instance.Id(boostrapInstanceId) |
197 | if err := environs.SaveState(env.Storage(), &environs.BootstrapState{[]instance.Id{bootstrapId}}); err != nil { |
198 | logger.Errorf("failed to save state instances: %v", err) |
199 | return err |
200 | @@ -143,7 +142,7 @@ |
201 | |
202 | // Have to initialize the state configuration with localhost so we get |
203 | // "special" permissions. |
204 | - stateConnection, err := env.initialStateConfiguration("localhost", cons) |
205 | + stateConnection, err := env.initialStateConfiguration(boostrapInstanceId, cons) |
206 | if err != nil { |
207 | return err |
208 | } |
209 | @@ -164,7 +163,7 @@ |
210 | return env.config.Config |
211 | } |
212 | |
213 | -func createLocalStorageListener(dir string) (net.Listener, error) { |
214 | +func createLocalStorageListener(dir, address string) (net.Listener, error) { |
215 | info, err := os.Stat(dir) |
216 | if os.IsNotExist(err) { |
217 | return nil, fmt.Errorf("storage directory %q does not exist, bootstrap first", dir) |
218 | @@ -173,8 +172,7 @@ |
219 | } else if !info.Mode().IsDir() { |
220 | return nil, fmt.Errorf("%q exists but is not a directory (and it needs to be)", dir) |
221 | } |
222 | - // TODO(thumper): this needs fixing when we have actual machines. |
223 | - return localstorage.Serve("localhost:0", dir) |
224 | + return localstorage.Serve(address, dir) |
225 | } |
226 | |
227 | // SetConfig is specified in the Environ interface. |
228 | @@ -189,28 +187,77 @@ |
229 | env.config = config |
230 | env.name = config.Name() |
231 | |
232 | - if err := config.createDirs(); err != nil { |
233 | - return err |
234 | - } |
235 | - |
236 | - sharedStorageListener, err := createLocalStorageListener(config.sharedStorageDir()) |
237 | - if err != nil { |
238 | - return err |
239 | - } |
240 | - |
241 | - storageListener, err := createLocalStorageListener(config.storageDir()) |
242 | - if err != nil { |
243 | - sharedStorageListener.Close() |
244 | - return err |
245 | - } |
246 | - if env.sharedStorageListener != nil { |
247 | - env.sharedStorageListener.Close() |
248 | - } |
249 | - if env.storageListener != nil { |
250 | - env.storageListener.Close() |
251 | - } |
252 | - env.sharedStorageListener = sharedStorageListener |
253 | - env.storageListener = storageListener |
254 | + env.containerManager = lxc.NewContainerManager( |
255 | + lxc.ManagerConfig{ |
256 | + Name: env.config.namespace(), |
257 | + LogDir: env.config.logDir(), |
258 | + }) |
259 | + |
260 | + // Here is the end of normal config setting. |
261 | + if config.bootstrapped() { |
262 | + return nil |
263 | + } |
264 | + return env.bootstrapAddressAndStorage(cfg) |
265 | +} |
266 | + |
267 | +// bootstrapAddressAndStorage finishes up the setup of the environment in |
268 | +// situations where there is no machine agent running yet. |
269 | +func (env *localEnviron) bootstrapAddressAndStorage(cfg *config.Config) error { |
270 | + // If we get to here, it is because we haven't yet bootstrapped an |
271 | + // environment, and saved the config in it, or we are running a command |
272 | + // from the command line, so it is ok to work on the assumption that we |
273 | + // have direct access to the directories. |
274 | + if err := env.config.createDirs(); err != nil { |
275 | + return err |
276 | + } |
277 | + |
278 | + bridgeAddress, err := env.findBridgeAddress() |
279 | + if err != nil { |
280 | + return err |
281 | + } |
282 | + logger.Debugf("found %q as address for %q", bridgeAddress, lxcBridgeName) |
283 | + cfg, err = cfg.Apply(map[string]interface{}{ |
284 | + "bootstrap-ip": bridgeAddress, |
285 | + }) |
286 | + if err != nil { |
287 | + logger.Errorf("failed to apply new addresses to config: %v", err) |
288 | + return err |
289 | + } |
290 | + config, err := provider.newConfig(cfg) |
291 | + if err != nil { |
292 | + logger.Errorf("failed to create new environ config: %v", err) |
293 | + return err |
294 | + } |
295 | + env.config = config |
296 | + |
297 | + return env.setupLocalStorage() |
298 | +} |
299 | + |
300 | +// setupLocalStorage looks to see if there is someone listening on the storage |
301 | +// address port. If there is we assume that it is ours and all is good. If |
302 | +// there is no one listening on that port, create listeners for both storage |
303 | +// and the shared storage for the duration of the commands execution. |
304 | +func (env *localEnviron) setupLocalStorage() error { |
305 | + // Try to listen to the storageAddress. |
306 | + logger.Debugf("checking %s to see if machine agent running storage listener", env.config.storageAddr()) |
307 | + connection, err := net.Dial("tcp", env.config.storageAddr()) |
308 | + if err != nil { |
309 | + logger.Debugf("nope, start some") |
310 | + // These listeners are part of the environment structure so as to remain |
311 | + // referenced for the duration of the open environment. This is only for |
312 | + // environs that have been created due to a user command. |
313 | + env.storageListener, err = createLocalStorageListener(env.config.storageDir(), env.config.storageAddr()) |
314 | + if err != nil { |
315 | + return err |
316 | + } |
317 | + env.sharedStorageListener, err = createLocalStorageListener(env.config.sharedStorageDir(), env.config.sharedStorageAddr()) |
318 | + if err != nil { |
319 | + return err |
320 | + } |
321 | + } else { |
322 | + logger.Debugf("yes, don't start local storage listeners") |
323 | + connection.Close() |
324 | + } |
325 | return nil |
326 | } |
327 | |
328 | @@ -218,15 +265,44 @@ |
329 | func (env *localEnviron) StartInstance( |
330 | machineId, machineNonce, series string, |
331 | cons constraints.Value, |
332 | - info *state.Info, |
333 | + stateInfo *state.Info, |
334 | apiInfo *api.Info, |
335 | ) (instance.Instance, *instance.HardwareCharacteristics, error) { |
336 | - return nil, nil, fmt.Errorf("start instance not implemented") |
337 | + // We pretty much ignore the constraints. |
338 | + logger.Debugf("StartInstance: %q, %s", machineId, series) |
339 | + possibleTools, err := environs.FindInstanceTools(env, series, cons) |
340 | + if err != nil { |
341 | + return nil, nil, err |
342 | + } |
343 | + if len(possibleTools) == 0 { |
344 | + return nil, nil, fmt.Errorf("could not find appropriate tools") |
345 | + } |
346 | + |
347 | + tools := possibleTools[0] |
348 | + logger.Debugf("tools: %#v", tools) |
349 | + |
350 | + inst, err := env.containerManager.StartContainer( |
351 | + machineId, series, machineNonce, |
352 | + tools, env.config.Config, |
353 | + stateInfo, apiInfo) |
354 | + if err != nil { |
355 | + return nil, nil, err |
356 | + } |
357 | + // TODO(thumper): return some hardware characteristics. |
358 | + return inst, nil, nil |
359 | } |
360 | |
361 | // StopInstances is specified in the Environ interface. |
362 | -func (env *localEnviron) StopInstances([]instance.Instance) error { |
363 | - return fmt.Errorf("stop instance not implemented") |
364 | +func (env *localEnviron) StopInstances(instances []instance.Instance) error { |
365 | + for _, inst := range instances { |
366 | + if inst.Id() == boostrapInstanceId { |
367 | + return fmt.Errorf("cannot stop the bootstrap instance") |
368 | + } |
369 | + if err := env.containerManager.StopContainer(inst); err != nil { |
370 | + return err |
371 | + } |
372 | + } |
373 | + return nil |
374 | } |
375 | |
376 | // Instances is specified in the Environ interface. |
377 | @@ -246,19 +322,26 @@ |
378 | |
379 | // AllInstances is specified in the Environ interface. |
380 | func (env *localEnviron) AllInstances() (instances []instance.Instance, err error) { |
381 | - // TODO(thumper): get all the instances from the container manager |
382 | - instances = append(instances, &localInstance{"localhost", env}) |
383 | + instances = append(instances, &localInstance{boostrapInstanceId, env}) |
384 | + // Add in all the containers as well. |
385 | + lxcInstances, err := env.containerManager.ListContainers() |
386 | + if err != nil { |
387 | + return nil, err |
388 | + } |
389 | + for _, inst := range lxcInstances { |
390 | + instances = append(instances, &localInstance{inst.Id(), env}) |
391 | + } |
392 | return instances, nil |
393 | } |
394 | |
395 | // Storage is specified in the Environ interface. |
396 | func (env *localEnviron) Storage() environs.Storage { |
397 | - return localstorage.Client(env.storageListener.Addr().String()) |
398 | + return localstorage.Client(env.config.storageAddr()) |
399 | } |
400 | |
401 | // PublicStorage is specified in the Environ interface. |
402 | func (env *localEnviron) PublicStorage() environs.StorageReader { |
403 | - return localstorage.Client(env.sharedStorageListener.Addr().String()) |
404 | + return localstorage.Client(env.config.sharedStorageAddr()) |
405 | } |
406 | |
407 | // Destroy is specified in the Environ interface. |
408 | @@ -266,6 +349,24 @@ |
409 | if !env.config.runningAsRoot { |
410 | return fmt.Errorf("destroying a local environment must be done as root") |
411 | } |
412 | + // Kill all running instances. |
413 | + containers, err := env.containerManager.ListContainers() |
414 | + if err != nil { |
415 | + return err |
416 | + } |
417 | + for _, inst := range containers { |
418 | + if err := env.containerManager.StopContainer(inst); err != nil { |
419 | + return err |
420 | + } |
421 | + } |
422 | + |
423 | + logger.Infof("removing service %s", env.machineAgentServiceName()) |
424 | + machineAgent := upstart.NewService(env.machineAgentServiceName()) |
425 | + machineAgent.InitDir = upstartScriptLocation |
426 | + if err := machineAgent.Remove(); err != nil { |
427 | + logger.Errorf("could not remove machine agent service: %v", err) |
428 | + return err |
429 | + } |
430 | |
431 | logger.Infof("removing service %s", env.mongoServiceName()) |
432 | mongo := upstart.NewService(env.mongoServiceName()) |
433 | @@ -354,7 +455,7 @@ |
434 | } |
435 | // unpack the first tools into the agent dir. |
436 | tools := toolList[0] |
437 | - logger.Infof("tools: %#v", tools) |
438 | + logger.Debugf("tools: %#v", tools) |
439 | // brutally abuse our knowledge of storage to directly open the file |
440 | toolsUrl, err := url.Parse(tools.URL) |
441 | toolsLocation := filepath.Join(env.config.storageDir(), toolsUrl.Path) |
442 | @@ -381,6 +482,10 @@ |
443 | toolsDir, dataDir, logDir, tag, machineId, logConfig, env.config.Type()) |
444 | agent.Env["USER"] = env.config.user |
445 | agent.Env["HOME"] = os.Getenv("HOME") |
446 | + agent.Env["JUJU_STORAGE_DIR"] = env.config.storageDir() |
447 | + agent.Env["JUJU_STORAGE_ADDR"] = env.config.storageAddr() |
448 | + agent.Env["JUJU_SHARED_STORAGE_DIR"] = env.config.sharedStorageDir() |
449 | + agent.Env["JUJU_SHARED_STORAGE_ADDR"] = env.config.sharedStorageAddr() |
450 | |
451 | agent.InitDir = upstartScriptLocation |
452 | logger.Infof("installing service %s to %s", env.machineAgentServiceName(), agent.InitDir) |
453 | @@ -391,20 +496,6 @@ |
454 | return nil |
455 | } |
456 | |
457 | -func getAddressForInterface(interfaceName string) (string, error) { |
458 | - bridge, err := net.InterfaceByName(interfaceName) |
459 | - if err != nil { |
460 | - logger.Errorf("cannot find network interface %q: %v", interfaceName, err) |
461 | - return "", err |
462 | - } |
463 | - addrs, err := bridge.Addrs() |
464 | - if err != nil { |
465 | - logger.Errorf("cannot get addresses for network interface %q: %v", interfaceName, err) |
466 | - return "", err |
467 | - } |
468 | - return utils.GetIPv4Address(addrs) |
469 | -} |
470 | - |
471 | func (env *localEnviron) findBridgeAddress() (string, error) { |
472 | return getAddressForInterface(lxcBridgeName) |
473 | } |
474 | |
475 | === modified file 'environs/local/environprovider.go' |
476 | --- environs/local/environprovider.go 2013-07-17 02:34:39 +0000 |
477 | +++ environs/local/environprovider.go 2013-07-17 05:56:24 +0000 |
478 | @@ -54,11 +54,11 @@ |
479 | if err := config.Validate(cfg, old); err != nil { |
480 | return nil, err |
481 | } |
482 | - v, err := configChecker.Coerce(cfg.UnknownAttrs(), nil) |
483 | + validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) |
484 | if err != nil { |
485 | return nil, err |
486 | } |
487 | - localConfig := newEnvironConfig(cfg, v.(map[string]interface{})) |
488 | + localConfig := newEnvironConfig(cfg, validated) |
489 | // Before potentially creating directories, make sure that the |
490 | // root directory has not changed. |
491 | if old != nil { |
492 | @@ -71,6 +71,16 @@ |
493 | oldLocalConfig.rootDir(), |
494 | localConfig.rootDir()) |
495 | } |
496 | + if localConfig.storagePort() != oldLocalConfig.storagePort() { |
497 | + return nil, fmt.Errorf("cannot change storage-port from %v to %v", |
498 | + oldLocalConfig.storagePort(), |
499 | + localConfig.storagePort()) |
500 | + } |
501 | + if localConfig.sharedStoragePort() != oldLocalConfig.sharedStoragePort() { |
502 | + return nil, fmt.Errorf("cannot change shared-storage-port from %v to %v", |
503 | + oldLocalConfig.sharedStoragePort(), |
504 | + localConfig.sharedStoragePort()) |
505 | + } |
506 | } |
507 | dir := utils.NormalizePath(localConfig.rootDir()) |
508 | if dir == "." { |
509 | @@ -92,6 +102,12 @@ |
510 | # The default location is $JUJU_HOME/<ENV>. |
511 | # $JUJU_HOME defaults to ~/.juju |
512 | # root-dir: ~/.juju/local |
513 | + # Override the storage port if you have multiple local providers, or if the |
514 | + # default port is used by another program. |
515 | + # storage-port: 8040 |
516 | + # Override the shared storage port if you have multiple local providers, or if the |
517 | + # default port is used by another program. |
518 | + # shared-storage-port: 8041 |
519 | |
520 | `[1:] |
521 | } |
522 | @@ -114,7 +130,8 @@ |
523 | |
524 | // PrivateAddress implements environs.EnvironProvider.PrivateAddress. |
525 | func (environProvider) PrivateAddress() (string, error) { |
526 | - return "", fmt.Errorf("private address not implemented") |
527 | + // Get the IPv4 address from eth0 |
528 | + return getAddressForInterface("eth0") |
529 | } |
530 | |
531 | // InstanceId implements environs.EnvironProvider.InstanceId. |
532 | |
533 | === modified file 'environs/local/environprovider_test.go' |
534 | --- environs/local/environprovider_test.go 2013-07-16 03:28:34 +0000 |
535 | +++ environs/local/environprovider_test.go 2013-07-17 05:56:24 +0000 |
536 | @@ -7,21 +7,29 @@ |
537 | gc "launchpad.net/gocheck" |
538 | "launchpad.net/loggo" |
539 | |
540 | + "launchpad.net/juju-core/container/lxc" |
541 | + "launchpad.net/juju-core/environs/local" |
542 | "launchpad.net/juju-core/testing" |
543 | ) |
544 | |
545 | type baseProviderSuite struct { |
546 | testing.LoggingSuite |
547 | - home *testing.FakeHome |
548 | + lxc.TestSuite |
549 | + home *testing.FakeHome |
550 | + restore func() |
551 | } |
552 | |
553 | func (s *baseProviderSuite) SetUpTest(c *gc.C) { |
554 | s.LoggingSuite.SetUpTest(c) |
555 | + s.TestSuite.SetUpTest(c) |
556 | s.home = testing.MakeFakeHomeNoEnvironments(c, "test") |
557 | loggo.GetLogger("juju.environs.local").SetLogLevel(loggo.TRACE) |
558 | + s.restore = local.MockAddressForInterface() |
559 | } |
560 | |
561 | func (s *baseProviderSuite) TearDownTest(c *gc.C) { |
562 | + s.restore() |
563 | s.home.Restore() |
564 | + s.TestSuite.TearDownTest(c) |
565 | s.LoggingSuite.TearDownTest(c) |
566 | } |
567 | |
568 | === modified file 'environs/local/export_test.go' |
569 | --- environs/local/export_test.go 2013-07-16 21:56:22 +0000 |
570 | +++ environs/local/export_test.go 2013-07-17 05:56:24 +0000 |
571 | @@ -8,7 +8,10 @@ |
572 | "launchpad.net/juju-core/environs/config" |
573 | ) |
574 | |
575 | -var Provider = provider |
576 | +var ( |
577 | + Provider = provider |
578 | + SudoCallerIds = sudoCallerIds |
579 | +) |
580 | |
581 | // SetRootCheckFunction allows tests to override the check for a root user. |
582 | // The return value is the function to restore the old value. |
583 | @@ -51,6 +54,14 @@ |
584 | } |
585 | } |
586 | |
587 | -func SudoCallerIds() (uid, gid int, err error) { |
588 | - return sudoCallerIds() |
589 | +// MockAddressForInterface replaces the getAddressForInterface with a function |
590 | +// that returns a constant localhost ip address. |
591 | +func MockAddressForInterface() func() { |
592 | + getAddressForInterface = func(name string) (string, error) { |
593 | + logger.Debugf("getAddressForInterface called for %s", name) |
594 | + return "127.0.0.1", nil |
595 | + } |
596 | + return func() { |
597 | + getAddressForInterface = getAddressForInterfaceImpl |
598 | + } |
599 | } |
600 | |
601 | === added file 'environs/local/net.go' |
602 | --- environs/local/net.go 1970-01-01 00:00:00 +0000 |
603 | +++ environs/local/net.go 2013-07-17 05:56:24 +0000 |
604 | @@ -0,0 +1,27 @@ |
605 | +// Copyright 2013 Canonical Ltd. |
606 | +// Licensed under the AGPLv3, see LICENCE file for details. |
607 | +package local |
608 | + |
609 | +import ( |
610 | + "net" |
611 | + |
612 | + "launchpad.net/juju-core/utils" |
613 | +) |
614 | + |
615 | +var getAddressForInterface = getAddressForInterfaceImpl |
616 | + |
617 | +// getAddressForInterfaceImpl looks for the network interface |
618 | +// and returns the IPv4 address from the possible addresses. |
619 | +func getAddressForInterfaceImpl(interfaceName string) (string, error) { |
620 | + iface, err := net.InterfaceByName(interfaceName) |
621 | + if err != nil { |
622 | + logger.Errorf("cannot find network interface %q: %v", interfaceName, err) |
623 | + return "", err |
624 | + } |
625 | + addrs, err := iface.Addrs() |
626 | + if err != nil { |
627 | + logger.Errorf("cannot get addresses for network interface %q: %v", interfaceName, err) |
628 | + return "", err |
629 | + } |
630 | + return utils.GetIPv4Address(addrs) |
631 | +} |
632 | |
633 | === added directory 'environs/local/storage' |
634 | === added file 'environs/local/storage/worker.go' |
635 | --- environs/local/storage/worker.go 1970-01-01 00:00:00 +0000 |
636 | +++ environs/local/storage/worker.go 2013-07-17 05:56:24 +0000 |
637 | @@ -0,0 +1,67 @@ |
638 | +package storage |
639 | + |
640 | +import ( |
641 | + "os" |
642 | + |
643 | + "launchpad.net/loggo" |
644 | + "launchpad.net/tomb" |
645 | + |
646 | + "launchpad.net/juju-core/environs/localstorage" |
647 | + "launchpad.net/juju-core/worker" |
648 | +) |
649 | + |
650 | +var logger = loggo.GetLogger("juju.local.storage") |
651 | + |
652 | +type storageWorker struct { |
653 | + tomb tomb.Tomb |
654 | +} |
655 | + |
656 | +func NewWorker() worker.Worker { |
657 | + w := &storageWorker{} |
658 | + go func() { |
659 | + defer w.tomb.Done() |
660 | + w.tomb.Kill(w.waitForDeath()) |
661 | + }() |
662 | + return w |
663 | +} |
664 | + |
665 | +// Kill implements worker.Worker.Kill. |
666 | +func (s *storageWorker) Kill() { |
667 | + s.tomb.Kill(nil) |
668 | +} |
669 | + |
670 | +// Wait implements worker.Worker.Wait. |
671 | +func (s *storageWorker) Wait() error { |
672 | + return s.tomb.Wait() |
673 | +} |
674 | + |
675 | +func (s *storageWorker) waitForDeath() error { |
676 | + storageDir := os.Getenv("JUJU_STORAGE_DIR") |
677 | + storageAddr := os.Getenv("JUJU_STORAGE_ADDR") |
678 | + logger.Infof("serving %s on %s", storageDir, storageAddr) |
679 | + |
680 | + storageListener, err := localstorage.Serve(storageAddr, storageDir) |
681 | + if err != nil { |
682 | + logger.Errorf("error with local storage: %v", err) |
683 | + return err |
684 | + } |
685 | + defer storageListener.Close() |
686 | + |
687 | + sharedStorageDir := os.Getenv("JUJU_SHARED_STORAGE_DIR") |
688 | + sharedStorageAddr := os.Getenv("JUJU_SHARED_STORAGE_ADDR") |
689 | + logger.Infof("serving %s on %s", sharedStorageDir, sharedStorageAddr) |
690 | + |
691 | + sharedStorageListener, err := localstorage.Serve(sharedStorageAddr, sharedStorageDir) |
692 | + if err != nil { |
693 | + logger.Errorf("error with local storage: %v", err) |
694 | + return err |
695 | + } |
696 | + defer sharedStorageListener.Close() |
697 | + |
698 | + logger.Infof("storage routines started, awaiting death") |
699 | + |
700 | + <-s.tomb.Dying() |
701 | + |
702 | + logger.Infof("dying, closing storage listeners") |
703 | + return tomb.ErrDying |
704 | +} |
Reviewers: mp+174930_ code.launchpad. net,
Message:
Please take a look.
Description:
Enable the local provider.
This branch changes how the local storage is handled.
It really wasn't possible to have the client keep serving
the storage on dynamic ports and expect other running
services to work.
Also we need something always acting as the storage. After
discussion with William last night, we agreed that this
should be a worker in the machine agent.
https:/ /code.launchpad .net/~thumper/ juju-core/ local-provider/ +merge/ 174930
Requires: /code.launchpad .net/~thumper/ juju-core/ selectively- install- lxc/+merge/ 174928
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/11333043/
Affected files: machine. go local/config. go local/environ. go local/environpr ovider. go local/storage/ worker. go
A [revision details]
M cmd/jujud/
M environs/all/all.go
M environs/
M environs/
M environs/
A environs/