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
=== modified file 'cmd/jujud/bootstrap.go'
--- cmd/jujud/bootstrap.go 2013-06-18 12:56:33 +0000
+++ cmd/jujud/bootstrap.go 2013-06-18 12:56:33 +0000
@@ -114,7 +114,7 @@
114 // Upgrader, and it's a capability we'll always want to have114 // Upgrader, and it's a capability we'll always want to have
115 // available for the aforementioned use case.115 // available for the aforementioned use case.
116 jobs := []state.MachineJob{116 jobs := []state.MachineJob{
117 state.JobManageEnviron, state.JobServeAPI, state.JobHostUnits,117 state.JobManageEnviron, state.JobManageState, state.JobHostUnits,
118 }118 }
119 m, err := st.InjectMachine(version.Current.Series, c.Constraints, instanceId, jobs...)119 m, err := st.InjectMachine(version.Current.Series, c.Constraints, instanceId, jobs...)
120 if err != nil {120 if err != nil {
121121
=== modified file 'cmd/jujud/bootstrap_test.go'
--- cmd/jujud/bootstrap_test.go 2013-06-18 12:56:33 +0000
+++ cmd/jujud/bootstrap_test.go 2013-06-18 12:56:33 +0000
@@ -166,7 +166,7 @@
166 m, err := st.Machine("0")166 m, err := st.Machine("0")
167 c.Assert(err, IsNil)167 c.Assert(err, IsNil)
168 c.Assert(m.Jobs(), DeepEquals, []state.MachineJob{168 c.Assert(m.Jobs(), DeepEquals, []state.MachineJob{
169 state.JobManageEnviron, state.JobServeAPI, state.JobHostUnits,169 state.JobManageEnviron, state.JobManageState, state.JobHostUnits,
170 })170 })
171}171}
172172
173173
=== modified file 'cmd/jujud/machine.go'
--- cmd/jujud/machine.go 2013-06-11 13:52:19 +0000
+++ cmd/jujud/machine.go 2013-06-18 12:56:33 +0000
@@ -122,7 +122,7 @@
122 tasks = append(tasks,122 tasks = append(tasks,
123 provisioner.NewProvisioner(st, a.MachineId),123 provisioner.NewProvisioner(st, a.MachineId),
124 firewaller.NewFirewaller(st))124 firewaller.NewFirewaller(st))
125 case state.JobServeAPI:125 case state.JobManageState:
126 // Ignore because it's started independently.126 // Ignore because it's started independently.
127 continue127 continue
128 default:128 default:
@@ -173,7 +173,7 @@
173 m := entity.(*state.Machine)173 m := entity.(*state.Machine)
174 runAPI := false174 runAPI := false
175 for _, job := range m.Jobs() {175 for _, job := range m.Jobs() {
176 if job == state.JobServeAPI {176 if job == state.JobManageState {
177 runAPI = true177 runAPI = true
178 break178 break
179 }179 }
180180
=== modified file 'cmd/jujud/machine_test.go'
--- cmd/jujud/machine_test.go 2013-06-18 12:56:33 +0000
+++ cmd/jujud/machine_test.go 2013-06-18 12:56:33 +0000
@@ -110,7 +110,7 @@
110}110}
111111
112func (s *MachineSuite) TestWithDeadMachine(c *C) {112func (s *MachineSuite) TestWithDeadMachine(c *C) {
113 m, _, _ := s.primeAgent(c, state.JobHostUnits, state.JobServeAPI)113 m, _, _ := s.primeAgent(c, state.JobHostUnits, state.JobManageState)
114 err := m.EnsureDead()114 err := m.EnsureDead()
115 c.Assert(err, IsNil)115 c.Assert(err, IsNil)
116 a := s.newAgent(c, m)116 a := s.newAgent(c, m)
@@ -248,7 +248,7 @@
248}248}
249249
250func (s *MachineSuite) TestUpgrade(c *C) {250func (s *MachineSuite) TestUpgrade(c *C) {
251 m, conf, currentTools := s.primeAgent(c, state.JobServeAPI, state.JobManageEnviron, state.JobHostUnits)251 m, conf, currentTools := s.primeAgent(c, state.JobManageState, state.JobManageEnviron, state.JobHostUnits)
252 addAPIInfo(conf, m)252 addAPIInfo(conf, m)
253 err := conf.Write()253 err := conf.Write()
254 c.Assert(err, IsNil)254 c.Assert(err, IsNil)
@@ -270,7 +270,7 @@
270}270}
271271
272func (s *MachineSuite) TestServeAPI(c *C) {272func (s *MachineSuite) TestServeAPI(c *C) {
273 stm, conf, _ := s.primeAgent(c, state.JobServeAPI)273 stm, conf, _ := s.primeAgent(c, state.JobManageState)
274 addAPIInfo(conf, stm)274 addAPIInfo(conf, stm)
275 err := conf.Write()275 err := conf.Write()
276 c.Assert(err, IsNil)276 c.Assert(err, IsNil)
@@ -320,7 +320,7 @@
320}}320}}
321321
322func (s *MachineSuite) TestServeAPIWithBadConf(c *C) {322func (s *MachineSuite) TestServeAPIWithBadConf(c *C) {
323 m, conf, _ := s.primeAgent(c, state.JobServeAPI)323 m, conf, _ := s.primeAgent(c, state.JobManageState)
324 addAPIInfo(conf, m)324 addAPIInfo(conf, m)
325 for i, t := range serveAPIWithBadConfTests {325 for i, t := range serveAPIWithBadConfTests {
326 c.Logf("test %d: %q", i, t.err)326 c.Logf("test %d: %q", i, t.err)
327327
=== modified file 'environs/config/config_test.go'
--- environs/config/config_test.go 2013-06-18 12:56:33 +0000
+++ environs/config/config_test.go 2013-06-10 10:30:13 +0000
@@ -130,7 +130,7 @@
130 "name": "my-name",130 "name": "my-name",
131 "ca-cert": invalidCACert,131 "ca-cert": invalidCACert,
132 },132 },
133 err: `bad CA certificate/key in configuration: (asn1:|ASN\.1) syntax error:.*`,133 err: "bad CA certificate/key in configuration: ASN.1 syntax error:.*",
134 }, {134 }, {
135 about: "Invalid CA key",135 about: "Invalid CA key",
136 attrs: attrs{136 attrs: attrs{
137137
=== modified file 'environs/dummy/environs.go'
--- environs/dummy/environs.go 2013-06-18 12:56:33 +0000
+++ environs/dummy/environs.go 2013-06-18 12:56:33 +0000
@@ -456,9 +456,6 @@
456 return fmt.Errorf("environment is already bootstrapped")456 return fmt.Errorf("environment is already bootstrapped")
457 }457 }
458 if e.ecfg().stateServer() {458 if e.ecfg().stateServer() {
459 // TODO(rog) factor out relevant code from cmd/jujud/bootstrap.go
460 // so that we can call it here.
461
462 info := stateInfo()459 info := stateInfo()
463 st, err := state.Initialize(info, cfg, state.DefaultDialOpts())460 st, err := state.Initialize(info, cfg, state.DefaultDialOpts())
464 if err != nil {461 if err != nil {
465462
=== modified file 'juju/testing/repo.go'
--- juju/testing/repo.go 2013-05-09 21:51:05 +0000
+++ juju/testing/repo.go 2013-06-18 12:56:33 +0000
@@ -52,7 +52,6 @@
52 c.Assert(ch.URL(), DeepEquals, expectCurl)52 c.Assert(ch.URL(), DeepEquals, expectCurl)
53 s.AssertCharmUploaded(c, expectCurl)53 s.AssertCharmUploaded(c, expectCurl)
54 units, err := svc.AllUnits()54 units, err := svc.AllUnits()
55 c.Logf("Service units: %+v", units)
56 c.Assert(err, IsNil)55 c.Assert(err, IsNil)
57 c.Assert(units, HasLen, unitCount)56 c.Assert(units, HasLen, unitCount)
58 s.AssertUnitMachines(c, units)57 s.AssertUnitMachines(c, units)
5958
=== renamed file 'state/api/client.go' => 'state/api/apiclient.go'
=== renamed file 'state/api/apiclient.go' => 'state/api/client.go'
=== renamed file 'state/api/apierror.go' => 'state/api/error.go'
=== modified file 'state/api/params/params.go'
--- state/api/params/params.go 2013-06-18 12:56:33 +0000
+++ state/api/params/params.go 2013-06-18 12:56:33 +0000
@@ -28,6 +28,12 @@
28 return e.Message28 return e.Message
29}29}
3030
31// GoString implements fmt.GoStringer. It means that a *Error shows its
32// contents correctly when printed with %#v.
33func (e Error) GoString() string {
34 return fmt.Sprintf("&params.Error{%q, %q}", e.Code, e.Message)
35}
36
31// ErrorResults holds the results of calling a bulk operation which37// ErrorResults holds the results of calling a bulk operation which
32// mutates multiple entites, like Machiner.SetStatus. The order and38// mutates multiple entites, like Machiner.SetStatus. The order and
33// number of elements matches the entities specified in the request.39// number of elements matches the entities specified in the request.
@@ -87,6 +93,30 @@
87 Machines []MachineLifeResult93 Machines []MachineLifeResult
88}94}
8995
96// MachineAgentGetMachinesResults holds the results of a
97// machineagent.API.GetMachines call.
98type MachineAgentGetMachinesResults struct {
99 Machines []MachineAgentGetMachinesResult
100}
101
102// MachineJob values define responsibilities that machines may be
103// expected to fulfil.
104type MachineJob string
105
106const (
107 JobHostUnits MachineJob = "JobHostUnits"
108 JobManageEnviron MachineJob = "JobManageEnviron"
109 JobManageState MachineJob = "JobManageState"
110)
111
112// MachineAgentGetMachinesResult holds the results of a
113// machineagent.API.GetMachines call for a single machine.
114type MachineAgentGetMachinesResult struct {
115 Life Life
116 Jobs []MachineJob
117 Error *Error
118}
119
90// ServiceDeploy holds the parameters for making the ServiceDeploy call.120// ServiceDeploy holds the parameters for making the ServiceDeploy call.
91type ServiceDeploy struct {121type ServiceDeploy struct {
92 ServiceName string122 ServiceName string
93123
=== removed file 'state/apiserver/admin.go'
--- state/apiserver/admin.go 2013-05-24 19:03:39 +0000
+++ state/apiserver/admin.go 1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package apiserver
5
6import "launchpad.net/juju-core/state/api/params"
7
8// srvAdmin is the only object that unlogged-in
9// clients can access. It holds any methods
10// that are needed to log in.
11type srvAdmin struct {
12 root *srvRoot
13}
14
15// Login logs in with the provided credentials.
16// All subsequent requests on the connection will
17// act as the authenticated user.
18func (a *srvAdmin) Login(c params.Creds) error {
19 return a.root.user.login(a.root.srv.state, c.AuthTag, c.Password)
20}
210
=== added file 'state/apiserver/apiserver.go'
--- state/apiserver/apiserver.go 1970-01-01 00:00:00 +0000
+++ state/apiserver/apiserver.go 2013-06-18 12:56:33 +0000
@@ -0,0 +1,750 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package apiserver
5
6import (
7 "fmt"
8 "launchpad.net/juju-core/charm"
9 "launchpad.net/juju-core/juju"
10 "launchpad.net/juju-core/log"
11 "launchpad.net/juju-core/state"
12 "launchpad.net/juju-core/state/api"
13 "launchpad.net/juju-core/state/api/params"
14 "launchpad.net/juju-core/state/multiwatcher"
15 "launchpad.net/juju-core/state/presence"
16 "launchpad.net/juju-core/state/statecmd"
17 statewatcher "launchpad.net/juju-core/state/watcher"
18 "strconv"
19 "sync"
20)
21
22// srvRoot represents a single client's connection to the state.
23type srvRoot struct {
24 admin *srvAdmin
25 client *srvClient
26 srv *Server
27 resources *resources
28
29 user authUser
30}
31
32// srvAdmin is the only object that unlogged-in
33// clients can access. It holds any methods
34// that are needed to log in.
35type srvAdmin struct {
36 root *srvRoot
37}
38
39// srvMachine serves API methods on a machine.
40type srvMachine struct {
41 root *srvRoot
42 m *state.Machine
43}
44
45// srvUnit serves API methods on a unit.
46type srvUnit struct {
47 root *srvRoot
48 u *state.Unit
49}
50
51// srvUser serves API methods on a state User.
52type srvUser struct {
53 root *srvRoot
54 u *state.User
55}
56
57// srvClient serves client-specific API methods.
58type srvClient struct {
59 root *srvRoot
60}
61
62func newStateServer(srv *Server) *srvRoot {
63 r := &srvRoot{
64 srv: srv,
65 resources: newResources(),
66 }
67 r.admin = &srvAdmin{
68 root: r,
69 }
70 r.client = &srvClient{
71 root: r,
72 }
73 return r
74}
75
76// Kill implements rpc.Killer. It cleans up any resources that need
77// cleaning up to ensure that all outstanding requests return.
78func (r *srvRoot) Kill() {
79 r.resources.stopAll()
80}
81
82// Admin returns an object that provides API access
83// to methods that can be called even when not
84// authenticated.
85func (r *srvRoot) Admin(id string) (*srvAdmin, error) {
86 if id != "" {
87 // Safeguard id for possible future use.
88 return nil, errBadId
89 }
90 return r.admin, nil
91}
92
93// requireAgent checks whether the current client is an agent and hence
94// may access the agent APIs. We filter out non-agents when calling one
95// of the accessor functions (Machine, Unit, etc) which avoids us making
96// the check in every single request method.
97func (r *srvRoot) requireAgent() error {
98 e := r.user.authenticator()
99 if e == nil {
100 return errNotLoggedIn
101 }
102 if !isAgent(e) {
103 return errPerm
104 }
105 return nil
106}
107
108// requireClient returns an error unless the current
109// client is a juju client user.
110func (r *srvRoot) requireClient() error {
111 e := r.user.authenticator()
112 if e == nil {
113 return errNotLoggedIn
114 }
115 if isAgent(e) {
116 return errPerm
117 }
118 return nil
119}
120
121// Machine returns an object that provides
122// API access to methods on a state.Machine.
123func (r *srvRoot) Machine(id string) (*srvMachine, error) {
124 if err := r.requireAgent(); err != nil {
125 return nil, err
126 }
127 m, err := r.srv.state.Machine(id)
128 if err != nil {
129 return nil, err
130 }
131 return &srvMachine{
132 root: r,
133 m: m,
134 }, nil
135}
136
137// Unit returns an object that provides
138// API access to methods on a state.Unit.
139func (r *srvRoot) Unit(name string) (*srvUnit, error) {
140 if err := r.requireAgent(); err != nil {
141 return nil, err
142 }
143 u, err := r.srv.state.Unit(name)
144 if err != nil {
145 return nil, err
146 }
147 return &srvUnit{
148 root: r,
149 u: u,
150 }, nil
151}
152
153// User returns an object that provides
154// API access to methods on a state.User.
155func (r *srvRoot) User(name string) (*srvUser, error) {
156 // Any user is allowed to access their own user object.
157 // We check at this level rather than at the operation
158 // level to stop malicious probing for current user names.
159 // When we provide support for user administration,
160 // this will need to be changed to allow access to
161 // the administrator.
162 e := r.user.authenticator()
163 if e == nil {
164 return nil, errNotLoggedIn
165 }
166 if e.Tag() != name {
167 return nil, errPerm
168 }
169 u, err := r.srv.state.User(name)
170 if err != nil {
171 return nil, err
172 }
173 return &srvUser{
174 root: r,
175 u: u,
176 }, nil
177}
178
179// Pinger returns an object that provides API access to methods on a
180// presence.Pinger. Each client has its own current set of pingers,
181// stored in r.resources.
182func (r *srvRoot) Pinger(id string) (*srvResource, error) {
183 if err := r.requireAgent(); err != nil {
184 return nil, err
185 }
186 pinger := r.resources.get(id)
187 if pinger == nil {
188 return nil, errUnknownPinger
189 }
190 if _, ok := pinger.resource.(*presence.Pinger); !ok {
191 return nil, errUnknownPinger
192 }
193 return pinger, nil
194}
195
196// EntityWatcher returns an object that provides
197// API access to methods on a state.EntityWatcher.
198// Each client has its own current set of watchers, stored
199// in r.resources.
200func (r *srvRoot) EntityWatcher(id string) (srvEntityWatcher, error) {
201 if err := r.requireAgent(); err != nil {
202 return srvEntityWatcher{}, err
203 }
204 watcher := r.resources.get(id)
205 if watcher == nil {
206 return srvEntityWatcher{}, errUnknownWatcher
207 }
208 if _, ok := watcher.resource.(*state.EntityWatcher); !ok {
209 return srvEntityWatcher{}, errUnknownWatcher
210 }
211 return srvEntityWatcher{watcher}, nil
212}
213
214// AllWatcher returns an object that provides API access to methods on
215// a state/multiwatcher.Watcher, which watches any changes to the
216// state. Each client has its own current set of watchers, stored in
217// r.resources.
218func (r *srvRoot) AllWatcher(id string) (srvClientAllWatcher, error) {
219 if err := r.requireClient(); err != nil {
220 return srvClientAllWatcher{}, err
221 }
222 watcher := r.resources.get(id)
223 if watcher == nil {
224 return srvClientAllWatcher{}, errUnknownWatcher
225 }
226 if _, ok := watcher.resource.(*multiwatcher.Watcher); !ok {
227 return srvClientAllWatcher{}, errUnknownWatcher
228 }
229 return srvClientAllWatcher{watcher}, nil
230
231}
232
233// Client returns an object that provides access
234// to methods accessible to non-agent clients.
235func (r *srvRoot) Client(id string) (*srvClient, error) {
236 if err := r.requireClient(); err != nil {
237 return nil, err
238 }
239 if id != "" {
240 // Safeguard id for possible future use.
241 return nil, errBadId
242 }
243 return r.client, nil
244}
245
246type tagger interface {
247 Tag() string
248}
249
250// authOwner returns whether the authenticated user's tag matches the
251// given entity's tag.
252func (r *srvRoot) authOwner(entity tagger) bool {
253 authUser := r.user.authenticator()
254 return authUser.Tag() == entity.Tag()
255}
256
257// authEnvironManager returns whether the authenticated user is a
258// machine with running the ManageEnviron job.
259func (r *srvRoot) authEnvironManager() bool {
260 authUser := r.user.authenticator()
261 return isMachineWithJob(authUser, state.JobManageEnviron)
262}
263
264type srvEntityWatcher struct {
265 *srvResource
266}
267
268// Next returns when a change has occurred to the
269// entity being watched since the most recent call to Next
270// or the Watch call that created the EntityWatcher.
271func (w srvEntityWatcher) Next() error {
272 watcher := w.resource.(*state.EntityWatcher)
273 if _, ok := <-watcher.Changes(); ok {
274 return nil
275 }
276 err := watcher.Err()
277 if err == nil {
278 err = errStoppedWatcher
279 }
280 return err
281}
282
283func (c *srvClient) Status() (api.Status, error) {
284 ms, err := c.root.srv.state.AllMachines()
285 if err != nil {
286 return api.Status{}, err
287 }
288 status := api.Status{
289 Machines: make(map[string]api.MachineInfo),
290 }
291 for _, m := range ms {
292 instId, _ := m.InstanceId()
293 status.Machines[m.Id()] = api.MachineInfo{
294 InstanceId: string(instId),
295 }
296 }
297 return status, nil
298}
299
300func (c *srvClient) WatchAll() (params.AllWatcherId, error) {
301 w := c.root.srv.state.Watch()
302 return params.AllWatcherId{
303 AllWatcherId: c.root.resources.register(w).id,
304 }, nil
305}
306
307type srvClientAllWatcher struct {
308 *srvResource
309}
310
311func (aw srvClientAllWatcher) Next() (params.AllWatcherNextResults, error) {
312 deltas, err := aw.resource.(*multiwatcher.Watcher).Next()
313 return params.AllWatcherNextResults{
314 Deltas: deltas,
315 }, err
316}
317
318func (aw srvClientAllWatcher) Stop() error {
319 return aw.resource.(*multiwatcher.Watcher).Stop()
320}
321
322// ServiceSet implements the server side of Client.ServerSet.
323func (c *srvClient) ServiceSet(p params.ServiceSet) error {
324 svc, err := c.root.srv.state.Service(p.ServiceName)
325 if err != nil {
326 return err
327 }
328 return svc.SetConfig(p.Options)
329}
330
331// ServiceSetYAML implements the server side of Client.ServerSetYAML.
332func (c *srvClient) ServiceSetYAML(p params.ServiceSetYAML) error {
333 svc, err := c.root.srv.state.Service(p.ServiceName)
334 if err != nil {
335 return err
336 }
337 return svc.SetConfigYAML([]byte(p.Config))
338}
339
340// ServiceGet returns the configuration for a service.
341func (c *srvClient) ServiceGet(args params.ServiceGet) (params.ServiceGetResults, error) {
342 return statecmd.ServiceGet(c.root.srv.state, args)
343}
344
345// Resolved implements the server side of Client.Resolved.
346func (c *srvClient) Resolved(p params.Resolved) error {
347 unit, err := c.root.srv.state.Unit(p.UnitName)
348 if err != nil {
349 return err
350 }
351 return unit.Resolve(p.Retry)
352}
353
354// ServiceExpose changes the juju-managed firewall to expose any ports that
355// were also explicitly marked by units as open.
356func (c *srvClient) ServiceExpose(args params.ServiceExpose) error {
357 return statecmd.ServiceExpose(c.root.srv.state, args)
358}
359
360// ServiceUnexpose changes the juju-managed firewall to unexpose any ports that
361// were also explicitly marked by units as open.
362func (c *srvClient) ServiceUnexpose(args params.ServiceUnexpose) error {
363 return statecmd.ServiceUnexpose(c.root.srv.state, args)
364}
365
366var CharmStore charm.Repository = charm.Store
367
368// ServiceDeploy fetches the charm from the charm store and deploys it. Local
369// charms are not supported.
370func (c *srvClient) ServiceDeploy(args params.ServiceDeploy) error {
371 state := c.root.srv.state
372 conf, err := state.EnvironConfig()
373 if err != nil {
374 return err
375 }
376 curl, err := charm.InferURL(args.CharmUrl, conf.DefaultSeries())
377 if err != nil {
378 return err
379 }
380 conn, err := juju.NewConnFromState(state)
381 if err != nil {
382 return err
383 }
384 if args.NumUnits == 0 {
385 args.NumUnits = 1
386 }
387 charm, err := conn.PutCharm(curl, CharmStore, false)
388 if err != nil {
389 return err
390 }
391 serviceName := args.ServiceName
392 if serviceName == "" {
393 serviceName = curl.Name
394 }
395 deployArgs := juju.DeployServiceParams{
396 Charm: charm,
397 ServiceName: serviceName,
398 NumUnits: args.NumUnits,
399 // BUG(lp:1162122): Config/ConfigYAML have no tests.
400 Config: args.Config,
401 ConfigYAML: args.ConfigYAML,
402 Constraints: args.Constraints,
403 }
404 _, err = conn.DeployService(deployArgs)
405 return err
406}
407
408// AddServiceUnits adds a given number of units to a service.
409func (c *srvClient) AddServiceUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) {
410 units, err := statecmd.AddServiceUnits(c.root.srv.state, args)
411 if err != nil {
412 return params.AddServiceUnitsResults{}, err
413 }
414 unitNames := make([]string, len(units))
415 for i, unit := range units {
416 unitNames[i] = unit.String()
417 }
418 return params.AddServiceUnitsResults{Units: unitNames}, nil
419}
420
421// DestroyServiceUnits removes a given set of service units.
422func (c *srvClient) DestroyServiceUnits(args params.DestroyServiceUnits) error {
423 return statecmd.DestroyServiceUnits(c.root.srv.state, args)
424}
425
426// ServiceDestroy destroys a given service.
427func (c *srvClient) ServiceDestroy(args params.ServiceDestroy) error {
428 return statecmd.ServiceDestroy(c.root.srv.state, args)
429}
430
431// GetServiceConstraints returns the constraints for a given service.
432func (c *srvClient) GetServiceConstraints(args params.GetServiceConstraints) (params.GetServiceConstraintsResults, error) {
433 return statecmd.GetServiceConstraints(c.root.srv.state, args)
434}
435
436// SetServiceConstraints sets the constraints for a given service.
437func (c *srvClient) SetServiceConstraints(args params.SetServiceConstraints) error {
438 return statecmd.SetServiceConstraints(c.root.srv.state, args)
439}
440
441// AddRelation adds a relation between the specified endpoints and returns the relation info.
442func (c *srvClient) AddRelation(args params.AddRelation) (params.AddRelationResults, error) {
443 return statecmd.AddRelation(c.root.srv.state, args)
444}
445
446// DestroyRelation removes the relation between the specified endpoints.
447func (c *srvClient) DestroyRelation(args params.DestroyRelation) error {
448 return statecmd.DestroyRelation(c.root.srv.state, args)
449}
450
451// CharmInfo returns information about the requested charm.
452func (c *srvClient) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) {
453 curl, err := charm.ParseURL(args.CharmURL)
454 if err != nil {
455 return api.CharmInfo{}, err
456 }
457 charm, err := c.root.srv.state.Charm(curl)
458 if err != nil {
459 return api.CharmInfo{}, err
460 }
461 info := api.CharmInfo{
462 Revision: charm.Revision(),
463 URL: curl.String(),
464 Config: charm.Config(),
465 Meta: charm.Meta(),
466 }
467 return info, nil
468}
469
470// EnvironmentInfo returns information about the current environment (default
471// series and type).
472func (c *srvClient) EnvironmentInfo() (api.EnvironmentInfo, error) {
473 conf, err := c.root.srv.state.EnvironConfig()
474 if err != nil {
475 return api.EnvironmentInfo{}, err
476 }
477 info := api.EnvironmentInfo{
478 DefaultSeries: conf.DefaultSeries(),
479 ProviderType: conf.Type(),
480 Name: conf.Name(),
481 }
482 return info, nil
483}
484
485// GetAnnotations returns annotations about a given entity.
486func (c *srvClient) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) {
487 entity, err := c.root.srv.state.Annotator(args.Tag)
488 if err != nil {
489 return params.GetAnnotationsResults{}, err
490 }
491 ann, err := entity.Annotations()
492 if err != nil {
493 return params.GetAnnotationsResults{}, err
494 }
495 return params.GetAnnotationsResults{Annotations: ann}, nil
496}
497
498// SetAnnotations stores annotations about a given entity.
499func (c *srvClient) SetAnnotations(args params.SetAnnotations) error {
500 entity, err := c.root.srv.state.Annotator(args.Tag)
501 if err != nil {
502 return err
503 }
504 return entity.SetAnnotations(args.Pairs)
505}
506
507// Login logs in with the provided credentials.
508// All subsequent requests on the connection will
509// act as the authenticated user.
510func (a *srvAdmin) Login(c params.Creds) error {
511 return a.root.user.login(a.root.srv.state, c.AuthTag, c.Password)
512}
513
514// Get retrieves all the details of a machine.
515func (m *srvMachine) Get() (info params.Machine) {
516 instId, _ := m.m.InstanceId()
517 info.InstanceId = string(instId)
518 info.Life = params.Life(m.m.Life().String())
519 return
520}
521
522func (m *srvMachine) Watch() (params.EntityWatcherId, error) {
523 w := m.m.Watch()
524 if _, ok := <-w.Changes(); !ok {
525 return params.EntityWatcherId{}, statewatcher.MustErr(w)
526 }
527 return params.EntityWatcherId{
528 EntityWatcherId: m.root.resources.register(w).id,
529 }, nil
530}
531
532// SetAgentAlive signals that the agent for machine m is alive.
533func (m *srvMachine) SetAgentAlive() (params.PingerId, error) {
534 if !m.root.authOwner(m.m) {
535 return params.PingerId{}, errPerm
536 }
537 pinger, err := m.m.SetAgentAlive()
538 if err != nil {
539 return params.PingerId{}, err
540 }
541 return params.PingerId{
542 PingerId: m.root.resources.register(pinger).id,
543 }, nil
544}
545
546// EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying.
547// It does nothing otherwise. See machine.EnsureDead().
548func (m *srvMachine) EnsureDead() error {
549 if !m.root.authOwner(m.m) {
550 return errPerm
551 }
552 return m.m.EnsureDead()
553}
554
555// SetStatus sets the status of the machine.
556func (m *srvMachine) SetStatus(status params.SetStatus) error {
557 if !m.root.authOwner(m.m) && !m.root.authEnvironManager() {
558 return errPerm
559 }
560 return m.m.SetStatus(status.Status, status.Info)
561}
562
563func setPassword(e state.TaggedAuthenticator, password string) error {
564 // Catch expected common case of mispelled
565 // or missing Password parameter.
566 if password == "" {
567 return fmt.Errorf("password is empty")
568 }
569 return e.SetPassword(password)
570}
571
572// SetPassword sets the machine's password.
573func (m *srvMachine) SetPassword(p params.Password) error {
574 if !m.root.authOwner(m.m) && !m.root.authEnvironManager() {
575 return errPerm
576 }
577 if err := setPassword(m.m, p.Password); err != nil {
578 return err
579 }
580 // Grant access to the mongo state if the machine requires it.
581 if isMachineWithJob(m.m, state.JobManageEnviron) ||
582 isMachineWithJob(m.m, state.JobServeAPI) {
583 return m.m.SetMongoPassword(p.Password)
584 }
585 return nil
586}
587
588// Get retrieves all the details of a unit.
589func (u *srvUnit) Get() (params.Unit, error) {
590 var ru params.Unit
591 ru.DeployerTag, _ = u.u.DeployerTag()
592 // TODO add other unit attributes
593 return ru, nil
594}
595
596// SetPassword sets the unit's password.
597func (u *srvUnit) SetPassword(p params.Password) error {
598 tag := u.root.user.authenticator().Tag()
599 // Allow:
600 // - the unit itself.
601 // - the machine responsible for unit, if unit is principal
602 // - the unit's principal unit, if unit is subordinate
603 allow := tag == u.u.Tag()
604 if !allow {
605 deployerTag, ok := u.u.DeployerTag()
606 allow = ok && tag == deployerTag
607 }
608 if !allow {
609 return errPerm
610 }
611 return setPassword(u.u, p.Password)
612}
613
614// SetPassword sets the user's password.
615func (u *srvUser) SetPassword(p params.Password) error {
616 return setPassword(u.u, p.Password)
617}
618
619// Get retrieves all details of a user.
620func (u *srvUser) Get() (params.User, error) {
621 return params.User{}, nil
622}
623
624// authUser holds login details. It's ok to call
625// its methods concurrently.
626type authUser struct {
627 mu sync.Mutex
628 entity state.TaggedAuthenticator // logged-in entity (access only when mu is locked)
629}
630
631// login authenticates as entity with the given name,.
632func (u *authUser) login(st *state.State, tag, password string) error {
633 u.mu.Lock()
634 defer u.mu.Unlock()
635 entity, err := st.Authenticator(tag)
636 if err != nil && !state.IsNotFound(err) {
637 return err
638 }
639 // We return the same error when an entity
640 // does not exist as for a bad password, so that
641 // we don't allow unauthenticated users to find information
642 // about existing entities.
643 if err != nil || !entity.PasswordValid(password) {
644 return errBadCreds
645 }
646 u.entity = entity
647 return nil
648}
649
650// authenticator returns the currently logged-in authenticator entity, or nil
651// if not currently logged on. The returned entity should not be modified
652// because it may be used concurrently.
653func (u *authUser) authenticator() state.TaggedAuthenticator {
654 u.mu.Lock()
655 defer u.mu.Unlock()
656 return u.entity
657}
658
659// isMachineWithJob returns whether the given entity is a machine that
660// is configured to run the given job.
661func isMachineWithJob(e state.TaggedAuthenticator, j state.MachineJob) bool {
662 m, ok := e.(*state.Machine)
663 if !ok {
664 return false
665 }
666 for _, mj := range m.Jobs() {
667 if mj == j {
668 return true
669 }
670 }
671 return false
672}
673
674// isAgent returns whether the given entity is an agent.
675func isAgent(e state.TaggedAuthenticator) bool {
676 _, isUser := e.(*state.User)
677 return !isUser
678}
679
680// resource represents the interface provided by state watchers and pingers.
681type resource interface {
682 Stop() error
683}
684
685// resources holds all the resources for a connection.
686type resources struct {
687 mu sync.Mutex
688 maxId uint64
689 rs map[string]*srvResource
690}
691
692// srvResource holds the details of a resource. It also implements the
693// Stop RPC method for all resources.
694type srvResource struct {
695 rs *resources
696 resource resource
697 id string
698}
699
700// Stop stops the given resource. It causes any outstanding
701// Next calls to return a CodeStopped error.
702// Any subsequent Next calls will return a CodeNotFound
703// error because the resource will no longer exist.
704func (r *srvResource) Stop() error {
705 err := r.resource.Stop()
706 r.rs.mu.Lock()
707 defer r.rs.mu.Unlock()
708 delete(r.rs.rs, r.id)
709 return err
710}
711
712func newResources() *resources {
713 return &resources{
714 rs: make(map[string]*srvResource),
715 }
716}
717
718// get returns the srvResource registered with the given
719// id, or nil if there is no such resource.
720func (rs *resources) get(id string) *srvResource {
721 rs.mu.Lock()
722 defer rs.mu.Unlock()
723 return rs.rs[id]
724}
725
726// register records the given watcher and returns
727// a srvResource instance for it.
728func (rs *resources) register(r resource) *srvResource {
729 rs.mu.Lock()
730 defer rs.mu.Unlock()
731 rs.maxId++
732 sr := &srvResource{
733 rs: rs,
734 id: strconv.FormatUint(rs.maxId, 10),
735 resource: r,
736 }
737 rs.rs[sr.id] = sr
738 return sr
739}
740
741func (rs *resources) stopAll() {
742 rs.mu.Lock()
743 defer rs.mu.Unlock()
744 for _, r := range rs.rs {
745 if err := r.resource.Stop(); err != nil {
746 log.Errorf("state/api: error stopping %T resource: %v", r, err)
747 }
748 }
749 rs.rs = make(map[string]*srvResource)
750}
0751
=== added file 'state/apiserver/apiserver.test'
1Binary files state/apiserver/apiserver.test 1970-01-01 00:00:00 +0000 and state/apiserver/apiserver.test 2013-06-18 12:56:33 +0000 differ752Binary 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
=== modified file 'state/apiserver/common/interfaces.go'
--- state/apiserver/common/interfaces.go 2013-06-06 17:09:49 +0000
+++ state/apiserver/common/interfaces.go 2013-06-18 12:56:33 +0000
@@ -25,7 +25,7 @@
2525
26 // AuthOwner returns whether the authenticated entity is the same26 // AuthOwner returns whether the authenticated entity is the same
27 // as the given entity.27 // as the given entity.
28 AuthOwner(entity Tagger) bool28 AuthOwner(tag string) bool
2929
30 // AuthEnvironManager returns whether the authenticated entity is30 // AuthEnvironManager returns whether the authenticated entity is
31 // a machine running the environment manager job.31 // a machine running the environment manager job.
3232
=== modified file 'state/apiserver/login_test.go'
--- state/apiserver/login_test.go 2013-06-06 17:55:14 +0000
+++ state/apiserver/login_test.go 2013-06-18 12:56:33 +0000
@@ -61,6 +61,10 @@
61 }61 }
62 for i, t := range badLoginTests {62 for i, t := range badLoginTests {
63 c.Logf("test %d; entity %q; password %q", i, t.tag, t.password)63 c.Logf("test %d; entity %q; password %q", i, t.tag, t.password)
64 // Note that Open does not log in if the tag and password
65 // are empty. This allows us to test operations on the connection
66 // before calling Login, which we could not do if Open
67 // always logged in.
64 info.Tag = ""68 info.Tag = ""
65 info.Password = ""69 info.Password = ""
66 func() {70 func() {
@@ -72,9 +76,6 @@
72 c.Assert(err, ErrorMatches, "not logged in")76 c.Assert(err, ErrorMatches, "not logged in")
73 c.Assert(api.ErrCode(err), Equals, api.CodeUnauthorized, Commentf("error %#v", err))77 c.Assert(api.ErrCode(err), Equals, api.CodeUnauthorized, Commentf("error %#v", err))
7478
75 // TODO (dimitern) This a really awkward way of testing -
76 // calling Login again here to reauth on the same
77 // connection. Seems wrong.
78 err = st.Login(t.tag, t.password)79 err = st.Login(t.tag, t.password)
79 c.Assert(err, ErrorMatches, t.err)80 c.Assert(err, ErrorMatches, t.err)
80 c.Assert(api.ErrCode(err), Equals, t.code)81 c.Assert(api.ErrCode(err), Equals, t.code)
8182
=== renamed directory 'state/apiserver/machiner' => 'state/apiserver/machine'
=== added file 'state/apiserver/machine/agent.go'
--- state/apiserver/machine/agent.go 1970-01-01 00:00:00 +0000
+++ state/apiserver/machine/agent.go 2013-06-18 12:56:33 +0000
@@ -0,0 +1,69 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4// The machine package implements the API interfaces
5// used by the machine agent.
6package machine
7
8import (
9 "launchpad.net/juju-core/state"
10 "launchpad.net/juju-core/state/api/params"
11 "launchpad.net/juju-core/state/apiserver/common"
12)
13
14type AgentAPI struct {
15 st *state.State
16 auth common.Authorizer
17}
18
19// NewAgentAPI returns an object implementing the machine agent API
20// for the given
21func NewAgentAPI(st *state.State, auth common.Authorizer) (*AgentAPI, error) {
22 if !auth.IsLoggedIn() {
23 return nil, common.ErrNotLoggedIn
24 }
25 if !auth.AuthMachineAgent() {
26 return nil, common.ErrPerm
27 }
28 return &AgentAPI{
29 st: st,
30 auth: auth,
31 }, nil
32}
33
34func (api *AgentAPI) GetMachines(args params.Machines) (params.MachineAgentGetMachinesResults, error) {
35 results := params.MachineAgentGetMachinesResults{
36 Machines: make([]params.MachineAgentGetMachinesResult, len(args.Ids)),
37 }
38 for i, id := range args.Ids {
39 result, err := api.getMachine(id)
40 result.Error = common.ServerError(err)
41 results.Machines[i] = result
42 }
43 return results, nil
44}
45
46func (api *AgentAPI) getMachine(id string) (result params.MachineAgentGetMachinesResult, err error) {
47 // Allow only for the owner agent.
48 // Note: having a bulk API call for this is utter madness, given that
49 // this check means we can only ever return a single object.
50 if !api.auth.AuthOwner(state.MachineTag(id)) {
51 err = common.ErrPerm
52 return
53 }
54 machine, err := api.st.Machine(id)
55 if err != nil {
56 return
57 }
58 result.Life = params.Life(machine.Life().String())
59 result.Jobs = stateJobsToAPIParamsJobs(machine.Jobs())
60 return
61}
62
63func stateJobsToAPIParamsJobs(jobs []state.MachineJob) []params.MachineJob {
64 pjobs := make([]params.MachineJob, len(jobs))
65 for i, job := range jobs {
66 pjobs[i] = params.MachineJob(job.String())
67 }
68 return pjobs
69}
070
=== added file 'state/apiserver/machine/agent_test.go'
--- state/apiserver/machine/agent_test.go 1970-01-01 00:00:00 +0000
+++ state/apiserver/machine/agent_test.go 2013-06-18 12:56:33 +0000
@@ -0,0 +1,91 @@
1package machine_test
2
3import (
4 . "launchpad.net/gocheck"
5 "launchpad.net/juju-core/state/api"
6 "launchpad.net/juju-core/state/api/params"
7 "launchpad.net/juju-core/state/apiserver/machine"
8)
9
10type agentSuite struct {
11 commonSuite
12 agent *machine.AgentAPI
13}
14
15var _ = Suite(&agentSuite{})
16
17func (s *agentSuite) SetUpTest(c *C) {
18 s.commonSuite.SetUpTest(c)
19
20 // Create a machiner API for machine 1.
21 api, err := machine.NewAgentAPI(
22 s.State,
23 s.authorizer,
24 )
25 c.Assert(err, IsNil)
26 s.agent = api
27}
28
29func (s *agentSuite) TestAgentFailsWithNonMachineAgentUser(c *C) {
30 auth := s.authorizer
31 auth.machineAgent = false
32 api, err := machine.NewAgentAPI(s.State, auth)
33 c.Assert(err, NotNil)
34 c.Assert(api, IsNil)
35 c.Assert(err, ErrorMatches, "permission denied")
36}
37
38func (s *agentSuite) TestAgentFailsWhenNotLoggedIn(c *C) {
39 auth := s.authorizer
40 auth.loggedIn = false
41 api, err := machine.NewAgentAPI(s.State, auth)
42 c.Assert(err, NotNil)
43 c.Assert(api, IsNil)
44 c.Assert(err, ErrorMatches, "not logged in")
45}
46
47func (s *agentSuite) TestGetMachines(c *C) {
48 err := s.machine1.Destroy()
49 c.Assert(err, IsNil)
50 results, err := s.agent.GetMachines(params.Machines{
51 Ids: []string{"1", "0", "42"},
52 })
53 c.Assert(err, IsNil)
54 c.Assert(results, DeepEquals, params.MachineAgentGetMachinesResults{
55 Machines: []params.MachineAgentGetMachinesResult{{
56 Life: "dying",
57 Jobs: []params.MachineJob{params.JobHostUnits},
58 }, {
59 Error: &params.Error{
60 Code: api.CodeUnauthorized,
61 Message: "permission denied",
62 },
63 }, {
64 Error: &params.Error{
65 Code: api.CodeUnauthorized,
66 Message: "permission denied",
67 },
68 }},
69 })
70}
71
72func (s *agentSuite) TestGetNotFoundMachine(c *C) {
73 err := s.machine1.Destroy()
74 c.Assert(err, IsNil)
75 err = s.machine1.EnsureDead()
76 c.Assert(err, IsNil)
77 err = s.machine1.Remove()
78 c.Assert(err, IsNil)
79 results, err := s.agent.GetMachines(params.Machines{
80 Ids: []string{"1"},
81 })
82 c.Assert(err, IsNil)
83 c.Assert(results, DeepEquals, params.MachineAgentGetMachinesResults{
84 Machines: []params.MachineAgentGetMachinesResult{{
85 Error: &params.Error{
86 Code: api.CodeNotFound,
87 Message: "machine 1 not found",
88 },
89 }},
90 })
91}
092
=== added file 'state/apiserver/machine/common_test.go'
--- state/apiserver/machine/common_test.go 1970-01-01 00:00:00 +0000
+++ state/apiserver/machine/common_test.go 2013-06-18 12:56:33 +0000
@@ -0,0 +1,66 @@
1package machine_test
2
3import (
4 . "launchpad.net/gocheck"
5 "launchpad.net/juju-core/juju/testing"
6 "launchpad.net/juju-core/state"
7 coretesting "launchpad.net/juju-core/testing"
8 stdtesting "testing"
9)
10
11func Test(t *stdtesting.T) {
12 coretesting.MgoTestPackage(t)
13}
14
15type commonSuite struct {
16 testing.JujuConnSuite
17
18 authorizer fakeAuthorizer
19
20 machine0 *state.Machine
21 machine1 *state.Machine
22}
23
24func (s *commonSuite) SetUpTest(c *C) {
25 s.JujuConnSuite.SetUpTest(c)
26
27 var err error
28 s.machine0, err = s.State.AddMachine("series", state.JobManageEnviron, state.JobManageState)
29 c.Assert(err, IsNil)
30
31 s.machine1, err = s.State.AddMachine("series", state.JobHostUnits)
32 c.Assert(err, IsNil)
33
34 // Create a fakeAuthorizer so we can check permissions,
35 // set up assuming machine 1 has logged in.
36 s.authorizer = fakeAuthorizer{
37 tag: state.MachineTag(s.machine1.Id()),
38 loggedIn: true,
39 manager: false,
40 machineAgent: true,
41 }
42}
43
44// fakeAuthorizer implements the common.Authorizer interface.
45type fakeAuthorizer struct {
46 tag string
47 loggedIn bool
48 manager bool
49 machineAgent bool
50}
51
52func (fa fakeAuthorizer) IsLoggedIn() bool {
53 return fa.loggedIn
54}
55
56func (fa fakeAuthorizer) AuthOwner(tag string) bool {
57 return fa.tag == tag
58}
59
60func (fa fakeAuthorizer) AuthEnvironManager() bool {
61 return fa.manager
62}
63
64func (fa fakeAuthorizer) AuthMachineAgent() bool {
65 return fa.machineAgent
66}
067
=== added file 'state/apiserver/machine/machine.test'
1Binary 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 differ68Binary 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
=== modified file 'state/apiserver/machine/machiner.go'
--- state/apiserver/machiner/machiner.go 2013-06-06 17:09:49 +0000
+++ state/apiserver/machine/machiner.go 2013-06-18 12:56:33 +0000
@@ -1,7 +1,7 @@
1// Copyright 2013 Canonical Ltd.1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.2// Licensed under the AGPLv3, see LICENCE file for details.
33
4package machiner4package machine
55
6import (6import (
7 "launchpad.net/juju-core/state"7 "launchpad.net/juju-core/state"
@@ -10,24 +10,24 @@
10)10)
1111
12// Machiner implements the API used by the machiner worker.12// Machiner implements the API used by the machiner worker.
13type Machiner struct {13type MachinerAPI struct {
14 st *state.State14 st *state.State
15 auth common.Authorizer15 auth common.Authorizer
16}16}
1717
18// New creates a new instance of the Machiner facade.18// NewMachinerAPI creates a new instance of the Machiner API.
19func New(st *state.State, authorizer common.Authorizer) (*Machiner, error) {19func NewMachinerAPI(st *state.State, authorizer common.Authorizer) (*MachinerAPI, error) {
20 if !authorizer.IsLoggedIn() {20 if !authorizer.IsLoggedIn() {
21 return nil, common.ErrNotLoggedIn21 return nil, common.ErrNotLoggedIn
22 }22 }
23 if !authorizer.AuthMachineAgent() {23 if !authorizer.AuthMachineAgent() {
24 return nil, common.ErrPerm24 return nil, common.ErrPerm
25 }25 }
26 return &Machiner{st, authorizer}, nil26 return &MachinerAPI{st, authorizer}, nil
27}27}
2828
29// SetStatus sets the status of each given machine.29// SetStatus sets the status of each given machine.
30func (m *Machiner) SetStatus(args params.MachinesSetStatus) (params.ErrorResults, error) {30func (m *MachinerAPI) SetStatus(args params.MachinesSetStatus) (params.ErrorResults, error) {
31 result := params.ErrorResults{31 result := params.ErrorResults{
32 Errors: make([]*params.Error, len(args.Machines)),32 Errors: make([]*params.Error, len(args.Machines)),
33 }33 }
@@ -38,7 +38,7 @@
38 machine, err := m.st.Machine(arg.Id)38 machine, err := m.st.Machine(arg.Id)
39 if err == nil {39 if err == nil {
40 // Allow only for the owner agent.40 // Allow only for the owner agent.
41 if !m.auth.AuthOwner(machine) {41 if !m.auth.AuthOwner(machine.Tag()) {
42 err = common.ErrPerm42 err = common.ErrPerm
43 } else {43 } else {
44 err = machine.SetStatus(arg.Status, arg.Info)44 err = machine.SetStatus(arg.Status, arg.Info)
@@ -50,12 +50,12 @@
50}50}
5151
52// Watch starts an EntityWatcher for each given machine.52// Watch starts an EntityWatcher for each given machine.
53//func (m *Machiner) Watch(args params.Machines) (params.MachinerWatchResults, error) {53//func (m *MachinerAPI) Watch(args params.Machines) (params.MachinerWatchResults, error) {
54// TODO (dimitern) implement this once the watchers can handle bulk ops54// TODO (dimitern) implement this once the watchers can handle bulk ops
55//}55//}
5656
57// Life returns the lifecycle state of each given machine.57// Life returns the lifecycle state of each given machine.
58func (m *Machiner) Life(args params.Machines) (params.MachinesLifeResults, error) {58func (m *MachinerAPI) Life(args params.Machines) (params.MachinesLifeResults, error) {
59 result := params.MachinesLifeResults{59 result := params.MachinesLifeResults{
60 Machines: make([]params.MachineLifeResult, len(args.Ids)),60 Machines: make([]params.MachineLifeResult, len(args.Ids)),
61 }61 }
@@ -66,7 +66,7 @@
66 machine, err := m.st.Machine(id)66 machine, err := m.st.Machine(id)
67 if err == nil {67 if err == nil {
68 // Allow only for the owner agent.68 // Allow only for the owner agent.
69 if !m.auth.AuthOwner(machine) {69 if !m.auth.AuthOwner(machine.Tag()) {
70 err = common.ErrPerm70 err = common.ErrPerm
71 } else {71 } else {
72 result.Machines[i].Life = params.Life(machine.Life().String())72 result.Machines[i].Life = params.Life(machine.Life().String())
@@ -79,7 +79,7 @@
7979
80// EnsureDead changes the lifecycle of each given machine to Dead if80// EnsureDead changes the lifecycle of each given machine to Dead if
81// it's Alive or Dying. It does nothing otherwise.81// it's Alive or Dying. It does nothing otherwise.
82func (m *Machiner) EnsureDead(args params.Machines) (params.ErrorResults, error) {82func (m *MachinerAPI) EnsureDead(args params.Machines) (params.ErrorResults, error) {
83 result := params.ErrorResults{83 result := params.ErrorResults{
84 Errors: make([]*params.Error, len(args.Ids)),84 Errors: make([]*params.Error, len(args.Ids)),
85 }85 }
@@ -90,7 +90,7 @@
90 machine, err := m.st.Machine(id)90 machine, err := m.st.Machine(id)
91 if err == nil {91 if err == nil {
92 // Allow only for the owner agent.92 // Allow only for the owner agent.
93 if !m.auth.AuthOwner(machine) {93 if !m.auth.AuthOwner(machine.Tag()) {
94 err = common.ErrPerm94 err = common.ErrPerm
95 } else {95 } else {
96 err = machine.EnsureDead()96 err = machine.EnsureDead()
9797
=== modified file 'state/apiserver/machine/machiner_test.go'
--- state/apiserver/machiner/machiner_test.go 2013-06-06 17:09:49 +0000
+++ state/apiserver/machine/machiner_test.go 2013-06-18 12:56:33 +0000
@@ -1,85 +1,33 @@
1// Copyright 2013 Canonical Ltd.1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.2// Licensed under the AGPLv3, see LICENCE file for details.
33
4package machiner_test4package machine_test
55
6import (6import (
7 . "launchpad.net/gocheck"7 . "launchpad.net/gocheck"
8 "launchpad.net/juju-core/juju/testing"
9 "launchpad.net/juju-core/state"8 "launchpad.net/juju-core/state"
10 "launchpad.net/juju-core/state/api"9 "launchpad.net/juju-core/state/api"
11 "launchpad.net/juju-core/state/api/params"10 "launchpad.net/juju-core/state/api/params"
12 "launchpad.net/juju-core/state/apiserver/common"11 "launchpad.net/juju-core/state/apiserver/machine"
13 "launchpad.net/juju-core/state/apiserver/machiner"
14 coretesting "launchpad.net/juju-core/testing"
15 stdtesting "testing"
16)12)
1713
18func Test(t *stdtesting.T) {
19 coretesting.MgoTestPackage(t)
20}
21
22type machinerSuite struct {14type machinerSuite struct {
23 testing.JujuConnSuite15 commonSuite
2416 machiner *machine.MachinerAPI
25 authorizer *fakeAuthorizer
26 machiner *machiner.Machiner
27
28 machine0 *state.Machine
29 machine1 *state.Machine
30}17}
3118
32var _ = Suite(&machinerSuite{})19var _ = Suite(&machinerSuite{})
3320
34// fakeAuthorizer implements the common.Authorizer interface.
35type fakeAuthorizer struct {
36 tag string
37 loggedIn bool
38 manager bool
39 machineAgent bool
40}
41
42func (fa *fakeAuthorizer) IsLoggedIn() bool {
43 return fa.loggedIn
44}
45
46func (fa *fakeAuthorizer) AuthOwner(entity common.Tagger) bool {
47 return entity.Tag() == fa.tag
48}
49
50func (fa *fakeAuthorizer) AuthEnvironManager() bool {
51 return fa.manager
52}
53
54func (fa *fakeAuthorizer) AuthMachineAgent() bool {
55 return fa.machineAgent
56}
57
58func (s *machinerSuite) SetUpTest(c *C) {21func (s *machinerSuite) SetUpTest(c *C) {
59 s.JujuConnSuite.SetUpTest(c)22 s.commonSuite.SetUpTest(c)
60
61 // Create a machine so that we can login as its agent
62 var err error
63 s.machine0, err = s.State.AddMachine("series", state.JobManageEnviron)
64 c.Assert(err, IsNil)
65 // Add another normal machine
66 s.machine1, err = s.State.AddMachine("series", state.JobHostUnits)
67 c.Assert(err, IsNil)
68
69 // Create a fakeAuthorizer so we can check permissions.
70 s.authorizer = &fakeAuthorizer{
71 tag: state.MachineTag(s.machine1.Id()),
72 loggedIn: true,
73 manager: false,
74 machineAgent: true,
75 }
7623
77 // Create a machiner API for machine 1.24 // Create a machiner API for machine 1.
78 s.machiner, err = machiner.New(25 machiner, err := machine.NewMachinerAPI(
79 s.State,26 s.State,
80 s.authorizer,27 s.authorizer,
81 )28 )
82 c.Assert(err, IsNil)29 c.Assert(err, IsNil)
30 s.machiner = machiner
83}31}
8432
85func (s *machinerSuite) assertError(c *C, err *params.Error, code, messageRegexp string) {33func (s *machinerSuite) assertError(c *C, err *params.Error, code, messageRegexp string) {
@@ -91,7 +39,7 @@
91func (s *machinerSuite) TestMachinerFailsWithNonMachineAgentUser(c *C) {39func (s *machinerSuite) TestMachinerFailsWithNonMachineAgentUser(c *C) {
92 anAuthorizer := s.authorizer40 anAuthorizer := s.authorizer
93 anAuthorizer.machineAgent = false41 anAuthorizer.machineAgent = false
94 aMachiner, err := machiner.New(s.State, anAuthorizer)42 aMachiner, err := machine.NewMachinerAPI(s.State, anAuthorizer)
95 c.Assert(err, NotNil)43 c.Assert(err, NotNil)
96 c.Assert(aMachiner, IsNil)44 c.Assert(aMachiner, IsNil)
97 c.Assert(err, ErrorMatches, "permission denied")45 c.Assert(err, ErrorMatches, "permission denied")
@@ -100,7 +48,7 @@
100func (s *machinerSuite) TestMachinerFailsWhenNotLoggedIn(c *C) {48func (s *machinerSuite) TestMachinerFailsWhenNotLoggedIn(c *C) {
101 anAuthorizer := s.authorizer49 anAuthorizer := s.authorizer
102 anAuthorizer.loggedIn = false50 anAuthorizer.loggedIn = false
103 aMachiner, err := machiner.New(s.State, anAuthorizer)51 aMachiner, err := machine.NewMachinerAPI(s.State, anAuthorizer)
104 c.Assert(err, NotNil)52 c.Assert(err, NotNil)
105 c.Assert(aMachiner, IsNil)53 c.Assert(aMachiner, IsNil)
106 c.Assert(err, ErrorMatches, "not logged in")54 c.Assert(err, ErrorMatches, "not logged in")
10755
=== removed file 'state/apiserver/resource.go'
--- state/apiserver/resource.go 2013-05-24 19:03:39 +0000
+++ state/apiserver/resource.go 1970-01-01 00:00:00 +0000
@@ -1,82 +0,0 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package apiserver
5
6import (
7 "launchpad.net/juju-core/log"
8 "strconv"
9 "sync"
10)
11
12// resource represents the interface provided by state watchers and pingers.
13type resource interface {
14 Stop() error
15}
16
17// resources holds all the resources for a connection.
18type resources struct {
19 mu sync.Mutex
20 maxId uint64
21 rs map[string]*srvResource
22}
23
24// srvResource holds the details of a resource. It also implements the
25// Stop RPC method for all resources.
26type srvResource struct {
27 rs *resources
28 resource resource
29 id string
30}
31
32// Stop stops the given resource. It causes any outstanding
33// Next calls to return a CodeStopped error.
34// Any subsequent Next calls will return a CodeNotFound
35// error because the resource will no longer exist.
36func (r *srvResource) Stop() error {
37 err := r.resource.Stop()
38 r.rs.mu.Lock()
39 defer r.rs.mu.Unlock()
40 delete(r.rs.rs, r.id)
41 return err
42}
43
44func newResources() *resources {
45 return &resources{
46 rs: make(map[string]*srvResource),
47 }
48}
49
50// get returns the srvResource registered with the given
51// id, or nil if there is no such resource.
52func (rs *resources) get(id string) *srvResource {
53 rs.mu.Lock()
54 defer rs.mu.Unlock()
55 return rs.rs[id]
56}
57
58// register records the given watcher and returns
59// a srvResource instance for it.
60func (rs *resources) register(r resource) *srvResource {
61 rs.mu.Lock()
62 defer rs.mu.Unlock()
63 rs.maxId++
64 sr := &srvResource{
65 rs: rs,
66 id: strconv.FormatUint(rs.maxId, 10),
67 resource: r,
68 }
69 rs.rs[sr.id] = sr
70 return sr
71}
72
73func (rs *resources) stopAll() {
74 rs.mu.Lock()
75 defer rs.mu.Unlock()
76 for _, r := range rs.rs {
77 if err := r.resource.Stop(); err != nil {
78 log.Errorf("state/api: error stopping %T resource: %v", r, err)
79 }
80 }
81 rs.rs = make(map[string]*srvResource)
82}
830
=== modified file 'state/apiserver/root.go'
--- state/apiserver/root.go 2013-06-06 17:09:49 +0000
+++ state/apiserver/root.go 2013-06-18 12:56:33 +0000
@@ -6,7 +6,7 @@
6import (6import (
7 "launchpad.net/juju-core/state"7 "launchpad.net/juju-core/state"
8 "launchpad.net/juju-core/state/apiserver/common"8 "launchpad.net/juju-core/state/apiserver/common"
9 "launchpad.net/juju-core/state/apiserver/machiner"9 "launchpad.net/juju-core/state/apiserver/machine"
10 "launchpad.net/juju-core/state/multiwatcher"10 "launchpad.net/juju-core/state/multiwatcher"
11)11)
1212
@@ -83,15 +83,24 @@
83 return nil83 return nil
84}84}
8585
86// Machiner returns an object that provides access to the Machiner API86// Machiner returns an object that provides access to the Machiner API.
87// facade. The id argument is reserved for future use and currently87// The id argument is reserved for future use and must currently
88// needs to be empty.88// be empty.
89func (r *srvRoot) Machiner(id string) (*machiner.Machiner, error) {89func (r *srvRoot) Machiner(id string) (*machine.MachinerAPI, error) {
90 if id != "" {90 if id != "" {
91 // Safeguard id for possible future use.91 return nil, common.ErrBadId
92 return nil, common.ErrBadId92 }
93 }93 return machine.NewMachinerAPI(r.srv.state, r)
94 return machiner.New(r.srv.state, r)94}
95
96// MachineAgent returns an object that provides access to the machine
97// agent API. The id argument is reserved for future use and must currently
98// be empty.
99func (r *srvRoot) MachineAgent(id string) (*machine.AgentAPI, error) {
100 if id != "" {
101 return nil, common.ErrBadId
102 }
103 return machine.NewAgentAPI(r.srv.state, r)
95}104}
96105
97// User returns an object that provides106// User returns an object that provides
@@ -237,10 +246,10 @@
237}246}
238247
239// AuthOwner returns whether the authenticated user's tag matches the248// AuthOwner returns whether the authenticated user's tag matches the
240// given entity's tag.249// given entity tag.
241func (r *srvRoot) AuthOwner(entity common.Tagger) bool {250func (r *srvRoot) AuthOwner(tag string) bool {
242 authUser := r.user.authenticator()251 authUser := r.user.authenticator()
243 return authUser.Tag() == entity.Tag()252 return authUser.Tag() == tag
244}253}
245254
246// AuthEnvironManager returns whether the authenticated user is a255// AuthEnvironManager returns whether the authenticated user is a
247256
=== renamed file 'state/apiserver/apiserver.go' => 'state/apiserver/server.go'
=== added directory 'state/apiserver/testing'
=== modified file 'state/machine.go'
--- state/machine.go 2013-06-18 12:56:33 +0000
+++ state/machine.go 2013-06-18 12:56:33 +0000
@@ -32,13 +32,13 @@
32 _ MachineJob = iota32 _ MachineJob = iota
33 JobHostUnits33 JobHostUnits
34 JobManageEnviron34 JobManageEnviron
35 JobServeAPI35 JobManageState
36)36)
3737
38var jobNames = []string{38var jobNames = []params.MachineJob{
39 JobHostUnits: "JobHostUnits",39 JobHostUnits: params.JobHostUnits,
40 JobManageEnviron: "JobManageEnviron",40 JobManageEnviron: params.JobManageEnviron,
41 JobServeAPI: "JobServeAPI",41 JobManageState: params.JobManageState,
42}42}
4343
44func (job MachineJob) String() string {44func (job MachineJob) String() string {
@@ -46,7 +46,7 @@
46 if j <= 0 || j >= len(jobNames) {46 if j <= 0 || j >= len(jobNames) {
47 return fmt.Sprintf("<unknown job %d>", j)47 return fmt.Sprintf("<unknown job %d>", j)
48 }48 }
49 return jobNames[j]49 return string(jobNames[j])
50}50}
5151
52// machineDoc represents the internal state of a machine in MongoDB.52// machineDoc represents the internal state of a machine in MongoDB.
5353
=== modified file 'state/state_test.go'
--- state/state_test.go 2013-06-18 12:56:33 +0000
+++ state/state_test.go 2013-06-18 12:56:33 +0000
@@ -117,7 +117,7 @@
117}{117}{
118 {state.JobHostUnits, "JobHostUnits"},118 {state.JobHostUnits, "JobHostUnits"},
119 {state.JobManageEnviron, "JobManageEnviron"},119 {state.JobManageEnviron, "JobManageEnviron"},
120 {state.JobServeAPI, "JobServeAPI"},120 {state.JobManageState, "JobManageState"},
121 {0, "<unknown job 0>"},121 {0, "<unknown job 0>"},
122 {5, "<unknown job 5>"},122 {5, "<unknown job 5>"},
123}123}
@@ -155,7 +155,7 @@
155 allJobs := []state.MachineJob{155 allJobs := []state.MachineJob{
156 state.JobHostUnits,156 state.JobHostUnits,
157 state.JobManageEnviron,157 state.JobManageEnviron,
158 state.JobServeAPI,158 state.JobManageState,
159 }159 }
160 m1, err := s.State.AddMachine("blahblah", allJobs...)160 m1, err := s.State.AddMachine("blahblah", allJobs...)
161 c.Assert(err, IsNil)161 c.Assert(err, IsNil)

Subscribers

People subscribed via source and target branches