internal/system/swap.go (172 lines of code) (raw):

package system import ( "bufio" "bytes" "errors" "fmt" "io/fs" "os" "os/exec" "strings" "go.uber.org/zap" "github.com/aws/eks-hybrid/internal/api" ) const ( swapAspectName = "swap" swapTypePartition = "partition" swapTypeFile = "file" ) type swapAspect struct { nodeConfig *api.NodeConfig logger *zap.Logger } var _ SystemAspect = &swapAspect{} func NewSwapAspect(cfg *api.NodeConfig, logger *zap.Logger) SystemAspect { return &swapAspect{nodeConfig: cfg, logger: logger} } func (s *swapAspect) Name() string { return swapAspectName } func (s *swapAspect) Setup() error { hasSwapPartition, err := partitionSwapExists() if err != nil { return err } if hasSwapPartition { return fmt.Errorf("failed to disable swap: partition type swap found on the host") } if err = s.swapOff(); err != nil { return err } return disableSwapOnFstab() } // Check if there are swaps of type partition exist on host because currently // nodeadm can only disable file type swap, if it's partition type, nodeadm // can only temporarily disable the swap, and swap will come back after host reboot. // If partition type swap exists, user needs to manually remove the partition swap before // running nodeadm init. func partitionSwapExists() (bool, error) { swapfiles, err := getSwapfilePaths() if err != nil { return false, err } for _, swap := range swapfiles { if swap.swapType == swapTypePartition { return true, nil } } return false, nil } func (s *swapAspect) swapOff() error { swapfiles, err := getSwapfilePaths() if err != nil { return err } for _, swap := range swapfiles { path := swap.filePath if swap.swapType == swapTypePartition { return fmt.Errorf("partition type swapfile %s found in /proc/swaps, please remove the swapfile", swap.filePath) } if _, err := os.Stat(path); err == nil { s.logger.Info("Disabling swap...", zap.Reflect("swapfile path", path)) offCmd := exec.Command("swapoff", path) out, err := offCmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to turn of swap on %s, command output: %s, %v", path, out, err) } } else if errors.Is(err, fs.ErrNotExist) { // path to swapfile does not exist s.logger.Warn("swapfile path does not exists", zap.Reflect("swapfile path", path)) } else { // file may or may not exist. See err for details. return fmt.Errorf("unexpced error while trying to open /proc/swaps file: %v", err) } } return nil } // Read swapfile paths from /proc/fstab file and return them as a list of string // If there is no /proc/swaps file, returns a nil slice // /proc/fstab file format will be like: // Filename Type Size Used Priority // <path-to-swap-file> file/partition 524280 0 -1 func getSwapfilePaths() ([]*swap, error) { var paths []*swap file, err := os.OpenFile("/proc/swaps", os.O_RDONLY, 0o444) if err != nil { if os.IsNotExist(err) { return paths, nil } return paths, err } defer file.Close() scanner := bufio.NewScanner(file) lineNo := 0 for scanner.Scan() { lineNo++ if lineNo == 1 { continue } swap, err := parseProcSwapsLine(scanner.Text()) if err != nil { return nil, fmt.Errorf("/proc/swaps file syntax error at line %d: %s", lineNo, err) } paths = append(paths, swap) } return paths, nil } type swap struct { // path of swap file filePath string // type of swap, value is one of partition and file swapType string } // mount represents the filesystem info type mount struct { // The block special device or remote filesystem to be mounted spec string // The mount point for the filesystem file string // The type of the filesystem vfsType string } func disableSwapOnFstab() error { file, err := os.OpenFile("/etc/fstab", os.O_RDWR, 0o644) if err != nil { return err } defer file.Close() var bs []byte buf := bytes.NewBuffer(bs) scanner := bufio.NewScanner(file) lineNo := 0 for scanner.Scan() { lineNo++ fstabMount, err := parseFstabLine(scanner.Text()) if err != nil { return fmt.Errorf("/etc/fstab syntax error at line %d: %s", lineNo, err) } if fstabMount == nil || fstabMount != nil && fstabMount.vfsType != "swap" { buf.WriteString(scanner.Text() + "\n") } } if err := file.Truncate(0); err != nil { return err } if _, err := file.Seek(0, 0); err != nil { return err } _, err = buf.WriteTo(file) return err } func parseFstabLine(line string) (*mount, error) { line = strings.TrimSpace(line) // Lines starting with a pound sign (#) are comments, and are ignored. So are empty lines. if (line == "") || (line[0] == '#') { return nil, nil } fields := strings.Fields(line) fstabMount := &mount{} if len(fields) < 3 { return nil, fmt.Errorf("too few fields (%d), at least 4 are expected", len(fields)) } else { fstabMount.spec = fields[0] fstabMount.file = fields[1] fstabMount.vfsType = fields[2] } return fstabMount, nil } func parseProcSwapsLine(line string) (*swap, error) { line = strings.TrimSpace(line) if (line == "") || (line[0] == '#') { return nil, nil } fields := strings.Fields(line) if len(fields) != 5 { return nil, fmt.Errorf("Error in /proc/swaps file, line (%s) has %d fields, 5 are expected", line, len(fields)) } return &swap{ filePath: fields[0], swapType: fields[1], }, nil }