forked from canonical/lxd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
devices.go
186 lines (147 loc) · 4.83 KB
/
devices.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
package config
import (
"fmt"
"sort"
)
// Device represents a LXD container device.
type Device map[string]string
// Clone returns a copy of the Device.
func (device Device) Clone() Device {
copy := map[string]string{}
for k, v := range device {
copy[k] = v
}
return copy
}
// Validate accepts a map of field/validation functions to run against the device's config.
func (device Device) Validate(rules map[string]func(value string) error) error {
checkedFields := map[string]struct{}{}
for k, validator := range rules {
checkedFields[k] = struct{}{} //Mark field as checked.
err := validator(device[k])
if err != nil {
return fmt.Errorf("Invalid value for device option %q: %v", k, err)
}
}
// Look for any unchecked fields, as these are unknown fields and validation should fail.
for k := range device {
_, checked := checkedFields[k]
if checked {
continue
}
// Skip type fields are these are validated by the presence of an implementation.
if k == "type" {
continue
}
if k == "nictype" && (device["type"] == "nic" || device["type"] == "infiniband") {
continue
}
if k == "gputype" && device["type"] == "gpu" {
continue
}
return fmt.Errorf("Invalid device option %q", k)
}
return nil
}
// Devices represents a set of LXD container devices.
type Devices map[string]Device
// NewDevices creates a new Devices set from a native map[string]map[string]string set.
func NewDevices(nativeSet map[string]map[string]string) Devices {
newDevices := Devices{}
for devName, devConfig := range nativeSet {
newDev := Device{}
for k, v := range devConfig {
newDev[k] = v
}
newDevices[devName] = newDev
}
return newDevices
}
// Contains checks if a given device exists in the set and if it's identical to that provided.
func (list Devices) Contains(k string, d Device) bool {
// If it didn't exist, it's different
if list[k] == nil {
return false
}
old := list[k]
return deviceEquals(old, d)
}
// Update returns the difference between two device sets (removed, added, updated devices) and a list of all
// changed keys across all devices. Accepts a function to return which keys can be live updated, which prevents
// them being removed and re-added if the device supports live updates of certain keys.
func (list Devices) Update(newlist Devices, updateFields func(Device, Device) []string) (map[string]Device, map[string]Device, map[string]Device, []string) {
rmlist := map[string]Device{}
addlist := map[string]Device{}
updatelist := map[string]Device{}
// Detect which devices have changed or been removed in in new list.
for key, d := range list {
if !newlist.Contains(key, d) {
rmlist[key] = d
}
}
// Detect which devices have changed or been added in in new list.
for key, d := range newlist {
if !list.Contains(key, d) {
addlist[key] = d
}
}
allChangedKeys := []string{}
for key, d := range addlist {
srcOldDevice := rmlist[key]
oldDevice := srcOldDevice.Clone()
srcNewDevice := newlist[key]
newDevice := srcNewDevice.Clone()
// Detect keys different between old and new device and append to the all changed keys list.
allChangedKeys = append(allChangedKeys, deviceEqualsDiffKeys(oldDevice, newDevice)...)
// Remove any fields that can be live-updated without adding/removing the device from instance.
if updateFields != nil {
for _, k := range updateFields(oldDevice, newDevice) {
delete(oldDevice, k)
delete(newDevice, k)
}
}
// If after removing the live-updatable keys the devices are equal, then we know the device has
// been updated rather than added or removed, so add it to the update list, and remove it from
// the added and removed lists.
if deviceEquals(oldDevice, newDevice) {
delete(rmlist, key)
delete(addlist, key)
updatelist[key] = d
}
}
return rmlist, addlist, updatelist, allChangedKeys
}
// Clone returns a copy of the Devices set.
func (list Devices) Clone() Devices {
copy := Devices{}
for deviceName, device := range list {
copy[deviceName] = device.Clone()
}
return copy
}
// CloneNative returns a copy of the Devices set as a native map[string]map[string]string type.
func (list Devices) CloneNative() map[string]map[string]string {
copy := map[string]map[string]string{}
for deviceName, device := range list {
copy[deviceName] = device.Clone()
}
return copy
}
// Sorted returns the name of all devices in the set, sorted properly.
func (list Devices) Sorted() DevicesSortable {
sortable := DevicesSortable{}
for k, d := range list {
sortable = append(sortable, DeviceNamed{k, d})
}
sort.Sort(sortable)
return sortable
}
// Reversed returns the name of all devices in the set, sorted reversed.
func (list Devices) Reversed() DevicesSortable {
sortable := DevicesSortable{}
for k, d := range list {
sortable = append(sortable, DeviceNamed{k, d})
}
sort.Sort(sort.Reverse(sortable))
return sortable
}