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