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
1=== modified file 'state/api/apiclient.go'
2--- state/api/apiclient.go 2013-03-08 20:52:12 +0000
3+++ state/api/apiclient.go 2013-03-08 21:34:19 +0000
4@@ -179,6 +179,29 @@
5 return newAllWatcher(c, &info.AllWatcherId), nil
6 }
7
8+// GetAnnotations returns annotations that have been set on the given entity.
9+func (c *Client) GetAnnotations(entityId string) (map[string]string, error) {
10+ args := params.GetAnnotations{entityId}
11+ ann := new(params.GetAnnotationsResults)
12+ err := c.st.client.Call("Client", "", "GetAnnotations", args, ann)
13+ if err != nil {
14+ return nil, clientError(err)
15+ }
16+ return ann.Annotations, nil
17+}
18+
19+// SetAnnotation sets the annotation with the given key on the given entity to
20+// the given value. Currently annotations are supported on machines, services,
21+// units and the environment itself.
22+func (c *Client) SetAnnotation(entityId, key, value string) error {
23+ args := params.SetAnnotation{entityId, key, value}
24+ err := c.st.client.Call("Client", "", "SetAnnotation", args, nil)
25+ if err != nil {
26+ return clientError(err)
27+ }
28+ return nil
29+}
30+
31 // Machine returns a reference to the machine with the given id.
32 func (st *State) Machine(id string) (*Machine, error) {
33 m := &Machine{
34
35=== modified file 'state/api/params/params.go'
36--- state/api/params/params.go 2013-03-08 20:36:42 +0000
37+++ state/api/params/params.go 2013-03-08 21:34:19 +0000
38@@ -93,6 +93,23 @@
39 // future.
40 }
41
42+// GetAnnotationsResults holds annotations associated with an entity.
43+type GetAnnotationsResults struct {
44+ Annotations map[string]string
45+}
46+
47+// GetAnnotations stores parameters for making the GetAnnotations call.
48+type GetAnnotations struct {
49+ EntityId string
50+}
51+
52+// SetAnnotation stores parameters for making the SetAnnotation call.
53+type SetAnnotation struct {
54+ EntityId string
55+ Key string
56+ Value string
57+}
58+
59 // Delta holds details of a change to the environment.
60 type Delta struct {
61 // If Removed is true, the entity has been removed;
62
63=== modified file 'state/apiserver/api_test.go'
64--- state/apiserver/api_test.go 2013-03-08 20:36:42 +0000
65+++ state/apiserver/api_test.go 2013-03-08 21:34:19 +0000
66@@ -85,6 +85,14 @@
67 op: opClientServiceUnexpose,
68 allow: []string{"user-admin", "user-other"},
69 }, {
70+ about: "Client.GetAnnotations",
71+ op: opClientGetAnnotations,
72+ allow: []string{"user-admin", "user-other"},
73+}, {
74+ about: "Client.SetAnnotation",
75+ op: opClientSetAnnotation,
76+ allow: []string{"user-admin", "user-other"},
77+}, {
78 about: "Client.ServiceAddUnits",
79 op: opClientServiceAddUnits,
80 allow: []string{"user-admin", "user-other"},
81@@ -282,6 +290,27 @@
82 return func() {}, nil
83 }
84
85+func opClientGetAnnotations(c *C, st *api.State) (func(), error) {
86+ ann, err := st.Client().GetAnnotations("service-wordpress")
87+ if err != nil {
88+ return func() {}, err
89+ }
90+ c.Assert(err, IsNil)
91+ c.Assert(ann, DeepEquals, make(map[string]string))
92+ return func() {}, nil
93+}
94+
95+func opClientSetAnnotation(c *C, st *api.State) (func(), error) {
96+ err := st.Client().SetAnnotation("service-wordpress", "key", "value")
97+ if err != nil {
98+ return func() {}, err
99+ }
100+ c.Assert(err, IsNil)
101+ return func() {
102+ st.Client().SetAnnotation("service-wordpress", "key", "")
103+ }, nil
104+}
105+
106 func opClientServiceAddUnits(c *C, st *api.State) (func(), error) {
107 // This test only checks that the call is made without error, ensuring the
108 // signatures match.
109@@ -353,7 +382,7 @@
110 // environment manager (bootstrap machine), so is
111 // hopefully easier to remember as such.
112 func (s *suite) setUpScenario(c *C) (entities []string) {
113- add := func(e state.AuthEntity) {
114+ add := func(e state.Entity) {
115 entities = append(entities, e.EntityName())
116 }
117 u, err := s.State.User("admin")
118@@ -427,9 +456,10 @@
119 return
120 }
121
122-// AuthEntity is the same as state.AuthEntity but
123-// without PasswordValid, which is implemented
124-// by state entities but not by api entities.
125+// AuthEntity is the same as state.Entity but
126+// without PasswordValid and annotations handling
127+// which are implemented by state entities but not
128+// by api entities.
129 type AuthEntity interface {
130 EntityName() string
131 SetPassword(pass string) error
132@@ -579,6 +609,95 @@
133 c.Assert(info.ProviderType, Equals, conf.Type())
134 }
135
136+var clientAnnotationsTests = []struct {
137+ about string
138+ initial map[string]string
139+ input map[string]string
140+ expected map[string]string
141+ err string
142+}{
143+ {
144+ about: "test setting an annotation",
145+ input: map[string]string{"mykey": "myvalue"},
146+ expected: map[string]string{"mykey": "myvalue"},
147+ },
148+ {
149+ about: "test setting multiple annotations",
150+ input: map[string]string{"key1": "value1", "key2": "value2"},
151+ expected: map[string]string{"key1": "value1", "key2": "value2"},
152+ },
153+ {
154+ about: "test overriding annotations",
155+ initial: map[string]string{"mykey": "myvalue"},
156+ input: map[string]string{"mykey": "another-value"},
157+ expected: map[string]string{"mykey": "another-value"},
158+ },
159+ {
160+ about: "test setting an invalid annotation",
161+ input: map[string]string{"invalid.key": "myvalue"},
162+ err: `invalid key "invalid.key"`,
163+ },
164+}
165+
166+func (s *suite) TestClientAnnotations(c *C) {
167+ // Set up entities.
168+ service, err := s.State.AddService("dummy", s.AddTestingCharm(c, "dummy"))
169+ c.Assert(err, IsNil)
170+ unit, err := service.AddUnit()
171+ c.Assert(err, IsNil)
172+ machine, err := s.State.AddMachine("series", state.JobHostUnits)
173+ c.Assert(err, IsNil)
174+ entities := []state.Entity{service, unit, machine}
175+ for i, t := range clientAnnotationsTests {
176+ loop:
177+ for _, entity := range entities {
178+ id := entity.EntityName()
179+ c.Logf("test %d. %s. entity %s", i, t.about, id)
180+ // Set initial entity annotations.
181+ for key, value := range t.initial {
182+ err := entity.SetAnnotation(key, value)
183+ c.Assert(err, IsNil)
184+ }
185+ // Add annotations using the API call.
186+ for key, value := range t.input {
187+ err := s.APIState.Client().SetAnnotation(id, key, value)
188+ if t.err != "" {
189+ c.Assert(err, ErrorMatches, t.err)
190+ continue loop
191+ }
192+ c.Assert(err, IsNil)
193+ }
194+ // Check annotations are correctly set.
195+ err := entity.Refresh()
196+ c.Assert(err, IsNil)
197+ c.Assert(entity.Annotations(), DeepEquals, t.expected)
198+ // Retrieve annotations using the API call.
199+ ann, err := s.APIState.Client().GetAnnotations(id)
200+ c.Assert(err, IsNil)
201+ // Check annotations are correctly returned.
202+ err = entity.Refresh()
203+ c.Assert(err, IsNil)
204+ c.Assert(ann, DeepEquals, entity.Annotations())
205+ // Clean up annotations on the current entity.
206+ for key := range entity.Annotations() {
207+ err = entity.SetAnnotation(key, "")
208+ c.Assert(err, IsNil)
209+ }
210+ }
211+ }
212+}
213+
214+func (s *suite) TestClientAnnotationsBadEntity(c *C) {
215+ bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"}
216+ expected := `invalid entity name ".*"`
217+ for _, id := range bad {
218+ err := s.APIState.Client().SetAnnotation(id, "mykey", "myvalue")
219+ c.Assert(err, ErrorMatches, expected)
220+ _, err = s.APIState.Client().GetAnnotations(id)
221+ c.Assert(err, ErrorMatches, expected)
222+ }
223+}
224+
225 func (s *suite) TestMachineLogin(c *C) {
226 stm, err := s.State.AddMachine("series", state.JobHostUnits)
227 c.Assert(err, IsNil)
228
229=== modified file 'state/apiserver/apiserver.go'
230--- state/apiserver/apiserver.go 2013-03-08 20:36:42 +0000
231+++ state/apiserver/apiserver.go 2013-03-08 21:34:19 +0000
232@@ -346,6 +346,24 @@
233 return info, nil
234 }
235
236+// GetAnnotations returns annotations about a given entity.
237+func (c *srvClient) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) {
238+ entity, err := c.root.srv.state.Entity(args.EntityId)
239+ if err != nil {
240+ return params.GetAnnotationsResults{}, err
241+ }
242+ return params.GetAnnotationsResults{Annotations: entity.Annotations()}, nil
243+}
244+
245+// SetAnnotation stores an annotation about a given entity.
246+func (c *srvClient) SetAnnotation(args params.SetAnnotation) error {
247+ entity, err := c.root.srv.state.Entity(args.EntityId)
248+ if err != nil {
249+ return err
250+ }
251+ return entity.SetAnnotation(args.Key, args.Value)
252+}
253+
254 // Login logs in with the provided credentials.
255 // All subsequent requests on the connection will
256 // act as the authenticated user.
257@@ -370,7 +388,7 @@
258 }, nil
259 }
260
261-func setPassword(e state.AuthEntity, password string) error {
262+func setPassword(e state.Entity, password string) error {
263 // Catch expected common case of mispelled
264 // or missing Password parameter.
265 if password == "" {
266@@ -433,14 +451,14 @@
267 // its methods concurrently.
268 type authUser struct {
269 mu sync.Mutex
270- _entity state.AuthEntity // logged-in entity (access only when mu is locked)
271+ _entity state.Entity // logged-in entity (access only when mu is locked)
272 }
273
274 // login authenticates as entity with the given name,.
275 func (u *authUser) login(st *state.State, entityName, password string) error {
276 u.mu.Lock()
277 defer u.mu.Unlock()
278- entity, err := st.AuthEntity(entityName)
279+ entity, err := st.Entity(entityName)
280 if err != nil && !state.IsNotFound(err) {
281 return err
282 }
283@@ -463,7 +481,7 @@
284 // entity returns the currently logged-in entity, or nil if not
285 // currently logged on. The returned entity should not be modified
286 // because it may be used concurrently.
287-func (u *authUser) entity() state.AuthEntity {
288+func (u *authUser) entity() state.Entity {
289 u.mu.Lock()
290 defer u.mu.Unlock()
291 return u._entity
292@@ -471,7 +489,7 @@
293
294 // isMachineWithJob returns whether the given entity is a machine that
295 // is configured to run the given job.
296-func isMachineWithJob(e state.AuthEntity, j state.MachineJob) bool {
297+func isMachineWithJob(e state.Entity, j state.MachineJob) bool {
298 m, ok := e.(*state.Machine)
299 if !ok {
300 return false
301@@ -485,7 +503,7 @@
302 }
303
304 // isAgent returns whether the given entity is an agent.
305-func isAgent(e state.AuthEntity) bool {
306+func isAgent(e state.Entity) bool {
307 _, isUser := e.(*state.User)
308 return !isUser
309 }
310
311=== modified file 'state/machine_test.go'
312--- state/machine_test.go 2013-03-05 15:26:26 +0000
313+++ state/machine_test.go 2013-03-08 21:34:19 +0000
314@@ -111,7 +111,7 @@
315 }
316
317 func (s *MachineSuite) TestSetPassword(c *C) {
318- testSetPassword(c, func() (state.AuthEntity, error) {
319+ testSetPassword(c, func() (state.Entity, error) {
320 return s.State.Machine(s.machine.Id())
321 })
322 }
323
324=== modified file 'state/service.go'
325--- state/service.go 2013-03-08 17:56:51 +0000
326+++ state/service.go 2013-03-08 21:34:19 +0000
327@@ -52,6 +52,25 @@
328 return s.doc.Name
329 }
330
331+// EntityName returns a name identifying the service that is safe to use
332+// as a file name. The returned name will be different from other
333+// EntityName values returned by any other entities from the same state.
334+func (s *Service) EntityName() string {
335+ return "service-" + s.Name()
336+}
337+
338+// PasswordValid currently just returns false. Implemented here so that
339+// a service can be used as an Entity.
340+func (s *Service) PasswordValid(password string) bool {
341+ return false
342+}
343+
344+// SetPassword currently just returns an error. Implemented here so that
345+// a service can be used as an Entity.
346+func (s *Service) SetPassword(password string) error {
347+ return fmt.Errorf("cannot set password of service")
348+}
349+
350 // Annotations returns the service annotations.
351 func (s *Service) Annotations() map[string]string {
352 return s.doc.Annotations
353
354=== modified file 'state/service_test.go'
355--- state/service_test.go 2013-03-08 19:55:44 +0000
356+++ state/service_test.go 2013-03-08 21:34:19 +0000
357@@ -64,6 +64,10 @@
358 }
359 }
360
361+func (s *ServiceSuite) TestEntityName(c *C) {
362+ c.Assert(s.mysql.EntityName(), Equals, "service-mysql")
363+}
364+
365 func (s *ServiceSuite) TestMysqlEndpoints(c *C) {
366 _, err := s.mysql.Endpoint("mysql")
367 c.Assert(err, ErrorMatches, `service "mysql" has no "mysql" relation`)
368
369=== modified file 'state/state.go'
370--- state/state.go 2013-03-08 19:27:06 +0000
371+++ state/state.go 2013-03-08 21:34:19 +0000
372@@ -274,17 +274,19 @@
373 return newMachine(st, mdoc), nil
374 }
375
376-// AuthEntity represents an entity that has
377-// a password that can be authenticated against.
378-type AuthEntity interface {
379+// Entity represents an entity capabable of handling password authentication
380+// and annotations.
381+type Entity interface {
382 EntityName() string
383 SetPassword(pass string) error
384 PasswordValid(pass string) bool
385 Refresh() error
386+ SetAnnotation(key, value string) error
387+ Annotations() map[string]string
388 }
389
390-// AuthEntity returns the entity for the given name.
391-func (st *State) AuthEntity(entityName string) (AuthEntity, error) {
392+// Entity returns the entity for the given name.
393+func (st *State) Entity(entityName string) (Entity, error) {
394 i := strings.Index(entityName, "-")
395 if i <= 0 || i >= len(entityName)-1 {
396 return nil, fmt.Errorf("invalid entity name %q", entityName)
397@@ -308,6 +310,11 @@
398 return st.Unit(name)
399 case "user":
400 return st.User(id)
401+ case "service":
402+ if !IsServiceName(id) {
403+ return nil, fmt.Errorf("invalid entity name %q", entityName)
404+ }
405+ return st.Service(id)
406 }
407 return nil, fmt.Errorf("invalid entity name %q", entityName)
408 }
409
410=== modified file 'state/state_test.go'
411--- state/state_test.go 2013-03-07 16:05:22 +0000
412+++ state/state_test.go 2013-03-08 21:34:19 +0000
413@@ -1194,7 +1194,7 @@
414 c.Assert(err, ErrorMatches, "no reachable servers")
415 }
416
417-func testSetPassword(c *C, getEntity func() (state.AuthEntity, error)) {
418+func testSetPassword(c *C, getEntity func() (state.Entity, error)) {
419 e, err := getEntity()
420 c.Assert(err, IsNil)
421
422@@ -1317,30 +1317,30 @@
423 c.Assert(err, IsNil)
424 }
425
426-func (s *StateSuite) TestAuthEntity(c *C) {
427- bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo"}
428+func (s *StateSuite) TestEntity(c *C) {
429+ bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"}
430 for _, name := range bad {
431- e, err := s.State.AuthEntity(name)
432+ e, err := s.State.Entity(name)
433 c.Check(e, IsNil)
434 c.Assert(err, ErrorMatches, `invalid entity name ".*"`)
435 }
436
437- e, err := s.State.AuthEntity("machine-1234")
438+ e, err := s.State.Entity("machine-1234")
439 c.Check(e, IsNil)
440 c.Assert(err, ErrorMatches, `machine 1234 not found`)
441 c.Assert(state.IsNotFound(err), Equals, true)
442
443- e, err = s.State.AuthEntity("unit-foo-654")
444+ e, err = s.State.Entity("unit-foo-654")
445 c.Check(e, IsNil)
446 c.Assert(err, ErrorMatches, `unit "foo/654" not found`)
447 c.Assert(state.IsNotFound(err), Equals, true)
448
449- e, err = s.State.AuthEntity("unit-foo-bar-654")
450+ e, err = s.State.Entity("unit-foo-bar-654")
451 c.Check(e, IsNil)
452 c.Assert(err, ErrorMatches, `unit "foo-bar/654" not found`)
453 c.Assert(state.IsNotFound(err), Equals, true)
454
455- e, err = s.State.AuthEntity("user-arble")
456+ e, err = s.State.Entity("user-arble")
457 c.Check(e, IsNil)
458 c.Assert(err, ErrorMatches, `user "arble" not found`)
459 c.Assert(state.IsNotFound(err), Equals, true)
460@@ -1348,17 +1348,23 @@
461 m, err := s.State.AddMachine("series", state.JobHostUnits)
462 c.Assert(err, IsNil)
463
464- e, err = s.State.AuthEntity(m.EntityName())
465+ e, err = s.State.Entity(m.EntityName())
466 c.Assert(err, IsNil)
467 c.Assert(e, FitsTypeOf, m)
468 c.Assert(e.EntityName(), Equals, m.EntityName())
469
470 svc, err := s.State.AddService("ser-vice1", s.AddTestingCharm(c, "dummy"))
471 c.Assert(err, IsNil)
472+
473+ service, err := s.State.Entity(svc.EntityName())
474+ c.Assert(err, IsNil)
475+ c.Assert(service, FitsTypeOf, svc)
476+ c.Assert(service.EntityName(), Equals, svc.EntityName())
477+
478 u, err := svc.AddUnit()
479 c.Assert(err, IsNil)
480
481- e, err = s.State.AuthEntity(u.EntityName())
482+ e, err = s.State.Entity(u.EntityName())
483 c.Assert(err, IsNil)
484 c.Assert(e, FitsTypeOf, u)
485 c.Assert(e.EntityName(), Equals, u.EntityName())
486@@ -1366,7 +1372,7 @@
487 user, err := s.State.AddUser("arble", "pass")
488 c.Assert(err, IsNil)
489
490- e, err = s.State.AuthEntity(user.EntityName())
491+ e, err = s.State.Entity(user.EntityName())
492 c.Assert(err, IsNil)
493 c.Assert(e, FitsTypeOf, user)
494 c.Assert(e.EntityName(), Equals, user.EntityName())
495
496=== modified file 'state/unit_test.go'
497--- state/unit_test.go 2013-03-05 15:26:26 +0000
498+++ state/unit_test.go 2013-03-08 21:34:19 +0000
499@@ -174,7 +174,7 @@
500 }
501
502 func (s *UnitSuite) TestSetPassword(c *C) {
503- testSetPassword(c, func() (state.AuthEntity, error) {
504+ testSetPassword(c, func() (state.Entity, error) {
505 return s.State.Unit(s.unit.Name())
506 })
507 }
508
509=== modified file 'state/user.go'
510--- state/user.go 2013-02-18 17:03:00 +0000
511+++ state/user.go 2013-03-08 21:34:19 +0000
512@@ -79,6 +79,18 @@
513 return "user-" + u.doc.Name
514 }
515
516+// Annotations currently just returns an empty map. Implemented here so that
517+// a user can be used as an Entity.
518+func (u *User) Annotations() map[string]string {
519+ return map[string]string{}
520+}
521+
522+// SetAnnotation currently just returns an error. Implemented here so that
523+// a user can be used as an Entity.
524+func (u *User) SetAnnotation(key, value string) error {
525+ return fmt.Errorf("cannot set annotation of user")
526+}
527+
528 // SetPassword sets the password associated with the user.
529 func (u *User) SetPassword(password string) error {
530 hp := trivial.PasswordHash(password)
531
532=== modified file 'state/user_test.go'
533--- state/user_test.go 2013-02-06 19:57:33 +0000
534+++ state/user_test.go 2013-03-08 21:34:19 +0000
535@@ -43,7 +43,7 @@
536 u, err := s.State.AddUser("someuser", "")
537 c.Assert(err, IsNil)
538
539- testSetPassword(c, func() (state.AuthEntity, error) {
540+ testSetPassword(c, func() (state.Entity, error) {
541 return s.State.User(u.Name())
542 })
543 }

Subscribers

People subscribed via source and target branches