Skip to content

Commit

Permalink
Add Provisioner.HostChangesForContainer()
Browse files Browse the repository at this point in the history
This updates bits of state to support detecting what bridges
a machine needs to create for us to put a container onto that
machine that wants access to particular spaces.
It then exposes that via the API, and then implements the
client side of that API.

This version is targetted at the 2.1 branch, instead of
'develop'.
  • Loading branch information
jameinel committed Dec 21, 2016
1 parent b6bb8f7 commit c4a5607
Show file tree
Hide file tree
Showing 10 changed files with 832 additions and 168 deletions.
36 changes: 30 additions & 6 deletions api/provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@ func (st *State) prepareOrGetContainerInterfaceInfo(
args := params.Entities{
Entities: []params.Entity{{Tag: containerTag.String()}},
}
facadeName := ""
methodName := ""
if allocateNewAddress {
facadeName = "PrepareContainerInterfaceInfo"
methodName = "PrepareContainerInterfaceInfo"
} else {
facadeName = "GetContainerInterfaceInfo"
methodName = "GetContainerInterfaceInfo"
}
if err := st.facade.FacadeCall(facadeName, args, &result); err != nil {
if err := st.facade.FacadeCall(methodName, args, &result); err != nil {
return nil, err
}
if len(result.Results) != 1 {
Expand All @@ -209,8 +209,9 @@ func (st *State) prepareOrGetContainerInterfaceInfo(
if err := result.Results[0].Error; err != nil {
return nil, err
}
ifaceInfo := make([]network.InterfaceInfo, len(result.Results[0].Config))
for i, cfg := range result.Results[0].Config {
machineConf := result.Results[0]
ifaceInfo := make([]network.InterfaceInfo, len(machineConf.Config))
for i, cfg := range machineConf.Config {
ifaceInfo[i] = network.InterfaceInfo{
DeviceIndex: cfg.DeviceIndex,
MACAddress: cfg.MACAddress,
Expand All @@ -236,3 +237,26 @@ func (st *State) prepareOrGetContainerInterfaceInfo(
}
return ifaceInfo, nil
}

func (st *State) HostChangesForContainer(containerTag names.MachineTag) ([]network.DeviceToBridge, error) {
var result params.HostNetworkChangeResults
args := params.Entities{
Entities: []params.Entity{{Tag: containerTag.String()}},
}
if err := st.facade.FacadeCall("HostChangesForContainers", args, &result); err != nil {
return nil, err
}
if len(result.Results) != 1 {
return nil, errors.Errorf("expected 1 result, got %d", len(result.Results))
}
if err := result.Results[0].Error; err != nil {
return nil, err
}
newBridges := result.Results[0].NewBridges
res := make([]network.DeviceToBridge, len(newBridges))
for i, bridgeInfo := range newBridges {
res[i].BridgeName = bridgeInfo.BridgeName
res[i].DeviceName = bridgeInfo.HostDeviceName
}
return res, nil
}
45 changes: 45 additions & 0 deletions api/provisioner/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,48 @@ func (s *provisionerSuite) testFindTools(c *gc.C, matchArch bool, apiError, logi
c.Assert(apiList, jc.SameContents, toolsList)
}
}

func (s *provisionerSuite) TestHostChangesForContainer(c *gc.C) {
// Create a machine, put it in "default" space with a single NIC. Create
// a container that is also in the "default" space, and request the
// HostChangesForContainer to see that it wants to bridge that NIC
_, err := s.State.AddSpace("default", network.Id("default"), nil, true)
c.Assert(err, jc.ErrorIsNil)
_, err = s.State.AddSubnet(state.SubnetInfo{
CIDR: "10.0.0.0/24",
SpaceName: "default",
})
c.Assert(err, jc.ErrorIsNil)
err = s.machine.SetLinkLayerDevices(
state.LinkLayerDeviceArgs{
Name: "ens3",
Type: state.EthernetDevice,
ParentName: "",
IsUp: true,
},
)
c.Assert(err, jc.ErrorIsNil)
err = s.machine.SetDevicesAddresses(
state.LinkLayerDeviceAddress{
DeviceName: "ens3",
CIDRAddress: "10.0.0.10/24",
ConfigMethod: state.StaticAddress,
},
)
c.Assert(err, jc.ErrorIsNil)
containerTemplate := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
container, err := s.State.AddMachineInsideMachine(containerTemplate, s.machine.Id(), instance.LXD)
c.Assert(err, jc.ErrorIsNil)

changes, err := s.provisioner.HostChangesForContainer(container.MachineTag())
c.Assert(err, gc.ErrorMatches, "dummy provider network config not supported.*")
c.Skip("can't test without network support")
c.Assert(err, jc.ErrorIsNil)
c.Check(changes, gc.DeepEquals, []network.DeviceToBridge{{
BridgeName: "br-ens3",
DeviceName: "ens3",
}})
}
26 changes: 21 additions & 5 deletions apiserver/params/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ type NetworkConfig struct {
GatewayAddress string `json:"gateway-address,omitempty"`
}

// NetworkConfigs holds the network configuration for multiple networks
type NetworkConfigs struct {
Results []NetworkConfig `json:"results"`
Errors []error `json:"errors,omitempty"`
// DeviceBridgeInfo lists the host device and the expected bridge to be
// created.
type DeviceBridgeInfo struct {
HostDeviceName string `json:"host-device-name"`
BridgeName string `json:"bridge-name"`
}

// ProviderInterfaceInfoResults holds the results of a
Expand Down Expand Up @@ -443,7 +444,7 @@ type UnitNetworkConfigResult struct {
Config []NetworkConfig `json:"info"`
}

// UnitNetworkConfigResults holds network configuration for multiple machines.
// UnitNetworkConfigResults holds network configuration for multiple units.
type UnitNetworkConfigResults struct {
Results []UnitNetworkConfigResult `json:"results"`
}
Expand All @@ -461,6 +462,21 @@ type MachineNetworkConfigResults struct {
Results []MachineNetworkConfigResult `json:"results"`
}

// HostNetworkChange holds the information about how a host machine should be
// modified to prepare for a container.
type HostNetworkChange struct {
Error *Error `json:"error,omitempty"`

// NewBridges lists the bridges that need to be created and what host
// device they should be connected to.
NewBridges []DeviceBridgeInfo `json:"new-bridges"`
}

// HostNetworkChangeResults holds the network changes that are necessary for multiple containers to be created.
type HostNetworkChangeResults struct {
Results []HostNetworkChange `json:"results"`
}

// MachinePortsParams holds the arguments for making a
// FirewallerAPIV1.GetMachinePorts() API call.
type MachinePortsParams struct {
Expand Down
134 changes: 134 additions & 0 deletions apiserver/provisioner/container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package provisioner_test

import (
jc "github.com/juju/testing/checkers"

gc "gopkg.in/check.v1"

"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/apiserver/provisioner"
apiservertesting "github.com/juju/juju/apiserver/testing"
"github.com/juju/juju/instance"
"github.com/juju/juju/state"
)

type containerProvisionerSuite struct {
provisionerSuite
}

var _ = gc.Suite(&containerProvisionerSuite{})

func (s *containerProvisionerSuite) SetUpTest(c *gc.C) {
// We have a Controller machine, and 5 other machines to provision in
s.setUpTest(c, true)
}

func addContainerToMachine(c *gc.C, st *state.State, machine *state.Machine) *state.Machine {
// Add a container machine with machine as its host.
containerTemplate := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
container, err := st.AddMachineInsideMachine(containerTemplate, machine.Id(), instance.LXD)
c.Assert(err, jc.ErrorIsNil)
return container
}

func (s *containerProvisionerSuite) TestPrepareContainerInterfaceInfoPermission(c *gc.C) {
// Login as a machine agent for machine 1, which has a container put on it
addContainerToMachine(c, s.State, s.machines[1])
addContainerToMachine(c, s.State, s.machines[1])
addContainerToMachine(c, s.State, s.machines[2])

anAuthorizer := s.authorizer
anAuthorizer.EnvironManager = false
anAuthorizer.Tag = s.machines[1].Tag()
aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
c.Assert(err, jc.ErrorIsNil)
c.Assert(aProvisioner, gc.NotNil)

args := params.Entities{
Entities: []params.Entity{{
Tag: "machine-1/lxd/0", // valid
}, {
Tag: "machine-1/lxd/1", // valid
}, {
Tag: "machine-2/lxd/0", // wrong host machine
}, {
Tag: "machine-2", // host machine
}, {
Tag: "unit-mysql-0", // not a valid machine tag
}}}
// Only machine 0 can have it's containers updated.
results, err := aProvisioner.PrepareContainerInterfaceInfo(args)
c.Assert(err, gc.ErrorMatches, "dummy provider network config not supported")
c.Skip("dummy provider needs networking interface")
// Overall request is ok
c.Assert(err, jc.ErrorIsNil)

errors := make([]*params.Error, 0)
c.Check(results.Results, gc.HasLen, 4)
for _, configResult := range results.Results {
errors = append(errors, configResult.Error)
}
c.Check(errors, gc.DeepEquals, []*params.Error{
nil, // can touch 1/lxd/0
nil, // can touch 1/lxd/1
apiservertesting.ErrUnauthorized, // not 2/lxd/0
apiservertesting.ErrUnauthorized, // nor 2
})
}

// TODO(jam): Add a test for requesting PrepareContainerInterfaceInfo with a
// machine that is not yet provisioned.

func (s *containerProvisionerSuite) TestHostChangesForContainersPermission(c *gc.C) {
// Login as a machine agent for machine 1, which has a container put on it
addContainerToMachine(c, s.State, s.machines[1])
addContainerToMachine(c, s.State, s.machines[1])
addContainerToMachine(c, s.State, s.machines[2])

anAuthorizer := s.authorizer
anAuthorizer.EnvironManager = false
anAuthorizer.Tag = s.machines[1].Tag()
aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
c.Assert(err, jc.ErrorIsNil)
c.Assert(aProvisioner, gc.NotNil)

args := params.Entities{
Entities: []params.Entity{{
Tag: "machine-1/lxd/0", // valid
}, {
Tag: "machine-1/lxd/1", // valid
}, {
Tag: "machine-2/lxd/0", // wrong host machine
}, {
Tag: "machine-2", // host machine
}, {
Tag: "unit-mysql-0", // not a valid machine tag
}}}
// Only machine 0 can have it's containers updated.
results, err := aProvisioner.HostChangesForContainers(args)
c.Assert(err, gc.ErrorMatches, "dummy provider network config not supported")
c.Skip("dummy provider needs networking interface")
// Overall request is ok
c.Assert(err, jc.ErrorIsNil)

errors := make([]*params.Error, 0)
c.Check(results.Results, gc.HasLen, 4)
for _, configResult := range results.Results {
errors = append(errors, configResult.Error)
}
c.Check(errors, gc.DeepEquals, []*params.Error{
nil, // can touch 1/lxd/0
nil, // can touch 1/lxd/1
apiservertesting.ErrUnauthorized, // not 2/lxd/0
apiservertesting.ErrUnauthorized, // nor 2
})
}

// TODO(jam): Add a test for requesting HostChangesForContainers with a
// machine that is not yet provisioned.
Loading

0 comments on commit c4a5607

Please sign in to comment.