github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"math/rand"
     8  	"os/exec"
     9  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/packaging/v3/manager"
    15  	"github.com/juju/proxy"
    16  
    17  	"github.com/juju/juju/container"
    18  	corebase "github.com/juju/juju/core/base"
    19  	coreos "github.com/juju/juju/core/os"
    20  	"github.com/juju/juju/packaging"
    21  	"github.com/juju/juju/packaging/dependency"
    22  	"github.com/juju/juju/service"
    23  )
    24  
    25  var hostBase = coreos.HostBase
    26  
    27  type containerInitialiser struct {
    28  	containerNetworkingMethod string
    29  	getExecCommand            func(string, ...string) *exec.Cmd
    30  	configureLxdProxies       func(_ proxy.Settings, isRunningLocally func() (bool, error), newLocalServer func() (*Server, error)) error
    31  	isRunningLocally          func() (bool, error)
    32  	newLocalServer            func() (*Server, error)
    33  	lxdSnapChannel            string
    34  }
    35  
    36  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/snap_manager_mock.go github.com/juju/juju/container/lxd SnapManager
    37  
    38  // SnapManager defines an interface implemented by types that can query and/or
    39  // change the channel for installed snaps.
    40  type SnapManager interface {
    41  	InstalledChannel(string) string
    42  	ChangeChannel(string, string) error
    43  }
    44  
    45  // getSnapManager returns a snap manager implementation that is used to query
    46  // and/or change the channel for the installed lxd snap. Defined as a function
    47  // so it can be overridden by tests.
    48  var getSnapManager = func() SnapManager {
    49  	return manager.NewSnapPackageManager()
    50  }
    51  
    52  // containerInitialiser implements container.Initialiser.
    53  var _ container.Initialiser = (*containerInitialiser)(nil)
    54  
    55  // NewContainerInitialiser returns an instance used to perform the steps
    56  // required to allow a host machine to run a LXC container.
    57  func NewContainerInitialiser(lxdSnapChannel, containerNetworkingMethod string) container.Initialiser {
    58  	ci := &containerInitialiser{
    59  		containerNetworkingMethod: containerNetworkingMethod,
    60  		getExecCommand:            exec.Command,
    61  		lxdSnapChannel:            lxdSnapChannel,
    62  		isRunningLocally:          isRunningLocally,
    63  		newLocalServer:            NewLocalServer,
    64  	}
    65  	ci.configureLxdProxies = internalConfigureLXDProxies
    66  	return ci
    67  }
    68  
    69  // Initialise is specified on the container.Initialiser interface.
    70  func (ci *containerInitialiser) Initialise() (err error) {
    71  	localBase, err := hostBase()
    72  	if err != nil {
    73  		return errors.Trace(err)
    74  	}
    75  
    76  	if err := ensureDependencies(ci.lxdSnapChannel, localBase); err != nil {
    77  		return errors.Trace(err)
    78  	}
    79  
    80  	// We need to wait for LXD to be configured (via lxd init below)
    81  	// before potentially updating proxy config for a local server.
    82  	defer func() {
    83  		if err == nil {
    84  			proxies := proxy.DetectProxies()
    85  			err = ci.configureLxdProxies(proxies, ci.isRunningLocally, ci.newLocalServer)
    86  		}
    87  	}()
    88  
    89  	var output []byte
    90  	if ci.containerNetworkingMethod == "local" {
    91  		output, err = ci.getExecCommand(
    92  			"lxd",
    93  			"init",
    94  			"--auto",
    95  		).CombinedOutput()
    96  
    97  		if err == nil {
    98  			return nil
    99  		}
   100  
   101  	} else {
   102  
   103  		lxdInitCfg := `config: {}
   104  networks: []
   105  storage_pools:
   106  - config: {}
   107    description: ""
   108    name: default
   109    driver: dir
   110  profiles:
   111  - config: {}
   112    description: ""
   113    devices:
   114      root:
   115        path: /
   116        pool: default
   117        type: disk
   118    name: default
   119  projects: []
   120  cluster: null`
   121  
   122  		cmd := ci.getExecCommand("lxd", "init", "--preseed")
   123  		cmd.Stdin = strings.NewReader(lxdInitCfg)
   124  
   125  		output, err = cmd.CombinedOutput()
   126  
   127  		if err == nil {
   128  			return nil
   129  		}
   130  	}
   131  
   132  	out := string(output)
   133  	if strings.Contains(out, "You have existing containers or images. lxd init requires an empty LXD.") {
   134  		// this error means we've already run lxd init. Just ignore it.
   135  		return nil
   136  	}
   137  
   138  	return errors.Annotate(err, "running lxd init: "+out)
   139  }
   140  
   141  // ConfigureLXDProxies will try to set the lxc config core.proxy_http and
   142  // core.proxy_https configuration values based on the current environment.
   143  // If LXD is not installed, we skip the configuration.
   144  func ConfigureLXDProxies(proxies proxy.Settings) error {
   145  	return internalConfigureLXDProxies(proxies, isRunningLocally, NewLocalServer)
   146  }
   147  
   148  func internalConfigureLXDProxies(
   149  	proxies proxy.Settings,
   150  	isRunningLocally func() (bool, error),
   151  	newLocalServer func() (*Server, error),
   152  ) error {
   153  	running, err := isRunningLocally()
   154  	if err != nil {
   155  		return errors.Trace(err)
   156  	}
   157  
   158  	if !running {
   159  		logger.Debugf("LXD is not running; skipping proxy configuration")
   160  		return nil
   161  	}
   162  
   163  	svr, err := newLocalServer()
   164  	if err != nil {
   165  		return errors.Trace(err)
   166  	}
   167  
   168  	return errors.Trace(svr.UpdateServerConfig(map[string]string{
   169  		"core.proxy_http":         proxies.Http,
   170  		"core.proxy_https":        proxies.Https,
   171  		"core.proxy_ignore_hosts": proxies.NoProxy,
   172  	}))
   173  }
   174  
   175  // df returns the number of free bytes on the file system at the given path
   176  var df = func(path string) (uint64, error) {
   177  	// Note: do not use golang.org/x/sys/unix for this, it is
   178  	// the best solution but will break the build in s390x
   179  	// and introduce cgo dependency lp:1632541
   180  	statfs := syscall.Statfs_t{}
   181  	err := syscall.Statfs(path, &statfs)
   182  	if err != nil {
   183  		return 0, err
   184  	}
   185  	return uint64(statfs.Bsize) * statfs.Bfree, nil
   186  }
   187  
   188  // ensureDependencies install the required dependencies for running LXD.
   189  func ensureDependencies(lxdSnapChannel string, base corebase.Base) error {
   190  	// If the snap is already installed, check whether the operator asked
   191  	// us to use a different channel. If so, switch to it.
   192  	if lxdViaSnap() {
   193  		snapManager := getSnapManager()
   194  		trackedChannel := snapManager.InstalledChannel("lxd")
   195  		// Note that images with pre-installed snaps are normally
   196  		// tracking "latest/stable/ubuntu-$release_number". As our
   197  		// default model config setting is "latest/stable", we perform
   198  		// a starts-with check instead of an equality check to avoid
   199  		// switching channels when we don't actually need to.
   200  		if strings.HasPrefix(trackedChannel, lxdSnapChannel) {
   201  			logger.Infof("LXD snap is already installed (channel: %s); skipping package installation", trackedChannel)
   202  			return nil
   203  		}
   204  
   205  		// We need to switch to a different channel
   206  		logger.Infof("switching LXD snap channel from %s to %s", trackedChannel, lxdSnapChannel)
   207  		if err := snapManager.ChangeChannel("lxd", lxdSnapChannel); err != nil {
   208  			return errors.Trace(err)
   209  		}
   210  		return nil
   211  	}
   212  
   213  	if err := packaging.InstallDependency(dependency.LXD(lxdSnapChannel), base); err != nil {
   214  		return errors.Trace(err)
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // lxdViaSnap interrogates the location of the Snap LXD socket in order
   221  // to determine if LXD is being provided via that method.
   222  var lxdViaSnap = func() bool {
   223  	return IsUnixSocket("/var/snap/lxd/common/lxd/unix.socket")
   224  }
   225  
   226  // randomizedOctetRange is a variable for testing purposes.
   227  var randomizedOctetRange = func() []int {
   228  	rand.Seed(time.Now().UnixNano())
   229  	return rand.Perm(255)
   230  }
   231  
   232  func isRunningLocally() (bool, error) {
   233  	svcName, err := installedServiceName()
   234  	if svcName == "" || err != nil {
   235  		return false, errors.Trace(err)
   236  	}
   237  
   238  	svc, err := service.NewServiceReference(svcName)
   239  	if err != nil {
   240  		return false, errors.Trace(err)
   241  	}
   242  	running, err := svc.Running()
   243  	if err != nil {
   244  		return running, errors.Trace(err)
   245  	}
   246  	return running, nil
   247  }
   248  
   249  // installedServiceName returns the name of the running service for the LXD
   250  // daemon. If LXD is not installed, the return is an empty string.
   251  func installedServiceName() (string, error) {
   252  	names, err := service.ListServices()
   253  	if err != nil {
   254  		return "", errors.Trace(err)
   255  	}
   256  
   257  	// Look for the Snap service.
   258  	svcName := ""
   259  	for _, name := range names {
   260  		if name == "snap.lxd.daemon" {
   261  			return name, nil
   262  		}
   263  	}
   264  	return svcName, nil
   265  }