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