Index: state/relation.go |
=== modified file 'state/relation.go' |
--- state/relation.go 2012-10-09 02:18:51 +0000 |
+++ state/relation.go 2012-10-16 08:27:54 +0000 |
@@ -1,6 +1,7 @@ |
package state |
import ( |
+ "errors" |
"fmt" |
"labix.org/v2/mgo" |
"labix.org/v2/mgo/txn" |
@@ -81,6 +82,7 @@ |
Id int |
Endpoints []RelationEndpoint |
Life Life |
+ UnitCount int |
} |
// Relation represents a relation between one or two service endpoints. |
@@ -131,10 +133,17 @@ |
return nil |
} |
-// EnsureDead sets the relation lifecycle to Dead if it is Alive or Dying. |
-// It does nothing otherwise. |
+// EnsureDead sets the relation lifecycle to Dead if it is Alive or Dying, |
+// and does nothing if already Dead. |
+// It's an error to call it while there are still units within one or more |
+// scopes in the relation. |
func (r *Relation) EnsureDead() error { |
- err := ensureDead(r.st, r.st.relations, r.doc.Key, "relation", nil, "") |
+ ops := []txn.Op{{ |
+ C: r.st.relations.Name, |
+ Id: r.doc.Key, |
+ Assert: D{{"unitcount", 0}}, |
+ }} |
+ err := ensureDead(r.st, r.st.relations, r.doc.Key, "relation", ops, "relation still has member units") |
if err != nil { |
return err |
} |
@@ -226,52 +235,127 @@ |
return ru.endpoint |
} |
-// EnterScope ensures that the unit has entered its scope in the relation. |
-// A unit is a member of a relation when it has both entered its respective |
-// scope and its pinger is signaling presence in the environment. |
-func (ru *RelationUnit) EnterScope() (err error) { |
- defer trivial.ErrorContextf(&err, "cannot initialize state for unit %q in relation %q", ru.unit, ru.relation) |
+// ErrScopeClosed indicates that a relation scope cannot accept new members |
+// because the relation is not Alive. |
+var ErrScopeClosed = errors.New("scope is closed to new member units") |
+ |
+// EnterScope ensures that the unit has entered its scope in the relation and |
+// that its relation settings contain its private address. |
+// It is an error to enter a scope of a relation that is not alive, and no |
+// relation becomes Dead before all units have left. |
+func (ru *RelationUnit) EnterScope() error { |
address, err := ru.unit.PrivateAddress() |
if err != nil { |
- return err |
+ return fmt.Errorf("cannot initialize state for unit %q in relation %q: %v", ru.unit, ru.relation, err) |
} |
key, err := ru.key(ru.unit.Name()) |
if err != nil { |
return err |
} |
- _, err = createSettings(ru.st, key, map[string]interface{}{"private-address": address}) |
- if err == errSettingsExist { |
- node, err := readSettings(ru.st, key) |
- if err != nil { |
- return err |
- } |
- node.Set("private-address", address) |
- if _, err := node.Write(); err != nil { |
- return err |
- } |
+ content := map[string]interface{}{"private-address": address} |
+ |
+ // Assemble the resuable building blocks for the various transactions that |
+ // could be valid, depending on current remote state. |
+ enterScope := []txn.Op{{ |
+ C: ru.st.relationScopes.Name, |
+ Id: key, |
+ Assert: txn.DocMissing, |
+ Insert: relationScopeDoc{key}, |
+ }, { |
+ C: ru.st.relations.Name, |
+ Id: ru.relation.doc.Key, |
+ Assert: isAlive, |
+ Update: D{{"$inc", D{{"unitcount", 1}}}}, |
+ }} |
+ preserveScope := []txn.Op{{ |
+ C: ru.st.relationScopes.Name, |
+ Id: key, |
+ Assert: txn.DocExists, |
+ }, { |
+ C: ru.st.relations.Name, |
+ Id: ru.relation.doc.Key, |
+ Assert: D{{"unitcount", D{{"$gt", 0}}}}, |
+ }} |
+ updateSettings := txn.Op{ |
+ C: ru.st.settings.Name, |
+ Id: key, |
+ Assert: D{{"private-address", D{{"$ne", address}}}}, |
+ Update: content, |
+ } |
+ preserveSettings := txn.Op{ |
+ C: ru.st.settings.Name, |
+ Id: key, |
+ Assert: D{{"private-address", address}}, |
+ } |
+ |
+ // Any of the following op lists may be valid, but no more than one. |
+ opss := [][]txn.Op{ |
+ append(preserveScope, preserveSettings), |
+ append(preserveScope, updateSettings), |
+ append(enterScope, preserveSettings), |
+ append(enterScope, updateSettings), |
+ append(enterScope, txn.Op{ |
+ C: ru.st.settings.Name, |
+ Id: key, |
+ Assert: txn.DocMissing, |
+ Insert: content, |
+ }), |
+ } |
+ for _, ops := range opss { |
+ if err := ru.st.runner.Run(ops, "", nil); err == txn.ErrAborted { |
+ continue |
+ } else if err != nil { |
+ return err |
+ } |
+ return nil |
+ } |
+ |
+ // Reaching this point may indicate that the relation is not alive, or that |
+ // state is corrupt in some way. |
+ if err := ru.relation.Refresh(); IsNotFound(err) { |
+ return ErrScopeClosed |
} else if err != nil { |
return err |
} |
- ops := []txn.Op{{ |
- C: ru.st.relationScopes.Name, |
- Id: key, |
- Insert: relationScopeDoc{key}, |
- }} |
- return ru.st.runner.Run(ops, "", nil) |
+ if ru.relation.Life() == Alive { |
+ return fmt.Errorf("unit %q cannot enter relation %q scope: inconsistent state", ru.unit, ru.relation) |
+ } |
+ return ErrScopeClosed |
} |
// LeaveScope signals that the unit has left its scope in the relation. |
+// After the unit has left its relation scope, it is no longer a member |
+// of the relation. It is not an error to leave a scope that the unit is |
+// not, or never was, a member of. |
func (ru *RelationUnit) LeaveScope() error { |
key, err := ru.key(ru.unit.Name()) |
if err != nil { |
return err |
} |
- ops := []txn.Op{{ |
- C: ru.st.relationScopes.Name, |
- Id: key, |
+ preserveAbsence := []txn.Op{{ |
+ C: ru.st.relationScopes.Name, |
+ Id: key, |
+ Assert: txn.DocMissing, |
+ }} |
+ leaveScope := []txn.Op{{ |
+ C: ru.st.relations.Name, |
+ Id: ru.relation.doc.Key, |
+ Assert: D{{"unitcount", D{{"$gt", 0}}}}, |
+ Update: D{{"$inc", D{{"unitcount", -1}}}}, |
+ }, { |
+ C: ru.st.relationScopes.Name, |
+ Id: key, |
+ Assert: txn.DocExists, |
Remove: true, |
}} |
- return ru.st.runner.Run(ops, "", nil) |
+ err = ru.st.runner.Run(preserveAbsence, "", nil) |
+ if err == txn.ErrAborted { |
+ err = ru.st.runner.Run(leaveScope, "", nil) |
+ if err == txn.ErrAborted { |
+ return fmt.Errorf("unit %q cannot leave relation %q scope: inconsistent state", ru.unit, ru.relation) |
+ } |
+ } |
+ return err |
} |
// WatchScope returns a watcher which notifies of counterpart units |
@@ -308,13 +392,6 @@ |
if err != nil { |
return nil, err |
} |
- // TODO drop Count once readSettings refuses to read |
- // non-existent settings (which it should). |
- if n, err := ru.st.settings.FindId(key).Count(); err != nil { |
- return nil, err |
- } else if n == 0 { |
- return nil, fmt.Errorf("not found") |
- } |
node, err := readSettings(ru.st, key) |
if err != nil { |
return nil, err |