github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/provisioner/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  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/utils/arch"
    10  	"github.com/juju/version"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	apiprovisioner "github.com/juju/juju/api/provisioner"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/cloudconfig"
    16  	"github.com/juju/juju/container"
    17  	"github.com/juju/juju/core/instance"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/tools"
    20  )
    21  
    22  //go:generate mockgen -package mocks -destination mocks/apicalls_mock.go github.com/juju/juju/worker/provisioner APICalls
    23  type APICalls interface {
    24  	ContainerConfig() (params.ContainerConfig, error)
    25  	PrepareContainerInterfaceInfo(names.MachineTag) ([]network.InterfaceInfo, error)
    26  	GetContainerInterfaceInfo(names.MachineTag) ([]network.InterfaceInfo, error)
    27  	GetContainerProfileInfo(names.MachineTag) ([]*apiprovisioner.LXDProfileResult, error)
    28  	ReleaseContainerAddresses(names.MachineTag) error
    29  	SetHostMachineNetworkConfig(names.MachineTag, []params.NetworkConfig) error
    30  	HostChangesForContainer(containerTag names.MachineTag) ([]network.DeviceToBridge, int, error)
    31  }
    32  
    33  type hostArchToolsFinder struct {
    34  	f ToolsFinder
    35  }
    36  
    37  // FindTools is defined on the ToolsFinder interface.
    38  func (h hostArchToolsFinder) FindTools(v version.Number, series, _ string) (tools.List, error) {
    39  	// Override the arch constraint with the arch of the host.
    40  	return h.f.FindTools(v, series, arch.HostArch())
    41  }
    42  
    43  // resolvConf contains the full path to common resolv.conf files on the local
    44  // system. Defined here so it can be overridden for testing.
    45  var resolvConfFiles = []string{"/etc/resolv.conf", "/etc/systemd/resolved.conf", "/run/systemd/resolve/resolv.conf"}
    46  
    47  func prepareOrGetContainerInterfaceInfo(
    48  	api APICalls,
    49  	machineID string,
    50  	allocateOrMaintain bool,
    51  	log loggo.Logger,
    52  ) ([]network.InterfaceInfo, error) {
    53  	maintain := !allocateOrMaintain
    54  
    55  	if maintain {
    56  		// TODO(jam): 2016-12-14 The function is called
    57  		// 'prepareOrGet', but the only time we would handle the 'Get'
    58  		// side, we explicitly abort. Something seems wrong.
    59  		log.Debugf("not running maintenance for machine %q", machineID)
    60  		return nil, nil
    61  	}
    62  
    63  	log.Debugf("using multi-bridge networking for container %q", machineID)
    64  
    65  	containerTag := names.NewMachineTag(machineID)
    66  	preparedInfo, err := api.PrepareContainerInterfaceInfo(containerTag)
    67  	if err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  	log.Tracef("PrepareContainerInterfaceInfo returned %+v", preparedInfo)
    71  
    72  	return preparedInfo, nil
    73  }
    74  
    75  // finishNetworkConfig populates the ParentInterfaceName, DNSServers, and
    76  // DNSSearchDomains fields on each element, when they are not set. The given
    77  // bridgeDevice is used for ParentInterfaceName, while the DNS config is
    78  // discovered using network.ParseResolvConf(). If interfaces has zero length,
    79  // container.FallbackInterfaceInfo() is used as fallback.
    80  func finishNetworkConfig(bridgeDevice string, interfaces []network.InterfaceInfo) ([]network.InterfaceInfo, error) {
    81  	haveNameservers, haveSearchDomains := false, false
    82  	if len(interfaces) == 0 {
    83  		// Use the fallback network config as a last resort.
    84  		interfaces = container.FallbackInterfaceInfo()
    85  	}
    86  
    87  	results := make([]network.InterfaceInfo, len(interfaces))
    88  	for i, info := range interfaces {
    89  		if info.ParentInterfaceName == "" {
    90  			info.ParentInterfaceName = bridgeDevice
    91  		}
    92  
    93  		if len(info.DNSServers) > 0 {
    94  			haveNameservers = true
    95  		}
    96  
    97  		if len(info.DNSSearchDomains) > 0 {
    98  			haveSearchDomains = true
    99  		}
   100  		results[i] = info
   101  	}
   102  
   103  	if !haveNameservers || !haveSearchDomains {
   104  		warnMissing := func(s string) { logger.Warningf("no %s supplied by provider, using host's %s.", s, s) }
   105  		if !haveNameservers {
   106  			warnMissing("name servers")
   107  		}
   108  		if !haveSearchDomains {
   109  			warnMissing("search domains")
   110  		}
   111  
   112  		logger.Warningf("incomplete DNS config found, discovering host's DNS config")
   113  		dnsConfig, err := findDNSServerConfig()
   114  		if err != nil {
   115  			return nil, errors.Trace(err)
   116  		}
   117  
   118  		// Since the result is sorted, the first entry is the primary NIC. Also,
   119  		// results always contains at least one element.
   120  		results[0].DNSServers = dnsConfig.Nameservers
   121  		results[0].DNSSearchDomains = dnsConfig.SearchDomains
   122  		logger.Debugf(
   123  			"setting DNS servers %+v and domains %+v on container interface %q",
   124  			results[0].DNSServers, results[0].DNSSearchDomains, results[0].InterfaceName,
   125  		)
   126  	}
   127  
   128  	return results, nil
   129  }
   130  
   131  // findDNSServerConfig is a heuristic method to find an adequate DNS
   132  // configuration. Currently the only rule that is implemented is that common
   133  // configuration files are parsed until a configuration is found that is not a
   134  // loopback address (i.e systemd/resolved stub address).
   135  func findDNSServerConfig() (*network.DNSConfig, error) {
   136  	for _, dnsConfigFile := range resolvConfFiles {
   137  		dnsConfig, err := network.ParseResolvConf(dnsConfigFile)
   138  		if err != nil {
   139  			return nil, errors.Trace(err)
   140  		}
   141  		for _, nameServer := range dnsConfig.Nameservers {
   142  			if nameServer.Scope != network.ScopeMachineLocal {
   143  				logger.Debugf("The DNS configuration from %s has been selected for use", dnsConfigFile)
   144  				return dnsConfig, nil
   145  			}
   146  		}
   147  	}
   148  	return nil, errors.New("A DNS configuration could not be found.")
   149  }
   150  
   151  func releaseContainerAddresses(
   152  	api APICalls,
   153  	instanceID instance.Id,
   154  	namespace instance.Namespace,
   155  	log loggo.Logger,
   156  ) {
   157  	containerTag, err := namespace.MachineTag(string(instanceID))
   158  	if err != nil {
   159  		// Not a reason to cause StopInstances to fail though..
   160  		log.Warningf("unexpected container tag %q: %v", instanceID, err)
   161  		return
   162  	}
   163  	err = api.ReleaseContainerAddresses(containerTag)
   164  	switch {
   165  	case err == nil:
   166  		log.Infof("released all addresses for container %q", containerTag.Id())
   167  	case errors.IsNotSupported(err):
   168  		log.Warningf("not releasing all addresses for container %q: %v", containerTag.Id(), err)
   169  	default:
   170  		log.Warningf(
   171  			"unexpected error trying to release container %q addreses: %v",
   172  			containerTag.Id(), err,
   173  		)
   174  	}
   175  }
   176  
   177  // matchHostArchTools filters the given list of tools to the host architecture.
   178  func matchHostArchTools(allTools tools.List) (tools.List, error) {
   179  	arch := arch.HostArch()
   180  	archTools, err := allTools.Match(tools.Filter{Arch: arch})
   181  	if err == tools.ErrNoMatches {
   182  		return nil, errors.Errorf(
   183  			"need agent binaries for arch %s, only found %s",
   184  			arch, allTools.Arches(),
   185  		)
   186  	} else if err != nil {
   187  		return nil, errors.Trace(err)
   188  	}
   189  	return archTools, nil
   190  }
   191  
   192  // GetMachineCloudInitData is for testing purposes.
   193  var GetMachineCloudInitData = cloudconfig.GetMachineCloudInitData
   194  
   195  // combinedCloudInitData returns a combined map of the given cloudInitData
   196  // and instance cloud init properties provided.
   197  func combinedCloudInitData(
   198  	cloudInitData map[string]interface{},
   199  	containerInheritProperties, series string,
   200  	log loggo.Logger,
   201  ) (map[string]interface{}, error) {
   202  	if containerInheritProperties == "" {
   203  		return cloudInitData, nil
   204  	}
   205  	machineData, err := GetMachineCloudInitData(series)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	if machineData == nil {
   210  		return cloudInitData, nil
   211  	}
   212  
   213  	if cloudInitData == nil {
   214  		cloudInitData = make(map[string]interface{})
   215  	}
   216  
   217  	resultsMap := cloudconfig.CloudConfigByVersionFunc(series)(containerInheritProperties, machineData, log)
   218  	for k, v := range resultsMap {
   219  		cloudInitData[k] = v
   220  	}
   221  
   222  	return cloudInitData, nil
   223  }