Merge lp:~frankban/juju-core/bug-1147138-annotations-api into lp:~juju/juju-core/trunk

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 980
Proposed branch: lp:~frankban/juju-core/bug-1147138-annotations-api
Merge into: lp:~juju/juju-core/trunk
Diff against target: 543 lines (+254/-29)
12 files modified
state/api/apiclient.go (+23/-0)
state/api/params/params.go (+17/-0)
state/apiserver/api_test.go (+123/-4)
state/apiserver/apiserver.go (+24/-6)
state/machine_test.go (+1/-1)
state/service.go (+19/-0)
state/service_test.go (+4/-0)
state/state.go (+12/-5)
state/state_test.go (+17/-11)
state/unit_test.go (+1/-1)
state/user.go (+12/-0)
state/user_test.go (+1/-1)
To merge this branch: bzr merge lp:~frankban/juju-core/bug-1147138-annotations-api
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+152037@code.launchpad.net

Description of the change

Annotations API functionality

Annotations are now exposed through the API via 'GetAnnotations' and
'SetAnnotation'. These allow a user to set an annotation on an Entity (service,
'unit', 'machine', with environment coming in a future branch), and retrieve
all annotations on an entity. Tests were added around the added functionality.
Note that now the service is an Entity, as suggested by Roger.

https://codereview.appspot.com/7526043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+152037_code.launchpad.net,

Message:
Please take a look.

Description:
Annotations API functionality

Annotations are now exposed through the API via 'GetAnnotations' and
'SetAnnotation'. These allow a user to set an annotation on an Entity
(service,
'unit', 'machine', with environment coming in a future branch), and
retrieve
all annotations on an entity. Tests were added around the added
functionality.
Note that now the service is an Entity, as suggested by Roger.

https://code.launchpad.net/~frankban/juju-core/bug-1147138-annotations-api/+merge/152037

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/7526043/

Affected files:
   A [revision details]
   M state/api/apiclient.go
   M state/apiserver/api_test.go
   M state/apiserver/apiserver.go
   M state/machine_test.go
   M state/service.go
   M state/service_test.go
   M state/state.go
   M state/state_test.go
   M state/unit_test.go
   M state/user.go
   M state/user_test.go

Revision history for this message
Francesco Banconi (frankban) wrote :
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Overall LGTM, just a couple of comments.

https://codereview.appspot.com/7526043/diff/2001/state/api/apiclient.go
File state/api/apiclient.go (right):

https://codereview.appspot.com/7526043/diff/2001/state/api/apiclient.go#newcode127
state/api/apiclient.go:127: args := params.GetAnnotations{Id: id}
can you skip the keys here? If it's only one {id} will work.

https://codereview.appspot.com/7526043/diff/2001/state/api/apiclient.go#newcode138
state/api/apiclient.go:138: args := params.SetAnnotation{Id: id, Key:
key, Value: value}
similarly - see above

https://codereview.appspot.com/7526043/

Revision history for this message
Francesco Banconi (frankban) wrote :

Please take a look.

https://codereview.appspot.com/7526043/diff/2001/state/api/apiclient.go
File state/api/apiclient.go (right):

https://codereview.appspot.com/7526043/diff/2001/state/api/apiclient.go#newcode127
state/api/apiclient.go:127: args := params.GetAnnotations{Id: id}
On 2013/03/07 14:28:01, dimitern wrote:
> can you skip the keys here? If it's only one {id} will work.

Done.

https://codereview.appspot.com/7526043/diff/2001/state/api/apiclient.go#newcode138
state/api/apiclient.go:138: args := params.SetAnnotation{Id: id, Key:
key, Value: value}
On 2013/03/07 14:28:01, dimitern wrote:
> similarly - see above

Done.

https://codereview.appspot.com/7526043/

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (3.3 KiB)

LGTM with the below trivial points addressed.
really good stuff, thanks!

https://codereview.appspot.com/7526043/diff/8001/state/api/apiclient.go
File state/api/apiclient.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/api/apiclient.go#newcode146
state/api/apiclient.go:146: func (c *Client) GetAnnotations(id string)
(*Annotations, error) {
i think there's not really a point in having a new type for annotations
here, just to contain a map.
i'd just return the map directly, like the state Annotations method
does.

something like this, perhaps? (note the variable name change,
just to make it obvious what kind of id we're talking about here).

// SetAnnotations returns annotations that have been
// set on the given entity.
func (c *Client) SetAnnotations(entityId string) (map[string]string,
error)

https://codereview.appspot.com/7526043/diff/8001/state/api/apiclient.go#newcode156
state/api/apiclient.go:156: // SetAnnotation stores an annotation about
a given entity.
// SetAnnotation sets the annotation with the given key
// on the given entity to the given value.
// Currently annotations are supported on machines,
// services, units and the environment itself.
func (c *Client) SetAnnotation(entityId, key, value string) error {

?

https://codereview.appspot.com/7526043/diff/8001/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/api/params/params.go#newcode76
state/api/params/params.go:76: Id string
s/Id/EntityId/

https://codereview.appspot.com/7526043/diff/8001/state/api/params/params.go#newcode81
state/api/params/params.go:81: Id string
s/Id/EntityId/

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go
File state/apiserver/api_test.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode301
state/apiserver/api_test.go:301: return func() {}, nil
the returned function should reset the annotation to its original value
- all these op* functions should leave the state unchanged.

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode641
state/apiserver/api_test.go:641: // Annotations are correctly set.
s/Annotations/Check annotations/ ?

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode642
state/apiserver/api_test.go:642: entity.Refresh()
check error

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode647
state/apiserver/api_test.go:647: // Annotations are correctly returned.
s/Annotations/Check annotations/ ?

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode648
state/apiserver/api_test.go:648: entity.Refresh()
check error

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode652
state/apiserver/api_test.go:652: err = entity.SetAnnotation(key, "")
check error

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/apiserver.go
File state/apiserver/apiserver.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/apiserver.go#newcode308
state/apiserver/apiserver.go:308: func (c *srvClient)
GetA...

Read more...

Revision history for this message
Francesco Banconi (frankban) wrote :
Download full text (4.2 KiB)

*** Submitted:

Annotations API functionality

Annotations are now exposed through the API via 'GetAnnotations' and
'SetAnnotation'. These allow a user to set an annotation on an Entity
(service,
'unit', 'machine', with environment coming in a future branch), and
retrieve
all annotations on an entity. Tests were added around the added
functionality.
Note that now the service is an Entity, as suggested by Roger.

R=dimitern, rog
CC=
https://codereview.appspot.com/7526043

https://codereview.appspot.com/7526043/diff/8001/state/api/apiclient.go
File state/api/apiclient.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/api/apiclient.go#newcode146
state/api/apiclient.go:146: func (c *Client) GetAnnotations(id string)
(*Annotations, error) {
On 2013/03/08 20:52:37, rog wrote:
> i think there's not really a point in having a new type for
annotations here,
> just to contain a map.
> i'd just return the map directly, like the state Annotations method
does.

> something like this, perhaps? (note the variable name change,
> just to make it obvious what kind of id we're talking about here).

> // SetAnnotations returns annotations that have been
> // set on the given entity.
> func (c *Client) SetAnnotations(entityId string) (map[string]string,
error)

Done.

https://codereview.appspot.com/7526043/diff/8001/state/api/apiclient.go#newcode156
state/api/apiclient.go:156: // SetAnnotation stores an annotation about
a given entity.
On 2013/03/08 20:52:37, rog wrote:
> // SetAnnotation sets the annotation with the given key
> // on the given entity to the given value.
> // Currently annotations are supported on machines,
> // services, units and the environment itself.
> func (c *Client) SetAnnotation(entityId, key, value string) error {

> ?

Done.

https://codereview.appspot.com/7526043/diff/8001/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/api/params/params.go#newcode76
state/api/params/params.go:76: Id string
On 2013/03/08 20:52:37, rog wrote:
> s/Id/EntityId/

Done.

https://codereview.appspot.com/7526043/diff/8001/state/api/params/params.go#newcode81
state/api/params/params.go:81: Id string
On 2013/03/08 20:52:37, rog wrote:
> s/Id/EntityId/

Done.

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go
File state/apiserver/api_test.go (right):

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode301
state/apiserver/api_test.go:301: return func() {}, nil
On 2013/03/08 20:52:37, rog wrote:
> the returned function should reset the annotation to its original
value - all
> these op* functions should leave the state unchanged.

Done.

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode641
state/apiserver/api_test.go:641: // Annotations are correctly set.
On 2013/03/08 20:52:37, rog wrote:
> s/Annotations/Check annotations/ ?

Done.

https://codereview.appspot.com/7526043/diff/8001/state/apiserver/api_test.go#newcode642
state/apiserver/api_test.go:642: entity.Refresh()
On 2013/03/08 20:52:37, rog wrote:
> check error

Done.

https://codereview.appspot.com/7526043/diff/8001...

Read more...

Revision history for this message
Francesco Banconi (frankban) wrote :

Hi Dimiter and Roger,
thanks for the reviews.

https://codereview.appspot.com/7526043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'state/api/apiclient.go'
--- state/api/apiclient.go 2013-03-08 20:52:12 +0000
+++ state/api/apiclient.go 2013-03-08 21:34:19 +0000
@@ -179,6 +179,29 @@
179 return newAllWatcher(c, &info.AllWatcherId), nil179 return newAllWatcher(c, &info.AllWatcherId), nil
180}180}
181181
182// GetAnnotations returns annotations that have been set on the given entity.
183func (c *Client) GetAnnotations(entityId string) (map[string]string, error) {
184 args := params.GetAnnotations{entityId}
185 ann := new(params.GetAnnotationsResults)
186 err := c.st.client.Call("Client", "", "GetAnnotations", args, ann)
187 if err != nil {
188 return nil, clientError(err)
189 }
190 return ann.Annotations, nil
191}
192
193// SetAnnotation sets the annotation with the given key on the given entity to
194// the given value. Currently annotations are supported on machines, services,
195// units and the environment itself.
196func (c *Client) SetAnnotation(entityId, key, value string) error {
197 args := params.SetAnnotation{entityId, key, value}
198 err := c.st.client.Call("Client", "", "SetAnnotation", args, nil)
199 if err != nil {
200 return clientError(err)
201 }
202 return nil
203}
204
182// Machine returns a reference to the machine with the given id.205// Machine returns a reference to the machine with the given id.
183func (st *State) Machine(id string) (*Machine, error) {206func (st *State) Machine(id string) (*Machine, error) {
184 m := &Machine{207 m := &Machine{
185208
=== modified file 'state/api/params/params.go'
--- state/api/params/params.go 2013-03-08 20:36:42 +0000
+++ state/api/params/params.go 2013-03-08 21:34:19 +0000
@@ -93,6 +93,23 @@
93 // future.93 // future.
94}94}
9595
96// GetAnnotationsResults holds annotations associated with an entity.
97type GetAnnotationsResults struct {
98 Annotations map[string]string
99}
100
101// GetAnnotations stores parameters for making the GetAnnotations call.
102type GetAnnotations struct {
103 EntityId string
104}
105
106// SetAnnotation stores parameters for making the SetAnnotation call.
107type SetAnnotation struct {
108 EntityId string
109 Key string
110 Value string
111}
112
96// Delta holds details of a change to the environment.113// Delta holds details of a change to the environment.
97type Delta struct {114type Delta struct {
98 // If Removed is true, the entity has been removed;115 // If Removed is true, the entity has been removed;
99116
=== modified file 'state/apiserver/api_test.go'
--- state/apiserver/api_test.go 2013-03-08 20:36:42 +0000
+++ state/apiserver/api_test.go 2013-03-08 21:34:19 +0000
@@ -85,6 +85,14 @@
85 op: opClientServiceUnexpose,85 op: opClientServiceUnexpose,
86 allow: []string{"user-admin", "user-other"},86 allow: []string{"user-admin", "user-other"},
87}, {87}, {
88 about: "Client.GetAnnotations",
89 op: opClientGetAnnotations,
90 allow: []string{"user-admin", "user-other"},
91}, {
92 about: "Client.SetAnnotation",
93 op: opClientSetAnnotation,
94 allow: []string{"user-admin", "user-other"},
95}, {
88 about: "Client.ServiceAddUnits",96 about: "Client.ServiceAddUnits",
89 op: opClientServiceAddUnits,97 op: opClientServiceAddUnits,
90 allow: []string{"user-admin", "user-other"},98 allow: []string{"user-admin", "user-other"},
@@ -282,6 +290,27 @@
282 return func() {}, nil290 return func() {}, nil
283}291}
284292
293func opClientGetAnnotations(c *C, st *api.State) (func(), error) {
294 ann, err := st.Client().GetAnnotations("service-wordpress")
295 if err != nil {
296 return func() {}, err
297 }
298 c.Assert(err, IsNil)
299 c.Assert(ann, DeepEquals, make(map[string]string))
300 return func() {}, nil
301}
302
303func opClientSetAnnotation(c *C, st *api.State) (func(), error) {
304 err := st.Client().SetAnnotation("service-wordpress", "key", "value")
305 if err != nil {
306 return func() {}, err
307 }
308 c.Assert(err, IsNil)
309 return func() {
310 st.Client().SetAnnotation("service-wordpress", "key", "")
311 }, nil
312}
313
285func opClientServiceAddUnits(c *C, st *api.State) (func(), error) {314func opClientServiceAddUnits(c *C, st *api.State) (func(), error) {
286 // This test only checks that the call is made without error, ensuring the315 // This test only checks that the call is made without error, ensuring the
287 // signatures match.316 // signatures match.
@@ -353,7 +382,7 @@
353// environment manager (bootstrap machine), so is382// environment manager (bootstrap machine), so is
354// hopefully easier to remember as such.383// hopefully easier to remember as such.
355func (s *suite) setUpScenario(c *C) (entities []string) {384func (s *suite) setUpScenario(c *C) (entities []string) {
356 add := func(e state.AuthEntity) {385 add := func(e state.Entity) {
357 entities = append(entities, e.EntityName())386 entities = append(entities, e.EntityName())
358 }387 }
359 u, err := s.State.User("admin")388 u, err := s.State.User("admin")
@@ -427,9 +456,10 @@
427 return456 return
428}457}
429458
430// AuthEntity is the same as state.AuthEntity but459// AuthEntity is the same as state.Entity but
431// without PasswordValid, which is implemented460// without PasswordValid and annotations handling
432// by state entities but not by api entities.461// which are implemented by state entities but not
462// by api entities.
433type AuthEntity interface {463type AuthEntity interface {
434 EntityName() string464 EntityName() string
435 SetPassword(pass string) error465 SetPassword(pass string) error
@@ -579,6 +609,95 @@
579 c.Assert(info.ProviderType, Equals, conf.Type())609 c.Assert(info.ProviderType, Equals, conf.Type())
580}610}
581611
612var clientAnnotationsTests = []struct {
613 about string
614 initial map[string]string
615 input map[string]string
616 expected map[string]string
617 err string
618}{
619 {
620 about: "test setting an annotation",
621 input: map[string]string{"mykey": "myvalue"},
622 expected: map[string]string{"mykey": "myvalue"},
623 },
624 {
625 about: "test setting multiple annotations",
626 input: map[string]string{"key1": "value1", "key2": "value2"},
627 expected: map[string]string{"key1": "value1", "key2": "value2"},
628 },
629 {
630 about: "test overriding annotations",
631 initial: map[string]string{"mykey": "myvalue"},
632 input: map[string]string{"mykey": "another-value"},
633 expected: map[string]string{"mykey": "another-value"},
634 },
635 {
636 about: "test setting an invalid annotation",
637 input: map[string]string{"invalid.key": "myvalue"},
638 err: `invalid key "invalid.key"`,
639 },
640}
641
642func (s *suite) TestClientAnnotations(c *C) {
643 // Set up entities.
644 service, err := s.State.AddService("dummy", s.AddTestingCharm(c, "dummy"))
645 c.Assert(err, IsNil)
646 unit, err := service.AddUnit()
647 c.Assert(err, IsNil)
648 machine, err := s.State.AddMachine("series", state.JobHostUnits)
649 c.Assert(err, IsNil)
650 entities := []state.Entity{service, unit, machine}
651 for i, t := range clientAnnotationsTests {
652 loop:
653 for _, entity := range entities {
654 id := entity.EntityName()
655 c.Logf("test %d. %s. entity %s", i, t.about, id)
656 // Set initial entity annotations.
657 for key, value := range t.initial {
658 err := entity.SetAnnotation(key, value)
659 c.Assert(err, IsNil)
660 }
661 // Add annotations using the API call.
662 for key, value := range t.input {
663 err := s.APIState.Client().SetAnnotation(id, key, value)
664 if t.err != "" {
665 c.Assert(err, ErrorMatches, t.err)
666 continue loop
667 }
668 c.Assert(err, IsNil)
669 }
670 // Check annotations are correctly set.
671 err := entity.Refresh()
672 c.Assert(err, IsNil)
673 c.Assert(entity.Annotations(), DeepEquals, t.expected)
674 // Retrieve annotations using the API call.
675 ann, err := s.APIState.Client().GetAnnotations(id)
676 c.Assert(err, IsNil)
677 // Check annotations are correctly returned.
678 err = entity.Refresh()
679 c.Assert(err, IsNil)
680 c.Assert(ann, DeepEquals, entity.Annotations())
681 // Clean up annotations on the current entity.
682 for key := range entity.Annotations() {
683 err = entity.SetAnnotation(key, "")
684 c.Assert(err, IsNil)
685 }
686 }
687 }
688}
689
690func (s *suite) TestClientAnnotationsBadEntity(c *C) {
691 bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"}
692 expected := `invalid entity name ".*"`
693 for _, id := range bad {
694 err := s.APIState.Client().SetAnnotation(id, "mykey", "myvalue")
695 c.Assert(err, ErrorMatches, expected)
696 _, err = s.APIState.Client().GetAnnotations(id)
697 c.Assert(err, ErrorMatches, expected)
698 }
699}
700
582func (s *suite) TestMachineLogin(c *C) {701func (s *suite) TestMachineLogin(c *C) {
583 stm, err := s.State.AddMachine("series", state.JobHostUnits)702 stm, err := s.State.AddMachine("series", state.JobHostUnits)
584 c.Assert(err, IsNil)703 c.Assert(err, IsNil)
585704
=== modified file 'state/apiserver/apiserver.go'
--- state/apiserver/apiserver.go 2013-03-08 20:36:42 +0000
+++ state/apiserver/apiserver.go 2013-03-08 21:34:19 +0000
@@ -346,6 +346,24 @@
346 return info, nil346 return info, nil
347}347}
348348
349// GetAnnotations returns annotations about a given entity.
350func (c *srvClient) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) {
351 entity, err := c.root.srv.state.Entity(args.EntityId)
352 if err != nil {
353 return params.GetAnnotationsResults{}, err
354 }
355 return params.GetAnnotationsResults{Annotations: entity.Annotations()}, nil
356}
357
358// SetAnnotation stores an annotation about a given entity.
359func (c *srvClient) SetAnnotation(args params.SetAnnotation) error {
360 entity, err := c.root.srv.state.Entity(args.EntityId)
361 if err != nil {
362 return err
363 }
364 return entity.SetAnnotation(args.Key, args.Value)
365}
366
349// Login logs in with the provided credentials.367// Login logs in with the provided credentials.
350// All subsequent requests on the connection will368// All subsequent requests on the connection will
351// act as the authenticated user.369// act as the authenticated user.
@@ -370,7 +388,7 @@
370 }, nil388 }, nil
371}389}
372390
373func setPassword(e state.AuthEntity, password string) error {391func setPassword(e state.Entity, password string) error {
374 // Catch expected common case of mispelled392 // Catch expected common case of mispelled
375 // or missing Password parameter.393 // or missing Password parameter.
376 if password == "" {394 if password == "" {
@@ -433,14 +451,14 @@
433// its methods concurrently.451// its methods concurrently.
434type authUser struct {452type authUser struct {
435 mu sync.Mutex453 mu sync.Mutex
436 _entity state.AuthEntity // logged-in entity (access only when mu is locked)454 _entity state.Entity // logged-in entity (access only when mu is locked)
437}455}
438456
439// login authenticates as entity with the given name,.457// login authenticates as entity with the given name,.
440func (u *authUser) login(st *state.State, entityName, password string) error {458func (u *authUser) login(st *state.State, entityName, password string) error {
441 u.mu.Lock()459 u.mu.Lock()
442 defer u.mu.Unlock()460 defer u.mu.Unlock()
443 entity, err := st.AuthEntity(entityName)461 entity, err := st.Entity(entityName)
444 if err != nil && !state.IsNotFound(err) {462 if err != nil && !state.IsNotFound(err) {
445 return err463 return err
446 }464 }
@@ -463,7 +481,7 @@
463// entity returns the currently logged-in entity, or nil if not481// entity returns the currently logged-in entity, or nil if not
464// currently logged on. The returned entity should not be modified482// currently logged on. The returned entity should not be modified
465// because it may be used concurrently.483// because it may be used concurrently.
466func (u *authUser) entity() state.AuthEntity {484func (u *authUser) entity() state.Entity {
467 u.mu.Lock()485 u.mu.Lock()
468 defer u.mu.Unlock()486 defer u.mu.Unlock()
469 return u._entity487 return u._entity
@@ -471,7 +489,7 @@
471489
472// isMachineWithJob returns whether the given entity is a machine that490// isMachineWithJob returns whether the given entity is a machine that
473// is configured to run the given job.491// is configured to run the given job.
474func isMachineWithJob(e state.AuthEntity, j state.MachineJob) bool {492func isMachineWithJob(e state.Entity, j state.MachineJob) bool {
475 m, ok := e.(*state.Machine)493 m, ok := e.(*state.Machine)
476 if !ok {494 if !ok {
477 return false495 return false
@@ -485,7 +503,7 @@
485}503}
486504
487// isAgent returns whether the given entity is an agent.505// isAgent returns whether the given entity is an agent.
488func isAgent(e state.AuthEntity) bool {506func isAgent(e state.Entity) bool {
489 _, isUser := e.(*state.User)507 _, isUser := e.(*state.User)
490 return !isUser508 return !isUser
491}509}
492510
=== modified file 'state/machine_test.go'
--- state/machine_test.go 2013-03-05 15:26:26 +0000
+++ state/machine_test.go 2013-03-08 21:34:19 +0000
@@ -111,7 +111,7 @@
111}111}
112112
113func (s *MachineSuite) TestSetPassword(c *C) {113func (s *MachineSuite) TestSetPassword(c *C) {
114 testSetPassword(c, func() (state.AuthEntity, error) {114 testSetPassword(c, func() (state.Entity, error) {
115 return s.State.Machine(s.machine.Id())115 return s.State.Machine(s.machine.Id())
116 })116 })
117}117}
118118
=== modified file 'state/service.go'
--- state/service.go 2013-03-08 17:56:51 +0000
+++ state/service.go 2013-03-08 21:34:19 +0000
@@ -52,6 +52,25 @@
52 return s.doc.Name52 return s.doc.Name
53}53}
5454
55// EntityName returns a name identifying the service that is safe to use
56// as a file name. The returned name will be different from other
57// EntityName values returned by any other entities from the same state.
58func (s *Service) EntityName() string {
59 return "service-" + s.Name()
60}
61
62// PasswordValid currently just returns false. Implemented here so that
63// a service can be used as an Entity.
64func (s *Service) PasswordValid(password string) bool {
65 return false
66}
67
68// SetPassword currently just returns an error. Implemented here so that
69// a service can be used as an Entity.
70func (s *Service) SetPassword(password string) error {
71 return fmt.Errorf("cannot set password of service")
72}
73
55// Annotations returns the service annotations.74// Annotations returns the service annotations.
56func (s *Service) Annotations() map[string]string {75func (s *Service) Annotations() map[string]string {
57 return s.doc.Annotations76 return s.doc.Annotations
5877
=== modified file 'state/service_test.go'
--- state/service_test.go 2013-03-08 19:55:44 +0000
+++ state/service_test.go 2013-03-08 21:34:19 +0000
@@ -64,6 +64,10 @@
64 }64 }
65}65}
6666
67func (s *ServiceSuite) TestEntityName(c *C) {
68 c.Assert(s.mysql.EntityName(), Equals, "service-mysql")
69}
70
67func (s *ServiceSuite) TestMysqlEndpoints(c *C) {71func (s *ServiceSuite) TestMysqlEndpoints(c *C) {
68 _, err := s.mysql.Endpoint("mysql")72 _, err := s.mysql.Endpoint("mysql")
69 c.Assert(err, ErrorMatches, `service "mysql" has no "mysql" relation`)73 c.Assert(err, ErrorMatches, `service "mysql" has no "mysql" relation`)
7074
=== modified file 'state/state.go'
--- state/state.go 2013-03-08 19:27:06 +0000
+++ state/state.go 2013-03-08 21:34:19 +0000
@@ -274,17 +274,19 @@
274 return newMachine(st, mdoc), nil274 return newMachine(st, mdoc), nil
275}275}
276276
277// AuthEntity represents an entity that has277// Entity represents an entity capabable of handling password authentication
278// a password that can be authenticated against.278// and annotations.
279type AuthEntity interface {279type Entity interface {
280 EntityName() string280 EntityName() string
281 SetPassword(pass string) error281 SetPassword(pass string) error
282 PasswordValid(pass string) bool282 PasswordValid(pass string) bool
283 Refresh() error283 Refresh() error
284 SetAnnotation(key, value string) error
285 Annotations() map[string]string
284}286}
285287
286// AuthEntity returns the entity for the given name.288// Entity returns the entity for the given name.
287func (st *State) AuthEntity(entityName string) (AuthEntity, error) {289func (st *State) Entity(entityName string) (Entity, error) {
288 i := strings.Index(entityName, "-")290 i := strings.Index(entityName, "-")
289 if i <= 0 || i >= len(entityName)-1 {291 if i <= 0 || i >= len(entityName)-1 {
290 return nil, fmt.Errorf("invalid entity name %q", entityName)292 return nil, fmt.Errorf("invalid entity name %q", entityName)
@@ -308,6 +310,11 @@
308 return st.Unit(name)310 return st.Unit(name)
309 case "user":311 case "user":
310 return st.User(id)312 return st.User(id)
313 case "service":
314 if !IsServiceName(id) {
315 return nil, fmt.Errorf("invalid entity name %q", entityName)
316 }
317 return st.Service(id)
311 }318 }
312 return nil, fmt.Errorf("invalid entity name %q", entityName)319 return nil, fmt.Errorf("invalid entity name %q", entityName)
313}320}
314321
=== modified file 'state/state_test.go'
--- state/state_test.go 2013-03-07 16:05:22 +0000
+++ state/state_test.go 2013-03-08 21:34:19 +0000
@@ -1194,7 +1194,7 @@
1194 c.Assert(err, ErrorMatches, "no reachable servers")1194 c.Assert(err, ErrorMatches, "no reachable servers")
1195}1195}
11961196
1197func testSetPassword(c *C, getEntity func() (state.AuthEntity, error)) {1197func testSetPassword(c *C, getEntity func() (state.Entity, error)) {
1198 e, err := getEntity()1198 e, err := getEntity()
1199 c.Assert(err, IsNil)1199 c.Assert(err, IsNil)
12001200
@@ -1317,30 +1317,30 @@
1317 c.Assert(err, IsNil)1317 c.Assert(err, IsNil)
1318}1318}
13191319
1320func (s *StateSuite) TestAuthEntity(c *C) {1320func (s *StateSuite) TestEntity(c *C) {
1321 bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo"}1321 bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"}
1322 for _, name := range bad {1322 for _, name := range bad {
1323 e, err := s.State.AuthEntity(name)1323 e, err := s.State.Entity(name)
1324 c.Check(e, IsNil)1324 c.Check(e, IsNil)
1325 c.Assert(err, ErrorMatches, `invalid entity name ".*"`)1325 c.Assert(err, ErrorMatches, `invalid entity name ".*"`)
1326 }1326 }
13271327
1328 e, err := s.State.AuthEntity("machine-1234")1328 e, err := s.State.Entity("machine-1234")
1329 c.Check(e, IsNil)1329 c.Check(e, IsNil)
1330 c.Assert(err, ErrorMatches, `machine 1234 not found`)1330 c.Assert(err, ErrorMatches, `machine 1234 not found`)
1331 c.Assert(state.IsNotFound(err), Equals, true)1331 c.Assert(state.IsNotFound(err), Equals, true)
13321332
1333 e, err = s.State.AuthEntity("unit-foo-654")1333 e, err = s.State.Entity("unit-foo-654")
1334 c.Check(e, IsNil)1334 c.Check(e, IsNil)
1335 c.Assert(err, ErrorMatches, `unit "foo/654" not found`)1335 c.Assert(err, ErrorMatches, `unit "foo/654" not found`)
1336 c.Assert(state.IsNotFound(err), Equals, true)1336 c.Assert(state.IsNotFound(err), Equals, true)
13371337
1338 e, err = s.State.AuthEntity("unit-foo-bar-654")1338 e, err = s.State.Entity("unit-foo-bar-654")
1339 c.Check(e, IsNil)1339 c.Check(e, IsNil)
1340 c.Assert(err, ErrorMatches, `unit "foo-bar/654" not found`)1340 c.Assert(err, ErrorMatches, `unit "foo-bar/654" not found`)
1341 c.Assert(state.IsNotFound(err), Equals, true)1341 c.Assert(state.IsNotFound(err), Equals, true)
13421342
1343 e, err = s.State.AuthEntity("user-arble")1343 e, err = s.State.Entity("user-arble")
1344 c.Check(e, IsNil)1344 c.Check(e, IsNil)
1345 c.Assert(err, ErrorMatches, `user "arble" not found`)1345 c.Assert(err, ErrorMatches, `user "arble" not found`)
1346 c.Assert(state.IsNotFound(err), Equals, true)1346 c.Assert(state.IsNotFound(err), Equals, true)
@@ -1348,17 +1348,23 @@
1348 m, err := s.State.AddMachine("series", state.JobHostUnits)1348 m, err := s.State.AddMachine("series", state.JobHostUnits)
1349 c.Assert(err, IsNil)1349 c.Assert(err, IsNil)
13501350
1351 e, err = s.State.AuthEntity(m.EntityName())1351 e, err = s.State.Entity(m.EntityName())
1352 c.Assert(err, IsNil)1352 c.Assert(err, IsNil)
1353 c.Assert(e, FitsTypeOf, m)1353 c.Assert(e, FitsTypeOf, m)
1354 c.Assert(e.EntityName(), Equals, m.EntityName())1354 c.Assert(e.EntityName(), Equals, m.EntityName())
13551355
1356 svc, err := s.State.AddService("ser-vice1", s.AddTestingCharm(c, "dummy"))1356 svc, err := s.State.AddService("ser-vice1", s.AddTestingCharm(c, "dummy"))
1357 c.Assert(err, IsNil)1357 c.Assert(err, IsNil)
1358
1359 service, err := s.State.Entity(svc.EntityName())
1360 c.Assert(err, IsNil)
1361 c.Assert(service, FitsTypeOf, svc)
1362 c.Assert(service.EntityName(), Equals, svc.EntityName())
1363
1358 u, err := svc.AddUnit()1364 u, err := svc.AddUnit()
1359 c.Assert(err, IsNil)1365 c.Assert(err, IsNil)
13601366
1361 e, err = s.State.AuthEntity(u.EntityName())1367 e, err = s.State.Entity(u.EntityName())
1362 c.Assert(err, IsNil)1368 c.Assert(err, IsNil)
1363 c.Assert(e, FitsTypeOf, u)1369 c.Assert(e, FitsTypeOf, u)
1364 c.Assert(e.EntityName(), Equals, u.EntityName())1370 c.Assert(e.EntityName(), Equals, u.EntityName())
@@ -1366,7 +1372,7 @@
1366 user, err := s.State.AddUser("arble", "pass")1372 user, err := s.State.AddUser("arble", "pass")
1367 c.Assert(err, IsNil)1373 c.Assert(err, IsNil)
13681374
1369 e, err = s.State.AuthEntity(user.EntityName())1375 e, err = s.State.Entity(user.EntityName())
1370 c.Assert(err, IsNil)1376 c.Assert(err, IsNil)
1371 c.Assert(e, FitsTypeOf, user)1377 c.Assert(e, FitsTypeOf, user)
1372 c.Assert(e.EntityName(), Equals, user.EntityName())1378 c.Assert(e.EntityName(), Equals, user.EntityName())
13731379
=== modified file 'state/unit_test.go'
--- state/unit_test.go 2013-03-05 15:26:26 +0000
+++ state/unit_test.go 2013-03-08 21:34:19 +0000
@@ -174,7 +174,7 @@
174}174}
175175
176func (s *UnitSuite) TestSetPassword(c *C) {176func (s *UnitSuite) TestSetPassword(c *C) {
177 testSetPassword(c, func() (state.AuthEntity, error) {177 testSetPassword(c, func() (state.Entity, error) {
178 return s.State.Unit(s.unit.Name())178 return s.State.Unit(s.unit.Name())
179 })179 })
180}180}
181181
=== modified file 'state/user.go'
--- state/user.go 2013-02-18 17:03:00 +0000
+++ state/user.go 2013-03-08 21:34:19 +0000
@@ -79,6 +79,18 @@
79 return "user-" + u.doc.Name79 return "user-" + u.doc.Name
80}80}
8181
82// Annotations currently just returns an empty map. Implemented here so that
83// a user can be used as an Entity.
84func (u *User) Annotations() map[string]string {
85 return map[string]string{}
86}
87
88// SetAnnotation currently just returns an error. Implemented here so that
89// a user can be used as an Entity.
90func (u *User) SetAnnotation(key, value string) error {
91 return fmt.Errorf("cannot set annotation of user")
92}
93
82// SetPassword sets the password associated with the user.94// SetPassword sets the password associated with the user.
83func (u *User) SetPassword(password string) error {95func (u *User) SetPassword(password string) error {
84 hp := trivial.PasswordHash(password)96 hp := trivial.PasswordHash(password)
8597
=== modified file 'state/user_test.go'
--- state/user_test.go 2013-02-06 19:57:33 +0000
+++ state/user_test.go 2013-03-08 21:34:19 +0000
@@ -43,7 +43,7 @@
43 u, err := s.State.AddUser("someuser", "")43 u, err := s.State.AddUser("someuser", "")
44 c.Assert(err, IsNil)44 c.Assert(err, IsNil)
4545
46 testSetPassword(c, func() (state.AuthEntity, error) {46 testSetPassword(c, func() (state.Entity, error) {
47 return s.State.User(u.Name())47 return s.State.User(u.Name())
48 })48 })
49}49}

Subscribers

People subscribed via source and target branches