1
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
30
31
32
33 type DataDirectory struct {
34 path string
35 versionFile *VersionFile
36 }
37
38
39
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
59
60
61
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
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
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
112 func (d *DataDirectory) String() string {
113 return d.path
114 }
115
116
117
118
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
128 func (v *VersionFile) Exists() (bool, error) {
129 return exists(v.path)
130 }
131
132
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
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
163 func (v *VersionFile) Write(vp *EtcdVersionPair) error {
164
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
173
174
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