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