Merge lp:~rogpeppe/juju-core/312-api-jobs into lp:~juju/juju-core/trunk

Proposed by Roger Peppe
Status: Rejected
Rejected by: William Reade
Proposed branch: lp:~rogpeppe/juju-core/312-api-jobs
Merge into: lp:~juju/juju-core/trunk
Prerequisite: lp:~rogpeppe/juju-core/311-juju-bootstrap-state-change-password-1.5
Diff against target: 1654 lines (+1071/-213)
21 files modified
cmd/jujud/bootstrap.go (+1/-1)
cmd/jujud/bootstrap_test.go (+1/-1)
cmd/jujud/machine.go (+2/-2)
cmd/jujud/machine_test.go (+4/-4)
environs/config/config_test.go (+1/-1)
environs/dummy/environs.go (+0/-3)
juju/testing/repo.go (+0/-1)
state/api/params/params.go (+30/-0)
state/apiserver/admin.go (+0/-20)
state/apiserver/apiserver.go (+750/-0)
state/apiserver/common/interfaces.go (+1/-1)
state/apiserver/login_test.go (+4/-3)
state/apiserver/machine/agent.go (+69/-0)
state/apiserver/machine/agent_test.go (+91/-0)
state/apiserver/machine/common_test.go (+66/-0)
state/apiserver/machine/machiner.go (+12/-12)
state/apiserver/machine/machiner_test.go (+9/-61)
state/apiserver/resource.go (+0/-82)
state/apiserver/root.go (+22/-13)
state/machine.go (+6/-6)
state/state_test.go (+2/-2)
To merge this branch: bzr merge lp:~rogpeppe/juju-core/312-api-jobs
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+165705@code.launchpad.net

Description of the change

api: implement Machine.Jobs

https://codereview.appspot.com/9754043/

To post a comment you must log in.
Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (4.1 KiB)

Reviewers: mp+165705_code.launchpad.net,

Message:
Please take a look.

Description:
api: implement Machine.Jobs

https://code.launchpad.net/~rogpeppe/juju-core/312-api-jobs/+merge/165705

Requires:
https://code.launchpad.net/~rogpeppe/juju-core/311-juju-bootstrap-state-change-password-1.5/+merge/165675

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M state/api/machine.go
   M state/api/params/params.go
   M state/apiserver/machine_test.go
   M state/apiserver/utils.go
   M state/machine.go

Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: <email address hidden>
+New revision: <email address hidden>

Index: state/machine.go
=== modified file 'state/machine.go'
--- state/machine.go 2013-05-21 16:40:29 +0000
+++ state/machine.go 2013-05-24 21:38:13 +0000
@@ -36,7 +36,7 @@
   JobServeAPI
  )

-var jobNames = []string{
+var jobNames = []params.MachineJob{
   JobHostUnits: "JobHostUnits",
   JobManageEnviron: "JobManageEnviron",
   JobServeAPI: "JobServeAPI",
@@ -47,7 +47,7 @@
   if j <= 0 || j >= len(jobNames) {
    return fmt.Sprintf("<unknown job %d>", j)
   }
- return jobNames[j]
+ return string(jobNames[j])
  }

  // machineDoc represents the internal state of a machine in MongoDB.

Index: state/api/machine.go
=== modified file 'state/api/machine.go'
--- state/api/machine.go 2013-05-24 19:03:39 +0000
+++ state/api/machine.go 2013-05-24 21:38:13 +0000
@@ -139,6 +139,11 @@
   return m.doc.Life
  }

+// Jobs returns the responsibilities that must be fulfilled by m's agent.
+func (m *Machine) Jobs() []params.MachineJob {
+ return m.doc.Jobs
+}
+
  // Series returns the operating system series running on the machine.
  func (m *Machine) Series() string {
   return m.doc.Series

Index: state/apiserver/machine_test.go
=== modified file 'state/apiserver/machine_test.go'
--- state/apiserver/machine_test.go 2013-05-24 19:03:39 +0000
+++ state/apiserver/machine_test.go 2013-05-24 21:38:13 +0000
@@ -212,6 +212,29 @@
   c.Assert(string(life), Equals, "dead")
  }

+func (s *suite) TestMachineJobs(c *C) {
+ stm, err := s.State.AddMachine(
+ "series",
+ state.JobHostUnits,
+ state.JobManageEnviron,
+ state.JobServeAPI,
+ )
+ c.Assert(err, IsNil)
+ setDefaultPassword(c, stm)
+
+ st := s.openAs(c, stm.Tag())
+ defer st.Close()
+
+ m, err := st.Machine(stm.Id())
+ c.Assert(err, IsNil)
+
+ c.Assert(m.Jobs(), DeepEquals, []params.MachineJob{
+ params.JobHostUnits,
+ params.JobManageEnviron,
+ params.JobServeAPI,
+ })
+}
+
  func (s *suite) TestMachineEnsureDead(c *C) {
   stm, err := s.State.AddMachine("series", state.JobHostUnits)
   c.Assert(err, IsNil)

Index: state/apiserver/utils.go
=== modified file 'state/apiserver/utils.go'
--- state/apiserver/utils.go 2013-05-24 19:11:19 +0000
+++ state/apiserver/utils.go 2013-05-24 21:38:13 +0000
@@ -44,10 +44,16 @@
    return nil
   }
   instId, _ := stm.InstanceId()
+ jobs := stm....

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :
Revision history for this message
William Reade (fwereade) wrote :

NOT LGTM. Int jobs aren't great, but they're better than baking bad
names into the API forever, I think.

https://codereview.appspot.com/9754043/diff/13001/state/machine.go
File state/machine.go (right):

https://codereview.appspot.com/9754043/diff/13001/state/machine.go#newcode38
state/machine.go:38:
// jobNames MUST NEVER BE CHANGED.

...or maybe we could just use ints, as designed originally, and not
lumber the API with guaranteed-inaccurate names. (JobServeAPI is not
sensible; it conflates jobs with tasks.)

https://codereview.appspot.com/9754043/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

Please take a look.

https://codereview.appspot.com/9754043/diff/13001/state/machine.go
File state/machine.go (right):

https://codereview.appspot.com/9754043/diff/13001/state/machine.go#newcode38
state/machine.go:38:
On 2013/05/27 20:23:20, fwereade wrote:
> // jobNames MUST NEVER BE CHANGED.

> ...or maybe we could just use ints, as designed originally, and not
lumber the
> API with guaranteed-inaccurate names. (JobServeAPI is not sensible; it
conflates
> jobs with tasks.)

renamed to JobManageState as agreed in discussion.

https://codereview.appspot.com/9754043/

Revision history for this message
William Reade (fwereade) wrote :
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please do not commit this without refactoring it into a facade. We don't
have api/machine.go or apiserver/machine.go anymore.

https://codereview.appspot.com/9754043/

lp:~rogpeppe/juju-core/312-api-jobs updated
1242. By Roger Peppe

revert all jobs changes

1243. By Roger Peppe

merge 311-juju-bootstrap-state-change-password-1.5

1244. By Roger Peppe

merge 321-apiserver-machineagent-api

Revision history for this message
William Reade (fwereade) wrote :

Unmerged revisions

1244. By Roger Peppe

merge 321-apiserver-machineagent-api

1243. By Roger Peppe

merge 311-juju-bootstrap-state-change-password-1.5

1242. By Roger Peppe

revert all jobs changes

1241. By Roger Peppe

gofmt

1240. By Roger Peppe

all: rename JobServeAPI to JobManageState

1239. By Roger Peppe

Merged 311-juju-bootstrap-state-change-password-1.5 into 312-api-jobs.

1238. By Roger Peppe

api: implement Machine.Jobs

1237. By Roger Peppe

merge trunk

1236. By Roger Peppe

cmd/juju: revert bogus change

1235. By Roger Peppe

cmd/jujud: change password for machine agent

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/jujud/bootstrap.go'
2--- cmd/jujud/bootstrap.go 2013-06-18 12:56:33 +0000
3+++ cmd/jujud/bootstrap.go 2013-06-18 12:56:33 +0000
4@@ -114,7 +114,7 @@
5 // Upgrader, and it's a capability we'll always want to have
6 // available for the aforementioned use case.
7 jobs := []state.MachineJob{
8- state.JobManageEnviron, state.JobServeAPI, state.JobHostUnits,
9+ state.JobManageEnviron, state.JobManageState, state.JobHostUnits,
10 }
11 m, err := st.InjectMachine(version.Current.Series, c.Constraints, instanceId, jobs...)
12 if err != nil {
13
14=== modified file 'cmd/jujud/bootstrap_test.go'
15--- cmd/jujud/bootstrap_test.go 2013-06-18 12:56:33 +0000
16+++ cmd/jujud/bootstrap_test.go 2013-06-18 12:56:33 +0000
17@@ -166,7 +166,7 @@
18 m, err := st.Machine("0")
19 c.Assert(err, IsNil)
20 c.Assert(m.Jobs(), DeepEquals, []state.MachineJob{
21- state.JobManageEnviron, state.JobServeAPI, state.JobHostUnits,
22+ state.JobManageEnviron, state.JobManageState, state.JobHostUnits,
23 })
24 }
25
26
27=== modified file 'cmd/jujud/machine.go'
28--- cmd/jujud/machine.go 2013-06-11 13:52:19 +0000
29+++ cmd/jujud/machine.go 2013-06-18 12:56:33 +0000
30@@ -122,7 +122,7 @@
31 tasks = append(tasks,
32 provisioner.NewProvisioner(st, a.MachineId),
33 firewaller.NewFirewaller(st))
34- case state.JobServeAPI:
35+ case state.JobManageState:
36 // Ignore because it's started independently.
37 continue
38 default:
39@@ -173,7 +173,7 @@
40 m := entity.(*state.Machine)
41 runAPI := false
42 for _, job := range m.Jobs() {
43- if job == state.JobServeAPI {
44+ if job == state.JobManageState {
45 runAPI = true
46 break
47 }
48
49=== modified file 'cmd/jujud/machine_test.go'
50--- cmd/jujud/machine_test.go 2013-06-18 12:56:33 +0000
51+++ cmd/jujud/machine_test.go 2013-06-18 12:56:33 +0000
52@@ -110,7 +110,7 @@
53 }
54
55 func (s *MachineSuite) TestWithDeadMachine(c *C) {
56- m, _, _ := s.primeAgent(c, state.JobHostUnits, state.JobServeAPI)
57+ m, _, _ := s.primeAgent(c, state.JobHostUnits, state.JobManageState)
58 err := m.EnsureDead()
59 c.Assert(err, IsNil)
60 a := s.newAgent(c, m)
61@@ -248,7 +248,7 @@
62 }
63
64 func (s *MachineSuite) TestUpgrade(c *C) {
65- m, conf, currentTools := s.primeAgent(c, state.JobServeAPI, state.JobManageEnviron, state.JobHostUnits)
66+ m, conf, currentTools := s.primeAgent(c, state.JobManageState, state.JobManageEnviron, state.JobHostUnits)
67 addAPIInfo(conf, m)
68 err := conf.Write()
69 c.Assert(err, IsNil)
70@@ -270,7 +270,7 @@
71 }
72
73 func (s *MachineSuite) TestServeAPI(c *C) {
74- stm, conf, _ := s.primeAgent(c, state.JobServeAPI)
75+ stm, conf, _ := s.primeAgent(c, state.JobManageState)
76 addAPIInfo(conf, stm)
77 err := conf.Write()
78 c.Assert(err, IsNil)
79@@ -320,7 +320,7 @@
80 }}
81
82 func (s *MachineSuite) TestServeAPIWithBadConf(c *C) {
83- m, conf, _ := s.primeAgent(c, state.JobServeAPI)
84+ m, conf, _ := s.primeAgent(c, state.JobManageState)
85 addAPIInfo(conf, m)
86 for i, t := range serveAPIWithBadConfTests {
87 c.Logf("test %d: %q", i, t.err)
88
89=== modified file 'environs/config/config_test.go'
90--- environs/config/config_test.go 2013-06-18 12:56:33 +0000
91+++ environs/config/config_test.go 2013-06-10 10:30:13 +0000
92@@ -130,7 +130,7 @@
93 "name": "my-name",
94 "ca-cert": invalidCACert,
95 },
96- err: `bad CA certificate/key in configuration: (asn1:|ASN\.1) syntax error:.*`,
97+ err: "bad CA certificate/key in configuration: ASN.1 syntax error:.*",
98 }, {
99 about: "Invalid CA key",
100 attrs: attrs{
101
102=== modified file 'environs/dummy/environs.go'
103--- environs/dummy/environs.go 2013-06-18 12:56:33 +0000
104+++ environs/dummy/environs.go 2013-06-18 12:56:33 +0000
105@@ -456,9 +456,6 @@
106 return fmt.Errorf("environment is already bootstrapped")
107 }
108 if e.ecfg().stateServer() {
109- // TODO(rog) factor out relevant code from cmd/jujud/bootstrap.go
110- // so that we can call it here.
111-
112 info := stateInfo()
113 st, err := state.Initialize(info, cfg, state.DefaultDialOpts())
114 if err != nil {
115
116=== modified file 'juju/testing/repo.go'
117--- juju/testing/repo.go 2013-05-09 21:51:05 +0000
118+++ juju/testing/repo.go 2013-06-18 12:56:33 +0000
119@@ -52,7 +52,6 @@
120 c.Assert(ch.URL(), DeepEquals, expectCurl)
121 s.AssertCharmUploaded(c, expectCurl)
122 units, err := svc.AllUnits()
123- c.Logf("Service units: %+v", units)
124 c.Assert(err, IsNil)
125 c.Assert(units, HasLen, unitCount)
126 s.AssertUnitMachines(c, units)
127
128=== renamed file 'state/api/client.go' => 'state/api/apiclient.go'
129=== renamed file 'state/api/apiclient.go' => 'state/api/client.go'
130=== renamed file 'state/api/apierror.go' => 'state/api/error.go'
131=== modified file 'state/api/params/params.go'
132--- state/api/params/params.go 2013-06-18 12:56:33 +0000
133+++ state/api/params/params.go 2013-06-18 12:56:33 +0000
134@@ -28,6 +28,12 @@
135 return e.Message
136 }
137
138+// GoString implements fmt.GoStringer. It means that a *Error shows its
139+// contents correctly when printed with %#v.
140+func (e Error) GoString() string {
141+ return fmt.Sprintf("&params.Error{%q, %q}", e.Code, e.Message)
142+}
143+
144 // ErrorResults holds the results of calling a bulk operation which
145 // mutates multiple entites, like Machiner.SetStatus. The order and
146 // number of elements matches the entities specified in the request.
147@@ -87,6 +93,30 @@
148 Machines []MachineLifeResult
149 }
150
151+// MachineAgentGetMachinesResults holds the results of a
152+// machineagent.API.GetMachines call.
153+type MachineAgentGetMachinesResults struct {
154+ Machines []MachineAgentGetMachinesResult
155+}
156+
157+// MachineJob values define responsibilities that machines may be
158+// expected to fulfil.
159+type MachineJob string
160+
161+const (
162+ JobHostUnits MachineJob = "JobHostUnits"
163+ JobManageEnviron MachineJob = "JobManageEnviron"
164+ JobManageState MachineJob = "JobManageState"
165+)
166+
167+// MachineAgentGetMachinesResult holds the results of a
168+// machineagent.API.GetMachines call for a single machine.
169+type MachineAgentGetMachinesResult struct {
170+ Life Life
171+ Jobs []MachineJob
172+ Error *Error
173+}
174+
175 // ServiceDeploy holds the parameters for making the ServiceDeploy call.
176 type ServiceDeploy struct {
177 ServiceName string
178
179=== removed file 'state/apiserver/admin.go'
180--- state/apiserver/admin.go 2013-05-24 19:03:39 +0000
181+++ state/apiserver/admin.go 1970-01-01 00:00:00 +0000
182@@ -1,20 +0,0 @@
183-// Copyright 2013 Canonical Ltd.
184-// Licensed under the AGPLv3, see LICENCE file for details.
185-
186-package apiserver
187-
188-import "launchpad.net/juju-core/state/api/params"
189-
190-// srvAdmin is the only object that unlogged-in
191-// clients can access. It holds any methods
192-// that are needed to log in.
193-type srvAdmin struct {
194- root *srvRoot
195-}
196-
197-// Login logs in with the provided credentials.
198-// All subsequent requests on the connection will
199-// act as the authenticated user.
200-func (a *srvAdmin) Login(c params.Creds) error {
201- return a.root.user.login(a.root.srv.state, c.AuthTag, c.Password)
202-}
203
204=== added file 'state/apiserver/apiserver.go'
205--- state/apiserver/apiserver.go 1970-01-01 00:00:00 +0000
206+++ state/apiserver/apiserver.go 2013-06-18 12:56:33 +0000
207@@ -0,0 +1,750 @@
208+// Copyright 2013 Canonical Ltd.
209+// Licensed under the AGPLv3, see LICENCE file for details.
210+
211+package apiserver
212+
213+import (
214+ "fmt"
215+ "launchpad.net/juju-core/charm"
216+ "launchpad.net/juju-core/juju"
217+ "launchpad.net/juju-core/log"
218+ "launchpad.net/juju-core/state"
219+ "launchpad.net/juju-core/state/api"
220+ "launchpad.net/juju-core/state/api/params"
221+ "launchpad.net/juju-core/state/multiwatcher"
222+ "launchpad.net/juju-core/state/presence"
223+ "launchpad.net/juju-core/state/statecmd"
224+ statewatcher "launchpad.net/juju-core/state/watcher"
225+ "strconv"
226+ "sync"
227+)
228+
229+// srvRoot represents a single client's connection to the state.
230+type srvRoot struct {
231+ admin *srvAdmin
232+ client *srvClient
233+ srv *Server
234+ resources *resources
235+
236+ user authUser
237+}
238+
239+// srvAdmin is the only object that unlogged-in
240+// clients can access. It holds any methods
241+// that are needed to log in.
242+type srvAdmin struct {
243+ root *srvRoot
244+}
245+
246+// srvMachine serves API methods on a machine.
247+type srvMachine struct {
248+ root *srvRoot
249+ m *state.Machine
250+}
251+
252+// srvUnit serves API methods on a unit.
253+type srvUnit struct {
254+ root *srvRoot
255+ u *state.Unit
256+}
257+
258+// srvUser serves API methods on a state User.
259+type srvUser struct {
260+ root *srvRoot
261+ u *state.User
262+}
263+
264+// srvClient serves client-specific API methods.
265+type srvClient struct {
266+ root *srvRoot
267+}
268+
269+func newStateServer(srv *Server) *srvRoot {
270+ r := &srvRoot{
271+ srv: srv,
272+ resources: newResources(),
273+ }
274+ r.admin = &srvAdmin{
275+ root: r,
276+ }
277+ r.client = &srvClient{
278+ root: r,
279+ }
280+ return r
281+}
282+
283+// Kill implements rpc.Killer. It cleans up any resources that need
284+// cleaning up to ensure that all outstanding requests return.
285+func (r *srvRoot) Kill() {
286+ r.resources.stopAll()
287+}
288+
289+// Admin returns an object that provides API access
290+// to methods that can be called even when not
291+// authenticated.
292+func (r *srvRoot) Admin(id string) (*srvAdmin, error) {
293+ if id != "" {
294+ // Safeguard id for possible future use.
295+ return nil, errBadId
296+ }
297+ return r.admin, nil
298+}
299+
300+// requireAgent checks whether the current client is an agent and hence
301+// may access the agent APIs. We filter out non-agents when calling one
302+// of the accessor functions (Machine, Unit, etc) which avoids us making
303+// the check in every single request method.
304+func (r *srvRoot) requireAgent() error {
305+ e := r.user.authenticator()
306+ if e == nil {
307+ return errNotLoggedIn
308+ }
309+ if !isAgent(e) {
310+ return errPerm
311+ }
312+ return nil
313+}
314+
315+// requireClient returns an error unless the current
316+// client is a juju client user.
317+func (r *srvRoot) requireClient() error {
318+ e := r.user.authenticator()
319+ if e == nil {
320+ return errNotLoggedIn
321+ }
322+ if isAgent(e) {
323+ return errPerm
324+ }
325+ return nil
326+}
327+
328+// Machine returns an object that provides
329+// API access to methods on a state.Machine.
330+func (r *srvRoot) Machine(id string) (*srvMachine, error) {
331+ if err := r.requireAgent(); err != nil {
332+ return nil, err
333+ }
334+ m, err := r.srv.state.Machine(id)
335+ if err != nil {
336+ return nil, err
337+ }
338+ return &srvMachine{
339+ root: r,
340+ m: m,
341+ }, nil
342+}
343+
344+// Unit returns an object that provides
345+// API access to methods on a state.Unit.
346+func (r *srvRoot) Unit(name string) (*srvUnit, error) {
347+ if err := r.requireAgent(); err != nil {
348+ return nil, err
349+ }
350+ u, err := r.srv.state.Unit(name)
351+ if err != nil {
352+ return nil, err
353+ }
354+ return &srvUnit{
355+ root: r,
356+ u: u,
357+ }, nil
358+}
359+
360+// User returns an object that provides
361+// API access to methods on a state.User.
362+func (r *srvRoot) User(name string) (*srvUser, error) {
363+ // Any user is allowed to access their own user object.
364+ // We check at this level rather than at the operation
365+ // level to stop malicious probing for current user names.
366+ // When we provide support for user administration,
367+ // this will need to be changed to allow access to
368+ // the administrator.
369+ e := r.user.authenticator()
370+ if e == nil {
371+ return nil, errNotLoggedIn
372+ }
373+ if e.Tag() != name {
374+ return nil, errPerm
375+ }
376+ u, err := r.srv.state.User(name)
377+ if err != nil {
378+ return nil, err
379+ }
380+ return &srvUser{
381+ root: r,
382+ u: u,
383+ }, nil
384+}
385+
386+// Pinger returns an object that provides API access to methods on a
387+// presence.Pinger. Each client has its own current set of pingers,
388+// stored in r.resources.
389+func (r *srvRoot) Pinger(id string) (*srvResource, error) {
390+ if err := r.requireAgent(); err != nil {
391+ return nil, err
392+ }
393+ pinger := r.resources.get(id)
394+ if pinger == nil {
395+ return nil, errUnknownPinger
396+ }
397+ if _, ok := pinger.resource.(*presence.Pinger); !ok {
398+ return nil, errUnknownPinger
399+ }
400+ return pinger, nil
401+}
402+
403+// EntityWatcher returns an object that provides
404+// API access to methods on a state.EntityWatcher.
405+// Each client has its own current set of watchers, stored
406+// in r.resources.
407+func (r *srvRoot) EntityWatcher(id string) (srvEntityWatcher, error) {
408+ if err := r.requireAgent(); err != nil {
409+ return srvEntityWatcher{}, err
410+ }
411+ watcher := r.resources.get(id)
412+ if watcher == nil {
413+ return srvEntityWatcher{}, errUnknownWatcher
414+ }
415+ if _, ok := watcher.resource.(*state.EntityWatcher); !ok {
416+ return srvEntityWatcher{}, errUnknownWatcher
417+ }
418+ return srvEntityWatcher{watcher}, nil
419+}
420+
421+// AllWatcher returns an object that provides API access to methods on
422+// a state/multiwatcher.Watcher, which watches any changes to the
423+// state. Each client has its own current set of watchers, stored in
424+// r.resources.
425+func (r *srvRoot) AllWatcher(id string) (srvClientAllWatcher, error) {
426+ if err := r.requireClient(); err != nil {
427+ return srvClientAllWatcher{}, err
428+ }
429+ watcher := r.resources.get(id)
430+ if watcher == nil {
431+ return srvClientAllWatcher{}, errUnknownWatcher
432+ }
433+ if _, ok := watcher.resource.(*multiwatcher.Watcher); !ok {
434+ return srvClientAllWatcher{}, errUnknownWatcher
435+ }
436+ return srvClientAllWatcher{watcher}, nil
437+
438+}
439+
440+// Client returns an object that provides access
441+// to methods accessible to non-agent clients.
442+func (r *srvRoot) Client(id string) (*srvClient, error) {
443+ if err := r.requireClient(); err != nil {
444+ return nil, err
445+ }
446+ if id != "" {
447+ // Safeguard id for possible future use.
448+ return nil, errBadId
449+ }
450+ return r.client, nil
451+}
452+
453+type tagger interface {
454+ Tag() string
455+}
456+
457+// authOwner returns whether the authenticated user's tag matches the
458+// given entity's tag.
459+func (r *srvRoot) authOwner(entity tagger) bool {
460+ authUser := r.user.authenticator()
461+ return authUser.Tag() == entity.Tag()
462+}
463+
464+// authEnvironManager returns whether the authenticated user is a
465+// machine with running the ManageEnviron job.
466+func (r *srvRoot) authEnvironManager() bool {
467+ authUser := r.user.authenticator()
468+ return isMachineWithJob(authUser, state.JobManageEnviron)
469+}
470+
471+type srvEntityWatcher struct {
472+ *srvResource
473+}
474+
475+// Next returns when a change has occurred to the
476+// entity being watched since the most recent call to Next
477+// or the Watch call that created the EntityWatcher.
478+func (w srvEntityWatcher) Next() error {
479+ watcher := w.resource.(*state.EntityWatcher)
480+ if _, ok := <-watcher.Changes(); ok {
481+ return nil
482+ }
483+ err := watcher.Err()
484+ if err == nil {
485+ err = errStoppedWatcher
486+ }
487+ return err
488+}
489+
490+func (c *srvClient) Status() (api.Status, error) {
491+ ms, err := c.root.srv.state.AllMachines()
492+ if err != nil {
493+ return api.Status{}, err
494+ }
495+ status := api.Status{
496+ Machines: make(map[string]api.MachineInfo),
497+ }
498+ for _, m := range ms {
499+ instId, _ := m.InstanceId()
500+ status.Machines[m.Id()] = api.MachineInfo{
501+ InstanceId: string(instId),
502+ }
503+ }
504+ return status, nil
505+}
506+
507+func (c *srvClient) WatchAll() (params.AllWatcherId, error) {
508+ w := c.root.srv.state.Watch()
509+ return params.AllWatcherId{
510+ AllWatcherId: c.root.resources.register(w).id,
511+ }, nil
512+}
513+
514+type srvClientAllWatcher struct {
515+ *srvResource
516+}
517+
518+func (aw srvClientAllWatcher) Next() (params.AllWatcherNextResults, error) {
519+ deltas, err := aw.resource.(*multiwatcher.Watcher).Next()
520+ return params.AllWatcherNextResults{
521+ Deltas: deltas,
522+ }, err
523+}
524+
525+func (aw srvClientAllWatcher) Stop() error {
526+ return aw.resource.(*multiwatcher.Watcher).Stop()
527+}
528+
529+// ServiceSet implements the server side of Client.ServerSet.
530+func (c *srvClient) ServiceSet(p params.ServiceSet) error {
531+ svc, err := c.root.srv.state.Service(p.ServiceName)
532+ if err != nil {
533+ return err
534+ }
535+ return svc.SetConfig(p.Options)
536+}
537+
538+// ServiceSetYAML implements the server side of Client.ServerSetYAML.
539+func (c *srvClient) ServiceSetYAML(p params.ServiceSetYAML) error {
540+ svc, err := c.root.srv.state.Service(p.ServiceName)
541+ if err != nil {
542+ return err
543+ }
544+ return svc.SetConfigYAML([]byte(p.Config))
545+}
546+
547+// ServiceGet returns the configuration for a service.
548+func (c *srvClient) ServiceGet(args params.ServiceGet) (params.ServiceGetResults, error) {
549+ return statecmd.ServiceGet(c.root.srv.state, args)
550+}
551+
552+// Resolved implements the server side of Client.Resolved.
553+func (c *srvClient) Resolved(p params.Resolved) error {
554+ unit, err := c.root.srv.state.Unit(p.UnitName)
555+ if err != nil {
556+ return err
557+ }
558+ return unit.Resolve(p.Retry)
559+}
560+
561+// ServiceExpose changes the juju-managed firewall to expose any ports that
562+// were also explicitly marked by units as open.
563+func (c *srvClient) ServiceExpose(args params.ServiceExpose) error {
564+ return statecmd.ServiceExpose(c.root.srv.state, args)
565+}
566+
567+// ServiceUnexpose changes the juju-managed firewall to unexpose any ports that
568+// were also explicitly marked by units as open.
569+func (c *srvClient) ServiceUnexpose(args params.ServiceUnexpose) error {
570+ return statecmd.ServiceUnexpose(c.root.srv.state, args)
571+}
572+
573+var CharmStore charm.Repository = charm.Store
574+
575+// ServiceDeploy fetches the charm from the charm store and deploys it. Local
576+// charms are not supported.
577+func (c *srvClient) ServiceDeploy(args params.ServiceDeploy) error {
578+ state := c.root.srv.state
579+ conf, err := state.EnvironConfig()
580+ if err != nil {
581+ return err
582+ }
583+ curl, err := charm.InferURL(args.CharmUrl, conf.DefaultSeries())
584+ if err != nil {
585+ return err
586+ }
587+ conn, err := juju.NewConnFromState(state)
588+ if err != nil {
589+ return err
590+ }
591+ if args.NumUnits == 0 {
592+ args.NumUnits = 1
593+ }
594+ charm, err := conn.PutCharm(curl, CharmStore, false)
595+ if err != nil {
596+ return err
597+ }
598+ serviceName := args.ServiceName
599+ if serviceName == "" {
600+ serviceName = curl.Name
601+ }
602+ deployArgs := juju.DeployServiceParams{
603+ Charm: charm,
604+ ServiceName: serviceName,
605+ NumUnits: args.NumUnits,
606+ // BUG(lp:1162122): Config/ConfigYAML have no tests.
607+ Config: args.Config,
608+ ConfigYAML: args.ConfigYAML,
609+ Constraints: args.Constraints,
610+ }
611+ _, err = conn.DeployService(deployArgs)
612+ return err
613+}
614+
615+// AddServiceUnits adds a given number of units to a service.
616+func (c *srvClient) AddServiceUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) {
617+ units, err := statecmd.AddServiceUnits(c.root.srv.state, args)
618+ if err != nil {
619+ return params.AddServiceUnitsResults{}, err
620+ }
621+ unitNames := make([]string, len(units))
622+ for i, unit := range units {
623+ unitNames[i] = unit.String()
624+ }
625+ return params.AddServiceUnitsResults{Units: unitNames}, nil
626+}
627+
628+// DestroyServiceUnits removes a given set of service units.
629+func (c *srvClient) DestroyServiceUnits(args params.DestroyServiceUnits) error {
630+ return statecmd.DestroyServiceUnits(c.root.srv.state, args)
631+}
632+
633+// ServiceDestroy destroys a given service.
634+func (c *srvClient) ServiceDestroy(args params.ServiceDestroy) error {
635+ return statecmd.ServiceDestroy(c.root.srv.state, args)
636+}
637+
638+// GetServiceConstraints returns the constraints for a given service.
639+func (c *srvClient) GetServiceConstraints(args params.GetServiceConstraints) (params.GetServiceConstraintsResults, error) {
640+ return statecmd.GetServiceConstraints(c.root.srv.state, args)
641+}
642+
643+// SetServiceConstraints sets the constraints for a given service.
644+func (c *srvClient) SetServiceConstraints(args params.SetServiceConstraints) error {
645+ return statecmd.SetServiceConstraints(c.root.srv.state, args)
646+}
647+
648+// AddRelation adds a relation between the specified endpoints and returns the relation info.
649+func (c *srvClient) AddRelation(args params.AddRelation) (params.AddRelationResults, error) {
650+ return statecmd.AddRelation(c.root.srv.state, args)
651+}
652+
653+// DestroyRelation removes the relation between the specified endpoints.
654+func (c *srvClient) DestroyRelation(args params.DestroyRelation) error {
655+ return statecmd.DestroyRelation(c.root.srv.state, args)
656+}
657+
658+// CharmInfo returns information about the requested charm.
659+func (c *srvClient) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) {
660+ curl, err := charm.ParseURL(args.CharmURL)
661+ if err != nil {
662+ return api.CharmInfo{}, err
663+ }
664+ charm, err := c.root.srv.state.Charm(curl)
665+ if err != nil {
666+ return api.CharmInfo{}, err
667+ }
668+ info := api.CharmInfo{
669+ Revision: charm.Revision(),
670+ URL: curl.String(),
671+ Config: charm.Config(),
672+ Meta: charm.Meta(),
673+ }
674+ return info, nil
675+}
676+
677+// EnvironmentInfo returns information about the current environment (default
678+// series and type).
679+func (c *srvClient) EnvironmentInfo() (api.EnvironmentInfo, error) {
680+ conf, err := c.root.srv.state.EnvironConfig()
681+ if err != nil {
682+ return api.EnvironmentInfo{}, err
683+ }
684+ info := api.EnvironmentInfo{
685+ DefaultSeries: conf.DefaultSeries(),
686+ ProviderType: conf.Type(),
687+ Name: conf.Name(),
688+ }
689+ return info, nil
690+}
691+
692+// GetAnnotations returns annotations about a given entity.
693+func (c *srvClient) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) {
694+ entity, err := c.root.srv.state.Annotator(args.Tag)
695+ if err != nil {
696+ return params.GetAnnotationsResults{}, err
697+ }
698+ ann, err := entity.Annotations()
699+ if err != nil {
700+ return params.GetAnnotationsResults{}, err
701+ }
702+ return params.GetAnnotationsResults{Annotations: ann}, nil
703+}
704+
705+// SetAnnotations stores annotations about a given entity.
706+func (c *srvClient) SetAnnotations(args params.SetAnnotations) error {
707+ entity, err := c.root.srv.state.Annotator(args.Tag)
708+ if err != nil {
709+ return err
710+ }
711+ return entity.SetAnnotations(args.Pairs)
712+}
713+
714+// Login logs in with the provided credentials.
715+// All subsequent requests on the connection will
716+// act as the authenticated user.
717+func (a *srvAdmin) Login(c params.Creds) error {
718+ return a.root.user.login(a.root.srv.state, c.AuthTag, c.Password)
719+}
720+
721+// Get retrieves all the details of a machine.
722+func (m *srvMachine) Get() (info params.Machine) {
723+ instId, _ := m.m.InstanceId()
724+ info.InstanceId = string(instId)
725+ info.Life = params.Life(m.m.Life().String())
726+ return
727+}
728+
729+func (m *srvMachine) Watch() (params.EntityWatcherId, error) {
730+ w := m.m.Watch()
731+ if _, ok := <-w.Changes(); !ok {
732+ return params.EntityWatcherId{}, statewatcher.MustErr(w)
733+ }
734+ return params.EntityWatcherId{
735+ EntityWatcherId: m.root.resources.register(w).id,
736+ }, nil
737+}
738+
739+// SetAgentAlive signals that the agent for machine m is alive.
740+func (m *srvMachine) SetAgentAlive() (params.PingerId, error) {
741+ if !m.root.authOwner(m.m) {
742+ return params.PingerId{}, errPerm
743+ }
744+ pinger, err := m.m.SetAgentAlive()
745+ if err != nil {
746+ return params.PingerId{}, err
747+ }
748+ return params.PingerId{
749+ PingerId: m.root.resources.register(pinger).id,
750+ }, nil
751+}
752+
753+// EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying.
754+// It does nothing otherwise. See machine.EnsureDead().
755+func (m *srvMachine) EnsureDead() error {
756+ if !m.root.authOwner(m.m) {
757+ return errPerm
758+ }
759+ return m.m.EnsureDead()
760+}
761+
762+// SetStatus sets the status of the machine.
763+func (m *srvMachine) SetStatus(status params.SetStatus) error {
764+ if !m.root.authOwner(m.m) && !m.root.authEnvironManager() {
765+ return errPerm
766+ }
767+ return m.m.SetStatus(status.Status, status.Info)
768+}
769+
770+func setPassword(e state.TaggedAuthenticator, password string) error {
771+ // Catch expected common case of mispelled
772+ // or missing Password parameter.
773+ if password == "" {
774+ return fmt.Errorf("password is empty")
775+ }
776+ return e.SetPassword(password)
777+}
778+
779+// SetPassword sets the machine's password.
780+func (m *srvMachine) SetPassword(p params.Password) error {
781+ if !m.root.authOwner(m.m) && !m.root.authEnvironManager() {
782+ return errPerm
783+ }
784+ if err := setPassword(m.m, p.Password); err != nil {
785+ return err
786+ }
787+ // Grant access to the mongo state if the machine requires it.
788+ if isMachineWithJob(m.m, state.JobManageEnviron) ||
789+ isMachineWithJob(m.m, state.JobServeAPI) {
790+ return m.m.SetMongoPassword(p.Password)
791+ }
792+ return nil
793+}
794+
795+// Get retrieves all the details of a unit.
796+func (u *srvUnit) Get() (params.Unit, error) {
797+ var ru params.Unit
798+ ru.DeployerTag, _ = u.u.DeployerTag()
799+ // TODO add other unit attributes
800+ return ru, nil
801+}
802+
803+// SetPassword sets the unit's password.
804+func (u *srvUnit) SetPassword(p params.Password) error {
805+ tag := u.root.user.authenticator().Tag()
806+ // Allow:
807+ // - the unit itself.
808+ // - the machine responsible for unit, if unit is principal
809+ // - the unit's principal unit, if unit is subordinate
810+ allow := tag == u.u.Tag()
811+ if !allow {
812+ deployerTag, ok := u.u.DeployerTag()
813+ allow = ok && tag == deployerTag
814+ }
815+ if !allow {
816+ return errPerm
817+ }
818+ return setPassword(u.u, p.Password)
819+}
820+
821+// SetPassword sets the user's password.
822+func (u *srvUser) SetPassword(p params.Password) error {
823+ return setPassword(u.u, p.Password)
824+}
825+
826+// Get retrieves all details of a user.
827+func (u *srvUser) Get() (params.User, error) {
828+ return params.User{}, nil
829+}
830+
831+// authUser holds login details. It's ok to call
832+// its methods concurrently.
833+type authUser struct {
834+ mu sync.Mutex
835+ entity state.TaggedAuthenticator // logged-in entity (access only when mu is locked)
836+}
837+
838+// login authenticates as entity with the given name,.
839+func (u *authUser) login(st *state.State, tag, password string) error {
840+ u.mu.Lock()
841+ defer u.mu.Unlock()
842+ entity, err := st.Authenticator(tag)
843+ if err != nil && !state.IsNotFound(err) {
844+ return err
845+ }
846+ // We return the same error when an entity
847+ // does not exist as for a bad password, so that
848+ // we don't allow unauthenticated users to find information
849+ // about existing entities.
850+ if err != nil || !entity.PasswordValid(password) {
851+ return errBadCreds
852+ }
853+ u.entity = entity
854+ return nil
855+}
856+
857+// authenticator returns the currently logged-in authenticator entity, or nil
858+// if not currently logged on. The returned entity should not be modified
859+// because it may be used concurrently.
860+func (u *authUser) authenticator() state.TaggedAuthenticator {
861+ u.mu.Lock()
862+ defer u.mu.Unlock()
863+ return u.entity
864+}
865+
866+// isMachineWithJob returns whether the given entity is a machine that
867+// is configured to run the given job.
868+func isMachineWithJob(e state.TaggedAuthenticator, j state.MachineJob) bool {
869+ m, ok := e.(*state.Machine)
870+ if !ok {
871+ return false
872+ }
873+ for _, mj := range m.Jobs() {
874+ if mj == j {
875+ return true
876+ }
877+ }
878+ return false
879+}
880+
881+// isAgent returns whether the given entity is an agent.
882+func isAgent(e state.TaggedAuthenticator) bool {
883+ _, isUser := e.(*state.User)
884+ return !isUser
885+}
886+
887+// resource represents the interface provided by state watchers and pingers.
888+type resource interface {
889+ Stop() error
890+}
891+
892+// resources holds all the resources for a connection.
893+type resources struct {
894+ mu sync.Mutex
895+ maxId uint64
896+ rs map[string]*srvResource
897+}
898+
899+// srvResource holds the details of a resource. It also implements the
900+// Stop RPC method for all resources.
901+type srvResource struct {
902+ rs *resources
903+ resource resource
904+ id string
905+}
906+
907+// Stop stops the given resource. It causes any outstanding
908+// Next calls to return a CodeStopped error.
909+// Any subsequent Next calls will return a CodeNotFound
910+// error because the resource will no longer exist.
911+func (r *srvResource) Stop() error {
912+ err := r.resource.Stop()
913+ r.rs.mu.Lock()
914+ defer r.rs.mu.Unlock()
915+ delete(r.rs.rs, r.id)
916+ return err
917+}
918+
919+func newResources() *resources {
920+ return &resources{
921+ rs: make(map[string]*srvResource),
922+ }
923+}
924+
925+// get returns the srvResource registered with the given
926+// id, or nil if there is no such resource.
927+func (rs *resources) get(id string) *srvResource {
928+ rs.mu.Lock()
929+ defer rs.mu.Unlock()
930+ return rs.rs[id]
931+}
932+
933+// register records the given watcher and returns
934+// a srvResource instance for it.
935+func (rs *resources) register(r resource) *srvResource {
936+ rs.mu.Lock()
937+ defer rs.mu.Unlock()
938+ rs.maxId++
939+ sr := &srvResource{
940+ rs: rs,
941+ id: strconv.FormatUint(rs.maxId, 10),
942+ resource: r,
943+ }
944+ rs.rs[sr.id] = sr
945+ return sr
946+}
947+
948+func (rs *resources) stopAll() {
949+ rs.mu.Lock()
950+ defer rs.mu.Unlock()
951+ for _, r := range rs.rs {
952+ if err := r.resource.Stop(); err != nil {
953+ log.Errorf("state/api: error stopping %T resource: %v", r, err)
954+ }
955+ }
956+ rs.rs = make(map[string]*srvResource)
957+}
958
959=== added file 'state/apiserver/apiserver.test'
960Binary files state/apiserver/apiserver.test 1970-01-01 00:00:00 +0000 and state/apiserver/apiserver.test 2013-06-18 12:56:33 +0000 differ
961=== modified file 'state/apiserver/common/interfaces.go'
962--- state/apiserver/common/interfaces.go 2013-06-06 17:09:49 +0000
963+++ state/apiserver/common/interfaces.go 2013-06-18 12:56:33 +0000
964@@ -25,7 +25,7 @@
965
966 // AuthOwner returns whether the authenticated entity is the same
967 // as the given entity.
968- AuthOwner(entity Tagger) bool
969+ AuthOwner(tag string) bool
970
971 // AuthEnvironManager returns whether the authenticated entity is
972 // a machine running the environment manager job.
973
974=== modified file 'state/apiserver/login_test.go'
975--- state/apiserver/login_test.go 2013-06-06 17:55:14 +0000
976+++ state/apiserver/login_test.go 2013-06-18 12:56:33 +0000
977@@ -61,6 +61,10 @@
978 }
979 for i, t := range badLoginTests {
980 c.Logf("test %d; entity %q; password %q", i, t.tag, t.password)
981+ // Note that Open does not log in if the tag and password
982+ // are empty. This allows us to test operations on the connection
983+ // before calling Login, which we could not do if Open
984+ // always logged in.
985 info.Tag = ""
986 info.Password = ""
987 func() {
988@@ -72,9 +76,6 @@
989 c.Assert(err, ErrorMatches, "not logged in")
990 c.Assert(api.ErrCode(err), Equals, api.CodeUnauthorized, Commentf("error %#v", err))
991
992- // TODO (dimitern) This a really awkward way of testing -
993- // calling Login again here to reauth on the same
994- // connection. Seems wrong.
995 err = st.Login(t.tag, t.password)
996 c.Assert(err, ErrorMatches, t.err)
997 c.Assert(api.ErrCode(err), Equals, t.code)
998
999=== renamed directory 'state/apiserver/machiner' => 'state/apiserver/machine'
1000=== added file 'state/apiserver/machine/agent.go'
1001--- state/apiserver/machine/agent.go 1970-01-01 00:00:00 +0000
1002+++ state/apiserver/machine/agent.go 2013-06-18 12:56:33 +0000
1003@@ -0,0 +1,69 @@
1004+// Copyright 2013 Canonical Ltd.
1005+// Licensed under the AGPLv3, see LICENCE file for details.
1006+
1007+// The machine package implements the API interfaces
1008+// used by the machine agent.
1009+package machine
1010+
1011+import (
1012+ "launchpad.net/juju-core/state"
1013+ "launchpad.net/juju-core/state/api/params"
1014+ "launchpad.net/juju-core/state/apiserver/common"
1015+)
1016+
1017+type AgentAPI struct {
1018+ st *state.State
1019+ auth common.Authorizer
1020+}
1021+
1022+// NewAgentAPI returns an object implementing the machine agent API
1023+// for the given
1024+func NewAgentAPI(st *state.State, auth common.Authorizer) (*AgentAPI, error) {
1025+ if !auth.IsLoggedIn() {
1026+ return nil, common.ErrNotLoggedIn
1027+ }
1028+ if !auth.AuthMachineAgent() {
1029+ return nil, common.ErrPerm
1030+ }
1031+ return &AgentAPI{
1032+ st: st,
1033+ auth: auth,
1034+ }, nil
1035+}
1036+
1037+func (api *AgentAPI) GetMachines(args params.Machines) (params.MachineAgentGetMachinesResults, error) {
1038+ results := params.MachineAgentGetMachinesResults{
1039+ Machines: make([]params.MachineAgentGetMachinesResult, len(args.Ids)),
1040+ }
1041+ for i, id := range args.Ids {
1042+ result, err := api.getMachine(id)
1043+ result.Error = common.ServerError(err)
1044+ results.Machines[i] = result
1045+ }
1046+ return results, nil
1047+}
1048+
1049+func (api *AgentAPI) getMachine(id string) (result params.MachineAgentGetMachinesResult, err error) {
1050+ // Allow only for the owner agent.
1051+ // Note: having a bulk API call for this is utter madness, given that
1052+ // this check means we can only ever return a single object.
1053+ if !api.auth.AuthOwner(state.MachineTag(id)) {
1054+ err = common.ErrPerm
1055+ return
1056+ }
1057+ machine, err := api.st.Machine(id)
1058+ if err != nil {
1059+ return
1060+ }
1061+ result.Life = params.Life(machine.Life().String())
1062+ result.Jobs = stateJobsToAPIParamsJobs(machine.Jobs())
1063+ return
1064+}
1065+
1066+func stateJobsToAPIParamsJobs(jobs []state.MachineJob) []params.MachineJob {
1067+ pjobs := make([]params.MachineJob, len(jobs))
1068+ for i, job := range jobs {
1069+ pjobs[i] = params.MachineJob(job.String())
1070+ }
1071+ return pjobs
1072+}
1073
1074=== added file 'state/apiserver/machine/agent_test.go'
1075--- state/apiserver/machine/agent_test.go 1970-01-01 00:00:00 +0000
1076+++ state/apiserver/machine/agent_test.go 2013-06-18 12:56:33 +0000
1077@@ -0,0 +1,91 @@
1078+package machine_test
1079+
1080+import (
1081+ . "launchpad.net/gocheck"
1082+ "launchpad.net/juju-core/state/api"
1083+ "launchpad.net/juju-core/state/api/params"
1084+ "launchpad.net/juju-core/state/apiserver/machine"
1085+)
1086+
1087+type agentSuite struct {
1088+ commonSuite
1089+ agent *machine.AgentAPI
1090+}
1091+
1092+var _ = Suite(&agentSuite{})
1093+
1094+func (s *agentSuite) SetUpTest(c *C) {
1095+ s.commonSuite.SetUpTest(c)
1096+
1097+ // Create a machiner API for machine 1.
1098+ api, err := machine.NewAgentAPI(
1099+ s.State,
1100+ s.authorizer,
1101+ )
1102+ c.Assert(err, IsNil)
1103+ s.agent = api
1104+}
1105+
1106+func (s *agentSuite) TestAgentFailsWithNonMachineAgentUser(c *C) {
1107+ auth := s.authorizer
1108+ auth.machineAgent = false
1109+ api, err := machine.NewAgentAPI(s.State, auth)
1110+ c.Assert(err, NotNil)
1111+ c.Assert(api, IsNil)
1112+ c.Assert(err, ErrorMatches, "permission denied")
1113+}
1114+
1115+func (s *agentSuite) TestAgentFailsWhenNotLoggedIn(c *C) {
1116+ auth := s.authorizer
1117+ auth.loggedIn = false
1118+ api, err := machine.NewAgentAPI(s.State, auth)
1119+ c.Assert(err, NotNil)
1120+ c.Assert(api, IsNil)
1121+ c.Assert(err, ErrorMatches, "not logged in")
1122+}
1123+
1124+func (s *agentSuite) TestGetMachines(c *C) {
1125+ err := s.machine1.Destroy()
1126+ c.Assert(err, IsNil)
1127+ results, err := s.agent.GetMachines(params.Machines{
1128+ Ids: []string{"1", "0", "42"},
1129+ })
1130+ c.Assert(err, IsNil)
1131+ c.Assert(results, DeepEquals, params.MachineAgentGetMachinesResults{
1132+ Machines: []params.MachineAgentGetMachinesResult{{
1133+ Life: "dying",
1134+ Jobs: []params.MachineJob{params.JobHostUnits},
1135+ }, {
1136+ Error: &params.Error{
1137+ Code: api.CodeUnauthorized,
1138+ Message: "permission denied",
1139+ },
1140+ }, {
1141+ Error: &params.Error{
1142+ Code: api.CodeUnauthorized,
1143+ Message: "permission denied",
1144+ },
1145+ }},
1146+ })
1147+}
1148+
1149+func (s *agentSuite) TestGetNotFoundMachine(c *C) {
1150+ err := s.machine1.Destroy()
1151+ c.Assert(err, IsNil)
1152+ err = s.machine1.EnsureDead()
1153+ c.Assert(err, IsNil)
1154+ err = s.machine1.Remove()
1155+ c.Assert(err, IsNil)
1156+ results, err := s.agent.GetMachines(params.Machines{
1157+ Ids: []string{"1"},
1158+ })
1159+ c.Assert(err, IsNil)
1160+ c.Assert(results, DeepEquals, params.MachineAgentGetMachinesResults{
1161+ Machines: []params.MachineAgentGetMachinesResult{{
1162+ Error: &params.Error{
1163+ Code: api.CodeNotFound,
1164+ Message: "machine 1 not found",
1165+ },
1166+ }},
1167+ })
1168+}
1169
1170=== added file 'state/apiserver/machine/common_test.go'
1171--- state/apiserver/machine/common_test.go 1970-01-01 00:00:00 +0000
1172+++ state/apiserver/machine/common_test.go 2013-06-18 12:56:33 +0000
1173@@ -0,0 +1,66 @@
1174+package machine_test
1175+
1176+import (
1177+ . "launchpad.net/gocheck"
1178+ "launchpad.net/juju-core/juju/testing"
1179+ "launchpad.net/juju-core/state"
1180+ coretesting "launchpad.net/juju-core/testing"
1181+ stdtesting "testing"
1182+)
1183+
1184+func Test(t *stdtesting.T) {
1185+ coretesting.MgoTestPackage(t)
1186+}
1187+
1188+type commonSuite struct {
1189+ testing.JujuConnSuite
1190+
1191+ authorizer fakeAuthorizer
1192+
1193+ machine0 *state.Machine
1194+ machine1 *state.Machine
1195+}
1196+
1197+func (s *commonSuite) SetUpTest(c *C) {
1198+ s.JujuConnSuite.SetUpTest(c)
1199+
1200+ var err error
1201+ s.machine0, err = s.State.AddMachine("series", state.JobManageEnviron, state.JobManageState)
1202+ c.Assert(err, IsNil)
1203+
1204+ s.machine1, err = s.State.AddMachine("series", state.JobHostUnits)
1205+ c.Assert(err, IsNil)
1206+
1207+ // Create a fakeAuthorizer so we can check permissions,
1208+ // set up assuming machine 1 has logged in.
1209+ s.authorizer = fakeAuthorizer{
1210+ tag: state.MachineTag(s.machine1.Id()),
1211+ loggedIn: true,
1212+ manager: false,
1213+ machineAgent: true,
1214+ }
1215+}
1216+
1217+// fakeAuthorizer implements the common.Authorizer interface.
1218+type fakeAuthorizer struct {
1219+ tag string
1220+ loggedIn bool
1221+ manager bool
1222+ machineAgent bool
1223+}
1224+
1225+func (fa fakeAuthorizer) IsLoggedIn() bool {
1226+ return fa.loggedIn
1227+}
1228+
1229+func (fa fakeAuthorizer) AuthOwner(tag string) bool {
1230+ return fa.tag == tag
1231+}
1232+
1233+func (fa fakeAuthorizer) AuthEnvironManager() bool {
1234+ return fa.manager
1235+}
1236+
1237+func (fa fakeAuthorizer) AuthMachineAgent() bool {
1238+ return fa.machineAgent
1239+}
1240
1241=== added file 'state/apiserver/machine/machine.test'
1242Binary files state/apiserver/machine/machine.test 1970-01-01 00:00:00 +0000 and state/apiserver/machine/machine.test 2013-06-18 12:56:33 +0000 differ
1243=== modified file 'state/apiserver/machine/machiner.go'
1244--- state/apiserver/machiner/machiner.go 2013-06-06 17:09:49 +0000
1245+++ state/apiserver/machine/machiner.go 2013-06-18 12:56:33 +0000
1246@@ -1,7 +1,7 @@
1247 // Copyright 2013 Canonical Ltd.
1248 // Licensed under the AGPLv3, see LICENCE file for details.
1249
1250-package machiner
1251+package machine
1252
1253 import (
1254 "launchpad.net/juju-core/state"
1255@@ -10,24 +10,24 @@
1256 )
1257
1258 // Machiner implements the API used by the machiner worker.
1259-type Machiner struct {
1260+type MachinerAPI struct {
1261 st *state.State
1262 auth common.Authorizer
1263 }
1264
1265-// New creates a new instance of the Machiner facade.
1266-func New(st *state.State, authorizer common.Authorizer) (*Machiner, error) {
1267+// NewMachinerAPI creates a new instance of the Machiner API.
1268+func NewMachinerAPI(st *state.State, authorizer common.Authorizer) (*MachinerAPI, error) {
1269 if !authorizer.IsLoggedIn() {
1270 return nil, common.ErrNotLoggedIn
1271 }
1272 if !authorizer.AuthMachineAgent() {
1273 return nil, common.ErrPerm
1274 }
1275- return &Machiner{st, authorizer}, nil
1276+ return &MachinerAPI{st, authorizer}, nil
1277 }
1278
1279 // SetStatus sets the status of each given machine.
1280-func (m *Machiner) SetStatus(args params.MachinesSetStatus) (params.ErrorResults, error) {
1281+func (m *MachinerAPI) SetStatus(args params.MachinesSetStatus) (params.ErrorResults, error) {
1282 result := params.ErrorResults{
1283 Errors: make([]*params.Error, len(args.Machines)),
1284 }
1285@@ -38,7 +38,7 @@
1286 machine, err := m.st.Machine(arg.Id)
1287 if err == nil {
1288 // Allow only for the owner agent.
1289- if !m.auth.AuthOwner(machine) {
1290+ if !m.auth.AuthOwner(machine.Tag()) {
1291 err = common.ErrPerm
1292 } else {
1293 err = machine.SetStatus(arg.Status, arg.Info)
1294@@ -50,12 +50,12 @@
1295 }
1296
1297 // Watch starts an EntityWatcher for each given machine.
1298-//func (m *Machiner) Watch(args params.Machines) (params.MachinerWatchResults, error) {
1299+//func (m *MachinerAPI) Watch(args params.Machines) (params.MachinerWatchResults, error) {
1300 // TODO (dimitern) implement this once the watchers can handle bulk ops
1301 //}
1302
1303 // Life returns the lifecycle state of each given machine.
1304-func (m *Machiner) Life(args params.Machines) (params.MachinesLifeResults, error) {
1305+func (m *MachinerAPI) Life(args params.Machines) (params.MachinesLifeResults, error) {
1306 result := params.MachinesLifeResults{
1307 Machines: make([]params.MachineLifeResult, len(args.Ids)),
1308 }
1309@@ -66,7 +66,7 @@
1310 machine, err := m.st.Machine(id)
1311 if err == nil {
1312 // Allow only for the owner agent.
1313- if !m.auth.AuthOwner(machine) {
1314+ if !m.auth.AuthOwner(machine.Tag()) {
1315 err = common.ErrPerm
1316 } else {
1317 result.Machines[i].Life = params.Life(machine.Life().String())
1318@@ -79,7 +79,7 @@
1319
1320 // EnsureDead changes the lifecycle of each given machine to Dead if
1321 // it's Alive or Dying. It does nothing otherwise.
1322-func (m *Machiner) EnsureDead(args params.Machines) (params.ErrorResults, error) {
1323+func (m *MachinerAPI) EnsureDead(args params.Machines) (params.ErrorResults, error) {
1324 result := params.ErrorResults{
1325 Errors: make([]*params.Error, len(args.Ids)),
1326 }
1327@@ -90,7 +90,7 @@
1328 machine, err := m.st.Machine(id)
1329 if err == nil {
1330 // Allow only for the owner agent.
1331- if !m.auth.AuthOwner(machine) {
1332+ if !m.auth.AuthOwner(machine.Tag()) {
1333 err = common.ErrPerm
1334 } else {
1335 err = machine.EnsureDead()
1336
1337=== modified file 'state/apiserver/machine/machiner_test.go'
1338--- state/apiserver/machiner/machiner_test.go 2013-06-06 17:09:49 +0000
1339+++ state/apiserver/machine/machiner_test.go 2013-06-18 12:56:33 +0000
1340@@ -1,85 +1,33 @@
1341 // Copyright 2013 Canonical Ltd.
1342 // Licensed under the AGPLv3, see LICENCE file for details.
1343
1344-package machiner_test
1345+package machine_test
1346
1347 import (
1348 . "launchpad.net/gocheck"
1349- "launchpad.net/juju-core/juju/testing"
1350 "launchpad.net/juju-core/state"
1351 "launchpad.net/juju-core/state/api"
1352 "launchpad.net/juju-core/state/api/params"
1353- "launchpad.net/juju-core/state/apiserver/common"
1354- "launchpad.net/juju-core/state/apiserver/machiner"
1355- coretesting "launchpad.net/juju-core/testing"
1356- stdtesting "testing"
1357+ "launchpad.net/juju-core/state/apiserver/machine"
1358 )
1359
1360-func Test(t *stdtesting.T) {
1361- coretesting.MgoTestPackage(t)
1362-}
1363-
1364 type machinerSuite struct {
1365- testing.JujuConnSuite
1366-
1367- authorizer *fakeAuthorizer
1368- machiner *machiner.Machiner
1369-
1370- machine0 *state.Machine
1371- machine1 *state.Machine
1372+ commonSuite
1373+ machiner *machine.MachinerAPI
1374 }
1375
1376 var _ = Suite(&machinerSuite{})
1377
1378-// fakeAuthorizer implements the common.Authorizer interface.
1379-type fakeAuthorizer struct {
1380- tag string
1381- loggedIn bool
1382- manager bool
1383- machineAgent bool
1384-}
1385-
1386-func (fa *fakeAuthorizer) IsLoggedIn() bool {
1387- return fa.loggedIn
1388-}
1389-
1390-func (fa *fakeAuthorizer) AuthOwner(entity common.Tagger) bool {
1391- return entity.Tag() == fa.tag
1392-}
1393-
1394-func (fa *fakeAuthorizer) AuthEnvironManager() bool {
1395- return fa.manager
1396-}
1397-
1398-func (fa *fakeAuthorizer) AuthMachineAgent() bool {
1399- return fa.machineAgent
1400-}
1401-
1402 func (s *machinerSuite) SetUpTest(c *C) {
1403- s.JujuConnSuite.SetUpTest(c)
1404-
1405- // Create a machine so that we can login as its agent
1406- var err error
1407- s.machine0, err = s.State.AddMachine("series", state.JobManageEnviron)
1408- c.Assert(err, IsNil)
1409- // Add another normal machine
1410- s.machine1, err = s.State.AddMachine("series", state.JobHostUnits)
1411- c.Assert(err, IsNil)
1412-
1413- // Create a fakeAuthorizer so we can check permissions.
1414- s.authorizer = &fakeAuthorizer{
1415- tag: state.MachineTag(s.machine1.Id()),
1416- loggedIn: true,
1417- manager: false,
1418- machineAgent: true,
1419- }
1420+ s.commonSuite.SetUpTest(c)
1421
1422 // Create a machiner API for machine 1.
1423- s.machiner, err = machiner.New(
1424+ machiner, err := machine.NewMachinerAPI(
1425 s.State,
1426 s.authorizer,
1427 )
1428 c.Assert(err, IsNil)
1429+ s.machiner = machiner
1430 }
1431
1432 func (s *machinerSuite) assertError(c *C, err *params.Error, code, messageRegexp string) {
1433@@ -91,7 +39,7 @@
1434 func (s *machinerSuite) TestMachinerFailsWithNonMachineAgentUser(c *C) {
1435 anAuthorizer := s.authorizer
1436 anAuthorizer.machineAgent = false
1437- aMachiner, err := machiner.New(s.State, anAuthorizer)
1438+ aMachiner, err := machine.NewMachinerAPI(s.State, anAuthorizer)
1439 c.Assert(err, NotNil)
1440 c.Assert(aMachiner, IsNil)
1441 c.Assert(err, ErrorMatches, "permission denied")
1442@@ -100,7 +48,7 @@
1443 func (s *machinerSuite) TestMachinerFailsWhenNotLoggedIn(c *C) {
1444 anAuthorizer := s.authorizer
1445 anAuthorizer.loggedIn = false
1446- aMachiner, err := machiner.New(s.State, anAuthorizer)
1447+ aMachiner, err := machine.NewMachinerAPI(s.State, anAuthorizer)
1448 c.Assert(err, NotNil)
1449 c.Assert(aMachiner, IsNil)
1450 c.Assert(err, ErrorMatches, "not logged in")
1451
1452=== removed file 'state/apiserver/resource.go'
1453--- state/apiserver/resource.go 2013-05-24 19:03:39 +0000
1454+++ state/apiserver/resource.go 1970-01-01 00:00:00 +0000
1455@@ -1,82 +0,0 @@
1456-// Copyright 2013 Canonical Ltd.
1457-// Licensed under the AGPLv3, see LICENCE file for details.
1458-
1459-package apiserver
1460-
1461-import (
1462- "launchpad.net/juju-core/log"
1463- "strconv"
1464- "sync"
1465-)
1466-
1467-// resource represents the interface provided by state watchers and pingers.
1468-type resource interface {
1469- Stop() error
1470-}
1471-
1472-// resources holds all the resources for a connection.
1473-type resources struct {
1474- mu sync.Mutex
1475- maxId uint64
1476- rs map[string]*srvResource
1477-}
1478-
1479-// srvResource holds the details of a resource. It also implements the
1480-// Stop RPC method for all resources.
1481-type srvResource struct {
1482- rs *resources
1483- resource resource
1484- id string
1485-}
1486-
1487-// Stop stops the given resource. It causes any outstanding
1488-// Next calls to return a CodeStopped error.
1489-// Any subsequent Next calls will return a CodeNotFound
1490-// error because the resource will no longer exist.
1491-func (r *srvResource) Stop() error {
1492- err := r.resource.Stop()
1493- r.rs.mu.Lock()
1494- defer r.rs.mu.Unlock()
1495- delete(r.rs.rs, r.id)
1496- return err
1497-}
1498-
1499-func newResources() *resources {
1500- return &resources{
1501- rs: make(map[string]*srvResource),
1502- }
1503-}
1504-
1505-// get returns the srvResource registered with the given
1506-// id, or nil if there is no such resource.
1507-func (rs *resources) get(id string) *srvResource {
1508- rs.mu.Lock()
1509- defer rs.mu.Unlock()
1510- return rs.rs[id]
1511-}
1512-
1513-// register records the given watcher and returns
1514-// a srvResource instance for it.
1515-func (rs *resources) register(r resource) *srvResource {
1516- rs.mu.Lock()
1517- defer rs.mu.Unlock()
1518- rs.maxId++
1519- sr := &srvResource{
1520- rs: rs,
1521- id: strconv.FormatUint(rs.maxId, 10),
1522- resource: r,
1523- }
1524- rs.rs[sr.id] = sr
1525- return sr
1526-}
1527-
1528-func (rs *resources) stopAll() {
1529- rs.mu.Lock()
1530- defer rs.mu.Unlock()
1531- for _, r := range rs.rs {
1532- if err := r.resource.Stop(); err != nil {
1533- log.Errorf("state/api: error stopping %T resource: %v", r, err)
1534- }
1535- }
1536- rs.rs = make(map[string]*srvResource)
1537-}
1538
1539=== modified file 'state/apiserver/root.go'
1540--- state/apiserver/root.go 2013-06-06 17:09:49 +0000
1541+++ state/apiserver/root.go 2013-06-18 12:56:33 +0000
1542@@ -6,7 +6,7 @@
1543 import (
1544 "launchpad.net/juju-core/state"
1545 "launchpad.net/juju-core/state/apiserver/common"
1546- "launchpad.net/juju-core/state/apiserver/machiner"
1547+ "launchpad.net/juju-core/state/apiserver/machine"
1548 "launchpad.net/juju-core/state/multiwatcher"
1549 )
1550
1551@@ -83,15 +83,24 @@
1552 return nil
1553 }
1554
1555-// Machiner returns an object that provides access to the Machiner API
1556-// facade. The id argument is reserved for future use and currently
1557-// needs to be empty.
1558-func (r *srvRoot) Machiner(id string) (*machiner.Machiner, error) {
1559- if id != "" {
1560- // Safeguard id for possible future use.
1561- return nil, common.ErrBadId
1562- }
1563- return machiner.New(r.srv.state, r)
1564+// Machiner returns an object that provides access to the Machiner API.
1565+// The id argument is reserved for future use and must currently
1566+// be empty.
1567+func (r *srvRoot) Machiner(id string) (*machine.MachinerAPI, error) {
1568+ if id != "" {
1569+ return nil, common.ErrBadId
1570+ }
1571+ return machine.NewMachinerAPI(r.srv.state, r)
1572+}
1573+
1574+// MachineAgent returns an object that provides access to the machine
1575+// agent API. The id argument is reserved for future use and must currently
1576+// be empty.
1577+func (r *srvRoot) MachineAgent(id string) (*machine.AgentAPI, error) {
1578+ if id != "" {
1579+ return nil, common.ErrBadId
1580+ }
1581+ return machine.NewAgentAPI(r.srv.state, r)
1582 }
1583
1584 // User returns an object that provides
1585@@ -237,10 +246,10 @@
1586 }
1587
1588 // AuthOwner returns whether the authenticated user's tag matches the
1589-// given entity's tag.
1590-func (r *srvRoot) AuthOwner(entity common.Tagger) bool {
1591+// given entity tag.
1592+func (r *srvRoot) AuthOwner(tag string) bool {
1593 authUser := r.user.authenticator()
1594- return authUser.Tag() == entity.Tag()
1595+ return authUser.Tag() == tag
1596 }
1597
1598 // AuthEnvironManager returns whether the authenticated user is a
1599
1600=== renamed file 'state/apiserver/apiserver.go' => 'state/apiserver/server.go'
1601=== added directory 'state/apiserver/testing'
1602=== modified file 'state/machine.go'
1603--- state/machine.go 2013-06-18 12:56:33 +0000
1604+++ state/machine.go 2013-06-18 12:56:33 +0000
1605@@ -32,13 +32,13 @@
1606 _ MachineJob = iota
1607 JobHostUnits
1608 JobManageEnviron
1609- JobServeAPI
1610+ JobManageState
1611 )
1612
1613-var jobNames = []string{
1614- JobHostUnits: "JobHostUnits",
1615- JobManageEnviron: "JobManageEnviron",
1616- JobServeAPI: "JobServeAPI",
1617+var jobNames = []params.MachineJob{
1618+ JobHostUnits: params.JobHostUnits,
1619+ JobManageEnviron: params.JobManageEnviron,
1620+ JobManageState: params.JobManageState,
1621 }
1622
1623 func (job MachineJob) String() string {
1624@@ -46,7 +46,7 @@
1625 if j <= 0 || j >= len(jobNames) {
1626 return fmt.Sprintf("<unknown job %d>", j)
1627 }
1628- return jobNames[j]
1629+ return string(jobNames[j])
1630 }
1631
1632 // machineDoc represents the internal state of a machine in MongoDB.
1633
1634=== modified file 'state/state_test.go'
1635--- state/state_test.go 2013-06-18 12:56:33 +0000
1636+++ state/state_test.go 2013-06-18 12:56:33 +0000
1637@@ -117,7 +117,7 @@
1638 }{
1639 {state.JobHostUnits, "JobHostUnits"},
1640 {state.JobManageEnviron, "JobManageEnviron"},
1641- {state.JobServeAPI, "JobServeAPI"},
1642+ {state.JobManageState, "JobManageState"},
1643 {0, "<unknown job 0>"},
1644 {5, "<unknown job 5>"},
1645 }
1646@@ -155,7 +155,7 @@
1647 allJobs := []state.MachineJob{
1648 state.JobHostUnits,
1649 state.JobManageEnviron,
1650- state.JobServeAPI,
1651+ state.JobManageState,
1652 }
1653 m1, err := s.State.AddMachine("blahblah", allJobs...)
1654 c.Assert(err, IsNil)

Subscribers

People subscribed via source and target branches