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  }