Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1754)

Unified Diff: state/relation.go

Issue 6687043: state: integrate relation membership with life
Patch Set: state: integrate relation membership with life Created 12 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « state/open.go ('k') | state/relation_test.go » ('j') | state/state.go » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « state/open.go ('k') | state/relation_test.go » ('j') | state/state.go » ('J')

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b