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 }