Merge lp:~thumper/juju-core/local-provider-bootstrap into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
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
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.

https://codereview.appspot.com/11325043/

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.

https://codereview.appspot.com/11325043/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

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:
https://code.launchpad.net/~thumper/juju-core/local-default-root-dir/+merge/174908

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/11325043/

Affected files:
   A [revision details]
   M environs/local/environ.go
   M environs/local/environ_test.go
   M environs/local/environprovider.go
   M environs/local/export_test.go
   A environs/local/instance.go

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

For your tests, you may now want to call
PatchAttemptStrategies(&shortAttempt) to make sure that you don't waste
too much time sleeping in tests. The function is
launchpad.net/juju-core/environs/testing.

https://codereview.appspot.com/11325043/

Revision history for this message
Martin Packman (gz) wrote :

LGTM

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go
File environs/local/environ.go (right):

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode30
environs/local/environ.go:30: var lxcBridgeName = "lxcbr0"
const, as it's not being overriden in tests anywhere?

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode108
environs/local/environ.go:108: // up yet, so we retry to verify if that
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode233
environs/local/environ.go:233: func (env *localEnviron) Instances(ids
[]instance.Id) ([]instance.Instance, error) {
This probably wants a TODO about checking that the id is a machine that
actually exists?

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode277
environs/local/environ.go:277: if err :=
os.RemoveAll(env.config.rootDir()); err != nil {
Is this enough to cleanup current lxc containers?

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go
File environs/local/instance.go (right):

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go#newcode40
environs/local/instance.go:40: return "", instance.ErrNoDNSName
Can't this just be `return environs.WaitDNSName(inst)` now?

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go#newcode45
environs/local/instance.go:45: return fmt.Errorf("not implemented")
I'd be tempted to just log here, OpenPort never did anything on the
local provider.

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go#newcode50
environs/local/instance.go:50: return fmt.Errorf("not implemented")
Likewise, just log for ClosePort.

https://codereview.appspot.com/11325043/

Revision history for this message
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go
File environs/local/environ.go (right):

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode35
environs/local/environ.go:35: var upstartScriptLocation = "/etc/init"
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode233
environs/local/environ.go:233: func (env *localEnviron) Instances(ids
[]instance.Id) ([]instance.Instance, error) {
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode277
environs/local/environ.go:277: if err :=
os.RemoveAll(env.config.rootDir()); err != nil {
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://codereview.appspot.com/11325043/diff/1/environs/local/environprovider.go
File environs/local/environprovider.go (right):

https://codereview.appspot.com/11325043/diff/1/environs/local/environprovider.go#newcode122
environs/local/environprovider.go:122: // return "", fmt.Errorf("not
implemented")
d

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go
File environs/local/instance.go (right):

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go#newcode40
environs/local/instance.go:40: return "", instance.ErrNoDNSName
On 2013/07/16 09:13:03, gz wrote:
> Can't this just be `return environs.WaitDNSName(inst)` now?

+1

https://codereview.appspot.com/11325043/diff/1/environs/local/instance.go#newcode45
environs/local/instance.go:45: return fmt.Errorf("not implemented")
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://codereview.appspot.com/11325043/diff/1/environs/local/instance.go#newcode50
environs/local/instance.go:50: return fmt.Errorf("not implemented")
On 2013/07/16 09:13:03, gz wrote:
> Likewise, just log for ClosePort.

+1

https://codereview.appspot.com/11325043/

Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (4.8 KiB)

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go
File environs/local/environ.go (right):

https://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode30
environs/local/environ.go:30: var lxcBridgeName = "lxcbr0"
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode35
environs/local/environ.go:35: var upstartScriptLocation = "/etc/init"
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode108
environs/local/environ.go:108: // up yet, so we retry to verify if that
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode233
environs/local/environ.go:233: func (env *localEnviron) Instances(ids
[]instance.Id) ([]instance.Instance, error) {
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode277
environs/local/environ.go:277: if err :=
os.RemoveAll(env.config.rootDir()); err != nil {
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://codereview.appspot.com/11325043/diff/1/environs/local/environ.go#newcode277
environs/local/environ.go:277: if err :=
os.RemoveAll(env.config.rootDir()); err != nil {
On 2013/07/16 11:01:25, fwereade wrote:
> On 2013/07/16 09:13:03, gz wrote:
> > Is this enough to...

Read more...

Revision history for this message
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.net/juju-core/environs/local
environs/local/environ.go:75: undefined: getSudoCallerIds
environs/local/instance.go:40: undefined: environs
environs/local/instance.go:40: not enough arguments to return

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches

to status/vote changes: