github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/storage/volumelist_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage_test 5 6 import ( 7 "encoding/json" 8 "time" 9 10 "github.com/juju/cmd" 11 "github.com/juju/cmd/cmdtesting" 12 "github.com/juju/errors" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 goyaml "gopkg.in/yaml.v2" 16 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/cmd/juju/storage" 19 "github.com/juju/juju/core/status" 20 ) 21 22 func (s *ListSuite) TestVolumeListEmpty(c *gc.C) { 23 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 24 return nil, nil 25 } 26 s.assertValidVolumeList( 27 c, 28 []string{"--format", "yaml"}, 29 "", 30 ) 31 } 32 33 func (s *ListSuite) TestVolumeListError(c *gc.C) { 34 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 35 return nil, errors.New("just my luck") 36 } 37 context, err := s.runVolumeList(c, "--format", "yaml") 38 c.Assert(errors.Cause(err), gc.ErrorMatches, "just my luck") 39 s.assertUserFacingVolumeOutput(c, context, "", "") 40 } 41 42 func (s *ListSuite) TestVolumeListArgs(c *gc.C) { 43 var called bool 44 expectedArgs := []string{"a", "b", "c"} 45 s.mockAPI.listVolumes = func(arg []string) ([]params.VolumeDetailsListResult, error) { 46 c.Assert(arg, jc.DeepEquals, expectedArgs) 47 called = true 48 return nil, nil 49 } 50 s.assertValidVolumeList( 51 c, 52 append([]string{"--format", "yaml"}, expectedArgs...), 53 "", 54 ) 55 c.Assert(called, jc.IsTrue) 56 } 57 58 func (s *ListSuite) TestVolumeListYaml(c *gc.C) { 59 s.assertUnmarshalledVolumeOutput( 60 c, 61 goyaml.Unmarshal, 62 "", // no error 63 "--format", "yaml") 64 } 65 66 func (s *ListSuite) TestVolumeListJSON(c *gc.C) { 67 s.assertUnmarshalledVolumeOutput( 68 c, 69 json.Unmarshal, 70 "", // no error 71 "--format", "json") 72 } 73 74 func (s *ListSuite) TestVolumeListWithErrorResults(c *gc.C) { 75 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 76 var emptyMockAPI mockListAPI 77 results, _ := emptyMockAPI.ListVolumes(nil) 78 results = append(results, params.VolumeDetailsListResult{ 79 Error: ¶ms.Error{Message: "bad"}, 80 }) 81 results = append(results, params.VolumeDetailsListResult{ 82 Error: ¶ms.Error{Message: "ness"}, 83 }) 84 return results, nil 85 } 86 // we should see the error in stderr, but it should not 87 // otherwise affect the rendering of valid results. 88 s.assertUnmarshalledVolumeOutput(c, json.Unmarshal, "bad\nness\n", "--format", "json") 89 s.assertUnmarshalledVolumeOutput(c, goyaml.Unmarshal, "bad\nness\n", "--format", "yaml") 90 } 91 92 var expectedVolumeListTabular = ` 93 Machine Unit Storage id Volume id Provider Id Device Size State Message 94 0 abc/0 db-dir/1001 0/0 provider-supplied-volume-0-0 loop0 512MiB attached 95 0 transcode/0 shared-fs/0 4 provider-supplied-volume-4 xvdf2 1.0GiB attached 96 0 1 provider-supplied-volume-1 2.0GiB attaching failed to attach, will retry 97 1 transcode/1 shared-fs/0 4 provider-supplied-volume-4 xvdf3 1.0GiB attached 98 1 2 provider-supplied-volume-2 xvdf1 3.0MiB attached 99 1 3 42MiB pending 100 101 `[1:] 102 103 func (s *ListSuite) TestVolumeListTabular(c *gc.C) { 104 s.assertValidVolumeList(c, []string{}, expectedVolumeListTabular) 105 106 // Do it again, reversing the results returned by the API. 107 // We should get everything sorted in the appropriate order. 108 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 109 var emptyMockAPI mockListAPI 110 results, _ := emptyMockAPI.ListVolumes(nil) 111 n := len(results) 112 for i := 0; i < n/2; i++ { 113 results[i], results[n-i-1] = results[n-i-1], results[i] 114 } 115 return results, nil 116 } 117 s.assertValidVolumeList(c, []string{}, expectedVolumeListTabular) 118 } 119 120 var expectedCAASVolumeListTabular = ` 121 Unit Storage id Volume id Provider Id Size State Message 122 mysql/0 db-dir/1001 0 provider-supplied-volume-0 512MiB attached 123 124 `[1:] 125 126 func (s *ListSuite) TestCAASVolumeListTabular(c *gc.C) { 127 s.assertValidFilesystemList(c, []string{}, expectedFilesystemListTabular) 128 129 // Do it again, reversing the results returned by the API. 130 // We should get everything sorted in the appropriate order. 131 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 132 results := []params.VolumeDetailsListResult{{Result: []params.VolumeDetails{ 133 { 134 VolumeTag: "volume-0", 135 Info: params.VolumeInfo{ 136 VolumeId: "provider-supplied-volume-0", 137 Size: 512, 138 }, 139 Life: "alive", 140 Status: createTestStatus(status.Attached, "", s.mockAPI.time), 141 UnitAttachments: map[string]params.VolumeAttachmentDetails{ 142 "unit-mysql-0": { 143 Life: "alive", 144 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 145 ReadOnly: true, 146 }, 147 }, 148 }, 149 Storage: ¶ms.StorageDetails{ 150 StorageTag: "storage-db-dir-1001", 151 OwnerTag: "unit-abc-0", 152 Kind: params.StorageKindBlock, 153 Life: "alive", 154 Status: createTestStatus(status.Attached, "", s.mockAPI.time), 155 Attachments: map[string]params.StorageAttachmentDetails{ 156 "unit-mysql-0": { 157 StorageTag: "storage-db-dir-1001", 158 UnitTag: "unit-abc-0", 159 MachineTag: "machine-0", 160 Location: "/mnt/fuji", 161 }, 162 }, 163 }, 164 }, 165 }}} 166 return results, nil 167 } 168 s.assertValidVolumeList(c, []string{}, expectedCAASVolumeListTabular) 169 } 170 171 func (s *ListSuite) assertUnmarshalledVolumeOutput(c *gc.C, unmarshal unmarshaller, expectedErr string, args ...string) { 172 context, err := s.runVolumeList(c, args...) 173 c.Assert(err, jc.ErrorIsNil) 174 175 var result struct { 176 Volumes map[string]storage.VolumeInfo 177 } 178 err = unmarshal([]byte(cmdtesting.Stdout(context)), &result) 179 c.Assert(err, jc.ErrorIsNil) 180 181 expected := s.expectVolume(c, nil) 182 c.Assert(result.Volumes, jc.DeepEquals, expected) 183 184 obtainedErr := cmdtesting.Stderr(context) 185 c.Assert(obtainedErr, gc.Equals, expectedErr) 186 } 187 188 // expect returns the VolumeInfo mapping we should expect to unmarshal 189 // from rendered YAML or JSON. 190 func (s *ListSuite) expectVolume(c *gc.C, machines []string) map[string]storage.VolumeInfo { 191 all, err := s.mockAPI.ListVolumes(machines) 192 c.Assert(err, jc.ErrorIsNil) 193 194 var valid []params.VolumeDetails 195 for _, result := range all { 196 if result.Error == nil { 197 valid = append(valid, result.Result...) 198 } 199 } 200 result, err := storage.ConvertToVolumeInfo(valid) 201 c.Assert(err, jc.ErrorIsNil) 202 return result 203 } 204 205 func (s *ListSuite) assertValidVolumeList(c *gc.C, args []string, expectedOut string) { 206 context, err := s.runVolumeList(c, args...) 207 c.Assert(err, jc.ErrorIsNil) 208 s.assertUserFacingVolumeOutput(c, context, expectedOut, "") 209 } 210 211 func (s *ListSuite) runVolumeList(c *gc.C, args ...string) (*cmd.Context, error) { 212 return cmdtesting.RunCommand(c, 213 storage.NewListCommandForTest(s.mockAPI, s.store), append(args, "--volume")...) 214 } 215 216 func (s *ListSuite) assertUserFacingVolumeOutput(c *gc.C, context *cmd.Context, expectedOut, expectedErr string) { 217 obtainedOut := cmdtesting.Stdout(context) 218 c.Assert(obtainedOut, gc.Equals, expectedOut) 219 220 obtainedErr := cmdtesting.Stderr(context) 221 c.Assert(obtainedErr, gc.Equals, expectedErr) 222 } 223 224 func (s *mockListAPI) ListVolumes(machines []string) ([]params.VolumeDetailsListResult, error) { 225 if s.listVolumes != nil { 226 return s.listVolumes(machines) 227 } 228 results := []params.VolumeDetailsListResult{{Result: []params.VolumeDetails{ 229 // volume 0/0 is attached to machine 0, assigned to 230 // storage db-dir/1001, which is attached to unit 231 // abc/0. 232 { 233 VolumeTag: "volume-0-0", 234 Info: params.VolumeInfo{ 235 VolumeId: "provider-supplied-volume-0-0", 236 Pool: "radiance", 237 Size: 512, 238 }, 239 Life: "alive", 240 Status: createTestStatus(status.Attached, "", s.time), 241 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 242 "machine-0": { 243 Life: "alive", 244 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 245 DeviceName: "loop0", 246 }, 247 }, 248 }, 249 Storage: ¶ms.StorageDetails{ 250 StorageTag: "storage-db-dir-1001", 251 OwnerTag: "unit-abc-0", 252 Kind: params.StorageKindBlock, 253 Life: "alive", 254 Status: createTestStatus(status.Attached, "", s.time), 255 Attachments: map[string]params.StorageAttachmentDetails{ 256 "unit-abc-0": { 257 StorageTag: "storage-db-dir-1001", 258 UnitTag: "unit-abc-0", 259 MachineTag: "machine-0", 260 Location: "/dev/loop0", 261 }, 262 }, 263 }, 264 }, 265 // volume 1 is attaching to machine 0, but is not assigned 266 // to any storage. 267 { 268 VolumeTag: "volume-1", 269 Info: params.VolumeInfo{ 270 VolumeId: "provider-supplied-volume-1", 271 HardwareId: "serial blah blah", 272 Persistent: true, 273 Size: 2048, 274 }, 275 Status: createTestStatus(status.Attaching, "failed to attach, will retry", s.time), 276 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 277 "machine-0": {}, 278 }, 279 }, 280 // volume 3 is due to be attached to machine 1, but is not 281 // assigned to any storage and has not yet been provisioned. 282 { 283 VolumeTag: "volume-3", 284 Info: params.VolumeInfo{ 285 Size: 42, 286 }, 287 Status: createTestStatus(status.Pending, "", s.time), 288 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 289 "machine-1": {}, 290 }, 291 }, 292 // volume 2 is due to be attached to machine 1, but is not 293 // assigned to any storage and has not yet been provisioned. 294 { 295 VolumeTag: "volume-2", 296 Info: params.VolumeInfo{ 297 VolumeId: "provider-supplied-volume-2", 298 Size: 3, 299 }, 300 Status: createTestStatus(status.Attached, "", s.time), 301 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 302 "machine-1": { 303 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 304 DeviceName: "xvdf1", 305 }, 306 }, 307 }, 308 }, 309 // volume 4 is attached to machines 0 and 1, and is assigned 310 // to shared storage. 311 { 312 VolumeTag: "volume-4", 313 Info: params.VolumeInfo{ 314 VolumeId: "provider-supplied-volume-4", 315 Persistent: true, 316 Size: 1024, 317 }, 318 Status: createTestStatus(status.Attached, "", s.time), 319 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 320 "machine-0": { 321 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 322 DeviceName: "xvdf2", 323 ReadOnly: true, 324 }, 325 }, 326 "machine-1": { 327 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 328 DeviceName: "xvdf3", 329 ReadOnly: true, 330 }, 331 }, 332 }, 333 Storage: ¶ms.StorageDetails{ 334 StorageTag: "storage-shared-fs-0", 335 OwnerTag: "application-transcode", 336 Kind: params.StorageKindBlock, 337 Status: createTestStatus(status.Attached, "", s.time), 338 Attachments: map[string]params.StorageAttachmentDetails{ 339 "unit-transcode-0": { 340 StorageTag: "storage-shared-fs-0", 341 UnitTag: "unit-transcode-0", 342 MachineTag: "machine-0", 343 Location: "/mnt/bits", 344 }, 345 "unit-transcode-1": { 346 StorageTag: "storage-shared-fs-0", 347 UnitTag: "unit-transcode-1", 348 MachineTag: "machine-1", 349 Location: "/mnt/pieces", 350 }, 351 }, 352 }, 353 }, 354 }}} 355 if s.omitPool { 356 for _, result := range results { 357 for i, details := range result.Result { 358 details.Info.Pool = "" 359 result.Result[i] = details 360 } 361 } 362 } 363 return results, nil 364 } 365 366 func createTestStatus(testStatus status.Status, message string, since time.Time) params.EntityStatus { 367 return params.EntityStatus{ 368 Status: testStatus, 369 Info: message, 370 Since: &since, 371 } 372 }