github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/container/lxd/initialisation_linux.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  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"strings"
    16  	"syscall"
    17  
    18  	"github.com/juju/errors"
    19  	"github.com/juju/utils/packaging/config"
    20  	"github.com/juju/utils/packaging/manager"
    21  	"github.com/juju/utils/proxy"
    22  	"github.com/lxc/lxd/shared"
    23  
    24  	"github.com/juju/juju/container"
    25  	"github.com/juju/juju/tools/lxdclient"
    26  )
    27  
    28  const lxdBridgeFile = "/etc/default/lxd-bridge"
    29  
    30  var requiredPackages = []string{
    31  	"lxd",
    32  }
    33  
    34  var xenialPackages = []string{
    35  	"zfsutils-linux",
    36  }
    37  
    38  type containerInitialiser struct {
    39  	series string
    40  }
    41  
    42  // containerInitialiser implements container.Initialiser.
    43  var _ container.Initialiser = (*containerInitialiser)(nil)
    44  
    45  // NewContainerInitialiser returns an instance used to perform the steps
    46  // required to allow a host machine to run a LXC container.
    47  func NewContainerInitialiser(series string) container.Initialiser {
    48  	return &containerInitialiser{series}
    49  }
    50  
    51  // Initialise is specified on the container.Initialiser interface.
    52  func (ci *containerInitialiser) Initialise() error {
    53  	err := ensureDependencies(ci.series)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	err = configureLXDBridge()
    59  	if err != nil {
    60  		return err
    61  	}
    62  	proxies := proxy.DetectProxies()
    63  	err = ConfigureLXDProxies(proxies)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	// Well... this will need to change soon once we are passed 17.04 as who
    69  	// knows what the series name will be.
    70  	if ci.series >= "xenial" {
    71  		configureZFS()
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  // getPackageManager is a helper function which returns the
    78  // package manager implementation for the current system.
    79  func getPackageManager(series string) (manager.PackageManager, error) {
    80  	return manager.NewPackageManager(series)
    81  }
    82  
    83  // getPackagingConfigurer is a helper function which returns the
    84  // packaging configuration manager for the current system.
    85  func getPackagingConfigurer(series string) (config.PackagingConfigurer, error) {
    86  	return config.NewPackagingConfigurer(series)
    87  }
    88  
    89  // ConfigureLXDProxies will try to set the lxc config core.proxy_http and core.proxy_https
    90  // configuration values based on the current environment.
    91  func ConfigureLXDProxies(proxies proxy.Settings) error {
    92  	setter, err := getLXDConfigSetter()
    93  	if err != nil {
    94  		return errors.Trace(err)
    95  	}
    96  	return errors.Trace(configureLXDProxies(setter, proxies))
    97  }
    98  
    99  var getLXDConfigSetter = getConfigSetterConnect
   100  
   101  func getConfigSetterConnect() (configSetter, error) {
   102  	return ConnectLocal()
   103  }
   104  
   105  type configSetter interface {
   106  	SetConfig(key, value string) error
   107  }
   108  
   109  func configureLXDProxies(setter configSetter, proxies proxy.Settings) error {
   110  	err := setter.SetConfig("core.proxy_http", proxies.Http)
   111  	if err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  	err = setter.SetConfig("core.proxy_https", proxies.Https)
   115  	if err != nil {
   116  		return errors.Trace(err)
   117  	}
   118  	err = setter.SetConfig("core.proxy_ignore_hosts", proxies.NoProxy)
   119  	if err != nil {
   120  		return errors.Trace(err)
   121  	}
   122  	return nil
   123  }
   124  
   125  // df returns the number of free bytes on the file system at the given path
   126  var df = func(path string) (uint64, error) {
   127  	// Note: do not use golang.org/x/sys/unix for this, it is
   128  	// the best solution but will break the build in s390x
   129  	// and introduce cgo dependency lp:1632541
   130  	statfs := syscall.Statfs_t{}
   131  	err := syscall.Statfs(path, &statfs)
   132  	if err != nil {
   133  		return 0, err
   134  	}
   135  	return uint64(statfs.Bsize) * statfs.Bfree, nil
   136  }
   137  
   138  var configureZFS = func() {
   139  	/* create a pool that will occupy 90% of the free disk space
   140  	   (sparse, so it won't actually fill that immediately)
   141  	*/
   142  
   143  	// Find 90% of the free disk space
   144  	freeBytes, err := df("/")
   145  	if err != nil {
   146  		logger.Errorf("configuring zfs failed - unable to find file system size: %s", err)
   147  	}
   148  	GigaBytesToUse := freeBytes * 9 / (10 * 1024 * 1024 * 1024)
   149  
   150  	output, err := exec.Command(
   151  		"lxd",
   152  		"init",
   153  		"--auto",
   154  		"--storage-backend", "zfs",
   155  		"--storage-pool", "lxd",
   156  		"--storage-create-loop", fmt.Sprintf("%d", GigaBytesToUse),
   157  	).CombinedOutput()
   158  
   159  	if err != nil {
   160  		logger.Errorf("configuring zfs failed with %s: %s", err, string(output))
   161  	}
   162  }
   163  
   164  var configureLXDBridge = func() error {
   165  	client, err := ConnectLocal()
   166  	if err != nil {
   167  		return errors.Trace(err)
   168  	}
   169  
   170  	status, err := client.ServerStatus()
   171  	if err != nil {
   172  		return errors.Trace(err)
   173  	}
   174  
   175  	if shared.StringInSlice("network", status.APIExtensions) {
   176  		return lxdclient.CreateDefaultBridgeInDefaultProfile(client)
   177  	}
   178  
   179  	f, err := os.OpenFile(lxdBridgeFile, os.O_RDWR, 0777)
   180  	if err != nil {
   181  		/* We're using an old version of LXD which doesn't have
   182  		 * lxd-bridge; let's not fail here.
   183  		 */
   184  		if os.IsNotExist(err) {
   185  			logger.Debugf("couldn't find %s, not configuring it", lxdBridgeFile)
   186  			return nil
   187  		}
   188  		return errors.Trace(err)
   189  	}
   190  	defer f.Close()
   191  
   192  	existing, err := ioutil.ReadAll(f)
   193  	if err != nil {
   194  		return errors.Trace(err)
   195  	}
   196  
   197  	newBridgeCfg, err := bridgeConfiguration(string(existing))
   198  	if err != nil {
   199  		return errors.Trace(err)
   200  	}
   201  
   202  	if newBridgeCfg == string(existing) {
   203  		return nil
   204  	}
   205  
   206  	_, err = f.Seek(0, 0)
   207  	if err != nil {
   208  		return errors.Trace(err)
   209  	}
   210  	_, err = f.WriteString(newBridgeCfg)
   211  	if err != nil {
   212  		return errors.Trace(err)
   213  	}
   214  
   215  	/* non-systemd systems don't have the lxd-bridge service, so this always fails */
   216  	_ = exec.Command("service", "lxd-bridge", "restart").Run()
   217  	return exec.Command("service", "lxd", "restart").Run()
   218  }
   219  
   220  var interfaceAddrs = func() ([]net.Addr, error) {
   221  	return net.InterfaceAddrs()
   222  }
   223  
   224  func editLXDBridgeFile(input string, subnet string) string {
   225  	buffer := bytes.Buffer{}
   226  
   227  	newValues := map[string]string{
   228  		"USE_LXD_BRIDGE":      "true",
   229  		"EXISTING_BRIDGE":     "",
   230  		"LXD_BRIDGE":          "lxdbr0",
   231  		"LXD_IPV4_ADDR":       fmt.Sprintf("10.0.%s.1", subnet),
   232  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   233  		"LXD_IPV4_NETWORK":    fmt.Sprintf("10.0.%s.1/24", subnet),
   234  		"LXD_IPV4_DHCP_RANGE": fmt.Sprintf("10.0.%s.2,10.0.%s.254", subnet, subnet),
   235  		"LXD_IPV4_DHCP_MAX":   "253",
   236  		"LXD_IPV4_NAT":        "true",
   237  		"LXD_IPV6_PROXY":      "false",
   238  	}
   239  	found := map[string]bool{}
   240  
   241  	for _, line := range strings.Split(input, "\n") {
   242  		out := line
   243  
   244  		for prefix, value := range newValues {
   245  			if strings.HasPrefix(line, prefix+"=") {
   246  				out = fmt.Sprintf(`%s="%s"`, prefix, value)
   247  				found[prefix] = true
   248  				break
   249  			}
   250  		}
   251  
   252  		buffer.WriteString(out)
   253  		buffer.WriteString("\n")
   254  	}
   255  
   256  	for prefix, value := range newValues {
   257  		if !found[prefix] {
   258  			buffer.WriteString(prefix)
   259  			buffer.WriteString("=")
   260  			buffer.WriteString(value)
   261  			buffer.WriteString("\n")
   262  			found[prefix] = true // not necessary but keeps "found" logically consistent
   263  		}
   264  	}
   265  
   266  	return buffer.String()
   267  }
   268  
   269  // ensureDependencies creates a set of install packages using
   270  // apt.GetPreparePackages and runs each set of packages through
   271  // apt.GetInstall.
   272  func ensureDependencies(series string) error {
   273  	if series == "precise" {
   274  		return fmt.Errorf("LXD is not supported in precise.")
   275  	}
   276  
   277  	pacman, err := getPackageManager(series)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	pacconfer, err := getPackagingConfigurer(series)
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	for _, pack := range requiredPackages {
   287  		pkg := pack
   288  		if config.SeriesRequiresCloudArchiveTools(series) &&
   289  			pacconfer.IsCloudArchivePackage(pack) {
   290  			pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pack), " ")
   291  		}
   292  
   293  		if config.RequiresBackports(series, pack) {
   294  			pkg = fmt.Sprintf("--target-release %s-backports %s", series, pkg)
   295  		}
   296  
   297  		if err := pacman.Install(pkg); err != nil {
   298  			return err
   299  		}
   300  	}
   301  
   302  	if series >= "xenial" {
   303  		for _, pack := range xenialPackages {
   304  			pacman.Install(fmt.Sprintf("--no-install-recommends %s", pack))
   305  		}
   306  	}
   307  
   308  	return err
   309  }
   310  
   311  // findNextAvailableIPv4Subnet scans the list of interfaces on the machine
   312  // looking for 10.0.0.0/16 networks and returns the next subnet not in
   313  // use, having first detected the highest subnet. The next subnet can
   314  // actually be lower if we overflowed 255 whilst seeking out the next
   315  // unused subnet. If all subnets are in use an error is returned.
   316  //
   317  // TODO(frobware): this is not an ideal solution as it doesn't take
   318  // into account any static routes that may be set up on the machine.
   319  //
   320  // TODO(frobware): this only caters for IPv4 setups.
   321  func findNextAvailableIPv4Subnet() (string, error) {
   322  	_, ip10network, err := net.ParseCIDR("10.0.0.0/16")
   323  	if err != nil {
   324  		return "", errors.Trace(err)
   325  	}
   326  
   327  	addrs, err := interfaceAddrs()
   328  	if err != nil {
   329  		return "", errors.Annotatef(err, "cannot get network interface addresses")
   330  	}
   331  
   332  	max := 0
   333  	usedSubnets := make(map[int]bool)
   334  
   335  	for _, address := range addrs {
   336  		addr, network, err := net.ParseCIDR(address.String())
   337  		if err != nil {
   338  			logger.Debugf("cannot parse address %q: %v (ignoring)", address.String(), err)
   339  			continue
   340  		}
   341  		if !ip10network.Contains(addr) {
   342  			logger.Debugf("find available subnet, skipping %q", network.String())
   343  			continue
   344  		}
   345  		subnet := int(network.IP[2])
   346  		usedSubnets[subnet] = true
   347  		if subnet > max {
   348  			max = subnet
   349  		}
   350  	}
   351  
   352  	if len(usedSubnets) == 0 {
   353  		return "0", nil
   354  	}
   355  
   356  	for i := 0; i < 256; i++ {
   357  		max = (max + 1) % 256
   358  		if _, inUse := usedSubnets[max]; !inUse {
   359  			return fmt.Sprintf("%d", max), nil
   360  		}
   361  	}
   362  
   363  	return "", errors.New("could not find unused subnet")
   364  }
   365  
   366  func parseLXDBridgeConfigValues(input string) map[string]string {
   367  	values := make(map[string]string)
   368  
   369  	for _, line := range strings.Split(input, "\n") {
   370  		line = strings.TrimSpace(line)
   371  
   372  		if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
   373  			continue
   374  		}
   375  
   376  		tokens := strings.Split(line, "=")
   377  
   378  		if tokens[0] == "" {
   379  			continue // no key
   380  		}
   381  
   382  		value := ""
   383  
   384  		if len(tokens) > 1 {
   385  			value = tokens[1]
   386  			if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
   387  				value = strings.Trim(value, `"`)
   388  			}
   389  		}
   390  
   391  		values[tokens[0]] = value
   392  	}
   393  	return values
   394  }
   395  
   396  // bridgeConfiguration ensures that input has a valid setting for
   397  // LXD_IPV4_ADDR, returning the existing input if is already set, and
   398  // allocating the next available subnet if it is not.
   399  func bridgeConfiguration(input string) (string, error) {
   400  	values := parseLXDBridgeConfigValues(input)
   401  	ipAddr := net.ParseIP(values["LXD_IPV4_ADDR"])
   402  
   403  	if ipAddr == nil || ipAddr.To4() == nil {
   404  		logger.Infof("LXD_IPV4_ADDR is not set; searching for unused subnet")
   405  		subnet, err := findNextAvailableIPv4Subnet()
   406  		if err != nil {
   407  			return "", errors.Trace(err)
   408  		}
   409  		logger.Infof("setting LXD_IPV4_ADDR=10.0.%s.1", subnet)
   410  		return editLXDBridgeFile(input, subnet), nil
   411  	}
   412  	return input, nil
   413  }