github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/nodeagent/dep/kernel/dep.go (about)

     1  package kernel
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/fs"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/caos/orbos/mntr"
    12  
    13  	"github.com/caos/orbos/internal/operator/common"
    14  	"github.com/caos/orbos/internal/operator/nodeagent"
    15  	"github.com/caos/orbos/internal/operator/nodeagent/dep"
    16  	"github.com/caos/orbos/internal/operator/nodeagent/dep/middleware"
    17  )
    18  
    19  var _ Installer = (*kernelDep)(nil)
    20  
    21  type Installer interface {
    22  	isKernel()
    23  	nodeagent.Installer
    24  }
    25  
    26  type kernelDep struct {
    27  	ctx     context.Context
    28  	monitor mntr.Monitor
    29  	manager *dep.PackageManager
    30  }
    31  
    32  /*
    33  New returns the kernel dependency
    34  
    35  Node Agent uninstalls all kernels that don't have a corresponding initramfs file except for the currently
    36  loaded kernel. If the currently loaded kernel doesn't have a corresponding initramfs file, Node Agent panics.
    37  
    38  If ORBITER desires a specific kernel version, Node Agent installs and locks it, checks the initramfs file and reboots.
    39  It is in the ORBITERS responsibility to ensure not all nodes are updated and rebooted simultaneously.
    40  */
    41  func New(ctx context.Context, monitor mntr.Monitor, manager *dep.PackageManager) *kernelDep {
    42  	return &kernelDep{
    43  		ctx:     ctx,
    44  		monitor: monitor,
    45  		manager: manager,
    46  	}
    47  }
    48  
    49  func (kernelDep) isKernel() {}
    50  
    51  func (kernelDep) Is(other nodeagent.Installer) bool {
    52  	_, ok := middleware.Unwrap(other).(Installer)
    53  	return ok
    54  }
    55  
    56  func (kernelDep) String() string { return "Kernel" }
    57  
    58  func (*kernelDep) Equals(other nodeagent.Installer) bool {
    59  	_, ok := other.(*kernelDep)
    60  	return ok
    61  }
    62  
    63  func (*kernelDep) InstalledFilter() []string {
    64  	return []string{"kernel"}
    65  }
    66  
    67  func (k *kernelDep) Current() (pkg common.Package, err error) {
    68  	loaded, corrupted, err := k.kernelVersions()
    69  	if err != nil {
    70  		return pkg, err
    71  	}
    72  
    73  	dashIdx := strings.Index(loaded, "-")
    74  	pkg.Version = loaded[0:dashIdx]
    75  	pkg.Config = map[string]string{
    76  		dep.CentOS7.String(): loaded[dashIdx+1:],
    77  	}
    78  
    79  	if len(corrupted) > 0 {
    80  		pkg.Config = map[string]string{"corrupted": strings.Join(corrupted, ",")}
    81  	}
    82  
    83  	return pkg, nil
    84  }
    85  
    86  func (k *kernelDep) Ensure(remove common.Package, ensure common.Package, _ bool) error {
    87  
    88  	corruptedKernels := make([]*dep.Software, 0)
    89  	corruptedKernelsStr, ok := remove.Config["corrupted"]
    90  	if ok && corruptedKernelsStr != "" {
    91  		corruptedKernelsStrs := strings.Split(corruptedKernelsStr, ",")
    92  		for i := range corruptedKernelsStrs {
    93  			corruptedKernels = append(corruptedKernels, &dep.Software{
    94  				Package: "kernel",
    95  				Version: corruptedKernelsStrs[i],
    96  			})
    97  		}
    98  	}
    99  
   100  	if err := k.manager.Remove(corruptedKernels...); err != nil {
   101  		return err
   102  	}
   103  
   104  	removeVersion := fmt.Sprintf("%s-%s", remove.Version, remove.Config[dep.CentOS7.String()])
   105  	ensureVersion := fmt.Sprintf("%s-%s", ensure.Version, ensure.Config[dep.CentOS7.String()])
   106  
   107  	if removeVersion == ensureVersion || ensure.Version == "" {
   108  		return nil
   109  	}
   110  
   111  	if err := k.manager.Install(&dep.Software{
   112  		Package: "kernel",
   113  		Version: ensureVersion,
   114  	}); err != nil {
   115  		return err
   116  	}
   117  
   118  	initramfsVersions, err := listInitramfsVersions()
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	var found bool
   124  	for i := range initramfsVersions {
   125  		if initramfsVersions[i] == ensureVersion {
   126  			found = true
   127  			break
   128  		}
   129  	}
   130  	if !found {
   131  		return fmt.Errorf("couldn't find a corresponding initramfs file corresponding kernel version %s. Not rebooting", ensure.Version)
   132  	}
   133  
   134  	k.monitor.Info("Stopping node-agentd in the background")
   135  	if err := exec.Command("bash", "-c", "systemctl stop node-agentd &").Run(); err != nil {
   136  		return fmt.Errorf("stopping node-agentd failed: %w", err)
   137  	}
   138  
   139  	k.monitor.Info("Rebooting system in the background")
   140  	if err := exec.Command("bash", "-c", "reboot &").Run(); err != nil {
   141  		return fmt.Errorf("rebooting the system failed: %w", err)
   142  	}
   143  
   144  	k.monitor.Info("Awaiting SIGTERM")
   145  	<-k.ctx.Done()
   146  	return nil
   147  }
   148  
   149  func (k *kernelDep) kernelVersions() (loadedKernel string, corruptedKernels []string, err error) {
   150  
   151  	loadedKernelBytes, err := exec.Command("uname", "-r").CombinedOutput()
   152  	if err != nil {
   153  		return loadedKernel, corruptedKernels, fmt.Errorf("getting loaded kernel failed: %s: %w", loadedKernel, err)
   154  	}
   155  
   156  	loadedKernel = trimArchitecture(string(loadedKernelBytes))
   157  
   158  	initramfsVersions, err := listInitramfsVersions()
   159  	if err != nil {
   160  		return loadedKernel, corruptedKernels, err
   161  	}
   162  
   163  	corruptedKernels = make([]string, 0)
   164  kernels:
   165  	for _, installedKernel := range k.manager.CurrentVersions("kernel") {
   166  		for i := range initramfsVersions {
   167  			if initramfsVersions[i] == installedKernel.Version {
   168  				continue kernels
   169  			}
   170  		}
   171  		if installedKernel.Version == loadedKernel {
   172  			panic("The actively loaded kernel has no corresponding initramfs file. Pleases fix it manually so the machine survives the next reboot")
   173  		}
   174  		corruptedKernels = append(corruptedKernels, installedKernel.Version)
   175  	}
   176  
   177  	return loadedKernel, corruptedKernels, nil
   178  }
   179  
   180  func listInitramfsVersions() ([]string, error) {
   181  	initramfsdir := "/boot/"
   182  
   183  	var initramfsKernels []string
   184  	if err := filepath.WalkDir(initramfsdir, func(path string, d fs.DirEntry, err error) error {
   185  		if err != nil {
   186  			return err
   187  		}
   188  		if path != initramfsdir && d.IsDir() {
   189  			return filepath.SkipDir
   190  		}
   191  		if strings.HasPrefix(path, initramfsdir+"initramfs-") && strings.HasSuffix(path, ".img") {
   192  			version := trimArchitecture(path[len(initramfsdir+"initramfs-"):strings.LastIndex(path, ".img")])
   193  			initramfsKernels = append(initramfsKernels, version)
   194  		}
   195  		return nil
   196  	}); err != nil {
   197  		return nil, err
   198  	}
   199  	return initramfsKernels, nil
   200  }
   201  
   202  func trimArchitecture(kernel string) string {
   203  	return strings.TrimSuffix(strings.TrimSuffix(kernel, "\n"), ".x86_64")
   204  }