Merge lp:~themue/juju-core/go-state-first-error-improvement into lp:~juju/juju-core/trunk

Proposed by Frank Mueller
Status: Rejected
Rejected by: Gustavo Niemeyer
Proposed branch: lp:~themue/juju-core/go-state-first-error-improvement
Merge into: lp:~juju/juju-core/trunk
Diff against target: 812 lines (+163/-110)
10 files modified
state/internal_test.go (+10/-10)
state/machine.go (+2/-2)
state/relation.go (+19/-1)
state/service.go (+4/-4)
state/state.go (+8/-11)
state/state_test.go (+4/-4)
state/topology.go (+43/-54)
state/unit.go (+17/-12)
state/util.go (+55/-11)
state/watcher_test.go (+1/-1)
To merge this branch: bzr merge lp:~themue/juju-core/go-state-first-error-improvement
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+109641@code.launchpad.net

Description of the change

state: First step of error handling improvement

Error handling in state will be improved to use the new type
StateError containing a describing code, the message and if
wanted a wrapped error. The returned error string depends on
the error code, low-level errors will be covered have to be
retrieved on demand.

ATTENTION: This is a first proposal to discuss this kind of
error wrapping. So not all errors are handled proper.

https://codereview.appspot.com/6306067/

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

This branch doesn't seem to be addressing the actual issues we have, and
instead introduces some further logic in the state package which we
don't benefit from.

Frank and I brainstormed on it privately, and the conversation was
mailed to juju-dev@. I'm closing this as another branch is coming.

https://codereview.appspot.com/6306067/

Unmerged revisions

214. By Frank Mueller

state: First proposal for an improvement of the error handling in state.

213. By Roger Peppe

state: store units inside their respective services

Making this change simplifies a lot of code - no need
to keep a separate sequence numbering system for units,
for example.

(original proposal at https://codereview.appspot.com/6247066/)

R=
CC=
https://codereview.appspot.com/6300060

212. By Gustavo Niemeyer

.lbox: update to juju-core

211. By Gustavo Niemeyer

Translated all paths to use the juju-core project in Launchpad.

210. By Frank Mueller

state: Changed relation service mapping in topology.

The first approach mapped roles to service key and relation
name opposite to todays Python implementation. This reduces
the flexibility for future purposes (e.g. multiple peers).
So the mapping has now been changed back from service keys
to relation role and name.

R=
CC=
https://codereview.appspot.com/6268049

209. By Gustavo Niemeyer

state: rename Environment method to EnvironConfig

R=TheMue, fwereade
CC=
https://codereview.appspot.com/6295048

208. By William Reade

subordinate service units can now be added to principal service units

This entails the following changes:

* new Service.AddUnitSubordinateTo method, which requires a principal unit
* topology.AddUnit now takes an additional principalKey string argument,
  which will be empty for a principal unit;
* new topology.UnitPrincipalKey, which returns the principal unit key for a
  subordinate unit or an error for a principal unit;
* new Unit.IsPrincipal method, which returns whether the unit is a principal
  unit;
* checks to prevent subordinate units being assigned directly to machines.

Most of the diff lines are a result of the change to topology.AddUnit;
sorry for the noise.

R=TheMue, niemeyer
CC=
https://codereview.appspot.com/6268050

207. By Frank Mueller

state: Added the method RemoveRelation() to State.

Relation is now an interface defining the single
method relationKey() to allow a function signature
like today in Python. The implementation is now
named relation and can be accessed via type
assertion.

R=niemeyer
CC=
https://codereview.appspot.com/6250076

206. By William Reade

trivial: fix inconsistency in deploy command documentation

R=
CC=
https://codereview.appspot.com/6304045

205. By William Reade

add AssignmentPolicy to Environ

This is very likely to migrate to a Settings type at some stage, but I don't
think that's justified yet.

R=niemeyer
CC=
https://codereview.appspot.com/6259062

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'state/internal_test.go'
2--- state/internal_test.go 2012-06-07 11:51:54 +0000
3+++ state/internal_test.go 2012-06-11 14:08:22 +0000
4@@ -290,7 +290,7 @@
5 err = s.t.AddUnit("unit-0-05", "")
6 c.Assert(err, IsNil)
7 _, err = s.t.UnitPrincipalKey("unit-0-05")
8- c.Assert(err, Equals, unitNotSubordinate)
9+ c.Assert(err, Equals, unitNotSubordinateError)
10 err = s.t.AddUnit("unit-0-12", "")
11 c.Assert(err, IsNil)
12 err = s.t.AddUnit("unit-1-07", "unit-0-05")
13@@ -515,8 +515,8 @@
14 Interface: "ifce",
15 Scope: ScopeGlobal,
16 Services: map[string]*topoRelationService{
17- "service-p": &topoRelationService{RoleProvider, "db"},
18- "illegal": &topoRelationService{RoleRequirer, "db"},
19+ "service-p": &topoRelationService{RoleProvider, "db"},
20+ "illegal": &topoRelationService{RoleRequirer, "db"},
21 },
22 })
23 c.Assert(err, ErrorMatches, `service with key "illegal" not found`)
24@@ -679,11 +679,11 @@
25
26 // Endpoints without relation.
27 _, err = s.t.RelationKey(mysqlep3, blogep3)
28- c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "wordpress:db"`)
29+ c.Assert(err, ErrorMatches, `no relation between "mysql:db" and "wordpress:db"`)
30
31 // Mix of endpoints of two relations.
32 _, err = s.t.RelationKey(mysqlep1, blogep2)
33- c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "wordpress:db"`)
34+ c.Assert(err, ErrorMatches, `no relation between "mysql:db" and "wordpress:db"`)
35
36 // Illegal number of endpoints.
37 _, err = s.t.RelationKey()
38@@ -712,13 +712,13 @@
39
40 key, err := s.t.RelationKey(mysqlep1, blogep2)
41 c.Assert(key, Equals, "")
42- c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "illegal-wordpress:db"`)
43+ c.Assert(err, ErrorMatches, `no relation between "mysql:db" and "illegal-wordpress:db"`)
44 key, err = s.t.RelationKey(mysqlep2, blogep1)
45 c.Assert(key, Equals, "")
46- c.Assert(err, ErrorMatches, `state: no relation between "illegal-mysql:db" and "wordpress:db"`)
47+ c.Assert(err, ErrorMatches, `no relation between "illegal-mysql:db" and "wordpress:db"`)
48 key, err = s.t.RelationKey(mysqlep1, riakep3)
49 c.Assert(key, Equals, "")
50- c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "riak:ring"`)
51+ c.Assert(err, ErrorMatches, `no relation between "mysql:db" and "riak:ring"`)
52 }
53
54 func (s *TopologySuite) TestPeerRelationKeyEndpoints(c *C) {
55@@ -751,7 +751,7 @@
56
57 // Endpoint without relation.
58 key, err = s.t.RelationKey(riakep3)
59- c.Assert(err, ErrorMatches, `state: no peer relation for "riak:ring"`)
60+ c.Assert(err, ErrorMatches, `no peer relation for "riak:ring"`)
61 }
62
63 func (s *TopologySuite) TestPeerRelationKeyIllegalEndpoints(c *C) {
64@@ -767,7 +767,7 @@
65
66 key, err := s.t.RelationKey(riakep1)
67 c.Assert(key, Equals, "")
68- c.Assert(err, ErrorMatches, `state: no peer relation for "riak:illegal-ring"`)
69+ c.Assert(err, ErrorMatches, `no peer relation for "riak:illegal-ring"`)
70 }
71
72 type ConfigNodeSuite struct {
73
74=== modified file 'state/machine.go'
75--- state/machine.go 2012-06-07 11:51:54 +0000
76+++ state/machine.go 2012-06-11 14:08:22 +0000
77@@ -35,7 +35,7 @@
78 func (m *Machine) WaitAgentAlive(timeout time.Duration) error {
79 err := presence.WaitAlive(m.st.zk, m.zkAgentPath(), timeout)
80 if err != nil {
81- return fmt.Errorf("state: waiting for agent of machine %s: %v", m, err)
82+ return fmt.Errorf("waiting for agent of machine %s: %v", m, err)
83 }
84 return nil
85 }
86@@ -61,7 +61,7 @@
87 if id, ok := v.(string); ok {
88 return id, nil
89 }
90- return "", fmt.Errorf("state: invalid internal machine key type: %T", v)
91+ return "", fmt.Errorf("invalid internal machine key type: %T", v)
92 }
93
94 // SetInstanceId sets the provider specific machine id for this machine.
95
96=== modified file 'state/relation.go'
97--- state/relation.go 2012-06-01 10:33:35 +0000
98+++ state/relation.go 2012-06-11 14:08:22 +0000
99@@ -1,6 +1,8 @@
100 package state
101
102-import ()
103+import (
104+ "fmt"
105+)
106
107 // RelationRole defines the role of a relation endpoint.
108 type RelationRole string
109@@ -19,6 +21,22 @@
110 ScopeContainer RelationScope = "container"
111 )
112
113+// NoRelationError represents a relation not found for one or more endpoints.
114+type NoRelationError struct {
115+ Endpoints []RelationEndpoint
116+}
117+
118+// Error returns the string representation of the error.
119+func (e NoRelationError) Error() string {
120+ switch len(e.Endpoints) {
121+ case 1:
122+ return fmt.Sprintf("no peer relation for %q", e.Endpoints[0])
123+ case 2:
124+ return fmt.Sprintf("no relation between %q and %q", e.Endpoints[0], e.Endpoints[1])
125+ }
126+ panic("illegal relation")
127+}
128+
129 // RelationEndpoint represents one endpoint of a relation.
130 type RelationEndpoint struct {
131 ServiceName string
132
133=== modified file 'state/service.go'
134--- state/service.go 2012-06-07 11:51:54 +0000
135+++ state/service.go 2012-06-11 14:08:22 +0000
136@@ -86,7 +86,7 @@
137 key := pathPkg.Base(path)
138 addUnit := func(t *topology) error {
139 if !t.HasService(s.key) {
140- return stateChanged
141+ return stateChangedError
142 }
143 err := t.AddUnit(key, principalKey)
144 if err != nil {
145@@ -140,7 +140,7 @@
146 }
147 removeUnit := func(t *topology) error {
148 if !t.HasUnit(unit.key) {
149- return stateChanged
150+ return stateChangedError
151 }
152 if err := t.RemoveUnit(unit.key); err != nil {
153 return err
154@@ -168,7 +168,7 @@
155 return nil, err
156 }
157 if !topology.HasService(s.key) {
158- return nil, stateChanged
159+ return nil, stateChangedError
160 }
161
162 // Check that unit exists.
163@@ -190,7 +190,7 @@
164 return nil, err
165 }
166 if !topology.HasService(s.key) {
167- return nil, stateChanged
168+ return nil, stateChangedError
169 }
170 keys, err := topology.UnitKeys(s.key)
171 if err != nil {
172
173=== modified file 'state/state.go'
174--- state/state.go 2012-06-07 11:51:54 +0000
175+++ state/state.go 2012-06-11 14:08:22 +0000
176@@ -46,19 +46,19 @@
177 key := machineKey(id)
178 removeMachine := func(t *topology) error {
179 if !t.HasMachine(key) {
180- return fmt.Errorf("machine not found")
181+ return stateError(MachineError, nil, "machine not found")
182 }
183 hasUnits, err := t.MachineHasUnits(key)
184 if err != nil {
185 return err
186 }
187 if hasUnits {
188- return fmt.Errorf("machine has units")
189+ return stateError(MachineError, nil, "machine has units")
190 }
191 return t.RemoveMachine(key)
192 }
193 if err := retryTopologyChange(s.zk, removeMachine); err != nil {
194- return fmt.Errorf("can't remove machine %d: %v", id, err)
195+ return stateError(MachineError, err, "can't remove machine %d", id)
196 }
197 return zkRemoveTree(s.zk, fmt.Sprintf("/machines/%s", key))
198 }
199@@ -207,7 +207,7 @@
200 // Remove the service from the topology.
201 removeService := func(t *topology) error {
202 if !t.HasService(svc.key) {
203- return stateChanged
204+ return stateChangedError
205 }
206 t.RemoveService(svc.key)
207 return nil
208@@ -381,13 +381,10 @@
209 // RemoveRelation removes the relation.
210 func (s *State) RemoveRelation(relation *Relation) error {
211 removeRelation := func(t *topology) error {
212- _, err := t.Relation(relation.key)
213- if err != nil {
214- return fmt.Errorf("can't remove relation: %v", err)
215- }
216 return t.RemoveRelation(relation.key)
217 }
218- // TODO: Improve high-level errors, no passing of low-level
219- // errors directly to the caller.
220- return retryTopologyChange(s.zk, removeRelation)
221+ if err := retryTopologyChange(s.zk, removeRelation); err != nil {
222+ return stateError(RelationError, err, "can't remove relation")
223+ }
224+ return nil
225 }
226
227=== modified file 'state/state_test.go'
228--- state/state_test.go 2012-06-07 11:51:54 +0000
229+++ state/state_test.go 2012-06-11 14:08:22 +0000
230@@ -221,7 +221,7 @@
231 c.Assert(err, IsNil)
232
233 id, err := machine.InstanceId()
234- c.Assert(err, ErrorMatches, "state: invalid internal machine key type: .*")
235+ c.Assert(err, ErrorMatches, "invalid internal machine key type: .*")
236 c.Assert(id, Equals, "")
237 }
238
239@@ -305,7 +305,7 @@
240 c.Assert(alive, Equals, false)
241
242 err = machine0.WaitAgentAlive(timeout)
243- c.Assert(err, ErrorMatches, `state: waiting for agent of machine 0: presence: still not alive after timeout`)
244+ c.Assert(err, ErrorMatches, `waiting for agent of machine 0: presence: still not alive after timeout`)
245
246 pinger, err := machine0.SetAgentAlive()
247 c.Assert(err, IsNil)
248@@ -675,7 +675,7 @@
249
250 // Assigning the unit to a different machine should fail.
251 err = unit.AssignToMachine(machineTwo)
252- c.Assert(err, ErrorMatches, `unit "wordpress/0" already assigned to machine 0`)
253+ c.Assert(err, ErrorMatches, `can't assign unit "wordpress/0" to machine 1: unit "wordpress/0" already assigned to machine 0`)
254
255 machineId, err := unit.AssignedMachineId()
256 c.Assert(err, IsNil)
257@@ -1249,5 +1249,5 @@
258 c.Assert(hasRelation, Equals, false)
259 c.Assert(err, ErrorMatches, `relation "relation-0000000000" does not exist`)
260 err = s.st.RemoveRelation(relation)
261- c.Assert(err, ErrorMatches, `can't remove relation: relation "relation-0000000000" does not exist`)
262+ c.Assert(err, ErrorMatches, `can't remove relation`)
263 }
264
265=== modified file 'state/topology.go'
266--- state/topology.go 2012-06-07 11:51:54 +0000
267+++ state/topology.go 2012-06-11 14:08:22 +0000
268@@ -1,7 +1,6 @@
269 package state
270
271 import (
272- "errors"
273 "fmt"
274 "launchpad.net/goyaml"
275 "launchpad.net/gozk/zookeeper"
276@@ -13,22 +12,6 @@
277 // when we know that a version is in fact actually incompatible.
278 const topologyVersion = 1
279
280-// NoRelationError represents a relation not found for one or more endpoints.
281-type NoRelationError struct {
282- Endpoints []RelationEndpoint
283-}
284-
285-// Error returns the string representation of the error.
286-func (e NoRelationError) Error() string {
287- switch len(e.Endpoints) {
288- case 1:
289- return fmt.Sprintf("state: no peer relation for %q", e.Endpoints[0])
290- case 2:
291- return fmt.Sprintf("state: no relation between %q and %q", e.Endpoints[0], e.Endpoints[1])
292- }
293- panic("state: illegal relation")
294-}
295-
296 // topoTopology is used to marshal and unmarshal the content
297 // of the /topology node in ZooKeeper.
298 type topoTopology struct {
299@@ -76,10 +59,10 @@
300 // check verifies that r is a proper relation.
301 func (r *topoRelation) check() error {
302 if len(r.Interface) == 0 {
303- return fmt.Errorf("relation interface is empty")
304+ return stateError(TopologyError, nil, "relation interface is empty")
305 }
306 if len(r.Services) == 0 {
307- return fmt.Errorf("relation has no services")
308+ return stateError(TopologyError, nil, "relation has no services")
309 }
310 counterpart := map[RelationRole]RelationRole{
311 RoleRequirer: RoleProvider,
312@@ -88,21 +71,21 @@
313 }
314 for serviceKey, service := range r.Services {
315 if serviceKey == "" {
316- return fmt.Errorf("relation has service with empty key")
317+ return stateError(TopologyError, nil, "relation has service with empty key")
318 }
319 if service.RelationName == "" {
320- return fmt.Errorf("relation has %s service with empty relation name", service.RelationRole)
321+ return stateError(TopologyError, nil, "relation has %s service with empty relation name", service.RelationRole)
322 }
323 counterRole, ok := counterpart[service.RelationRole]
324 if !ok {
325- return fmt.Errorf("relation has unknown service role: %q", service.RelationRole)
326+ return stateError(TopologyError, nil, "relation has unknown service role: %q", service.RelationRole)
327 }
328 if !r.hasServiceWithRole(counterRole) {
329- return fmt.Errorf("relation has %s but no %s", service.RelationRole, counterRole)
330+ return stateError(TopologyError, nil, "relation has %s but no %s", service.RelationRole, counterRole)
331 }
332 }
333 if len(r.Services) > 2 {
334- return fmt.Errorf("relation with mixed peer, provider, and requirer roles")
335+ return stateError(TopologyError, nil, "relation with mixed peer, provider, and requirer roles")
336 }
337 return nil
338 }
339@@ -132,7 +115,7 @@
340 // No topology node, so return empty topology.
341 return parseTopology("")
342 }
343- return nil, err
344+ return nil, stateError(PersistencyError, err, "can't read topology")
345 }
346 return parseTopology(yaml)
347 }
348@@ -141,7 +124,7 @@
349 func (t *topology) dump() (string, error) {
350 yaml, err := goyaml.Marshal(t.topology)
351 if err != nil {
352- return "", err
353+ return "", stateError(DataFormatError, err, "illegal topology format")
354 }
355 return string(yaml), nil
356 }
357@@ -156,7 +139,7 @@
358 if t.topology.Machines == nil {
359 t.topology.Machines = make(map[string]*topoMachine)
360 } else if t.HasMachine(key) {
361- return fmt.Errorf("attempted to add duplicated machine %q", key)
362+ return stateError(TopologyError, nil, "attempted to add duplicated machine %q", key)
363 }
364 t.topology.Machines[key] = &topoMachine{}
365 return nil
366@@ -169,7 +152,7 @@
367 return err
368 }
369 if ok {
370- return fmt.Errorf("can't remove machine %q while units ared assigned", key)
371+ return stateError(TopologyError, nil, "can't remove machine %q while units ared assigned", key)
372 }
373 // Machine exists and has no units, so remove it.
374 delete(t.topology.Machines, key)
375@@ -213,10 +196,10 @@
376 t.topology.Services = make(map[string]*topoService)
377 }
378 if t.HasService(key) {
379- return fmt.Errorf("attempted to add duplicated service %q", key)
380+ return stateError(TopologyError, nil, "attempted to add duplicated service %q", key)
381 }
382 if _, err := t.ServiceKey(name); err == nil {
383- return fmt.Errorf("service name %q already in use", name)
384+ return stateError(TopologyError, nil, "service name %q already in use", name)
385 }
386 t.topology.Services[key] = &topoService{
387 Name: name,
388@@ -235,7 +218,7 @@
389 return err
390 }
391 if len(relations) > 0 {
392- return fmt.Errorf("cannot remove service %q with active relations", key)
393+ return stateError(TopologyError, nil, "cannot remove service %q with active relations", key)
394 }
395 delete(t.topology.Services, key)
396 return nil
397@@ -253,7 +236,7 @@
398 return key, nil
399 }
400 }
401- return "", fmt.Errorf("service with name %q not found", name)
402+ return "", stateError(TopologyError, nil, "service with name %q not found", name)
403 }
404
405 // ServiceKeys returns all service keys.
406@@ -271,7 +254,7 @@
407 if svc, ok := t.topology.Services[key]; ok {
408 return svc.Name, nil
409 }
410- return "", fmt.Errorf("service with key %q not found", key)
411+ return "", stateError(TopologyError, nil, "service with key %q not found", key)
412 }
413
414 // HasUnit returns true if a unit with given service and unit keys exists.
415@@ -291,7 +274,7 @@
416 return err
417 }
418 if _, ok := svc.Units[unitKey]; ok {
419- return fmt.Errorf("unit %q already in use", unitKey)
420+ return stateError(TopologyError, nil, "unit %q already in use", unitKey)
421 }
422 svc.Units[unitKey] = &topoUnit{Principal: principalKey}
423 return nil
424@@ -332,35 +315,35 @@
425 return fmt.Sprintf("%s/%d", svc.Name, keySeq(unitKey)), nil
426 }
427
428-// unitNotSubordinate indicates that a unit is principal rather than subordinate.
429-var unitNotSubordinate = errors.New("service unit is a principal rather than a subordinate")
430+// unitNotSubordinateError indicates that a unit is principal rather than subordinate.
431+var unitNotSubordinateError = stateError(TopologyError, nil, "service unit is a principal rather than a subordinate")
432
433 // UnitPrincipalKey returns the unit key of the principal unit alongside which
434 // the specified subordinate unit is deployed. If the specified unit is not
435-// subordinate, unitNotSubordinate will be returned.
436+// subordinate, unitNotSubordinateError will be returned.
437 func (t *topology) UnitPrincipalKey(unitKey string) (string, error) {
438 _, unit, err := t.serviceAndUnit(unitKey)
439 if err != nil {
440 return "", err
441 }
442 if unit.Principal == "" {
443- return "", unitNotSubordinate
444+ return "", unitNotSubordinateError
445 }
446 return unit.Principal, nil
447 }
448
449-// unitNotAssigned indicates that a unit is not assigned to a machine.
450-var unitNotAssigned = errors.New("unit not assigned to machine")
451+// unitNotAssignedError indicates that a unit is not assigned to a machine.
452+var unitNotAssignedError = stateError(TopologyError, nil, "unit not assigned to machine")
453
454 // UnitMachineKey returns the key of an assigned machine of the unit. If no machine
455-// is assigned the error unitNotAssigned will be returned.
456+// is assigned the error unitNotAssignedError will be returned.
457 func (t *topology) UnitMachineKey(unitKey string) (string, error) {
458 _, unit, err := t.serviceAndUnit(unitKey)
459 if err != nil {
460 return "", err
461 }
462 if unit.Machine == "" {
463- return "", unitNotAssigned
464+ return "", unitNotAssignedError
465 }
466 return unit.Machine, nil
467 }
468@@ -378,10 +361,10 @@
469 return err
470 }
471 if unit.Principal != "" {
472- return errors.New("cannot assign subordinate units directly to machines")
473+ return stateError(TopologyError, nil, "cannot assign subordinate units directly to machines")
474 }
475 if unit.Machine != "" {
476- return fmt.Errorf("unit %q already assigned to machine %q", unitKey, unit.Machine)
477+ return stateError(TopologyError, nil, "unit %q already assigned to machine %q", unitKey, unit.Machine)
478 }
479 unit.Machine = machineKey
480 return nil
481@@ -394,7 +377,7 @@
482 return err
483 }
484 if unit.Machine == "" {
485- return fmt.Errorf("unit %q not assigned to a machine", unitKey)
486+ return stateError(TopologyError, nil, "unit %q not assigned to a machine", unitKey)
487 }
488 unit.Machine = ""
489 return nil
490@@ -403,7 +386,7 @@
491 // Relation returns the relation with key from the topology.
492 func (t *topology) Relation(key string) (*topoRelation, error) {
493 if t.topology.Relations == nil || t.topology.Relations[key] == nil {
494- return nil, fmt.Errorf("relation %q does not exist", key)
495+ return nil, stateError(TopologyError, nil, "relation %q does not exist", key)
496 }
497 return t.topology.Relations[key], nil
498 }
499@@ -415,7 +398,7 @@
500 }
501 _, ok := t.topology.Relations[relationKey]
502 if ok {
503- return fmt.Errorf("relation key %q already in use", relationKey)
504+ return stateError(TopologyError, nil, "relation key %q already in use", relationKey)
505 }
506 // Check if the relation definition and the service keys are valid.
507 if err := relation.check(); err != nil {
508@@ -514,7 +497,7 @@
509 if m, ok := t.topology.Machines[machineKey]; ok {
510 return m, nil
511 }
512- return nil, fmt.Errorf("machine with key %q not found", machineKey)
513+ return nil, stateError(TopologyError, nil, "machine with key %q not found", machineKey)
514 }
515
516 // service returns the service for the given key.
517@@ -522,7 +505,7 @@
518 if svc, ok := t.topology.Services[serviceKey]; ok {
519 return svc, nil
520 }
521- return nil, fmt.Errorf("service with key %q not found", serviceKey)
522+ return nil, stateError(TopologyError, nil, "service with key %q not found", serviceKey)
523 }
524
525 // serviceAndUnit returns the service and unit for the given unit key.
526@@ -538,7 +521,7 @@
527 if unit, ok := svc.Units[unitKey]; ok {
528 return svc, unit, nil
529 }
530- return nil, nil, fmt.Errorf("unit with key %q not found", unitKey)
531+ return nil, nil, stateError(TopologyError, nil, "unit with key %q not found", unitKey)
532 }
533
534 // relation returns the relation for the given key
535@@ -546,17 +529,17 @@
536 if t, ok := t.topology.Relations[relationKey]; ok {
537 return t, nil
538 }
539- return nil, fmt.Errorf("relation with key %q not found", relationKey)
540+ return nil, stateError(TopologyError, nil, "relation with key %q not found", relationKey)
541 }
542
543 // parseTopology returns the topology represented by yaml.
544 func parseTopology(yaml string) (*topology, error) {
545 t := &topology{topology: &topoTopology{Version: topologyVersion}}
546 if err := goyaml.Unmarshal([]byte(yaml), t.topology); err != nil {
547- return nil, err
548+ return nil, stateError(DataFormatError, err, "illegal topology format")
549 }
550 if t.topology.Version != topologyVersion {
551- return nil, fmt.Errorf("incompatible topology versions: got %d, want %d",
552+ return nil, stateError(TopologyError, nil, "incompatible topology versions: got %d, want %d",
553 t.topology.Version, topologyVersion)
554 }
555 return t, nil
556@@ -583,5 +566,11 @@
557 }
558 return it.dump()
559 }
560- return zk.RetryChange(zkTopologyPath, 0, zkPermAll, change)
561+ if err := zk.RetryChange(zkTopologyPath, 0, zkPermAll, change); err != nil {
562+ if sterr, ok := err.(StateError); ok {
563+ return sterr
564+ }
565+ return stateError(PersistencyError, err, "can't change topology")
566+ }
567+ return nil
568 }
569
570=== modified file 'state/unit.go'
571--- state/unit.go 2012-06-07 12:06:16 +0000
572+++ state/unit.go 2012-06-11 14:08:22 +0000
573@@ -194,7 +194,7 @@
574 return false, err
575 }
576 _, err = topology.UnitPrincipalKey(u.key)
577- if err == unitNotSubordinate {
578+ if err == unitNotSubordinateError {
579 return true, nil
580 }
581 return false, err
582@@ -216,7 +216,7 @@
583 return
584 }
585 case AssignUnused:
586- if _, err = u.AssignToUnusedMachine(); err != noUnusedMachines {
587+ if _, err = u.AssignToUnusedMachine(); err != noUnusedMachinesError {
588 return
589 }
590 if m, err = u.st.AddMachine(); err != nil {
591@@ -235,7 +235,7 @@
592 return 0, err
593 }
594 if !topology.HasUnit(u.key) {
595- return 0, stateChanged
596+ return 0, stateChangedError
597 }
598 machineKey, err := topology.UnitMachineKey(u.key)
599 if err != nil {
600@@ -248,10 +248,10 @@
601 func (u *Unit) AssignToMachine(machine *Machine) error {
602 assignUnit := func(t *topology) error {
603 if !t.HasUnit(u.key) {
604- return stateChanged
605+ return stateChangedError
606 }
607 machineKey, err := t.UnitMachineKey(u.key)
608- if err == unitNotAssigned {
609+ if err == unitNotAssignedError {
610 return t.AssignUnitToMachine(u.key, machine.key)
611 } else if err != nil {
612 return err
613@@ -259,12 +259,17 @@
614 // Everything is fine, it's already assigned.
615 return nil
616 }
617- return fmt.Errorf("unit %q already assigned to machine %d", u.Name(), keySeq(machineKey))
618- }
619- return retryTopologyChange(u.st.zk, assignUnit)
620+ return stateError(UnitError, nil, "unit %q already assigned to machine %d", u.Name(), keySeq(machineKey))
621+ }
622+ if err := retryTopologyChange(u.st.zk, assignUnit); err != nil {
623+ return stateError(UnitError, err, "can't assign unit %q to machine %d", u.Name(), machine.Id())
624+ }
625+ return nil
626 }
627
628-var noUnusedMachines = errors.New("no unused machine found")
629+// noUnusedMachinesError is returned if no unused machine for the
630+// assignment of a unit can be found.
631+var noUnusedMachinesError = stateError(UnitError, nil, "no unused machine found")
632
633 // AssignToUnusedMachine assigns u to a machine without other units.
634 // If there are no unused machines besides machine 0, an error is returned.
635@@ -272,7 +277,7 @@
636 machineKey := ""
637 assignUnusedUnit := func(t *topology) error {
638 if !t.HasUnit(u.key) {
639- return stateChanged
640+ return stateChangedError
641 }
642 for _, machineKey = range t.MachineKeys() {
643 if keySeq(machineKey) != 0 {
644@@ -288,7 +293,7 @@
645 machineKey = ""
646 }
647 if machineKey == "" {
648- return noUnusedMachines
649+ return noUnusedMachinesError
650 }
651 if err := t.AssignUnitToMachine(u.key, machineKey); err != nil {
652 return err
653@@ -306,7 +311,7 @@
654 func (u *Unit) UnassignFromMachine() error {
655 unassignUnit := func(t *topology) error {
656 if !t.HasUnit(u.key) {
657- return stateChanged
658+ return stateChangedError
659 }
660 // If for whatever reason it's already not assigned to a
661 // machine, ignore it and move forward so that we don't
662
663=== modified file 'state/util.go'
664--- state/util.go 2012-06-06 10:50:44 +0000
665+++ state/util.go 2012-06-11 14:08:22 +0000
666@@ -5,7 +5,6 @@
667 package state
668
669 import (
670- "errors"
671 "fmt"
672 "launchpad.net/goyaml"
673 "launchpad.net/gozk/zookeeper"
674@@ -13,9 +12,51 @@
675 "sort"
676 )
677
678+// ErrorCode represents a kind of state error.
679+type ErrorCode int
680+
681+const (
682+ PersistencyError ErrorCode = -4
683+ DataFormatError ErrorCode = -3
684+ StateChangedError ErrorCode = -2
685+ TopologyError ErrorCode = -1
686+ OK ErrorCode = 0
687+ MachineError ErrorCode = 1
688+ UnitError ErrorCode = 2
689+ RelationError ErrorCode = 3
690+)
691+
692+// StateError encapsulates errors occuring during topology operations.
693+type StateError struct {
694+ Code ErrorCode
695+ Message string
696+ Err error
697+}
698+
699+// stateError creates a new state error.
700+func stateError(code ErrorCode, err error, msg string, args ...interface{}) error {
701+ return StateError{
702+ Code: code,
703+ Message: fmt.Sprintf(msg, args...),
704+ Err: err,
705+ }
706+}
707+
708+// Error returns the string representation of the error.
709+func (e StateError) Error() string {
710+ if e.Err != nil {
711+ if sterr, ok := e.Err.(StateError); ok {
712+ if sterr.Code > OK {
713+ return e.Message + ": " + e.Err.Error()
714+ }
715+ }
716+ }
717+ return e.Message
718+}
719+
720 var (
721- // stateChange is a common error inside the state processing.
722- stateChanged = errors.New("environment state has changed")
723+ // stateChangedError is a common error inside the state processing.
724+ stateChangedError = stateError(StateChangedError, nil, "environment state has changed")
725 // zkPermAll is a convenience variable for creating new nodes.
726 zkPermAll = zookeeper.WorldACL(zookeeper.PERM_ALL)
727 )
728@@ -89,7 +130,7 @@
729 c.cache = copyCache(values)
730 _, err := c.Write()
731 if err != nil {
732- return nil, err
733+ return nil, stateError(PersistencyError, err, "can't create config node")
734 }
735 return c, nil
736 }
737@@ -108,7 +149,7 @@
738 // It does not affect the user-set contents.
739 func (c *ConfigNode) setPristineContent(content string) error {
740 if err := goyaml.Unmarshal([]byte(content), &c.pristineCache); err != nil {
741- return err
742+ return stateError(DataFormatError, err, "illegal content in config node")
743 }
744 return nil
745 }
746@@ -127,7 +168,7 @@
747 yaml, _, err := c.zk.Get(c.path)
748 if err != nil {
749 if !zookeeper.IsError(err, zookeeper.ZNONODE) {
750- return err
751+ return stateError(PersistencyError, err, "can't read config node")
752 }
753 }
754 if err := c.setPristineContent(yaml); err != nil {
755@@ -153,7 +194,7 @@
756 current := make(map[string]interface{})
757 if yaml != "" {
758 if err := goyaml.Unmarshal([]byte(yaml), current); err != nil {
759- return "", err
760+ return "", stateError(DataFormatError, err, "illegal content in config node")
761 }
762 }
763 for key := range cacheKeys(c.pristineCache, c.cache) {
764@@ -184,12 +225,15 @@
765 }
766 currentYaml, err := goyaml.Marshal(current)
767 if err != nil {
768- return "", err
769+ return "", stateError(DataFormatError, err, "illegal content for config node")
770 }
771 return string(currentYaml), nil
772 }
773 if err := c.zk.RetryChange(c.path, 0, zkPermAll, applyChanges); err != nil {
774- return nil, err
775+ if sterr, ok := err.(StateError); ok {
776+ return nil, sterr
777+ }
778+ return nil, stateError(PersistencyError, err, "can't change config node")
779 }
780 sort.Sort(itemChangeSlice(changes))
781 c.pristineCache = copyCache(c.cache)
782@@ -249,7 +293,7 @@
783 if zookeeper.IsError(err, zookeeper.ZNONODE) {
784 return nil
785 }
786- return err
787+ return stateError(PersistencyError, err, "can't remove tree")
788 }
789 for _, child := range children {
790 if err := zkRemoveTree(zk, pathpkg.Join(path, child)); err != nil {
791@@ -262,7 +306,7 @@
792 }
793 err = zk.Delete(path, -1)
794 if err != nil && !zookeeper.IsError(err, zookeeper.ZNONODE) {
795- return err
796+ return stateError(PersistencyError, err, "can't remove tree")
797 }
798 return nil
799 }
800
801=== modified file 'state/watcher_test.go'
802--- state/watcher_test.go 2012-06-06 19:19:11 +0000
803+++ state/watcher_test.go 2012-06-11 14:08:22 +0000
804@@ -95,7 +95,7 @@
805 }
806
807 err = configWatcher.Stop()
808- c.Assert(err, ErrorMatches, "YAML error: .*")
809+ c.Assert(err, ErrorMatches, "illegal content in config node")
810 }
811
812 type unitWatchNeedsUpgradeTest struct {

Subscribers

People subscribed via source and target branches