Merge lp:~jimbaker/pyjuju/rel-mgmt-add-relation-simple into lp:pyjuju
- rel-mgmt-add-relation-simple
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gustavo Niemeyer |
Approved revision: | 131 |
Merged at revision: | 115 |
Proposed branch: | lp:~jimbaker/pyjuju/rel-mgmt-add-relation-simple |
Merge into: | lp:pyjuju |
Diff against target: |
1674 lines (+608/-532) 12 files modified
ensemble/hooks/tests/test_invoker.py (+9/-5) ensemble/state/endpoint.py (+6/-6) ensemble/state/errors.py (+24/-10) ensemble/state/relation.py (+88/-102) ensemble/state/service.py (+5/-2) ensemble/state/tests/test_endpoint.py (+8/-8) ensemble/state/tests/test_errors.py (+31/-8) ensemble/state/tests/test_hook.py (+9/-5) ensemble/state/tests/test_relation.py (+297/-235) ensemble/state/tests/test_service.py (+49/-142) ensemble/state/tests/test_topology.py (+55/-7) ensemble/state/topology.py (+27/-2) |
To merge this branch: | bzr merge lp:~jimbaker/pyjuju/rel-mgmt-add-relation-simple |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
Review via email: mp+42704@code.launchpad.net |
Commit message
Description of the change
This is substantially Kapil's patch (https:/
This branch is of course much simpler than what I originally proposed for a command pattern approach to composable changes in https:/
Jim Baker (jimbaker) wrote : | # |
On Mon, Dec 6, 2010 at 1:13 PM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Fixing
>
> [1]
>
> Jim, as Kapil stated and as I reminded over our call, Kapil's code was a
> quick hack put
> in place to demonstrate the concept. There are error situations which
> should be verified
> and tested within that logic. Assigning a service to the same relation
> twice shouldn't
> be possible, for instance, and InternalTopolog
> out of the
> state code, so there are checks which must be made and appropriate errors
> raised in
> those cases.
>
> Please look at existing code similar to that logic to identify the pattern.
>
ack
- 111. By Jim Baker
-
Added unit tests for duplicate assignments and missing services in add_relation_state
- 112. By Jim Baker
-
Added unit tests for duplicate assignments and missing services in add_relation_state
- 113. By Jim Baker
-
More unit tests around add_relation_state to enusre InternalTopolog
yError exceptions do not leak - 114. By Jim Baker
-
PEP8
Jim Baker (jimbaker) wrote : | # |
Please take a look at the latest version of the branch that I've pushed.
On Mon, Dec 6, 2010 at 1:58 PM, Jim Baker <email address hidden> wrote:
> On Mon, Dec 6, 2010 at 1:13 PM, Gustavo Niemeyer <email address hidden>wrote:
>
>> Review: Needs Fixing
>>
>> [1]
>>
>> Jim, as Kapil stated and as I reminded over our call, Kapil's code was a
>> quick hack put
>> in place to demonstrate the concept. There are error situations which
>> should be verified
>> and tested within that logic. Assigning a service to the same relation
>> twice shouldn't
>> be possible, for instance, and InternalTopolog
>> out of the
>> state code, so there are checks which must be made and appropriate errors
>> raised in
>> those cases.
>>
>> Please look at existing code similar to that logic to identify the
>> pattern.
>>
>
> ack
>
Additional checks added, along with unit tests to verify that appropriate
errors are raised (presumably ServiceRelation
InternalTopolog
Gustavo Niemeyer (niemeyer) wrote : | # |
[1]
Is the error checking really doing the right thing? It looks like it's verifying that
the services were not yet added to the relation which was just created, which will of
course always be false.
Am I misunderstanding it?
Gustavo Niemeyer (niemeyer) wrote : | # |
As an additional aid, please note that the intention is preventing that the same
relation *endpoints* are not connected twice between the same two services. It's
fine to have different endpoints connected, though.
Gustavo Niemeyer (niemeyer) wrote : | # |
[2]
One more point, I believe there's no need to have relation_type in the method signature,
since all of the endpoints must necessarily have that type as well (and they must be
the same, which is another interesting situation to catch).
Gustavo Niemeyer (niemeyer) wrote : | # |
[3]
379 + def relation_
380 + """Return true if this `role` is already assigned to `relation_id`.
381 + """
That's missing unittests.
Gustavo Niemeyer (niemeyer) wrote : | # |
[4]
RelationState.
focus on the real issues. We have no plans of introducing a way to link additional *services*
into the same *relation*.
Jim Baker (jimbaker) wrote : | # |
On Tue, Dec 7, 2010 at 1:41 PM, Gustavo Niemeyer <email address hidden>wrote:
> [4]
>
> RelationState.
> removed so that we can
> focus on the real issues. We have no plans of introducing a way to link
> additional *services*
> into the same *relation*.
>
agreed!
Jim Baker (jimbaker) wrote : | # |
On Tue, Dec 7, 2010 at 1:38 PM, Gustavo Niemeyer <email address hidden>wrote:
> [3]
>
> 379 + def relation_
> 380 + """Return true if this `role` is already assigned to
> `relation_id`.
> 381 + """
>
> That's missing unittests.
>
ack
Jim Baker (jimbaker) wrote : | # |
On Tue, Dec 7, 2010 at 1:25 PM, Gustavo Niemeyer <email address hidden>wrote:
> [2]
>
> One more point, I believe there's no need to have relation_type in the
> method signature,
> since all of the endpoints must necessarily have that type as well (and
> they must be
> the same, which is another interesting situation to catch).
We should assert the endpoints share a relation_type. In addition we now
must have either one or two endpoints passed in, since we will eliminate the
relation_type (and vestigial assign_service).
Jim Baker (jimbaker) wrote : | # |
On Tue, Dec 7, 2010 at 1:20 PM, Gustavo Niemeyer <email address hidden>wrote:
>
> [1]
>
> Is the error checking really doing the right thing? It looks like it's
> verifying that
> the services were not yet added to the relation which was just created,
> which will of
> course always be false.
>
> Am I misunderstanding it?
This is not clear to me - what error checking are you referring specifically
to?
Gustavo Niemeyer (niemeyer) wrote : | # |
[1]
Of course. I'm referring to the error checking you introduced in the branch as
a side effect of point 1 above. E.g.:
55 + if (topology.
56 + service_
57 + service_
58 + topology.
59 + service_
60 + service_
61 + raise ServiceRelation
62 + topology.
63 + service_
64 + service_
65 + service_
[2]
> We should assert the endpoints share a relation_type. In addition we now
> must have either one or two endpoints passed in, since we will eliminate the
> relation_type (and vestigial assign_service).
Sounds good. It doesn't ever make sense to create a relation without services, and
most probably, as we discussed yesterday, it doesn't make sense to assign new
services to an existing relation either.
- 115. By Jim Baker
-
Merge trunk
- 116. By Jim Baker
-
Transitional code to remove support for assign_
service/ unassign_ service - 117. By Jim Baker
-
Fix ensemble.
state.tests. test_hook - 118. By Jim Baker
-
Fix ensemble.
hooks.tests. test_invoker - 119. By Jim Baker
-
Fix test_watch_
relations_ when_being_ created - 120. By Jim Baker
-
Change add_relation_state so that it requires 1 or 2 endpoints, which share a relation type
- 121. By Jim Baker
-
Removed relation_type from add_relation_state signature
- 122. By Jim Baker
-
Cleanup
- 123. By Jim Baker
-
Update docs and use better method names
- 124. By Jim Baker
-
Merged trunk
- 125. By Jim Baker
-
Test topology.
relation_ has_role - 126. By Jim Baker
-
Verify compatibility of endpoints in add_relation_state, so we can remove topology checking logic
- 127. By Jim Baker
-
PEP8
Jim Baker (jimbaker) wrote : | # |
On Wed, Dec 8, 2010 at 6:07 AM, Gustavo Niemeyer <email address hidden>wrote:
> [1]
>
> Of course. I'm referring to the error checking you introduced in the branch
> as
> a side effect of point 1 above. E.g.:
>
> 55 + if (topology.
> 56 + service_
> 57 + service_
> 58 + topology.
> 59 + service_
> 60 + service_
> 61 + raise ServiceRelation
> 62 + topology.
> 63 + service_
> 64 + service_
> 65 + service_
>
>
Removed error checking in the topology test in favor of checking the
endpoints for compatibility. As a side effect, I also did the pending
revision of provides -> server, requires -> client, and peers -> peer to
standardize role names. Note that since we use other role names in testing,
the endpoint verification is weaker than it might be.
> [2]
>
> > We should assert the endpoints share a relation_type. In addition we now
> > must have either one or two endpoints passed in, since we will eliminate
> the
> > relation_type (and vestigial assign_service).
>
> Sounds good. It doesn't ever make sense to create a relation without
> services, and
> most probably, as we discussed yesterday, it doesn't make sense to assign
> new
> services to an existing relation either.
>
>
Done
Jim Baker (jimbaker) wrote : | # |
On Tue, Dec 7, 2010 at 1:25 PM, Gustavo Niemeyer <email address hidden>wrote:
> [2]
>
> One more point, I believe there's no need to have relation_type in the
> method signature,
> since all of the endpoints must necessarily have that type as well (and
> they must be
> the same, which is another interesting situation to catch).
Removed, and asserted they must be the same in the endpoints, with tests.
Gustavo Niemeyer (niemeyer) wrote : | # |
[5]
Here is a simpler version of has_relation_
service_ids = dict((e, self.find_
relations = self._state.
for _, services in relations.
for endpoint in endpoints:
if not service or service["name"] != endpoint.
else:
return False
[6]
1450 + def test_has_
This test is verifying a situation we don't ever want to allow, and if the situation
does to through as a bug, we shouldn't allow a new relation to be created among the
same endpoints. In other words, (service1:a, service2:b) shouldn't *ever* be ambiguous.
[7]
+ # TODO: consider adding this assertion, but it will break some
+ # test code (example: some tests use the role name of
+ # ``role``). It may also make sense to have other types of
+ # role names outside of testing (such as ``dev`` or ``prod``).
The assertion seems sensible. The alternative relation roles don't seem to make sense, though.
[8]
+ def add_relation(
+ if topology.
+ raise RelationAlready
Besides doing this inside the change method, I suggest redundantly doing that
upfront before the relation state is created, so that we avoid garbage which
we know won't be usable.
[9]
+ if len(endpoints) == 2:
+ assert endpoints[
+ "Duplicate peer endpoint"
+ if (endpoints[
+ endpoints[
+ assert endpoints[
+ "Endpoints are not compatible: %r" % (endpoints,)
These assertions should be proper well-tested errors, rather than AssertionErrors,
since they are acceptable situations within the application which need proper
handling and proper notifying.
Also, note that the nested "if" isn't really necessary. If there are two endpoints,
they must necessarily be compatible, so you can safely enforce endpoints[
[10]
+ self.topology.
+ "mysql",
+ ("mysql", {"s-0": {"name": "mysql", "role": "client"}}),
+ {"s-0": blog_ep, "s-1": mysql_ep}),
+ "Missing assigned service")
Unless really necessary, we try to avoid whitebox testing, since it binds the test to
the specifics of the implementation. That case is even more obvious given how convoluted
the signature of this method is. Just looking at this method, for instance, I have no
idea about what these arguments mean.
Luckily, given [5], this should easily go away.
Jim Baker (jimbaker) wrote : | # |
Pushed the latest revision, so please do what I hope is one last review on
that.
On Thu, Dec 9, 2010 at 8:36 AM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Fixing
> [5]
>
> Here is a simpler version of has_relation_
>
> service_ids = dict((e, self.find_
> for e in endpoints)
> relations = self._state.
> for _, services in relations.
> for endpoint in endpoints:
> service = services.
> if not service or service["name"] != endpoint.
> break
> else:
> return True
> return False
>
>
Implemented, thanks!
> [6]
>
> 1450 + def
> test_has_
>
> This test is verifying a situation we don't ever want to allow, and if the
> situation
> does to through as a bug, we shouldn't allow a new relation to be created
> among the
> same endpoints. In other words, (service1:a, service2:b) shouldn't *ever*
> be ambiguous.
>
>
Removed
> [7]
>
> + # TODO: consider adding this assertion, but it will break some
> + # test code (example: some tests use the role name of
> + # ``role``). It may also make sense to have other types of
> + # role names outside of testing (such as ``dev`` or ``prod``).
>
> The assertion seems sensible. The alternative relation roles don't seem to
> make sense, though.
>
Fixed the TODO to document the intention for a future branch, where we will
change the tests accordingly so they don't use invalid role names.
> [8]
>
> + def add_relation(
> + if topology.
> + raise RelationAlready
>
> Besides doing this inside the change method, I suggest redundantly doing
> that
> upfront before the relation state is created, so that we avoid garbage
> which
> we know won't be usable.
>
Makes sense, added. Ideally this race on adds would be tested via mocker's
patching support, but deferring that for now, since this can be considered
an optimization.
> [9]
>
> + if len(endpoints) == 2:
> + assert endpoints[
> + "Duplicate peer endpoint"
> + if (endpoints[
> + endpoints[
> + assert endpoints[
> + "Endpoints are not compatible: %r" % (endpoints,)
>
> These assertions should be proper well-tested errors, rather than
> AssertionErrors,
> since they are acceptable situations within the application which need
> proper
> handling and proper notifying.
>
> Also, note that the nested "if" isn't really necessary. If there are two
> endpoints,
> they must necessarily be compatible, so you can safely enforce
> endpoints[
>
Changed the errors accordingly and cleaned up the type checking logic. The
n...
Gustavo Niemeyer (niemeyer) wrote : | # |
Thanks for the changes. Just one follow up:
[9]
> Changed the errors accordingly and cleaned up the type checking logic. The
> nested-if is still required until we update the tests, per [7].
No, it's not required. As mentioned, the only chance of may_relate_to() being true
in that location is them being of the correct roles and types, and if it is false
the endpoints are necessarily incompatible.
Considering that, +1!
Preview Diff
1 | === modified file 'ensemble/hooks/tests/test_invoker.py' |
2 | --- ensemble/hooks/tests/test_invoker.py 2010-12-07 19:05:09 +0000 |
3 | +++ ensemble/hooks/tests/test_invoker.py 2010-12-10 23:06:27 +0000 |
4 | @@ -11,6 +11,7 @@ |
5 | from ensemble.hooks import invoker |
6 | from ensemble.hooks.protocol import UnitSettingsFactory |
7 | from ensemble.state import hook |
8 | +from ensemble.state.endpoint import RelationEndpoint |
9 | from ensemble.state.tests.test_relation import RelationTestBase |
10 | |
11 | |
12 | @@ -104,11 +105,14 @@ |
13 | |
14 | @defer.inlineCallbacks |
15 | def _default_relations(self): |
16 | - self.mysql_states = yield self.add_relation_service_unit( |
17 | - "mysql", "mysql", "", "server") |
18 | - self.wordpress_states = yield self.add_relation_service_unit( |
19 | - "mysql", "wordpress", "", "client", |
20 | - relation_state=self.mysql_states["relation"]) |
21 | + wordpress_ep = RelationEndpoint( |
22 | + "wordpress", "client-server", "", "client") |
23 | + mysql_ep = RelationEndpoint( |
24 | + "mysql", "client-server", "", "server") |
25 | + self.wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
26 | + wordpress_ep, mysql_ep) |
27 | + self.mysql_states = yield self.add_opposite_service_unit( |
28 | + self.wordpress_states) |
29 | self.relation = self.mysql_states["unit_relation"] |
30 | |
31 | def _manage_path(self): |
32 | |
33 | === modified file 'ensemble/state/endpoint.py' |
34 | --- ensemble/state/endpoint.py 2010-12-03 03:36:30 +0000 |
35 | +++ ensemble/state/endpoint.py 2010-12-10 23:06:27 +0000 |
36 | @@ -33,9 +33,9 @@ |
37 | if not isinstance(other, RelationEndpoint): |
38 | raise TypeError("Not a RelationEndpoint", other) |
39 | return (self.relation_type == other.relation_type and |
40 | - ((self.relation_role == "provides" and |
41 | - other.relation_role == "requires") or |
42 | - (self.relation_role == "requires" and |
43 | - other.relation_role == "provides") or |
44 | - (self.relation_role == "peers" and |
45 | - other.relation_role == "peers"))) |
46 | + ((self.relation_role == "server" and |
47 | + other.relation_role == "client") or |
48 | + (self.relation_role == "client" and |
49 | + other.relation_role == "server") or |
50 | + (self.relation_role == "peer" and |
51 | + other.relation_role == "peer"))) |
52 | |
53 | === modified file 'ensemble/state/errors.py' |
54 | --- ensemble/state/errors.py 2010-12-10 04:55:47 +0000 |
55 | +++ ensemble/state/errors.py 2010-12-10 23:06:27 +0000 |
56 | @@ -94,18 +94,13 @@ |
57 | self.unit_name |
58 | |
59 | |
60 | -class ServiceRelationAlreadyAssigned(StateError): |
61 | +class RelationAlreadyExists(StateError): |
62 | |
63 | - def __init__(self, service_name, relation_name, relation_role): |
64 | - self.service_name = service_name |
65 | - self.relation_name = relation_name |
66 | - self.relation_role = relation_role |
67 | + def __init__(self, *endpoints): |
68 | + self.endpoints = endpoints |
69 | |
70 | def __str__(self): |
71 | - return ( |
72 | - "Service %r is already in relation with different name %r " |
73 | - "or role %r.") % ( |
74 | - self.service_name, self.relation_name, self.relation_role) |
75 | + return "Relation already exists %r" % (self.endpoints,) |
76 | |
77 | |
78 | class UnitRelationStateAlreadyAssigned(StateError): |
79 | @@ -156,7 +151,7 @@ |
80 | self.relation_role, self.service_name) |
81 | |
82 | |
83 | -class BadDescriptor(StateError): |
84 | +class BadDescriptor(ValueError, EnsembleError): |
85 | """Descriptor is not valid. |
86 | |
87 | A descriptor must be of the form <service name>[:<relation name>]. |
88 | @@ -169,3 +164,22 @@ |
89 | |
90 | def __str__(self): |
91 | return "Bad descriptor: %r" % (self.descriptor,) |
92 | + |
93 | + |
94 | +class DuplicateEndpoints(StateError): |
95 | + """Endpoints cannot be duplicate.""" |
96 | + |
97 | + def __init__(self, *endpoints): |
98 | + self.endpoints = endpoints |
99 | + |
100 | + def __str__(self): |
101 | + return "Duplicate endpoints: %r" % (self.endpoints,) |
102 | + |
103 | +class IncompatibleEndpoints(StateError): |
104 | + """Endpoints are incompatible.""" |
105 | + |
106 | + def __init__(self, *endpoints): |
107 | + self.endpoints = endpoints |
108 | + |
109 | + def __str__(self): |
110 | + return "Incompatible endpoints: %r" % (self.endpoints,) |
111 | |
112 | === modified file 'ensemble/state/relation.py' |
113 | --- ensemble/state/relation.py 2010-12-07 21:49:45 +0000 |
114 | +++ ensemble/state/relation.py 2010-12-10 23:06:27 +0000 |
115 | @@ -9,37 +9,107 @@ |
116 | |
117 | from ensemble.state.base import StateBase |
118 | from ensemble.state.errors import ( |
119 | - StateChanged, ServiceRelationAlreadyAssigned, UnitRelationStateNotFound, |
120 | - UnitRelationStateAlreadyAssigned, UnknownRelationRole) |
121 | + DuplicateEndpoints, IncompatibleEndpoints, RelationAlreadyExists, |
122 | + StateChanged, UnitRelationStateAlreadyAssigned, UnitRelationStateNotFound, |
123 | + UnknownRelationRole) |
124 | |
125 | |
126 | class RelationStateManager(StateBase): |
127 | """Manages the state of relations in an environment.""" |
128 | |
129 | @inlineCallbacks |
130 | - def add_relation_state(self, relation_type): |
131 | - """Add a new relation state. The relation is of the given type. |
132 | + def add_relation_state(self, *endpoints): |
133 | + """Add new relation state with the common relation type of `endpoints`. |
134 | |
135 | - Type is used to distinguish the type of the relation, ie. |
136 | - 'postgres', or 'varnish'. |
137 | + There must be one or two endpoints specified, with the same |
138 | + `relation_type`. Their corresponding services will be assigned |
139 | + atomically. |
140 | """ |
141 | + |
142 | + # the TOODs in the following comments in this function are for |
143 | + # type checking to be implemented ASAP. However, this will |
144 | + # require some nontrivial test modification, so it's best to |
145 | + # be done in a future branch. This is because these tests use |
146 | + # such invalid role names as ``role``, ``dev``, and ``prod``; |
147 | + # or they add non-peer relations of only one endpoint. |
148 | + |
149 | + if len(endpoints) == 1: |
150 | + # TODO verify that the endpoint is a peer endpoint only |
151 | + pass |
152 | + elif len(endpoints) == 2: |
153 | + if endpoints[0] == endpoints[1]: |
154 | + raise DuplicateEndpoints(endpoints) |
155 | + |
156 | + # TODO verify that the relation roles are client or server |
157 | + # only |
158 | + if (endpoints[0].relation_role in ("client", "server") or |
159 | + endpoints[1].relation_role in ("client", "server")): |
160 | + if not endpoints[0].may_relate_to(endpoints[1]): |
161 | + raise IncompatibleEndpoints(endpoints) |
162 | + else: |
163 | + raise TypeError("Requires 1 or 2 endpoints, %d given" % \ |
164 | + len(endpoints)) |
165 | + |
166 | + # first check so as to prevent unnecessary garbage in ZK in |
167 | + # case this relation has been previously added |
168 | + topology = yield self._read_topology() |
169 | + if topology.has_relation_between_endpoints(endpoints): |
170 | + raise RelationAlreadyExists(endpoints) |
171 | + |
172 | + relation_type = endpoints[0].relation_type |
173 | + relation_id = yield self._add_relation_state(relation_type) |
174 | + services = [] |
175 | + for endpoint in endpoints: |
176 | + service_id = topology.find_service_with_name(endpoint.service_name) |
177 | + yield self._add_service_relation_state( |
178 | + relation_id, service_id, endpoint) |
179 | + services.append(ServiceRelationState(self._client, |
180 | + service_id, |
181 | + relation_id, |
182 | + endpoint.relation_role, |
183 | + endpoint.relation_name)) |
184 | + |
185 | + def add_relation(topology): |
186 | + if topology.has_relation_between_endpoints(endpoints): |
187 | + raise RelationAlreadyExists(endpoints) |
188 | + topology.add_relation(relation_id, relation_type) |
189 | + for service_relation in services: |
190 | + if not topology.has_service(service_id): |
191 | + raise StateChanged() |
192 | + topology.assign_service_to_relation( |
193 | + relation_id, |
194 | + service_relation.internal_service_id, |
195 | + service_relation.relation_name, |
196 | + service_relation.relation_role) |
197 | + yield self._retry_topology_change(add_relation) |
198 | + |
199 | + returnValue((RelationState(self._client, relation_id), services)) |
200 | + |
201 | + @inlineCallbacks |
202 | + def _add_relation_state(self, relation_type): |
203 | path = yield self._client.create( |
204 | "/relations/relation-", flags=zookeeper.SEQUENCE) |
205 | - |
206 | internal_id = basename(path) |
207 | - |
208 | # create the settings container, for individual units settings. |
209 | yield self._client.create(path + "/settings") |
210 | - |
211 | - # Not clear if we really need to update the topology till the relation |
212 | - # is in use. Topology updates are broadcasting to most connected |
213 | - # clients. Perhaps a separate batch api would be useful to minimize |
214 | - # broadcast events on the topology. |
215 | - def add_relation(topology): |
216 | - topology.add_relation(internal_id, relation_type) |
217 | - yield self._retry_topology_change(add_relation) |
218 | - |
219 | - returnValue(RelationState(self._client, internal_id)) |
220 | + returnValue(internal_id) |
221 | + |
222 | + @inlineCallbacks |
223 | + def _add_service_relation_state( |
224 | + self, relation_id, service_id, endpoint): |
225 | + """Add a service relation state. |
226 | + """ |
227 | + # Add service container in relation. |
228 | + node_data = yaml.safe_dump( |
229 | + {"name": endpoint.relation_name, "role": endpoint.relation_role}) |
230 | + path = "/relations/%s/%s" % (relation_id, endpoint.relation_role) |
231 | + |
232 | + try: |
233 | + yield self._client.create(path, node_data) |
234 | + except zookeeper.NodeExistsException: |
235 | + # If its not, then update the node, and continue. |
236 | + yield retry_change( |
237 | + self._client, path, lambda content, stat: node_data) |
238 | |
239 | @inlineCallbacks |
240 | def remove_relation_state(self, relation_state): |
241 | @@ -86,89 +156,6 @@ |
242 | def internal_id(self): |
243 | return self._internal_id |
244 | |
245 | - @inlineCallbacks |
246 | - def unassign_service(self, service_state): |
247 | - """Remove a service from the relation. |
248 | - |
249 | - Any removed service needs to have its zookeeper state garbage collected |
250 | - external to this api. The individual units should remove their |
251 | - contained nodes, upon notification but even then the service container |
252 | - node within the relation remains to be garbage collected. |
253 | - """ |
254 | - |
255 | - def remove_service(topology): |
256 | - # Verify our relation and service still exist. |
257 | - if not topology.has_relation(self._internal_id) or \ |
258 | - not topology.has_service(service_state.internal_id): |
259 | - raise StateChanged() |
260 | - |
261 | - # Ignore concurrent requests that achieve the same state. |
262 | - if not topology.relation_has_service( |
263 | - self._internal_id, service_state.internal_id): |
264 | - return |
265 | - |
266 | - topology.unassign_service_from_relation( |
267 | - self._internal_id, service_state.internal_id) |
268 | - |
269 | - yield self._retry_topology_change(remove_service) |
270 | - |
271 | - @inlineCallbacks |
272 | - def assign_service(self, service_state, relation_name, relation_role): |
273 | - """Add a service to the relation. |
274 | - |
275 | - @param service_state - The service to be added to the relation. |
276 | - @param name - The service's name for this relation. |
277 | - @param role - The service's role within this relation. |
278 | - """ |
279 | - # Add service container in relation. |
280 | - node_data = yaml.safe_dump( |
281 | - {"name": relation_name, "role": relation_role}) |
282 | - path = "/relations/%s/%s" % (self.internal_id, relation_role) |
283 | - |
284 | - try: |
285 | - yield self._client.create(path, node_data) |
286 | - except zookeeper.NodeExistsException: |
287 | - # because we don't garbage collect, we need to ascertain |
288 | - # if the service node is actually in use. |
289 | - topology = yield self._read_topology() |
290 | - |
291 | - # If it is assigned, its an error. |
292 | - # There's a small chance for a race condition between the check |
293 | - # and set. |
294 | - if topology.relation_has_service( |
295 | - self._internal_id, service_state.internal_id): |
296 | - raise ServiceRelationAlreadyAssigned( |
297 | - service_state.service_name, relation_name, relation_role) |
298 | - |
299 | - # If its not, then update the node, and continue. |
300 | - yield retry_change( |
301 | - self._client, path, lambda content, stat: node_data) |
302 | - |
303 | - # Associate service to relation in the topology. |
304 | - def add_service(topology): |
305 | - # Verify our service and relation exists |
306 | - if not topology.has_relation(self._internal_id) or \ |
307 | - not topology.has_service(service_state.internal_id): |
308 | - raise StateChanged() |
309 | - |
310 | - # Add it the topology if its not there. |
311 | - if not topology.relation_has_service( |
312 | - self._internal_id, service_state.internal_id): |
313 | - topology.assign_service_to_relation( |
314 | - self.internal_id, |
315 | - service_state.internal_id, |
316 | - relation_name, |
317 | - relation_role) |
318 | - |
319 | - |
320 | - yield self._retry_topology_change(add_service) |
321 | - |
322 | - returnValue(ServiceRelationState(self._client, |
323 | - service_state.internal_id, |
324 | - self.internal_id, |
325 | - relation_role, |
326 | - relation_name)) |
327 | - |
328 | |
329 | class ServiceRelationState(object): |
330 | """A state representative of a relation between one or more services.""" |
331 | @@ -695,4 +682,3 @@ |
332 | """ |
333 | return [unit_id for unit_id in units \ |
334 | if unit_id != self._watcher_unit.internal_unit_id] |
335 | - |
336 | |
337 | === modified file 'ensemble/state/service.py' |
338 | --- ensemble/state/service.py 2010-12-10 04:59:54 +0000 |
339 | +++ ensemble/state/service.py 2010-12-10 23:06:27 +0000 |
340 | @@ -88,8 +88,11 @@ |
341 | formula_id) |
342 | formula_metadata = yield formula_state.get_metadata() |
343 | endpoints = set() |
344 | - for relation_role in ("peers", "provides", "requires"): |
345 | - relations = getattr(formula_metadata, relation_role) |
346 | + relation_role_map = { |
347 | + "peer": "peers", "client": "requires", "server": "provides"} |
348 | + for relation_role in ("peer", "client", "server"): |
349 | + relations = getattr( |
350 | + formula_metadata, relation_role_map[relation_role]) |
351 | if relations: |
352 | for relation_name, spec in relations.iteritems(): |
353 | if (query_relation_name is None or |
354 | |
355 | === modified file 'ensemble/state/tests/test_endpoint.py' |
356 | --- ensemble/state/tests/test_endpoint.py 2010-11-25 05:28:27 +0000 |
357 | +++ ensemble/state/tests/test_endpoint.py 2010-12-10 23:06:27 +0000 |
358 | @@ -4,9 +4,9 @@ |
359 | |
360 | class RelationEndpointTest(TestCase): |
361 | def test_may_relate_to(self): |
362 | - mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "provides") |
363 | - blog_ep = RelationEndpoint("blog", "mysql", "mysql", "requires") |
364 | - pg_ep = RelationEndpoint("postgres", "postgres", "db", "provides") |
365 | + mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") |
366 | + blog_ep = RelationEndpoint("blog", "mysql", "mysql", "client") |
367 | + pg_ep = RelationEndpoint("postgres", "postgres", "db", "server") |
368 | |
369 | self.assertRaises(TypeError, mysql_ep.may_relate_to, 42) |
370 | |
371 | @@ -21,15 +21,15 @@ |
372 | # antireflexive om relation_role - |
373 | # must be consumer AND provider or vice versa |
374 | self.assertFalse(blog_ep.may_relate_to( |
375 | - RelationEndpoint("foo", "mysql", "db", "requires"))) |
376 | + RelationEndpoint("foo", "mysql", "db", "client"))) |
377 | self.assertFalse(mysql_ep.may_relate_to( |
378 | - RelationEndpoint("foo", "mysql", "db", "provides"))) |
379 | + RelationEndpoint("foo", "mysql", "db", "server"))) |
380 | |
381 | - # irreflexive for provides/requires |
382 | + # irreflexive for server/client |
383 | self.assertFalse(mysql_ep.may_relate_to(mysql_ep)) |
384 | self.assertFalse(blog_ep.may_relate_to(blog_ep)) |
385 | self.assertFalse(pg_ep.may_relate_to(pg_ep)) |
386 | |
387 | - # but reflexive for peers |
388 | - riak_ep = RelationEndpoint("riak", "riak", "riak", "peers") |
389 | + # but reflexive for peer |
390 | + riak_ep = RelationEndpoint("riak", "riak", "riak", "peer") |
391 | self.assert_(riak_ep.may_relate_to(riak_ep)) |
392 | |
393 | === modified file 'ensemble/state/tests/test_errors.py' |
394 | --- ensemble/state/tests/test_errors.py 2010-11-04 03:41:11 +0000 |
395 | +++ ensemble/state/tests/test_errors.py 2010-12-10 23:06:27 +0000 |
396 | @@ -1,12 +1,14 @@ |
397 | from ensemble.lib.testing import TestCase |
398 | |
399 | +from ensemble.state.endpoint import RelationEndpoint |
400 | from ensemble.state.errors import ( |
401 | EnsembleError, StateError, StateChanged, FormulaStateNotFound, |
402 | ServiceStateNotFound, ServiceUnitStateNotFound, MachineStateNotFound, |
403 | ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse, |
404 | BadServiceStateName, EnvironmentStateNotFound, |
405 | - ServiceRelationAlreadyAssigned, UnitRelationStateNotFound, |
406 | - UnitRelationStateAlreadyAssigned, UnknownRelationRole) |
407 | + RelationAlreadyExists, UnitRelationStateNotFound, |
408 | + UnitRelationStateAlreadyAssigned, UnknownRelationRole, |
409 | + BadDescriptor, DuplicateEndpoints, IncompatibleEndpoints) |
410 | |
411 | |
412 | class StateErrorsTest(TestCase): |
413 | @@ -76,13 +78,13 @@ |
414 | self.assertEquals(str(error), |
415 | "Environment state was not found") |
416 | |
417 | - def test_service_relation_already_assigned(self): |
418 | - error = ServiceRelationAlreadyAssigned( |
419 | - "wordpress", "mysql", "client") |
420 | + def test_relation_already_exists(self): |
421 | + error = RelationAlreadyExists( |
422 | + RelationEndpoint("wordpress", "mysql", "mysql", "client"), |
423 | + RelationEndpoint("mysql", "mysql", "db", "server")) |
424 | self.assertIsStateError(error) |
425 | - msg = ("Service 'wordpress' is already in relation with different " |
426 | - "name 'mysql' or role 'client'.") |
427 | - self.assertEquals(str(error), msg) |
428 | + self.assertTrue("wordpress" in str(error)) |
429 | + self.assertTrue("mysql" in str(error)) |
430 | |
431 | def test_unit_relation_state_not_found(self): |
432 | error = UnitRelationStateNotFound( |
433 | @@ -103,3 +105,24 @@ |
434 | self.assertIsStateError(error) |
435 | msg = "Unknown relation role 'server2' for service 'service-name'" |
436 | self.assertEquals(str(error), msg) |
437 | + |
438 | + def test_bad_descriptor(self): |
439 | + error = BadDescriptor("a:b:c") |
440 | + self.assertTrue(isinstance(error, EnsembleError)) |
441 | + msg = "Bad descriptor: 'a:b:c'" |
442 | + self.assertEquals(str(error), msg) |
443 | + |
444 | + def test_duplicate_endpoints(self): |
445 | + riak_ep = RelationEndpoint("riak", "riak", "ring", "peer") |
446 | + error = DuplicateEndpoints(riak_ep, riak_ep) |
447 | + self.assertIsStateError(error) |
448 | + self.assertTrue("riak" in str(error)) |
449 | + |
450 | + def test_incompatible_endpoints(self): |
451 | + error = IncompatibleEndpoints( |
452 | + RelationEndpoint("mysql", "mysql", "db", "server"), |
453 | + RelationEndpoint("riak", "riak", "ring", "peer")) |
454 | + self.assertIsStateError(error) |
455 | + self.assertTrue("mysql" in str(error)) |
456 | + self.assertTrue("riak" in str(error)) |
457 | + |
458 | |
459 | === modified file 'ensemble/state/tests/test_hook.py' |
460 | --- ensemble/state/tests/test_hook.py 2010-12-03 00:13:01 +0000 |
461 | +++ ensemble/state/tests/test_hook.py 2010-12-10 23:06:27 +0000 |
462 | @@ -3,6 +3,7 @@ |
463 | from twisted.internet.defer import inlineCallbacks |
464 | from ensemble.lib.testing import TestCase |
465 | |
466 | +from ensemble.state.endpoint import RelationEndpoint |
467 | from ensemble.state.hook import RelationChange, HookContext |
468 | from ensemble.state.errors import UnitRelationStateNotFound, StateError |
469 | from ensemble.state.tests.test_relation import RelationTestBase |
470 | @@ -23,11 +24,14 @@ |
471 | @inlineCallbacks |
472 | def setUp(self): |
473 | yield super(ExecutionContextTest, self).setUp() |
474 | - self.mysql_states = yield self.add_relation_service_unit( |
475 | - "mysql", "mysql", "", "server") |
476 | - self.wordpress_states = yield self.add_relation_service_unit( |
477 | - "mysql", "wordpress", "", "client", |
478 | - relation_state=self.mysql_states["relation"]) |
479 | + wordpress_ep = RelationEndpoint( |
480 | + "wordpress", "client-server", "", "client") |
481 | + mysql_ep = RelationEndpoint( |
482 | + "mysql", "client-server", "", "server") |
483 | + self.wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
484 | + wordpress_ep, mysql_ep) |
485 | + self.mysql_states = yield self.add_opposite_service_unit( |
486 | + self.wordpress_states) |
487 | self.relation = self.mysql_states["relation"] |
488 | |
489 | def get_execution_context(self, states, change_type, unit_name): |
490 | |
491 | === modified file 'ensemble/state/tests/test_relation.py' |
492 | --- ensemble/state/tests/test_relation.py 2010-12-07 00:22:20 +0000 |
493 | +++ ensemble/state/tests/test_relation.py 2010-12-10 23:06:27 +0000 |
494 | @@ -8,11 +8,12 @@ |
495 | inlineCallbacks, returnValue, Deferred, fail, succeed) |
496 | from txzookeeper import ZookeeperClient |
497 | |
498 | +from ensemble.state.endpoint import RelationEndpoint |
499 | from ensemble.state.formula import FormulaStateManager |
500 | from ensemble.state.errors import ( |
501 | - StateChanged, ServiceRelationAlreadyAssigned, |
502 | - UnitRelationStateNotFound, UnitRelationStateAlreadyAssigned, |
503 | - UnknownRelationRole) |
504 | + DuplicateEndpoints, IncompatibleEndpoints, RelationAlreadyExists, |
505 | + StateChanged, UnitRelationStateAlreadyAssigned, |
506 | + UnitRelationStateNotFound, UnknownRelationRole) |
507 | from ensemble.state.relation import ( |
508 | RelationStateManager, ServiceRelationState, UnitRelationState) |
509 | from ensemble.state.service import ServiceStateManager |
510 | @@ -40,13 +41,95 @@ |
511 | |
512 | @inlineCallbacks |
513 | def add_relation(self, relation_type, *services): |
514 | - relation_state = yield self.relation_manager.add_relation_state( |
515 | - relation_type) |
516 | + """Support older tests that don't use `RelationEndpoint`s""" |
517 | + endpoints = [] |
518 | for service_meta in services: |
519 | service_state, relation_name, relation_role = service_meta |
520 | - yield relation_state.assign_service( |
521 | - service_state, relation_name, relation_role) |
522 | - returnValue(relation_state) |
523 | + endpoints.append(RelationEndpoint( |
524 | + service_state.service_name, |
525 | + relation_type, |
526 | + relation_name, |
527 | + relation_role)) |
528 | + relation_state = yield self.relation_manager.add_relation_state( |
529 | + *endpoints) |
530 | + returnValue(relation_state[0]) |
531 | + |
532 | + @inlineCallbacks |
533 | + def add_relation_service_unit_from_endpoints(self, *endpoints): |
534 | + """Build the relation and add one service unit to the first endpoint. |
535 | + |
536 | + This method is used to migrate older tests that would create |
537 | + the relation, assign one service, add a service unit, AND then |
538 | + assign a service. However, service assignment is now done all |
539 | + at once with the relation creation. Because we are interested |
540 | + in testing what happens with the changes to the service units, |
541 | + such tests remain valid. |
542 | + |
543 | + Returns a dict to collect together the various state objects |
544 | + being created. This is created from the perspective of the |
545 | + first endpoint, but the states of all of the endpoints are |
546 | + also captured, so it can be worked with from the opposite |
547 | + endpoint, as seen in :func:`add_opposite_service_unit`. |
548 | + """ |
549 | + # 1. Setup all service states |
550 | + service_states = [] |
551 | + for endpoint in endpoints: |
552 | + service_state = yield self.add_service(endpoint.service_name) |
553 | + service_states.append(service_state) |
554 | + |
555 | + # 2. And join together in a relation |
556 | + relation_state, service_relation_states = \ |
557 | + yield self.relation_manager.add_relation_state( |
558 | + *endpoints) |
559 | + |
560 | + # 3. Add a service unit to only the first endpoint - we need |
561 | + # to test what happens when service units are added to the |
562 | + # other service state (if any), so do so separately |
563 | + unit_state = yield service_states[0].add_unit_state() |
564 | + relation_unit_state = yield service_relation_states[0].add_unit_state( |
565 | + unit_state) |
566 | + |
567 | + returnValue({ |
568 | + "service": service_states[0], |
569 | + "services": service_states, |
570 | + "unit": unit_state, |
571 | + "relation": relation_state, |
572 | + "service_relation": service_relation_states[0], |
573 | + "unit_relation": relation_unit_state, |
574 | + "service_relations": service_relation_states}) |
575 | + |
576 | + @inlineCallbacks |
577 | + def add_opposite_service_unit(self, other_states): |
578 | + """Given `other_states`, add a service unit to the opposite endpoint. |
579 | + |
580 | + Like :func:`add_relation_service_unit_from_endpoints`, this is |
581 | + used to support older tests. Although it's slightly awkward to |
582 | + use because of attempt to be backwards compatible, it does |
583 | + enable the testing of a typical case: we are now bringing |
584 | + online a service unit on the opposite side of a relation |
585 | + endpoint pairing. |
586 | + |
587 | + TODO: there's probably a better name for this method. |
588 | + """ |
589 | + |
590 | + assert len(other_states["services"]) == 2 |
591 | + unit_state = yield other_states["services"][1].add_unit_state() |
592 | + relation_unit_state = yield other_states["service_relations"][1].\ |
593 | + add_unit_state(unit_state) |
594 | + |
595 | + def rotate(X): |
596 | + rotated = X[1:] |
597 | + rotated.append(X[0]) |
598 | + return rotated |
599 | + |
600 | + returnValue({ |
601 | + "service": other_states["services"][1], |
602 | + "services": rotate(other_states["services"]), |
603 | + "unit": unit_state, |
604 | + "relation": other_states["relation"], |
605 | + "service_relation": other_states["service_relations"][1], |
606 | + "unit_relation": relation_unit_state, |
607 | + "service_relations": rotate(other_states["service_relations"])}) |
608 | |
609 | @inlineCallbacks |
610 | def add_relation_service_unit(self, |
611 | @@ -54,7 +137,6 @@ |
612 | service_name, |
613 | relation_name="name", |
614 | relation_role="role", |
615 | - relation_state=None, |
616 | client=None): |
617 | """ |
618 | Create a relation, service, and service unit, with the |
619 | @@ -63,21 +145,11 @@ |
620 | the service relation and the service relation state will |
621 | utilize that as their zookeeper client. |
622 | """ |
623 | - # Add the service |
624 | + # Add the service, relation, unit states |
625 | service_state = yield self.add_service(service_name) |
626 | - |
627 | - if relation_state is None: |
628 | - # If no relation is passed, create the relation |
629 | - # and associate the service to it. |
630 | - relation_state = yield self.add_relation( |
631 | - relation_type, |
632 | - (service_state, relation_name, relation_role)) |
633 | - else: |
634 | - # If the relation exists, assign the service to it. |
635 | - yield relation_state.assign_service( |
636 | - service_state, relation_name, relation_role) |
637 | - |
638 | - # add a unit of the service |
639 | + relation_state = yield self.add_relation( |
640 | + relation_type, |
641 | + (service_state, relation_name, relation_role)) |
642 | unit_state = yield service_state.add_unit_state() |
643 | |
644 | # Get the service relation. |
645 | @@ -132,11 +204,11 @@ |
646 | |
647 | @inlineCallbacks |
648 | def test_add_relation_state(self): |
649 | - """ |
650 | - Adding a relation will create a relation node and update the topology. |
651 | - """ |
652 | - relation_state = yield self.relation_manager.add_relation_state( |
653 | - "mysql") |
654 | + """Adding a relation will create a relation node and update topology.""" |
655 | + mysql_ep = RelationEndpoint("mysql", "mysql", "db", "server") |
656 | + yield self.add_service("mysql") |
657 | + relation_state = (yield self.relation_manager.add_relation_state( |
658 | + mysql_ep))[0] |
659 | topology = yield self.get_topology() |
660 | self.assertTrue(topology.has_relation(relation_state.internal_id)) |
661 | exists = yield self.client.exists( |
662 | @@ -147,12 +219,118 @@ |
663 | self.assertTrue(exists) |
664 | |
665 | @inlineCallbacks |
666 | + def test_add_relation_state_to_missing_service(self): |
667 | + """Test adding a relation to a nonexistent service""" |
668 | + mysql_ep = RelationEndpoint("mysql", "mysql", "db", "server") |
669 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
670 | + yield self.add_service("mysql") |
671 | + # but didn't create the service for wordpress |
672 | + yield self.assertFailure( |
673 | + self.relation_manager.add_relation_state( |
674 | + mysql_ep, blog_ep), |
675 | + StateChanged) |
676 | + |
677 | + @inlineCallbacks |
678 | + def test_add_relation_state_bad_relation_role(self): |
679 | + """Test adding a relation with a bad role when is one is well defined (client or server)""" |
680 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
681 | + mysql_ep = RelationEndpoint("mysql", "mysql", "db", "server") |
682 | + bad_mysql_ep = RelationEndpoint("mysql", "mysql", "db", "bad-server-role") |
683 | + bad_blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "bad-client-role") |
684 | + yield self.add_service("mysql") |
685 | + yield self.add_service("wordpress") |
686 | + yield self.assertFailure( |
687 | + self.relation_manager.add_relation_state( |
688 | + bad_mysql_ep, blog_ep), |
689 | + IncompatibleEndpoints) |
690 | + yield self.assertFailure( |
691 | + self.relation_manager.add_relation_state( |
692 | + bad_blog_ep, mysql_ep), |
693 | + IncompatibleEndpoints) |
694 | + # TODO in future branch referenced in relation, also test |
695 | + # bad_blog_ep *and* bad_mysql_ep |
696 | + |
697 | + @inlineCallbacks |
698 | + def test_add_binary_relation_state_twice(self): |
699 | + """Test adding the same relation twice""" |
700 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
701 | + mysql_ep = RelationEndpoint("mysql", "mysql", "db", "server") |
702 | + yield self.add_service("mysql") |
703 | + yield self.add_service("wordpress") |
704 | + yield self.relation_manager.add_relation_state(mysql_ep, blog_ep) |
705 | + yield self.assertFailure( |
706 | + self.relation_manager.add_relation_state(blog_ep, mysql_ep), |
707 | + RelationAlreadyExists) |
708 | + yield self.assertFailure( |
709 | + self.relation_manager.add_relation_state(mysql_ep, blog_ep), |
710 | + RelationAlreadyExists) |
711 | + |
712 | + @inlineCallbacks |
713 | + def test_add_peer_relation_state_twice(self): |
714 | + """Test adding the same relation twice""" |
715 | + riak_ep = RelationEndpoint("riak", "riak", "ring", "peer") |
716 | + yield self.add_service("riak") |
717 | + yield self.relation_manager.add_relation_state(riak_ep) |
718 | + yield self.assertFailure( |
719 | + self.relation_manager.add_relation_state(riak_ep), |
720 | + RelationAlreadyExists) |
721 | + |
722 | + @inlineCallbacks |
723 | + def test_add_relation_state_no_endpoints(self): |
724 | + """Test adding a relation with no endpoints (no longer allowed)""" |
725 | + yield self.assertFailure( |
726 | + self.relation_manager.add_relation_state(), |
727 | + TypeError) |
728 | + |
729 | + @inlineCallbacks |
730 | + def test_add_relation_state_relation_type_unshared(self): |
731 | + """Test adding a relation with endpoints not sharing a relation type""" |
732 | + pg_ep = RelationEndpoint("pg", "postgres", "db", "server") |
733 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
734 | + yield self.assertFailure( |
735 | + self.relation_manager.add_relation_state(pg_ep, blog_ep), |
736 | + IncompatibleEndpoints) |
737 | + |
738 | + @inlineCallbacks |
739 | + def test_add_relation_state_too_many_endpoints(self): |
740 | + """Test adding a relation between too many endpoints (> 2)""" |
741 | + mysql_ep = RelationEndpoint("mysql", "mysql", "db", "server") |
742 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
743 | + yield self.add_service("mysql") |
744 | + yield self.add_service("wordpress") |
745 | + yield self.assertFailure( |
746 | + self.relation_manager.add_relation_state( |
747 | + mysql_ep, blog_ep, mysql_ep), |
748 | + TypeError) |
749 | + |
750 | + @inlineCallbacks |
751 | + def test_add_relation_state_duplicate_peer_endpoints(self): |
752 | + """Test adding a relation between duplicate peer endpoints""" |
753 | + riak_ep = RelationEndpoint("riak", "riak", "ring", "peer") |
754 | + yield self.add_service("riak") |
755 | + yield self.assertFailure( |
756 | + self.relation_manager.add_relation_state(riak_ep, riak_ep), |
757 | + DuplicateEndpoints) |
758 | + |
759 | + @inlineCallbacks |
760 | + def test_add_relation_state_endpoints_duplicate_role(self): |
761 | + """Test adding a relation with services overlapped by duplicate role""" |
762 | + mysql_ep = RelationEndpoint("mysql", "mysql", "db", "server") |
763 | + drizzle_ep = RelationEndpoint("drizzle", "mysql", "db", "server") |
764 | + yield self.add_service("mysql") |
765 | + yield self.add_service("drizzle") |
766 | + yield self.assertFailure( |
767 | + self.relation_manager.add_relation_state(mysql_ep, drizzle_ep), |
768 | + IncompatibleEndpoints) |
769 | + |
770 | + @inlineCallbacks |
771 | def test_remove_relation_state(self): |
772 | - """Removing a relation will remove it from the topology. |
773 | - """ |
774 | + """Removing a relation will remove it from the topology.""" |
775 | # Simulate add and remove. |
776 | - relation_state = yield self.relation_manager.add_relation_state( |
777 | - "varnish") |
778 | + varnish_ep = RelationEndpoint("varnish", "webcache", "cache", "server") |
779 | + yield self.add_service("varnish") |
780 | + relation_state = (yield self.relation_manager.add_relation_state( |
781 | + varnish_ep))[0] |
782 | topology = yield self.get_topology() |
783 | self.assertTrue(topology.has_relation(relation_state.internal_id)) |
784 | yield self.relation_manager.remove_relation_state(relation_state) |
785 | @@ -164,8 +342,10 @@ |
786 | @inlineCallbacks |
787 | def test_remove_relation_with_changing_state(self): |
788 | # Simulate add and remove. |
789 | - relation_state = yield self.relation_manager.add_relation_state( |
790 | - "varnish") |
791 | + varnish_ep = RelationEndpoint("varnish", "webcache", "cache", "server") |
792 | + yield self.add_service("varnish") |
793 | + relation_state = (yield self.relation_manager.add_relation_state( |
794 | + varnish_ep))[0] |
795 | topology = yield self.get_topology() |
796 | self.assertTrue(topology.has_relation(relation_state.internal_id)) |
797 | yield self.relation_manager.remove_relation_state(relation_state) |
798 | @@ -218,8 +398,8 @@ |
799 | def setUp(self): |
800 | yield super(RelationStateTest, self).setUp() |
801 | |
802 | - self.relation_state = yield self.relation_manager.add_relation_state( |
803 | - "rel-type") |
804 | + self.relation_state = (yield self.relation_manager.add_relation_state( |
805 | + "rel-type"))[0] |
806 | self.formula_state = yield self.formula_manager.add_formula_state( |
807 | "namespace", self.formula) |
808 | self.service_state1 = yield self.service_manager.add_service_state( |
809 | @@ -227,106 +407,6 @@ |
810 | self.service_state2 = yield self.service_manager.add_service_state( |
811 | "wordpress-dev", self.formula_state) |
812 | |
813 | - @inlineCallbacks |
814 | - def test_assign_service(self): |
815 | - yield self.relation_state.assign_service( |
816 | - self.service_state1, |
817 | - "dev-instance", |
818 | - "prod") |
819 | - |
820 | - yield self.relation_state.assign_service( |
821 | - self.service_state2, |
822 | - "prod-instance", |
823 | - "dev") |
824 | - |
825 | - topology = yield self.get_topology() |
826 | - |
827 | - self.assertEqual( |
828 | - topology.get_relations_for_service( |
829 | - self.service_state1.internal_id), |
830 | - [(self.relation_state.internal_id, |
831 | - "rel-type", |
832 | - {"name": "dev-instance", "role": "prod"})]) |
833 | - |
834 | - self.assertEqual( |
835 | - topology.get_relation_services(self.relation_state.internal_id), |
836 | - {'service-0000000000': {'name': 'dev-instance', 'role': 'prod'}, |
837 | - 'service-0000000001': {'name': 'prod-instance', 'role': 'dev'}}) |
838 | - |
839 | - @inlineCallbacks |
840 | - def test_assign_service_already_assigned(self): |
841 | - """ |
842 | - Attempting to add a service to a relation multiple times |
843 | - will raise an error if its already there. |
844 | - """ |
845 | - # Assign and verify |
846 | - yield self.relation_state.assign_service(self.service_state1, |
847 | - "dev-instance", |
848 | - "prod") |
849 | - topology = yield self.get_topology() |
850 | - self.assertTrue(topology.relation_has_service( |
851 | - self.relation_state.internal_id, self.service_state1.internal_id)) |
852 | - |
853 | - # If we attempt to add again, we'll get an error. |
854 | - yield self.assertFailure( |
855 | - self.relation_state.assign_service(self.service_state1, |
856 | - "dev-instance", |
857 | - "prod"), |
858 | - ServiceRelationAlreadyAssigned) |
859 | - |
860 | - # If we unassign and assign again, the service container state updates. |
861 | - yield self.relation_state.unassign_service(self.service_state1) |
862 | - yield self.relation_state.assign_service(self.service_state1, |
863 | - "dev-instance2", |
864 | - "prod") |
865 | - content, stat = yield self.client.get( |
866 | - "/relations/%s/%s" % ( |
867 | - self.relation_state.internal_id, "prod")) |
868 | - |
869 | - node_data = yaml.load(content) |
870 | - self.assertEqual(node_data["name"], "dev-instance2") |
871 | - |
872 | - # Attempting to assign when the relation or service has gone away |
873 | - # raises a state changed error. |
874 | - yield self.relation_manager.remove_relation_state(self.relation_state) |
875 | - topology = yield self.get_topology() |
876 | - self.assertFalse( |
877 | - topology.has_relation(self.relation_state.internal_id)) |
878 | - |
879 | - yield self.assertFailure( |
880 | - self.relation_state.assign_service(self.service_state1, |
881 | - "dev-instance2", |
882 | - "prod2"), |
883 | - StateChanged) |
884 | - |
885 | - @inlineCallbacks |
886 | - def test_unassign_service(self): |
887 | - """A service can be disassociated from a relation. |
888 | - """ |
889 | - yield self.relation_state.assign_service(self.service_state1, |
890 | - "dev-instance", |
891 | - "prod") |
892 | - topology = yield self.get_topology() |
893 | - self.assertTrue(topology.relation_has_service( |
894 | - self.relation_state.internal_id, |
895 | - self.service_state1.internal_id)) |
896 | - |
897 | - # Remove and verify |
898 | - yield self.relation_state.unassign_service(self.service_state1) |
899 | - topology = yield self.get_topology() |
900 | - self.assertFalse(topology.relation_has_service( |
901 | - self.relation_state.internal_id, |
902 | - self.service_state1.internal_id)) |
903 | - |
904 | - # if we remove again, we'll succed as the final state is the same. |
905 | - yield self.relation_state.unassign_service(self.service_state1) |
906 | - |
907 | - # Removing again, without the relation state, errors on state change. |
908 | - yield self.relation_manager.remove_relation_state(self.relation_state) |
909 | - yield self.assertFailure( |
910 | - self.relation_state.unassign_service(self.service_state1), |
911 | - StateChanged) |
912 | - |
913 | |
914 | class ServiceRelationStateTest(RelationTestBase): |
915 | |
916 | @@ -596,53 +676,16 @@ |
917 | self.assertEqual(path, container_path) |
918 | |
919 | @inlineCallbacks |
920 | - def test_watch_start_existing_service(self): |
921 | - """Invoking watcher.start returns a deferred that only fires |
922 | - after watch on the container is in place. In the case of an |
923 | - existing service, this is after a child watch is established. |
924 | - """ |
925 | - wordpress_states = yield self.add_relation_service_unit( |
926 | - "client-server", "wordpress", "", "client") |
927 | - |
928 | - mysql_states = yield self.add_relation_service_unit( |
929 | - "client-server", "mysql", "", "server", |
930 | - relation_state = wordpress_states["relation"]) |
931 | - |
932 | - results = [] |
933 | - wait_callback = [Deferred() for i in range(5)] |
934 | - |
935 | - def watch_related(old_units=None, new_units=None, modified=None): |
936 | - results.append((old_units, new_units, modified)) |
937 | - wait_callback[len(results)-1].callback(True) |
938 | - |
939 | - def invoked(*args, **kw): |
940 | - # sleep to make sure that things haven't fired till the watch is |
941 | - # in place. |
942 | - time.sleep(0.1) |
943 | - self.assertFalse(results) |
944 | - |
945 | - mock_client = self.mocker.patch(self.client) |
946 | - mock_client.get_children_and_watch("/relations/%s/server" % ( |
947 | - wordpress_states["relation"].internal_id)) |
948 | - |
949 | - self.mocker.call(invoked) |
950 | - self.mocker.passthrough() |
951 | - self.mocker.replay() |
952 | - |
953 | - watcher = yield wordpress_states["unit_relation"].watch_related_units( |
954 | - watch_related) |
955 | - yield watcher.start() |
956 | - yield wait_callback[0] |
957 | - |
958 | - @inlineCallbacks |
959 | def test_watch_start_new_service(self): |
960 | """Invoking watcher.start returns a deferred that only fires |
961 | after watch on the containr is in place. In the case of a new |
962 | service this after an existance watch is established on the |
963 | container. |
964 | """ |
965 | - wordpress_states = yield self.add_relation_service_unit( |
966 | - "client-server", "wordpress", "", "client") |
967 | + wordpress_ep = RelationEndpoint( |
968 | + "wordpress", "client-server", "", "client") |
969 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
970 | + wordpress_ep) |
971 | |
972 | results = [] |
973 | |
974 | @@ -673,8 +716,12 @@ |
975 | first within relation, and start to monitor the server service |
976 | as it joins the relation, adds a unit, modifies, the unit. |
977 | """ |
978 | - wordpress_states = yield self.add_relation_service_unit( |
979 | - "client-server", "wordpress", "", "client") |
980 | + wordpress_ep = RelationEndpoint( |
981 | + "wordpress", "client-server", "", "client") |
982 | + mysql_ep = RelationEndpoint( |
983 | + "mysql", "client-server", "", "server") |
984 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
985 | + wordpress_ep, mysql_ep) |
986 | |
987 | # setup watch callbacks, and start watching. |
988 | wait_callback = [Deferred() for i in range(5)] |
989 | @@ -700,9 +747,8 @@ |
990 | self.assertFalse(results) |
991 | |
992 | # add the server service and a unit of that |
993 | - mysql_states = yield self.add_relation_service_unit( |
994 | - "client-server", "mysql", "", "server", |
995 | - relation_state=wordpress_states["relation"]) |
996 | + mysql_states = yield self.add_opposite_service_unit( |
997 | + wordpress_states) |
998 | |
999 | topology = yield self.get_topology() |
1000 | # assert the relation is established correctly |
1001 | @@ -734,17 +780,18 @@ |
1002 | @inlineCallbacks |
1003 | def test_watch_client_server_with_existing_service(self): |
1004 | """We simulate a scenario where the client and server are both |
1005 | - in place before the client begins observing. The server subequently, |
1006 | + in place before the client begins observing. The server subsequently |
1007 | modifies, and remove its unit from the relation. |
1008 | """ |
1009 | # add the client service and a unit of that |
1010 | - wordpress_states = yield self.add_relation_service_unit( |
1011 | - "client-server", "wordpress", "", "client") |
1012 | - |
1013 | - # add the server service and a unit of that |
1014 | - mysql_states = yield self.add_relation_service_unit( |
1015 | - "client-server", "mysql", "", "server", |
1016 | - relation_state=wordpress_states["relation"]) |
1017 | + wordpress_ep = RelationEndpoint( |
1018 | + "wordpress", "client-server", "", "client") |
1019 | + mysql_ep = RelationEndpoint( |
1020 | + "mysql", "client-server", "", "server") |
1021 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
1022 | + wordpress_ep, mysql_ep) |
1023 | + mysql_states = yield self.add_opposite_service_unit( |
1024 | + wordpress_states) |
1025 | |
1026 | # setup callbacks and start observing. |
1027 | wait_callback = [Deferred() for i in range(5)] |
1028 | @@ -784,9 +831,13 @@ |
1029 | def test_watch_server_client_with_new_service(self): |
1030 | """We simulate a server watching a client. |
1031 | """ |
1032 | + wordpress_ep = RelationEndpoint( |
1033 | + "wordpress", "client-server", "", "client") |
1034 | + mysql_ep = RelationEndpoint( |
1035 | + "mysql", "client-server", "", "server") |
1036 | # add the server service and a unit of that |
1037 | - mysql_states = yield self.add_relation_service_unit( |
1038 | - "client-server", "mysql", "", "server") |
1039 | + mysql_states = yield self.add_relation_service_unit_from_endpoints( |
1040 | + mysql_ep, wordpress_ep) |
1041 | |
1042 | # setup callbacks and start observing. |
1043 | wait_callback = [Deferred() for i in range(5)] |
1044 | @@ -804,9 +855,8 @@ |
1045 | self.assertFalse(results) |
1046 | |
1047 | # add the client service and a unit of that |
1048 | - wordpress_states = yield self.add_relation_service_unit( |
1049 | - "client-server", "wordpress", "", "client", |
1050 | - relation_state=mysql_states["relation"]) |
1051 | + wordpress_states = yield self.add_opposite_service_unit( |
1052 | + mysql_states) |
1053 | |
1054 | yield wait_callback[0] |
1055 | self.verify_unit_watch_result( |
1056 | @@ -817,10 +867,9 @@ |
1057 | """Peer relations always watch the peer container. |
1058 | """ |
1059 | # add the peer relation and two unit of the service. |
1060 | - relation_state = yield self.relation_manager.add_relation_state("peer") |
1061 | - riak_states = yield self.add_relation_service_unit( |
1062 | - "peer", "riak", "riak-db", "peer", |
1063 | - relation_state=relation_state) |
1064 | + riak_ep = RelationEndpoint("riak", "peer", "riak-db", "peer") |
1065 | + riak_states = yield self.add_relation_service_unit_from_endpoints( |
1066 | + riak_ep) |
1067 | |
1068 | riak2_unit = yield riak_states["service"].add_unit_state() |
1069 | yield riak_states["service_relation"].add_unit_state(riak2_unit) |
1070 | @@ -878,13 +927,15 @@ |
1071 | created container is detected correctly and the observation |
1072 | works immediately. |
1073 | """ |
1074 | - # Add the server service and a service unit. |
1075 | - mysql_states = yield self.add_relation_service_unit( |
1076 | - "client-server", "mysql", "", "server") |
1077 | - |
1078 | - wordpress_states = yield self.add_relation_service_unit( |
1079 | - "client-server", "wordpress", "", "client", |
1080 | - relation_state=mysql_states["relation"]) |
1081 | + # Add the relation, services, and related units. |
1082 | + wordpress_ep = RelationEndpoint( |
1083 | + "wordpress", "client-server", "", "client") |
1084 | + mysql_ep = RelationEndpoint( |
1085 | + "mysql", "client-server", "", "server") |
1086 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
1087 | + wordpress_ep, mysql_ep) |
1088 | + mysql_states = yield self.add_opposite_service_unit( |
1089 | + wordpress_states) |
1090 | |
1091 | container_path = "/relations/%s/client" % ( |
1092 | mysql_states["relation"].internal_id) |
1093 | @@ -929,12 +980,14 @@ |
1094 | notification. |
1095 | """ |
1096 | # Add the relation, services, and related units. |
1097 | - mysql_states = yield self.add_relation_service_unit( |
1098 | - "client-server", "mysql", "", "server") |
1099 | - |
1100 | - wordpress_states = yield self.add_relation_service_unit( |
1101 | - "client-server", "wordpress", "", "client", |
1102 | - relation_state=mysql_states["relation"]) |
1103 | + wordpress_ep = RelationEndpoint( |
1104 | + "wordpress", "client-server", "", "client") |
1105 | + mysql_ep = RelationEndpoint( |
1106 | + "mysql", "client-server", "", "server") |
1107 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
1108 | + wordpress_ep, mysql_ep) |
1109 | + mysql_states = yield self.add_opposite_service_unit( |
1110 | + wordpress_states) |
1111 | |
1112 | # setup callbacks and start observing. |
1113 | wait_callback = [Deferred() for i in range(5)] |
1114 | @@ -981,12 +1034,14 @@ |
1115 | the unit is being observed, the watcher will ignore the deletion. |
1116 | """ |
1117 | # Add the relation, services, and related units. |
1118 | - mysql_states = yield self.add_relation_service_unit( |
1119 | - "client-server", "mysql", "", "server") |
1120 | - |
1121 | - wordpress_states = yield self.add_relation_service_unit( |
1122 | - "client-server", "wordpress", "", "client", |
1123 | - relation_state=mysql_states["relation"]) |
1124 | + wordpress_ep = RelationEndpoint( |
1125 | + "wordpress", "client-server", "", "client") |
1126 | + mysql_ep = RelationEndpoint( |
1127 | + "mysql", "client-server", "", "server") |
1128 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
1129 | + wordpress_ep, mysql_ep) |
1130 | + mysql_states = yield self.add_opposite_service_unit( |
1131 | + wordpress_states) |
1132 | |
1133 | # setup callbacks and start observing. |
1134 | wait_callback = [Deferred() for i in range(5)] |
1135 | @@ -1039,12 +1094,14 @@ |
1136 | without additional callbacks. |
1137 | """ |
1138 | # Add the relation, services, and related units. |
1139 | - mysql_states = yield self.add_relation_service_unit( |
1140 | - "client-server", "mysql", "", "server") |
1141 | - |
1142 | - wordpress_states = yield self.add_relation_service_unit( |
1143 | - "client-server", "wordpress", "", "client", |
1144 | - relation_state=mysql_states["relation"]) |
1145 | + wordpress_ep = RelationEndpoint( |
1146 | + "wordpress", "client-server", "", "client") |
1147 | + mysql_ep = RelationEndpoint( |
1148 | + "mysql", "client-server", "", "server") |
1149 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
1150 | + wordpress_ep, mysql_ep) |
1151 | + mysql_states = yield self.add_opposite_service_unit( |
1152 | + wordpress_states) |
1153 | |
1154 | # setup callbacks and start observing. |
1155 | wait_callback = [Deferred() for i in range(5)] |
1156 | @@ -1099,8 +1156,12 @@ |
1157 | without additional callbacks. |
1158 | """ |
1159 | # Add the relation, services, and related units. |
1160 | - mysql_states = yield self.add_relation_service_unit( |
1161 | - "client-server", "mysql", "", "server") |
1162 | + wordpress_ep = RelationEndpoint( |
1163 | + "wordpress", "client-server", "", "client") |
1164 | + mysql_ep = RelationEndpoint( |
1165 | + "mysql", "client-server", "", "server") |
1166 | + mysql_states = yield self.add_relation_service_unit_from_endpoints( |
1167 | + mysql_ep, wordpress_ep) |
1168 | |
1169 | # setup callbacks and start observing. |
1170 | wait_callback = [Deferred() for i in range(5)] |
1171 | @@ -1119,9 +1180,8 @@ |
1172 | watcher.stop() |
1173 | |
1174 | # Add the new service and a unit |
1175 | - wordpress_states = yield self.add_relation_service_unit( |
1176 | - "client-server", "wordpress", "", "client", |
1177 | - relation_state=mysql_states["relation"]) |
1178 | + wordpress_states = yield self.add_opposite_service_unit( |
1179 | + mysql_states) |
1180 | |
1181 | # Add another unit |
1182 | wordpress2_states = yield self.add_related_service_unit( |
1183 | @@ -1314,12 +1374,14 @@ |
1184 | watcher_callback = ParallelWatcherCallback() |
1185 | |
1186 | # Add the relation, services, and related units. |
1187 | - mysql_states = yield self.add_relation_service_unit( |
1188 | - "client-server", "mysql", "", "server") |
1189 | - |
1190 | - wordpress_states = yield self.add_relation_service_unit( |
1191 | - "client-server", "wordpress", "", "client", |
1192 | - relation_state=mysql_states["relation"]) |
1193 | + wordpress_ep = RelationEndpoint( |
1194 | + "wordpress", "client-server", "", "client") |
1195 | + mysql_ep = RelationEndpoint( |
1196 | + "mysql", "client-server", "", "server") |
1197 | + wordpress_states = yield self.add_relation_service_unit_from_endpoints( |
1198 | + wordpress_ep, mysql_ep) |
1199 | + mysql_states = yield self.add_opposite_service_unit( |
1200 | + wordpress_states) |
1201 | |
1202 | # Start watching, and give a moment to establish |
1203 | watcher = yield mysql_states["unit_relation"].watch_related_units( |
1204 | |
1205 | === modified file 'ensemble/state/tests/test_service.py' |
1206 | --- ensemble/state/tests/test_service.py 2010-12-10 04:59:54 +0000 |
1207 | +++ ensemble/state/tests/test_service.py 2010-12-10 23:06:27 +0000 |
1208 | @@ -27,8 +27,8 @@ |
1209 | self.formula_state_manager = FormulaStateManager(self.client) |
1210 | self.service_state_manager = ServiceStateManager(self.client) |
1211 | self.machine_state_manager = MachineStateManager(self.client) |
1212 | - self.formula_state = yield self.formula_state_manager.add_formula_state( |
1213 | - "namespace", self.formula) |
1214 | + self.formula_state = yield self.formula_state_manager.\ |
1215 | + add_formula_state("namespace", self.formula) |
1216 | self.relation_state_manager = RelationStateManager(self.client) |
1217 | |
1218 | @inlineCallbacks |
1219 | @@ -39,13 +39,17 @@ |
1220 | |
1221 | @inlineCallbacks |
1222 | def add_relation(self, relation_type, *services): |
1223 | - relation_state = yield self.relation_state_manager.add_relation_state( |
1224 | - relation_type) |
1225 | + endpoints = [] |
1226 | for service_meta in services: |
1227 | service_state, relation_name, relation_role = service_meta |
1228 | - yield relation_state.assign_service( |
1229 | - service_state, relation_name, relation_role) |
1230 | - returnValue(relation_state) |
1231 | + endpoints.append(RelationEndpoint( |
1232 | + service_state.service_name, |
1233 | + relation_type, |
1234 | + relation_name, |
1235 | + relation_role)) |
1236 | + relation_state = yield self.relation_state_manager.add_relation_state( |
1237 | + *endpoints) |
1238 | + returnValue(relation_state[0]) |
1239 | |
1240 | @inlineCallbacks |
1241 | def remove_service(self, internal_service_id): |
1242 | @@ -435,55 +439,12 @@ |
1243 | yield self.assertFailure(d, StateChanged) |
1244 | |
1245 | @inlineCallbacks |
1246 | - def test_watch_relations_with_changing_topology(self): |
1247 | - """ |
1248 | - If the topology changes in an unrelated way, the |
1249 | - relations watch callback should not be called. |
1250 | - """ |
1251 | - service_state = yield self.add_service("wordpress") |
1252 | - relation_state = yield self.add_relation("rel-type") |
1253 | - |
1254 | - wait_callback = [Deferred() for i in range(5)] |
1255 | - calls = [] |
1256 | - |
1257 | - def watch_relations(old_relations, new_relations): |
1258 | - calls.append((old_relations, new_relations)) |
1259 | - wait_callback[len(calls)-1].callback(True) |
1260 | - |
1261 | - # Start watching |
1262 | - service_state.watch_relation_states(watch_relations) |
1263 | - |
1264 | - # Callback is still untouched. |
1265 | - self.assertEquals(calls, []) |
1266 | - |
1267 | - # assign service to relation, and wait for callback |
1268 | - yield relation_state.assign_service(service_state, "name", "role") |
1269 | - yield wait_callback[0] |
1270 | - yield self.sleep(0.1) |
1271 | - self.assertEqual(len(calls), 2) |
1272 | - self.assertEquals(calls[0], (None, [])) |
1273 | - self.assertEquals(calls[1][0], []) |
1274 | - self.assertEquals( |
1275 | - calls[1][1][0].internal_service_id, service_state.internal_id) |
1276 | - self.assertEquals( |
1277 | - calls[1][1][0].internal_relation_id, relation_state.internal_id) |
1278 | - |
1279 | - # Change the topology in an unrelated way, and give it some time |
1280 | - # in case a callback happens. |
1281 | - yield self.add_service("s-1") |
1282 | - yield self.add_relation("r-2-type") |
1283 | - yield self.sleep(0.2) |
1284 | - |
1285 | - # Assert no changes observed. |
1286 | - self.assertEquals(len(calls), 2) |
1287 | - |
1288 | - @inlineCallbacks |
1289 | def test_watch_relations_when_being_created(self): |
1290 | """ |
1291 | We can watch relations before we have any. |
1292 | """ |
1293 | service_state = yield self.add_service("wordpress") |
1294 | - relation_state = yield self.add_relation("rel-type") |
1295 | + |
1296 | |
1297 | wait_callback = [Deferred() for i in range(5)] |
1298 | calls = [] |
1299 | @@ -499,10 +460,11 @@ |
1300 | self.assertEquals(calls, []) |
1301 | |
1302 | # add a service relation and wait for the callback |
1303 | - yield relation_state.assign_service(service_state, "name", "role") |
1304 | + relation_state = yield self.add_relation( |
1305 | + "rel-type", [service_state, "name", "role"]) |
1306 | yield wait_callback[1] |
1307 | |
1308 | - # verify the result |
1309 | + # # verify the result |
1310 | self.assertEquals(len(calls), 2) |
1311 | old_relations, new_relations = calls[1] |
1312 | self.assertFalse(old_relations) |
1313 | @@ -511,7 +473,6 @@ |
1314 | # add a new relation with the service assigned to it. |
1315 | relation_state2 = yield self.add_relation( |
1316 | "rel-type2", [service_state, "app", "server"]) |
1317 | - |
1318 | yield wait_callback[2] |
1319 | |
1320 | self.assertEquals(len(calls), 3) |
1321 | @@ -555,7 +516,7 @@ |
1322 | |
1323 | # Assign to another relation. |
1324 | yield self.add_relation("rel-type", |
1325 | - (service_state, "name", "role")) |
1326 | + (service_state, "name2", "role")) |
1327 | |
1328 | # Give a chance for something bad to happen. |
1329 | yield self.sleep(0.3) |
1330 | @@ -573,61 +534,6 @@ |
1331 | self.assertEquals(len(calls), 2) |
1332 | |
1333 | @inlineCallbacks |
1334 | - def test_watch_relation_assign_and_unassign_at_once(self): |
1335 | - """ |
1336 | - If a service is quickly assigned and unassigned to a relation, the |
1337 | - change maybe be observed, as a single modificaiton in state. |
1338 | - """ |
1339 | - wait_callback = [Deferred() for i in range(10)] |
1340 | - |
1341 | - calls = [] |
1342 | - |
1343 | - def watch_units(old_units, new_units): |
1344 | - calls.append((old_units, new_units)) |
1345 | - wait_callback[len(calls)-1].callback(True) |
1346 | - |
1347 | - # Create the service |
1348 | - service_state = yield self.add_service("s-1") |
1349 | - |
1350 | - # Create a relation with the service assigned. |
1351 | - relation_state1 = yield self.add_relation( |
1352 | - "rel-type", (service_state, "name", "role")) |
1353 | - relation_state2 = yield self.add_relation("rel-type") |
1354 | - |
1355 | - # Start watching |
1356 | - yield service_state.watch_relation_states(watch_units) |
1357 | - |
1358 | - yield wait_callback[0] |
1359 | - self.assertEquals(len(calls), 1) |
1360 | - old_relations, new_relations = calls[0] |
1361 | - self.assertEqual(old_relations, None) |
1362 | - |
1363 | - # Modify the topology by hand so we get notification of multiple |
1364 | - # things simultaneously. |
1365 | - topology = yield self.get_topology() |
1366 | - topology.unassign_service_from_relation( |
1367 | - relation_state1.internal_id, |
1368 | - service_state.internal_id) |
1369 | - |
1370 | - topology.assign_service_to_relation( |
1371 | - relation_state2.internal_id, |
1372 | - service_state.internal_id, |
1373 | - "role", "name") |
1374 | - |
1375 | - yield self.set_topology(topology) |
1376 | - |
1377 | - yield wait_callback[1] |
1378 | - self.assertEqual(len(calls), 2) |
1379 | - old_relations, new_relations = calls[1] |
1380 | - |
1381 | - # Verify the changes seen. |
1382 | - self.assertEqual([r.internal_relation_id for r in old_relations], |
1383 | - [relation_state1.internal_id]) |
1384 | - |
1385 | - self.assertEqual([r.internal_relation_id for r in new_relations], |
1386 | - [relation_state2.internal_id]) |
1387 | - |
1388 | - @inlineCallbacks |
1389 | def add_service_from_formula(self, service_name): |
1390 | formula_dir = FormulaDirectory(os.path.join( |
1391 | test_repository_path, service_name)) |
1392 | @@ -644,17 +550,17 @@ |
1393 | self.assertEqual( |
1394 | (yield self.service_state_manager.get_relation_endpoints( |
1395 | "wordpress")), |
1396 | - set([RelationEndpoint("wordpress", "http", "url", "provides"), |
1397 | - RelationEndpoint("wordpress", "mysql", "db", "requires"), |
1398 | + set([RelationEndpoint("wordpress", "http", "url", "server"), |
1399 | + RelationEndpoint("wordpress", "mysql", "db", "client"), |
1400 | RelationEndpoint( |
1401 | - "wordpress", "varnish", "cache", "requires")])) |
1402 | + "wordpress", "varnish", "cache", "client")])) |
1403 | yield self.add_service_from_formula("riak") |
1404 | self.assertEqual( |
1405 | (yield self.service_state_manager.get_relation_endpoints( |
1406 | "riak")), |
1407 | - set([RelationEndpoint("riak", "http", "admin", "provides"), |
1408 | - RelationEndpoint("riak", "http", "endpoint", "provides"), |
1409 | - RelationEndpoint("riak", "riak", "ring", "peers")])) |
1410 | + set([RelationEndpoint("riak", "http", "admin", "server"), |
1411 | + RelationEndpoint("riak", "http", "endpoint", "server"), |
1412 | + RelationEndpoint("riak", "riak", "ring", "peer")])) |
1413 | |
1414 | @inlineCallbacks |
1415 | def test_get_relation_endpoints_service_name_relation_name(self): |
1416 | @@ -663,21 +569,21 @@ |
1417 | self.assertEqual( |
1418 | (yield self.service_state_manager.get_relation_endpoints( |
1419 | "wordpress:url")), |
1420 | - set([RelationEndpoint("wordpress", "http", "url", "provides")])) |
1421 | + set([RelationEndpoint("wordpress", "http", "url", "server")])) |
1422 | self.assertEqual( |
1423 | (yield self.service_state_manager.get_relation_endpoints( |
1424 | "wordpress:db")), |
1425 | - set([RelationEndpoint("wordpress", "mysql", "db", "requires")])) |
1426 | + set([RelationEndpoint("wordpress", "mysql", "db", "client")])) |
1427 | self.assertEqual( |
1428 | (yield self.service_state_manager.get_relation_endpoints( |
1429 | "wordpress:cache")), |
1430 | set([RelationEndpoint( |
1431 | - "wordpress", "varnish", "cache", "requires")])) |
1432 | + "wordpress", "varnish", "cache", "client")])) |
1433 | yield self.add_service_from_formula("riak") |
1434 | self.assertEqual( |
1435 | (yield self.service_state_manager.get_relation_endpoints( |
1436 | "riak:ring")), |
1437 | - set([RelationEndpoint("riak", "riak", "ring", "peers")])) |
1438 | + set([RelationEndpoint("riak", "riak", "ring", "peer")])) |
1439 | |
1440 | @inlineCallbacks |
1441 | def test_descriptor_for_services_without_formulas(self): |
1442 | @@ -718,20 +624,20 @@ |
1443 | self.assertEqual( |
1444 | (yield self.service_state_manager.join_descriptors( |
1445 | "wordpress", "mysql")), |
1446 | - [(RelationEndpoint("wordpress", "mysql", "db", "requires"), |
1447 | - RelationEndpoint("mysql", "mysql", "server", "provides"))]) |
1448 | + [(RelationEndpoint("wordpress", "mysql", "db", "client"), |
1449 | + RelationEndpoint("mysql", "mysql", "server", "server"))]) |
1450 | # symmetric - note the pair has rotated |
1451 | self.assertEqual( |
1452 | (yield self.service_state_manager.join_descriptors( |
1453 | "mysql", "wordpress")), |
1454 | - [(RelationEndpoint("mysql", "mysql", "server", "provides"), |
1455 | - RelationEndpoint("wordpress", "mysql", "db", "requires"))]) |
1456 | + [(RelationEndpoint("mysql", "mysql", "server", "server"), |
1457 | + RelationEndpoint("wordpress", "mysql", "db", "client"))]) |
1458 | yield self.add_service_from_formula("varnish") |
1459 | self.assertEqual( |
1460 | (yield self.service_state_manager.join_descriptors( |
1461 | "wordpress", "varnish")), |
1462 | - [(RelationEndpoint("wordpress", "varnish", "cache", "requires"), |
1463 | - RelationEndpoint("varnish", "varnish", "webcache", "provides"))]) |
1464 | + [(RelationEndpoint("wordpress", "varnish", "cache", "client"), |
1465 | + RelationEndpoint("varnish", "varnish", "webcache", "server"))]) |
1466 | |
1467 | @inlineCallbacks |
1468 | def test_join_descriptors_service_name_relation_name(self): |
1469 | @@ -741,30 +647,30 @@ |
1470 | self.assertEqual( |
1471 | (yield self.service_state_manager.join_descriptors( |
1472 | "wordpress:db", "mysql")), |
1473 | - [(RelationEndpoint("wordpress", "mysql", "db", "requires"), |
1474 | - RelationEndpoint("mysql", "mysql", "server", "provides"))]) |
1475 | + [(RelationEndpoint("wordpress", "mysql", "db", "client"), |
1476 | + RelationEndpoint("mysql", "mysql", "server", "server"))]) |
1477 | self.assertEqual( |
1478 | (yield self.service_state_manager.join_descriptors( |
1479 | "mysql:server", "wordpress")), |
1480 | - [(RelationEndpoint("mysql", "mysql", "server", "provides"), |
1481 | - RelationEndpoint("wordpress", "mysql", "db", "requires"))]) |
1482 | + [(RelationEndpoint("mysql", "mysql", "server", "server"), |
1483 | + RelationEndpoint("wordpress", "mysql", "db", "client"))]) |
1484 | self.assertEqual( |
1485 | (yield self.service_state_manager.join_descriptors( |
1486 | "mysql:server", "wordpress:db")), |
1487 | - [(RelationEndpoint("mysql", "mysql", "server", "provides"), |
1488 | - RelationEndpoint("wordpress", "mysql", "db", "requires"))]) |
1489 | + [(RelationEndpoint("mysql", "mysql", "server", "server"), |
1490 | + RelationEndpoint("wordpress", "mysql", "db", "client"))]) |
1491 | |
1492 | yield self.add_service_from_formula("varnish") |
1493 | self.assertEqual( |
1494 | (yield self.service_state_manager.join_descriptors( |
1495 | "wordpress:cache", "varnish")), |
1496 | - [(RelationEndpoint("wordpress", "varnish", "cache", "requires"), |
1497 | - RelationEndpoint("varnish", "varnish", "webcache", "provides"))]) |
1498 | + [(RelationEndpoint("wordpress", "varnish", "cache", "client"), |
1499 | + RelationEndpoint("varnish", "varnish", "webcache", "server"))]) |
1500 | self.assertEqual( |
1501 | (yield self.service_state_manager.join_descriptors( |
1502 | "wordpress:cache", "varnish:webcache")), |
1503 | - [(RelationEndpoint("wordpress", "varnish", "cache", "requires"), |
1504 | - RelationEndpoint("varnish", "varnish", "webcache", "provides"))]) |
1505 | + [(RelationEndpoint("wordpress", "varnish", "cache", "client"), |
1506 | + RelationEndpoint("varnish", "varnish", "webcache", "server"))]) |
1507 | |
1508 | @inlineCallbacks |
1509 | def test_join_peer_descriptors(self): |
1510 | @@ -773,18 +679,18 @@ |
1511 | self.assertEqual( |
1512 | (yield self.service_state_manager.join_descriptors( |
1513 | "riak", "riak")), |
1514 | - [(RelationEndpoint("riak", "riak", "ring", "peers"), |
1515 | - RelationEndpoint("riak", "riak", "ring", "peers"))]) |
1516 | + [(RelationEndpoint("riak", "riak", "ring", "peer"), |
1517 | + RelationEndpoint("riak", "riak", "ring", "peer"))]) |
1518 | self.assertEqual( |
1519 | (yield self.service_state_manager.join_descriptors( |
1520 | "riak:ring", "riak")), |
1521 | - [(RelationEndpoint("riak", "riak", "ring", "peers"), |
1522 | - RelationEndpoint("riak", "riak", "ring", "peers"))]) |
1523 | + [(RelationEndpoint("riak", "riak", "ring", "peer"), |
1524 | + RelationEndpoint("riak", "riak", "ring", "peer"))]) |
1525 | self.assertEqual( |
1526 | (yield self.service_state_manager.join_descriptors( |
1527 | "riak:ring", "riak:ring")), |
1528 | - [(RelationEndpoint("riak", "riak", "ring", "peers"), |
1529 | - RelationEndpoint("riak", "riak", "ring", "peers"))]) |
1530 | + [(RelationEndpoint("riak", "riak", "ring", "peer"), |
1531 | + RelationEndpoint("riak", "riak", "ring", "peer"))]) |
1532 | self.assertEqual( |
1533 | (yield self.service_state_manager.join_descriptors( |
1534 | "riak:no-ring", "riak:ring")), |
1535 | @@ -828,3 +734,4 @@ |
1536 | yield self.assertFailure( |
1537 | self.service_state_manager.join_descriptors("notyet", "nosuch"), |
1538 | ServiceStateNotFound) |
1539 | + |
1540 | |
1541 | === modified file 'ensemble/state/tests/test_topology.py' |
1542 | --- ensemble/state/tests/test_topology.py 2010-11-17 20:40:28 +0000 |
1543 | +++ ensemble/state/tests/test_topology.py 2010-12-10 23:06:27 +0000 |
1544 | @@ -1,9 +1,8 @@ |
1545 | import yaml |
1546 | |
1547 | from ensemble.lib.testing import TestCase |
1548 | - |
1549 | -from ensemble.state.topology import ( |
1550 | - InternalTopology, InternalTopologyError) |
1551 | +from ensemble.state.endpoint import RelationEndpoint |
1552 | +from ensemble.state.topology import InternalTopology, InternalTopologyError |
1553 | |
1554 | |
1555 | class InternalTopologyMapTest(TestCase): |
1556 | @@ -790,8 +789,7 @@ |
1557 | self.assertFalse(self.topology.get_relations_for_service("s-0")) |
1558 | |
1559 | def test_relation_has_service(self): |
1560 | - """We can test to see if a service is associated to a relation. |
1561 | - """ |
1562 | + """We can test to see if a service is associated to a relation.""" |
1563 | self.assertFalse(self.topology.relation_has_service("r-1", "s-0")) |
1564 | self.topology.add_relation("r-1", "type") |
1565 | self.topology.add_service("s-0", "wordpress") |
1566 | @@ -799,8 +797,7 @@ |
1567 | self.assertTrue(self.topology.relation_has_service("r-1", "s-0")) |
1568 | |
1569 | def test_get_relation_service(self): |
1570 | - """We can fetch the setting of a service within a relation. |
1571 | - """ |
1572 | + """We can fetch the setting of a service within a relation.""" |
1573 | # Invalid relations cause an exception. |
1574 | self.assertRaises(InternalTopologyError, |
1575 | self.topology.get_relation_service, |
1576 | @@ -903,3 +900,54 @@ |
1577 | self.assertRaises(InternalTopologyError, |
1578 | self.topology.remove_service, |
1579 | "s-0") |
1580 | + |
1581 | + def test_has_relation_between_dyadic_endpoints(self): |
1582 | + mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") |
1583 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
1584 | + self.topology.add_service("s-0", "wordpress") |
1585 | + self.topology.add_service("s-1", "mysqldb") |
1586 | + self.topology.add_relation("r-0", "mysql") |
1587 | + self.topology.assign_service_to_relation( |
1588 | + "r-0", "s-0", "mysql", "client") |
1589 | + self.topology.assign_service_to_relation( |
1590 | + "r-0", "s-1", "db", "server") |
1591 | + self.assertTrue(self.topology.has_relation_between_endpoints([ |
1592 | + mysql_ep, blog_ep])) |
1593 | + self.assertTrue(self.topology.has_relation_between_endpoints([ |
1594 | + blog_ep, mysql_ep])) |
1595 | + |
1596 | + def test_has_relation_between_dyadic_endpoints_missing_assignment(self): |
1597 | + mysql_ep = RelationEndpoint("mysqldb", "mysql", "db", "server") |
1598 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
1599 | + self.topology.add_service("s-0", "wordpress") |
1600 | + self.topology.add_service("s-1", "mysqldb") |
1601 | + self.topology.add_relation("r-0", "mysql") |
1602 | + self.topology.assign_service_to_relation( |
1603 | + "r-0", "s-1", "db", "server") |
1604 | + self.assertFalse(self.topology.has_relation_between_endpoints([ |
1605 | + mysql_ep, blog_ep])) |
1606 | + self.assertFalse(self.topology.has_relation_between_endpoints([ |
1607 | + blog_ep, mysql_ep])) |
1608 | + |
1609 | + def test_has_relation_between_dyadic_endpoints_wrong_relation_name(self): |
1610 | + mysql_ep = RelationEndpoint("mysqldb", "mysql", "wrong-name", "server") |
1611 | + blog_ep = RelationEndpoint("wordpress", "mysql", "mysql", "client") |
1612 | + self.topology.add_service("s-0", "wordpress") |
1613 | + self.topology.add_service("s-1", "mysqldb") |
1614 | + self.topology.add_relation("r-0", "mysql") |
1615 | + self.topology.assign_service_to_relation( |
1616 | + "r-0", "s-0", "mysql", "client") |
1617 | + self.topology.assign_service_to_relation( |
1618 | + "r-0", "s-1", "db", "server") |
1619 | + self.assertFalse(self.topology.has_relation_between_endpoints([ |
1620 | + mysql_ep, blog_ep])) |
1621 | + self.assertFalse(self.topology.has_relation_between_endpoints([ |
1622 | + blog_ep, mysql_ep])) |
1623 | + |
1624 | + def test_has_relation_between_monadic_endpoints(self): |
1625 | + riak_ep = RelationEndpoint("riak", "riak", "riak", "peer") |
1626 | + self.topology.add_service("s-0", "riak") |
1627 | + self.topology.add_relation("r-0", "riak") |
1628 | + self.topology.assign_service_to_relation("r-0", "s-0", "riak", "peer") |
1629 | + self.assertTrue(self.topology.has_relation_between_endpoints( |
1630 | + [riak_ep])) |
1631 | |
1632 | === modified file 'ensemble/state/topology.py' |
1633 | --- ensemble/state/topology.py 2010-11-17 20:40:28 +0000 |
1634 | +++ ensemble/state/topology.py 2010-12-10 23:06:27 +0000 |
1635 | @@ -308,8 +308,7 @@ |
1636 | return relation_type |
1637 | |
1638 | def relation_has_service(self, relation_id, service_id): |
1639 | - """Return true if service_id is assigned to relation_id. |
1640 | - """ |
1641 | + """Return if `service_id` is assigned to `relation_id`.""" |
1642 | relations = self._state.get("relations", self._nil_dict) |
1643 | relation_type, services = relations.get(relation_id, |
1644 | (None, self._nil_dict)) |
1645 | @@ -396,3 +395,29 @@ |
1646 | raise InternalTopologyError( |
1647 | "Service unit %s not found in service %s" % ( |
1648 | unit_id, service_id)) |
1649 | + |
1650 | + def has_relation_between_endpoints(self, endpoints): |
1651 | + """Examine topology to check if relation exists between `endpoints`. |
1652 | + |
1653 | + The relation, with a ``relation type`` common to the |
1654 | + endpoints, must exist between all endpoints (presumably one |
1655 | + for peer, two for client-server). The topology for the |
1656 | + relations looks like the following in YAML:: |
1657 | + |
1658 | + relations: |
1659 | + relation-0000000000: |
1660 | + - mysql |
1661 | + - service-0000000000: {name: db, role: client} |
1662 | + service-0000000001: {name: server, role: server} |
1663 | + """ |
1664 | + service_ids = dict((e, self.find_service_with_name(e.service_name)) |
1665 | + for e in endpoints) |
1666 | + relations = self._state.get("relations", self._nil_dict) |
1667 | + for _, services in relations.itervalues(): |
1668 | + for endpoint in endpoints: |
1669 | + service = services.get(service_ids[endpoint]) |
1670 | + if not service or service["name"] != endpoint.relation_name: |
1671 | + break |
1672 | + else: |
1673 | + return True |
1674 | + return False |
[1]
Jim, as Kapil stated and as I reminded over our call, Kapil's code was a quick hack put yErrors should never surface out of the
in place to demonstrate the concept. There are error situations which should be verified
and tested within that logic. Assigning a service to the same relation twice shouldn't
be possible, for instance, and InternalTopolog
state code, so there are checks which must be made and appropriate errors raised in
those cases.
Please look at existing code similar to that logic to identify the pattern.