Merge lp:~frankban/juju-core/bug-1147138-annotations-api into lp:~juju/juju-core/trunk
- bug-1147138-annotations-api
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+152037@code.launchpad.net |
Commit message
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.
Francesco Banconi (frankban) wrote : | # |
Francesco Banconi (frankban) wrote : | # |
Please take a look.
Dimiter Naydenov (dimitern) wrote : | # |
Overall LGTM, just a couple of comments.
https:/
File state/api/
https:/
state/api/
can you skip the keys here? If it's only one {id} will work.
https:/
state/api/
key, Value: value}
similarly - see above
Francesco Banconi (frankban) wrote : | # |
Please take a look.
https:/
File state/api/
https:/
state/api/
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:/
state/api/
key, Value: value}
On 2013/03/07 14:28:01, dimitern wrote:
> similarly - see above
Done.
Roger Peppe (rogpeppe) wrote : | # |
LGTM with the below trivial points addressed.
really good stuff, thanks!
https:/
File state/api/
https:/
state/api/
(*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(
error)
https:/
state/api/
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(
?
https:/
File state/api/
https:/
state/api/
s/Id/EntityId/
https:/
state/api/
s/Id/EntityId/
https:/
File state/apiserver
https:/
state/apiserver
the returned function should reset the annotation to its original value
- all these op* functions should leave the state unchanged.
https:/
state/apiserver
s/Annotations/Check annotations/ ?
https:/
state/apiserver
check error
https:/
state/apiserver
s/Annotations/Check annotations/ ?
https:/
state/apiserver
check error
https:/
state/apiserver
check error
https:/
File state/apiserver
https:/
state/apiserver
GetA...
Francesco Banconi (frankban) wrote : | # |
*** 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:/
https:/
File state/api/
https:/
state/api/
(*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(
error)
Done.
https:/
state/api/
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(
> ?
Done.
https:/
File state/api/
https:/
state/api/
On 2013/03/08 20:52:37, rog wrote:
> s/Id/EntityId/
Done.
https:/
state/api/
On 2013/03/08 20:52:37, rog wrote:
> s/Id/EntityId/
Done.
https:/
File state/apiserver
https:/
state/apiserver
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:/
state/apiserver
On 2013/03/08 20:52:37, rog wrote:
> s/Annotations/Check annotations/ ?
Done.
https:/
state/apiserver
On 2013/03/08 20:52:37, rog wrote:
> check error
Done.
Francesco Banconi (frankban) wrote : | # |
Hi Dimiter and Roger,
thanks for the reviews.
Preview Diff
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 | } |
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: apiclient. go /api_test. go /apiserver. go test.go test.go
A [revision details]
M state/api/
M state/apiserver
M state/apiserver
M state/machine_
M state/service.go
M state/service_
M state/state.go
M state/state_test.go
M state/unit_test.go
M state/user.go
M state/user_test.go