Merge lp:~thumper/juju-core/local-provider-bootstrap into lp:~go-bot/juju-core/trunk
- local-provider-bootstrap
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1469 |
Proposed branch: | lp:~thumper/juju-core/local-provider-bootstrap |
Merge into: | lp:~go-bot/juju-core/trunk |
Prerequisite: | lp:~thumper/juju-core/local-default-root-dir |
Diff against target: |
536 lines (+388/-12) 5 files modified
environs/local/environ.go (+263/-6) environs/local/environ_test.go (+41/-3) environs/local/environprovider.go (+13/-3) environs/local/export_test.go (+7/-0) environs/local/instance.go (+64/-0) |
To merge this branch: | bzr merge lp:~thumper/juju-core/local-provider-bootstrap |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+174913@code.launchpad.net |
Commit message
Add the initial bootstrap implementation.
This isn't all that is needed to bootstrap, but it is the start.
In particular, this adds the mongo upstart service. This branch
also adds a very simple instance implementation for the local
instances.
Description of the change
Add the initial bootstrap implementation.
This isn't all that is needed to bootstrap, but it is the start.
In particular, this adds the mongo upstart service. This branch
also adds a very simple instance implementation for the local
instances.
I honestly spent about a day trying to get the bootstrap local
juju test working, but there were just too many moving parts.
Bootstrapping the local environment means installing an upstart
service and waiting for it to start, then connecting to the
just started service.
Tim Penhey (thumper) wrote : | # |
Jeroen T. Vermeulen (jtv) wrote : | # |
For your tests, you may now want to call
PatchAttemptStr
too much time sleeping in tests. The function is
launchpad.
Martin Packman (gz) wrote : | # |
LGTM
https:/
File environs/
https:/
environs/
const, as it's not being overriden in tests anywhere?
https:/
environs/
is happening.
Is this comment at all truthful in the context of the local provider?
What eventual consistency do we have to deal with for the local file
store implementation?
https:/
environs/
[]instance.Id) ([]instance.
This probably wants a TODO about checking that the id is a machine that
actually exists?
https:/
environs/
os.RemoveAll(
Is this enough to cleanup current lxc containers?
https:/
File environs/
https:/
environs/
Can't this just be `return environs.
https:/
environs/
I'd be tempted to just log here, OpenPort never did anything on the
local provider.
https:/
environs/
Likewise, just log for ClosePort.
William Reade (fwereade) wrote : | # |
LGTM, but I'm a bit worried about the firewalling. What actually happens
when we run the firewaller? I guess probably nothing *until* someone
tries to expose a service... but I think we'll need a better experience
there before too long.
https:/
File environs/
https:/
environs/
I'm a little suspicious that this is showing up again... I suspect it's
in a few places already. I think it's usually called InitDir, IIRC;
please do a quick search and see if there's any obvious deduplication to
be done.
https:/
environs/
[]instance.Id) ([]instance.
On 2013/07/16 09:13:03, gz wrote:
> This probably wants a TODO about checking that the id is a machine
that actually
> exists?
Yeah, I'm definitely raising eyebrows here. Can this bit be cleanly
separated into a new CL? What's it actually used for here?
https:/
environs/
os.RemoveAll(
On 2013/07/16 09:13:03, gz wrote:
> Is this enough to cleanup current lxc containers?
Don't think so. But I presume that this CL contains the absolute minimum
of instancey bits necessary to get bootstrap working? If so I'm ok with
this and the above.
https:/
File environs/
https:/
environs/
implemented")
d
https:/
File environs/
https:/
environs/
On 2013/07/16 09:13:03, gz wrote:
> Can't this just be `return environs.
+1
https:/
environs/
On 2013/07/16 09:13:03, gz wrote:
> I'd be tempted to just log here, OpenPort never did anything on the
local
> provider.
+1
https:/
environs/
On 2013/07/16 09:13:03, gz wrote:
> Likewise, just log for ClosePort.
+1
Tim Penhey (thumper) wrote : | # |
https:/
File environs/
https:/
environs/
On 2013/07/16 09:13:03, gz wrote:
> const, as it's not being overriden in tests anywhere?
Sure. I had planned at some stage to allow this to be part of the
configuration.
If or when we do this, we can remove this I guess. Changed to const.
https:/
environs/
On 2013/07/16 11:01:25, fwereade wrote:
> I'm a little suspicious that this is showing up again... I suspect
it's in a few
> places already. I think it's usually called InitDir, IIRC; please do a
quick
> search and see if there's any obvious deduplication to be done.
This is more so it can be overridden in the tests, otherwise the code
would try
to write to /etc/init in the tests, which is bad (and problematic).
There doesn't seem to be any particular deduplication that can be done
right now.
https:/
environs/
is happening.
On 2013/07/16 09:13:03, gz wrote:
> Is this comment at all truthful in the context of the local provider?
What
> eventual consistency do we have to deal with for the local file store
> implementation?
It is at least theoretically possible if the user is either scripting
things or very quick on the fingers. I'll update the comment slightly.
Actually, I'm just going to error out. They can always try again.
https:/
environs/
[]instance.Id) ([]instance.
On 2013/07/16 11:01:25, fwereade wrote:
> On 2013/07/16 09:13:03, gz wrote:
> > This probably wants a TODO about checking that the id is a machine
that
> actually
> > exists?
> Yeah, I'm definitely raising eyebrows here. Can this bit be cleanly
separated
> into a new CL? What's it actually used for here?
I actually considered checking to see if the ids exist, but then
thought, why?
This is only ever called with instance ids that we already know exist,
so why check again?
If we are wrong (which we aren't), it will fail later.
It is used in the bootstrap process somewhere. I just implemented what
was called.
This reminds me that I need to implement a stacktrace logging helper.
https:/
environs/
os.RemoveAll(
On 2013/07/16 09:13:03, gz wrote:
> Is this enough to cleanup current lxc containers?
No, but we aren't creating any either.
https:/
environs/
os.RemoveAll(
On 2013/07/16 11:01:25, fwereade wrote:
> On 2013/07/16 09:13:03, gz wrote:
> > Is this enough to...
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/local-provider-bootstrap into lp:juju-core failed. Below is the output from the failed tests.
# launchpad.
environs/
environs/
environs/
Preview Diff
1 | === modified file 'environs/local/environ.go' |
2 | --- environs/local/environ.go 2013-07-12 01:53:18 +0000 |
3 | +++ environs/local/environ.go 2013-07-17 00:28:26 +0000 |
4 | @@ -5,10 +5,14 @@ |
5 | |
6 | import ( |
7 | "fmt" |
8 | + "io/ioutil" |
9 | "net" |
10 | "os" |
11 | + "path/filepath" |
12 | "sync" |
13 | + "time" |
14 | |
15 | + "launchpad.net/juju-core/agent" |
16 | "launchpad.net/juju-core/constraints" |
17 | "launchpad.net/juju-core/environs" |
18 | "launchpad.net/juju-core/environs/config" |
19 | @@ -16,8 +20,30 @@ |
20 | "launchpad.net/juju-core/instance" |
21 | "launchpad.net/juju-core/state" |
22 | "launchpad.net/juju-core/state/api" |
23 | + "launchpad.net/juju-core/upstart" |
24 | + "launchpad.net/juju-core/utils" |
25 | ) |
26 | |
27 | +// lxcBridgeName is the name of the network interface that the local provider |
28 | +// uses to determine the ip address to use for machine-0 such that the |
29 | +// containers being created are able to communicate with it simply. |
30 | +const lxcBridgeName = "lxcbr0" |
31 | + |
32 | +// upstartScriptLocation is parameterised purely for testing purposes as we |
33 | +// don't really want to be installing and starting scripts as root for |
34 | +// testing. |
35 | +var upstartScriptLocation = "/etc/init" |
36 | + |
37 | +// A request may fail to due "eventual consistency" semantics, which |
38 | +// should resolve fairly quickly. A request may also fail due to a slow |
39 | +// state transition (for instance an instance taking a while to release |
40 | +// a security group after termination). The former failure mode is |
41 | +// dealt with by shortAttempt, the latter by longAttempt. |
42 | +var shortAttempt = utils.AttemptStrategy{ |
43 | + Total: 1 * time.Second, |
44 | + Delay: 50 * time.Millisecond, |
45 | +} |
46 | + |
47 | // localEnviron implements Environ. |
48 | var _ environs.Environ = (*localEnviron)(nil) |
49 | |
50 | @@ -34,14 +60,99 @@ |
51 | return env.name |
52 | } |
53 | |
54 | +func (env *localEnviron) mongoServiceName() string { |
55 | + return "juju-db-" + env.config.namespace() |
56 | +} |
57 | + |
58 | +// ensureCertOwner checks to make sure that the cert files created |
59 | +// by the bootstrap command are owned by the user and not root. |
60 | +func (env *localEnviron) ensureCertOwner() error { |
61 | + files := []string{ |
62 | + config.JujuHomePath(env.name + "-cert.pem"), |
63 | + config.JujuHomePath(env.name + "-private-key.pem"), |
64 | + } |
65 | + |
66 | + uid, gid, err := sudoCallerIds() |
67 | + if err != nil { |
68 | + return err |
69 | + } |
70 | + if uid != 0 || gid != 0 { |
71 | + for _, filename := range files { |
72 | + if err := os.Chown(filename, uid, gid); err != nil { |
73 | + return err |
74 | + } |
75 | + } |
76 | + } |
77 | + return nil |
78 | +} |
79 | + |
80 | // Bootstrap is specified in the Environ interface. |
81 | func (env *localEnviron) Bootstrap(cons constraints.Value) error { |
82 | - return fmt.Errorf("not implemented") |
83 | + logger.Infof("bootstrapping environment %q", env.name) |
84 | + if !env.config.runningAsRoot { |
85 | + return fmt.Errorf("bootstrapping a local environment must be done as root") |
86 | + } |
87 | + if err := env.config.createDirs(); err != nil { |
88 | + logger.Errorf("failed to create necessary directories: %v", err) |
89 | + return err |
90 | + } |
91 | + |
92 | + if err := env.ensureCertOwner(); err != nil { |
93 | + logger.Errorf("failed to reassign ownership of the certs to the user: %v", err) |
94 | + return err |
95 | + } |
96 | + // TODO(thumper): check that the constraints don't include "container=lxc" for now. |
97 | + |
98 | + var noRetry = utils.AttemptStrategy{} |
99 | + if err := environs.VerifyBootstrapInit(env, noRetry); err != nil { |
100 | + return err |
101 | + } |
102 | + |
103 | + cert, key, err := env.setupLocalMongoService() |
104 | + if err != nil { |
105 | + return err |
106 | + } |
107 | + |
108 | + // Work out the ip address of the lxc bridge, and use that for the mongo config. |
109 | + bridgeAddress, err := env.findBridgeAddress() |
110 | + if err != nil { |
111 | + return err |
112 | + } |
113 | + logger.Debugf("found %q as address for %q", bridgeAddress, lxcBridgeName) |
114 | + |
115 | + // Before we write the agent config file, we need to make sure the |
116 | + // instance is saved in the StateInfo. |
117 | + bootstrapId := instance.Id("localhost") |
118 | + if err := environs.SaveState(env.Storage(), &environs.BootstrapState{[]instance.Id{bootstrapId}}); err != nil { |
119 | + logger.Errorf("failed to save state instances: %v", err) |
120 | + return err |
121 | + } |
122 | + |
123 | + // Need to write out the agent file for machine-0 before initializing |
124 | + // state, as as part of that process, it will reset the password in the |
125 | + // agent file. |
126 | + if err := env.writeBootstrapAgentConfFile(cert, key); err != nil { |
127 | + return err |
128 | + } |
129 | + |
130 | + // Have to initialize the state configuration with localhost so we get |
131 | + // "special" permissions. |
132 | + stateConnection, err := env.initialStateConfiguration("localhost", cons) |
133 | + if err != nil { |
134 | + return err |
135 | + } |
136 | + defer stateConnection.Close() |
137 | + |
138 | + // TODO(thumper): upload tools into the storage |
139 | + |
140 | + // TODO(thumper): start the machine agent for machine-0 |
141 | + |
142 | + return nil |
143 | } |
144 | |
145 | // StateInfo is specified in the Environ interface. |
146 | func (env *localEnviron) StateInfo() (*state.Info, *api.Info, error) { |
147 | - return nil, nil, fmt.Errorf("not implemented") |
148 | + return environs.StateInfo(env) |
149 | } |
150 | |
151 | // Config is specified in the Environ interface. |
152 | @@ -118,12 +229,24 @@ |
153 | |
154 | // Instances is specified in the Environ interface. |
155 | func (env *localEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { |
156 | - return nil, fmt.Errorf("not implemented") |
157 | + // NOTE: do we actually care about checking the existance of the instances? |
158 | + // I posit that here we don't really care, and that we are only called with |
159 | + // instance ids that we know exist. |
160 | + if len(ids) == 0 { |
161 | + return nil, nil |
162 | + } |
163 | + insts := make([]instance.Instance, len(ids)) |
164 | + for i, id := range ids { |
165 | + insts[i] = &localInstance{id, env} |
166 | + } |
167 | + return insts, nil |
168 | } |
169 | |
170 | // AllInstances is specified in the Environ interface. |
171 | -func (env *localEnviron) AllInstances() ([]instance.Instance, error) { |
172 | - return nil, fmt.Errorf("not implemented") |
173 | +func (env *localEnviron) AllInstances() (instances []instance.Instance, err error) { |
174 | + // TODO(thumper): get all the instances from the container manager |
175 | + instances = append(instances, &localInstance{"localhost", env}) |
176 | + return instances, nil |
177 | } |
178 | |
179 | // Storage is specified in the Environ interface. |
180 | @@ -138,7 +261,26 @@ |
181 | |
182 | // Destroy is specified in the Environ interface. |
183 | func (env *localEnviron) Destroy(insts []instance.Instance) error { |
184 | - return fmt.Errorf("not implemented") |
185 | + if !env.config.runningAsRoot { |
186 | + return fmt.Errorf("destroying a local environment must be done as root") |
187 | + } |
188 | + |
189 | + logger.Infof("removing service %s", env.mongoServiceName()) |
190 | + mongo := upstart.NewService(env.mongoServiceName()) |
191 | + mongo.InitDir = upstartScriptLocation |
192 | + if err := mongo.Remove(); err != nil { |
193 | + logger.Errorf("could not remove mongo service: %v", err) |
194 | + return err |
195 | + } |
196 | + |
197 | + // Remove the rootdir. |
198 | + logger.Infof("removing state dir %s", env.config.rootDir()) |
199 | + if err := os.RemoveAll(env.config.rootDir()); err != nil { |
200 | + logger.Errorf("could not remove local state dir: %v", err) |
201 | + return err |
202 | + } |
203 | + |
204 | + return nil |
205 | } |
206 | |
207 | // OpenPorts is specified in the Environ interface. |
208 | @@ -160,3 +302,118 @@ |
209 | func (env *localEnviron) Provider() environs.EnvironProvider { |
210 | return &provider |
211 | } |
212 | + |
213 | +// setupLocalMongoService returns the cert and key if there was no error. |
214 | +func (env *localEnviron) setupLocalMongoService() ([]byte, []byte, error) { |
215 | + journalDir := filepath.Join(env.config.mongoDir(), "journal") |
216 | + logger.Debugf("create mongo journal dir: %v", journalDir) |
217 | + if err := os.MkdirAll(journalDir, 0755); err != nil { |
218 | + logger.Errorf("failed to make mongo journal dir %s: %v", journalDir, err) |
219 | + return nil, nil, err |
220 | + } |
221 | + |
222 | + logger.Debugf("generate server cert") |
223 | + cert, key, err := env.config.GenerateStateServerCertAndKey() |
224 | + if err != nil { |
225 | + logger.Errorf("failed to generate server cert: %v", err) |
226 | + return nil, nil, err |
227 | + } |
228 | + if err := ioutil.WriteFile( |
229 | + env.config.configFile("server.pem"), |
230 | + append(cert, key...), |
231 | + 0600); err != nil { |
232 | + logger.Errorf("failed to write server.pem: %v", err) |
233 | + return nil, nil, err |
234 | + } |
235 | + |
236 | + mongo := upstart.MongoUpstartService( |
237 | + env.mongoServiceName(), |
238 | + env.config.rootDir(), |
239 | + env.config.mongoDir(), |
240 | + env.config.StatePort()) |
241 | + mongo.InitDir = upstartScriptLocation |
242 | + logger.Infof("installing service %s to %s", env.mongoServiceName(), mongo.InitDir) |
243 | + if err := mongo.Install(); err != nil { |
244 | + logger.Errorf("could not install mongo service: %v", err) |
245 | + return nil, nil, err |
246 | + } |
247 | + return cert, key, nil |
248 | +} |
249 | + |
250 | +func (env *localEnviron) findBridgeAddress() (string, error) { |
251 | + bridge, err := net.InterfaceByName(lxcBridgeName) |
252 | + if err != nil { |
253 | + logger.Errorf("cannot find network interface %q: %v", lxcBridgeName, err) |
254 | + return "", err |
255 | + } |
256 | + addrs, err := bridge.Addrs() |
257 | + if err != nil { |
258 | + logger.Errorf("cannot get addresses for network interface %q: %v", lxcBridgeName, err) |
259 | + return "", err |
260 | + } |
261 | + return utils.GetIPv4Address(addrs) |
262 | +} |
263 | + |
264 | +func (env *localEnviron) writeBootstrapAgentConfFile(cert, key []byte) error { |
265 | + info, apiInfo, err := env.StateInfo() |
266 | + if err != nil { |
267 | + logger.Errorf("failed to get state info to write bootstrap agent file: %v", err) |
268 | + return err |
269 | + } |
270 | + tag := state.MachineTag("0") |
271 | + info.Tag = tag |
272 | + apiInfo.Tag = tag |
273 | + conf := &agent.Conf{ |
274 | + DataDir: env.config.rootDir(), |
275 | + StateInfo: info, |
276 | + APIInfo: apiInfo, |
277 | + StateServerCert: cert, |
278 | + StateServerKey: key, |
279 | + StatePort: env.config.StatePort(), |
280 | + APIPort: env.config.StatePort(), |
281 | + MachineNonce: state.BootstrapNonce, |
282 | + } |
283 | + if err := conf.Write(); err != nil { |
284 | + logger.Errorf("failed to write bootstrap agent file: %v", err) |
285 | + return err |
286 | + } |
287 | + return nil |
288 | +} |
289 | + |
290 | +func (env *localEnviron) initialStateConfiguration(addr string, cons constraints.Value) (*state.State, error) { |
291 | + // We don't check the existance of the CACert here as if it wasn't set, we |
292 | + // wouldn't get this far. |
293 | + cfg := env.config.Config |
294 | + caCert, _ := cfg.CACert() |
295 | + addr = fmt.Sprintf("%s:%d", addr, cfg.StatePort()) |
296 | + info := &state.Info{ |
297 | + Addrs: []string{addr}, |
298 | + CACert: caCert, |
299 | + } |
300 | + timeout := state.DialOpts{10 * time.Second} |
301 | + bootstrap, err := environs.BootstrapConfig(cfg) |
302 | + if err != nil { |
303 | + return nil, err |
304 | + } |
305 | + st, err := state.Initialize(info, bootstrap, timeout) |
306 | + if err != nil { |
307 | + logger.Errorf("failed to initialize state: %v", err) |
308 | + return nil, err |
309 | + } |
310 | + logger.Debugf("state initialized") |
311 | + |
312 | + passwordHash := utils.PasswordHash(cfg.AdminSecret()) |
313 | + if err := environs.BootstrapUsers(st, cfg, passwordHash); err != nil { |
314 | + st.Close() |
315 | + return nil, err |
316 | + } |
317 | + jobs := []state.MachineJob{state.JobManageEnviron, state.JobManageState} |
318 | + |
319 | + if err := environs.ConfigureBootstrapMachine(st, cfg, cons, env.config.rootDir(), jobs); err != nil { |
320 | + st.Close() |
321 | + return nil, err |
322 | + } |
323 | + |
324 | + // Return an open state reference. |
325 | + return st, nil |
326 | +} |
327 | |
328 | === modified file 'environs/local/environ_test.go' |
329 | --- environs/local/environ_test.go 2013-07-16 22:01:19 +0000 |
330 | +++ environs/local/environ_test.go 2013-07-17 00:28:26 +0000 |
331 | @@ -4,6 +4,10 @@ |
332 | package local_test |
333 | |
334 | import ( |
335 | + "io/ioutil" |
336 | + "os" |
337 | + "path/filepath" |
338 | + |
339 | gc "launchpad.net/gocheck" |
340 | |
341 | "launchpad.net/juju-core/environs/jujutest" |
342 | @@ -40,19 +44,53 @@ |
343 | type localJujuTestSuite struct { |
344 | baseProviderSuite |
345 | jujutest.Tests |
346 | + restoreRootCheck func() |
347 | + oldUpstartLocation string |
348 | + oldPath string |
349 | + testPath string |
350 | + dbServiceName string |
351 | } |
352 | |
353 | func (s *localJujuTestSuite) SetUpTest(c *gc.C) { |
354 | s.baseProviderSuite.SetUpTest(c) |
355 | + // Construct the directories first. |
356 | + err := local.CreateDirs(c, minimalConfig(c)) |
357 | + c.Assert(err, gc.IsNil) |
358 | + s.oldUpstartLocation = local.SetUpstartScriptLocation(c.MkDir()) |
359 | + s.oldPath = os.Getenv("PATH") |
360 | + s.testPath = c.MkDir() |
361 | + os.Setenv("PATH", s.testPath+":"+s.oldPath) |
362 | + |
363 | + // Add in an admin secret |
364 | + s.Tests.TestConfig.Config["admin-secret"] = "sekrit" |
365 | + s.restoreRootCheck = local.SetRootCheckFunction(func() bool { return true }) |
366 | s.Tests.SetUpTest(c) |
367 | + s.dbServiceName = "juju-db-" + local.ConfigNamespace(s.Env.Config()) |
368 | } |
369 | |
370 | func (s *localJujuTestSuite) TearDownTest(c *gc.C) { |
371 | - // TODO(thumper): add the TearDownTest for s.Tests when destroy is implemented |
372 | - // s.Tests.TearDownTest(c) |
373 | + s.Tests.TearDownTest(c) |
374 | + os.Setenv("PATH", s.oldPath) |
375 | + s.restoreRootCheck() |
376 | + local.SetUpstartScriptLocation(s.oldUpstartLocation) |
377 | s.baseProviderSuite.TearDownTest(c) |
378 | } |
379 | |
380 | +func (s *localJujuTestSuite) MakeTool(c *gc.C, name, script string) { |
381 | + path := filepath.Join(s.testPath, name) |
382 | + script = "#!/bin/bash\n" + script |
383 | + err := ioutil.WriteFile(path, []byte(script), 0755) |
384 | + c.Assert(err, gc.IsNil) |
385 | +} |
386 | + |
387 | +func (s *localJujuTestSuite) StoppedStatus(c *gc.C) { |
388 | + s.MakeTool(c, "status", `echo "some-service stop/waiting"`) |
389 | +} |
390 | + |
391 | +func (s *localJujuTestSuite) RunningStatus(c *gc.C) { |
392 | + s.MakeTool(c, "status", `echo "some-service start/running, process 123"`) |
393 | +} |
394 | + |
395 | var _ = gc.Suite(&localJujuTestSuite{ |
396 | Tests: jujutest.Tests{ |
397 | TestConfig: jujutest.TestConfig{minimalConfigValues()}, |
398 | @@ -60,7 +98,7 @@ |
399 | }) |
400 | |
401 | func (s *localJujuTestSuite) TestBootstrap(c *gc.C) { |
402 | - c.Skip("Bootstrap not implemented yet.") |
403 | + c.Skip("Cannot test bootstrap at this stage.") |
404 | } |
405 | |
406 | func (s *localJujuTestSuite) TestStartStop(c *gc.C) { |
407 | |
408 | === modified file 'environs/local/environprovider.go' |
409 | --- environs/local/environprovider.go 2013-07-16 03:23:32 +0000 |
410 | +++ environs/local/environprovider.go 2013-07-17 00:28:26 +0000 |
411 | @@ -12,6 +12,7 @@ |
412 | "launchpad.net/juju-core/environs/config" |
413 | "launchpad.net/juju-core/instance" |
414 | "launchpad.net/juju-core/utils" |
415 | + "launchpad.net/juju-core/version" |
416 | ) |
417 | |
418 | var logger = loggo.GetLogger("juju.environs.local") |
419 | @@ -27,10 +28,18 @@ |
420 | } |
421 | |
422 | // Open implements environs.EnvironProvider.Open. |
423 | -func (environProvider) Open(cfg *config.Config) (environs.Environ, error) { |
424 | +func (environProvider) Open(cfg *config.Config) (env environs.Environ, err error) { |
425 | logger.Infof("opening environment %q", cfg.Name()) |
426 | + if _, ok := cfg.AgentVersion(); !ok { |
427 | + cfg, err = cfg.Apply(map[string]interface{}{ |
428 | + "agent-version": version.CurrentNumber().String(), |
429 | + }) |
430 | + if err != nil { |
431 | + return nil, err |
432 | + } |
433 | + } |
434 | environ := &localEnviron{name: cfg.Name()} |
435 | - err := environ.SetConfig(cfg) |
436 | + err = environ.SetConfig(cfg) |
437 | if err != nil { |
438 | logger.Errorf("failure setting config: %v", err) |
439 | return nil, err |
440 | @@ -108,7 +117,8 @@ |
441 | |
442 | // InstanceId implements environs.EnvironProvider.InstanceId. |
443 | func (environProvider) InstanceId() (instance.Id, error) { |
444 | - return "", fmt.Errorf("not implemented") |
445 | + // This hack only works until we get containers started. |
446 | + return instance.Id("localhost"), nil |
447 | } |
448 | |
449 | func (environProvider) newConfig(cfg *config.Config) (*environConfig, error) { |
450 | |
451 | === modified file 'environs/local/export_test.go' |
452 | --- environs/local/export_test.go 2013-07-16 21:56:22 +0000 |
453 | +++ environs/local/export_test.go 2013-07-17 00:28:26 +0000 |
454 | @@ -18,6 +18,13 @@ |
455 | return func() { checkIfRoot = old } |
456 | } |
457 | |
458 | +// SetUpstartScriptLocation allows tests to override the directory where the |
459 | +// provider writes the upstart scripts. |
460 | +func SetUpstartScriptLocation(location string) (old string) { |
461 | + old, upstartScriptLocation = upstartScriptLocation, location |
462 | + return |
463 | +} |
464 | + |
465 | // ConfigNamespace returns the result of the namespace call on the |
466 | // localConfig. |
467 | func ConfigNamespace(cfg *config.Config) string { |
468 | |
469 | === added file 'environs/local/instance.go' |
470 | --- environs/local/instance.go 1970-01-01 00:00:00 +0000 |
471 | +++ environs/local/instance.go 2013-07-17 00:28:26 +0000 |
472 | @@ -0,0 +1,64 @@ |
473 | +// Copyright 2013 Canonical Ltd. |
474 | +// Licensed under the AGPLv3, see LICENCE file for details. |
475 | + |
476 | +package local |
477 | + |
478 | +import ( |
479 | + "fmt" |
480 | + |
481 | + "launchpad.net/juju-core/environs" |
482 | + "launchpad.net/juju-core/instance" |
483 | +) |
484 | + |
485 | +type localInstance struct { |
486 | + id instance.Id |
487 | + env *localEnviron |
488 | +} |
489 | + |
490 | +var _ instance.Instance = (*localInstance)(nil) |
491 | + |
492 | +// Id implements instance.Instance.Id. |
493 | +func (inst *localInstance) Id() instance.Id { |
494 | + return inst.id |
495 | +} |
496 | + |
497 | +// DNSName implements instance.Instance.DNSName. |
498 | +func (inst *localInstance) DNSName() (string, error) { |
499 | + if string(inst.id) == "localhost" { |
500 | + // get the bridge address from the environment |
501 | + addr, err := inst.env.findBridgeAddress() |
502 | + if err != nil { |
503 | + logger.Errorf("failed to get bridge address: %v", err) |
504 | + return "", instance.ErrNoDNSName |
505 | + } |
506 | + return addr, nil |
507 | + } |
508 | + return "", instance.ErrNoDNSName |
509 | +} |
510 | + |
511 | +// WaitDNSName implements instance.Instance.WaitDNSName. |
512 | +func (inst *localInstance) WaitDNSName() (string, error) { |
513 | + return environs.WaitDNSName(inst) |
514 | +} |
515 | + |
516 | +// OpenPorts implements instance.Instance.OpenPorts. |
517 | +func (inst *localInstance) OpenPorts(machineId string, ports []instance.Port) error { |
518 | + logger.Infof("OpenPorts called for %s:%v", machineId, ports) |
519 | + return nil |
520 | +} |
521 | + |
522 | +// ClosePorts implements instance.Instance.ClosePorts. |
523 | +func (inst *localInstance) ClosePorts(machineId string, ports []instance.Port) error { |
524 | + logger.Infof("ClosePorts called for %s:%v", machineId, ports) |
525 | + return nil |
526 | +} |
527 | + |
528 | +// Ports implements instance.Instance.Ports. |
529 | +func (inst *localInstance) Ports(machineId string) ([]instance.Port, error) { |
530 | + return nil, fmt.Errorf("not implemented") |
531 | +} |
532 | + |
533 | +// Add a string representation of the id. |
534 | +func (inst *localInstance) String() string { |
535 | + return fmt.Sprintf("inst:%v", inst.id) |
536 | +} |
Reviewers: mp+174913_ code.launchpad. net,
Message:
Please take a look.
Description:
Add the initial bootstrap implementation.
This isn't all that is needed to bootstrap, but it is the start.
In particular, this adds the mongo upstart service. This branch
also adds a very simple instance implementation for the local
instances.
I honestly spent about a day trying to get the bootstrap local
juju test working, but there were just too many moving parts.
Bootstrapping the local environment means installing an upstart
service and waiting for it to start, then connecting to the
just started service.
https:/ /code.launchpad .net/~thumper/ juju-core/ local-provider- bootstrap/ +merge/ 174913
Requires: /code.launchpad .net/~thumper/ juju-core/ local-default- root-dir/ +merge/ 174908
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/11325043/
Affected files: local/environ. go local/environ_ test.go local/environpr ovider. go local/export_ test.go local/instance. go
A [revision details]
M environs/
M environs/
M environs/
M environs/
A environs/