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 }