-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
disk.go
203 lines (177 loc) · 4.88 KB
/
disk.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package vhostmd
import (
"encoding/binary"
"encoding/xml"
"fmt"
"io"
"os"
"strings"
"kubevirt.io/kubevirt/pkg/downwardmetrics/vhostmd/api"
)
const fileSize = 262144
const maxBodyLength = fileSize - 24
var signature = [4]byte{'m', 'v', 'b', 'd'}
type vhostmd struct {
filePath string
}
type Header struct {
Signature [4]byte
Flag int32
Checksum int32
Length int32
}
type Disk struct {
Header *Header
Raw []byte
}
func (d *Disk) String() string {
return fmt.Sprintf("%v:%v:%v:%v", string(d.Header.Signature[:]), d.Header.Flag, d.Header.Checksum, d.Header.Length)
}
func (d *Disk) Verify() error {
var checksum int32
for _, b := range d.Raw {
checksum = checksum + int32(b)
}
if d.Header.Flag > 0 {
return fmt.Errorf("file is locked")
}
if checksum != d.Header.Checksum {
return fmt.Errorf("checksum is %v, but expected %v", checksum, d.Header.Checksum)
}
return nil
}
func (d *Disk) Metrics() (*api.Metrics, error) {
m := &api.Metrics{}
if err := xml.Unmarshal(d.Raw, m); err != nil {
return nil, err
}
m.Text = strings.TrimSpace(m.Text)
for i, metric := range m.Metrics {
m.Metrics[i].Name = strings.TrimSpace(metric.Name)
m.Metrics[i].Type = api.MetricType(strings.TrimSpace(string(metric.Type)))
m.Metrics[i].Context = api.MetricContext(strings.TrimSpace(string(metric.Context)))
m.Metrics[i].Value = strings.TrimSpace(metric.Value)
m.Metrics[i].Text = strings.TrimSpace(metric.Text)
}
return m, nil
}
func (v *vhostmd) Create() error {
return createDisk(v.filePath)
}
func (v *vhostmd) Read() (*api.Metrics, error) {
disk, err := readDisk(v.filePath)
if err != nil {
return nil, fmt.Errorf("failed to load vhostmd file: %v", err)
}
if err := disk.Verify(); err != nil {
return nil, fmt.Errorf("failed to verify vhostmd file: %v", err)
}
return disk.Metrics()
}
func (v *vhostmd) Write(metrics *api.Metrics) (err error) {
f, err := os.OpenFile(v.filePath, os.O_RDWR, 0)
if err != nil {
return fmt.Errorf("failed to open vhostmd disk: %v", err)
}
defer func() {
if fileErr := f.Close(); fileErr != nil && err == nil {
err = fileErr
}
}()
if err := writeDisk(f, metrics); err != nil {
return fmt.Errorf("failed to write metrics: %v", err)
}
return nil
}
func readDisk(filePath string) (*Disk, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
d := &Disk{
Header: &Header{},
}
if err = binary.Read(f, binary.BigEndian, d.Header); err != nil {
return nil, err
}
if d.Header.Flag == 0 {
if d.Header.Length > maxBodyLength {
return nil, fmt.Errorf("Invalid metrics file. Expected a maximum body length of %v, got %v", maxBodyLength, d.Header.Length)
}
d.Raw = make([]byte, d.Header.Length, d.Header.Length)
if _, err = io.ReadFull(f, d.Raw); err != nil {
return nil, err
}
}
return d, err
}
func createDisk(filePath string) (err error) {
var f *os.File
if f, err = os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, 0755); err != nil {
return fmt.Errorf("failed getting vhostmd disk filestats: %v", err)
}
defer func() {
if fileErr := f.Close(); fileErr != nil && err == nil {
err = fileErr
}
}()
_, err = f.Seek(fileSize-1, 0)
if err != nil {
return fmt.Errorf("preallocating vhostmd disk failed: %v", err)
}
_, err = f.Write([]byte{0})
if err != nil {
return fmt.Errorf("preallocating vhostmd disk failed: %v", err)
}
_, err = f.Seek(0, 0)
if err != nil {
return fmt.Errorf("moving back to file start failed: %v", err)
}
return writeDisk(f, &api.Metrics{})
}
func writeDisk(file *os.File, m *api.Metrics) (err error) {
d := emptyLockedDisk()
if d.Raw, err = xml.MarshalIndent(m, "", " "); err != nil {
return fmt.Errorf("failed to encode metrics: %v", err)
}
// Add newline, since `vm-dump-metrics` does not append a newline when writing to metrics
d.Raw = append(d.Raw, '\n')
if len(d.Raw) > maxBodyLength {
return fmt.Errorf("vhostmd metrics body is too big, expected a maximum of %v, got %v", maxBodyLength, len(d.Raw))
}
var checksum int32
for _, b := range d.Raw {
checksum = checksum + int32(b)
}
d.Header.Checksum = checksum
d.Header.Length = int32(len(d.Raw))
if err = binary.Write(file, binary.BigEndian, d.Header); err != nil {
return fmt.Errorf("failed to write vhostmd header: %v", err)
}
if err = file.Sync(); err != nil {
return fmt.Errorf("failed to flush to vhostmd file, when trying to lock it: %v", err)
}
if _, err = file.Write(d.Raw); err != nil {
return fmt.Errorf("failed to write vhostmd body: %v", err)
}
_, err = file.Seek(0, 0)
if err != nil {
return fmt.Errorf("moving back to file start failed: %v", err)
}
d.Header.Flag = 0
if err = binary.Write(file, binary.BigEndian, d.Header); err != nil {
return fmt.Errorf("failed to unlock vhostmd file: %v", err)
}
return nil
}
func emptyLockedDisk() *Disk {
return &Disk{
Header: &Header{
Signature: signature,
Flag: 1,
Checksum: 0,
Length: 0,
},
}
}