...

Source file src/k8s.io/kubernetes/cluster/images/etcd/migrate/data_dir.go

Documentation: k8s.io/kubernetes/cluster/images/etcd/migrate

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"k8s.io/klog/v2"
    27  )
    28  
    29  // DataDirectory provides utilities for initializing and backing up an
    30  // etcd "data-dir" as well as managing a version.txt file to track the
    31  // etcd server version and storage version of the etcd data in the
    32  // directory.
    33  type DataDirectory struct {
    34  	path        string
    35  	versionFile *VersionFile
    36  }
    37  
    38  // OpenOrCreateDataDirectory opens a data directory, creating the directory
    39  // if it doesn't not already exist.
    40  func OpenOrCreateDataDirectory(path string) (*DataDirectory, error) {
    41  	exists, err := exists(path)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	if !exists {
    46  		klog.Infof("data directory '%s' does not exist, creating it", path)
    47  		err := os.MkdirAll(path, 0777)
    48  		if err != nil {
    49  			return nil, fmt.Errorf("failed to create data directory %s: %v", path, err)
    50  		}
    51  	}
    52  	versionFile := &VersionFile{
    53  		path: filepath.Join(path, versionFilename),
    54  	}
    55  	return &DataDirectory{path, versionFile}, nil
    56  }
    57  
    58  // Initialize set the version.txt to the target version if the data
    59  // directory is empty. If the data directory is non-empty, no
    60  // version.txt file will be written since the actual version of etcd
    61  // used to create the data is unknown.
    62  func (d *DataDirectory) Initialize(target *EtcdVersionPair) error {
    63  	isEmpty, err := d.IsEmpty()
    64  	if err != nil {
    65  		return err
    66  	}
    67  	if isEmpty {
    68  		klog.Infof("data directory '%s' is empty, writing target version '%s' to version.txt", d.path, target)
    69  		err = d.versionFile.Write(target)
    70  		if err != nil {
    71  			return fmt.Errorf("failed to write version.txt to '%s': %v", d.path, err)
    72  		}
    73  		return nil
    74  	}
    75  	return nil
    76  }
    77  
    78  // Backup creates a backup copy of data directory.
    79  func (d *DataDirectory) Backup() error {
    80  	backupDir := fmt.Sprintf("%s.bak", d.path)
    81  	err := os.RemoveAll(backupDir)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	err = os.MkdirAll(backupDir, 0777)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	err = copyDirectory(d.path, backupDir)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // IsEmpty returns true if the data directory is entirely empty.
    98  func (d *DataDirectory) IsEmpty() (bool, error) {
    99  	dir, err := os.Open(d.path)
   100  	if err != nil {
   101  		return false, fmt.Errorf("failed to open data directory %s: %v", d.path, err)
   102  	}
   103  	defer dir.Close()
   104  	_, err = dir.Readdirnames(1)
   105  	if err == io.EOF {
   106  		return true, nil
   107  	}
   108  	return false, err
   109  }
   110  
   111  // String returns the data directory path.
   112  func (d *DataDirectory) String() string {
   113  	return d.path
   114  }
   115  
   116  // VersionFile provides utilities for reading and writing version.txt files
   117  // to etcd "data-dir" for tracking the etcd server and storage versions
   118  // of the data in the directory.
   119  type VersionFile struct {
   120  	path string
   121  }
   122  
   123  func (v *VersionFile) nextPath() string {
   124  	return fmt.Sprintf("%s-next", v.path)
   125  }
   126  
   127  // Exists returns true if a version.txt file exists on the file system.
   128  func (v *VersionFile) Exists() (bool, error) {
   129  	return exists(v.path)
   130  }
   131  
   132  // Read parses the version.txt file and returns it's contents.
   133  func (v *VersionFile) Read() (*EtcdVersionPair, error) {
   134  	data, err := os.ReadFile(v.path)
   135  	if err != nil {
   136  		return nil, fmt.Errorf("failed to read version file %s: %v", v.path, err)
   137  	}
   138  	txt := strings.TrimSpace(string(data))
   139  	vp, err := ParseEtcdVersionPair(txt)
   140  	if err != nil {
   141  		return nil, fmt.Errorf("failed to parse etcd '<version>/<storage-version>' string from version.txt file contents '%s': %v", txt, err)
   142  	}
   143  	return vp, nil
   144  }
   145  
   146  // equals returns true iff VersionFile exists and contains given EtcdVersionPair.
   147  func (v *VersionFile) equals(vp *EtcdVersionPair) (bool, error) {
   148  	exists, err := v.Exists()
   149  	if err != nil {
   150  		return false, err
   151  	}
   152  	if !exists {
   153  		return false, nil
   154  	}
   155  	cvp, err := v.Read()
   156  	if err != nil {
   157  		return false, err
   158  	}
   159  	return vp.Equals(cvp), nil
   160  }
   161  
   162  // Write creates or overwrites the contents of the version.txt file with the given EtcdVersionPair.
   163  func (v *VersionFile) Write(vp *EtcdVersionPair) error {
   164  	// We do write only if file content differs from given EtcdVersionPair.
   165  	isUpToDate, err := v.equals(vp)
   166  	if err != nil {
   167  		return fmt.Errorf("failed to to check if version file %s should be changed: %v", v.path, err)
   168  	}
   169  	if isUpToDate {
   170  		return nil
   171  	}
   172  	// We do write + rename instead of just write to protect from version.txt
   173  	// corruption under full disk condition.
   174  	// See https://github.com/kubernetes/kubernetes/issues/98989.
   175  	err = os.WriteFile(v.nextPath(), []byte(vp.String()), 0666)
   176  	if err != nil {
   177  		return fmt.Errorf("failed to write new version file %s: %v", v.nextPath(), err)
   178  	}
   179  	return os.Rename(v.nextPath(), v.path)
   180  }
   181  
   182  func exists(path string) (bool, error) {
   183  	if _, err := os.Stat(path); os.IsNotExist(err) {
   184  		return false, nil
   185  	} else if err != nil {
   186  		return false, err
   187  	}
   188  	return true, nil
   189  }
   190  

View as plain text