Merge lp:~themue/pyjuju/go-state-add-relation into lp:pyjuju/go

Proposed by Frank Mueller
Status: Merged
Merged at revision: 200
Proposed branch: lp:~themue/pyjuju/go-state-add-relation
Merge into: lp:pyjuju/go
Prerequisite: lp:~themue/pyjuju/go-state-topology-relation-endpoints
Diff against target: 280 lines (+250/-3)
3 files modified
state/relation.go (+33/-0)
state/state.go (+117/-0)
state/state_test.go (+100/-3)
To merge this branch: bzr merge lp:~themue/pyjuju/go-state-add-relation
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+106652@code.launchpad.net

Description of the change

state: Added two methods to add relations to State.

AddClientServerReleation() gets a client and a server endpoint,
AddPeerRelation() a peer endpoint. They return the relation and
both service relations for client/server or the one for peer.
The seperation into two methods with well defined arguments make
it easier to ensure a valid relation creation.

https://codereview.appspot.com/6223055/

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

https://codereview.appspot.com/6223055/diff/1/state/relation.go
File state/relation.go (right):

https://codereview.appspot.com/6223055/diff/1/state/relation.go#newcode60
state/relation.go:60: func (r *Relation) Key() string {
The internal key is internal. :-)

https://codereview.appspot.com/6223055/diff/1/state/relation.go#newcode73
state/relation.go:73: // Key returns the internal key of a relation.
Ditto.

https://codereview.appspot.com/6223055/diff/1/state/relation.go#newcode78
state/relation.go:78: // ServiceKey returns the service key of a
relation.
Ditto.

https://codereview.appspot.com/6223055/diff/1/state/state.go
File state/state.go (right):

https://codereview.appspot.com/6223055/diff/1/state/state.go#newcode239
state/state.go:239: if clientEp.RelationRole != RoleClient {
As a general and vague comment, visually these functions do not look
nice. Maybe there's no way to avoid the complexity, but please look for
opportunities to separate logical blocks visually, and perhaps using
functions is adequate.

I'll hold-off on reviewing this until we sort out the pre-req, since
there are necessary changes there that will certainly affect this.

https://codereview.appspot.com/6223055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Download full text (6.6 KiB)

https://codereview.appspot.com/6223055/diff/9001/state/relation.go
File state/relation.go (right):

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode50
state/relation.go:50: // Relation represents a connection between one or
more services.
// Relation represents a link between services, or within a
// service (in the case of peer relations).

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode56
state/relation.go:56: // ServiceRelation represents a relation between
one or more services.
// ServiceRelation represents an established relation from
// the viewpoint of a participant service.

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode59
state/relation.go:59: key string
s/key/relationKey/; given the type name, this would be ambiguous.

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode63
state/relation.go:63: name string
relationScope, relationRole, relationName, for the same reason.

Also on the methods below please (RelationRole, RelationName, etc).

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode66
state/relation.go:66: // Scope returns the scope of a relation.
s/a/the/

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode71
state/relation.go:71: // Role returns the role of a relation.
// RelationRole returns the service role within the relation.

https://codereview.appspot.com/6223055/diff/9001/state/relation.go#newcode76
state/relation.go:76: // Name returns the name of a relation.
// RelationName returns the name this relation has within the service.

https://codereview.appspot.com/6223055/diff/9001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode257
state/state.go:257: if relationKey != "" {
This is ignoring err. Even if it works, I'd prefer if we were more
conservative about it:

if _, ok := err.(*NoRelationError); ok {
     return false, nil
}
if err != nil {
     return false, err
}
return true, err

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode266
state/state.go:266: // addRelation creates the relation node depending
on the given scope.
I don't get what "depending on the given scope" is trying to tell me.

addRelation also seems like a bad name for this function. There's both
addRelation and AddRelation, and they are very different beasts.

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode274
state/state.go:274: // Creation is per container for container scoped
relations and
I don't quite get what this comment is saying either.

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode276
state/state.go:276: if scope == ScopeGlobal {
This seems weird. Why is it only introducing the settings if the
relation is global? I don't know if that makes sense, and if it does we
certainly need a clear comment here explaining the background.

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode286
state/state.go:286: // and endpoint.
s/and endpoint/endpoint/, comments above hold here and in the function
below too.

https://codereview.apps...

Read more...

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

The code diff has a lot of unrelated code. Please let me know once the
code is clean and may be reviewed.

https://codereview.appspot.com/6223055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Download full text (3.9 KiB)

LGTM, but see the comments please:

https://codereview.appspot.com/6223055/diff/9001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode276
state/state.go:276: if scope == ScopeGlobal {
On 2012/05/31 16:23:59, TheMue wrote:
> On 2012/05/30 22:35:42, niemeyer wrote:
> > This seems weird. Why is it only introducing the settings if the
relation is
> > global? I don't know if that makes sense, and if it does we
certainly need a
> > clear comment here explaining the background.

> I tried to make the comments more clear, they have been almost a 1:1
copy of the
> Python code. We should talk about the container scope and if the whole
method(s)
> can't be modified to handle this too. So far I don't know enough about
the
> meaning of the scope and have to analyze it first.

Okay, this branch sounds fine besides what we already talked about and
the minor points, but please do understand the scope logic. As you have
seen in the relation+topology story, writing logic when you have no idea
of what is going on is hard.

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode334
state/state.go:334: relationKey, err := s.addRelation(scope)
On 2012/05/31 16:23:59, TheMue wrote:
> On 2012/05/30 22:35:42, niemeyer wrote:
> > Have we verified that the endpoints make sense at this point? It
looks like
> > we've checked that there is a good number of them, but I can't see
it
> verifying
> > that the endpoints make sense (service names, relation names,
interface names,
> > etc). It'd be bad to be creating state in ZooKeeper just to later
tell
> something
> > trivial to the user that we could have verified upfront. We
shouldn't be
> > duplicating this logic, though.

> Today this isn't done. I would like to do it in a new small branch
after this
> and RemoveRelation() are in.

I filed a bug to keep track of this and assigned to you:

https://bugs.launchpad.net/juju-core/+bug/1007373

https://codereview.appspot.com/6223055/diff/9001/state/state.go#newcode369
state/state.go:369: Service: serviceRelation.serviceKey,
On 2012/05/31 16:23:59, TheMue wrote:
> On 2012/05/30 22:35:42, niemeyer wrote:
> > serviceRelation has a public API that we can use it here, rather
than fiddling
> > with its internals assuming we know what's going on.
> >
> > Same below.

> The name can be retrieved with RelationName() but the service key is
internal so
> far. Shall a public accessor be added?

No, you're right. serviceKey is internal.

https://codereview.appspot.com/6223055/diff/10005/state/state.go
File state/state.go (right):

https://codereview.appspot.com/6223055/diff/10005/state/state.go#newcode263
state/state.go:263: // container occurs in ServiceRelation.AddUnit().
That's a lot nicer, thanks.

Would you mind to just drop the () as we tend to refer to methods by
their name without the call syntax.

https://codereview.appspot.com/6223055/diff/10005/state/state.go#newcode279
state/state.go:279: if scope == ScopeGlobal {
That's a bit strange. This method only does anything at all if the scope
is not global, and the scope is only provided so it can be checked here
for not doing anything at all.

Ins...

Read more...

164. By Frank Mueller

state: Merge of trunk after LGTM.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'state/relation.go'
--- state/relation.go 2012-05-25 16:48:30 +0000
+++ state/relation.go 2012-06-01 12:44:24 +0000
@@ -46,3 +46,36 @@
46func (e RelationEndpoint) String() string {46func (e RelationEndpoint) String() string {
47 return e.ServiceName + ":" + e.RelationName47 return e.ServiceName + ":" + e.RelationName
48}48}
49
50// Relation represents a link between services, or within a
51// service (in the case of peer relations).
52type Relation struct {
53 st *State
54 key string
55}
56
57// ServiceRelation represents an established relation from
58// the viewpoint of a participant service.
59type ServiceRelation struct {
60 st *State
61 relationKey string
62 serviceKey string
63 relationScope RelationScope
64 relationRole RelationRole
65 relationName string
66}
67
68// RelationScope returns the scope of the relation.
69func (r *ServiceRelation) RelationScope() RelationScope {
70 return r.relationScope
71}
72
73// RelationRole returns the service role within the relation.
74func (r *ServiceRelation) RelationRole() RelationRole {
75 return r.relationRole
76}
77
78// RelationName returns the name this relation has within the service.
79func (r *ServiceRelation) RelationName() string {
80 return r.relationName
81}
4982
=== modified file 'state/state.go'
--- state/state.go 2012-05-29 23:11:41 +0000
+++ state/state.go 2012-06-01 12:44:24 +0000
@@ -250,3 +250,120 @@
250 }250 }
251 return service.Unit(name)251 return service.Unit(name)
252}252}
253
254// addRelationNode creates the relation node.
255func (s *State) addRelationNode(scope RelationScope) (string, error) {
256 path, err := s.zk.Create("/relations/relation-", "", zookeeper.SEQUENCE, zkPermAll)
257 if err != nil {
258 return "", err
259 }
260 relationKey := strings.Split(path, "/")[2]
261 // Create the settings node only if the scope is global.
262 // In case of container scoped relations the creation per
263 // container occurs in ServiceRelation.AddUnit.
264 if scope == ScopeGlobal {
265 _, err = s.zk.Create(path+"/settings", "", 0, zkPermAll)
266 if err != nil {
267 return "", err
268 }
269 }
270 return relationKey, nil
271}
272
273// addRelationEndpointNode creates the endpoint role node below its relation node
274// for the given relation endpoint.
275func (s *State) addRelationEndpointNode(relationKey string, endpoint RelationEndpoint) error {
276 path := fmt.Sprintf("/relations/%s/%s", relationKey, string(endpoint.RelationRole))
277 _, err := s.zk.Create(path, "", 0, zkPermAll)
278 return err
279}
280
281// AddRelation creates a new relation with the given endpoints.
282func (s *State) AddRelation(endpoints ...RelationEndpoint) (*Relation, []*ServiceRelation, error) {
283 switch len(endpoints) {
284 case 1:
285 if endpoints[0].RelationRole != RolePeer {
286 return nil, nil, fmt.Errorf("can't add non-peer relation with a single service")
287 }
288 case 2:
289 if !endpoints[0].CanRelateTo(&endpoints[1]) {
290 return nil, nil, fmt.Errorf("can't add relation between %s and %s", endpoints[0], endpoints[1])
291 }
292 default:
293 return nil, nil, fmt.Errorf("can't add relations between %d services", len(endpoints))
294 }
295 t, err := readTopology(s.zk)
296 if err != nil {
297 return nil, nil, err
298 }
299 // Check if the relation already exists.
300 relationKey, err := t.RelationKey(endpoints...)
301 if err != nil {
302 if _, ok := err.(*NoRelationError); !ok {
303 return nil, nil, err
304 }
305 }
306 if relationKey != "" {
307 return nil, nil, fmt.Errorf("relation already exists")
308 }
309 scope := ScopeGlobal
310 for _, endpoint := range endpoints {
311 if endpoint.RelationScope == ScopeContainer {
312 scope = ScopeContainer
313 break
314 }
315 }
316 // Add a new relation node depending on the scope. Afterwards
317 // create a node and a service relation per endpoint.
318 relationKey, err = s.addRelationNode(scope)
319 if err != nil {
320 return nil, nil, err
321 }
322 serviceRelations := []*ServiceRelation{}
323 for _, endpoint := range endpoints {
324 serviceKey, err := t.ServiceKey(endpoint.ServiceName)
325 if err != nil {
326 return nil, nil, err
327 }
328 // The relation endpoint node is only created if the scope is
329 // global. In case of container scoped relations the creation
330 // per container occurs in ServiceRelation.AddUnit.
331 if scope == ScopeGlobal {
332 if err = s.addRelationEndpointNode(relationKey, endpoint); err != nil {
333 return nil, nil, err
334 }
335 }
336 serviceRelations = append(serviceRelations, &ServiceRelation{
337 st: s,
338 relationKey: relationKey,
339 serviceKey: serviceKey,
340 relationScope: endpoint.RelationScope,
341 relationRole: endpoint.RelationRole,
342 relationName: endpoint.RelationName,
343 })
344 }
345 // Add relation to topology.
346 addRelation := func(t *topology) error {
347 relation := &topoRelation{
348 Interface: endpoints[0].Interface,
349 Scope: scope,
350 Services: map[RelationRole]*topoRelationService{},
351 }
352 for _, serviceRelation := range serviceRelations {
353 if !t.HasService(serviceRelation.serviceKey) {
354 return fmt.Errorf("state for service %q has changed", serviceRelation.serviceKey)
355 }
356 service := &topoRelationService{
357 Service: serviceRelation.serviceKey,
358 RelationName: serviceRelation.RelationName(),
359 }
360 relation.Services[serviceRelation.RelationRole()] = service
361 }
362 return t.AddRelation(relationKey, relation)
363 }
364 err = retryTopologyChange(s.zk, addRelation)
365 if err != nil {
366 return nil, nil, err
367 }
368 return &Relation{s, relationKey}, serviceRelations, nil
369}
253370
=== modified file 'state/state_test.go'
--- state/state_test.go 2012-05-30 01:11:22 +0000
+++ state/state_test.go 2012-06-01 12:44:24 +0000
@@ -1,6 +1,3 @@
1// launchpad.net/juju/go/state
2//
3// Copyright (c) 2011-2012 Canonical Ltd.
4package state_test1package state_test
52
6import (3import (
@@ -1017,3 +1014,103 @@
1017 c.Assert(err, IsNil)1014 c.Assert(err, IsNil)
1018 c.Assert(alive, Equals, false)1015 c.Assert(alive, Equals, false)
1019}1016}
1017
1018func (s *StateSuite) TestAddRelation(c *C) {
1019 dummy := s.addDummyCharm(c)
1020 // Provider and requirer.
1021 s.st.AddService("mysqldb", dummy)
1022 s.st.AddService("wordpress", dummy)
1023 mysqlep := state.RelationEndpoint{"mysqldb", "blog", "db", state.RoleProvider, state.ScopeGlobal}
1024 blogep := state.RelationEndpoint{"wordpress", "blog", "db", state.RoleRequirer, state.ScopeGlobal}
1025 relation, serviceRelations, err := s.st.AddRelation(blogep, mysqlep)
1026 c.Assert(err, IsNil)
1027 c.Assert(relation, NotNil)
1028 c.Assert(serviceRelations, HasLen, 2)
1029 c.Assert(serviceRelations[0].RelationScope(), Equals, state.ScopeGlobal)
1030 c.Assert(serviceRelations[0].RelationRole(), Equals, state.RoleRequirer)
1031 c.Assert(serviceRelations[1].RelationScope(), Equals, state.ScopeGlobal)
1032 c.Assert(serviceRelations[1].RelationRole(), Equals, state.RoleProvider)
1033 c.Assert(serviceRelations[0].RelationName(), Equals, serviceRelations[1].RelationName())
1034 // Peer.
1035 s.st.AddService("riak", dummy)
1036 riakep := state.RelationEndpoint{"riak", "ring", "cache", state.RolePeer, state.ScopeGlobal}
1037 relation, serviceRelations, err = s.st.AddRelation(riakep)
1038 c.Assert(err, IsNil)
1039 c.Assert(relation, NotNil)
1040 c.Assert(serviceRelations, HasLen, 1)
1041 c.Assert(serviceRelations[0].RelationScope(), Equals, state.ScopeGlobal)
1042 c.Assert(serviceRelations[0].RelationRole(), Equals, state.RolePeer)
1043 c.Assert(serviceRelations[0].RelationName(), Equals, "cache")
1044
1045}
1046
1047func (s *StateSuite) TestAddRelationMissingService(c *C) {
1048 dummy := s.addDummyCharm(c)
1049 s.st.AddService("mysqldb", dummy)
1050 mysqlep := state.RelationEndpoint{"mysqldb", "blog", "db", state.RoleProvider, state.ScopeGlobal}
1051 blogep := state.RelationEndpoint{"wordpress", "blog", "db", state.RoleRequirer, state.ScopeGlobal}
1052 _, _, err := s.st.AddRelation(blogep, mysqlep)
1053 c.Assert(err, ErrorMatches, `service with name "wordpress" not found`)
1054}
1055
1056func (s *StateSuite) TestAddRelationMissingEndpoint(c *C) {
1057 dummy := s.addDummyCharm(c)
1058 s.st.AddService("mysqldb", dummy)
1059 mysqlep := state.RelationEndpoint{"mysqldb", "blog", "db", state.RoleProvider, state.ScopeGlobal}
1060 _, _, err := s.st.AddRelation(mysqlep)
1061 c.Assert(err, ErrorMatches, `can't add non-peer relation with a single service`)
1062}
1063
1064func (s *StateSuite) TestAddClientServerDifferentRoles(c *C) {
1065 dummy := s.addDummyCharm(c)
1066 s.st.AddService("mysqldb", dummy)
1067 s.st.AddService("riak", dummy)
1068 mysqlep := state.RelationEndpoint{"mysqldb", "ifce", "db", state.RoleRequirer, state.ScopeGlobal}
1069 riakep := state.RelationEndpoint{"riak", "ring", "cache", state.RolePeer, state.ScopeGlobal}
1070 _, _, err := s.st.AddRelation(mysqlep, riakep)
1071 c.Assert(err, ErrorMatches, `can't add relation between mysqldb:db and riak:cache`)
1072}
1073
1074func (s *StateSuite) TestAddRelationDifferentInterfaces(c *C) {
1075 dummy := s.addDummyCharm(c)
1076 s.st.AddService("mysqldb", dummy)
1077 s.st.AddService("wordpress", dummy)
1078 mysqlep := state.RelationEndpoint{"mysqldb", "ifce-a", "db", state.RoleProvider, state.ScopeGlobal}
1079 blogep := state.RelationEndpoint{"wordpress", "ifce-b", "db", state.RoleRequirer, state.ScopeGlobal}
1080 _, _, err := s.st.AddRelation(blogep, mysqlep)
1081 c.Assert(err, ErrorMatches, `can't add relation between wordpress:db and mysqldb:db`)
1082}
1083
1084func (s *StateSuite) TestAddClientServerRelationTwice(c *C) {
1085 dummy := s.addDummyCharm(c)
1086 // Provider and requirer.
1087 s.st.AddService("mysqldb", dummy)
1088 s.st.AddService("wordpress", dummy)
1089 mysqlep := state.RelationEndpoint{"mysqldb", "blog", "db", state.RoleProvider, state.ScopeGlobal}
1090 blogep := state.RelationEndpoint{"wordpress", "blog", "db", state.RoleRequirer, state.ScopeGlobal}
1091 _, _, err := s.st.AddRelation(blogep, mysqlep)
1092 c.Assert(err, IsNil)
1093 _, _, err = s.st.AddRelation(blogep, mysqlep)
1094 c.Assert(err, ErrorMatches, `relation already exists`)
1095 // Peer.
1096 s.st.AddService("riak", dummy)
1097 riakep := state.RelationEndpoint{"riak", "ring", "cache", state.RolePeer, state.ScopeGlobal}
1098 _, _, err = s.st.AddRelation(riakep)
1099 c.Assert(err, IsNil)
1100 _, _, err = s.st.AddRelation(riakep)
1101 c.Assert(err, ErrorMatches, `relation already exists`)
1102}
1103
1104func (s *StateSuite) TestAddPeerRelationIllegalEndpointNumber(c *C) {
1105 dummy := s.addDummyCharm(c)
1106 s.st.AddService("mysqldb", dummy)
1107 s.st.AddService("wordpress", dummy)
1108 s.st.AddService("riak", dummy)
1109 mysqlep := state.RelationEndpoint{"mysqldb", "ifce", "cache", state.RoleProvider, state.ScopeGlobal}
1110 blogep := state.RelationEndpoint{"wordpress", "ifce", "cache", state.RoleRequirer, state.ScopeGlobal}
1111 riakep := state.RelationEndpoint{"riak", "blog", "cache", state.RolePeer, state.ScopeGlobal}
1112 _, _, err := s.st.AddRelation()
1113 c.Assert(err, ErrorMatches, `can't add relations between 0 services`)
1114 _, _, err = s.st.AddRelation(mysqlep, blogep, riakep)
1115 c.Assert(err, ErrorMatches, `can't add relations between 3 services`)
1116}

Subscribers

People subscribed via source and target branches