Merge lp:~thumper/juju-core/local-provider 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: 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
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.

https://codereview.appspot.com/11333043/

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.

https://codereview.appspot.com/11333043/

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

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:
https://code.launchpad.net/~thumper/juju-core/selectively-install-lxc/+merge/174928

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M cmd/jujud/machine.go
   M environs/all/all.go
   M environs/local/config.go
   M environs/local/environ.go
   M environs/local/environprovider.go
   A environs/local/storage/worker.go

Revision history for this message
Frank Mueller (themue) wrote :

LGTM, great work, but some comments.

https://codereview.appspot.com/11333043/diff/1/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/11333043/diff/1/cmd/jujud/machine.go#newcode189
cmd/jujud/machine.go:189: if providerType != "local" &&
m.ContainerType() != instance.LXC {
Make "local" a const.

https://codereview.appspot.com/11333043/diff/1/cmd/jujud/machine.go#newcode197
cmd/jujud/machine.go:197: if providerType == "local" && m.Id() == "0" {
Also a const with a speaking name for the "0".

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

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode199
environs/local/environ.go:199: // If we get to here, it is because we
haven't yet bootstrapped an
Interesting hurdle. ;) But I would like a construct like

if !config.bootstrapped() {
     return env.<findAGoodNameForTheStuffBelow>()
}
return nil

more. So that logical block of code has its own method.

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode229
environs/local/environ.go:229: func (env *localEnviron)
setupLocalStorage() error {
Appreciate comment event for private methods.

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode287
environs/local/environ.go:287: if inst.Id() == "localhost" {
Again I like a const here. In the error you name it "bootstrap
instance". So

if inst.Id() == bootstrapInstance { ... }

would read better.

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode311
environs/local/environ.go:311: instances = append(instances,
&localInstance{"localhost", env})
Const, see above.

https://codereview.appspot.com/11333043/

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

https://codereview.appspot.com/11333043/diff/1/environs/local/config.go#newcode31
environs/local/config.go:31: "shared-storage-port": 8041,
Any reasoning behind these default ports?

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

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode298
environs/local/environ.go:298: func (env *localEnviron) Instances(ids
[]instance.Id) ([]instance.Instance, error) {
It's now possible to implement this correctly, by for instance calling
AllInstances then filtering.

https://codereview.appspot.com/11333043/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/11333043/diff/1/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/11333043/diff/1/cmd/jujud/machine.go#newcode189
cmd/jujud/machine.go:189: if providerType != "local" &&
m.ContainerType() != instance.LXC {
On 2013/07/16 10:16:35, mue wrote:
> Make "local" a const.

Done.

https://codereview.appspot.com/11333043/diff/1/cmd/jujud/machine.go#newcode197
cmd/jujud/machine.go:197: if providerType == "local" && m.Id() == "0" {
On 2013/07/16 10:16:35, mue wrote:
> Also a const with a speaking name for the "0".

Done.

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

https://codereview.appspot.com/11333043/diff/1/environs/local/config.go#newcode31
environs/local/config.go:31: "shared-storage-port": 8041,
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://codereview.appspot.com/11333043/diff/1/environs/local/environ.go
File environs/local/environ.go (right):

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode229
environs/local/environ.go:229: func (env *localEnviron)
setupLocalStorage() error {
On 2013/07/16 10:16:35, mue wrote:
> Appreciate comment event for private methods.

Done.

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode287
environs/local/environ.go:287: if inst.Id() == "localhost" {
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://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode311
environs/local/environ.go:311: instances = append(instances,
&localInstance{"localhost", env})
On 2013/07/16 10:16:35, mue wrote:
> Const, see above.

Done.

https://codereview.appspot.com/11333043/

Revision history for this message
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://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode298
> environs/local/environ.go:298: func (env *localEnviron) Instances(ids
> []instance.Id) ([]instance.Instance, error) {
> 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.

https://codereview.appspot.com/11333043/

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

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode341
environs/local/environ.go:341: return err
Should we try and do as much removal as we can here even if something
errors?

https://codereview.appspot.com/11333043/

Revision history for this message
Tim Penhey (thumper) wrote :

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

https://codereview.appspot.com/11333043/diff/1/environs/local/environ.go#newcode341
environs/local/environ.go:341: return err
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.

https://codereview.appspot.com/11333043/

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (8.4 KiB)

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.net/juju-core/agent 2.512s
ok launchpad.net/juju-core/bzr 6.897s
ok launchpad.net/juju-core/cert 3.634s
ok launchpad.net/juju-core/charm 0.564s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.009s
ok launchpad.net/juju-core/cmd 0.230s
? launchpad.net/juju-core/cmd/builddb [no test files]
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 112.606s
ok launchpad.net/juju-core/cmd/jujud 41.977s
ok launchpad.net/juju-core/constraints 0.013s
ok launchpad.net/juju-core/container/lxc 0.325s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.333s
ok launchpad.net/juju-core/environs 1.895s
? launchpad.net/juju-core/environs/all [no test files]
ok launchpad.net/juju-core/environs/azure 2.780s
ok launchpad.net/juju-core/environs/cloudinit 0.580s
ok launchpad.net/juju-core/environs/config 1.085s
ok launchpad.net/juju-core/environs/dummy 16.618s
ok launchpad.net/juju-core/environs/ec2 175.430s
ok launchpad.net/juju-core/environs/imagemetadata 0.267s
ok launchpad.net/juju-core/environs/instances 0.254s
ok launchpad.net/juju-core/environs/jujutest 0.257s

----------------------------------------------------------------------
FAIL: environ_test.go:35: environSuite.TestNameAndStorage

[LOG] 42.62519 INFO juju.environs.local opening environment "test"
[LOG] 42.67884 TRACE juju.environs.local creating directory /tmp/gocheck-220031192/0/.juju/test/shared-storage
[LOG] 42.67907 TRACE juju.environs.local creating directory /tmp/gocheck-220031192/0/.juju/test/storage
[LOG] 42.67912 TRACE juju.environs.local creating directory /tmp/gocheck-220031192/0/.juju/test/db
[LOG] 42.67916 TRACE juju.environs.local creating directory /tmp/gocheck-220031192/0/.juju/test/log
[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.errorString{s:"net: no such interface"} ("net: no such interface")

----------------------------------------------------------------------
FAIL: environ_test.go:54: localJujuTestSuite.SetUpTest

[LOG] 42.86084 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/shared-storage
[LOG] 42.86103 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/storage
[LOG] 42.86107 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/db
[LOG] 42.86111 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/log
[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...

Read more...

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (6.5 KiB)

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.net/juju-core/agent 2.061s
ok launchpad.net/juju-core/bzr 6.829s
ok launchpad.net/juju-core/cert 4.137s
ok launchpad.net/juju-core/charm 0.565s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.009s
ok launchpad.net/juju-core/cmd 0.221s
? launchpad.net/juju-core/cmd/builddb [no test files]
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 110.572s
ok launchpad.net/juju-core/cmd/jujud 41.854s
ok launchpad.net/juju-core/constraints 0.013s
ok launchpad.net/juju-core/container/lxc 0.326s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.314s
ok launchpad.net/juju-core/environs 1.924s
? launchpad.net/juju-core/environs/all [no test files]
ok launchpad.net/juju-core/environs/azure 2.728s
ok launchpad.net/juju-core/environs/cloudinit 0.575s
ok launchpad.net/juju-core/environs/config 1.038s
ok launchpad.net/juju-core/environs/dummy 16.877s
ok launchpad.net/juju-core/environs/ec2 175.395s
ok launchpad.net/juju-core/environs/imagemetadata 0.253s
ok launchpad.net/juju-core/environs/instances 0.267s
ok launchpad.net/juju-core/environs/jujutest 0.243s

----------------------------------------------------------------------
FAIL: environ_test.go:71: localJujuTestSuite.TearDownTest

environ_test.go:72:
    s.Tests.TearDownTest(c)
/home/tarmac/trees/src/launchpad.net/juju-core/environs/jujutest/tests.go:70:
    c.Check(err, IsNil)
... value *golxc.Error = &golxc.Error{Name:"lxc-ls", Err:(*exec.Error)(0xf840169cc0), Output:[]string(nil)} ("error executing \"lxc-ls\": exec: \"lxc-ls\": executable file not found in $PATH")

----------------------------------------------------------------------
PANIC: environ_test.go:100: localJujuTestSuite.TestBootstrap

[LOG] 62.75077 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/shared-storage
[LOG] 62.75101 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/storage
[LOG] 62.75105 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/db
[LOG] 62.75109 TRACE juju.environs.local creating directory /tmp/gocheck-2031484958/0/.juju/test/log
[LOG] 62.78159 INFO juju.environs.local opening environment "test"
[LOG] 62.83570 DEBUG juju.environs.local getAddressForInterface called for lxcbr0
[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...

Read more...

Preview Diff

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

Subscribers

People subscribed via source and target branches

to status/vote changes: