github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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/version" 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 runtimeGOOS = runtime.GOOS 46 runningInsideLXC = lxcutils.RunningInsideLXC 47 ) 48 49 const ( 50 // DefaultLxcBridge is the package created container bridge 51 DefaultLxcBridge = "lxcbr0" 52 // Btrfs is special as we treat it differently for create and clone. 53 Btrfs = "btrfs" 54 55 // etcNetworkInterfaces here is the path (inside the container's 56 // rootfs) where the network config is stored. 57 etcNetworkInterfaces = "/etc/network/interfaces" 58 ) 59 60 // DefaultNetworkConfig returns a valid NetworkConfig to use the 61 // defaultLxcBridge that is created by the lxc package. 62 func DefaultNetworkConfig() *container.NetworkConfig { 63 return container.BridgeNetworkConfig(DefaultLxcBridge, 0, nil) 64 } 65 66 // FsCommandOutput calls cmd.Output, this is used as an overloading point so 67 // we can test what *would* be run without actually executing another program 68 var FsCommandOutput = (*exec.Cmd).CombinedOutput 69 70 func containerDirFilesystem() (string, error) { 71 cmd := exec.Command("df", "--output=fstype", LxcContainerDir) 72 out, err := FsCommandOutput(cmd) 73 if err != nil { 74 return "", errors.Trace(err) 75 } 76 // The filesystem is the second line. 77 lines := strings.Split(string(out), "\n") 78 if len(lines) < 2 { 79 logger.Errorf("unexpected output: %q", out) 80 return "", errors.Errorf("could not determine filesystem type") 81 } 82 return lines[1], nil 83 } 84 85 // IsLXCSupported returns a boolean value indicating whether or not 86 // we can run LXC containers. 87 func IsLXCSupported() (bool, error) { 88 if runtimeGOOS != "linux" { 89 return false, nil 90 } 91 // We do not support running nested LXC containers. 92 insideLXC, err := runningInsideLXC() 93 if err != nil { 94 return false, errors.Trace(err) 95 } 96 return !insideLXC, nil 97 } 98 99 type containerManager struct { 100 name string 101 logdir string 102 createWithClone bool 103 useAUFS bool 104 backingFilesystem string 105 imageURLGetter container.ImageURLGetter 106 } 107 108 // containerManager implements container.Manager. 109 var _ container.Manager = (*containerManager)(nil) 110 111 // NewContainerManager returns a manager object that can start and 112 // stop lxc containers. The containers that are created are namespaced 113 // by the name parameter inside the given ManagerConfig. 114 func NewContainerManager(conf container.ManagerConfig, imageURLGetter container.ImageURLGetter) (container.Manager, error) { 115 name := conf.PopValue(container.ConfigName) 116 if name == "" { 117 return nil, errors.Errorf("name is required") 118 } 119 logDir := conf.PopValue(container.ConfigLogDir) 120 if logDir == "" { 121 logDir = agent.DefaultLogDir 122 } 123 var useClone bool 124 useCloneVal := conf.PopValue("use-clone") 125 if useCloneVal != "" { 126 // Explicitly ignore the error result from ParseBool. 127 // If it fails to parse, the value is false, and this suits 128 // us fine. 129 useClone, _ = strconv.ParseBool(useCloneVal) 130 } else { 131 // If no lxc-clone value is explicitly set in config, then 132 // see if the Ubuntu series we are running on supports it 133 // and if it does, we will use clone. 134 useClone = preferFastLXC(releaseVersion()) 135 } 136 useAUFS, _ := strconv.ParseBool(conf.PopValue("use-aufs")) 137 backingFS, err := containerDirFilesystem() 138 if err != nil { 139 // Especially in tests, or a bot, the lxc dir may not exist 140 // causing the test to fail. Since we only really care if the 141 // backingFS is 'btrfs' and we treat the rest the same, just 142 // call it 'unknown'. 143 backingFS = "unknown" 144 } 145 logger.Tracef("backing filesystem: %q", backingFS) 146 conf.WarnAboutUnused() 147 return &containerManager{ 148 name: name, 149 logdir: logDir, 150 createWithClone: useClone, 151 useAUFS: useAUFS, 152 backingFilesystem: backingFS, 153 imageURLGetter: imageURLGetter, 154 }, nil 155 } 156 157 // releaseVersion is a function that returns a string representing the 158 // DISTRIB_RELEASE from the /etc/lsb-release file. 159 var releaseVersion = version.ReleaseVersion 160 161 // preferFastLXC returns true if the host is capable of 162 // LXC cloning from a template. 163 func preferFastLXC(release string) bool { 164 if release == "" { 165 return false 166 } 167 value, err := strconv.ParseFloat(release, 64) 168 if err != nil { 169 return false 170 } 171 return value >= 14.04 172 } 173 174 // CreateContainer creates or clones an LXC container. 175 func (manager *containerManager) CreateContainer( 176 instanceConfig *instancecfg.InstanceConfig, 177 series string, 178 networkConfig *container.NetworkConfig, 179 storageConfig *container.StorageConfig, 180 ) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) { 181 // Check our preconditions 182 if manager == nil { 183 panic("manager is nil") 184 } else if series == "" { 185 panic("series not set") 186 } else if networkConfig == nil { 187 panic("networkConfig is nil") 188 } else if storageConfig == nil { 189 panic("storageConfig is nil") 190 } 191 192 // Log how long the start took 193 defer func(start time.Time) { 194 if err == nil { 195 logger.Tracef("container %q started: %v", inst.Id(), time.Now().Sub(start)) 196 } 197 }(time.Now()) 198 199 name := names.NewMachineTag(instanceConfig.MachineId).String() 200 if manager.name != "" { 201 name = fmt.Sprintf("%s-%s", manager.name, name) 202 } 203 204 // Create the cloud-init. 205 directory, err := container.NewDirectory(name) 206 if err != nil { 207 return nil, nil, errors.Annotate(err, "failed to create a directory for the container") 208 } 209 logger.Tracef("write cloud-init") 210 userDataFilename, err := containerinit.WriteUserData(instanceConfig, networkConfig, directory) 211 if err != nil { 212 return nil, nil, errors.Annotate(err, "failed to write user data") 213 } 214 215 var lxcContainer golxc.Container 216 if manager.createWithClone { 217 templateContainer, err := EnsureCloneTemplate( 218 manager.backingFilesystem, 219 series, 220 networkConfig, 221 instanceConfig.AuthorizedKeys, 222 instanceConfig.AptProxySettings, 223 instanceConfig.AptMirror, 224 instanceConfig.EnableOSRefreshUpdate, 225 instanceConfig.EnableOSUpgrade, 226 manager.imageURLGetter, 227 manager.useAUFS, 228 ) 229 if err != nil { 230 return nil, nil, errors.Annotate(err, "failed to retrieve the template to clone") 231 } 232 templateParams := []string{ 233 "--debug", // Debug errors in the cloud image 234 "--userdata", userDataFilename, // Our groovey cloud-init 235 "--hostid", name, // Use the container name as the hostid 236 } 237 var extraCloneArgs []string 238 if manager.backingFilesystem == Btrfs || manager.useAUFS { 239 extraCloneArgs = append(extraCloneArgs, "--snapshot") 240 } 241 if manager.backingFilesystem != Btrfs && manager.useAUFS { 242 extraCloneArgs = append(extraCloneArgs, "--backingstore", "aufs") 243 } 244 245 lock, err := AcquireTemplateLock(templateContainer.Name(), "clone") 246 if err != nil { 247 return nil, nil, errors.Annotate(err, "failed to acquire lock on template") 248 } 249 defer lock.Unlock() 250 251 // Ensure the run-time effective config of the template 252 // container has correctly ordered network settings, otherwise 253 // Clone() below will fail. This is needed in case we haven't 254 // created a new template now but are reusing an existing one. 255 // See LP bug #1414016. 256 configPath := containerConfigFilename(templateContainer.Name()) 257 if _, err := reorderNetworkConfig(configPath); err != nil { 258 return nil, nil, errors.Annotate(err, "failed to reorder network settings") 259 } 260 261 lxcContainer, err = templateContainer.Clone(name, extraCloneArgs, templateParams) 262 if err != nil { 263 return nil, nil, errors.Annotate(err, "lxc container cloning failed") 264 } 265 } else { 266 // Note here that the lxcObjectFacotry only returns a valid container 267 // object, and doesn't actually construct the underlying lxc container on 268 // disk. 269 lxcContainer = LxcObjectFactory.New(name) 270 templateParams := []string{ 271 "--debug", // Debug errors in the cloud image 272 "--userdata", userDataFilename, // Our groovey cloud-init 273 "--hostid", name, // Use the container name as the hostid 274 "-r", series, 275 } 276 var caCert []byte 277 if manager.imageURLGetter != nil { 278 arch := arch.HostArch() 279 imageURL, err := manager.imageURLGetter.ImageURL(instance.LXC, series, arch) 280 if err != nil { 281 return nil, nil, errors.Annotatef(err, "cannot determine cached image URL") 282 } 283 templateParams = append(templateParams, "-T", imageURL) 284 caCert = manager.imageURLGetter.CACert() 285 } 286 err = createContainer( 287 lxcContainer, 288 directory, 289 networkConfig, 290 nil, 291 templateParams, 292 caCert, 293 ) 294 if err != nil { 295 return nil, nil, errors.Trace(err) 296 } 297 } 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 356 // We explicitly don't pass through the config file to the container.Start 357 // method as we have passed it through at container creation time. This 358 // is necessary to get the appropriate rootfs reference without explicitly 359 // setting it ourselves. 360 if err = lxcContainer.Start("", consoleFile); err != nil { 361 logger.Warningf("container failed to start %v", err) 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 logger.Errorf("container failed to start and failed to destroy: %v", derr) 370 return nil, nil, errors.Annotate(err, "container failed to start and failed to destroy: manual cleanup of containers needed") 371 } 372 logger.Warningf("container failed to start and was destroyed - safe to retry") 373 return nil, nil, errors.Wrap(err, instance.NewRetryableCreationError("container failed to start and was destroyed: "+lxcContainer.Name())) 374 } 375 logger.Warningf("container failed to start: %v", err) 376 return nil, nil, errors.Annotate(err, "container failed to start") 377 } 378 379 hardware := &instance.HardwareCharacteristics{ 380 Arch: &version.Current.Arch, 381 } 382 383 return &lxcInstance{lxcContainer, name}, hardware, nil 384 } 385 386 func createContainer( 387 lxcContainer golxc.Container, 388 directory string, 389 networkConfig *container.NetworkConfig, 390 extraCreateArgs, templateParams []string, 391 caCert []byte, 392 ) error { 393 // Generate initial lxc.conf with networking settings. 394 netConfig := generateNetworkConfig(networkConfig) 395 configPath := filepath.Join(directory, "lxc.conf") 396 if err := ioutil.WriteFile(configPath, []byte(netConfig), 0644); err != nil { 397 return errors.Annotatef(err, "failed to write container config %q", configPath) 398 } 399 logger.Tracef("wrote initial config %q for container %q", configPath, lxcContainer.Name()) 400 401 var err error 402 var execEnv []string = nil 403 var closer func() 404 if caCert != nil { 405 execEnv, closer, err = wgetEnvironment(caCert) 406 if err != nil { 407 return errors.Annotatef(err, "failed to get environment for wget execution") 408 } 409 defer closer() 410 } 411 412 // Create the container. 413 logger.Debugf("creating lxc container %q", lxcContainer.Name()) 414 logger.Debugf("lxc-create template params: %v", templateParams) 415 if err := lxcContainer.Create(configPath, defaultTemplate, extraCreateArgs, templateParams, execEnv); err != nil { 416 return errors.Annotatef(err, "lxc container creation failed") 417 } 418 return nil 419 } 420 421 // wgetEnvironment creates a script to call wget with the 422 // --no-check-certificate argument, patching the PATH to ensure 423 // the script is invoked by the lxc template bash script. 424 // It returns a slice of env variables to pass to the lxc create command. 425 func wgetEnvironment(caCert []byte) (execEnv []string, closer func(), _ error) { 426 env := os.Environ() 427 kv, err := keyvalues.Parse(env, true) 428 if err != nil { 429 return nil, nil, errors.Trace(err) 430 } 431 // Create a wget bash script in a temporary directory. 432 tmpDir, err := ioutil.TempDir("", "wget") 433 if err != nil { 434 return nil, nil, errors.Trace(err) 435 } 436 closer = func() { 437 os.RemoveAll(tmpDir) 438 } 439 // Write the ca cert. 440 caCertPath := filepath.Join(tmpDir, "ca-cert.pem") 441 err = ioutil.WriteFile(caCertPath, caCert, 0755) 442 if err != nil { 443 defer closer() 444 return nil, nil, errors.Trace(err) 445 } 446 447 // Write the wget script. 448 wgetTmpl := `#!/bin/bash 449 /usr/bin/wget --ca-certificate=%s $* 450 ` 451 wget := fmt.Sprintf(wgetTmpl, caCertPath) 452 err = ioutil.WriteFile(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 if err := lxcContainer.Destroy(); err != nil { 768 logger.Errorf("failed to destroy lxc container: %v", err) 769 return err 770 } 771 772 err := container.RemoveDirectory(name) 773 logger.Tracef("container %q stopped: %v", name, time.Now().Sub(start)) 774 return err 775 } 776 777 func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { 778 containers, err := LxcObjectFactory.List() 779 if err != nil { 780 logger.Errorf("failed getting all instances: %v", err) 781 return 782 } 783 managerPrefix := "" 784 if manager.name != "" { 785 managerPrefix = fmt.Sprintf("%s-", manager.name) 786 } 787 788 for _, container := range containers { 789 // Filter out those not starting with our name. 790 name := container.Name() 791 if !strings.HasPrefix(name, managerPrefix) { 792 continue 793 } 794 if container.IsRunning() { 795 result = append(result, &lxcInstance{container, name}) 796 } 797 } 798 return 799 } 800 801 func (manager *containerManager) IsInitialized() bool { 802 requiredBinaries := []string{ 803 "lxc-ls", 804 } 805 for _, bin := range requiredBinaries { 806 if _, err := exec.LookPath(bin); err != nil { 807 return false 808 } 809 } 810 return true 811 } 812 813 const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju" 814 815 func internalLogDir(containerName string) string { 816 return fmt.Sprintf(internalLogDirTemplate, LxcContainerDir, containerName) 817 } 818 819 func restartSymlink(name string) string { 820 return filepath.Join(LxcRestartDir, name+".conf") 821 } 822 823 func containerConfigFilename(name string) string { 824 return filepath.Join(LxcContainerDir, name, "config") 825 } 826 827 const singleNICTemplate = ` 828 # network config 829 # interface "eth0" 830 lxc.network.type = {{.Type}} 831 lxc.network.link = {{.Link}} 832 lxc.network.flags = up{{if .MTU}} 833 lxc.network.mtu = {{.MTU}}{{end}} 834 835 ` 836 837 const multipleNICsTemplate = ` 838 # network config{{$mtu := .MTU}}{{range $nic := .Interfaces}} 839 {{$nic.Name | printf "# interface %q"}} 840 lxc.network.type = {{$nic.Type}}{{if $nic.VLANTag}} 841 lxc.network.vlan.id = {{$nic.VLANTag}}{{end}} 842 lxc.network.link = {{$nic.Link}}{{if not $nic.NoAutoStart}} 843 lxc.network.flags = up{{end}} 844 lxc.network.name = {{$nic.Name}}{{if $nic.MACAddress}} 845 lxc.network.hwaddr = {{$nic.MACAddress}}{{end}}{{if $nic.IPv4Address}} 846 lxc.network.ipv4 = {{$nic.IPv4Address}}{{end}}{{if $nic.IPv4Gateway}} 847 lxc.network.ipv4.gateway = {{$nic.IPv4Gateway}}{{end}}{{if $mtu}} 848 lxc.network.mtu = {{$mtu}}{{end}} 849 {{end}}{{/* range */}} 850 851 ` 852 853 func networkConfigTemplate(config container.NetworkConfig) string { 854 type nicData struct { 855 Name string 856 NoAutoStart bool 857 Type string 858 Link string 859 VLANTag int 860 MACAddress string 861 IPv4Address string 862 IPv4Gateway string 863 } 864 type configData struct { 865 Type string 866 Link string 867 MTU int 868 Interfaces []nicData 869 } 870 data := configData{ 871 Link: config.Device, 872 MTU: config.MTU, 873 } 874 if config.MTU > 0 { 875 logger.Infof("setting MTU to %v for all LXC network interfaces", config.MTU) 876 } 877 878 switch config.NetworkType { 879 case container.PhysicalNetwork: 880 data.Type = "phys" 881 case container.BridgeNetwork: 882 data.Type = "veth" 883 default: 884 logger.Warningf( 885 "unknown network type %q, using the default %q config", 886 config.NetworkType, container.BridgeNetwork, 887 ) 888 data.Type = "veth" 889 } 890 for _, iface := range config.Interfaces { 891 nic := nicData{ 892 Type: data.Type, 893 Link: config.Device, 894 Name: iface.InterfaceName, 895 NoAutoStart: iface.NoAutoStart, 896 VLANTag: iface.VLANTag, 897 MACAddress: iface.MACAddress, 898 IPv4Address: iface.Address.Value, 899 IPv4Gateway: iface.GatewayAddress.Value, 900 } 901 if iface.VLANTag > 0 { 902 nic.Type = "vlan" 903 } 904 if nic.IPv4Address != "" { 905 // LXC expects IPv4 addresses formatted like a CIDR: 906 // 1.2.3.4/5 (but without masking the least significant 907 // octets). Because statically configured IP addresses use 908 // the netmask 255.255.255.255, we always use /32 for 909 // here. 910 nic.IPv4Address += "/32" 911 } 912 if nic.NoAutoStart && nic.IPv4Gateway != "" { 913 // LXC refuses to add an ipv4 gateway when the NIC is not 914 // brought up. 915 logger.Warningf( 916 "not setting IPv4 gateway %q for non-auto start interface %q", 917 nic.IPv4Gateway, nic.Name, 918 ) 919 nic.IPv4Gateway = "" 920 } 921 922 data.Interfaces = append(data.Interfaces, nic) 923 } 924 templateName := multipleNICsTemplate 925 if len(config.Interfaces) == 0 { 926 logger.Tracef("generating default single NIC network config") 927 templateName = singleNICTemplate 928 } else { 929 logger.Tracef("generating network config with %d NIC(s)", len(config.Interfaces)) 930 } 931 tmpl, err := template.New("config").Parse(templateName) 932 if err != nil { 933 logger.Errorf("cannot parse container config template: %v", err) 934 return "" 935 } 936 937 var buf bytes.Buffer 938 if err := tmpl.Execute(&buf, data); err != nil { 939 logger.Errorf("cannot render container config: %v", err) 940 return "" 941 } 942 return buf.String() 943 } 944 945 func generateNetworkConfig(config *container.NetworkConfig) string { 946 if config == nil { 947 config = DefaultNetworkConfig() 948 logger.Warningf("network type missing, using the default %q config", config.NetworkType) 949 } 950 return networkConfigTemplate(*config) 951 } 952 953 // useRestartDir is used to determine whether or not to use a symlink to the 954 // container config as the restart mechanism. Older versions of LXC had the 955 // /etc/lxc/auto directory that would indicate that a container shoud auto- 956 // restart when the machine boots by having a symlink to the lxc.conf file. 957 // Newer versions don't do this, but instead have a config value inside the 958 // lxc.conf file. 959 func useRestartDir() bool { 960 if _, err := os.Stat(LxcRestartDir); err != nil { 961 if os.IsNotExist(err) { 962 return false 963 } 964 } 965 return true 966 }