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