github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/provisioner/lxc-broker.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"strings"
    13  	"text/template"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/names"
    18  	"github.com/juju/utils/exec"
    19  
    20  	"github.com/juju/juju/agent"
    21  	apiprovisioner "github.com/juju/juju/api/provisioner"
    22  	"github.com/juju/juju/apiserver/params"
    23  	"github.com/juju/juju/cloudconfig/instancecfg"
    24  	"github.com/juju/juju/container"
    25  	"github.com/juju/juju/container/lxc"
    26  	"github.com/juju/juju/environs"
    27  	"github.com/juju/juju/instance"
    28  	"github.com/juju/juju/network"
    29  	"github.com/juju/juju/tools"
    30  	"github.com/juju/juju/version"
    31  )
    32  
    33  var lxcLogger = loggo.GetLogger("juju.provisioner.lxc")
    34  
    35  var _ environs.InstanceBroker = (*lxcBroker)(nil)
    36  
    37  type APICalls interface {
    38  	ContainerConfig() (params.ContainerConfig, error)
    39  	PrepareContainerInterfaceInfo(names.MachineTag) ([]network.InterfaceInfo, error)
    40  	GetContainerInterfaceInfo(names.MachineTag) ([]network.InterfaceInfo, error)
    41  }
    42  
    43  var _ APICalls = (*apiprovisioner.State)(nil)
    44  
    45  // Override for testing.
    46  var NewLxcBroker = newLxcBroker
    47  
    48  func newLxcBroker(
    49  	api APICalls,
    50  	agentConfig agent.Config,
    51  	managerConfig container.ManagerConfig,
    52  	imageURLGetter container.ImageURLGetter,
    53  	enableNAT bool,
    54  	defaultMTU int,
    55  ) (environs.InstanceBroker, error) {
    56  	manager, err := lxc.NewContainerManager(managerConfig, imageURLGetter)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	return &lxcBroker{
    61  		manager:     manager,
    62  		api:         api,
    63  		agentConfig: agentConfig,
    64  		enableNAT:   enableNAT,
    65  		defaultMTU:  defaultMTU,
    66  	}, nil
    67  }
    68  
    69  type lxcBroker struct {
    70  	manager     container.Manager
    71  	api         APICalls
    72  	agentConfig agent.Config
    73  	enableNAT   bool
    74  	defaultMTU  int
    75  }
    76  
    77  // StartInstance is specified in the Broker interface.
    78  func (broker *lxcBroker) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
    79  	if args.InstanceConfig.HasNetworks() {
    80  		return nil, errors.New("starting lxc containers with networks is not supported yet")
    81  	}
    82  	// TODO: refactor common code out of the container brokers.
    83  	machineId := args.InstanceConfig.MachineId
    84  	lxcLogger.Infof("starting lxc container for machineId: %s", machineId)
    85  
    86  	// Default to using the host network until we can configure.
    87  	bridgeDevice := broker.agentConfig.Value(agent.LxcBridge)
    88  	if bridgeDevice == "" {
    89  		bridgeDevice = lxc.DefaultLxcBridge
    90  	}
    91  
    92  	if !environs.AddressAllocationEnabled() {
    93  		logger.Debugf(
    94  			"address allocation feature flag not enabled; using DHCP for container %q",
    95  			machineId,
    96  		)
    97  	} else {
    98  		logger.Debugf("trying to allocate static IP for container %q", machineId)
    99  		allocatedInfo, err := configureContainerNetwork(
   100  			machineId,
   101  			bridgeDevice,
   102  			broker.api,
   103  			args.NetworkInfo,
   104  			true, // allocate a new address.
   105  			broker.enableNAT,
   106  		)
   107  		if err != nil {
   108  			// It's fine, just ignore it. The effect will be that the
   109  			// container won't have a static address configured.
   110  			logger.Infof("not allocating static IP for container %q: %v", machineId, err)
   111  		} else {
   112  			args.NetworkInfo = allocatedInfo
   113  		}
   114  	}
   115  	network := container.BridgeNetworkConfig(bridgeDevice, broker.defaultMTU, args.NetworkInfo)
   116  
   117  	// The provisioner worker will provide all tools it knows about
   118  	// (after applying explicitly specified constraints), which may
   119  	// include tools for architectures other than the host's. We
   120  	// must constrain to the host's architecture for LXC.
   121  	archTools, err := args.Tools.Match(tools.Filter{
   122  		Arch: version.Current.Arch,
   123  	})
   124  	if err == tools.ErrNoMatches {
   125  		return nil, errors.Errorf(
   126  			"need tools for arch %s, only found %s",
   127  			version.Current.Arch,
   128  			args.Tools.Arches(),
   129  		)
   130  	}
   131  
   132  	series := archTools.OneSeries()
   133  	args.InstanceConfig.MachineContainerType = instance.LXC
   134  	args.InstanceConfig.Tools = archTools[0]
   135  
   136  	config, err := broker.api.ContainerConfig()
   137  	if err != nil {
   138  		lxcLogger.Errorf("failed to get container config: %v", err)
   139  		return nil, err
   140  	}
   141  	storageConfig := &container.StorageConfig{
   142  		AllowMount: config.AllowLXCLoopMounts,
   143  	}
   144  
   145  	if err := instancecfg.PopulateInstanceConfig(
   146  		args.InstanceConfig,
   147  		config.ProviderType,
   148  		config.AuthorizedKeys,
   149  		config.SSLHostnameVerification,
   150  		config.Proxy,
   151  		config.AptProxy,
   152  		config.AptMirror,
   153  		config.PreferIPv6,
   154  		config.EnableOSRefreshUpdate,
   155  		config.EnableOSUpgrade,
   156  	); err != nil {
   157  		lxcLogger.Errorf("failed to populate machine config: %v", err)
   158  		return nil, err
   159  	}
   160  
   161  	inst, hardware, err := broker.manager.CreateContainer(args.InstanceConfig, series, network, storageConfig)
   162  	if err != nil {
   163  		lxcLogger.Errorf("failed to start container: %v", err)
   164  		return nil, err
   165  	}
   166  	lxcLogger.Infof("started lxc container for machineId: %s, %s, %s", machineId, inst.Id(), hardware.String())
   167  	return &environs.StartInstanceResult{
   168  		Instance: inst,
   169  		Hardware: hardware,
   170  	}, nil
   171  }
   172  
   173  // StopInstances shuts down the given instances.
   174  func (broker *lxcBroker) StopInstances(ids ...instance.Id) error {
   175  	// TODO: potentially parallelise.
   176  	for _, id := range ids {
   177  		lxcLogger.Infof("stopping lxc container for instance: %s", id)
   178  		if err := broker.manager.DestroyContainer(id); err != nil {
   179  			lxcLogger.Errorf("container did not stop: %v", err)
   180  			return err
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  // AllInstances only returns running containers.
   187  func (broker *lxcBroker) AllInstances() (result []instance.Instance, err error) {
   188  	return broker.manager.ListContainers()
   189  }
   190  
   191  type hostArchToolsFinder struct {
   192  	f ToolsFinder
   193  }
   194  
   195  // FindTools is defined on the ToolsFinder interface.
   196  func (h hostArchToolsFinder) FindTools(v version.Number, series string, arch *string) (tools.List, error) {
   197  	// Override the arch constraint with the arch of the host.
   198  	return h.f.FindTools(v, series, &version.Current.Arch)
   199  }
   200  
   201  // resolvConf is the full path to the resolv.conf file on the local
   202  // system. Defined here so it can be overriden for testing.
   203  var resolvConf = "/etc/resolv.conf"
   204  
   205  // localDNSServers parses the /etc/resolv.conf file (if available) and
   206  // extracts all nameservers addresses, and the default search domain
   207  // and returns them.
   208  func localDNSServers() ([]network.Address, string, error) {
   209  	file, err := os.Open(resolvConf)
   210  	if os.IsNotExist(err) {
   211  		return nil, "", nil
   212  	} else if err != nil {
   213  		return nil, "", errors.Annotatef(err, "cannot open %q", resolvConf)
   214  	}
   215  	defer file.Close()
   216  
   217  	var addresses []network.Address
   218  	var searchDomain string
   219  	scanner := bufio.NewScanner(file)
   220  	for scanner.Scan() {
   221  		line := strings.TrimSpace(scanner.Text())
   222  		if strings.HasPrefix(line, "#") {
   223  			// Skip comments.
   224  			continue
   225  		}
   226  		if strings.HasPrefix(line, "nameserver") {
   227  			address := strings.TrimPrefix(line, "nameserver")
   228  			// Drop comments after the address, if any.
   229  			if strings.Contains(address, "#") {
   230  				address = address[:strings.Index(address, "#")]
   231  			}
   232  			address = strings.TrimSpace(address)
   233  			addresses = append(addresses, network.NewAddress(address))
   234  		}
   235  		if strings.HasPrefix(line, "search") {
   236  			searchDomain = strings.TrimPrefix(line, "search")
   237  			// Drop comments after the domain, if any.
   238  			if strings.Contains(searchDomain, "#") {
   239  				searchDomain = searchDomain[:strings.Index(searchDomain, "#")]
   240  			}
   241  			searchDomain = strings.TrimSpace(searchDomain)
   242  		}
   243  	}
   244  
   245  	if err := scanner.Err(); err != nil {
   246  		return nil, "", errors.Annotatef(err, "cannot read DNS servers from %q", resolvConf)
   247  	}
   248  	return addresses, searchDomain, nil
   249  }
   250  
   251  // ipRouteAdd is the command template to add a static route for
   252  // .ContainerIP using the .HostBridge device (usually lxcbr0 or virbr0).
   253  var ipRouteAdd = mustParseTemplate("ipRouteAdd", `
   254  ip route add {{.ContainerIP}} dev {{.HostBridge}}`[1:])
   255  
   256  type IptablesRule struct {
   257  	Table string
   258  	Chain string
   259  	Rule  string
   260  }
   261  
   262  var skipSNATRule = IptablesRule{
   263  	// For EC2, to get internet access we need traffic to appear with
   264  	// source address matching the container's host. For internal
   265  	// traffic we want to keep the container IP because it is used
   266  	// by some services. This rule sits above the SNAT rule, which
   267  	// changes the source address of traffic to the container host IP
   268  	// address, skipping this modification if the traffic destination
   269  	// is inside the EC2 VPC.
   270  	"nat",
   271  	"POSTROUTING",
   272  	"-d {{.SubnetCIDR}} -o {{.HostIF}} -j RETURN",
   273  }
   274  
   275  var iptablesRules = map[string]IptablesRule{
   276  	// iptablesCheckSNAT is the command template to verify if a SNAT
   277  	// rule already exists for the host NIC named .HostIF (usually
   278  	// eth0) and source address .HostIP (usually eth0's address). We
   279  	// need to check whether the rule exists because we only want to
   280  	// add it once. Exit code 0 means the rule exists, 1 means it
   281  	// doesn't.
   282  	"iptablesSNAT": {
   283  		"nat",
   284  		"POSTROUTING",
   285  		"-o {{.HostIF}} -j SNAT --to-source {{.HostIP}}",
   286  	}, "iptablesForwardOut": {
   287  		// Ensure that we have ACCEPT rules that apply to the containers that
   288  		// we are creating so any DROP rules added by libvirt while setting
   289  		// up virbr0 further down the chain don't disrupt wanted traffic.
   290  		"filter",
   291  		"FORWARD",
   292  		"-d {{.ContainerCIDR}} -o {{.HostBridge}} -j ACCEPT",
   293  	}, "iptablesForwardIn": {
   294  		"filter",
   295  		"FORWARD",
   296  		"-s {{.ContainerCIDR}} -i {{.HostBridge}} -j ACCEPT",
   297  	}}
   298  
   299  // mustParseTemplate works like template.Parse, but panics on error.
   300  func mustParseTemplate(name, source string) *template.Template {
   301  	templ, err := template.New(name).Parse(source)
   302  	if err != nil {
   303  		panic(err.Error())
   304  	}
   305  	return templ
   306  }
   307  
   308  // mustExecTemplate works like template.Parse followed by template.Execute,
   309  // but panics on error.
   310  func mustExecTemplate(name, tmpl string, data interface{}) string {
   311  	t := mustParseTemplate(name, tmpl)
   312  	var buf bytes.Buffer
   313  	if err := t.Execute(&buf, data); err != nil {
   314  		panic(err.Error())
   315  	}
   316  	return buf.String()
   317  }
   318  
   319  // runTemplateCommand executes the given template with the given data,
   320  // which generates a command to execute. If exitNonZeroOK is true, no
   321  // error is returned if the exit code is not 0, otherwise an error is
   322  // returned.
   323  func runTemplateCommand(t *template.Template, exitNonZeroOK bool, data interface{}) (
   324  	exitCode int, err error,
   325  ) {
   326  	// Clone the template to ensure the original won't be changed.
   327  	cloned, err := t.Clone()
   328  	if err != nil {
   329  		return -1, errors.Annotatef(err, "cannot clone command template %q", t.Name())
   330  	}
   331  	var buf bytes.Buffer
   332  	if err := cloned.Execute(&buf, data); err != nil {
   333  		return -1, errors.Annotatef(err, "cannot execute command template %q", t.Name())
   334  	}
   335  	command := buf.String()
   336  	logger.Debugf("running command %q", command)
   337  	result, err := exec.RunCommands(exec.RunParams{Commands: command})
   338  	if err != nil {
   339  		return -1, errors.Annotatef(err, "cannot run command %q", command)
   340  	}
   341  	exitCode = result.Code
   342  	stdout := string(result.Stdout)
   343  	stderr := string(result.Stderr)
   344  	logger.Debugf(
   345  		"command %q returned code=%d, stdout=%q, stderr=%q",
   346  		command, exitCode, stdout, stderr,
   347  	)
   348  	if exitCode != 0 {
   349  		if exitNonZeroOK {
   350  			return exitCode, nil
   351  		}
   352  		return exitCode, errors.Errorf(
   353  			"command %q failed with exit code %d",
   354  			command, exitCode,
   355  		)
   356  	}
   357  	return 0, nil
   358  }
   359  
   360  // setupRoutesAndIPTables sets up on the host machine the needed
   361  // iptables rules and static routes for an addressable container.
   362  var setupRoutesAndIPTables = func(
   363  	primaryNIC string,
   364  	primaryAddr network.Address,
   365  	bridgeName string,
   366  	ifaceInfo []network.InterfaceInfo,
   367  	enableNAT bool,
   368  ) error {
   369  
   370  	if primaryNIC == "" || primaryAddr.Value == "" || bridgeName == "" || len(ifaceInfo) == 0 {
   371  		return errors.Errorf("primaryNIC, primaryAddr, bridgeName, and ifaceInfo must be all set")
   372  	}
   373  
   374  	for _, iface := range ifaceInfo {
   375  		containerIP := iface.Address.Value
   376  		if containerIP == "" {
   377  			return errors.Errorf("container IP %q must be set", containerIP)
   378  		}
   379  		data := struct {
   380  			HostIF        string
   381  			HostIP        string
   382  			HostBridge    string
   383  			ContainerIP   string
   384  			ContainerCIDR string
   385  			SubnetCIDR    string
   386  		}{primaryNIC, primaryAddr.Value, bridgeName, containerIP, iface.CIDR, iface.CIDR}
   387  
   388  		var addRuleIfDoesNotExist = func(name string, rule IptablesRule) error {
   389  			check := mustExecTemplate("rule", "iptables -t {{.Table}} -C {{.Chain}} {{.Rule}}", rule)
   390  			t := mustParseTemplate(name+"Check", check)
   391  
   392  			code, err := runTemplateCommand(t, true, data)
   393  			if err != nil {
   394  				return errors.Trace(err)
   395  			}
   396  			switch code {
   397  			case 0:
   398  			// Rule does exist. Do nothing
   399  			case 1:
   400  				// Rule does not exist, add it. We insert the rule at the top of the list so it precedes any
   401  				// REJECT rules.
   402  				action := mustExecTemplate("action", "iptables -t {{.Table}} -I {{.Chain}} 1 {{.Rule}}", rule)
   403  				t = mustParseTemplate(name+"Add", action)
   404  				_, err = runTemplateCommand(t, false, data)
   405  				if err != nil {
   406  					return errors.Trace(err)
   407  				}
   408  			default:
   409  				// Unexpected code - better report it.
   410  				return errors.Errorf("iptables failed with unexpected exit code %d", code)
   411  			}
   412  			return nil
   413  		}
   414  
   415  		for name, rule := range iptablesRules {
   416  			if !enableNAT && name == "iptablesSNAT" {
   417  				// Do not add the SNAT rule if we shouldn't enable
   418  				// NAT.
   419  				continue
   420  			}
   421  			if err := addRuleIfDoesNotExist(name, rule); err != nil {
   422  				return err
   423  			}
   424  		}
   425  
   426  		// TODO(dooferlad): subnets should be a list of subnets in the EC2 VPC and
   427  		// should be empty for MAAS. See bug http://pad.lv/1443942
   428  		if enableNAT {
   429  			// Only add the following hack to allow AWS egress traffic
   430  			// for hosted containers to work.
   431  			subnets := []string{data.HostIP + "/16"}
   432  			for _, subnet := range subnets {
   433  				data.SubnetCIDR = subnet
   434  				if err := addRuleIfDoesNotExist("skipSNAT", skipSNATRule); err != nil {
   435  					return err
   436  				}
   437  			}
   438  		}
   439  
   440  		code, err := runTemplateCommand(ipRouteAdd, false, data)
   441  		// Ignore errors if the exit code was 2, which signals that the route was not added
   442  		// because it already exists.
   443  		if code != 2 && err != nil {
   444  			return errors.Trace(err)
   445  		}
   446  		if code == 2 {
   447  			logger.Tracef("route already exists - not added")
   448  		} else {
   449  			logger.Tracef("route added: container uses host network interface")
   450  		}
   451  	}
   452  	logger.Infof("successfully configured iptables and routes for container interfaces")
   453  
   454  	return nil
   455  }
   456  
   457  var (
   458  	netInterfaces  = net.Interfaces
   459  	interfaceAddrs = (*net.Interface).Addrs
   460  )
   461  
   462  // discoverPrimaryNIC returns the name of the first network interface
   463  // on the machine which is up and has address, along with the first
   464  // address it has.
   465  func discoverPrimaryNIC() (string, network.Address, error) {
   466  	interfaces, err := netInterfaces()
   467  	if err != nil {
   468  		return "", network.Address{}, errors.Annotatef(err, "cannot get network interfaces")
   469  	}
   470  	logger.Tracef("trying to discover primary network interface")
   471  	for _, iface := range interfaces {
   472  		if iface.Flags&net.FlagLoopback != 0 {
   473  			// Skip the loopback.
   474  			logger.Tracef("not using loopback interface %q", iface.Name)
   475  			continue
   476  		}
   477  		if iface.Flags&net.FlagUp != 0 {
   478  			// Possibly the primary, but ensure it has an address as
   479  			// well.
   480  			logger.Tracef("verifying interface %q has addresses", iface.Name)
   481  			addrs, err := interfaceAddrs(&iface)
   482  			if err != nil {
   483  				return "", network.Address{}, errors.Annotatef(err, "cannot get %q addresses", iface.Name)
   484  			}
   485  			if len(addrs) > 0 {
   486  				// We found it.
   487  				// Check if it's an IP or a CIDR.
   488  				addr := addrs[0].String()
   489  				ip := net.ParseIP(addr)
   490  				if ip == nil {
   491  					// Try a CIDR.
   492  					ip, _, err = net.ParseCIDR(addr)
   493  					if err != nil {
   494  						return "", network.Address{}, errors.Annotatef(err, "cannot parse address %q", addr)
   495  					}
   496  				}
   497  				addr = ip.String()
   498  
   499  				logger.Tracef("primary network interface is %q, address %q", iface.Name, addr)
   500  				return iface.Name, network.NewAddress(addr), nil
   501  			}
   502  		}
   503  	}
   504  	return "", network.Address{}, errors.Errorf("cannot detect the primary network interface")
   505  }
   506  
   507  // MACAddressTemplate is used to generate a unique MAC address for a
   508  // container. Every 'x' is replaced by a random hexadecimal digit,
   509  // while the rest is kept as-is.
   510  const MACAddressTemplate = "00:16:3e:xx:xx:xx"
   511  
   512  // configureContainerNetworking tries to allocate a static IP address
   513  // for the given containerId using the provisioner API, when
   514  // allocateAddress is true. Otherwise it configures the container with
   515  // an already allocated address, when allocateAddress is false (e.g.
   516  // after a host reboot). If the API call fails, it's not critical -
   517  // just a warning, and it won't cause StartInstance to fail.
   518  func configureContainerNetwork(
   519  	containerId, bridgeDevice string,
   520  	apiFacade APICalls,
   521  	ifaceInfo []network.InterfaceInfo,
   522  	allocateAddress bool,
   523  	enableNAT bool,
   524  ) (finalIfaceInfo []network.InterfaceInfo, err error) {
   525  	defer func() {
   526  		if err != nil {
   527  			logger.Warningf(
   528  				"failed configuring a static IP for container %q: %v",
   529  				containerId, err,
   530  			)
   531  		}
   532  	}()
   533  
   534  	if len(ifaceInfo) != 0 {
   535  		// When we already have interface info, don't overwrite it.
   536  		return nil, nil
   537  	}
   538  
   539  	var primaryNIC string
   540  	var primaryAddr network.Address
   541  	primaryNIC, primaryAddr, err = discoverPrimaryNIC()
   542  	if err != nil {
   543  		return nil, errors.Trace(err)
   544  	}
   545  
   546  	if allocateAddress {
   547  		logger.Debugf("trying to allocate a static IP for container %q", containerId)
   548  		finalIfaceInfo, err = apiFacade.PrepareContainerInterfaceInfo(names.NewMachineTag(containerId))
   549  	} else {
   550  		logger.Debugf("getting allocated static IP for container %q", containerId)
   551  		finalIfaceInfo, err = apiFacade.GetContainerInterfaceInfo(names.NewMachineTag(containerId))
   552  	}
   553  	if err != nil {
   554  		return nil, errors.Trace(err)
   555  	}
   556  	logger.Debugf("container interface info result %#v", finalIfaceInfo)
   557  
   558  	// Populate ConfigType and DNSServers as needed.
   559  	var dnsServers []network.Address
   560  	var searchDomain string
   561  	dnsServers, searchDomain, err = localDNSServers()
   562  	if err != nil {
   563  		return nil, errors.Trace(err)
   564  	}
   565  	// Generate the final configuration for each container interface.
   566  	for i, _ := range finalIfaceInfo {
   567  		// Always start at the first device index and generate the
   568  		// interface name based on that. We need to do this otherwise
   569  		// the container will inherit the host's device index and
   570  		// interface name.
   571  		finalIfaceInfo[i].DeviceIndex = i
   572  		finalIfaceInfo[i].InterfaceName = fmt.Sprintf("eth%d", i)
   573  		finalIfaceInfo[i].MACAddress = MACAddressTemplate
   574  		finalIfaceInfo[i].ConfigType = network.ConfigStatic
   575  		finalIfaceInfo[i].DNSServers = dnsServers
   576  		finalIfaceInfo[i].DNSSearch = searchDomain
   577  		finalIfaceInfo[i].GatewayAddress = primaryAddr
   578  	}
   579  	err = setupRoutesAndIPTables(
   580  		primaryNIC,
   581  		primaryAddr,
   582  		bridgeDevice,
   583  		finalIfaceInfo,
   584  		enableNAT,
   585  	)
   586  	if err != nil {
   587  		return nil, errors.Trace(err)
   588  	}
   589  	return finalIfaceInfo, nil
   590  }
   591  
   592  // MaintainInstance checks that the container's host has the required iptables and routing
   593  // rules to make the container visible to both the host and other machines on the same subnet.
   594  func (broker *lxcBroker) MaintainInstance(args environs.StartInstanceParams) error {
   595  	machineId := args.InstanceConfig.MachineId
   596  	if !environs.AddressAllocationEnabled() {
   597  		lxcLogger.Debugf("address allocation disabled: Not running maintenance for lxc container with machineId: %s",
   598  			machineId)
   599  		return nil
   600  	}
   601  
   602  	lxcLogger.Debugf("running maintenance for lxc container with machineId: %s", machineId)
   603  
   604  	// Default to using the host network until we can configure.
   605  	bridgeDevice := broker.agentConfig.Value(agent.LxcBridge)
   606  	if bridgeDevice == "" {
   607  		bridgeDevice = lxc.DefaultLxcBridge
   608  	}
   609  	_, err := configureContainerNetwork(
   610  		machineId,
   611  		bridgeDevice,
   612  		broker.api,
   613  		args.NetworkInfo,
   614  		false, // don't allocate a new address.
   615  		broker.enableNAT,
   616  	)
   617  	return err
   618  }