github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/container/lxd/initialisation.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build go1.3
     5  
     6  package lxd
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/juju/errors"
    18  
    19  	"github.com/juju/utils/packaging/config"
    20  	"github.com/juju/utils/packaging/manager"
    21  
    22  	"github.com/juju/juju/container"
    23  )
    24  
    25  const lxdBridgeFile = "/etc/default/lxd-bridge"
    26  
    27  var requiredPackages = []string{
    28  	"lxd",
    29  }
    30  
    31  var xenialPackages = []string{
    32  	"zfsutils-linux",
    33  }
    34  
    35  type containerInitialiser struct {
    36  	series string
    37  }
    38  
    39  // containerInitialiser implements container.Initialiser.
    40  var _ container.Initialiser = (*containerInitialiser)(nil)
    41  
    42  // NewContainerInitialiser returns an instance used to perform the steps
    43  // required to allow a host machine to run a LXC container.
    44  func NewContainerInitialiser(series string) container.Initialiser {
    45  	return &containerInitialiser{series}
    46  }
    47  
    48  // Initialise is specified on the container.Initialiser interface.
    49  func (ci *containerInitialiser) Initialise() error {
    50  	err := ensureDependencies(ci.series)
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	err = configureLXDBridge()
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	if ci.series >= "xenial" {
    61  		configureZFS()
    62  	}
    63  
    64  	return nil
    65  }
    66  
    67  // getPackageManager is a helper function which returns the
    68  // package manager implementation for the current system.
    69  func getPackageManager(series string) (manager.PackageManager, error) {
    70  	return manager.NewPackageManager(series)
    71  }
    72  
    73  // getPackagingConfigurer is a helper function which returns the
    74  // packaging configuration manager for the current system.
    75  func getPackagingConfigurer(series string) (config.PackagingConfigurer, error) {
    76  	return config.NewPackagingConfigurer(series)
    77  }
    78  
    79  var configureZFS = func() {
    80  	/* create a 100 GB pool by default (sparse, so it won't actually fill
    81  	 * that immediately)
    82  	 */
    83  	output, err := exec.Command(
    84  		"lxd",
    85  		"init",
    86  		"--auto",
    87  		"--storage-backend", "zfs",
    88  		"--storage-pool", "lxd",
    89  		"--storage-create-loop", "100",
    90  	).CombinedOutput()
    91  
    92  	if err != nil {
    93  		logger.Warningf("configuring zfs failed with %s: %s", err, string(output))
    94  	}
    95  }
    96  
    97  var configureLXDBridge = func() error {
    98  	f, err := os.OpenFile(lxdBridgeFile, os.O_RDWR, 0777)
    99  	if err != nil {
   100  		/* We're using an old version of LXD which doesn't have
   101  		 * lxd-bridge; let's not fail here.
   102  		 */
   103  		if os.IsNotExist(err) {
   104  			logger.Warningf("couldn't find %s, not configuring it", lxdBridgeFile)
   105  			return nil
   106  		}
   107  		return errors.Trace(err)
   108  	}
   109  	defer f.Close()
   110  
   111  	output, err := exec.Command("ip", "addr", "show").CombinedOutput()
   112  	if err != nil {
   113  		return errors.Trace(err)
   114  	}
   115  
   116  	subnet, err := detectSubnet(string(output))
   117  	if err != nil {
   118  		return errors.Trace(err)
   119  	}
   120  
   121  	existing, err := ioutil.ReadAll(f)
   122  	if err != nil {
   123  		return errors.Trace(err)
   124  	}
   125  
   126  	result := editLXDBridgeFile(string(existing), subnet)
   127  	_, err = f.Seek(0, 0)
   128  	if err != nil {
   129  		return errors.Trace(err)
   130  	}
   131  
   132  	_, err = f.WriteString(result)
   133  	if err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  
   137  	/* non-systemd systems don't have the lxd-bridge service, so this always fails */
   138  	_ = exec.Command("service", "lxd-bridge", "restart").Run()
   139  	return exec.Command("service", "lxd", "restart").Run()
   140  }
   141  
   142  func detectSubnet(ipAddrOutput string) (string, error) {
   143  	max := 0
   144  
   145  	for _, line := range strings.Split(ipAddrOutput, "\n") {
   146  		trimmed := strings.TrimSpace(line)
   147  		if trimmed == "" {
   148  			continue
   149  		}
   150  
   151  		columns := strings.Split(trimmed, " ")
   152  
   153  		if len(columns) < 2 {
   154  			return "", errors.Trace(fmt.Errorf("invalid ip addr output line %s", line))
   155  		}
   156  
   157  		if columns[0] != "inet" {
   158  			continue
   159  		}
   160  
   161  		addr := columns[1]
   162  		if !strings.HasPrefix(addr, "10.0.") {
   163  			continue
   164  		}
   165  
   166  		tuples := strings.Split(addr, ".")
   167  		if len(tuples) < 4 {
   168  			return "", errors.Trace(fmt.Errorf("invalid ip addr %s", addr))
   169  		}
   170  
   171  		subnet, err := strconv.Atoi(tuples[2])
   172  		if err != nil {
   173  			return "", errors.Trace(err)
   174  		}
   175  
   176  		if subnet > max {
   177  			max = subnet
   178  		}
   179  	}
   180  
   181  	return fmt.Sprintf("%d", max+1), nil
   182  }
   183  
   184  func editLXDBridgeFile(input string, subnet string) string {
   185  	buffer := bytes.Buffer{}
   186  
   187  	newValues := map[string]string{
   188  		"USE_LXD_BRIDGE":      "true",
   189  		"EXISTING_BRIDGE":     "",
   190  		"LXD_BRIDGE":          "lxdbr0",
   191  		"LXD_IPV4_ADDR":       fmt.Sprintf("10.0.%s.1", subnet),
   192  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   193  		"LXD_IPV4_NETWORK":    fmt.Sprintf("10.0.%s.1/24", subnet),
   194  		"LXD_IPV4_DHCP_RANGE": fmt.Sprintf("10.0.%s.2,10.0.%s.254", subnet, subnet),
   195  		"LXD_IPV4_DHCP_MAX":   "253",
   196  		"LXD_IPV4_NAT":        "true",
   197  		"LXD_IPV6_PROXY":      "false",
   198  	}
   199  	found := map[string]bool{}
   200  
   201  	for _, line := range strings.Split(input, "\n") {
   202  		out := line
   203  
   204  		for prefix, value := range newValues {
   205  			if strings.HasPrefix(line, prefix+"=") {
   206  				out = fmt.Sprintf(`%s="%s"`, prefix, value)
   207  				found[prefix] = true
   208  				break
   209  			}
   210  		}
   211  
   212  		buffer.WriteString(out)
   213  		buffer.WriteString("\n")
   214  	}
   215  
   216  	for prefix, value := range newValues {
   217  		if !found[prefix] {
   218  			buffer.WriteString(prefix)
   219  			buffer.WriteString("=")
   220  			buffer.WriteString(value)
   221  			buffer.WriteString("\n")
   222  			found[prefix] = true // not necessary but keeps "found" logically consistent
   223  		}
   224  	}
   225  
   226  	return buffer.String()
   227  }
   228  
   229  // ensureDependencies creates a set of install packages using
   230  // apt.GetPreparePackages and runs each set of packages through
   231  // apt.GetInstall.
   232  func ensureDependencies(series string) error {
   233  	if series == "precise" {
   234  		return fmt.Errorf("LXD is not supported in precise.")
   235  	}
   236  
   237  	pacman, err := getPackageManager(series)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	pacconfer, err := getPackagingConfigurer(series)
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	for _, pack := range requiredPackages {
   247  		pkg := pack
   248  		if config.SeriesRequiresCloudArchiveTools(series) &&
   249  			pacconfer.IsCloudArchivePackage(pack) {
   250  			pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pack), " ")
   251  		}
   252  
   253  		if config.RequiresBackports(series, pack) {
   254  			pkg = fmt.Sprintf("--target-release %s-backports %s", series, pkg)
   255  		}
   256  
   257  		if err := pacman.Install(pkg); err != nil {
   258  			return err
   259  		}
   260  	}
   261  
   262  	if series >= "xenial" {
   263  		for _, pack := range xenialPackages {
   264  			pacman.Install(fmt.Sprintf("--no-install-recommends %s", pack))
   265  		}
   266  	}
   267  
   268  	return err
   269  }