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