github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/series"
    12  	"github.com/juju/packaging/manager"
    13  	"github.com/juju/utils/arch"
    14  
    15  	"github.com/juju/juju/container"
    16  	"github.com/juju/juju/juju/paths"
    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  	// Check if we've done this already.
    37  	poolInfo, err := poolInfo(run)
    38  	if err != nil {
    39  		return errors.Trace(err)
    40  	}
    41  	if poolInfo == nil {
    42  		if err := createPool(paths.DataDir, run, chownToLibvirt); err != nil {
    43  			return errors.Trace(err)
    44  		}
    45  		return nil
    46  	}
    47  	logger.Debugf(`pool already initialised "%#v"`, poolInfo)
    48  
    49  	return nil
    50  }
    51  
    52  // getPackageManager is a helper function which returns the
    53  // package manager implementation for the current system.
    54  func getPackageManager() (manager.PackageManager, error) {
    55  	hostSeries, err := series.HostSeries()
    56  	if err != nil {
    57  		return nil, errors.Trace(err)
    58  	}
    59  	return manager.NewPackageManager(hostSeries)
    60  }
    61  
    62  func ensureDependencies() error {
    63  	pacman, err := getPackageManager()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	for _, pack := range getRequiredPackages(runtime.GOARCH) {
    69  		if err := pacman.Install(pack); err != nil {
    70  			return err
    71  		}
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  func getRequiredPackages(a string) []string {
    78  	var requiredPackages = []string{
    79  		// `qemu-kvm` must be installed before `libvirt-bin` on trusty. It appears
    80  		// that upstart doesn't reload libvirtd if installed after, and we see
    81  		// errors related to `qemu-kvm` not being installed.
    82  		"qemu-kvm",
    83  		"qemu-utils",
    84  		"genisoimage",
    85  		"libvirt-bin",
    86  	}
    87  	if a == arch.ARM64 {
    88  		// ARM64 doesn't support legacy BIOS so it requires Extensible Firmware
    89  		// Interface.
    90  		requiredPackages = append([]string{"qemu-efi"}, requiredPackages...)
    91  	}
    92  	return requiredPackages
    93  }
    94  
    95  // createPool creates the libvirt storage pool directory. runCmd and chownFunc
    96  // are here for testing. runCmd so we can check the right shell out calls are
    97  // made, and chownFunc because we cannot chown unless we are root.
    98  func createPool(pathfinder func(string) (string, error), runCmd runFunc, chownFunc func(string) error) error {
    99  	poolDir, err := guestPath(pathfinder)
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  
   104  	if err = definePool(poolDir, runCmd, chownFunc); err != nil {
   105  		return errors.Trace(err)
   106  	}
   107  	if err = buildPool(runCmd); err != nil {
   108  		return errors.Trace(err)
   109  	}
   110  
   111  	if err = startPool(runCmd); err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  	if err = autostartPool(runCmd); err != nil {
   115  		return errors.Trace(err)
   116  	}
   117  
   118  	// We have to set ownership of the guest pool directory after running virsh
   119  	// commands above, because it appears that the libvirt-bin version that
   120  	// ships with trusty sets the ownership of the pool directory to the user
   121  	// running the commands -- root in our case. Which causes container
   122  	// initialization to fail as we couldn't write volumes to the pool. We
   123  	// write them as libvirt-qemu:kvm so that libvirt -- which runs as that
   124  	// user -- can read them to boot the domains.
   125  	if err = chownFunc(poolDir); err != nil {
   126  		return errors.Trace(err)
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  // definePool creates the required directories and changes ownershipt of the
   133  // guest directory so that libvirt-qemu can read, write, and execute its
   134  // guest volumes.
   135  func definePool(dir string, runCmd runFunc, chownFunc func(string) error) error {
   136  	// Permissions gleaned from https://goo.gl/SZIw14
   137  	// The command itself would change the permissions to match anyhow.
   138  	err := os.MkdirAll(dir, 0755)
   139  	if err != nil {
   140  		return errors.Trace(err)
   141  	}
   142  
   143  	// The dashes are empty positional args for other types of pool storage:
   144  	// e.g. file, lvm, scsi, disk, NFS. Newer versions support using only named
   145  	// args (--type, --target) but this is backwards compatible for trusty.
   146  	output, err := runCmd(
   147  		"virsh",
   148  		"pool-define-as",
   149  		poolName,
   150  		"dir",
   151  		"-", "-", "-", "-",
   152  		dir)
   153  	if err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	logger.Debugf("pool-define-as output %s", output)
   157  
   158  	return nil
   159  }
   160  
   161  // chownToLibvirt changes ownership of the provided directory to
   162  // libvirt-qemu:kvm.
   163  func chownToLibvirt(dir string) error {
   164  	uid, gid, err := getUserUIDGID(libvirtUser)
   165  	if err != nil {
   166  		logger.Errorf("failed to get livirt-qemu uid:gid %s", err)
   167  		return errors.Trace(err)
   168  	}
   169  
   170  	err = os.Chown(dir, uid, gid)
   171  	if err != nil {
   172  		logger.Errorf("failed to change ownership of %q to uid:gid %d:%d %s", dir, uid, gid, err)
   173  		return errors.Trace(err)
   174  	}
   175  	logger.Tracef("%q is now owned by %q %d:%d", dir, libvirtUser, uid, gid)
   176  	return nil
   177  }
   178  
   179  // buildPool sets up libvirt internals for the guest pool.
   180  func buildPool(runCmd runFunc) error {
   181  	// This can run without error if the pool isn't active.
   182  	output, err := runCmd("virsh", "pool-build", poolName)
   183  	if err != nil {
   184  		return errors.Trace(err)
   185  	}
   186  	logger.Debugf("pool-build output %s", output)
   187  	return nil
   188  }
   189  
   190  // startPool makes the pool available for use in libvirt.
   191  func startPool(runCmd runFunc) error {
   192  	output, err := runCmd("virsh", "pool-start", poolName)
   193  	if err != nil {
   194  		return errors.Trace(err)
   195  	}
   196  	logger.Debugf("pool-start output %s", output)
   197  
   198  	return nil
   199  }
   200  
   201  // autostartPool sets up the pool to run automatically when libvirt starts.
   202  func autostartPool(runCmd runFunc) error {
   203  	output, err := runCmd("virsh", "pool-autostart", poolName)
   204  	if err != nil {
   205  		return errors.Trace(err)
   206  	}
   207  	logger.Debugf("pool-autostart output %s", output)
   208  
   209  	return nil
   210  }