github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/initialisation_linux.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"math/rand"
    11  	"net"
    12  	"os"
    13  	"os/exec"
    14  	"strings"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/juju/collections/set"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/packaging/config"
    21  	"github.com/juju/packaging/manager"
    22  	"github.com/juju/proxy"
    23  	"github.com/juju/utils/series"
    24  	"github.com/lxc/lxd/shared"
    25  
    26  	"github.com/juju/juju/container"
    27  	"github.com/juju/juju/service"
    28  	"github.com/juju/juju/service/common"
    29  )
    30  
    31  var hostSeries = series.HostSeries
    32  
    33  var requiredPackages = []string{"lxd"}
    34  
    35  type containerInitialiser struct {
    36  	getExecCommand func(string, ...string) *exec.Cmd
    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() container.Initialiser {
    45  	return &containerInitialiser{
    46  		exec.Command,
    47  	}
    48  }
    49  
    50  // Initialise is specified on the container.Initialiser interface.
    51  func (ci *containerInitialiser) Initialise() error {
    52  	localSeries, err := hostSeries()
    53  	if err != nil {
    54  		return errors.Trace(err)
    55  	}
    56  
    57  	if err := ensureDependencies(localSeries); err != nil {
    58  		return errors.Trace(err)
    59  	}
    60  
    61  	err = configureLXDBridge()
    62  	if err != nil {
    63  		return errors.Trace(err)
    64  	}
    65  	proxies := proxy.DetectProxies()
    66  	err = ConfigureLXDProxies(proxies)
    67  	if err != nil {
    68  		return errors.Trace(err)
    69  	}
    70  
    71  	// LXD init is only run on Xenial and later.
    72  	if localSeries == "trusty" {
    73  		return nil
    74  	}
    75  
    76  	output, err := ci.getExecCommand(
    77  		"lxd",
    78  		"init",
    79  		"--auto",
    80  	).CombinedOutput()
    81  
    82  	if err == nil {
    83  		return nil
    84  	}
    85  
    86  	out := string(output)
    87  	if strings.Contains(out, "You have existing containers or images. lxd init requires an empty LXD.") {
    88  		// this error means we've already run lxd init. Just ignore it.
    89  		return nil
    90  	}
    91  
    92  	return errors.Annotate(err, "running lxd init --auto: "+out)
    93  }
    94  
    95  // getPackageManager is a helper function which returns the
    96  // package manager implementation for the current system.
    97  func getPackageManager(series string) (manager.PackageManager, error) {
    98  	return manager.NewPackageManager(series)
    99  }
   100  
   101  // getPackagingConfigurer is a helper function which returns the
   102  // packaging configuration manager for the current system.
   103  func getPackagingConfigurer(series string) (config.PackagingConfigurer, error) {
   104  	return config.NewPackagingConfigurer(series)
   105  }
   106  
   107  // ConfigureLXDProxies will try to set the lxc config core.proxy_http and
   108  // core.proxy_https configuration values based on the current environment.
   109  // If LXD is not installed, we skip the configuration.
   110  func ConfigureLXDProxies(proxies proxy.Settings) error {
   111  	running, err := IsRunningLocally()
   112  	if err != nil {
   113  		return errors.Trace(err)
   114  	}
   115  
   116  	if !running {
   117  		logger.Debugf("LXD is not running; skipping proxy configuration")
   118  		return nil
   119  	}
   120  
   121  	svr, err := NewLocalServer()
   122  	if err != nil {
   123  		return errors.Trace(err)
   124  	}
   125  
   126  	return errors.Trace(svr.UpdateServerConfig(map[string]string{
   127  		"core.proxy_http":         proxies.Http,
   128  		"core.proxy_https":        proxies.Https,
   129  		"core.proxy_ignore_hosts": proxies.NoProxy,
   130  	}))
   131  }
   132  
   133  // df returns the number of free bytes on the file system at the given path
   134  var df = func(path string) (uint64, error) {
   135  	// Note: do not use golang.org/x/sys/unix for this, it is
   136  	// the best solution but will break the build in s390x
   137  	// and introduce cgo dependency lp:1632541
   138  	statfs := syscall.Statfs_t{}
   139  	err := syscall.Statfs(path, &statfs)
   140  	if err != nil {
   141  		return 0, err
   142  	}
   143  	return uint64(statfs.Bsize) * statfs.Bfree, nil
   144  }
   145  
   146  var configureLXDBridge = func() error {
   147  	server, err := NewLocalServer()
   148  	if err != nil {
   149  		return errors.Trace(err)
   150  	}
   151  	// If LXD itself supports managing networks (added in LXD 2.3) we can allow
   152  	// it to do all of the network configuration.
   153  	if server.networkAPISupport {
   154  		profile, eTag, err := server.GetProfile(lxdDefaultProfileName)
   155  		if err != nil {
   156  			return errors.Trace(err)
   157  		}
   158  		return server.ensureDefaultNetworking(profile, eTag)
   159  	}
   160  	return configureLXDBridgeForOlderLXD()
   161  }
   162  
   163  // configureLXDBridgeForOlderLXD is used for LXD agents that don't support the
   164  // Network API (pre 2.3)
   165  func configureLXDBridgeForOlderLXD() error {
   166  	f, err := os.OpenFile(BridgeConfigFile, os.O_RDWR, 0777)
   167  	if err != nil {
   168  		/* We're using an old version of LXD which doesn't have
   169  		 * lxd-bridge; let's not fail here.
   170  		 */
   171  		if os.IsNotExist(err) {
   172  			logger.Debugf("couldn't find %s, not configuring it", BridgeConfigFile)
   173  			return nil
   174  		}
   175  		return errors.Trace(err)
   176  	}
   177  	defer f.Close()
   178  
   179  	existing, err := ioutil.ReadAll(f)
   180  	if err != nil {
   181  		return errors.Trace(err)
   182  	}
   183  
   184  	newBridgeCfg, err := bridgeConfiguration(string(existing))
   185  	if err != nil {
   186  		return errors.Trace(err)
   187  	}
   188  
   189  	if newBridgeCfg == string(existing) {
   190  		return nil
   191  	}
   192  
   193  	_, err = f.Seek(0, 0)
   194  	if err != nil {
   195  		return errors.Trace(err)
   196  	}
   197  	_, err = f.WriteString(newBridgeCfg)
   198  	if err != nil {
   199  		return errors.Trace(err)
   200  	}
   201  
   202  	/* non-systemd systems don't have the lxd-bridge service, so this always fails */
   203  	_ = exec.Command("service", "lxd-bridge", "restart").Run()
   204  	return exec.Command("service", "lxd", "restart").Run()
   205  }
   206  
   207  var interfaceAddrs = func() ([]net.Addr, error) {
   208  	return net.InterfaceAddrs()
   209  }
   210  
   211  func editLXDBridgeFile(input string, subnet string) string {
   212  	buffer := bytes.Buffer{}
   213  
   214  	newValues := map[string]string{
   215  		"USE_LXD_BRIDGE":      "true",
   216  		"EXISTING_BRIDGE":     "",
   217  		"LXD_BRIDGE":          "lxdbr0",
   218  		"LXD_IPV4_ADDR":       fmt.Sprintf("10.0.%s.1", subnet),
   219  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   220  		"LXD_IPV4_NETWORK":    fmt.Sprintf("10.0.%s.1/24", subnet),
   221  		"LXD_IPV4_DHCP_RANGE": fmt.Sprintf("10.0.%s.2,10.0.%s.254", subnet, subnet),
   222  		"LXD_IPV4_DHCP_MAX":   "253",
   223  		"LXD_IPV4_NAT":        "true",
   224  		"LXD_IPV6_PROXY":      "false",
   225  	}
   226  	found := map[string]bool{}
   227  
   228  	for _, line := range strings.Split(input, "\n") {
   229  		out := line
   230  
   231  		for prefix, value := range newValues {
   232  			if strings.HasPrefix(line, prefix+"=") {
   233  				out = fmt.Sprintf(`%s="%s"`, prefix, value)
   234  				found[prefix] = true
   235  				break
   236  			}
   237  		}
   238  
   239  		buffer.WriteString(out)
   240  		buffer.WriteString("\n")
   241  	}
   242  
   243  	for prefix, value := range newValues {
   244  		if !found[prefix] {
   245  			buffer.WriteString(prefix)
   246  			buffer.WriteString("=")
   247  			buffer.WriteString(value)
   248  			buffer.WriteString("\n")
   249  			found[prefix] = true // not necessary but keeps "found" logically consistent
   250  		}
   251  	}
   252  
   253  	return buffer.String()
   254  }
   255  
   256  // ensureDependencies creates a set of install packages using
   257  // apt.GetPreparePackages and runs each set of packages through
   258  // apt.GetInstall.
   259  func ensureDependencies(series string) error {
   260  	if series == "precise" {
   261  		return errors.NotSupportedf(`LXD containers on series "precise"`)
   262  	}
   263  
   264  	if lxdViaSnap() {
   265  		logger.Infof("LXD snap is installed; skipping package installation")
   266  		return nil
   267  	}
   268  
   269  	pacman, err := getPackageManager(series)
   270  	if err != nil {
   271  		return errors.Trace(err)
   272  	}
   273  	pacconfer, err := getPackagingConfigurer(series)
   274  	if err != nil {
   275  		return errors.Trace(err)
   276  	}
   277  
   278  	for _, pack := range requiredPackages {
   279  		pkg := pack
   280  		if config.SeriesRequiresCloudArchiveTools(series) &&
   281  			pacconfer.IsCloudArchivePackage(pack) {
   282  			pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pack), " ")
   283  		}
   284  
   285  		if config.RequiresBackports(series, pack) {
   286  			pkg = fmt.Sprintf("--target-release %s-backports %s", series, pkg)
   287  		}
   288  
   289  		if err := pacman.Install(pkg); err != nil {
   290  			return errors.Trace(err)
   291  		}
   292  	}
   293  
   294  	return errors.Trace(err)
   295  }
   296  
   297  // lxdViaSnap interrogates the location of the Snap LXD socket in order
   298  // to determine if LXD is being provided via that method.
   299  var lxdViaSnap = func() bool {
   300  	return shared.IsUnixSocket("/var/snap/lxd/common/lxd/unix.socket")
   301  }
   302  
   303  // randomizedOctetRange is a variable for testing purposes.
   304  var randomizedOctetRange = func() []int {
   305  	rand.Seed(time.Now().UnixNano())
   306  	return rand.Perm(255)
   307  }
   308  
   309  // getKnownV4IPsAndCIDRs iterates all of the known Addresses on this machine
   310  // and groups them up into known CIDRs and IP addresses.
   311  func getKnownV4IPsAndCIDRs(addrFunc func() ([]net.Addr, error)) ([]net.IP, []*net.IPNet, error) {
   312  	addrs, err := addrFunc()
   313  	if err != nil {
   314  		return nil, nil, errors.Annotate(err, "cannot get network interface addresses")
   315  	}
   316  
   317  	knownIPs := []net.IP{}
   318  	seenIPs := set.NewStrings()
   319  	knownCIDRs := []*net.IPNet{}
   320  	seenCIDRs := set.NewStrings()
   321  	for _, netAddr := range addrs {
   322  		ip, ipNet, err := net.ParseCIDR(netAddr.String())
   323  		if err != nil {
   324  			continue
   325  		}
   326  		if ip.To4() == nil {
   327  			continue
   328  		}
   329  		if !seenIPs.Contains(ip.String()) {
   330  			knownIPs = append(knownIPs, ip)
   331  			seenIPs.Add(ip.String())
   332  		}
   333  		if !seenCIDRs.Contains(ipNet.String()) {
   334  			knownCIDRs = append(knownCIDRs, ipNet)
   335  			seenCIDRs.Add(ipNet.String())
   336  		}
   337  	}
   338  	return knownIPs, knownCIDRs, nil
   339  }
   340  
   341  // findNextAvailableIPv4Subnet scans the list of interfaces on the machine
   342  // looking for 10.0.0.0/16 networks and returns the next subnet not in
   343  // use, having first detected the highest subnet. The next subnet can
   344  // actually be lower if we overflowed 255 whilst seeking out the next
   345  // unused subnet. If all subnets are in use an error is returned.
   346  //
   347  // TODO(frobware): this is not an ideal solution as it doesn't take
   348  // into account any static routes that may be set up on the machine.
   349  //
   350  // TODO(frobware): this only caters for IPv4 setups.
   351  func findNextAvailableIPv4Subnet() (string, error) {
   352  	knownIPs, knownCIDRs, err := getKnownV4IPsAndCIDRs(interfaceAddrs)
   353  	if err != nil {
   354  		return "", errors.Trace(err)
   355  	}
   356  
   357  	randomized3rdSegment := randomizedOctetRange()
   358  	for _, i := range randomized3rdSegment {
   359  		// lxd randomizes the 2nd and 3rd segments, we should be fine with the
   360  		// 3rd only
   361  		ip, ip10network, err := net.ParseCIDR(fmt.Sprintf("10.0.%d.0/24", i))
   362  		if err != nil {
   363  			return "", errors.Trace(err)
   364  		}
   365  
   366  		collides := false
   367  		for _, kIP := range knownIPs {
   368  			if ip10network.Contains(kIP) {
   369  				collides = true
   370  				break
   371  			}
   372  		}
   373  		if !collides {
   374  			for _, kNet := range knownCIDRs {
   375  				if kNet.Contains(ip) || ip10network.Contains(kNet.IP) {
   376  					collides = true
   377  					break
   378  				}
   379  			}
   380  		}
   381  		if !collides {
   382  			return fmt.Sprintf("%d", i), nil
   383  		}
   384  	}
   385  	return "", errors.New("could not find unused subnet")
   386  }
   387  
   388  func parseLXDBridgeConfigValues(input string) map[string]string {
   389  	values := make(map[string]string)
   390  
   391  	for _, line := range strings.Split(input, "\n") {
   392  		line = strings.TrimSpace(line)
   393  		if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
   394  			continue
   395  		}
   396  
   397  		tokens := strings.Split(line, "=")
   398  		if tokens[0] == "" {
   399  			continue // no key
   400  		}
   401  
   402  		value := ""
   403  		if len(tokens) > 1 {
   404  			value = tokens[1]
   405  			if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
   406  				value = strings.Trim(value, `"`)
   407  			}
   408  		}
   409  		values[tokens[0]] = value
   410  	}
   411  	return values
   412  }
   413  
   414  // bridgeConfiguration ensures that input has a valid setting for
   415  // LXD_IPV4_ADDR, returning the existing input if is already set, and
   416  // allocating the next available subnet if it is not.
   417  func bridgeConfiguration(input string) (string, error) {
   418  	values := parseLXDBridgeConfigValues(input)
   419  	ipAddr := net.ParseIP(values["LXD_IPV4_ADDR"])
   420  
   421  	if ipAddr == nil || ipAddr.To4() == nil {
   422  		logger.Infof("LXD_IPV4_ADDR is not set; searching for unused subnet")
   423  		subnet, err := findNextAvailableIPv4Subnet()
   424  		if err != nil {
   425  			return "", errors.Trace(err)
   426  		}
   427  		logger.Infof("setting LXD_IPV4_ADDR=10.0.%s.1", subnet)
   428  		return editLXDBridgeFile(input, subnet), nil
   429  	}
   430  	return input, nil
   431  }
   432  
   433  // IsRunningLocally returns true if LXD is running locally.
   434  var IsRunningLocally = isRunningLocally
   435  
   436  func isRunningLocally() (bool, error) {
   437  	svcName, err := InstalledServiceName()
   438  	if svcName == "" || err != nil {
   439  		return false, errors.Trace(err)
   440  	}
   441  
   442  	hostSeries, err := series.HostSeries()
   443  	if err != nil {
   444  		return false, errors.Trace(err)
   445  	}
   446  
   447  	svc, err := service.NewService(svcName, common.Conf{}, hostSeries)
   448  	if err != nil {
   449  		return false, errors.Trace(err)
   450  	}
   451  	running, err := svc.Running()
   452  	if err != nil {
   453  		return running, errors.Trace(err)
   454  	}
   455  	return running, nil
   456  }
   457  
   458  // InstalledServiceName returns the name of the running service for the LXD
   459  // daemon. If LXD is not installed, the return is an empty string.
   460  func InstalledServiceName() (string, error) {
   461  	names, err := service.ListServices()
   462  	if err != nil {
   463  		return "", errors.Trace(err)
   464  	}
   465  
   466  	// Prefer the Snap service.
   467  	svcName := ""
   468  	for _, name := range names {
   469  		if name == "snap.lxd.daemon" {
   470  			return name, nil
   471  		}
   472  		if name == "lxd" {
   473  			svcName = name
   474  		}
   475  	}
   476  	return svcName, nil
   477  }