Merge lp:~dave-cheney/pyjuju/go-provisioning-woodenman into lp:pyjuju/go

Proposed by Dave Cheney
Status: Work in progress
Proposed branch: lp:~dave-cheney/pyjuju/go-provisioning-woodenman
Merge into: lp:pyjuju/go
Prerequisite: lp:~dave-cheney/pyjuju/go-provisioning-strawman
Diff against target: 355 lines (+236/-28)
2 files modified
cmd/jujud/provisioning.go (+122/-5)
cmd/jujud/provisioning_test.go (+114/-23)
To merge this branch: bzr merge lp:~dave-cheney/pyjuju/go-provisioning-woodenman
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+108287@code.launchpad.net

Description of the change

cmd/jujud: provisioning agent woodenman

WIP: Extending the strawman PA to act on the provider

https://codereview.appspot.com/6256079/

To post a comment you must log in.
197. By Dave Cheney

wip

198. By Dave Cheney

pass state into provisioner

199. By Dave Cheney

merge machine.String()

200. By Dave Cheney

merge from req

201. By Dave Cheney

added more unit tests

202. By Dave Cheney

increased the test load

203. By Dave Cheney

merge from trunk

204. By Dave Cheney

merge tests with trunk

Unmerged revisions

204. By Dave Cheney

merge tests with trunk

203. By Dave Cheney

merge from trunk

202. By Dave Cheney

increased the test load

201. By Dave Cheney

added more unit tests

200. By Dave Cheney

merge from req

199. By Dave Cheney

merge machine.String()

198. By Dave Cheney

pass state into provisioner

197. By Dave Cheney

wip

196. By Dave Cheney

merge strawman

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/jujud/provisioning.go'
2--- cmd/jujud/provisioning.go 2012-06-05 07:57:23 +0000
3+++ cmd/jujud/provisioning.go 2012-06-06 06:36:19 +0000
4@@ -1,6 +1,8 @@
5 package main
6
7 import (
8+ "fmt"
9+
10 "launchpad.net/gnuflag"
11 "launchpad.net/juju/go/cmd"
12 "launchpad.net/juju/go/environs"
13@@ -39,23 +41,30 @@
14 if err != nil {
15 return err
16 }
17- p := NewProvisioner(st)
18+ p := NewProvisioner(st, &a.Conf.StateInfo)
19 return p.Wait()
20 }
21
22 type Provisioner struct {
23 st *state.State
24+ info *state.Info
25 environ environs.Environ
26 tomb tomb.Tomb
27
28 environWatcher *state.ConfigWatcher
29 machinesWatcher *state.MachinesWatcher
30+
31+ providerIdToInstance map[string]environs.Instance
32+ machineIdToProviderId map[int]string
33 }
34
35 // NewProvisioner returns a Provisioner.
36-func NewProvisioner(st *state.State) *Provisioner {
37+func NewProvisioner(st *state.State, info *state.Info) *Provisioner {
38 p := &Provisioner{
39- st: st,
40+ st: st,
41+ info: info,
42+ providerIdToInstance: make(map[string]environs.Instance),
43+ machineIdToProviderId: make(map[int]string),
44 }
45 go p.loop()
46 return p
47@@ -63,7 +72,6 @@
48
49 func (p *Provisioner) loop() {
50 defer p.tomb.Done()
51-
52 p.environWatcher = p.st.WatchEnvironConfig()
53 // TODO(dfc) we need a method like state.IsConnected() here to exit cleanly if
54 // there is a connection problem.
55@@ -139,4 +147,113 @@
56 return p.tomb.Wait()
57 }
58
59-func (p *Provisioner) processMachines(changes *state.MachinesChange) {}
60+func (p *Provisioner) processMachines(changes *state.MachinesChange) error {
61+ for _, added := range changes.Added {
62+ if err := p.addMachine(added); err != nil {
63+ // TODO(dfc) implement retry logic
64+ return err
65+ }
66+ log.Printf("provisioning: machine %d added", added.Id())
67+ }
68+ for _, deleted := range changes.Deleted {
69+ if err := p.deleteMachine(deleted); err != nil {
70+ // TODO(dfc) implement retry logic
71+ return err
72+ }
73+ log.Printf("provisioning: machine %d deleted", deleted.Id())
74+ }
75+ return nil
76+}
77+
78+func (p *Provisioner) addMachine(m *state.Machine) error {
79+ id, err := m.InstanceId()
80+ if err != nil {
81+ return err
82+ }
83+ if id != "" {
84+ return fmt.Errorf("%s already reports a provider id %q, skipping", m, id)
85+ }
86+
87+ // TODO(dfc) the state.Info passed to environ.StartInstance remains contentious
88+ // however as the PA only knows one state.Info, and that info is used by MAs and
89+ // UAs to locate the ZK for this environment, it is logical to use the same
90+ // state.Info as the PA.
91+ inst, err := p.environ.StartInstance(m.Id(), p.info)
92+ if err != nil {
93+ return err
94+ }
95+
96+ // assign the provider id to the macine
97+ if err := m.SetInstanceId(inst.Id()); err != nil {
98+ return fmt.Errorf("unable to store provider id: %v", err)
99+ }
100+
101+ // populate the local caches
102+ p.machineIdToProviderId[m.Id()] = inst.Id()
103+ p.providerIdToInstance[inst.Id()] = inst
104+ return nil
105+}
106+
107+func (p *Provisioner) deleteMachine(m *state.Machine) error {
108+ insts, err := p.InstancesForMachines(m)
109+ if err != nil {
110+ return fmt.Errorf("%s has no refrence to a provider id, skipping", m)
111+ }
112+ return p.environ.StopInstances(insts)
113+}
114+
115+// InstanceForMachine returns the environs.Instance that represents this machines' running
116+// instance.
117+func (p *Provisioner) InstanceForMachine(m *state.Machine) (environs.Instance, error) {
118+ id, ok := p.machineIdToProviderId[m.Id()]
119+ if !ok {
120+ // not cached locally, ask the provider.
121+ var err error
122+ id, err = m.InstanceId()
123+ if err != nil {
124+ return nil, err
125+ }
126+ if id == "" {
127+ // nobody knows about this machine, give up.
128+ return nil, fmt.Errorf("instance not found")
129+ }
130+ p.machineIdToProviderId[m.Id()] = id
131+ }
132+ inst, ok := p.providerIdToInstance[id]
133+ if !ok {
134+ // not cached locally, ask the provider
135+ var err error
136+ inst, err = p.findInstance(id)
137+ if err != nil {
138+ // the provider doesn't know about this instance, give up.
139+ return nil, err
140+ }
141+ return nil, nil
142+ }
143+ return inst, nil
144+}
145+
146+// InstancesForMachines returns a list of environs.Instance that represent the list of machines running
147+// in the provider.
148+func (p *Provisioner) InstancesForMachines(machines ...*state.Machine) ([]environs.Instance, error) {
149+ var insts []environs.Instance
150+ for _, m := range machines {
151+ inst, err := p.InstanceForMachine(m)
152+ if err != nil {
153+ return nil, err
154+ }
155+ insts = append(insts, inst)
156+ }
157+ return insts, nil
158+}
159+
160+func (p *Provisioner) findInstance(id string) (environs.Instance, error) {
161+ insts, err := p.environ.Instances([]string{id})
162+ if err != nil {
163+ return nil, err
164+ }
165+ if len(insts) < 1 {
166+ return nil, fmt.Errorf("instance not found")
167+ }
168+ return insts[0], nil
169+}
170
171=== modified file 'cmd/jujud/provisioning_test.go'
172--- cmd/jujud/provisioning_test.go 2012-06-05 07:57:23 +0000
173+++ cmd/jujud/provisioning_test.go 2012-06-06 06:36:19 +0000
174@@ -1,6 +1,8 @@
175 package main
176
177 import (
178+ "time"
179+
180 . "launchpad.net/gocheck"
181 "launchpad.net/gozk/zookeeper"
182 "launchpad.net/juju/go/cmd"
183@@ -10,8 +12,9 @@
184 )
185
186 type ProvisioningSuite struct {
187- zkConn *zookeeper.Conn
188- st *state.State
189+ zkConn *zookeeper.Conn
190+ zkInfo *state.Info
191+ st *state.State
192 }
193
194 var _ = Suite(&ProvisioningSuite{})
195@@ -25,17 +28,26 @@
196 c.Assert(event.State, Equals, zookeeper.STATE_CONNECTED)
197
198 s.zkConn = zk
199- info := &state.Info{
200+ s.zkInfo = &state.Info{
201 Addrs: []string{zkAddr},
202 }
203- testing.ZkRemoveTree(s.zkConn, "/")
204- s.st, err = state.Initialize(info)
205+ s.st, err = state.Initialize(s.zkInfo)
206 c.Assert(err, IsNil)
207
208 dummy.Reset()
209+
210+ // seed /environment to point to dummy
211+ env, err := s.st.Environment()
212+ c.Assert(err, IsNil)
213+ env.Set("type", "dummy")
214+ env.Set("zookeeper", false)
215+ env.Set("name", "testing")
216+ _, err = env.Write()
217+ c.Assert(err, IsNil)
218 }
219
220 func (s *ProvisioningSuite) TearDownTest(c *C) {
221+ testing.ZkRemoveTree(s.zkConn, "/")
222 s.zkConn.Close()
223 }
224
225@@ -60,12 +72,12 @@
226 }
227
228 func (s *ProvisioningSuite) TestProvisionerStartStop(c *C) {
229- p := NewProvisioner(s.st)
230+ p := NewProvisioner(s.st, s.zkInfo)
231 c.Assert(p.Stop(), IsNil)
232 }
233
234 func (s *ProvisioningSuite) TestProvisionerEnvironmentChange(c *C) {
235- p := NewProvisioner(s.st)
236+ p := NewProvisioner(s.st, s.zkInfo)
237
238 // seed /environment to point to dummy
239 env, err := s.st.Environment()
240@@ -88,20 +100,99 @@
241 }
242
243 func (s *ProvisioningSuite) TestProvisionerStopOnStateClose(c *C) {
244- p := NewProvisioner(s.st)
245-
246- // seed /environment to point to dummy
247- env, err := s.st.Environment()
248- c.Assert(err, IsNil)
249- env.Set("type", "dummy")
250- env.Set("zookeeper", false)
251- env.Set("name", "testing")
252- _, err = env.Write()
253- c.Assert(err, IsNil)
254-
255- s.st.Close()
256-
257- c.Assert(p.Wait(), ErrorMatches, "watcher.*")
258- c.Assert(p.Stop(), ErrorMatches, "watcher.*")
259-
260+ p := NewProvisioner(s.st, s.zkInfo)
261+
262+ // seed /environment to point to dummy
263+ env, err := s.st.Environment()
264+ c.Assert(err, IsNil)
265+ env.Set("type", "dummy")
266+ env.Set("zookeeper", false)
267+ env.Set("name", "testing")
268+ _, err = env.Write()
269+ c.Assert(err, IsNil)
270+
271+ s.st.Close()
272+
273+ c.Assert(p.Wait(), ErrorMatches, "watcher.*")
274+ c.Assert(p.Stop(), ErrorMatches, "watcher.*")
275+}
276+
277+// Start and stop one machine, watch the PA.
278+func (s *ProvisioningSuite) TestSimple(c *C) {
279+ p := NewProvisioner(s.st, s.zkInfo)
280+
281+ op := make(chan dummy.Operation, 1)
282+ dummy.Listen(op)
283+
284+ // place a new machine into the state
285+ m, err := s.st.AddMachine()
286+ c.Assert(err, IsNil)
287+
288+ // watch the PA create it
289+ select {
290+ case o := <-op:
291+ c.Assert(o.Kind, Equals, dummy.OpStartInstance)
292+ case <-time.After(3 * time.Second):
293+ c.Fatalf("ProvisioningAgent did not action AddMachine after 3 second")
294+ }
295+
296+ // now remove it
297+ c.Assert(s.st.RemoveMachine(m.Id()), IsNil)
298+
299+ // watch the PA remove it
300+ select {
301+ case o := <-op:
302+ c.Assert(o.Kind, Equals, dummy.OpStopInstances)
303+ case <-time.After(3 * time.Second):
304+ c.Fatalf("ProvisioningAgent did not action RemoveMachine after 3 second")
305+ }
306+
307+ c.Assert(p.Stop(), IsNil)
308+}
309+
310+// Start and stop lots machines, watch the PA.
311+func (s *ProvisioningSuite) TestHard(c *C) {
312+ var N = 200
313+
314+ p := NewProvisioner(s.st, s.zkInfo)
315+
316+ op := make(chan dummy.Operation, 1)
317+ dummy.Listen(op)
318+
319+ go func() {
320+ for i := 0; i < N; i++ {
321+ m, err := s.st.AddMachine()
322+ <-time.After(20 * time.Millisecond)
323+ c.Assert(err, IsNil)
324+ c.Logf("added machine: %d", m.Id())
325+ go func() {
326+ err := s.st.RemoveMachine(m.Id())
327+ c.Assert(err, IsNil)
328+ c.Logf("removed machine: %d", m.Id())
329+ }()
330+ }
331+ }()
332+
333+ var started, stopped int
334+
335+ for stopped < N {
336+ select {
337+ case o := <-op:
338+ switch o.Kind {
339+ case dummy.OpStartInstance:
340+ started++
341+ case dummy.OpStopInstances:
342+ stopped++
343+ default:
344+ c.Fatalf("dummy reported unknown operation: %v", o)
345+ }
346+ case <-time.After(10 * time.Second):
347+ c.Fatalf("PA stalled")
348+ }
349+ }
350+
351+ c.Assert(started, Equals, N)
352+ c.Assert(stopped, Equals, N)
353+
354+ c.Assert(p.Stop(), IsNil)
355 }

Subscribers

People subscribed via source and target branches

to all changes: