/
storage.go
247 lines (203 loc) · 7.81 KB
/
storage.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package storage
import (
"context"
"fmt"
"os"
"sort"
"github.com/canonical/lxd/lxd/db"
"github.com/canonical/lxd/lxd/db/cluster"
"github.com/canonical/lxd/lxd/instance/instancetype"
"github.com/canonical/lxd/lxd/project"
"github.com/canonical/lxd/lxd/state"
"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/api"
"github.com/canonical/lxd/shared/version"
)
// InstancePath returns the directory of an instance or snapshot.
func InstancePath(instanceType instancetype.Type, projectName, instanceName string, isSnapshot bool) string {
fullName := project.Instance(projectName, instanceName)
if instanceType == instancetype.VM {
if isSnapshot {
return shared.VarPath("virtual-machines-snapshots", fullName)
}
return shared.VarPath("virtual-machines", fullName)
}
if isSnapshot {
return shared.VarPath("snapshots", fullName)
}
return shared.VarPath("containers", fullName)
}
// InstanceImportingFilePath returns the file path used to indicate an instance import is in progress.
// This marker file is created when using `lxd import` to import an instance that exists on the storage device
// but does not exist in the LXD database. The presence of this file causes the instance not to be removed from
// the storage device if the import should fail for some reason.
func InstanceImportingFilePath(instanceType instancetype.Type, poolName, projectName, instanceName string) string {
fullName := project.Instance(projectName, instanceName)
typeDir := "containers"
if instanceType == instancetype.VM {
typeDir = "virtual-machines"
}
return shared.VarPath("storage-pools", poolName, typeDir, fullName, ".importing")
}
// GetStoragePoolMountPoint returns the mountpoint of the given pool.
// {LXD_DIR}/storage-pools/<pool>
// Deprecated, use GetPoolMountPath in storage/drivers package.
func GetStoragePoolMountPoint(poolName string) string {
return shared.VarPath("storage-pools", poolName)
}
// GetSnapshotMountPoint returns the mountpoint of the given container snapshot.
// ${LXD_DIR}/storage-pools/<pool>/containers-snapshots/<snapshot_name>.
func GetSnapshotMountPoint(projectName, poolName string, snapshotName string) string {
return shared.VarPath("storage-pools", poolName, "containers-snapshots", project.Instance(projectName, snapshotName))
}
// GetImageMountPoint returns the mountpoint of the given image.
// ${LXD_DIR}/storage-pools/<pool>/images/<fingerprint>.
func GetImageMountPoint(poolName string, fingerprint string) string {
return shared.VarPath("storage-pools", poolName, "images", fingerprint)
}
// GetStoragePoolVolumeSnapshotMountPoint returns the mountpoint of the given pool volume snapshot.
// ${LXD_DIR}/storage-pools/<pool>/custom-snapshots/<custom volume name>/<snapshot name>.
func GetStoragePoolVolumeSnapshotMountPoint(poolName string, snapshotName string) string {
return shared.VarPath("storage-pools", poolName, "custom-snapshots", snapshotName)
}
// CreateContainerMountpoint creates the provided container mountpoint and symlink.
func CreateContainerMountpoint(mountPoint string, mountPointSymlink string, privileged bool) error {
mntPointSymlinkExist := shared.PathExists(mountPointSymlink)
mntPointSymlinkTargetExist := shared.PathExists(mountPoint)
var err error
if !mntPointSymlinkTargetExist {
err = os.MkdirAll(mountPoint, 0711)
if err != nil {
return err
}
}
err = os.Chmod(mountPoint, 0100)
if err != nil {
return err
}
if !mntPointSymlinkExist {
err := os.Symlink(mountPoint, mountPointSymlink)
if err != nil {
return err
}
}
return nil
}
// CreateSnapshotMountpoint creates the provided container snapshot mountpoint
// and symlink.
func CreateSnapshotMountpoint(snapshotMountpoint string, snapshotsSymlinkTarget string, snapshotsSymlink string) error {
snapshotMntPointExists := shared.PathExists(snapshotMountpoint)
mntPointSymlinkExist := shared.PathExists(snapshotsSymlink)
if !snapshotMntPointExists {
err := os.MkdirAll(snapshotMountpoint, 0711)
if err != nil {
return err
}
}
if !mntPointSymlinkExist {
err := os.Symlink(snapshotsSymlinkTarget, snapshotsSymlink)
if err != nil {
return err
}
}
return nil
}
// UsedBy returns list of API resources using storage pool. Accepts firstOnly argument to indicate that only the
// first resource using network should be returned. This can help to quickly check if the storage pool is in use.
// If memberSpecific is true, then the search is restricted to volumes that belong to this member or belong to
// all members. The ignoreVolumeType argument can be used to exclude certain volume type(s) from the list.
func UsedBy(ctx context.Context, s *state.State, pool Pool, firstOnly bool, memberSpecific bool, ignoreVolumeType ...string) ([]string, error) {
var err error
var usedBy []string
err = s.DB.Cluster.Transaction(ctx, func(ctx context.Context, tx *db.ClusterTx) error {
// Get all the volumes using the storage pool.
poolID := pool.ID() // Create local variable to get the pointer.
volumes, err := tx.GetStorageVolumes(ctx, memberSpecific, db.StorageVolumeFilter{PoolID: &poolID})
if err != nil {
return fmt.Errorf("Failed loading storage volumes: %w", err)
}
for _, vol := range volumes {
var u *api.URL
if shared.ValueInSlice(vol.Type, ignoreVolumeType) {
continue
}
// Generate URL for volume based on types that map to other entities.
if vol.Type == cluster.StoragePoolVolumeTypeNameContainer || vol.Type == cluster.StoragePoolVolumeTypeNameVM {
volName, snapName, isSnap := api.GetParentAndSnapshotName(vol.Name)
if isSnap {
u = api.NewURL().Path(version.APIVersion, "instances", volName, "snapshots", snapName).Project(vol.Project)
} else {
u = api.NewURL().Path(version.APIVersion, "instances", volName).Project(vol.Project)
}
usedBy = append(usedBy, u.String())
} else if vol.Type == cluster.StoragePoolVolumeTypeNameImage {
imgProjectNames, err := tx.GetProjectsUsingImage(ctx, vol.Name)
if err != nil {
return fmt.Errorf("Failed loading projects using image %q: %w", vol.Name, err)
}
if len(imgProjectNames) > 0 {
for _, imgProjectName := range imgProjectNames {
u = api.NewURL().Path(version.APIVersion, "images", vol.Name).Project(imgProjectName).Target(vol.Location)
usedBy = append(usedBy, u.String())
}
} else {
// Handle orphaned image volumes that are not associated to an image.
u = vol.URL(version.APIVersion)
usedBy = append(usedBy, u.String())
}
} else {
u = vol.URL(version.APIVersion)
usedBy = append(usedBy, u.String())
}
if firstOnly {
return nil
}
}
// Get all buckets using the storage pool.
filters := []db.StorageBucketFilter{{
PoolID: &poolID,
}}
buckets, err := tx.GetStoragePoolBuckets(ctx, memberSpecific, filters...)
if err != nil {
return fmt.Errorf("Failed loading storage buckets: %w", err)
}
for _, bucket := range buckets {
u := bucket.URL(version.APIVersion, pool.Name(), bucket.Project)
usedBy = append(usedBy, u.String())
if firstOnly {
return nil
}
}
// Get all the profiles using the storage pool.
profiles, err := cluster.GetProfiles(ctx, tx.Tx())
if err != nil {
return fmt.Errorf("Failed loading profiles: %w", err)
}
for _, profile := range profiles {
profileDevices, err := cluster.GetProfileDevices(ctx, tx.Tx(), profile.ID)
if err != nil {
return fmt.Errorf("Failed loading profile devices: %w", err)
}
for _, device := range profileDevices {
if device.Type != cluster.TypeDisk {
continue
}
if device.Config["pool"] != pool.Name() {
continue
}
u := api.NewURL().Path(version.APIVersion, "profiles", profile.Name).Project(profile.Project)
usedBy = append(usedBy, u.String())
if firstOnly {
return nil
}
break
}
}
return err
})
if err != nil {
return nil, err
}
sort.Strings(usedBy)
return usedBy, nil
}