github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/kvm/initialisation.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package kvm 5 6 import ( 7 "os" 8 "runtime" 9 10 "github.com/juju/errors" 11 "github.com/juju/os/v2/series" 12 13 "github.com/juju/juju/container" 14 "github.com/juju/juju/core/paths" 15 "github.com/juju/juju/packaging" 16 "github.com/juju/juju/packaging/dependency" 17 ) 18 19 type containerInitialiser struct{} 20 21 // containerInitialiser implements container.Initialiser. 22 var _ container.Initialiser = (*containerInitialiser)(nil) 23 24 // NewContainerInitialiser returns an instance used to perform the steps 25 // required to allow a host machine to run a KVM container. 26 func NewContainerInitialiser() container.Initialiser { 27 return &containerInitialiser{} 28 } 29 30 // Initialise is specified on the container.Initialiser interface. 31 func (ci *containerInitialiser) Initialise() error { 32 if err := ensureDependencies(); err != nil { 33 return errors.Trace(err) 34 } 35 36 poolInfo, err := poolInfo(run) 37 if err != nil { 38 return errors.Trace(err) 39 } 40 if err := ensurePool(poolInfo, paths.DataDir, run, chownToLibvirt); err != nil { 41 return errors.Trace(err) 42 } 43 return nil 44 } 45 46 func ensureDependencies() error { 47 hostSeries, err := series.HostSeries() 48 if err != nil { 49 return errors.Trace(err) 50 } 51 52 dep := dependency.KVM(runtime.GOARCH) 53 if err = packaging.InstallDependency(dep, hostSeries); err != nil { 54 return errors.Trace(err) 55 } 56 57 return nil 58 } 59 60 type pathfinderFunc func(paths.OS) string 61 62 // ensurePool creates the libvirt storage pool and ensures its is active. 63 // runCmd and chownFunc are here for testing. runCmd so we can check the 64 // right shell out calls are made, and chownFunc because we cannot chown 65 // unless we are root. 66 func ensurePool(poolInfo *libvirtPool, pathfinder pathfinderFunc, runCmd runFunc, chownFunc func(string) error) error { 67 poolDir, err := guestPath(pathfinder) 68 if err != nil { 69 return errors.Trace(err) 70 } 71 72 if poolInfo == nil { 73 poolInfo = &libvirtPool{} 74 } 75 76 if poolInfo.Name == "" { 77 if err = definePool(poolDir, runCmd, chownFunc); err != nil { 78 return errors.Trace(err) 79 } 80 } else { 81 logger.Debugf(`pool %q already created`, poolInfo.Name) 82 } 83 84 if poolInfo.State != "running" { 85 if err = buildPool(runCmd); err != nil { 86 return errors.Trace(err) 87 } 88 89 if err = startPool(runCmd); err != nil { 90 return errors.Trace(err) 91 } 92 } else { 93 logger.Debugf(`pool %q already active`, poolInfo.Name) 94 } 95 96 if poolInfo.Autostart != "yes" { 97 if err = autostartPool(runCmd); err != nil { 98 return errors.Trace(err) 99 } 100 } 101 102 // We have to set ownership of the guest pool directory after running virsh 103 // commands above, because it appears that the libvirt-bin version that 104 // ships with trusty sets the ownership of the pool directory to the user 105 // running the commands -- root in our case. Which causes container 106 // initialization to fail as we couldn't write volumes to the pool. We 107 // write them as libvirt-qemu:kvm so that libvirt -- which runs as that 108 // user -- can read them to boot the domains. 109 if err = chownFunc(poolDir); err != nil { 110 return errors.Trace(err) 111 } 112 113 return nil 114 } 115 116 // definePool creates the required directories and changes ownership of the 117 // guest directory so that libvirt-qemu can read, write, and execute its 118 // guest volumes. 119 func definePool(dir string, runCmd runFunc, _ func(string) error) error { 120 // Permissions gleaned from https://goo.gl/SZIw14 121 // The command itself would change the permissions to match anyhow. 122 err := os.MkdirAll(dir, 0755) 123 if err != nil { 124 return errors.Trace(err) 125 } 126 127 // The dashes are empty positional args for other types of pool storage: 128 // e.g. file, lvm, scsi, disk, NFS. Newer versions support using only named 129 // args (--type, --target) but this is backwards compatible for trusty. 130 output, err := runCmd( 131 "", 132 virsh, 133 "pool-define-as", 134 poolName, 135 "dir", 136 "-", "-", "-", "-", 137 dir) 138 if err != nil { 139 return errors.Trace(err) 140 } 141 logger.Debugf("pool-define-as output %s", output) 142 143 return nil 144 } 145 146 // chownToLibvirt changes ownership of the provided directory to 147 // libvirt-qemu:kvm. 148 func chownToLibvirt(dir string) error { 149 uid, gid, err := getUserUIDGID(libvirtUser) 150 if err != nil { 151 logger.Errorf("failed to get livirt-qemu uid:gid %s", err) 152 return errors.Trace(err) 153 } 154 155 err = os.Chown(dir, uid, gid) 156 if err != nil { 157 logger.Errorf("failed to change ownership of %q to uid:gid %d:%d %s", dir, uid, gid, err) 158 return errors.Trace(err) 159 } 160 logger.Tracef("%q is now owned by %q %d:%d", dir, libvirtUser, uid, gid) 161 return nil 162 } 163 164 // buildPool sets up libvirt internals for the guest pool. 165 func buildPool(runCmd runFunc) error { 166 // This can run without error if the pool isn't active. 167 output, err := runCmd("", virsh, "pool-build", poolName) 168 if err != nil { 169 return errors.Trace(err) 170 } 171 logger.Debugf("pool-build output %s", output) 172 return nil 173 } 174 175 // startPool makes the pool available for use in libvirt. 176 func startPool(runCmd runFunc) error { 177 output, err := runCmd("", virsh, "pool-start", poolName) 178 if err != nil { 179 return errors.Trace(err) 180 } 181 logger.Debugf("pool-start output %s", output) 182 183 return nil 184 } 185 186 // autostartPool sets up the pool to run automatically when libvirt starts. 187 func autostartPool(runCmd runFunc) error { 188 output, err := runCmd("", virsh, "pool-autostart", poolName) 189 if err != nil { 190 return errors.Trace(err) 191 } 192 logger.Debugf("pool-autostart output %s", output) 193 194 return nil 195 }