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