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 }