github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/storage/provider/loop.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider 5 6 import ( 7 "fmt" 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 13 "github.com/juju/errors" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/environs/context" 17 "github.com/juju/juju/storage" 18 ) 19 20 const ( 21 // Loop provider types. 22 LoopProviderType = storage.ProviderType("loop") 23 HostLoopProviderType = storage.ProviderType("hostloop") 24 ) 25 26 // loopProviders create volume sources which use loop devices. 27 type loopProvider struct { 28 // run is a function used for running commands on the local machine. 29 run runCommandFunc 30 } 31 32 var _ storage.Provider = (*loopProvider)(nil) 33 34 // ValidateConfig is defined on the Provider interface. 35 func (*loopProvider) ValidateConfig(*storage.Config) error { 36 // Loop provider has no configuration. 37 return nil 38 } 39 40 // validateFullConfig validates a fully-constructed storage config, 41 // combining the user-specified config and any internally specified 42 // config. 43 func (lp *loopProvider) validateFullConfig(cfg *storage.Config) error { 44 if err := lp.ValidateConfig(cfg); err != nil { 45 return err 46 } 47 storageDir, ok := cfg.ValueString(storage.ConfigStorageDir) 48 if !ok || storageDir == "" { 49 return errors.New("storage directory not specified") 50 } 51 return nil 52 } 53 54 // VolumeSource is defined on the Provider interface. 55 func (lp *loopProvider) VolumeSource(sourceConfig *storage.Config) (storage.VolumeSource, error) { 56 if err := lp.validateFullConfig(sourceConfig); err != nil { 57 return nil, err 58 } 59 // storageDir is validated by validateFullConfig. 60 storageDir, _ := sourceConfig.ValueString(storage.ConfigStorageDir) 61 return &loopVolumeSource{ 62 &osDirFuncs{lp.run}, 63 lp.run, 64 storageDir, 65 }, nil 66 } 67 68 // FilesystemSource is defined on the Provider interface. 69 func (lp *loopProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) { 70 return nil, errors.NotSupportedf("filesystems") 71 } 72 73 // Supports is defined on the Provider interface. 74 func (*loopProvider) Supports(k storage.StorageKind) bool { 75 return k == storage.StorageKindBlock 76 } 77 78 // Scope is defined on the Provider interface. 79 func (*loopProvider) Scope() storage.Scope { 80 return storage.ScopeMachine 81 } 82 83 // Dynamic is defined on the Provider interface. 84 func (*loopProvider) Dynamic() bool { 85 return true 86 } 87 88 // Releasable is defined on the Provider interface. 89 func (*loopProvider) Releasable() bool { 90 return false 91 } 92 93 // DefaultPools is defined on the Provider interface. 94 func (*loopProvider) DefaultPools() []*storage.Config { 95 return nil 96 } 97 98 // loopVolumeSource provides common functionality to handle 99 // loop devices for rootfs and host loop volume sources. 100 type loopVolumeSource struct { 101 dirFuncs dirFuncs 102 run runCommandFunc 103 storageDir string 104 } 105 106 var _ storage.VolumeSource = (*loopVolumeSource)(nil) 107 108 // CreateVolumes is defined on the VolumeSource interface. 109 func (lvs *loopVolumeSource) CreateVolumes(ctx context.ProviderCallContext, args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 110 results := make([]storage.CreateVolumesResult, len(args)) 111 for i, arg := range args { 112 volume, err := lvs.createVolume(arg) 113 if err != nil { 114 results[i].Error = errors.Annotate(err, "creating volume") 115 } 116 results[i].Volume = &volume 117 } 118 return results, nil 119 } 120 121 func (lvs *loopVolumeSource) createVolume(params storage.VolumeParams) (storage.Volume, error) { 122 volumeId := params.Tag.String() 123 loopFilePath := lvs.volumeFilePath(params.Tag) 124 if err := ensureDir(lvs.dirFuncs, filepath.Dir(loopFilePath)); err != nil { 125 return storage.Volume{}, errors.Trace(err) 126 } 127 if err := createBlockFile(lvs.run, loopFilePath, params.Size); err != nil { 128 return storage.Volume{}, errors.Annotate(err, "could not create block file") 129 } 130 return storage.Volume{ 131 params.Tag, 132 storage.VolumeInfo{ 133 VolumeId: volumeId, 134 Size: params.Size, 135 }, 136 }, nil 137 } 138 139 func (lvs *loopVolumeSource) volumeFilePath(tag names.VolumeTag) string { 140 return filepath.Join(lvs.storageDir, tag.String()) 141 } 142 143 // ListVolumes is defined on the VolumeSource interface. 144 func (lvs *loopVolumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) { 145 // TODO(axw) implement this when we need it. 146 return nil, errors.NotImplementedf("ListVolumes") 147 } 148 149 // DescribeVolumes is defined on the VolumeSource interface. 150 func (lvs *loopVolumeSource) DescribeVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]storage.DescribeVolumesResult, error) { 151 // TODO(axw) implement this when we need it. 152 return nil, errors.NotImplementedf("DescribeVolumes") 153 } 154 155 // DestroyVolumes is defined on the VolumeSource interface. 156 func (lvs *loopVolumeSource) DestroyVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { 157 results := make([]error, len(volumeIds)) 158 for i, volumeId := range volumeIds { 159 if err := lvs.destroyVolume(volumeId); err != nil { 160 results[i] = errors.Annotatef(err, "destroying %q", volumeId) 161 } 162 } 163 return results, nil 164 } 165 166 // ReleaseVolumes is defined on the VolumeSource interface. 167 func (lvs *loopVolumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { 168 return make([]error, len(volumeIds)), nil 169 } 170 171 func (lvs *loopVolumeSource) destroyVolume(volumeId string) error { 172 tag, err := names.ParseVolumeTag(volumeId) 173 if err != nil { 174 return errors.Errorf("invalid loop volume ID %q", volumeId) 175 } 176 loopFilePath := lvs.volumeFilePath(tag) 177 err = os.Remove(loopFilePath) 178 if err != nil && !os.IsNotExist(err) { 179 return errors.Annotate(err, "removing loop backing file") 180 } 181 return nil 182 } 183 184 // ValidateVolumeParams is defined on the VolumeSource interface. 185 func (lvs *loopVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 186 // ValdiateVolumeParams may be called on a machine other than the 187 // machine where the loop device will be created, so we cannot check 188 // available size until we get to CreateVolumes. 189 return nil 190 } 191 192 // AttachVolumes is defined on the VolumeSource interface. 193 func (lvs *loopVolumeSource) AttachVolumes(ctx context.ProviderCallContext, args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 194 results := make([]storage.AttachVolumesResult, len(args)) 195 for i, arg := range args { 196 attachment, err := lvs.attachVolume(arg) 197 if err != nil { 198 results[i].Error = errors.Annotatef(err, "attaching volume %v", arg.Volume.Id()) 199 continue 200 } 201 results[i].VolumeAttachment = attachment 202 } 203 return results, nil 204 } 205 206 func (lvs *loopVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (*storage.VolumeAttachment, error) { 207 loopFilePath := lvs.volumeFilePath(arg.Volume) 208 deviceName, err := attachLoopDevice(lvs.run, loopFilePath, arg.ReadOnly) 209 if err != nil { 210 os.Remove(loopFilePath) 211 return nil, errors.Annotate(err, "attaching loop device") 212 } 213 return &storage.VolumeAttachment{ 214 arg.Volume, 215 arg.Machine, 216 storage.VolumeAttachmentInfo{ 217 DeviceName: deviceName, 218 ReadOnly: arg.ReadOnly, 219 }, 220 }, nil 221 } 222 223 // DetachVolumes is defined on the VolumeSource interface. 224 func (lvs *loopVolumeSource) DetachVolumes(ctx context.ProviderCallContext, args []storage.VolumeAttachmentParams) ([]error, error) { 225 results := make([]error, len(args)) 226 for i, arg := range args { 227 if err := lvs.detachVolume(arg.Volume); err != nil { 228 results[i] = errors.Annotatef(err, "detaching volume %s", arg.Volume.Id()) 229 } 230 } 231 return results, nil 232 } 233 234 func (lvs *loopVolumeSource) detachVolume(tag names.VolumeTag) error { 235 loopFilePath := lvs.volumeFilePath(tag) 236 deviceNames, err := associatedLoopDevices(lvs.run, loopFilePath) 237 if err != nil { 238 return errors.Annotate(err, "locating loop device") 239 } 240 if len(deviceNames) > 1 { 241 logger.Errorf("expected 1 loop device, got %d", len(deviceNames)) 242 } 243 for _, deviceName := range deviceNames { 244 if err := detachLoopDevice(lvs.run, deviceName); err != nil { 245 return errors.Trace(err) 246 } 247 } 248 return nil 249 } 250 251 // createBlockFile creates a file at the specified path, with the 252 // given size in mebibytes. 253 func createBlockFile(run runCommandFunc, filePath string, sizeInMiB uint64) error { 254 // fallocate will reserve the space without actually writing to it. 255 _, err := run("fallocate", "-l", fmt.Sprintf("%dMiB", sizeInMiB), filePath) 256 if err != nil { 257 return errors.Annotatef(err, "allocating loop backing file %q", filePath) 258 } 259 return nil 260 } 261 262 // attachLoopDevice attaches a loop device to the file with the 263 // specified path, and returns the loop device's name (e.g. "loop0"). 264 // losetup will create additional loop devices as necessary. 265 func attachLoopDevice(run runCommandFunc, filePath string, readOnly bool) (loopDeviceName string, _ error) { 266 devices, err := associatedLoopDevices(run, filePath) 267 if err != nil { 268 return "", err 269 } 270 if len(devices) > 0 { 271 // Already attached. 272 logger.Debugf("%s already attached to %s", filePath, devices) 273 return devices[0], nil 274 } 275 // -f automatically finds the first available loop-device. 276 // -r sets up a read-only loop-device. 277 // --show returns the loop device chosen on stdout. 278 args := []string{"-f", "--show"} 279 if readOnly { 280 args = append(args, "-r") 281 } 282 args = append(args, filePath) 283 stdout, err := run("losetup", args...) 284 if err != nil { 285 return "", errors.Annotatef(err, "attaching loop device to %q", filePath) 286 } 287 stdout = strings.TrimSpace(stdout) 288 loopDeviceName = stdout[len("/dev/"):] 289 return loopDeviceName, nil 290 } 291 292 // detachLoopDevice detaches the loop device with the specified name. 293 func detachLoopDevice(run runCommandFunc, deviceName string) error { 294 _, err := run("losetup", "-d", path.Join("/dev", deviceName)) 295 if err != nil { 296 return errors.Annotatef(err, "detaching loop device %q", deviceName) 297 } 298 return err 299 } 300 301 // associatedLoopDevices returns the device names of the loop devices 302 // associated with the specified file path. 303 func associatedLoopDevices(run runCommandFunc, filePath string) ([]string, error) { 304 stdout, err := run("losetup", "-j", filePath) 305 if err != nil { 306 return nil, errors.Trace(err) 307 } 308 stdout = strings.TrimSpace(stdout) 309 if stdout == "" { 310 return nil, nil 311 } 312 // The output will be zero or more lines with the format: 313 // "/dev/loop0: [0021]:7504142 (/tmp/test.dat)" 314 lines := strings.Split(stdout, "\n") 315 deviceNames := make([]string, len(lines)) 316 for i, line := range lines { 317 pos := strings.IndexRune(line, ':') 318 if pos == -1 { 319 return nil, errors.Errorf("unexpected output %q", line) 320 } 321 deviceName := line[:pos][len("/dev/"):] 322 deviceNames[i] = deviceName 323 } 324 return deviceNames, nil 325 }