github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/storage/provider/managedfs.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 "path" 8 "path/filepath" 9 10 "github.com/juju/errors" 11 "github.com/juju/names" 12 13 "github.com/juju/juju/storage" 14 ) 15 16 const ( 17 // defaultFilesystemType is the default filesystem type 18 // to create for volume-backed managed filesystems. 19 defaultFilesystemType = "ext4" 20 ) 21 22 // managedFilesystemSource is an implementation of storage.FilesystemSource 23 // that manages filesystems on volumes attached to the host machine. 24 // 25 // managedFilesystemSource is expected to be called from a single goroutine. 26 type managedFilesystemSource struct { 27 run runCommandFunc 28 dirFuncs dirFuncs 29 volumeBlockDevices map[names.VolumeTag]storage.BlockDevice 30 filesystems map[names.FilesystemTag]storage.Filesystem 31 } 32 33 // NewManagedFilesystemSource returns a storage.FilesystemSource that manages 34 // filesystems on block devices on the host machine. 35 // 36 // The parameters are maps that the caller will update with information about 37 // block devices and filesystems created by the source. The caller must not 38 // update the maps during calls to the source's methods. 39 func NewManagedFilesystemSource( 40 volumeBlockDevices map[names.VolumeTag]storage.BlockDevice, 41 filesystems map[names.FilesystemTag]storage.Filesystem, 42 ) storage.FilesystemSource { 43 return &managedFilesystemSource{ 44 logAndExec, 45 &osDirFuncs{logAndExec}, 46 volumeBlockDevices, filesystems, 47 } 48 } 49 50 // ValidateFilesystemParams is defined on storage.FilesystemSource. 51 func (s *managedFilesystemSource) ValidateFilesystemParams(arg storage.FilesystemParams) error { 52 if _, err := s.backingVolumeBlockDevice(arg.Volume); err != nil { 53 return errors.Trace(err) 54 } 55 return nil 56 } 57 58 func (s *managedFilesystemSource) backingVolumeBlockDevice(v names.VolumeTag) (storage.BlockDevice, error) { 59 blockDevice, ok := s.volumeBlockDevices[v] 60 if !ok { 61 return storage.BlockDevice{}, errors.Errorf( 62 "backing-volume %s is not yet attached", v.Id(), 63 ) 64 } 65 return blockDevice, nil 66 } 67 68 // CreateFilesystems is defined on storage.FilesystemSource. 69 func (s *managedFilesystemSource) CreateFilesystems(args []storage.FilesystemParams) ([]storage.Filesystem, error) { 70 filesystems := make([]storage.Filesystem, len(args)) 71 for i, arg := range args { 72 filesystem, err := s.createFilesystem(arg) 73 if err != nil { 74 return nil, errors.Annotatef(err, "creating filesystem %s", arg.Tag.Id()) 75 } 76 filesystems[i] = filesystem 77 } 78 return filesystems, nil 79 } 80 81 func (s *managedFilesystemSource) createFilesystem(arg storage.FilesystemParams) (storage.Filesystem, error) { 82 blockDevice, err := s.backingVolumeBlockDevice(arg.Volume) 83 if err != nil { 84 return storage.Filesystem{}, errors.Trace(err) 85 } 86 devicePath := s.devicePath(blockDevice) 87 if err := createFilesystem(s.run, devicePath); err != nil { 88 return storage.Filesystem{}, errors.Trace(err) 89 } 90 return storage.Filesystem{ 91 arg.Tag, 92 arg.Volume, 93 storage.FilesystemInfo{ 94 arg.Tag.String(), 95 blockDevice.Size, 96 }, 97 }, nil 98 } 99 100 // DestroyFilesystems is defined on storage.FilesystemSource. 101 func (s *managedFilesystemSource) DestroyFilesystems(filesystemIds []string) []error { 102 // DestroyFilesystems is a no-op; there is nothing to destroy, 103 // since the filesystem is just data on a volume. The volume 104 // is destroyed separately. 105 return make([]error, len(filesystemIds)) 106 } 107 108 func (s *managedFilesystemSource) devicePath(dev storage.BlockDevice) string { 109 if dev.DeviceName != "" { 110 return path.Join("/dev", dev.DeviceName) 111 } 112 return path.Join("/dev/disk/by-id", dev.HardwareId) 113 } 114 115 // AttachFilesystems is defined on storage.FilesystemSource. 116 func (s *managedFilesystemSource) AttachFilesystems(args []storage.FilesystemAttachmentParams) ([]storage.FilesystemAttachment, error) { 117 attachments := make([]storage.FilesystemAttachment, len(args)) 118 for i, arg := range args { 119 attachment, err := s.attachFilesystem(arg) 120 if err != nil { 121 return nil, errors.Annotatef(err, "attaching filesystem %s", arg.Filesystem.Id()) 122 } 123 attachments[i] = attachment 124 } 125 return attachments, nil 126 } 127 128 func (s *managedFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (storage.FilesystemAttachment, error) { 129 filesystem, ok := s.filesystems[arg.Filesystem] 130 if !ok { 131 return storage.FilesystemAttachment{}, errors.Errorf("filesystem %v is not yet provisioned", arg.Filesystem.Id()) 132 } 133 blockDevice, err := s.backingVolumeBlockDevice(filesystem.Volume) 134 if err != nil { 135 return storage.FilesystemAttachment{}, errors.Trace(err) 136 } 137 devicePath := s.devicePath(blockDevice) 138 if err := mountFilesystem(s.run, s.dirFuncs, devicePath, arg.Path, arg.ReadOnly); err != nil { 139 return storage.FilesystemAttachment{}, errors.Trace(err) 140 } 141 return storage.FilesystemAttachment{ 142 arg.Filesystem, 143 arg.Machine, 144 storage.FilesystemAttachmentInfo{ 145 arg.Path, 146 arg.ReadOnly, 147 }, 148 }, nil 149 } 150 151 // DetachFilesystems is defined on storage.FilesystemSource. 152 func (s *managedFilesystemSource) DetachFilesystems(args []storage.FilesystemAttachmentParams) error { 153 for _, arg := range args { 154 if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil { 155 return errors.Annotatef(err, "detaching filesystem %s", arg.Filesystem.Id()) 156 } 157 } 158 return nil 159 } 160 161 func createFilesystem(run runCommandFunc, devicePath string) error { 162 logger.Debugf("attempting to create filesystem on %q", devicePath) 163 mkfscmd := "mkfs." + defaultFilesystemType 164 _, err := run(mkfscmd, devicePath) 165 if err != nil { 166 return errors.Annotatef(err, "%s failed (%q)", mkfscmd) 167 } 168 logger.Infof("created filesystem on %q", devicePath) 169 return nil 170 } 171 172 func mountFilesystem(run runCommandFunc, dirFuncs dirFuncs, devicePath, mountPoint string, readOnly bool) error { 173 logger.Debugf("attempting to mount filesystem on %q at %q", devicePath, mountPoint) 174 if err := dirFuncs.mkDirAll(mountPoint, 0755); err != nil { 175 return errors.Annotate(err, "creating mount point") 176 } 177 mounted, mountSource, err := isMounted(dirFuncs, mountPoint) 178 if err != nil { 179 return errors.Trace(err) 180 } 181 if mounted { 182 logger.Debugf("filesystem on %q already mounted at %q", mountSource, mountPoint) 183 return nil 184 } 185 var args []string 186 if readOnly { 187 args = append(args, "-o", "ro") 188 } 189 args = append(args, devicePath, mountPoint) 190 if _, err := run("mount", args...); err != nil { 191 return errors.Annotate(err, "mount failed") 192 } 193 logger.Infof("mounted filesystem on %q at %q", devicePath, mountPoint) 194 return nil 195 } 196 197 func maybeUnmount(run runCommandFunc, dirFuncs dirFuncs, mountPoint string) error { 198 mounted, _, err := isMounted(dirFuncs, mountPoint) 199 if err != nil { 200 return errors.Trace(err) 201 } 202 if !mounted { 203 return nil 204 } 205 logger.Debugf("attempting to unmount filesystem at %q", mountPoint) 206 if _, err := run("umount", mountPoint); err != nil { 207 return errors.Annotate(err, "umount failed") 208 } 209 logger.Infof("unmounted filesystem at %q", mountPoint) 210 return nil 211 } 212 213 func isMounted(dirFuncs dirFuncs, mountPoint string) (bool, string, error) { 214 mountPointParent := filepath.Dir(mountPoint) 215 parentSource, err := dirFuncs.mountPointSource(mountPointParent) 216 if err != nil { 217 return false, "", errors.Trace(err) 218 } 219 source, err := dirFuncs.mountPointSource(mountPoint) 220 if err != nil { 221 return false, "", errors.Trace(err) 222 } 223 if source != parentSource { 224 // Already mounted. 225 return true, source, nil 226 } 227 return false, "", nil 228 }