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