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