github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/storage/provider/loop_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider_test 5 6 import ( 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/storage" 17 "github.com/juju/juju/storage/provider" 18 "github.com/juju/juju/testing" 19 ) 20 21 const stubMachineId = "machine101" 22 23 var _ = gc.Suite(&loopSuite{}) 24 25 type loopSuite struct { 26 testing.BaseSuite 27 storageDir string 28 commands *mockRunCommand 29 } 30 31 func (s *loopSuite) SetUpTest(c *gc.C) { 32 s.BaseSuite.SetUpTest(c) 33 s.storageDir = c.MkDir() 34 } 35 36 func (s *loopSuite) TearDownTest(c *gc.C) { 37 s.commands.assertDrained() 38 s.BaseSuite.TearDownTest(c) 39 } 40 41 func (s *loopSuite) loopProvider(c *gc.C) storage.Provider { 42 s.commands = &mockRunCommand{c: c} 43 return provider.LoopProvider(s.commands.run) 44 } 45 46 func (s *loopSuite) TestVolumeSource(c *gc.C) { 47 p := s.loopProvider(c) 48 cfg, err := storage.NewConfig("name", provider.LoopProviderType, map[string]interface{}{}) 49 c.Assert(err, jc.ErrorIsNil) 50 _, err = p.VolumeSource(nil, cfg) 51 c.Assert(err, gc.ErrorMatches, "storage directory not specified") 52 cfg, err = storage.NewConfig("name", provider.LoopProviderType, map[string]interface{}{ 53 "storage-dir": c.MkDir(), 54 }) 55 c.Assert(err, jc.ErrorIsNil) 56 _, err = p.VolumeSource(nil, cfg) 57 c.Assert(err, jc.ErrorIsNil) 58 } 59 60 func (s *loopSuite) TestValidateConfig(c *gc.C) { 61 p := s.loopProvider(c) 62 cfg, err := storage.NewConfig("name", provider.LoopProviderType, map[string]interface{}{}) 63 c.Assert(err, jc.ErrorIsNil) 64 err = p.ValidateConfig(cfg) 65 // The loop provider does not have any user 66 // configuration, so an empty map will pass. 67 c.Assert(err, jc.ErrorIsNil) 68 } 69 70 func (s *loopSuite) TestSupports(c *gc.C) { 71 p := s.loopProvider(c) 72 c.Assert(p.Supports(storage.StorageKindBlock), jc.IsTrue) 73 c.Assert(p.Supports(storage.StorageKindFilesystem), jc.IsFalse) 74 } 75 76 func (s *loopSuite) TestScope(c *gc.C) { 77 p := s.loopProvider(c) 78 c.Assert(p.Scope(), gc.Equals, storage.ScopeMachine) 79 } 80 81 func (s *loopSuite) loopVolumeSource(c *gc.C) (storage.VolumeSource, *provider.MockDirFuncs) { 82 s.commands = &mockRunCommand{c: c} 83 return provider.LoopVolumeSource( 84 s.storageDir, 85 s.commands.run, 86 ) 87 } 88 89 func (s *loopSuite) TestCreateVolumes(c *gc.C) { 90 source, _ := s.loopVolumeSource(c) 91 s.commands.expect("fallocate", "-l", "2MiB", filepath.Join(s.storageDir, "volume-0")) 92 93 results, err := source.CreateVolumes([]storage.VolumeParams{{ 94 Tag: names.NewVolumeTag("0"), 95 Size: 2, 96 Attachment: &storage.VolumeAttachmentParams{ 97 AttachmentParams: storage.AttachmentParams{ 98 Machine: names.NewMachineTag("1"), 99 InstanceId: "instance-id", 100 }, 101 }, 102 }}) 103 c.Assert(err, jc.ErrorIsNil) 104 c.Assert(results, gc.HasLen, 1) 105 c.Assert(results[0].Error, jc.ErrorIsNil) 106 // volume attachments always deferred to AttachVolumes 107 c.Assert(results[0].VolumeAttachment, gc.IsNil) 108 c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{ 109 names.NewVolumeTag("0"), 110 storage.VolumeInfo{ 111 VolumeId: "volume-0", 112 Size: 2, 113 }, 114 }) 115 } 116 117 func (s *loopSuite) TestCreateVolumesNoAttachment(c *gc.C) { 118 source, _ := s.loopVolumeSource(c) 119 s.commands.expect("fallocate", "-l", "2MiB", filepath.Join(s.storageDir, "volume-0")) 120 _, err := source.CreateVolumes([]storage.VolumeParams{{ 121 Tag: names.NewVolumeTag("0"), 122 Size: 2, 123 }}) 124 // loop volumes may be created without attachments 125 c.Assert(err, jc.ErrorIsNil) 126 } 127 128 func (s *loopSuite) TestDestroyVolumes(c *gc.C) { 129 source, _ := s.loopVolumeSource(c) 130 fileName := filepath.Join(s.storageDir, "volume-0") 131 132 err := ioutil.WriteFile(fileName, nil, 0644) 133 c.Assert(err, jc.ErrorIsNil) 134 135 errs, err := source.DestroyVolumes([]string{"volume-0"}) 136 c.Assert(err, jc.ErrorIsNil) 137 c.Assert(errs, gc.HasLen, 1) 138 c.Assert(errs[0], jc.ErrorIsNil) 139 140 _, err = os.Stat(fileName) 141 c.Assert(err, jc.Satisfies, os.IsNotExist) 142 } 143 144 func (s *loopSuite) TestDestroyVolumesInvalidVolumeId(c *gc.C) { 145 source, _ := s.loopVolumeSource(c) 146 errs, err := source.DestroyVolumes([]string{"../super/important/stuff"}) 147 c.Assert(err, jc.ErrorIsNil) 148 c.Assert(errs, gc.HasLen, 1) 149 c.Assert(errs[0], gc.ErrorMatches, `.* invalid loop volume ID "\.\./super/important/stuff"`) 150 } 151 152 func (s *loopSuite) TestDescribeVolumes(c *gc.C) { 153 source, _ := s.loopVolumeSource(c) 154 _, err := source.DescribeVolumes([]string{"a", "b"}) 155 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 156 } 157 158 func (s *loopSuite) TestAttachVolumes(c *gc.C) { 159 source, _ := s.loopVolumeSource(c) 160 cmd := s.commands.expect("losetup", "-j", filepath.Join(s.storageDir, "volume-0")) 161 cmd.respond("", nil) // no existing attachment 162 cmd = s.commands.expect("losetup", "-f", "--show", filepath.Join(s.storageDir, "volume-0")) 163 cmd.respond("/dev/loop98", nil) // first available loop device 164 cmd = s.commands.expect("losetup", "-j", filepath.Join(s.storageDir, "volume-1")) 165 cmd.respond("", nil) // no existing attachment 166 cmd = s.commands.expect("losetup", "-f", "--show", "-r", filepath.Join(s.storageDir, "volume-1")) 167 cmd.respond("/dev/loop99", nil) 168 cmd = s.commands.expect("losetup", "-j", filepath.Join(s.storageDir, "volume-2")) 169 cmd.respond("/dev/loop42: foo\n/dev/loop1: foo\n", nil) // existing attachments 170 171 results, err := source.AttachVolumes([]storage.VolumeAttachmentParams{{ 172 Volume: names.NewVolumeTag("0"), 173 VolumeId: "vol-ume0", 174 AttachmentParams: storage.AttachmentParams{ 175 Machine: names.NewMachineTag("0"), 176 InstanceId: "inst-ance", 177 }, 178 }, { 179 Volume: names.NewVolumeTag("1"), 180 VolumeId: "vol-ume1", 181 AttachmentParams: storage.AttachmentParams{ 182 Machine: names.NewMachineTag("0"), 183 InstanceId: "inst-ance", 184 ReadOnly: true, 185 }, 186 }, { 187 Volume: names.NewVolumeTag("2"), 188 VolumeId: "vol-ume2", 189 AttachmentParams: storage.AttachmentParams{ 190 Machine: names.NewMachineTag("0"), 191 InstanceId: "inst-ance", 192 }, 193 }}) 194 c.Assert(err, jc.ErrorIsNil) 195 c.Assert(results, jc.DeepEquals, []storage.AttachVolumesResult{{ 196 VolumeAttachment: &storage.VolumeAttachment{names.NewVolumeTag("0"), 197 names.NewMachineTag("0"), 198 storage.VolumeAttachmentInfo{ 199 DeviceName: "loop98", 200 }, 201 }, 202 }, { 203 VolumeAttachment: &storage.VolumeAttachment{names.NewVolumeTag("1"), 204 names.NewMachineTag("0"), 205 storage.VolumeAttachmentInfo{ 206 DeviceName: "loop99", 207 ReadOnly: true, 208 }, 209 }, 210 }, { 211 VolumeAttachment: &storage.VolumeAttachment{names.NewVolumeTag("2"), 212 names.NewMachineTag("0"), 213 storage.VolumeAttachmentInfo{ 214 DeviceName: "loop42", 215 }, 216 }, 217 }}) 218 } 219 220 func (s *loopSuite) TestDetachVolumes(c *gc.C) { 221 source, _ := s.loopVolumeSource(c) 222 fileName := filepath.Join(s.storageDir, "volume-0") 223 cmd := s.commands.expect("losetup", "-j", fileName) 224 cmd.respond("/dev/loop0: foo\n/dev/loop1: bar\n", nil) 225 s.commands.expect("losetup", "-d", "/dev/loop0") 226 s.commands.expect("losetup", "-d", "/dev/loop1") 227 228 err := ioutil.WriteFile(fileName, nil, 0644) 229 c.Assert(err, jc.ErrorIsNil) 230 231 errs, err := source.DetachVolumes([]storage.VolumeAttachmentParams{{ 232 Volume: names.NewVolumeTag("0"), 233 VolumeId: "vol-ume0", 234 AttachmentParams: storage.AttachmentParams{ 235 Machine: names.NewMachineTag("0"), 236 InstanceId: "inst-ance", 237 }, 238 }}) 239 c.Assert(err, jc.ErrorIsNil) 240 c.Assert(errs, gc.HasLen, 1) 241 c.Assert(errs[0], jc.ErrorIsNil) 242 243 // file should not have been removed 244 _, err = os.Stat(fileName) 245 c.Assert(err, jc.ErrorIsNil) 246 } 247 248 func (s *loopSuite) TestDetachVolumesDetachFails(c *gc.C) { 249 source, _ := s.loopVolumeSource(c) 250 fileName := filepath.Join(s.storageDir, "volume-0") 251 cmd := s.commands.expect("losetup", "-j", fileName) 252 cmd.respond("/dev/loop0: foo\n/dev/loop1: bar\n", nil) 253 cmd = s.commands.expect("losetup", "-d", "/dev/loop0") 254 cmd.respond("", errors.New("oy")) 255 256 err := ioutil.WriteFile(fileName, nil, 0644) 257 c.Assert(err, jc.ErrorIsNil) 258 259 errs, err := source.DetachVolumes([]storage.VolumeAttachmentParams{{ 260 Volume: names.NewVolumeTag("0"), 261 VolumeId: "vol-ume0", 262 AttachmentParams: storage.AttachmentParams{ 263 Machine: names.NewMachineTag("0"), 264 InstanceId: "inst-ance", 265 }, 266 }}) 267 c.Assert(err, jc.ErrorIsNil) 268 c.Assert(errs, gc.HasLen, 1) 269 c.Assert(errs[0], gc.ErrorMatches, `.* detaching loop device "loop0": oy`) 270 271 // file should not have been removed 272 _, err = os.Stat(fileName) 273 c.Assert(err, jc.ErrorIsNil) 274 }