github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/container/lxc/lxc.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxc 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "net" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "runtime" 16 "strconv" 17 "strings" 18 "text/template" 19 "time" 20 21 "github.com/juju/errors" 22 "github.com/juju/loggo" 23 "github.com/juju/names" 24 "github.com/juju/utils" 25 "github.com/juju/utils/symlink" 26 "launchpad.net/golxc" 27 28 "github.com/juju/juju/agent" 29 "github.com/juju/juju/container" 30 "github.com/juju/juju/environs/cloudinit" 31 "github.com/juju/juju/instance" 32 "github.com/juju/juju/juju/arch" 33 "github.com/juju/juju/version" 34 "github.com/juju/utils/keyvalues" 35 ) 36 37 var logger = loggo.GetLogger("juju.container.lxc") 38 39 var ( 40 defaultTemplate = "ubuntu-cloud" 41 LxcContainerDir = golxc.GetDefaultLXCContainerDir() 42 LxcRestartDir = "/etc/lxc/auto" 43 LxcObjectFactory = golxc.Factory() 44 initProcessCgroupFile = "/proc/1/cgroup" 45 runtimeGOOS = runtime.GOOS 46 ) 47 48 const ( 49 // DefaultLxcBridge is the package created container bridge 50 DefaultLxcBridge = "lxcbr0" 51 // Btrfs is special as we treat it differently for create and clone. 52 Btrfs = "btrfs" 53 ) 54 55 // DefaultNetworkConfig returns a valid NetworkConfig to use the 56 // defaultLxcBridge that is created by the lxc package. 57 func DefaultNetworkConfig() *container.NetworkConfig { 58 return container.BridgeNetworkConfig(DefaultLxcBridge, nil) 59 } 60 61 // FsCommandOutput calls cmd.Output, this is used as an overloading point so 62 // we can test what *would* be run without actually executing another program 63 var FsCommandOutput = (*exec.Cmd).CombinedOutput 64 65 func containerDirFilesystem() (string, error) { 66 cmd := exec.Command("df", "--output=fstype", LxcContainerDir) 67 out, err := FsCommandOutput(cmd) 68 if err != nil { 69 return "", errors.Trace(err) 70 } 71 // The filesystem is the second line. 72 lines := strings.Split(string(out), "\n") 73 if len(lines) < 2 { 74 logger.Errorf("unexpected output: %q", out) 75 return "", errors.Errorf("could not determine filesystem type") 76 } 77 return lines[1], nil 78 } 79 80 // IsLXCSupported returns a boolean value indicating whether or not 81 // we can run LXC containers 82 func IsLXCSupported() (bool, error) { 83 if runtimeGOOS != "linux" { 84 return false, nil 85 } 86 87 file, err := os.Open(initProcessCgroupFile) 88 if err != nil { 89 return false, errors.Trace(err) 90 } 91 defer file.Close() 92 93 scanner := bufio.NewScanner(file) 94 for scanner.Scan() { 95 line := scanner.Text() 96 fields := strings.Split(line, ":") 97 if len(fields) != 3 { 98 return false, errors.Errorf("Malformed cgroup file") 99 } 100 if fields[2] != "/" { 101 // When running in a container the anchor point will be something 102 // other then "/". Return false here as we do not support nested LXC 103 // containers 104 return false, nil 105 } 106 } 107 108 if err := scanner.Err(); err != nil { 109 return false, errors.Errorf("Failed to read cgroup file") 110 } 111 return true, nil 112 } 113 114 type containerManager struct { 115 name string 116 logdir string 117 createWithClone bool 118 useAUFS bool 119 backingFilesystem string 120 imageURLGetter container.ImageURLGetter 121 } 122 123 // containerManager implements container.Manager. 124 var _ container.Manager = (*containerManager)(nil) 125 126 // NewContainerManager returns a manager object that can start and 127 // stop lxc containers. The containers that are created are namespaced 128 // by the name parameter inside the given ManagerConfig. 129 func NewContainerManager(conf container.ManagerConfig, imageURLGetter container.ImageURLGetter) (container.Manager, error) { 130 name := conf.PopValue(container.ConfigName) 131 if name == "" { 132 return nil, errors.Errorf("name is required") 133 } 134 logDir := conf.PopValue(container.ConfigLogDir) 135 if logDir == "" { 136 logDir = agent.DefaultLogDir 137 } 138 var useClone bool 139 useCloneVal := conf.PopValue("use-clone") 140 if useCloneVal != "" { 141 // Explicitly ignore the error result from ParseBool. 142 // If it fails to parse, the value is false, and this suits 143 // us fine. 144 useClone, _ = strconv.ParseBool(useCloneVal) 145 } else { 146 // If no lxc-clone value is explicitly set in config, then 147 // see if the Ubuntu series we are running on supports it 148 // and if it does, we will use clone. 149 useClone = preferFastLXC(releaseVersion()) 150 } 151 useAUFS, _ := strconv.ParseBool(conf.PopValue("use-aufs")) 152 backingFS, err := containerDirFilesystem() 153 if err != nil { 154 // Especially in tests, or a bot, the lxc dir may not exist 155 // causing the test to fail. Since we only really care if the 156 // backingFS is 'btrfs' and we treat the rest the same, just 157 // call it 'unknown'. 158 backingFS = "unknown" 159 } 160 logger.Tracef("backing filesystem: %q", backingFS) 161 conf.WarnAboutUnused() 162 return &containerManager{ 163 name: name, 164 logdir: logDir, 165 createWithClone: useClone, 166 useAUFS: useAUFS, 167 backingFilesystem: backingFS, 168 imageURLGetter: imageURLGetter, 169 }, nil 170 } 171 172 // releaseVersion is a function that returns a string representing the 173 // DISTRIB_RELEASE from the /etc/lsb-release file. 174 var releaseVersion = version.ReleaseVersion 175 176 // preferFastLXC returns true if the host is capable of 177 // LXC cloning from a template. 178 func preferFastLXC(release string) bool { 179 if release == "" { 180 return false 181 } 182 value, err := strconv.ParseFloat(release, 64) 183 if err != nil { 184 return false 185 } 186 return value >= 14.04 187 } 188 189 // CreateContainer creates or clones an LXC container. 190 func (manager *containerManager) CreateContainer( 191 machineConfig *cloudinit.MachineConfig, 192 series string, 193 networkConfig *container.NetworkConfig, 194 ) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) { 195 // Check our preconditions 196 if manager == nil { 197 panic("manager is nil") 198 } else if series == "" { 199 panic("series not set") 200 } else if networkConfig == nil { 201 panic("networkConfig is nil") 202 } 203 204 // Log how long the start took 205 defer func(start time.Time) { 206 if err == nil { 207 logger.Tracef("container %q started: %v", inst.Id(), time.Now().Sub(start)) 208 } 209 }(time.Now()) 210 211 name := names.NewMachineTag(machineConfig.MachineId).String() 212 if manager.name != "" { 213 name = fmt.Sprintf("%s-%s", manager.name, name) 214 } 215 216 // Create the cloud-init. 217 directory, err := container.NewDirectory(name) 218 if err != nil { 219 return nil, nil, errors.Annotate(err, "failed to create a directory for the container") 220 } 221 logger.Tracef("write cloud-init") 222 userDataFilename, err := container.WriteUserData(machineConfig, networkConfig, directory) 223 if err != nil { 224 return nil, nil, errors.Annotate(err, "failed to write user data") 225 } 226 227 var lxcContainer golxc.Container 228 if manager.createWithClone { 229 templateContainer, err := EnsureCloneTemplate( 230 manager.backingFilesystem, 231 series, 232 networkConfig, 233 machineConfig.AuthorizedKeys, 234 machineConfig.AptProxySettings, 235 machineConfig.AptMirror, 236 machineConfig.EnableOSRefreshUpdate, 237 machineConfig.EnableOSUpgrade, 238 manager.imageURLGetter, 239 ) 240 if err != nil { 241 return nil, nil, errors.Annotate(err, "failed to retrieve the template to clone") 242 } 243 templateParams := []string{ 244 "--debug", // Debug errors in the cloud image 245 "--userdata", userDataFilename, // Our groovey cloud-init 246 "--hostid", name, // Use the container name as the hostid 247 } 248 var extraCloneArgs []string 249 if manager.backingFilesystem == Btrfs || manager.useAUFS { 250 extraCloneArgs = append(extraCloneArgs, "--snapshot") 251 } 252 if manager.backingFilesystem != Btrfs && manager.useAUFS { 253 extraCloneArgs = append(extraCloneArgs, "--backingstore", "aufs") 254 } 255 256 lock, err := AcquireTemplateLock(templateContainer.Name(), "clone") 257 if err != nil { 258 return nil, nil, errors.Annotate(err, "failed to acquire lock on template") 259 } 260 defer lock.Unlock() 261 262 // Ensure the run-time effective config of the template 263 // container has correctly ordered network settings, otherwise 264 // Clone() below will fail. This is needed in case we haven't 265 // created a new template now but are reusing an existing one. 266 // See LP bug #1414016. 267 configPath := containerConfigFilename(templateContainer.Name()) 268 if _, err := reorderNetworkConfig(configPath); err != nil { 269 return nil, nil, errors.Annotate(err, "failed to reorder network settings") 270 } 271 272 lxcContainer, err = templateContainer.Clone(name, extraCloneArgs, templateParams) 273 if err != nil { 274 return nil, nil, errors.Annotate(err, "lxc container cloning failed") 275 } 276 } else { 277 // Note here that the lxcObjectFacotry only returns a valid container 278 // object, and doesn't actually construct the underlying lxc container on 279 // disk. 280 lxcContainer = LxcObjectFactory.New(name) 281 templateParams := []string{ 282 "--debug", // Debug errors in the cloud image 283 "--userdata", userDataFilename, // Our groovey cloud-init 284 "--hostid", name, // Use the container name as the hostid 285 "-r", series, 286 } 287 var caCert []byte 288 if manager.imageURLGetter != nil { 289 arch := arch.HostArch() 290 imageURL, err := manager.imageURLGetter.ImageURL(instance.LXC, series, arch) 291 if err != nil { 292 return nil, nil, errors.Annotatef(err, "cannot determine cached image URL") 293 } 294 templateParams = append(templateParams, "-T", imageURL) 295 caCert = manager.imageURLGetter.CACert() 296 } 297 err = createContainer( 298 lxcContainer, 299 directory, 300 networkConfig, 301 nil, 302 templateParams, 303 caCert, 304 ) 305 if err != nil { 306 return nil, nil, errors.Trace(err) 307 } 308 } 309 310 if err := autostartContainer(name); err != nil { 311 return nil, nil, errors.Annotate(err, "failed to configure the container for autostart") 312 } 313 if err := mountHostLogDir(name, manager.logdir); err != nil { 314 return nil, nil, errors.Annotate(err, "failed to mount the directory to log to") 315 } 316 // Update the network settings inside the run-time config of the 317 // container (e.g. /var/lib/lxc/<name>/config) before starting it. 318 netConfig := generateNetworkConfig(networkConfig) 319 if err := updateContainerConfig(name, netConfig); err != nil { 320 return nil, nil, errors.Annotate(err, "failed to update network config") 321 } 322 configPath := containerConfigFilename(name) 323 logger.Tracef("updated network config in %q for container %q", configPath, name) 324 // Ensure the run-time config of the new container has correctly 325 // ordered network settings, otherwise Start() below will fail. We 326 // need this now because after lxc-create or lxc-clone the initial 327 // lxc.conf generated inside createContainer gets merged with 328 // other settings (e.g. system-wide overrides, changes made by 329 // hooks, etc.) and the result can still be incorrectly ordered. 330 // See LP bug #1414016. 331 if _, err := reorderNetworkConfig(configPath); err != nil { 332 return nil, nil, errors.Annotate(err, "failed to reorder network settings") 333 } 334 335 // Start the lxc container with the appropriate settings for grabbing the 336 // console output and a log file. 337 consoleFile := filepath.Join(directory, "console.log") 338 lxcContainer.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug) 339 logger.Tracef("start the container") 340 341 // We explicitly don't pass through the config file to the container.Start 342 // method as we have passed it through at container creation time. This 343 // is necessary to get the appropriate rootfs reference without explicitly 344 // setting it ourselves. 345 if err = lxcContainer.Start("", consoleFile); err != nil { 346 logger.Warningf("container failed to start %v", err) 347 // if the container fails to start we should try to destroy it 348 // check if the container has been constructed 349 if lxcContainer.IsConstructed() { 350 // if so, then we need to destroy the leftover container 351 if derr := lxcContainer.Destroy(); derr != nil { 352 // if an error is reported there is probably a leftover 353 // container that the user should clean up manually 354 logger.Errorf("container failed to start and failed to destroy: %v", derr) 355 return nil, nil, errors.Annotate(err, "container failed to start and failed to destroy: manual cleanup of containers needed") 356 } 357 logger.Warningf("container failed to start and was destroyed - safe to retry") 358 return nil, nil, errors.Wrap(err, instance.NewRetryableCreationError("container failed to start and was destroyed: "+lxcContainer.Name())) 359 } 360 logger.Warningf("container failed to start: %v", err) 361 return nil, nil, errors.Annotate(err, "container failed to start") 362 } 363 364 hardware := &instance.HardwareCharacteristics{ 365 Arch: &version.Current.Arch, 366 } 367 368 return &lxcInstance{lxcContainer, name}, hardware, nil 369 } 370 371 func createContainer( 372 lxcContainer golxc.Container, 373 directory string, 374 networkConfig *container.NetworkConfig, 375 extraCreateArgs, templateParams []string, 376 caCert []byte, 377 ) error { 378 // Generate initial lxc.conf with networking settings. 379 netConfig := generateNetworkConfig(networkConfig) 380 configPath := filepath.Join(directory, "lxc.conf") 381 if err := ioutil.WriteFile(configPath, []byte(netConfig), 0644); err != nil { 382 return errors.Annotatef(err, "failed to write container config %q", configPath) 383 } 384 logger.Tracef("wrote initial config %q for container %q", configPath, lxcContainer.Name()) 385 386 var err error 387 var execEnv []string = nil 388 var closer func() 389 if caCert != nil { 390 execEnv, closer, err = wgetEnvironment(caCert) 391 if err != nil { 392 return errors.Annotatef(err, "failed to get environment for wget execution") 393 } 394 defer closer() 395 } 396 397 // Create the container. 398 logger.Debugf("creating lxc container %q", lxcContainer.Name()) 399 logger.Debugf("lxc-create template params: %v", templateParams) 400 if err := lxcContainer.Create(configPath, defaultTemplate, extraCreateArgs, templateParams, execEnv); err != nil { 401 return errors.Annotatef(err, "lxc container creation failed") 402 } 403 return nil 404 } 405 406 // wgetEnvironment creates a script to call wget with the 407 // --no-check-certificate argument, patching the PATH to ensure 408 // the script is invoked by the lxc template bash script. 409 // It returns a slice of env variables to pass to the lxc create command. 410 func wgetEnvironment(caCert []byte) (execEnv []string, closer func(), _ error) { 411 env := os.Environ() 412 kv, err := keyvalues.Parse(env, true) 413 if err != nil { 414 return nil, nil, errors.Trace(err) 415 } 416 // Create a wget bash script in a temporary directory. 417 tmpDir, err := ioutil.TempDir("", "wget") 418 if err != nil { 419 return nil, nil, errors.Trace(err) 420 } 421 closer = func() { 422 os.RemoveAll(tmpDir) 423 } 424 // Write the ca cert. 425 caCertPath := filepath.Join(tmpDir, "ca-cert.pem") 426 err = ioutil.WriteFile(caCertPath, caCert, 0755) 427 if err != nil { 428 defer closer() 429 return nil, nil, errors.Trace(err) 430 } 431 432 // Write the wget script. 433 wgetTmpl := `#!/bin/bash 434 /usr/bin/wget --ca-certificate=%s $* 435 ` 436 wget := fmt.Sprintf(wgetTmpl, caCertPath) 437 err = ioutil.WriteFile(filepath.Join(tmpDir, "wget"), []byte(wget), 0755) 438 if err != nil { 439 defer closer() 440 return nil, nil, errors.Trace(err) 441 } 442 443 // Update the path to point to the script. 444 for k, v := range kv { 445 if strings.ToUpper(k) == "PATH" { 446 v = strings.Join([]string{tmpDir, v}, string(os.PathListSeparator)) 447 } 448 execEnv = append(execEnv, fmt.Sprintf("%s=%s", k, v)) 449 } 450 return execEnv, closer, nil 451 } 452 453 // parseConfigLine tries to parse a line from an LXC config file. 454 // Empty lines, comments, and lines not starting with "lxc." are 455 // ignored. If successful the setting and its value are returned 456 // stripped of leading/trailing whitespace and line comments (e.g. 457 // "lxc.rootfs" and "/some/path"), otherwise both results are empty. 458 func parseConfigLine(line string) (setting, value string) { 459 input := strings.TrimSpace(line) 460 if len(input) == 0 || 461 strings.HasPrefix(input, "#") || 462 !strings.HasPrefix(input, "lxc.") { 463 return "", "" 464 } 465 parts := strings.SplitN(input, "=", 2) 466 if len(parts) != 2 { 467 // Still not what we're looking for. 468 return "", "" 469 } 470 setting = strings.TrimSpace(parts[0]) 471 value = strings.TrimSpace(parts[1]) 472 if strings.Contains(value, "#") { 473 parts = strings.SplitN(value, "#", 2) 474 if len(parts) == 2 { 475 value = strings.TrimSpace(parts[0]) 476 } 477 } 478 return setting, value 479 } 480 481 // updateContainerConfig selectively replaces, deletes, and/or appends 482 // lines in the named container's current config file, depending on 483 // the contents of newConfig. First, newConfig is split into multiple 484 // lines and parsed, ignoring comments, empty lines and spaces. Then 485 // the occurrence of a setting in a line of the config file will be 486 // replaced by values from newConfig. Values in newConfig are only 487 // used once (in the order provided), so multiple replacements must be 488 // supplied as multiple input values for the same setting in 489 // newConfig. If the value of a setting is empty, the setting will be 490 // removed if found. Settings that are not found and have values will 491 // be appended (also if more values are given than exist). 492 // 493 // For example, with existing config like "lxc.foo = off\nlxc.bar=42\n", 494 // and newConfig like "lxc.bar=\nlxc.foo = bar\nlxc.foo = baz # xx", 495 // the updated config file contains "lxc.foo = bar\nlxc.foo = baz\n". 496 // TestUpdateContainerConfig has this example in code. 497 func updateContainerConfig(name, newConfig string) error { 498 lines := strings.Split(newConfig, "\n") 499 if len(lines) == 0 { 500 return nil 501 } 502 // Extract unique set of line prefixes to match later. Also, keep 503 // a slice of values to replace for each key, as well as a slice 504 // of parsed prefixes to preserve the order when replacing or 505 // appending. 506 parsedLines := make(map[string][]string) 507 var parsedPrefixes []string 508 for _, line := range lines { 509 prefix, value := parseConfigLine(line) 510 if prefix == "" { 511 // Ignore comments, empty lines, and unknown prefixes. 512 continue 513 } 514 if values, found := parsedLines[prefix]; !found { 515 parsedLines[prefix] = []string{value} 516 } else { 517 values = append(values, value) 518 parsedLines[prefix] = values 519 } 520 parsedPrefixes = append(parsedPrefixes, prefix) 521 } 522 523 path := containerConfigFilename(name) 524 currentConfig, err := ioutil.ReadFile(path) 525 if err != nil { 526 return errors.Annotatef(err, "cannot open config %q for container %q", path, name) 527 } 528 input := bytes.NewBuffer(currentConfig) 529 530 // Read the original config and prepare the output to replace it 531 // with. 532 var output bytes.Buffer 533 scanner := bufio.NewScanner(input) 534 for scanner.Scan() { 535 line := scanner.Text() 536 prefix, _ := parseConfigLine(line) 537 values, found := parsedLines[prefix] 538 if !found || len(values) == 0 { 539 // No need to change, just preserve. 540 output.WriteString(line + "\n") 541 continue 542 } 543 // We need to change this line. Pop the first value of the 544 // list and set it. 545 var newValue string 546 newValue, values = values[0], values[1:] 547 parsedLines[prefix] = values 548 549 if newValue == "" { 550 logger.Tracef("removing %q from container %q config %q", line, name, path) 551 continue 552 } 553 newLine := prefix + " = " + newValue 554 if newLine == line { 555 // No need to change and pollute the log, just write it. 556 output.WriteString(line + "\n") 557 continue 558 } 559 logger.Tracef( 560 "replacing %q with %q in container %q config %q", 561 line, newLine, name, path, 562 ) 563 output.WriteString(newLine + "\n") 564 } 565 if err := scanner.Err(); err != nil { 566 return errors.Annotatef(err, "cannot read config for container %q", name) 567 } 568 569 // Now process any prefixes with values still left for appending, 570 // in the original order. 571 for _, prefix := range parsedPrefixes { 572 values := parsedLines[prefix] 573 for _, value := range values { 574 if value == "" { 575 // No need to remove what's not there. 576 continue 577 } 578 newLine := prefix + " = " + value + "\n" 579 logger.Tracef("appending %q to container %q config %q", newLine, name, path) 580 output.WriteString(newLine) 581 } 582 // Reset the values, so we only append the once per prefix. 583 parsedLines[prefix] = []string{} 584 } 585 586 // Reset the original file and overwrite it atomically. 587 if err := utils.AtomicWriteFile(path, output.Bytes(), 0644); err != nil { 588 return errors.Annotatef(err, "cannot write new config %q for container %q", path, name) 589 } 590 return nil 591 } 592 593 // reorderNetworkConfig reads the contents of the given container 594 // config file and the modifies the contents, if needed, so that any 595 // lxc.network.* setting comes after the first lxc.network.type 596 // setting preserving the order. Every line formatting is preserved in 597 // the modified config, including whitespace and comments. The 598 // wasReordered flag will be set if the config was modified. 599 // 600 // This ensures the lxc tools won't report parsing errors for network 601 // settings. See also LP bug #1414016. 602 func reorderNetworkConfig(configFile string) (wasReordered bool, err error) { 603 data, err := ioutil.ReadFile(configFile) 604 if err != nil { 605 return false, errors.Annotatef(err, "cannot read config %q", configFile) 606 } 607 if len(data) == 0 { 608 // Nothing to do. 609 return false, nil 610 } 611 input := bytes.NewBuffer(data) 612 scanner := bufio.NewScanner(input) 613 var output bytes.Buffer 614 firstNetworkType := "" 615 var networkLines []string 616 mayNeedReordering := true 617 foundFirstType := false 618 doneReordering := false 619 for scanner.Scan() { 620 line := scanner.Text() 621 prefix, _ := parseConfigLine(line) 622 if mayNeedReordering { 623 if strings.HasPrefix(prefix, "lxc.network.type") { 624 if len(networkLines) == 0 { 625 // All good, no need to change. 626 logger.Tracef("correct network settings order in config %q", configFile) 627 return false, nil 628 } 629 // We found the first type. 630 firstNetworkType = line 631 foundFirstType = true 632 logger.Tracef( 633 "moving line(s) %q after line %q in config %q", 634 strings.Join(networkLines, "\n"), firstNetworkType, configFile, 635 ) 636 } else if strings.HasPrefix(prefix, "lxc.network.") { 637 if firstNetworkType != "" { 638 // All good, no need to change. 639 logger.Tracef("correct network settings order in config %q", configFile) 640 return false, nil 641 } 642 networkLines = append(networkLines, line) 643 logger.Tracef("need to move line %q in config %q", line, configFile) 644 continue 645 } 646 } 647 output.WriteString(line + "\n") 648 if foundFirstType && len(networkLines) > 0 { 649 // Now add the skipped networkLines. 650 output.WriteString(strings.Join(networkLines, "\n") + "\n") 651 doneReordering = true 652 mayNeedReordering = false 653 firstNetworkType = "" 654 networkLines = nil 655 } 656 } 657 if err := scanner.Err(); err != nil { 658 return false, errors.Annotatef(err, "cannot read config %q", configFile) 659 } 660 if !doneReordering { 661 if len(networkLines) > 0 { 662 logger.Errorf("invalid lxc network settings in config %q", configFile) 663 return false, errors.Errorf( 664 "cannot have line(s) %q without lxc.network.type in config %q", 665 strings.Join(networkLines, "\n"), configFile, 666 ) 667 } 668 // No networking settings to reorder. 669 return false, nil 670 } 671 // Reset the original file and overwrite it atomically. 672 if err := utils.AtomicWriteFile(configFile, output.Bytes(), 0644); err != nil { 673 return false, errors.Annotatef(err, "cannot write new config %q", configFile) 674 } 675 logger.Tracef("reordered network settings in config %q", configFile) 676 return true, nil 677 } 678 679 func appendToContainerConfig(name, line string) error { 680 configPath := containerConfigFilename(name) 681 file, err := os.OpenFile(configPath, os.O_RDWR|os.O_APPEND, 0644) 682 if err != nil { 683 return err 684 } 685 defer file.Close() 686 _, err = file.WriteString(line) 687 if err != nil { 688 return err 689 } 690 logger.Tracef("appended %q to config %q", line, configPath) 691 return nil 692 } 693 694 func autostartContainer(name string) error { 695 // Now symlink the config file into the restart directory, if it exists. 696 // This is for backwards compatiblity. From Trusty onwards, the auto start 697 // option should be set in the LXC config file, this is done in the networkConfigTemplate 698 // function below. 699 if useRestartDir() { 700 if err := symlink.New( 701 containerConfigFilename(name), 702 restartSymlink(name), 703 ); err != nil { 704 return err 705 } 706 logger.Tracef("auto-restart link created") 707 } else { 708 logger.Tracef("Setting auto start to true in lxc config.") 709 return appendToContainerConfig(name, "lxc.start.auto = 1\n") 710 } 711 return nil 712 } 713 714 func mountHostLogDir(name, logDir string) error { 715 // Make sure that the mount dir has been created. 716 internalDir := internalLogDir(name) 717 // Ensure that the logDir actually exists. 718 if err := os.MkdirAll(logDir, 0777); err != nil { 719 return errors.Trace(err) 720 } 721 logger.Tracef("make the mount dir for the shared logs: %s", internalDir) 722 if err := os.MkdirAll(internalDir, 0755); err != nil { 723 logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err) 724 return err 725 } 726 line := fmt.Sprintf( 727 "lxc.mount.entry = %s var/log/juju none defaults,bind 0 0\n", 728 logDir) 729 return appendToContainerConfig(name, line) 730 } 731 732 func (manager *containerManager) DestroyContainer(id instance.Id) error { 733 start := time.Now() 734 name := string(id) 735 lxcContainer := LxcObjectFactory.New(name) 736 if useRestartDir() { 737 // Remove the autostart link. 738 if err := os.Remove(restartSymlink(name)); err != nil { 739 logger.Errorf("failed to remove restart symlink: %v", err) 740 return err 741 } 742 } 743 if err := lxcContainer.Destroy(); err != nil { 744 logger.Errorf("failed to destroy lxc container: %v", err) 745 return err 746 } 747 748 err := container.RemoveDirectory(name) 749 logger.Tracef("container %q stopped: %v", name, time.Now().Sub(start)) 750 return err 751 } 752 753 func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { 754 containers, err := LxcObjectFactory.List() 755 if err != nil { 756 logger.Errorf("failed getting all instances: %v", err) 757 return 758 } 759 managerPrefix := "" 760 if manager.name != "" { 761 managerPrefix = fmt.Sprintf("%s-", manager.name) 762 } 763 764 for _, container := range containers { 765 // Filter out those not starting with our name. 766 name := container.Name() 767 if !strings.HasPrefix(name, managerPrefix) { 768 continue 769 } 770 if container.IsRunning() { 771 result = append(result, &lxcInstance{container, name}) 772 } 773 } 774 return 775 } 776 777 func (manager *containerManager) IsInitialized() bool { 778 requiredBinaries := []string{ 779 "lxc-ls", 780 } 781 for _, bin := range requiredBinaries { 782 if _, err := exec.LookPath(bin); err != nil { 783 return false 784 } 785 } 786 return true 787 } 788 789 const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju" 790 791 func internalLogDir(containerName string) string { 792 return fmt.Sprintf(internalLogDirTemplate, LxcContainerDir, containerName) 793 } 794 795 func restartSymlink(name string) string { 796 return filepath.Join(LxcRestartDir, name+".conf") 797 } 798 799 func containerConfigFilename(name string) string { 800 return filepath.Join(LxcContainerDir, name, "config") 801 } 802 803 const singleNICTemplate = ` 804 # network config 805 # interface "eth0" 806 lxc.network.type = {{.Type}} 807 lxc.network.link = {{.Link}} 808 lxc.network.flags = up{{if .MTU}} 809 lxc.network.mtu = {{.MTU}}{{end}} 810 811 ` 812 813 const multipleNICsTemplate = ` 814 # network config{{$mtu := .MTU}}{{range $nic := .Interfaces}} 815 {{$nic.Name | printf "# interface %q"}} 816 lxc.network.type = {{$nic.Type}}{{if $nic.VLANTag}} 817 lxc.network.vlan.id = {{$nic.VLANTag}}{{end}} 818 lxc.network.link = {{$nic.Link}}{{if not $nic.NoAutoStart}} 819 lxc.network.flags = up{{end}} 820 lxc.network.name = {{$nic.Name}}{{if $nic.MACAddress}} 821 lxc.network.hwaddr = {{$nic.MACAddress}}{{end}}{{if $nic.IPv4Address}} 822 lxc.network.ipv4 = {{$nic.IPv4Address}}{{end}}{{if $nic.IPv4Gateway}} 823 lxc.network.ipv4.gateway = {{$nic.IPv4Gateway}}{{end}}{{if $mtu}} 824 lxc.network.mtu = {{$mtu}}{{end}} 825 {{end}}{{/* range */}} 826 827 ` 828 829 func networkConfigTemplate(config container.NetworkConfig) string { 830 type nicData struct { 831 Name string 832 NoAutoStart bool 833 Type string 834 Link string 835 VLANTag int 836 MACAddress string 837 IPv4Address string 838 IPv4Gateway string 839 } 840 type configData struct { 841 Type string 842 Link string 843 MTU int 844 Interfaces []nicData 845 } 846 data := configData{ 847 Link: config.Device, 848 } 849 850 primaryNIC, err := discoverHostNIC() 851 if err != nil { 852 logger.Warningf("cannot determine primary NIC MTU, not setting for container: %v", err) 853 } else if primaryNIC.MTU > 0 { 854 logger.Infof("setting MTU to %d for all container network interfaces", primaryNIC.MTU) 855 data.MTU = primaryNIC.MTU 856 } 857 858 switch config.NetworkType { 859 case container.PhysicalNetwork: 860 data.Type = "phys" 861 case container.BridgeNetwork: 862 data.Type = "veth" 863 default: 864 logger.Warningf( 865 "unknown network type %q, using the default %q config", 866 config.NetworkType, container.BridgeNetwork, 867 ) 868 data.Type = "veth" 869 } 870 for _, iface := range config.Interfaces { 871 nic := nicData{ 872 Type: data.Type, 873 Link: config.Device, 874 Name: iface.InterfaceName, 875 NoAutoStart: iface.NoAutoStart, 876 VLANTag: iface.VLANTag, 877 MACAddress: iface.MACAddress, 878 IPv4Address: iface.Address.Value, 879 IPv4Gateway: iface.GatewayAddress.Value, 880 } 881 if iface.VLANTag > 0 { 882 nic.Type = "vlan" 883 } 884 if nic.IPv4Address != "" { 885 // LXC expects IPv4 addresses formatted like a CIDR: 886 // 1.2.3.4/5 (but without masking the least significant 887 // octets). So we need to extract the mask part of the 888 // iface.CIDR and append it. If CIDR is empty or invalid 889 // "/24" is used as a sane default. 890 _, ipNet, err := net.ParseCIDR(iface.CIDR) 891 if err != nil { 892 logger.Warningf( 893 "invalid CIDR %q for interface %q, using /24 as fallback", 894 iface.CIDR, nic.Name, 895 ) 896 nic.IPv4Address += "/24" 897 } else { 898 ones, _ := ipNet.Mask.Size() 899 nic.IPv4Address += fmt.Sprintf("/%d", ones) 900 } 901 } 902 if nic.NoAutoStart && nic.IPv4Gateway != "" { 903 // LXC refuses to add an ipv4 gateway when the NIC is not 904 // brought up. 905 logger.Warningf( 906 "not setting IPv4 gateway %q for non-auto start interface %q", 907 nic.IPv4Gateway, nic.Name, 908 ) 909 nic.IPv4Gateway = "" 910 } 911 912 data.Interfaces = append(data.Interfaces, nic) 913 } 914 templateName := multipleNICsTemplate 915 if len(config.Interfaces) == 0 { 916 logger.Tracef("generating default single NIC network config") 917 templateName = singleNICTemplate 918 } else { 919 logger.Tracef("generating network config with %d NIC(s)", len(config.Interfaces)) 920 } 921 tmpl, err := template.New("config").Parse(templateName) 922 if err != nil { 923 logger.Errorf("cannot parse container config template: %v", err) 924 return "" 925 } 926 927 var buf bytes.Buffer 928 if err := tmpl.Execute(&buf, data); err != nil { 929 logger.Errorf("cannot render container config: %v", err) 930 return "" 931 } 932 return buf.String() 933 } 934 935 // discoverHostNIC detects and returns the primary network interface 936 // on the machine. Out of all interfaces, the first non-loopback 937 // device which is up and has address is considered the primary. 938 var discoverHostNIC = func() (net.Interface, error) { 939 interfaces, err := net.Interfaces() 940 if err != nil { 941 return net.Interface{}, errors.Annotatef(err, "cannot get network interfaces") 942 } 943 logger.Tracef("trying to discover primary network interface") 944 for _, iface := range interfaces { 945 if iface.Flags&net.FlagLoopback != 0 { 946 // Skip the loopback. 947 logger.Tracef("not using loopback interface %q", iface.Name) 948 continue 949 } 950 if iface.Flags&net.FlagUp != 0 { 951 // Possibly the primary, but ensure it has an address as 952 // well. 953 logger.Tracef("verifying interface %q has addresses", iface.Name) 954 addrs, err := iface.Addrs() 955 if err != nil { 956 return net.Interface{}, errors.Annotatef(err, "cannot get %q addresses", iface.Name) 957 } 958 if len(addrs) > 0 { 959 // We found it. 960 logger.Tracef("primary network interface is %q", iface.Name) 961 return iface, nil 962 } 963 } 964 } 965 return net.Interface{}, errors.Errorf("cannot detect the primary network interface") 966 } 967 968 func generateNetworkConfig(config *container.NetworkConfig) string { 969 if config == nil { 970 config = DefaultNetworkConfig() 971 logger.Warningf("network type missing, using the default %q config", config.NetworkType) 972 } 973 return networkConfigTemplate(*config) 974 } 975 976 // useRestartDir is used to determine whether or not to use a symlink to the 977 // container config as the restart mechanism. Older versions of LXC had the 978 // /etc/lxc/auto directory that would indicate that a container shoud auto- 979 // restart when the machine boots by having a symlink to the lxc.conf file. 980 // Newer versions don't do this, but instead have a config value inside the 981 // lxc.conf file. 982 func useRestartDir() bool { 983 if _, err := os.Stat(LxcRestartDir); err != nil { 984 if os.IsNotExist(err) { 985 return false 986 } 987 } 988 return true 989 }