Description:
Fix relation-ids cache coherency for hooks.
Relation-ids was answering questions about relations
directly from state, whilst other pieces of relation-*
cli was using a hook execution cache. This led to
odd races where a hook would query relation-ids
and then attempt to use relation-list/etc against
that relation resulting in a relation not found error.
This branch also does a drive by to improve the error
reporting for relation not found to include the relation
id where applicable.
from juju import errors
-from juju.state.errors import RelationStateNotFound,
InvalidRelationIdentity
+from juju.state.errors import RelationIdentNotFound,
InvalidRelationIdentity
from juju.state.hook import RelationHookContext
@@ -172,7 +172,8 @@
def start(self):
"""Cache relation hook contexts for all relation idents."""
# Get all relation idents (None means "all")
- relation_idents = set((yield self.get_relation_idents(None)))
+ relation_idents = set((yield
self._context.get_relation_idents(None)))
+ #relation_idents = set((yield self.get_relation_idents(None)))
if isinstance(self._context, RelationHookContext):
# Exclude the parent context for being looked up as a child relation_idents.discard(self._context.relation_ident)
@@ -244,10 +245,14 @@
if len(parts) != 2 or not parts[1].isdigit(): raise InvalidRelationIdentity(relation_ident)
else:
- raise RelationStateNotFound()
+ raise RelationIdentNotFound(relation_ident)
+ @inlineCallbacks
def get_relation_idents(self, relation_name):
- return self._context.get_relation_idents(relation_name)
+ """Returns valid relation instances for the given name."""
+ idents = yield self._context.get_relation_idents(relation_name)
+ returnValue(
+ list(set(idents).intersection(set(self._relation_contexts))))
def validate_hook(self, hook_filename):
"""Verify that the hook_filename exists and is executable. """
+class RelationIdentNotFound(StateError):
+
+ def __init__(self, relation_ident):
+ # Play nice with amp oddities, where we get our own string repr
+ if "-" in relation_ident:
+ relation_ident = relation_ident.split("-")[-1].strip()
+ self.relation_ident = relation_ident
+
+ def __str__(self):
+ return "Relation not found for - %s" % (self.relation_ident)
+
+
class UnitRelationStateAlreadyAssigned(StateError):
"""The unit already exists in the relation."""
Index: juju/hooks/tests/test_invoker.py
=== modified file 'juju/hooks/tests/test_invoker.py'
--- juju/hooks/tests/test_invoker.py 2013-01-15 19:53:21 +0000
+++ juju/hooks/tests/test_invoker.py 2013-02-01 03:35:02 +0000
@@ -24,7 +24,7 @@
from juju.lib.twistutils import get_module_directory
from juju.state import hook
from juju.state.endpoint import RelationEndpoint
-from juju.state.errors import RelationStateNotFound
+from juju.state.errors import RelationIdentNotFound
from juju.state.relation import RelationStateManager
from juju.state.tests.test_relation import RelationTestBase
Reviewers: mp+146033_ code.launchpad. net,
Message:
Please take a look.
Description:
Fix relation-ids cache coherency for hooks.
Relation-ids was answering questions about relations
directly from state, whilst other pieces of relation-*
cli was using a hook execution cache. This led to
odd races where a hook would query relation-ids
and then attempt to use relation-list/etc against
that relation resulting in a relation not found error.
This branch also does a drive by to improve the error
reporting for relation not found to include the relation
id where applicable.
https:/ /code.launchpad .net/~hazmat/ juju/sane- relation- ids/+merge/ 146033
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/7241062/
Affected files: invoker. py protocol. py tests/test_ invoker. py errors. py
A [revision details]
M juju/hooks/
M juju/hooks/
M juju/hooks/
M juju/state/
Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: <email address hidden>
+New revision: <email address hidden>
Index: juju/hooks/ invoker. py invoker. py' invoker. py 2013-01-15 19:53:21 +0000 invoker. py 2013-02-01 03:35:02 +0000 python. failure import Failure
=== modified file 'juju/hooks/
--- juju/hooks/
+++ juju/hooks/
@@ -6,7 +6,7 @@
from twisted.
from juju import errors tFound, Identity tFound, Identity
-from juju.state.errors import RelationStateNo
InvalidRelation
+from juju.state.errors import RelationIdentNo
InvalidRelation
from juju.state.hook import RelationHookContext
@@ -172,7 +172,8 @@ relation_ idents( None))) get_relation_ idents( None))) relation_ idents( None))) self._context, RelationHookCon text):
relation_ idents. discard( self._context. relation_ ident)
raise InvalidRelation Identity( relation_ ident) tFound( ) tFound( relation_ ident)
def start(self):
"""Cache relation hook contexts for all relation idents."""
# Get all relation idents (None means "all")
- relation_idents = set((yield self.get_
+ relation_idents = set((yield
self._context.
+ #relation_idents = set((yield self.get_
if isinstance(
# Exclude the parent context for being looked up as a child
@@ -244,10 +245,14 @@
if len(parts) != 2 or not parts[1].isdigit():
else:
- raise RelationStateNo
+ raise RelationIdentNo
+ @inlineCallbacks idents( self, relation_name): get_relation_ idents( relation_ name) get_relation_ idents( relation_ name) idents) .intersection( set(self. _relation_ contexts) )))
def get_relation_
- return self._context.
+ """Returns valid relation instances for the given name."""
+ idents = yield self._context.
+ returnValue(
+ list(set(
def validate_hook(self, hook_filename):
"""Verify that the hook_filename exists and is executable. """
Index: juju/hooks/ protocol. py protocol. py' protocol. py 2012-10-16 23:19:55 +0000 protocol. py 2013-02-01 03:35:02 +0000 formatter, formatter_ from_env Identity, RelationStateNo tFound, teNotFound) Identity, RelationStateNo tFound, teNotFound, tFound)
=== modified file 'juju/hooks/
--- juju/hooks/
+++ juju/hooks/
@@ -54,7 +54,8 @@
from juju.lib import serializer
from juju.lib.format import get_charm_
get_charm_
from juju.state.errors import (
- InvalidRelation
UnitRelationSta
+ InvalidRelation
UnitRelationSta
+ RelationIdentNo
from juju.state.hook import RelationHookContext
@@ -92,7 +93,8 @@
UnitRelation StateNotFound: "UnitRelationSt ateNotFound" ,
MustSpecifyR elationName: "MustSpecifyRel ationName" ,
InvalidRelat ionIdentity: "InvalidRelatio nIdentity" , tFound: "RelationStateN otFound" tFound: "RelationStateN otFound" , tFound: "RelationIdentN otFound"
- RelationStateNo
+ RelationStateNo
+ RelationIdentNo
}
Index: juju/state/ errors. py errors. py' errors. py 2012-04-11 17:48:26 +0000 errors. py 2013-02-01 03:35:02 +0000
=== modified file 'juju/state/
--- juju/state/
+++ juju/state/
@@ -228,6 +228,18 @@
return "Relation not found"
+class RelationIdentNo tFound( StateError) : ident.split( "-")[-1] .strip( ) ident) teAlreadyAssign ed(StateError) :
+
+ def __init__(self, relation_ident):
+ # Play nice with amp oddities, where we get our own string repr
+ if "-" in relation_ident:
+ relation_ident = relation_
+ self.relation_ident = relation_ident
+
+ def __str__(self):
+ return "Relation not found for - %s" % (self.relation_
+
+
class UnitRelationSta
"""The unit already exists in the relation."""
Index: juju/hooks/ tests/test_ invoker. py tests/test_ invoker. py' tests/test_ invoker. py 2013-01-15 19:53:21 +0000 tests/test_ invoker. py 2013-02-01 03:35:02 +0000 directory tFound tFound nager tests.test_ relation import RelationTestBase
=== modified file 'juju/hooks/
--- juju/hooks/
+++ juju/hooks/
@@ -24,7 +24,7 @@
from juju.lib.twistutils import get_module_
from juju.state import hook
from juju.state.endpoint import RelationEndpoint
-from juju.state.errors import RelationStateNo
+from juju.state.errors import RelationIdentNo
from juju.state.relation import RelationStateMa
from juju.state.
@@ -1046,12 +1046,12 @@
# Not yet visible relation
self. assertRaises( tFound, tFound,
exe.get_ cached_ relation_ hook_context, "db:2")
- RelationStateNo
+ RelationIdentNo
# Nonexistent relation idents
self. assertRaises( tFound, tFound,
exe.get_ cached_ relation_ hook_context, "db:12345")
- RelationStateNo
+ RelationIdentNo
# Reread parent and child contexts
set((yield exe.get_ context( ).get_relation_ idents( "db"))) ,
set(["db: 0", "db:2"])) tFound, tFound,
exe.get_ cached_ relation_ hook_context, "db:1")
@@ -1156,7 +1156,7 @@
yield self.assertRaises(
- RelationStateNo
+ RelationIdentNo
@ defer.inlineCal lbacks
@@ -1294,9 +1294,9 @@
db1 = exe.get_ cached_ relation_ hook_context( "db:1") zk_data( db1, { 0.example. com"}) 0.example. com"})
self. assertLogLines(
self.log. getvalue( ),
["Cached relation hook contexts on 'db:0': ['db:1']",
self. assertEqual( result, 0)
self. assert_ file(args[ "stdout" ], "") file(args[ "stderr" ], "Relation not found\n") file(args[ "stderr" ], "Relation not found for -
self. assertEqual(
hook_log. getvalue( ),
"Cached relation hook contexts on 'db:0': ['db:1']\n"
self. assertEqual( result, 0)
self. assert_ file(args[ "settings" ], "") file(args[ "stderr" ], "Relation not found\n") file(args[ "stderr" ], "Relation not found for -
self. assertEqual(
hook_log. getvalue( ),
"Cached relation hook contexts on 'db:0': ['db:1']\n"
self. assertEqual( result, 0)
self. assert_ file(args[ "stdout" ], "") file(args[ "stderr" ], "Relation not found\n") file(args[ "stderr" ], "Relation not found for -
self. assertEqual(
hook_log. getvalue( ),
"Cached relation hook contexts on 'db:0': ['db:1']\n"
yield self.assert_
- "a": "42",
- "b": "xyz",
- "private-address": "mysql-
+ "a": "42",
+ "b": "xyz",
+ "private-address": "mysql-
@@ -1323,7 +1323,7 @@
yield exe.ended
- self.assert_
+ self.assert_
db:12345\n")
@@ -1400,7 +1400,7 @@
yield exe.ended
- self.assert_
+ self.assert_
db:12345\n")
@@ -1493,7 +1493,7 @@
yield exe.ended
- self.assert_
+ self.assert_
db:12345\n")