Merge lp:~dimitern/juju-core/382-api-provisioner-machine-nics into lp:~go-bot/juju-core/trunk

Proposed by Dimiter Naydenov
Status: Merged
Approved by: Dimiter Naydenov
Approved revision: no longer in the source branch.
Merged at revision: 2569
Proposed branch: lp:~dimitern/juju-core/382-api-provisioner-machine-nics
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~dimitern/juju-core/381-state-machine-nics
Diff against target: 444 lines (+342/-4)
7 files modified
state/api/params/internal.go (+26/-0)
state/api/provisioner/machine.go (+28/-0)
state/api/provisioner/provisioner.go (+20/-0)
state/api/provisioner/provisioner_test.go (+94/-2)
state/apiserver/provisioner/provisioner.go (+36/-0)
state/apiserver/provisioner/provisioner_test.go (+134/-2)
state/apiserver/testing/errors.go (+4/-0)
To merge this branch: bzr merge lp:~dimitern/juju-core/382-api-provisioner-machine-nics
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+214272@code.launchpad.net

Commit message

api/provisioner: Allow adding networks and NICs

This adds two new Provisioner API calls:
 * AddNetwork (exposed as st.AddNetworks bulk call on
   the client-side provisioner API)
 * AddNetworkInterface (exposed in the client-side API
   as machine.AddNetworkInterfaces bulk call)

These will be used to set the networks/interfaces that
will be configured by MAAS at provisioning time.

Next, we'll make the necessary changes to StartInstance()
in general (and in provider/maas specifically) to return
what networks will the machine start with.

https://codereview.appspot.com/84570043/

R=gz

Description of the change

api/provisioner: Allow adding networks and NICs

This adds two new Provisioner API calls:
 * AddNetwork (exposed as st.AddNetworks bulk call on
   the client-side provisioner API)
 * AddNetworkInterface (exposed in the client-side API
   as machine.AddNetworkInterfaces bulk call)

These will be used to set the networks/interfaces that
will be configured by MAAS at provisioning time.

Next, we'll make the necessary changes to StartInstance()
in general (and in provider/maas specifically) to return
what networks will the machine start with.

https://codereview.appspot.com/84570043/

To post a comment you must log in.
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Reviewers: mp+214272_code.launchpad.net,

Message:
Please take a look.

Description:
api/provisioner: Allow adding networks and NICs

This adds two new Provisioner API calls:
  * AddNetwork (exposed as st.AddNetworks bulk call on
    the client-side provisioner API)
  * AddNetworkInterface (exposed in the client-side API
    as machine.AddNetworkInterfaces bulk call)

These will be used to set the networks/interfaces that
will be configured by MAAS at provisioning time.

Next, we'll make the necessary changes to StartInstance()
in general (and in provider/maas specifically) to return
what networks will the machine start with.

https://code.launchpad.net/~dimitern/juju-core/382-api-provisioner-machine-nics/+merge/214272

Requires:
https://code.launchpad.net/~dimitern/juju-core/381-state-machine-nics/+merge/213796

(do not edit description out of merge proposal)

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

Affected files (+322, -4 lines):
   A [revision details]
   M state/api/params/internal.go
   M state/api/provisioner/machine.go
   M state/api/provisioner/provisioner.go
   M state/api/provisioner/provisioner_test.go
   M state/apiserver/provisioner/provisioner.go
   M state/apiserver/provisioner/provisioner_test.go

Revision history for this message
Martin Packman (gz) wrote :

LGTM. I am a little panicked around future-proofing though, I think
these api structs will want to look a little different, or maybe quite a
bit different, when we add other clouds and better networking support.
We seem to be reserving some pretty generic API names 'AddNetworks" and
will probably want a number of revisions. We may get to several -Ex
suffixes or something.

https://codereview.appspot.com/84570043/diff/1/state/api/params/internal.go
File state/api/params/internal.go (right):

https://codereview.appspot.com/84570043/diff/1/state/api/params/internal.go#newcode398
state/api/params/internal.go:398: NetworkName string
This will probably be a non-name id for clouds other than MAAS.
Shouldn't matter apart from naming.

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/machine.go
File state/api/provisioner/machine.go (right):

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/machine.go#newcode85
state/api/provisioner/machine.go:85: if interfaces[i].MachineTag !=
m.tag {
Why the conditional?

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/provisioner_test.go
File state/api/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/provisioner_test.go#newcode359
state/api/provisioner/provisioner_test.go:359: c.Assert(net.Name(),
gc.Equals, expectParams.Name)
Can use c.Check for these two after the err Assert.

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/provisioner_test.go#newcode412
state/api/provisioner/provisioner_test.go:412: // Ensure only the first
error is reported.
This feels like a seperate testcase really, the current test is > one
screen high which is a bit of a personal bugbear of mine. Sharing setup
steps is not that hard.

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provisioner_test.go#newcode828
state/apiserver/provisioner/provisioner_test.go:828:
c.Assert(net.Name(), gc.Equals, "vlan42")
Can use c.Check, and below.

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provisioner_test.go#newcode839
state/apiserver/provisioner/provisioner_test.go:839: // Test it fails
with a non-environment-manager.
This feels like a seperate testcase.

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provisioner_test.go#newcode850
state/apiserver/provisioner/provisioner_test.go:850: func (s
*withoutStateServerSuite) TestAddNetworkInterface(c *gc.C) {
Giant complicated test again...

https://codereview.appspot.com/84570043/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Download full text (3.8 KiB)

Please take a look.

https://codereview.appspot.com/84570043/diff/1/[revision%20details]
File [revision details] (right):

https://codereview.appspot.com/84570043/diff/1/[revision%20details]#newcode1
[revision details]:1: Old revision:
<email address hidden>
> LGTM. I am a little panicked around future-proofing though, I
> think these api structs will want to look a little different,
> or maybe quite a bit different, when we add other clouds and
> better networking support. We seem to be reserving some
> pretty generic API names 'AddNetworks" and will probably want
> a number of revisions. We may get to several -Ex suffixes or
> something.

Structs can evolve and we can phase-out API calls and add new ones with
extended functionality. This is a general problem with the API w/o
versioning.

https://codereview.appspot.com/84570043/diff/1/state/api/params/internal.go
File state/api/params/internal.go (right):

https://codereview.appspot.com/84570043/diff/1/state/api/params/internal.go#newcode398
state/api/params/internal.go:398: NetworkName string
On 2014/04/04 15:40:02, gz wrote:
> This will probably be a non-name id for clouds other than MAAS.
Shouldn't matter
> apart from naming.

We can add fields as needed later (and "name" can also be a UUID if
needed :)

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/machine.go
File state/api/provisioner/machine.go (right):

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/machine.go#newcode85
state/api/provisioner/machine.go:85: if interfaces[i].MachineTag !=
m.tag {
On 2014/04/04 15:40:02, gz wrote:
> Why the conditional?

To make sure we can only add NICs to the current machine (in case it's
empty or different). There are tests for that in state/api/provisioner.

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/provisioner_test.go
File state/api/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/provisioner_test.go#newcode359
state/api/provisioner/provisioner_test.go:359: c.Assert(net.Name(),
gc.Equals, expectParams.Name)
On 2014/04/04 15:40:02, gz wrote:
> Can use c.Check for these two after the err Assert.

Done.

https://codereview.appspot.com/84570043/diff/1/state/api/provisioner/provisioner_test.go#newcode412
state/api/provisioner/provisioner_test.go:412: // Ensure only the first
error is reported.
On 2014/04/04 15:40:02, gz wrote:
> This feels like a seperate testcase really, the current test is > one
screen
> high which is a bit of a personal bugbear of mine. Sharing setup steps
is not
> that hard.

Moved to a separate test case.

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provisioner_test.go#newcode828
state/apiserver/provisioner/provisioner_test.go:828:
c.Assert(net.Name(), gc.Equals, "vlan42")
On 2014/04/04 15:40:02, gz wrote:
> Can use c.Check, and below.

Done.

https://codereview.appspot.com/84570043/diff/1/state/apiserver/provisioner/provis...

Read more...

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

I'm a bit disappointed that we're making this so chatty, when ISTM that
we could pack all this into SetProvisioned, but... well, deadlines are
tight and internal APIs are much easier to change in future. Let it
stand, I guess :).

https://codereview.appspot.com/84570043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'state/api/params/internal.go'
2--- state/api/params/internal.go 2014-03-31 11:59:16 +0000
3+++ state/api/params/internal.go 2014-04-04 16:33:28 +0000
4@@ -378,6 +378,32 @@
5 Results []NetworkResult
6 }
7
8+// NetworkParams holds a single network definition.
9+type NetworkParams struct {
10+ Name string
11+ CIDR string
12+ VLANTag int
13+}
14+
15+// AddNetworkParams holds the parameters for making an AddNetwork call.
16+type AddNetworkParams struct {
17+ Networks []NetworkParams
18+}
19+
20+// NetworkInterfaceParams holds a single network interface definition.
21+type NetworkInterfaceParams struct {
22+ MACAddress string
23+ MachineTag string
24+ InterfaceName string
25+ NetworkName string
26+}
27+
28+// AddNetworkInterfaceParams holds the parameters for making an
29+// AddNetworkInterface call.
30+type AddNetworkInterfaceParams struct {
31+ Interfaces []NetworkInterfaceParams
32+}
33+
34 // AgentGetEntitiesResults holds the results of a
35 // agent.API.GetEntities call.
36 type AgentGetEntitiesResults struct {
37
38=== modified file 'state/api/provisioner/machine.go'
39--- state/api/provisioner/machine.go 2014-04-04 09:08:42 +0000
40+++ state/api/provisioner/machine.go 2014-04-04 16:33:28 +0000
41@@ -74,6 +74,34 @@
42 return result.IncludeNetworks, result.ExcludeNetworks, nil
43 }
44
45+// AddNetworkInterfaces creates one or more network interfaces each on
46+// an existing network and bound to the machine, which must not be
47+// provisioned yet. MachineTag inside interfaces params is always set
48+// to the current machine's tag. If any operation fails, the first
49+// error is returned.
50+func (m *Machine) AddNetworkInterfaces(interfaces []params.NetworkInterfaceParams) error {
51+ var results params.ErrorResults
52+ for i, _ := range interfaces {
53+ if interfaces[i].MachineTag != m.tag {
54+ interfaces[i].MachineTag = m.tag
55+ }
56+ }
57+ args := params.AddNetworkInterfaceParams{Interfaces: interfaces}
58+ err := m.st.call("AddNetworkInterface", args, &results)
59+ if err != nil {
60+ return err
61+ }
62+ if n := len(results.Results); n != len(interfaces) {
63+ return fmt.Errorf("expected %d result(s), got %d", len(interfaces), n)
64+ }
65+ for _, result := range results.Results {
66+ if err := result.Error; err != nil {
67+ return err
68+ }
69+ }
70+ return nil
71+}
72+
73 // SetStatus sets the status of the machine.
74 func (m *Machine) SetStatus(status params.Status, info string, data params.StatusData) error {
75 var result params.ErrorResults
76
77=== modified file 'state/api/provisioner/provisioner.go'
78--- state/api/provisioner/provisioner.go 2014-03-26 03:15:41 +0000
79+++ state/api/provisioner/provisioner.go 2014-04-04 16:33:28 +0000
80@@ -143,3 +143,23 @@
81 }
82 return machines, results.Results, nil
83 }
84+
85+// AddNetworks creates one or more networks with the given parameters.
86+// If any operation fails, the first error is returned.
87+func (st *State) AddNetworks(networks []params.NetworkParams) error {
88+ var results params.ErrorResults
89+ args := params.AddNetworkParams{Networks: networks}
90+ err := st.call("AddNetwork", args, &results)
91+ if err != nil {
92+ return err
93+ }
94+ if n := len(results.Results); n != len(networks) {
95+ return fmt.Errorf("expected %d result(s), got %d", len(networks), n)
96+ }
97+ for _, result := range results.Results {
98+ if err := result.Error; err != nil {
99+ return err
100+ }
101+ }
102+ return nil
103+}
104
105=== modified file 'state/api/provisioner/provisioner_test.go'
106--- state/api/provisioner/provisioner_test.go 2014-04-04 09:08:42 +0000
107+++ state/api/provisioner/provisioner_test.go 2014-04-04 16:33:28 +0000
108@@ -13,6 +13,7 @@
109 "launchpad.net/juju-core/errors"
110 "launchpad.net/juju-core/instance"
111 "launchpad.net/juju-core/juju/testing"
112+ "launchpad.net/juju-core/names"
113 "launchpad.net/juju-core/state"
114 "launchpad.net/juju-core/state/api"
115 "launchpad.net/juju-core/state/api/params"
116@@ -316,8 +317,8 @@
117 c.Assert(cons, gc.DeepEquals, constraints.Value{})
118 }
119
120-func (s *provisionerSuite) TestNetworks(c *gc.C) {
121- // Create a fresh machine with some networks.
122+func (s *provisionerSuite) TestRequestedNetworks(c *gc.C) {
123+ // Create a fresh machine with some requested networks.
124 template := state.MachineTemplate{
125 Series: "quantal",
126 Jobs: []state.MachineJob{state.JobHostUnits},
127@@ -343,6 +344,97 @@
128 c.Assert(excludeNetworks, gc.HasLen, 0)
129 }
130
131+func (s *provisionerSuite) TestAddNetworks(c *gc.C) {
132+ args := []params.NetworkParams{
133+ {Name: "vlan42", CIDR: "0.1.2.0/24", VLANTag: 42},
134+ {Name: "net1", CIDR: "0.2.1.0/24", VLANTag: 0},
135+ }
136+ err := s.provisioner.AddNetworks(args)
137+ c.Assert(err, gc.IsNil)
138+
139+ assertNetwork := func(name string, expectParams params.NetworkParams) {
140+ net, err := s.State.Network(name)
141+ c.Assert(err, gc.IsNil)
142+ c.Check(net.CIDR(), gc.Equals, expectParams.CIDR)
143+ c.Check(net.Name(), gc.Equals, expectParams.Name)
144+ c.Check(net.VLANTag(), gc.Equals, expectParams.VLANTag)
145+ }
146+ assertNetwork("vlan42", args[0])
147+ assertNetwork("net1", args[1])
148+
149+ // Test the first error is returned.
150+ args = []params.NetworkParams{
151+ {Name: "net2", CIDR: "0.2.2.0/24", VLANTag: 0},
152+ {Name: "", CIDR: "0.1.2.0/24", VLANTag: 0},
153+ {Name: "net2", CIDR: "0.2.2.0/24", VLANTag: -1},
154+ }
155+ err = s.provisioner.AddNetworks(args)
156+ c.Assert(err, gc.ErrorMatches, `cannot add network "": name must be not empty`)
157+
158+ assertNetwork("net2", args[0])
159+}
160+
161+func (s *provisionerSuite) addMachineAndNetworks(c *gc.C) (*state.Machine, *provisioner.Machine) {
162+ err := s.provisioner.AddNetworks([]params.NetworkParams{
163+ {Name: "vlan42", CIDR: "0.1.2.0/24", VLANTag: 42},
164+ {Name: "net1", CIDR: "0.2.1.0/24", VLANTag: 0},
165+ })
166+ c.Assert(err, gc.IsNil)
167+ machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
168+ c.Assert(err, gc.IsNil)
169+ apiMachine, err := s.provisioner.Machine(machine.Tag())
170+ c.Assert(err, gc.IsNil)
171+ return machine, apiMachine
172+}
173+
174+func (s *provisionerSuite) TestMachineAddNetworkInterfaces(c *gc.C) {
175+ machine, apiMachine := s.addMachineAndNetworks(c)
176+
177+ args := []params.NetworkInterfaceParams{
178+ {"aa:bb:cc:dd:ee:f0", machine.Tag(), "eth0", "net1"},
179+ {"aa:bb:cc:dd:ee:f1", "", "eth0", "net1"}, // tag filled in when empty
180+ {"aa:bb:cc:dd:ee:f2", "machine-42", "eth2", "vlan42"}, // tag overwritten
181+ }
182+ err := apiMachine.AddNetworkInterfaces(args)
183+ c.Assert(err, gc.IsNil)
184+
185+ // Check the interfaces are there.
186+ ifaces, err := machine.NetworkInterfaces()
187+ c.Assert(err, gc.IsNil)
188+ c.Assert(ifaces, gc.HasLen, len(args))
189+ actual := make([]params.NetworkInterfaceParams, len(args))
190+ for i, iface := range ifaces {
191+ actual[i] = params.NetworkInterfaceParams{
192+ MACAddress: iface.MACAddress(),
193+ MachineTag: names.MachineTag(iface.MachineId()),
194+ InterfaceName: iface.InterfaceName(),
195+ NetworkName: iface.NetworkName(),
196+ }
197+ }
198+ c.Assert(actual, jc.SameContents, args)
199+}
200+
201+func (s *provisionerSuite) TestMachineAddNetworkInterfacesReportsFirstError(c *gc.C) {
202+ machine, apiMachine := s.addMachineAndNetworks(c)
203+
204+ // Ensure only the first error is reported.
205+ args := []params.NetworkInterfaceParams{
206+ {"aa:bb:cc:dd:ee:f3", "", "eth3", "net1"},
207+ {"aa:bb:cc:dd:ee:f4", "", "eth0", "missing"},
208+ {"invalid", "", "eth42", "net1"},
209+ }
210+ err := apiMachine.AddNetworkInterfaces(args)
211+ c.Assert(err, gc.ErrorMatches, `cannot add network interface to machine 1: network "missing" not found`)
212+
213+ ifaces, err := machine.NetworkInterfaces()
214+ c.Assert(err, gc.IsNil)
215+ c.Assert(ifaces, gc.HasLen, 1)
216+ c.Assert(ifaces[0].MachineId(), gc.Equals, machine.Id())
217+ c.Assert(ifaces[0].MACAddress(), gc.Equals, args[0].MACAddress)
218+ c.Assert(ifaces[0].InterfaceName(), gc.Equals, args[0].InterfaceName)
219+ c.Assert(ifaces[0].NetworkName(), gc.Equals, args[0].NetworkName)
220+}
221+
222 func (s *provisionerSuite) TestWatchContainers(c *gc.C) {
223 apiMachine, err := s.provisioner.Machine(s.machine.Tag())
224 c.Assert(err, gc.IsNil)
225
226=== modified file 'state/apiserver/provisioner/provisioner.go'
227--- state/apiserver/provisioner/provisioner.go 2014-04-04 09:08:42 +0000
228+++ state/apiserver/provisioner/provisioner.go 2014-04-04 16:33:28 +0000
229@@ -432,6 +432,42 @@
230 return result, nil
231 }
232
233+// AddNetwork creates one or more new networks with the given parameters.
234+// Only the environment manager can add networks.
235+func (p *ProvisionerAPI) AddNetwork(args params.AddNetworkParams) (params.ErrorResults, error) {
236+ result := params.ErrorResults{
237+ Results: make([]params.ErrorResult, len(args.Networks)),
238+ }
239+ if !p.authorizer.AuthEnvironManager() {
240+ return result, common.ErrPerm
241+ }
242+ for i, arg := range args.Networks {
243+ _, err := p.st.AddNetwork(arg.Name, arg.CIDR, arg.VLANTag)
244+ result.Results[i].Error = common.ServerError(err)
245+ }
246+ return result, nil
247+}
248+
249+// AddNetworkInterface creates one or more new network interfaces with
250+// the given parameters.
251+func (p *ProvisionerAPI) AddNetworkInterface(args params.AddNetworkInterfaceParams) (params.ErrorResults, error) {
252+ result := params.ErrorResults{
253+ Results: make([]params.ErrorResult, len(args.Interfaces)),
254+ }
255+ canAccess, err := p.getAuthFunc()
256+ if err != nil {
257+ return result, err
258+ }
259+ for i, arg := range args.Interfaces {
260+ machine, err := p.getMachine(canAccess, arg.MachineTag)
261+ if err == nil {
262+ _, err = machine.AddNetworkInterface(arg.MACAddress, arg.InterfaceName, arg.NetworkName)
263+ }
264+ result.Results[i].Error = common.ServerError(err)
265+ }
266+ return result, nil
267+}
268+
269 // SetProvisioned sets the provider specific machine id, nonce and
270 // metadata for each given machine. Once set, the instance id cannot
271 // be changed.
272
273=== modified file 'state/apiserver/provisioner/provisioner_test.go'
274--- state/apiserver/provisioner/provisioner_test.go 2014-04-04 09:08:42 +0000
275+++ state/apiserver/provisioner/provisioner_test.go 2014-04-04 16:33:28 +0000
276@@ -15,6 +15,7 @@
277 "launchpad.net/juju-core/instance"
278 "launchpad.net/juju-core/juju/osenv"
279 "launchpad.net/juju-core/juju/testing"
280+ "launchpad.net/juju-core/names"
281 "launchpad.net/juju-core/state"
282 "launchpad.net/juju-core/state/api/params"
283 "launchpad.net/juju-core/state/apiserver/common"
284@@ -758,8 +759,8 @@
285 })
286 }
287
288-func (s *withoutStateServerSuite) TestNetworks(c *gc.C) {
289- // Add a machine with some networks.
290+func (s *withoutStateServerSuite) TestRequestedNetworks(c *gc.C) {
291+ // Add a machine with some requested networks.
292 template := state.MachineTemplate{
293 Series: "quantal",
294 Jobs: []state.MachineJob{state.JobHostUnits},
295@@ -798,6 +799,137 @@
296 })
297 }
298
299+func (s *withoutStateServerSuite) TestAddNetwork(c *gc.C) {
300+ args := params.AddNetworkParams{Networks: []params.NetworkParams{
301+ {Name: "vlan42", CIDR: "0.1.2.0/24", VLANTag: 42},
302+ {Name: "net1", CIDR: "0.2.1.0/24", VLANTag: 0},
303+ {Name: "", CIDR: "0.1.3.0/24", VLANTag: 0},
304+ {Name: "net2", CIDR: "invalid", VLANTag: 0},
305+ {Name: "net1", CIDR: "0.1.4.0/24", VLANTag: 0},
306+ {Name: "net2", CIDR: "0.1.5.0/24", VLANTag: -1},
307+ }}
308+ result, err := s.provisioner.AddNetwork(args)
309+ c.Assert(err, gc.IsNil)
310+ prefix := "cannot add network %q: "
311+ c.Assert(result, jc.DeepEquals, params.ErrorResults{
312+ Results: []params.ErrorResult{
313+ {Error: nil},
314+ {Error: nil},
315+ {Error: apiservertesting.PrefixedError(
316+ fmt.Sprintf(prefix, ""),
317+ "name must be not empty")},
318+ {Error: apiservertesting.PrefixedError(
319+ fmt.Sprintf(prefix, "net2"),
320+ "invalid CIDR address: invalid")},
321+ {Error: apiservertesting.PrefixedError(
322+ fmt.Sprintf(prefix, "net1"),
323+ "already exists")},
324+ {Error: apiservertesting.PrefixedError(
325+ fmt.Sprintf(prefix, "net2"),
326+ "invalid VLAN tag -1: must be between 0 and 4094")},
327+ }})
328+
329+ // Check add networks are there and failed ones are not.
330+ net, err := s.State.Network("vlan42")
331+ c.Assert(err, gc.IsNil)
332+ c.Check(net.Name(), gc.Equals, "vlan42")
333+ c.Check(net.CIDR(), gc.Equals, "0.1.2.0/24")
334+ c.Check(net.VLANTag(), gc.Equals, 42)
335+ net, err = s.State.Network("net1")
336+ c.Assert(err, gc.IsNil)
337+ c.Check(net.Name(), gc.Equals, "net1")
338+ c.Check(net.CIDR(), gc.Equals, "0.2.1.0/24")
339+ c.Check(net.VLANTag(), gc.Equals, 0)
340+ _, err = s.State.Network("net2")
341+ c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
342+}
343+
344+func (s *withoutStateServerSuite) TestAddNetworkFailsWithNonEnvironManager(c *gc.C) {
345+ anAuthorizer := s.authorizer
346+ anAuthorizer.MachineAgent = true
347+ anAuthorizer.EnvironManager = false
348+ aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
349+ c.Assert(err, gc.IsNil)
350+ c.Assert(aProvisioner, gc.NotNil)
351+ _, err = aProvisioner.AddNetwork(params.AddNetworkParams{})
352+ c.Assert(err, gc.ErrorMatches, "permission denied")
353+}
354+
355+func assertInterfaces(c *gc.C, machine *state.Machine, expect ...params.NetworkInterfaceParams) {
356+ ifaces, err := machine.NetworkInterfaces()
357+ c.Assert(err, gc.IsNil)
358+ c.Assert(ifaces, gc.HasLen, len(expect))
359+ actual := make([]params.NetworkInterfaceParams, len(expect))
360+ for i, iface := range ifaces {
361+ actual[i] = params.NetworkInterfaceParams{
362+ MACAddress: iface.MACAddress(),
363+ MachineTag: names.MachineTag(iface.MachineId()),
364+ InterfaceName: iface.InterfaceName(),
365+ NetworkName: iface.NetworkName(),
366+ }
367+ }
368+ c.Assert(actual, jc.SameContents, expect)
369+}
370+
371+func (s *withoutStateServerSuite) TestAddNetworkInterface(c *gc.C) {
372+ // Add a few networks first.
373+ _, err := s.State.AddNetwork("net1", "0.1.2.0/24", 0)
374+ c.Assert(err, gc.IsNil)
375+ _, err = s.State.AddNetwork("vlan42", "0.2.1.0/24", 42)
376+ c.Assert(err, gc.IsNil)
377+
378+ args := params.AddNetworkInterfaceParams{Interfaces: []params.NetworkInterfaceParams{
379+ {"aa:bb:cc:dd:ee:f0", s.machines[0].Tag(), "eth0", "net1"},
380+ {"aa:bb:cc:dd:ee:f1", s.machines[1].Tag(), "eth0", "net1"},
381+ {"aa:bb:cc:dd:ee:f2", s.machines[1].Tag(), "eth1", "vlan42"},
382+ {"invalid", s.machines[1].Tag(), "eth1", "vlan42"},
383+ {"aa:bb:cc:dd:ee:ff", s.machines[1].Tag(), "", "net1"},
384+ {"aa:bb:cc:dd:ee:ff", s.machines[1].Tag(), "eth1", "invalid"},
385+ {"aa:bb:cc:dd:ee:ff", s.machines[1].Tag(), "eth1", ""},
386+ {"aa:bb:cc:dd:ee:f1", s.machines[1].Tag(), "eth2", "net1"},
387+ {"aa:bb:cc:dd:ee:f1", s.machines[2].Tag(), "eth0", "net1"},
388+ {"aa:bb:cc:dd:ee:f1", "machine-42", "eth0", "net1"},
389+ {"aa:bb:cc:dd:ee:f1", "unit-foo-42", "eth0", "net1"},
390+ }}
391+ err = s.machines[2].SetProvisioned("i-am", "fake_nonce", nil)
392+ c.Assert(err, gc.IsNil)
393+
394+ prefix := "cannot add network interface to machine %s: "
395+ prefixM1 := fmt.Sprintf(prefix, s.machines[1].Id())
396+ result, err := s.provisioner.AddNetworkInterface(args)
397+ c.Assert(err, gc.IsNil)
398+ c.Assert(result, jc.DeepEquals, params.ErrorResults{
399+ Results: []params.ErrorResult{
400+ {Error: nil},
401+ {Error: nil},
402+ {Error: nil},
403+ {Error: apiservertesting.PrefixedError(
404+ prefixM1, "invalid MAC address: invalid")},
405+ {Error: apiservertesting.PrefixedError(
406+ prefixM1, "interface name must be not empty")},
407+ {Error: apiservertesting.PrefixedError(
408+ prefixM1, `network "invalid" not found`)},
409+ {Error: apiservertesting.PrefixedError(
410+ prefixM1, `network "" not found`)},
411+ {Error: apiservertesting.PrefixedError(
412+ prefixM1,
413+ `interface with MAC address "aa:bb:cc:dd:ee:f1" already exists`),
414+ },
415+ {Error: apiservertesting.PrefixedError(
416+ fmt.Sprintf(prefix, s.machines[2].Id()),
417+ `machine already provisioned: dynamic network interfaces not currently supported`,
418+ )},
419+ {Error: apiservertesting.NotFoundError("machine 42")},
420+ {Error: apiservertesting.ErrUnauthorized},
421+ },
422+ })
423+
424+ // Now check the added interfaces are there.
425+ assertInterfaces(c, s.machines[0], args.Interfaces[0])
426+ assertInterfaces(c, s.machines[1], args.Interfaces[1], args.Interfaces[2])
427+ assertInterfaces(c, s.machines[2]) // None
428+}
429+
430 func (s *withoutStateServerSuite) TestSetProvisioned(c *gc.C) {
431 // Provision machine 0 first.
432 hwChars := instance.MustParseHardware("arch=i386", "mem=4G")
433
434=== modified file 'state/apiserver/testing/errors.go'
435--- state/apiserver/testing/errors.go 2014-01-21 10:05:20 +0000
436+++ state/apiserver/testing/errors.go 2014-04-04 16:33:28 +0000
437@@ -41,3 +41,7 @@
438 Code: "",
439 }
440 }
441+
442+func PrefixedError(prefix, message string) *params.Error {
443+ return ServerError(prefix + message)
444+}

Subscribers

People subscribed via source and target branches

to status/vote changes: