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 }