Skip to content

Commit

Permalink
Move bridge script out of cloud-init.
Browse files Browse the repository at this point in the history
This is a backport of:
commit b6d930c
Author: Andrew McDermott <[email protected]>
Date:   Wed Dec 21 22:38:03 2016 +0000

This brings in all of the changes to allow us to not bridge as part of
cloud-init, and instead wait to bridge for when we are starting to
deploy a container.
  • Loading branch information
Andrew McDermott authored and jameinel committed Dec 22, 2016
1 parent ad09031 commit 4728ab6
Show file tree
Hide file tree
Showing 19 changed files with 758 additions and 196 deletions.
14 changes: 14 additions & 0 deletions api/provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,20 @@ func (st *State) prepareOrGetContainerInterfaceInfo(
return ifaceInfo, nil
}

// SetHostMachineNetworkConfig sets the network configuration of the
// machine with netConfig
func (st *State) SetHostMachineNetworkConfig(hostMachineID string, netConfig []params.NetworkConfig) error {
args := params.SetMachineNetworkConfig{
Tag: hostMachineID,
Config: netConfig,
}
err := st.facade.FacadeCall("SetHostMachineNetworkConfig", args, nil)
if err != nil {
return errors.Trace(err)
}
return nil
}

func (st *State) HostChangesForContainer(containerTag names.MachineTag) ([]network.DeviceToBridge, error) {
var result params.HostNetworkChangeResults
args := params.Entities{
Expand Down
124 changes: 124 additions & 0 deletions apiserver/provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type ProvisionerAPI struct {
storagePoolManager poolmanager.PoolManager
configGetter environs.EnvironConfigGetter
getAuthFunc common.GetAuthFunc
getCanModify common.GetAuthFunc
}

// NewProvisionerAPI creates a new server-side ProvisionerAPI facade.
Expand Down Expand Up @@ -95,6 +96,9 @@ func NewProvisionerAPI(st *state.State, resources facade.Resources, authorizer f
}
}, nil
}
getCanModify := func() (common.AuthFunc, error) {
return authorizer.AuthOwner, nil
}
getAuthOwner := func() (common.AuthFunc, error) {
return authorizer.AuthOwner, nil
}
Expand Down Expand Up @@ -131,6 +135,7 @@ func NewProvisionerAPI(st *state.State, resources facade.Resources, authorizer f
storageProviderRegistry: storageProviderRegistry,
storagePoolManager: poolmanager.New(state.NewStateSettings(st), storageProviderRegistry),
getAuthFunc: getAuthFunc,
getCanModify: getCanModify,
}, nil
}

Expand Down Expand Up @@ -923,3 +928,122 @@ func (p *ProvisionerAPI) markOneMachineForRemoval(machineTag string, canAccess c
}
return machine.MarkForRemoval()
}

func (p *ProvisionerAPI) SetObservedNetworkConfig(args params.SetMachineNetworkConfig) error {
m, err := p.getMachineForSettingNetworkConfig(args.Tag)
if err != nil {
return errors.Trace(err)
}
if m.IsContainer() {
return nil
}
observedConfig := args.Config
logger.Tracef("observed network config of machine %q: %+v", m.Id(), observedConfig)
if len(observedConfig) == 0 {
logger.Infof("not updating machine network config: no observed network config found")
return nil
}

providerConfig, err := p.getOneMachineProviderNetworkConfig(m)
if errors.IsNotProvisioned(err) {
logger.Infof("not updating provider network config: %v", err)
return nil
}
if err != nil {
return errors.Trace(err)
}
if len(providerConfig) == 0 {
logger.Infof("not updating machine network config: no provider network config found")
return nil
}

mergedConfig := networkingcommon.MergeProviderAndObservedNetworkConfigs(providerConfig, observedConfig)
logger.Tracef("merged observed and provider network config: %+v", mergedConfig)

return p.setOneMachineNetworkConfig(m, mergedConfig)
}

func (p *ProvisionerAPI) getMachineForSettingNetworkConfig(machineTag string) (*state.Machine, error) {
canModify, err := p.getCanModify()
if err != nil {
return nil, errors.Trace(err)
}

tag, err := names.ParseMachineTag(machineTag)
if err != nil {
return nil, errors.Trace(err)
}
if !canModify(tag) {
return nil, errors.Trace(common.ErrPerm)
}

canAccess, err := p.getAuthFunc()
if err != nil {
return nil, err
}

m, err := p.getMachine(canAccess, tag)
if errors.IsNotFound(err) {
return nil, errors.Trace(common.ErrPerm)
} else if err != nil {
return nil, errors.Trace(err)
}

if m.IsContainer() {
logger.Warningf("not updating network config for container %q", m.Id())
}

return m, nil
}

func (p *ProvisionerAPI) setOneMachineNetworkConfig(m *state.Machine, networkConfig []params.NetworkConfig) error {
devicesArgs, devicesAddrs := networkingcommon.NetworkConfigsToStateArgs(networkConfig)

logger.Debugf("setting devices: %+v", devicesArgs)
if err := m.SetParentLinkLayerDevicesBeforeTheirChildren(devicesArgs); err != nil {
return errors.Trace(err)
}

logger.Debugf("setting addresses: %+v", devicesAddrs)
if err := m.SetDevicesAddressesIdempotently(devicesAddrs); err != nil {
return errors.Trace(err)
}

logger.Debugf("updated machine %q network config", m.Id())
return nil
}

func (p *ProvisionerAPI) getOneMachineProviderNetworkConfig(m *state.Machine) ([]params.NetworkConfig, error) {
instId, err := m.InstanceId()
if err != nil {
return nil, errors.Trace(err)
}

netEnviron, err := networkingcommon.NetworkingEnvironFromModelConfig(
stateenvirons.EnvironConfigGetter{p.st},
)
if errors.IsNotSupported(err) {
logger.Infof("not updating provider network config: %v", err)
return nil, nil
} else if err != nil {
return nil, errors.Annotate(err, "cannot get provider network config")
}

interfaceInfos, err := netEnviron.NetworkInterfaces(instId)
if err != nil {
return nil, errors.Annotatef(err, "cannot get network interfaces of %q", instId)
}
if len(interfaceInfos) == 0 {
logger.Infof("not updating provider network config: no interfaces returned")
return nil, nil
}

providerConfig := networkingcommon.NetworkConfigFromInterfaceInfo(interfaceInfos)
logger.Tracef("provider network config instance %q: %+v", instId, providerConfig)

return providerConfig, nil
}

func (p *ProvisionerAPI) SetHostMachineNetworkConfig(args params.SetMachineNetworkConfig) error {
return p.SetObservedNetworkConfig(args)
}
5 changes: 2 additions & 3 deletions provider/maas/Makefile → network/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ all: bridgescript.go
bridgescript.go: add-juju-bridge.py Makefile
$(RM) $@
echo -n '// This file is auto generated. Edits will be lost.\n\n' >> $@
echo -n 'package maas\n\n' >> $@
echo -n 'const bridgeScriptName = "add-juju-bridge.py"\n\n' >> $@
echo -n "const bridgeScriptPython = \`" >> $@
echo -n 'package network\n\n' >> $@
echo -n "const BridgeScriptPythonContent = \`" >> $@
cat add-juju-bridge.py >> $@
echo -n '`\n' >> $@

Expand Down
15 changes: 3 additions & 12 deletions provider/maas/add-juju-bridge.py → network/add-juju-bridge.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env python

# Copyright 2015 Canonical Ltd.
# Licensed under the AGPLv3, see LICENCE file for details.

Expand Down Expand Up @@ -390,7 +388,6 @@ def shell_cmd(s, verbose=True, exit_on_error=False, dry_run=False):
def arg_parser():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--bridge-prefix', help="bridge prefix", type=str, required=False, default='br-')
parser.add_argument('--one-time-backup', help='A one time backup of filename', action='store_true', default=True, required=False)
parser.add_argument('--activate', help='activate new configuration', action='store_true', default=False, required=False)
parser.add_argument('--interfaces-to-bridge', help="interfaces to bridge; space delimited", type=str, required=True)
parser.add_argument('--dry-run', help="dry run, no activation", action='store_true', default=False, required=False)
Expand Down Expand Up @@ -427,17 +424,10 @@ def main(args):
print("already bridged, or nothing to do.")
exit(0)

if not args.dry_run and args.one_time_backup:
backup_file = "{}-before-add-juju-bridge".format(args.filename)
if not os.path.isfile(backup_file):
shutil.copy2(args.filename, backup_file)

ifquery = "$(ifquery --interfaces={} --exclude=lo --list)".format(args.filename)

print("**** Original configuration")
shell_cmd("cat {}".format(args.filename), dry_run=args.dry_run)
shell_cmd("ifconfig -a", dry_run=args.dry_run)
shell_cmd("ifdown --exclude=lo --interfaces={} {}".format(args.filename, ifquery), dry_run=args.dry_run)
shell_cmd("ifdown --exclude=lo --interfaces={} {}".format(args.filename, " ".join(interfaces)), dry_run=args.dry_run)

print("**** Activating new configuration")

Expand All @@ -461,8 +451,9 @@ def main(args):
shell_cmd("sleep 3", dry_run=args.dry_run)
break

bridged_names = [ args.bridge_prefix + x for x in interfaces ]
shell_cmd("cat {}".format(args.filename), dry_run=args.dry_run)
shell_cmd("ifup --exclude=lo --interfaces={} {}".format(args.filename, ifquery), dry_run=args.dry_run)
shell_cmd("ifup --exclude=lo --interfaces={} {}".format(args.filename, " ".join(bridged_names)), dry_run=args.dry_run)
shell_cmd("ip link show up", dry_run=args.dry_run)
shell_cmd("ifconfig -a", dry_run=args.dry_run)
shell_cmd("ip route show", dry_run=args.dry_run)
Expand Down
158 changes: 158 additions & 0 deletions network/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package network

import (
"fmt"
"io/ioutil"
"os"
"strings"
"time"

"github.com/juju/errors"
"github.com/juju/utils/clock"
"github.com/juju/utils/exec"
)

type BridgerConfig struct {
Clock clock.Clock
Timeout time.Duration
BridgePrefix string
InputFile string
}

type Bridger interface {
Bridge(deviceNames []string) error
}

type etcNetworkInterfacesBridger struct {
cfg BridgerConfig
}

var _ Bridger = (*etcNetworkInterfacesBridger)(nil)

// bestPythonVersion returns a string to the best python interpreter
// found.
//
// For ubuntu series < xenial we prefer python2 over python3 as we
// don't want to invalidate lots of testing against known cloud-image
// contents. A summary of Ubuntu releases and python inclusion in the
// default install of Ubuntu Server is as follows:
//
// 12.04 precise: python 2 (2.7.3)
// 14.04 trusty: python 2 (2.7.5) and python3 (3.4.0)
// 14.10 utopic: python 2 (2.7.8) and python3 (3.4.2)
// 15.04 vivid: python 2 (2.7.9) and python3 (3.4.3)
// 15.10 wily: python 2 (2.7.9) and python3 (3.4.3)
// 16.04 xenial: python 3 only (3.5.1)
//
// going forward: python 3 only
func bestPythonVersion() string {
for _, version := range []string{
"/usr/bin/python2",
"/usr/bin/python3",
"/usr/bin/python",
} {
if _, err := os.Stat(version); err == nil {
return version
}
}
return ""
}

func writePythonScript(content string, perms os.FileMode) (string, error) {
tmpfile, err := ioutil.TempFile("", "add-bridge")

if err != nil {
return "", errors.Trace(err)
}

if err := tmpfile.Close(); err != nil {
os.Remove(tmpfile.Name())
return "", errors.Trace(err)
}

if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), perms); err != nil {
os.Remove(tmpfile.Name())
return "", errors.Trace(err)
}

if err := os.Chmod(tmpfile.Name(), 0755); err != nil {
os.Remove(tmpfile.Name())
return "", errors.Trace(err)
}

return tmpfile.Name(), nil
}

func runCommandWithTimeout(command string, timeout time.Duration, clock clock.Clock) (*exec.ExecResponse, error) {
cmd := exec.RunParams{
Commands: command,
Environment: os.Environ(),
Clock: clock,
}

err := cmd.Run()
if err != nil {
return nil, errors.Trace(err)
}

var cancel chan struct{}
if timeout != 0 {
cancel = make(chan struct{})
go func() {
<-clock.After(timeout)
close(cancel)
}()
}

return cmd.WaitWithCancel(cancel)
}

func (b *etcNetworkInterfacesBridger) Bridge(deviceNames []string) error {
pythonVersion := bestPythonVersion()
if pythonVersion == "" {
return errors.New("no Python installation found")
}

content := fmt.Sprintf("#!%s\n%s\n", pythonVersion, BridgeScriptPythonContent)
tmpfile, err := writePythonScript(content, 0755)

if err != nil {
return errors.Annotatef(err, "failed to write bridgescript")
}

defer os.Remove(tmpfile)

command := fmt.Sprintf("%s %q --activate --bridge-prefix=%q --interfaces-to-bridge=%q %q",
pythonVersion, tmpfile, b.cfg.BridgePrefix, strings.Join(deviceNames, " "), b.cfg.InputFile)

logger.Infof("running %q with timeout=%v", command, b.cfg.Timeout)

result, err := runCommandWithTimeout(command, time.Duration(b.cfg.Timeout), b.cfg.Clock)

if err != nil {
return errors.Trace(err)
}

logger.Infof("bridgescript result=%v", result.Code)

if result.Code != 0 {
logger.Errorf("bridgescript stdout\n%s\n", result.Stdout)
logger.Errorf("bridgescript stderr\n%s\n", result.Stderr)
}

return err
}

func NewEtcNetworkInterfacesBridger(clock clock.Clock, timeout time.Duration, bridgePrefix, filename string) Bridger {
return &etcNetworkInterfacesBridger{
cfg: BridgerConfig{
Clock: clock,
Timeout: timeout,
BridgePrefix: bridgePrefix,
InputFile: filename,
},
}
}
Loading

0 comments on commit 4728ab6

Please sign in to comment.