github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/broker/broker.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package broker
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names/v5"
    13  
    14  	apiprovisioner "github.com/juju/juju/api/agent/provisioner"
    15  	"github.com/juju/juju/cloudconfig"
    16  	"github.com/juju/juju/cloudconfig/instancecfg"
    17  	"github.com/juju/juju/core/arch"
    18  	corebase "github.com/juju/juju/core/base"
    19  	"github.com/juju/juju/core/instance"
    20  	corenetwork "github.com/juju/juju/core/network"
    21  	"github.com/juju/juju/network"
    22  	"github.com/juju/juju/rpc/params"
    23  	coretools "github.com/juju/juju/tools"
    24  )
    25  
    26  var logger = loggo.GetLogger("juju.container.broker")
    27  
    28  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/apicalls_mock.go github.com/juju/juju/container/broker APICalls
    29  type APICalls interface {
    30  	ContainerConfig() (params.ContainerConfig, error)
    31  	PrepareContainerInterfaceInfo(names.MachineTag) (corenetwork.InterfaceInfos, error)
    32  	GetContainerProfileInfo(names.MachineTag) ([]*apiprovisioner.LXDProfileResult, error)
    33  	ReleaseContainerAddresses(names.MachineTag) error
    34  	SetHostMachineNetworkConfig(names.MachineTag, []params.NetworkConfig) error
    35  	HostChangesForContainer(containerTag names.MachineTag) ([]network.DeviceToBridge, int, error)
    36  }
    37  
    38  // resolvConf contains the full path to common resolv.conf files on the local
    39  // system. Defined here so it can be overridden for testing.
    40  var resolvConfFiles = []string{"/etc/resolv.conf", "/etc/systemd/resolved.conf", "/run/systemd/resolve/resolv.conf"}
    41  
    42  func prepareContainerInterfaceInfo(
    43  	api APICalls, machineID string, log loggo.Logger,
    44  ) (corenetwork.InterfaceInfos, error) {
    45  	log.Debugf("using multi-bridge networking for container %q", machineID)
    46  
    47  	containerTag := names.NewMachineTag(machineID)
    48  	preparedInfo, err := api.PrepareContainerInterfaceInfo(containerTag)
    49  	if err != nil {
    50  		return nil, errors.Trace(err)
    51  	}
    52  	log.Tracef("PrepareContainerInterfaceInfo returned %+v", preparedInfo)
    53  
    54  	return preparedInfo, nil
    55  }
    56  
    57  // finishNetworkConfig populates the DNSServers and DNSSearchDomains fields on
    58  // each element when they are not set. The given the DNS config is discovered
    59  // using network.ParseResolvConf(). If interfaces has zero length,
    60  // container.FallbackInterfaceInfo() is used as fallback.
    61  func finishNetworkConfig(interfaces corenetwork.InterfaceInfos) (corenetwork.InterfaceInfos, error) {
    62  	haveNameservers, haveSearchDomains := false, false
    63  
    64  	results := make(corenetwork.InterfaceInfos, len(interfaces))
    65  	for i, info := range interfaces {
    66  		if len(info.DNSServers) > 0 {
    67  			haveNameservers = true
    68  		}
    69  
    70  		if len(info.DNSSearchDomains) > 0 {
    71  			haveSearchDomains = true
    72  		}
    73  		results[i] = info
    74  	}
    75  
    76  	if !haveNameservers || !haveSearchDomains {
    77  		warnMissing := func(s string) { logger.Warningf("no %s supplied by provider, using host's %s.", s, s) }
    78  		if !haveNameservers {
    79  			warnMissing("name servers")
    80  		}
    81  		if !haveSearchDomains {
    82  			warnMissing("search domains")
    83  		}
    84  
    85  		logger.Warningf("incomplete DNS config found, discovering host's DNS config")
    86  		dnsConfig, err := findDNSServerConfig()
    87  		if err != nil {
    88  			return nil, errors.Trace(err)
    89  		}
    90  
    91  		// Since the result is sorted, the first entry is the primary NIC. Also,
    92  		// results always contains at least one element.
    93  		results[0].DNSServers = dnsConfig.Nameservers
    94  		results[0].DNSSearchDomains = dnsConfig.SearchDomains
    95  		logger.Debugf(
    96  			"setting DNS servers %+v and domains %+v on container interface %q",
    97  			results[0].DNSServers, results[0].DNSSearchDomains, results[0].InterfaceName,
    98  		)
    99  	}
   100  
   101  	return results, nil
   102  }
   103  
   104  // findDNSServerConfig is a heuristic method to find an adequate DNS
   105  // configuration. Currently the only rule that is implemented is that common
   106  // configuration files are parsed until a configuration is found that is not a
   107  // loopback address (i.e systemd/resolved stub address).
   108  func findDNSServerConfig() (*corenetwork.DNSConfig, error) {
   109  	for _, dnsConfigFile := range resolvConfFiles {
   110  		dnsConfig, err := corenetwork.ParseResolvConf(dnsConfigFile)
   111  		if err != nil {
   112  			return nil, errors.Trace(err)
   113  		}
   114  		// network.ParseResolvConf returns nil error and nil dnsConfig if the
   115  		// file isn't found, which can lead to a panic when attempting to
   116  		// access the dnsConfig.Nameservers. So instead, just continue and
   117  		// exhaust the resolvConfFiles slice.
   118  		if dnsConfig == nil {
   119  			logger.Tracef("The DNS configuration from %s returned no dnsConfig", dnsConfigFile)
   120  			continue
   121  		}
   122  		for _, nameServer := range dnsConfig.Nameservers {
   123  			if nameServer.Scope != corenetwork.ScopeMachineLocal {
   124  				logger.Debugf("The DNS configuration from %s has been selected for use", dnsConfigFile)
   125  				return dnsConfig, nil
   126  			}
   127  		}
   128  	}
   129  	return nil, errors.New("A DNS configuration could not be found.")
   130  }
   131  
   132  func releaseContainerAddresses(
   133  	api APICalls,
   134  	instanceID instance.Id,
   135  	namespace instance.Namespace,
   136  	log loggo.Logger,
   137  ) {
   138  	containerTag, err := namespace.MachineTag(string(instanceID))
   139  	if err != nil {
   140  		// Not a reason to cause StopInstances to fail though..
   141  		log.Warningf("unexpected container tag %q: %v", instanceID, err)
   142  		return
   143  	}
   144  	err = api.ReleaseContainerAddresses(containerTag)
   145  	switch {
   146  	case err == nil:
   147  		log.Infof("released all addresses for container %q", containerTag.Id())
   148  	case errors.IsNotSupported(err):
   149  		log.Warningf("not releasing all addresses for container %q: %v", containerTag.Id(), err)
   150  	default:
   151  		log.Warningf(
   152  			"unexpected error trying to release container %q addresses: %v",
   153  			containerTag.Id(), err,
   154  		)
   155  	}
   156  }
   157  
   158  // matchHostArchTools filters the given list of tools to the host architecture.
   159  func matchHostArchTools(allTools coretools.List) (coretools.List, error) {
   160  	arch := arch.HostArch()
   161  	archTools, err := allTools.Match(coretools.Filter{Arch: arch})
   162  	if err == coretools.ErrNoMatches {
   163  		agentArch, _ := allTools.OneArch()
   164  		return nil, errors.Errorf(
   165  			"need agent binaries for arch %s, only found %s",
   166  			arch, agentArch,
   167  		)
   168  	} else if err != nil {
   169  		return nil, errors.Trace(err)
   170  	}
   171  	return archTools, nil
   172  }
   173  
   174  var newMachineInitReader = cloudconfig.NewMachineInitReader
   175  
   176  // combinedCloudInitData returns a combined map of the given cloudInitData
   177  // and instance cloud init properties provided.
   178  func combinedCloudInitData(
   179  	cloudInitData map[string]interface{},
   180  	containerInheritProperties string, base corebase.Base,
   181  	log loggo.Logger,
   182  ) (map[string]interface{}, error) {
   183  	if containerInheritProperties == "" {
   184  		return cloudInitData, nil
   185  	}
   186  
   187  	reader, err := newMachineInitReader(base)
   188  	if err != nil {
   189  		return nil, errors.Trace(err)
   190  	}
   191  
   192  	machineData, err := reader.GetInitConfig()
   193  	if err != nil {
   194  		return nil, errors.Trace(err)
   195  	}
   196  	if machineData == nil {
   197  		return cloudInitData, nil
   198  	}
   199  
   200  	if cloudInitData == nil {
   201  		cloudInitData = make(map[string]interface{})
   202  	}
   203  
   204  	props := strings.Split(containerInheritProperties, ",")
   205  	for i, p := range props {
   206  		props[i] = strings.TrimSpace(p)
   207  	}
   208  
   209  	// MAAS versions 2.5 and later no longer write repository settings as apt
   210  	// config in cloud-init data.
   211  	// These settings are now represented in curtin data and are a single key,
   212  	// "sources_list" with a value equal to what the content of
   213  	// /etc/apt/sources.list will be.
   214  	// If apt-sources is being inherited, automatically search for the new
   215  	// setting, so new MAAS versions keep working with inherited apt sources.
   216  	if set.NewStrings(props...).Contains("apt-sources") {
   217  		props = append(props, "apt-sources_list")
   218  	}
   219  
   220  	resultsMap := reader.ExtractPropertiesFromConfig(props, machineData, log)
   221  	for k, v := range resultsMap {
   222  		cloudInitData[k] = v
   223  	}
   224  
   225  	return cloudInitData, nil
   226  }
   227  
   228  // proxyConfigurationFromContainerCfg populates a ProxyConfiguration object
   229  // from an ContainerConfig API response.
   230  func proxyConfigurationFromContainerCfg(cfg params.ContainerConfig) instancecfg.ProxyConfiguration {
   231  	return instancecfg.ProxyConfiguration{
   232  		Legacy:              cfg.LegacyProxy,
   233  		Juju:                cfg.JujuProxy,
   234  		Apt:                 cfg.AptProxy,
   235  		AptMirror:           cfg.AptMirror,
   236  		Snap:                cfg.SnapProxy,
   237  		SnapStoreAssertions: cfg.SnapStoreAssertions,
   238  		SnapStoreProxyID:    cfg.SnapStoreProxyID,
   239  		SnapStoreProxyURL:   cfg.SnapStoreProxyURL,
   240  	}
   241  }