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