data_dir.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. import (
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "os"
  19. "os/exec"
  20. "path/filepath"
  21. "strings"
  22. "k8s.io/klog"
  23. )
  24. // DataDirectory provides utilities for initializing and backing up an
  25. // etcd "data-dir" as well as managing a version.txt file to track the
  26. // etcd server version and storage version of the etcd data in the
  27. // directory.
  28. type DataDirectory struct {
  29. path string
  30. versionFile *VersionFile
  31. }
  32. // OpenOrCreateDataDirectory opens a data directory, creating the directory
  33. // if it doesn't not already exist.
  34. func OpenOrCreateDataDirectory(path string) (*DataDirectory, error) {
  35. exists, err := exists(path)
  36. if err != nil {
  37. return nil, err
  38. }
  39. if !exists {
  40. klog.Infof("data directory '%s' does not exist, creating it", path)
  41. err := os.MkdirAll(path, 0777)
  42. if err != nil {
  43. return nil, fmt.Errorf("failed to create data directory %s: %v", path, err)
  44. }
  45. }
  46. versionFile := &VersionFile{
  47. path: filepath.Join(path, versionFilename),
  48. }
  49. return &DataDirectory{path, versionFile}, nil
  50. }
  51. // Initialize set the version.txt to the target version if the data
  52. // directory is empty. If the data directory is non-empty, no
  53. // version.txt file will be written since the actual version of etcd
  54. // used to create the data is unknown.
  55. func (d *DataDirectory) Initialize(target *EtcdVersionPair) error {
  56. isEmpty, err := d.IsEmpty()
  57. if err != nil {
  58. return err
  59. }
  60. if isEmpty {
  61. klog.Infof("data directory '%s' is empty, writing target version '%s' to version.txt", d.path, target)
  62. err = d.versionFile.Write(target)
  63. if err != nil {
  64. return fmt.Errorf("failed to write version.txt to '%s': %v", d.path, err)
  65. }
  66. return nil
  67. }
  68. return nil
  69. }
  70. // Backup creates a backup copy of data directory.
  71. func (d *DataDirectory) Backup() error {
  72. backupDir := fmt.Sprintf("%s.bak", d.path)
  73. err := os.RemoveAll(backupDir)
  74. if err != nil {
  75. return err
  76. }
  77. err = os.MkdirAll(backupDir, 0777)
  78. if err != nil {
  79. return err
  80. }
  81. err = exec.Command("cp", "-r", d.path, backupDir).Run()
  82. if err != nil {
  83. return err
  84. }
  85. return nil
  86. }
  87. // IsEmpty returns true if the data directory is entirely empty.
  88. func (d *DataDirectory) IsEmpty() (bool, error) {
  89. dir, err := os.Open(d.path)
  90. if err != nil {
  91. return false, fmt.Errorf("failed to open data directory %s: %v", d.path, err)
  92. }
  93. defer dir.Close()
  94. _, err = dir.Readdirnames(1)
  95. if err == io.EOF {
  96. return true, nil
  97. }
  98. return false, err
  99. }
  100. // String returns the data directory path.
  101. func (d *DataDirectory) String() string {
  102. return d.path
  103. }
  104. // VersionFile provides utilities for reading and writing version.txt files
  105. // to etcd "data-dir" for tracking the etcd server and storage verions
  106. // of the data in the directory.
  107. type VersionFile struct {
  108. path string
  109. }
  110. // Exists returns true if a version.txt file exists on the file system.
  111. func (v *VersionFile) Exists() (bool, error) {
  112. return exists(v.path)
  113. }
  114. // Read parses the version.txt file and returns it's contents.
  115. func (v *VersionFile) Read() (*EtcdVersionPair, error) {
  116. data, err := ioutil.ReadFile(v.path)
  117. if err != nil {
  118. return nil, fmt.Errorf("failed to read version file %s: %v", v.path, err)
  119. }
  120. txt := strings.TrimSpace(string(data))
  121. vp, err := ParseEtcdVersionPair(txt)
  122. if err != nil {
  123. return nil, fmt.Errorf("failed to parse etcd '<version>/<storage-version>' string from version.txt file contents '%s': %v", txt, err)
  124. }
  125. return vp, nil
  126. }
  127. // Write creates or overwrites the contents of the version.txt file with the given EtcdVersionPair.
  128. func (v *VersionFile) Write(vp *EtcdVersionPair) error {
  129. data := []byte(fmt.Sprintf("%s/%s", vp.version, vp.storageVersion))
  130. return ioutil.WriteFile(v.path, data, 0666)
  131. }
  132. func exists(path string) (bool, error) {
  133. if _, err := os.Stat(path); os.IsNotExist(err) {
  134. return false, nil
  135. } else if err != nil {
  136. return false, err
  137. }
  138. return true, nil
  139. }