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