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