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 }